- meow_svc_power: 单一二进制(svcd空参IPC常驻 /usr/data/meow_power.sock + root CLI免鉴权), 12命令(shutdown/reboot强制=0/upgrade/set-default-hiby/reboot--hiby + set-default-meow/status/screen/dac(status仅)/battery/reboot-reason/upgrade-status); 动作同源sys_do_reboot/set_boot_default_hiby/mtd5=ota:kernel2; dac on/off无控制点不支持 - meow_svc_perf: IPC(/usr/data/meow_perf.sock MSG_PERF_*) + CLI, 聚合/proc/df/thermal/version/uname JSON(与MEOWPANEL_PROTOCOL对齐) - power_menu.c 改经 meow_power_action(launcher先转储再IPC), 删sys_do_reboot/set_boot_default_hiby; 4动作+8s强关+confirm_reboot_player+3处长按强关全经服务; launcher内 sys_do_reboot/bootmode.sh/set_boot_default_hiby 0调用 - meow_ipc.h 仅加 MSG_PERF_QUERY/RESULT(0x50/51)+MSG_POWER_ACTION/STATUS(0x60/61)+POWER_*(12)+m_perf/power_t; 未动既有 - S89meow_svcd 预置 meow_svc_perf/power.conf(trigger=boot); 各独立socket - IPC鉴权一期=SO_PEERCRED uid==0基础(完整os.meow.*二期); 通知转储=launcher先转储再IPC(二期补launcher→服务通道); 失败安全(服务不可达→按取消,不静默不直连) - meow_svc_power=32d6be92/meow_svc_perf=7b0f3b64/launcher=d90f09e9/svcd=fb2d608b(未动)/meow_ui.a=92e61fb8(未改); 全0warning; 备份power_menu.c.pre-svc.bak回退
1323 lines
74 KiB
C
1323 lines
74 KiB
C
/* settings_page.c — MeowOS launcher 拆分模块(LAUNCHER_SPLIT_PLAN 拆分)。*/
|
||
/* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/
|
||
#include "meow_launcher_internal.h"
|
||
|
||
|
||
/* SC_PRESET 预设表(路径/枚举字符串的"预设项,自定义文件选择留后期") */
|
||
static const char *const bg_presets[] = { "black", "blue", "green" };
|
||
|
||
static const char *const bg_preset_labels[] = { "黑色", "深蓝", "深绿" };
|
||
|
||
static const char *const hk_presets[] = { "power_voldown", "seq" };
|
||
|
||
static const char *const hk_preset_labels[] = { "电源+音量减", "连按序列" };
|
||
|
||
static const char *const seq_presets[] = { "volup,volup,volup", "voldown,voldown,voldown" };
|
||
|
||
static const char *const seq_preset_labels[] = { "音量上×3", "音量下×3" };
|
||
|
||
/* font.file 行(字体文件)不使用编译期静态表:其 presets/preset_labels 指向字体区
|
||
* (font_presets/font_preset_labels, 见本文件字体区) 的运行时动态数组,计数由
|
||
* s_row_presets() 动态返回(默认 1,进设置页扫描 TF 卡后更新)。见 SMENU 行。 */
|
||
static const char *const out_presets[] = { "hw:0,0" };
|
||
|
||
static const char *const out_preset_labels[] = { "CS43198 (hw:0,0)" };
|
||
|
||
|
||
/* M2.37:息屏时间滑条改为「离散档位」而非线性 0~86400 秒(线性步进 1 根本调不到 30s,
|
||
* 上限 24h 过于抽象)。档位 0=常亮;从 5s 起步的细粒度段可精调「30s 左右」,随后按
|
||
* 45s/分/刻/时 跳档,最高 2 时。滑条值 = 档位索引(0..S_TIME_TIERS_N-1,步进 1),
|
||
* 落盘仍写「秒」(display.screen_off_timeout);索引<->秒双向映射见 s_time_tier_*。 */
|
||
static const int s_time_tiers[] = {
|
||
0, /* 常亮 */
|
||
5, 10, 15, 20, /* 细粒度段:可精确到 30s 附近 */
|
||
30, 45, /* 半分钟/三刻钟 */
|
||
60, 90, 120, 180, /* 1 分 / 1.5 分 / 2 分 / 3 分 */
|
||
300, 600, 900, /* 5 分 / 10 分 / 15 分 */
|
||
1800, /* 30 分 */
|
||
3600, 7200 /* 1 时 / 2 时 */
|
||
};
|
||
|
||
|
||
/* 设置页扁平化菜单(分组=组标题行,其余=设置项)。顺序即显示顺序。 */
|
||
/* 分组/键/类型/默认与 SYSTEM_SETTINGS.md §1 一致(新建项四端同步)。 */
|
||
static const srow_t SMENU[] = {
|
||
/* 显示 */
|
||
{ "显示", NULL, SC_HEADER, 0,0,0, NULL,NULL,0 },
|
||
{ "亮度", "display.brightness", SC_SLIDER, 1,100,1, NULL,NULL,0 },
|
||
{ "息屏模式", "display.screen_off_mode", SC_ENUM, 0,0,0, NULL,NULL,0 },
|
||
{ "息屏时间", "display.screen_off_timeout", SC_SLIDER, 0,(S_TIME_TIERS_N-1),1, NULL,NULL,0 },
|
||
{ "刷新率", "display.refresh_rate", SC_ENUM, 0,0,0, NULL,NULL,0 },
|
||
{ "最高刷新率", "display.refresh_always_max", SC_SWITCH, 0,0,0, NULL,NULL,0 },
|
||
/* B2b 主题三方式:标题行「主题」改选 display.theme_mode(手动/时段自动),
|
||
* 手动深浅由下方「主题深浅」(display.theme) 控制;时段两键为分钟滑条(STEP=15)。 */
|
||
{ "主题", "display.theme_mode", SC_ENUM, 0,0,0, NULL,NULL,0 },
|
||
{ "主题深浅", "display.theme", SC_ENUM, 0,0,0, NULL,NULL,0 },
|
||
{ "深色时段开始", "display.theme_dark_start", SC_SLIDER, 0,1439,15, NULL,NULL,0 },
|
||
{ "深色时段结束", "display.theme_dark_end", SC_SLIDER, 0,1439,15, NULL,NULL,0 },
|
||
{ "背景", "display.background", SC_PRESET, 0,0,0, bg_presets, bg_preset_labels, BG_PRESETS_N },
|
||
/* 网络 */
|
||
{ "网络", NULL, SC_HEADER, 0,0,0, NULL,NULL,0 },
|
||
{ "WiFi", "network.wifi_enabled", SC_SWITCH, 0,0,0, NULL,NULL,0 },
|
||
{ "WiFi连接", NULL, SC_LIST, 0,0,0, NULL,NULL,0, NET_LIST_WIFI_SSID },
|
||
{ "已保存网络", NULL, SC_LIST, 0,0,0, NULL,NULL,0, NET_LIST_WIFI_SAVED },
|
||
{ "附近WiFi", NULL, SC_LIST, 0,0,0, NULL,NULL,0, NET_LIST_WIFI_SCAN },
|
||
{ "蓝牙", "network.bluetooth_enabled", SC_SWITCH, 0,0,0, NULL,NULL,0 },
|
||
{ "蓝牙模式", NULL, SC_INFO, 0,0,0, NULL,NULL,0, NET_LIST_BT_MODE },
|
||
{ "蓝牙设备", NULL, SC_LIST, 0,0,0, NULL,NULL,0, NET_LIST_BT_NEARBY },
|
||
/* 电源 */
|
||
{ "电源", NULL, SC_HEADER, 0,0,0, NULL,NULL,0 },
|
||
{ "长按弹菜单", "power.longpress_menu_ms", SC_SLIDER, 500,5000,500, NULL,NULL,0 },
|
||
{ "长按强关", "power.forceoff_ms", SC_SLIDER, 5500,15000,500, NULL,NULL,0 },
|
||
{ "退出热键", "power.defeat_hotkey", SC_PRESET, 0,0,0, hk_presets, hk_preset_labels, HK_PRESETS_N },
|
||
/* 开机 */
|
||
{ "开机", NULL, SC_HEADER, 0,0,0, NULL,NULL,0 },
|
||
{ "默认系统", "boot.default", SC_ENUM, 0,0,0, NULL,NULL,0 },
|
||
{ "显示选单", "boot.menu_enabled", SC_SWITCH, 0,0,0, NULL,NULL,0 },
|
||
{ "选单超时", "boot.timeout_ms", SC_SLIDER, 0,30000,500, NULL,NULL,0 },
|
||
/* 输入 */
|
||
{ "输入", NULL, SC_HEADER, 0,0,0, NULL,NULL,0 },
|
||
{ "热键序列", "input.hotkey_seq", SC_PRESET, 0,0,0, seq_presets, seq_preset_labels, SEQ_PRESETS_N },
|
||
{ "热键窗口", "input.hotkey_window_ms", SC_SLIDER, 0,5000,100, NULL,NULL,0 },
|
||
{ "触摸布防", "input.touch_arm_delay_ms", SC_SLIDER, 0,2000,50, NULL,NULL,0 },
|
||
{ "滑动方向", "input.swipe_invert", SC_SWITCH, 0,0,0, NULL,NULL,0 },
|
||
/* 字体主题 */
|
||
{ "字体主题", NULL, SC_HEADER, 0,0,0, NULL,NULL,0 },
|
||
{ "字体文件", "font.file", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 },
|
||
/* M2.5x 字体档位分层/按需大字库:「加载大字库」= font.load_full(int 0/1 默认 0)。
|
||
* 开 → 强制全系统用大字库 gbk≈20k 档(覆盖 app 未声明/覆盖默认 font.file);
|
||
* 关(默认)→ 小字库 common≈4k,仅 app 声明 fonts:"full" 才大字库。 */
|
||
{ "加载大字库", "font.load_full", SC_SWITCH, 0,0,0, NULL,NULL,0 },
|
||
/* M2.5x 多字体:开关 + 6 个字符类别字体(preset=可用 MeowOS/fonts/<family>/<weight>/ 列表;
|
||
* 值空 = 跟随全局 font.file)。仅开关开启时 6 行可见(s_row_visible)。 */
|
||
{ "多字体", "display.multi_font", SC_SWITCH, 0,0,0, NULL,NULL,0 },
|
||
{ "中文(CJK)", "font.class_cjk", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 },
|
||
{ "中文标点", "font.class_cjk_sym", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 },
|
||
{ "数字", "font.class_num", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 },
|
||
{ "拉丁字母", "font.class_latin", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 },
|
||
{ "半角标点", "font.class_latin_sym", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 },
|
||
{ "其他字符", "font.class_other", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 },
|
||
{ "标题字号", "font.size.title", SC_SLIDER, 16,40,1, NULL,NULL,0 },
|
||
{ "正文字号", "font.size.body", SC_SLIDER, 12,28,1, NULL,NULL,0 },
|
||
{ "UI风格", "ui.style", SC_ENUM, 0,0,0, NULL,NULL,0 },
|
||
/* 通知 */
|
||
{ "通知", NULL, SC_HEADER, 0,0,0, NULL,NULL,0 },
|
||
{ "通知中心", "notify.center_enabled", SC_SWITCH, 0,0,0, NULL,NULL,0 },
|
||
{ "应用配额", "notify.app_quota", SC_SLIDER, 1,64,1, NULL,NULL,0 },
|
||
/* 日志 */
|
||
{ "日志", NULL, SC_HEADER, 0,0,0, NULL,NULL,0 },
|
||
{ "日志级别", "log.level", SC_ENUM, 0,0,0, NULL,NULL,0 },
|
||
{ "写入TF卡", "log.to_card", SC_SWITCH, 0,0,0, NULL,NULL,0 },
|
||
/* 音频 */
|
||
{ "音频", NULL, SC_HEADER, 0,0,0, NULL,NULL,0 },
|
||
{ "音量", "audio.volume", SC_SLIDER, 0,100,1, NULL,NULL,0 },
|
||
{ "音量上限", "audio.volume_max", SC_SLIDER, 0,100,1, NULL,NULL,0 },
|
||
{ "音量跳步", "audio.volume_step", SC_SLIDER, 1,5,1, NULL,NULL,0 },
|
||
{ "输出设备", "audio.output", SC_PRESET, 0,0,0, out_presets, out_preset_labels, OUT_PRESETS_N },
|
||
/* 关于(设置列表最底;「关于本机」点开 s_about_open 弹只读设备信息框) */
|
||
{ "关于", NULL, SC_HEADER, 0,0,0, NULL,NULL,0 },
|
||
{ "关于本机", NULL, SC_LIST, 0,0,0, NULL,NULL,0, NET_LIST_ABOUT },
|
||
};
|
||
|
||
/* M2.39 左缘返回手势已抽为共享函数 meow_edge_back()(meow_touch.h MEOW_EDGE_W/SWIPE 由
|
||
* 其定义),设置页运行期调用;此处不再定义独立 S_EDGE_W/S_SWIPE_THRESH。 */
|
||
|
||
/* 当前选中行若是滑条:记录其 track 纵向位置,供触摸拖动命中判(x/w 为布局常量,
|
||
* 所有滑条行相同,可直接算;仅 y 随行变,故独立记录)。 */
|
||
static int s_sel_track_y = -1;
|
||
|
||
|
||
/* ---- 行高/位置/导航辅助 ---- */
|
||
/* M2.5x 多字体:6 个「字体文件」行仅在 display.multi_font=1 时可见(开时显示)。
|
||
* 可见性与分组的标题行无关(标题恒显示)。 */
|
||
static int s_row_visible(int i)
|
||
{
|
||
const srow_t *r = &SMENU[i];
|
||
if (r->key && !strncmp(r->key, "font.class_", 11)) {
|
||
int on = 0; meow_settings_get_switch("display.multi_font", &on);
|
||
return on;
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
/* 可见行数(= 压缩后占用的行数;标题行计入,隐藏的 class 行不计) */
|
||
static int s_vis_count(void)
|
||
{
|
||
int n = 0;
|
||
for (int i = 0; i < SMENU_N; i++) if (s_row_visible(i)) n++;
|
||
return n;
|
||
}
|
||
|
||
/* SMENU 下标 → 压缩位置(第几个可见行);不可见返回 -1 */
|
||
static int s_vis_of(int i)
|
||
{
|
||
if (i < 0 || i >= SMENU_N || !s_row_visible(i)) return -1;
|
||
int p = 0;
|
||
for (int k = 0; k < i; k++) if (s_row_visible(k)) p++;
|
||
return p;
|
||
}
|
||
|
||
/* 压缩位置 → SMENU 下标 */
|
||
static int s_vis_at(int p)
|
||
{
|
||
int c = 0;
|
||
for (int i = 0; i < SMENU_N; i++) if (s_row_visible(i)) { if (c == p) return i; c++; }
|
||
return SMENU_N - 1;
|
||
}
|
||
|
||
|
||
static int s_setting_idx(int i) { return i >= 0 && i < SMENU_N && SMENU[i].ctrl != SC_HEADER && s_row_visible(i); }
|
||
|
||
static int s_first_setting(void)
|
||
{
|
||
for (int i = 0; i < SMENU_N; i++) if (s_setting_idx(i)) return i;
|
||
return 0;
|
||
}
|
||
|
||
static int s_next_setting(int i)
|
||
{
|
||
for (int k = i + 1; k < SMENU_N; k++) if (s_setting_idx(k)) return k;
|
||
return s_first_setting(); /* 回绕 */
|
||
}
|
||
|
||
|
||
/* 字体行(font.file + font.class_*)走「family→字重」两级子菜单(M2.6 字重结构) */
|
||
static int s_row_is_font(const srow_t *r)
|
||
{
|
||
return r && r->key && (!strcmp(r->key, "font.file") || !strncmp(r->key, "font.class_", 11));
|
||
}
|
||
|
||
|
||
/* SC_PRESET 行取「选项表 + 数量」:字体行(font.file + font.class_*)用运行时动态数组
|
||
* (TF 卡 MeowOS/fonts/<family>/<weight>/ 扫描结果,见字体区 font_presets/font_presets_n),
|
||
* 其余行用编译期静态表(r->presets / r->preset_n)。 */
|
||
static const char *const *s_row_presets(const srow_t *r, int *n)
|
||
{
|
||
if (r && r->key && (!strcmp(r->key, "font.file") || !strncmp(r->key, "font.class_", 11))) {
|
||
if (n) *n = font_presets_n;
|
||
return font_presets;
|
||
}
|
||
if (n) *n = r->preset_n;
|
||
return r->presets;
|
||
}
|
||
|
||
|
||
/* 滚动:纯范围钳制到 [0, 内容高度-视口高](列表拖动滚动用;不强制选中行可见) */
|
||
static int s_clamp_scroll_range(int scroll)
|
||
{
|
||
int total = s_vis_count() * SROW_H;
|
||
if (total <= SET_CONTENT_H) return 0;
|
||
int max_scroll = total - SET_CONTENT_H;
|
||
if (scroll < 0) scroll = 0;
|
||
if (scroll > max_scroll) scroll = max_scroll;
|
||
return scroll;
|
||
}
|
||
|
||
|
||
/* 滚动:保证当前选中行在 [SET_CONTENT_Y, SET_CONTENT_BOT] 内 */
|
||
static int s_clamp_scroll(int sel, int scroll)
|
||
{
|
||
scroll = s_clamp_scroll_range(scroll);
|
||
int pos = s_vis_of(sel);
|
||
if (pos < 0) pos = s_vis_of(s_first_setting());
|
||
int top = pos * SROW_H;
|
||
int bot = top + SROW_H;
|
||
if (top < scroll) scroll = top; /* 选中行滑出顶部 → 上滚 */
|
||
if (bot > scroll + SET_CONTENT_H) scroll = bot - SET_CONTENT_H; /* 滑出底部 → 下滚 */
|
||
return s_clamp_scroll_range(scroll);
|
||
}
|
||
|
||
|
||
/* 行 y 落在 content 坐标 cy 时对应的行号(压缩位置 → SMENU 下标);越界返回 -1 */
|
||
static int s_row_at_content_y(int cy)
|
||
{
|
||
if (cy < 0) return -1;
|
||
int pos = cy / SROW_H;
|
||
if (pos >= s_vis_count()) return -1;
|
||
return s_vis_at(pos);
|
||
}
|
||
|
||
|
||
/* ---- 值格式化/展示 ---- */
|
||
static void s_fmt_time(int sec, char *buf, int len)
|
||
{
|
||
if (sec <= 0) { snprintf(buf, len, "常亮"); return; }
|
||
if (sec < 60) { snprintf(buf, len, "%d 秒", sec); return; }
|
||
if (sec < 3600) { snprintf(buf, len, "%d 分", sec / 60); return; }
|
||
snprintf(buf, len, "%d 时", sec / 3600);
|
||
}
|
||
|
||
/* B2b 主题时段:分钟(0-1439) → "HH:MM"(如 1140→"19:00")。注意与 s_fmt_time 的秒语义区分。 */
|
||
static void s_fmt_minutes(int mins, char *buf, int len)
|
||
{
|
||
if (mins < 0) mins = 0;
|
||
if (mins > 1439) mins = 1439;
|
||
snprintf(buf, len, "%02d:%02d", mins / 60, mins % 60);
|
||
}
|
||
|
||
/* 息屏时间:秒 → 最近档位索引(显示滑动位置用;等值优先,跨档取较近档,平手取高档) */
|
||
static int s_time_tier_idx(int sec)
|
||
{
|
||
if (sec <= s_time_tiers[0]) return 0;
|
||
for (int i = 1; i < S_TIME_TIERS_N; i++) {
|
||
if (sec == s_time_tiers[i]) return i;
|
||
if (sec < s_time_tiers[i]) {
|
||
int lo = s_time_tiers[i - 1], hi = s_time_tiers[i];
|
||
return (sec - lo < hi - sec) ? i - 1 : i;
|
||
}
|
||
}
|
||
return S_TIME_TIERS_N - 1;
|
||
}
|
||
|
||
/* 息屏时间:档位索引 → 秒(落盘写给 display.screen_off_timeout 的实值) */
|
||
static int s_time_tier_val(int idx)
|
||
{
|
||
if (idx < 0) idx = 0;
|
||
if (idx >= S_TIME_TIERS_N) idx = S_TIME_TIERS_N - 1;
|
||
return s_time_tiers[idx];
|
||
}
|
||
|
||
static void s_fmt_value(const srow_t *r, char *buf, int len)
|
||
{
|
||
int v = 0;
|
||
if (meow_settings_get_int(r->key, &v) != 0) { snprintf(buf, len, "-"); return; }
|
||
if (!strcmp(r->key, "display.screen_off_timeout")) { s_fmt_time(v, buf, len); return; }
|
||
/* B2b 深色时段(分钟 0-1439 → "HH:MM");start==end 视为未配置,显示「未设置」 */
|
||
if (!strcmp(r->key, "display.theme_dark_start") || !strcmp(r->key, "display.theme_dark_end")) {
|
||
int s2 = 0, e2 = 0;
|
||
meow_settings_get_int("display.theme_dark_start", &s2);
|
||
meow_settings_get_int("display.theme_dark_end", &e2);
|
||
if (s2 == e2) { snprintf(buf, len, "未设置"); return; }
|
||
s_fmt_minutes(v, buf, len);
|
||
return;
|
||
}
|
||
/* 长按弹菜单/强关:毫秒换算成秒(0.5 步进,如 "2.5 秒") */
|
||
if (!strcmp(r->key, "power.longpress_menu_ms") || !strcmp(r->key, "power.forceoff_ms")) {
|
||
if (v % 1000 == 0) snprintf(buf, len, "%d 秒", v / 1000);
|
||
else snprintf(buf, len, "%.1f 秒", v / 1000.0);
|
||
return;
|
||
}
|
||
snprintf(buf, len, "%d", v);
|
||
}
|
||
|
||
static void s_display_value(const srow_t *r, char *buf, int len)
|
||
{
|
||
if (r->ctrl == SC_ENUM) {
|
||
int idx = 0;
|
||
if (meow_settings_get_enum(r->key, &idx) != 0) idx = 0;
|
||
if (meow_settings_enum_label(r->key, idx, buf, len) != 0) snprintf(buf, len, "-");
|
||
return;
|
||
}
|
||
/* SC_PRESET */
|
||
char cur[MEOW_SET_MAX_LEN];
|
||
if (meow_settings_get_str(r->key, cur, sizeof cur) != 0) { snprintf(buf, len, "-"); return; }
|
||
/* M2.5x 多字体:class 行空值 = 跟随全局 font.file */
|
||
if (r->key && !strncmp(r->key, "font.class_", 11) && !cur[0]) {
|
||
snprintf(buf, len, "跟随全局");
|
||
return;
|
||
}
|
||
int pn = 0;
|
||
const char *const *pps = s_row_presets(r, &pn);
|
||
for (int i = 0; i < pn; i++) {
|
||
if (!strcmp(pps[i], cur)) {
|
||
snprintf(buf, len, "%s", (r->preset_labels && r->preset_labels[i][0]) ? r->preset_labels[i] : cur);
|
||
return;
|
||
}
|
||
}
|
||
snprintf(buf, len, "%s", cur);
|
||
}
|
||
|
||
|
||
|
||
/* ---- 控件绘制(MD3 形态;全部用主题角色色,无散落硬编码)---- */
|
||
void s_draw_switch(int x, int y, int on, const meow_ui_theme_t *th)
|
||
{
|
||
int w = 48, h = 28;
|
||
meow_gfx_fill_round(x, y, w, h, h / 2, on ? th->primary : th->surface_container);
|
||
int knob_r = h / 2 - 3;
|
||
int kx = on ? x + w - knob_r * 2 - 3 : x + 3;
|
||
meow_gfx_fill_round(kx, y + 3, knob_r * 2, knob_r * 2, knob_r,
|
||
on ? 0xFFFFFF : th->on_surface_variant);
|
||
}
|
||
|
||
void s_draw_slider(int x, int y, int w, int val, int mn, int mx,
|
||
const meow_ui_theme_t *th)
|
||
{
|
||
int h = 6, ty = y;
|
||
fill_rect(x, ty, w, h, th->surface_container); /* 未达段 */
|
||
int range = mx - mn;
|
||
int frac = range ? (val - mn) * w / range : 0;
|
||
if (frac > 0) fill_rect(x, ty, frac, h, th->primary); /* 已达段 */
|
||
int tx = x + frac, cy = ty + h / 2;
|
||
meow_gfx_fill_round(tx - 12, cy - 12, 24, 24, 12, th->primary); /* thumb */
|
||
meow_gfx_fill_round(tx - 5, cy - 5, 10, 10, 5, 0xFFFFFF); /* thumb 中点 */
|
||
}
|
||
|
||
|
||
/* ---- set 后统一侧效应(DAC/背光/刷新率)+ 可选落盘 ---- */
|
||
static void s_commit(const srow_t *r, int save_now)
|
||
{
|
||
if (!r || !r->key) return;
|
||
if (!strcmp(r->key, "audio.volume")) {
|
||
/* M2.38 Bug1/2:音量变更——拖动中节流写 DAC(防 system() 高频卡死),抬手才无条件直写;
|
||
* 同时把 g_sys.vol 同步为当前值并刷整条状态栏(顶层 VOL 数字随之更新,不整屏重绘)。 */
|
||
g_sys.vol = settings_volume_get();
|
||
paint_sb_right();
|
||
if (save_now) meow_dac_vol_flush(); /* 抬手/点击落盘=无条件直写真实 DAC */
|
||
else meow_dac_vol_write_throttled();/* 拖动中=墙钟节流写 DAC */
|
||
}
|
||
else if (!strcmp(r->key, "display.brightness")) {
|
||
/* M2.46-2 ①:亮度滑条拖动时状态栏百分比数字实时跟。根因:旧代码只 write_brightness(v)
|
||
* 写背光,未同步 g_sys.brightness(状态栏亮度数字取它)也未重绘 → 数字卡住。
|
||
* 修法:写背光后同步 g_sys.brightness 并 paint_sb_right()(≡sb_redraw 整条标脏)。
|
||
* 一致性:其后 meow_sys_refresh 从 sysfs 读回的也是刚写入的 v,二者一致不打架。 */
|
||
int v; if (meow_settings_get_int("display.brightness", &v) == 0) {
|
||
write_brightness(v);
|
||
g_sys.brightness = v;
|
||
paint_sb_right();
|
||
}
|
||
}
|
||
else if (!strcmp(r->key, "display.refresh_rate"))
|
||
meow_ui_set_refresh_rate(refresh_rate_cfg);
|
||
else if (!strcmp(r->key, "display.refresh_always_max"))
|
||
meow_ui_set_refresh_always_max(refresh_always_max);
|
||
else if (!strcmp(r->key, "network.wifi_enabled")) {
|
||
int on = 0; meow_settings_get_bool(r->key, &on);
|
||
s_net_toggle_wifi(on);
|
||
s_net_refresh();
|
||
}
|
||
else if (!strcmp(r->key, "network.bluetooth_enabled")) {
|
||
int on = 0; meow_settings_get_bool(r->key, &on);
|
||
s_net_toggle_bt(on);
|
||
s_net_refresh();
|
||
}
|
||
/* M2.44 背景选项真正生效:display.background(black/blue/green) → menu_bg(主界面背景)。
|
||
* 此前 SC_PRESET 只写 meow_settings,未应用 menu_bg → 黑/绿点了没用。 */
|
||
else if (!strcmp(r->key, "display.background")) {
|
||
char bg[16]; meow_settings_get_str(r->key, bg, sizeof bg);
|
||
if (!strcmp(bg, "blue")) menu_bg = 0x0A1428; /* 深蓝 */
|
||
else if (!strcmp(bg, "green")) menu_bg = 0x0A2814; /* 深绿 */
|
||
else menu_bg = 0x000000; /* black(默认) */
|
||
/* 状态栏(menu_bg)与主界面背景都会用到 menu_bg;退出设置页回主界面时重绘即可。 */
|
||
}
|
||
/* M2.44 主题/mode 变更:状态栏文字/底色已接 s_ui_theme(),此处仅确保回主界面重绘。 */
|
||
else if (!strcmp(r->key, "display.theme") || !strcmp(r->key, "display.theme_mode") ||
|
||
!strcmp(r->key, "display.theme_dark_start") || !strcmp(r->key, "display.theme_dark_end")) {
|
||
paint_sb_right(); /* 状态栏文字/底色随主题刷新(整条标脏) */
|
||
}
|
||
/* M2.5x:字体文件(font.file)变更 → 立即 unload + reload(若已加载)。
|
||
* 置 mfont_forced 跳过 meow_font_ensure 的 SDK 复用捷径,强制走
|
||
* font.file → 卡默认 → 固件 链;全失败则回退 5x7 点阵(系统不死)。 */
|
||
else if (!strcmp(r->key, "font.file")) {
|
||
mfont_ready = 0;
|
||
mfont_forced = 1;
|
||
if (!meow_font_ensure())
|
||
logmsg("font.file: reload failed -> dotmatrix fallback");
|
||
else
|
||
logmsg("font.file: reloaded");
|
||
meow_font_reload_multifont(); /* M2.5x:全局字体变更 → 重配多字体(各类跟随全局者随之更新) */
|
||
meow_dirty_full(); /* 字体变化可能影响所有文本渲染,整帧重绘 */
|
||
}
|
||
/* M2.5x「加载大字库」开关(font.load_full)变更 → 强制重载 launcher 自身字体(小/大字库切换),
|
||
* 并重配多字体;大字库 atlas 大,meow_font_ensure 先 unload 旧再 load 新(防双驻留)。 */
|
||
else if (!strcmp(r->key, "font.load_full")) {
|
||
mfont_ready = 0;
|
||
mfont_forced = 1; /* 跳过 SDK 复用捷径,强制按生效档(gbk/common)真实 reload */
|
||
if (!meow_font_ensure())
|
||
logmsg("font.load_full: reload failed -> dotmatrix fallback");
|
||
else {
|
||
int lf = 0; meow_settings_get_switch("font.load_full", &lf);
|
||
logmsg("font.load_full: reloaded (load_full=%d)", lf);
|
||
}
|
||
meow_font_reload_multifont();
|
||
meow_dirty_full();
|
||
}
|
||
/* M2.5x 多字体:开关或任一类别字体变更 → 重新去重装载类别字库并重绘(多字体可能影响文本渲染)。 */
|
||
else if (!strcmp(r->key, "display.multi_font") || !strncmp(r->key, "font.class_", 11)) {
|
||
meow_font_reload_multifont();
|
||
meow_dirty_full();
|
||
}
|
||
if (save_now) meow_settings_save();
|
||
}
|
||
|
||
static void s_set_int_value(const srow_t *r, int v, int save_now)
|
||
{
|
||
if (!strcmp(r->key, "audio.volume")) v = vol_clamp(v); /* 尊重 audio.volume_max */
|
||
meow_settings_set_int(r->key, v);
|
||
s_commit(r, save_now);
|
||
}
|
||
|
||
/* 滑条拖动/点击落值:对「档位型」息屏时间做 索引→秒 换算,其余行原样写盘。
|
||
* sl_v 为滑条轨迹值(s_slider_val_from_x 返回值;息屏时间=档位索引)。 */
|
||
static void s_slider_set_from_track(const srow_t *r, int sl_v, int save_now)
|
||
{
|
||
if (!strcmp(r->key, "display.screen_off_timeout")) sl_v = s_time_tier_val(sl_v);
|
||
/* B2b 深色时段滑条:分钟按 15 分钟步进吸附(0-1439,整 15 分钟),避免拖出任意分钟 */
|
||
if (!strcmp(r->key, "display.theme_dark_start") || !strcmp(r->key, "display.theme_dark_end")) {
|
||
sl_v = ((sl_v + 7) / 15) * 15;
|
||
if (sl_v < 0) sl_v = 0;
|
||
if (sl_v > 1439) sl_v = 1439;
|
||
}
|
||
s_set_int_value(r, sl_v, save_now);
|
||
}
|
||
|
||
|
||
/* 滑条拖动值域映射:x(屏幕坐标)→ value,基于布局常量 S_SLIDER_X/W。
|
||
* 所有滑条行共用同一 track 几何,故任意滑条行都能正确映射。 */
|
||
static int s_slider_val_from_x(const srow_t *r, int px)
|
||
{
|
||
int range = r->max - r->min;
|
||
int v = r->min + (range ? (px - S_SLIDER_X) * range / S_SLIDER_W : 0);
|
||
if (v < r->min) v = r->min;
|
||
if (v > r->max) v = r->max;
|
||
return v;
|
||
}
|
||
|
||
|
||
/* ---- 绘制设置页(整页画进 SDK back_buf;调用方再 meow_dirty_add 局部 + flush)---- */
|
||
static void draw_settings_page(int sel, int scroll_off)
|
||
{
|
||
const meow_ui_theme_t *th = s_ui_theme();
|
||
int W = fb_xres, H = fb_yres;
|
||
fill_rect(0, 0, W, H, th->surface);
|
||
draw_statusbar(); /* Stage-One:驱动 SDK 全局状态栏(喂数据+标脏顶条) */
|
||
if (mfont_ready) ui_draw(24, 50, "系统设置", th->on_surface, 28);
|
||
else draw_text(24, 30, "SETTINGS", 3, th->on_surface);
|
||
fill_rect(0, 50 + (mfont_ready ? meow_font_line_height(28) : 24) + 2, W, 2, th->outline);
|
||
|
||
/* M2.38 Bug5:整屏背景替代带圆角的单张卡片(用户反馈圆角非全屏底很怪)。
|
||
* 内容区从 SET_CARD_Y 起整屏 fill surface_variant、去圆角、满宽直到屏幕底;
|
||
* 状态栏(0-28)与标题区(28-92)仍为 surface,标题/分隔线/分组 outline/选中高亮/滚动条布局不变。 */
|
||
fill_rect(0, SET_CARD_Y, W, H - SET_CARD_Y, th->surface_variant);
|
||
|
||
int clip_top = SET_CONTENT_Y, clip_bot = SET_CONTENT_BOT;
|
||
s_sel_track_y = -1;
|
||
for (int i = 0; i < SMENU_N; i++) {
|
||
int pos = s_vis_of(i);
|
||
if (pos < 0) continue; /* 隐藏行(多字体关闭时 6 个 class 行) */
|
||
int sy = SET_CONTENT_Y + pos * SROW_H - scroll_off;
|
||
if (sy + SROW_H < clip_top || sy >= clip_bot) continue; /* 视野外行跳过(配滚动) */
|
||
const srow_t *r = &SMENU[i];
|
||
int selected = (i == sel);
|
||
if (selected)
|
||
meow_ui_fill_rect_alpha(SET_CARD_X + 4, sy, SET_CARD_W - 8, SROW_H, th->primary, 34);
|
||
meow_ui_fill_rect(SET_CARD_X + 12, sy + SROW_H - 1, SET_CONTENT_W, 1, th->outline);
|
||
|
||
if (r->ctrl == SC_HEADER) {
|
||
ui_draw(SET_CARD_X + 16, sy + 14, r->label, th->on_surface_variant, 18);
|
||
} else if (r->ctrl == SC_SWITCH) {
|
||
ui_draw(SET_CARD_X + 16, sy + (SROW_H - meow_font_line_height(22)) / 2, r->label,
|
||
th->on_surface, 22);
|
||
int on = 0; meow_settings_get_switch(r->key, &on);
|
||
s_draw_switch(SET_CARD_X + SET_CARD_W - 16 - 48, sy + (SROW_H - 28) / 2, on, th);
|
||
} else if (r->ctrl == SC_SLIDER) {
|
||
int ly = sy + (SROW_H - meow_font_line_height(22)) / 2 - 6; /* 标题略上移,给下方滑条留位 */
|
||
ui_draw(SET_CARD_X + 16, ly, r->label, th->on_surface, 22);
|
||
char vb[32]; s_fmt_value(r, vb, sizeof vb);
|
||
ui_draw(SET_CARD_X + SET_CARD_W - 16 - ui_width(vb, 22), ly, vb, th->on_surface_variant, 22);
|
||
int v = 0; meow_settings_get_int(r->key, &v);
|
||
/* 息屏时间为档位索引型:把秒换算成档位索引再画(各档等距,便于触摸对位);
|
||
* 其余滑条防御性 clamp 到 [min,max],防止后端暂存值越界(如 brightness 旧值 101
|
||
* 在画时被 clamp 到 100,thumb 不冲出 track)。 */
|
||
if (!strcmp(r->key, "display.screen_off_timeout")) v = s_time_tier_idx(v);
|
||
else {
|
||
if (v < r->min) v = r->min;
|
||
if (v > r->max) v = r->max;
|
||
}
|
||
int tyy = sy + 40;
|
||
s_draw_slider(S_SLIDER_X, tyy, S_SLIDER_W, v, r->min, r->max, th);
|
||
if (selected) s_sel_track_y = tyy;
|
||
} else if (r->ctrl == SC_INFO) { /* M2.39 只读动态信息(如 WiFi 连接 SSID) */
|
||
int ly = sy + (SROW_H - meow_font_line_height(22)) / 2;
|
||
ui_draw(SET_CARD_X + 16, ly, r->label, th->on_surface, 22);
|
||
char vb[48]; s_net_get(r, vb, sizeof vb);
|
||
int vx = SET_CARD_X + SET_CARD_W - 16 - ui_width(vb, 22);
|
||
ui_draw(vx, ly, vb, th->on_surface_variant, 22);
|
||
} else if (r->ctrl == SC_LIST) { /* M2.39 网络列表行:摘要 + 右向展开提示 */
|
||
int ly = sy + (SROW_H - meow_font_line_height(22)) / 2;
|
||
ui_draw(SET_CARD_X + 16, ly, r->label, th->on_surface, 22);
|
||
char vb[48]; s_net_get(r, vb, sizeof vb);
|
||
int vx = SET_CARD_X + SET_CARD_W - 16 - 12 - ui_width(vb, 22); /* 给右向 chevron 留位 */
|
||
ui_draw(vx, ly, vb, th->on_surface_variant, 22);
|
||
s_draw_chevron(SET_CARD_X + SET_CARD_W - 16, sy + SROW_H / 2, th->on_surface_variant);
|
||
} else { /* SC_ENUM / SC_PRESET */
|
||
int ly = sy + (SROW_H - meow_font_line_height(22)) / 2;
|
||
ui_draw(SET_CARD_X + 16, ly, r->label, th->on_surface, 22);
|
||
char vb[40]; s_display_value(r, vb, sizeof vb);
|
||
int vx = SET_CARD_X + SET_CARD_W - 16 - ui_width(vb, 22);
|
||
ui_draw(vx, ly, vb, th->on_surface_variant, 22);
|
||
s_draw_chevron(vx - 8, sy + SROW_H / 2, th->on_surface_variant);
|
||
}
|
||
}
|
||
|
||
/* 滚动条(内容超出视口时显示 thumb;on_surface_variant 弱化) */
|
||
int vis_total = s_vis_count() * SROW_H;
|
||
if (vis_total > SET_CONTENT_H) {
|
||
int total = vis_total;
|
||
int thumb_h = SET_CONTENT_H * SET_CONTENT_H / total;
|
||
if (thumb_h < 24) thumb_h = 24;
|
||
int th_y = SET_CONTENT_Y + (SET_CONTENT_H - thumb_h) * scroll_off / (total - SET_CONTENT_H);
|
||
meow_gfx_fill_round(fb_xres - 12, SET_CONTENT_Y, 5, SET_CONTENT_H, 3, th->surface_container);
|
||
meow_gfx_fill_round(fb_xres - 12, th_y, 5, thumb_h, 3, th->on_surface_variant);
|
||
}
|
||
|
||
/* 底部:返回提示(左)+ 版本号(右),复用应用列表底部对齐逻辑。
|
||
* M2.39:返回按钮改用像素左向箭头 + 「返回」——点阵回退字库缺少 '‹' 字形,
|
||
* 原 「‹ 返回」在 !mfont_ready 时 '‹' 渲染成方框。 */
|
||
{
|
||
char ver[32];
|
||
snprintf(ver, sizeof ver, "MeowOS v%s dev", g_meowos_version);
|
||
int foot_y = fb_yres - meow_font_line_height(20) - 8;
|
||
s_draw_chevron_left(SET_CARD_X + 12, foot_y + meow_font_line_height(20) / 2, th->on_surface_variant);
|
||
ui_draw(SET_CARD_X + 24, foot_y, "返回", th->on_surface_variant, 20);
|
||
ui_draw(fb_xres - 8 - ui_width(ver, 20), foot_y, ver, th->outline, 20);
|
||
}
|
||
}
|
||
|
||
|
||
/* M2.5x 多字体:class 行下拉的「跟随全局」选项(值=空串)在 write 路径经此映射回真实存储值。 */
|
||
static const char *s_preset_value(const srow_t *r, int pick)
|
||
{
|
||
int is_cls = r->key && !strncmp(r->key, "font.class_", 11);
|
||
if (is_cls) {
|
||
if (pick == 0) return ""; /* 跟随全局 font.file */
|
||
pick--;
|
||
}
|
||
int pn = 0;
|
||
const char *const *pps = s_row_presets(r, &pn);
|
||
if (pick < 0 || pick >= pn) return is_cls ? "" : (pps && pn > 0 ? pps[0] : "");
|
||
return pps[pick];
|
||
}
|
||
|
||
|
||
/* ---- 选择器子对话框(MD3 §3.3:居中圆角卡片,列选项;中文标签)----
|
||
* ENUM 用 meow_settings_enum_names/enum_label;PRESET 用本地预设表。
|
||
* 返回选中索引;取消返回 -1。阻塞(自开输入设备)。
|
||
* M2.39:modal 核心抽到 s_dd_modal(网络列表复用),本函数只负责组装选项数组。 */
|
||
static int s_dropdown(const srow_t *r)
|
||
{
|
||
int n = 0, cur = 0;
|
||
const char *names[8];
|
||
char labels[8][48];
|
||
const char *item[S_NET_MAX];
|
||
if (r->ctrl == SC_ENUM) {
|
||
int kn = 0;
|
||
meow_settings_enum_names(r->key, names, &kn);
|
||
n = kn;
|
||
if (n > S_NET_MAX) n = S_NET_MAX;
|
||
for (int i = 0; i < n; i++) {
|
||
meow_settings_enum_label(r->key, i, labels[i], sizeof labels[i]);
|
||
item[i] = labels[i];
|
||
}
|
||
int ci = 0; meow_settings_get_enum(r->key, &ci); cur = ci;
|
||
} else {
|
||
char cstr[MEOW_SET_MAX_LEN];
|
||
meow_settings_get_str(r->key, cstr, sizeof cstr);
|
||
int pn = 0;
|
||
const char *const *pps = s_row_presets(r, &pn);
|
||
int is_cls = r->key && !strncmp(r->key, "font.class_", 11);
|
||
int base = is_cls ? 1 : 0; /* class 行 0 号留给「跟随全局」 */
|
||
int cnt = pn; if (cnt > S_NET_MAX - base) cnt = S_NET_MAX - base;
|
||
n = base + cnt;
|
||
if (is_cls) {
|
||
names[0] = "";
|
||
snprintf(labels[0], sizeof labels[0], "跟随全局字体");
|
||
item[0] = labels[0];
|
||
if (!cstr[0]) cur = 0;
|
||
}
|
||
for (int i = 0; i < cnt; i++) {
|
||
names[base + i] = pps[i];
|
||
snprintf(labels[base + i], sizeof labels[base + i], "%s",
|
||
(r->preset_labels && r->preset_labels[i][0]) ? r->preset_labels[i] : pps[i]);
|
||
item[base + i] = labels[base + i];
|
||
if (!strcmp(pps[i], cstr)) cur = base + i;
|
||
}
|
||
}
|
||
if (n <= 0) return -1;
|
||
if (cur < 0 || cur >= n) cur = 0;
|
||
return s_dd_modal(r->label, item, n, cur);
|
||
}
|
||
|
||
|
||
/* ---- 通用选择器下拉模态(MD3 §3.3:居中圆角卡片,列选项)----
|
||
* title=标题;item[i]=第 i 项显示文本;n=项数(≤S_NET_MAX);cur=当前值下标(画勾选)。
|
||
* 返回选中下标;取消/点卡片外返回 -1。阻塞(自开输入设备)。
|
||
* s_dropdown(设置项 enum/preset)与 s_net_open(网络列表)共用。
|
||
* M2.40:触摸命中判定改在抬手用抬手时刻最新 abs 坐标(见循环内注释),修「点选项无反应」。
|
||
* 说明:模态内部仍保留音量键=移动选项高亮(这是「选值」不是「设置页列表光标」,属另一语义,
|
||
* 与 M2.39 仅移除 run_settings_page 列表光标移动的约束不冲突)。 */
|
||
/* 绘制下拉/关于本机模态:全屏压暗 + 居中圆角卡片 + 标题 + 可见行(按 dd_scroll 裁剪,视口外整行跳过)。 */
|
||
static void dd_modal_paint(const meow_ui_theme_t *th, int W, int H, int d_x, int d_y,
|
||
int d_w, int d_h, int title_h, int opt_row, const char *title,
|
||
const char **item, int n, int sel, int cur,
|
||
int content_top, int content_bot, int dd_scroll)
|
||
{
|
||
meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150);
|
||
meow_gfx_fill_round(d_x, d_y, d_w, d_h, 16, th->surface_container);
|
||
meow_ui_draw_rect_round(d_x, d_y, d_w, d_h, 16, th->outline);
|
||
if (mfont_ready) ui_draw(d_x + 18, d_y + 8, title, th->on_surface, 20);
|
||
else draw_text(d_x + 18, d_y + 8, title, 2, th->on_surface);
|
||
for (int i = 0; i < n; i++) {
|
||
int ry = d_y + title_h + i * opt_row - dd_scroll;
|
||
if (ry + opt_row < content_top || ry >= content_bot) continue; /* 视口外整行跳过(配滚动) */
|
||
int cur_sel = (i == sel);
|
||
if (cur_sel) meow_ui_fill_rect_alpha(d_x + 4, ry, d_w - 8, opt_row, th->primary, 36);
|
||
ui_draw(d_x + 18, ry + (opt_row - meow_font_line_height(20)) / 2, item[i] ? item[i] : "",
|
||
cur_sel ? 0xFFFFFF : th->on_surface, 20);
|
||
if (i == cur) meow_ui_fill_rect(d_x + d_w - 28, ry + (opt_row - 12) / 2, 12, 12, th->primary);
|
||
}
|
||
}
|
||
|
||
int s_dd_modal(const char *title, const char *item[S_NET_MAX], int n, int cur)
|
||
{
|
||
int sel = cur;
|
||
const meow_ui_theme_t *th = s_ui_theme();
|
||
int W = fb_xres, H = fb_yres;
|
||
int opt_row = 48, pad = 20, title_h = 40;
|
||
/* M2.5x 关于本机:内容超高(13 行)时把卡片高钳到屏内,内容区允许滚动(复用设置页列滚动语义)。 */
|
||
int raw_d_h = title_h + n * opt_row + pad;
|
||
int max_card_h = H - SB_BAR_H - 20; /* 卡片可视高上限(不压状态栏、留底边) */
|
||
int scrollable = (raw_d_h > max_card_h);
|
||
int d_h = scrollable ? max_card_h : raw_d_h;
|
||
int d_w = 348, d_x = (W - d_w) / 2, d_y = (H - d_h) / 2;
|
||
if (d_y < SB_BAR_H) d_y = SB_BAR_H; /* 下拉不压状态栏(栏高 40→28,随常量) */
|
||
if (H - d_y - d_h < 20) d_y = H - d_h - 20;
|
||
/* 内容可视区(卡片体内、标题下、底部内边距上);dd_scroll 仅在 scrollable 时非 0。 */
|
||
int content_top = d_y + title_h;
|
||
int content_bot = d_y + d_h - pad;
|
||
int dd_view_h = content_bot - content_top;
|
||
int dd_scroll_max = scrollable ? (n * opt_row - dd_view_h) : 0;
|
||
if (dd_scroll_max < 0) dd_scroll_max = 0;
|
||
int dd_scroll = 0;
|
||
int swipe_inv = 0;
|
||
if (scrollable) meow_settings_get_switch("input.swipe_invert", &swipe_inv);
|
||
|
||
struct keypoll kp_power, kp_keys;
|
||
kp_open(&kp_power, DEV_POWER); kp_open(&kp_keys, DEV_KEYS);
|
||
struct kd_state kd_vold, kd_volu, kd_pwr;
|
||
memset(&kd_vold, 0, sizeof kd_vold); memset(&kd_volu, 0, sizeof kd_volu); memset(&kd_pwr, 0, sizeof kd_pwr);
|
||
int m_prev_vold = 0, m_prev_volu = 0, m_prev_pwr = 0;
|
||
for (int w = 0; w < KEYBUF_WORDS; w++) {
|
||
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEDOWN % 32))) m_prev_vold = 1;
|
||
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEUP % 32))) m_prev_volu = 1;
|
||
if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) m_prev_pwr = 1;
|
||
}
|
||
int tfd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK);
|
||
if (tfd >= 0) {
|
||
/* M2.37:彻底清空「打开下拉」这次触摸的残留边沿。goodix 的抬起序列(ABS 坐标/
|
||
* BTN_TOUCH=0/MT_TRACKING=-1)在开 fd 后仍可能陆续送达,若只清空一次,第一轮
|
||
* 下拉循环会读到这些残边 → 误判抬起/选项错位/点卡片外穿透。故 排空→短延时→再排空。 */
|
||
unsigned char ev[16];
|
||
while (read(tfd, ev, sizeof ev) == 16) {}
|
||
usleep(40000); /* M2.38 Bug3:排空→40ms→再排空,吸收 goodix 抬起序列长尾 */
|
||
while (read(tfd, ev, sizeof ev) == 16) {}
|
||
}
|
||
int pwr_pending = 0;
|
||
int result = -1;
|
||
int dlx = -1, dly = -1, dtd = 0; /* 下拉触摸解析(dlx/dly=最新有效坐标;dtd=已按下) */
|
||
/* M2.5x 滚动状态(仅 scrollable 生效;非滚动路径与原文一致) */
|
||
int dd_oy = -1; /* 按下原点纵坐标(首个有效 ABS 后锁) */
|
||
int dd_scrolling = 0; /* 本次手势是否已判为纵向滚动 */
|
||
int dd_scroll_base = 0; /* 进入滚动时锁定的 dd_scroll 基准(连续跟手) */
|
||
int sel_moved = 0; /* 音量键改变了 sel(滚动需跟随选中行) */
|
||
/* M2.40:不再锁按下时刻的 ddx/ddy —— goodix 时序 BTN_TOUCH 先于 ABS,按下边沿时坐标可能
|
||
* 还是 -1/上一手势残留,锁原点会把点击判成「点卡片外=取消」。命中改在抬手时用抬手时刻的
|
||
* dlx/dly(抬起时必已更新到本次最新有效坐标),与 meow_touch mt_up 语义同源。 */
|
||
|
||
/* 初始绘制:全屏压暗覆盖层 + 居中圆角卡片 + 可见行(按 dd_scroll 裁剪) */
|
||
dd_modal_paint(th, W, H, d_x, d_y, d_w, d_h, title_h, opt_row, title, item, n,
|
||
sel, cur, content_top, content_bot, dd_scroll);
|
||
meow_dirty_full(); meow_ui_flush();
|
||
|
||
for (;;) {
|
||
if (sigterm_flag) { result = -1; break; }
|
||
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
|
||
int vold = 0, volu = 0, pwr = 0;
|
||
for (int w = 0; w < KEYBUF_WORDS; w++) {
|
||
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEDOWN % 32))) vold = 1;
|
||
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEUP % 32))) volu = 1;
|
||
if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) pwr = 1;
|
||
}
|
||
(void)kd_tick(&kd_vold, vold); (void)kd_tick(&kd_volu, volu); (void)kd_tick(&kd_pwr, pwr);
|
||
vold = kd_vold.stable; volu = kd_volu.stable; pwr = kd_pwr.stable;
|
||
int need = 0;
|
||
if (vold && !m_prev_vold && sel < n - 1) { sel++; need = 1; sel_moved = 1; }
|
||
if (volu && !m_prev_volu && sel > 0) { sel--; need = 1; sel_moved = 1; }
|
||
if (pwr && !m_prev_pwr) pwr_pending = 1; /* 按下:待抬起确认 */
|
||
if (!pwr && m_prev_pwr && pwr_pending) { pwr_pending = 0; result = sel; break; } /* 抬起=确认 */
|
||
m_prev_vold = vold; m_prev_volu = volu; m_prev_pwr = pwr;
|
||
|
||
/* 触摸:抬起命中选项=确认,点卡片外=取消 */
|
||
if (tfd >= 0) {
|
||
struct pollfd pfd = { tfd, POLLIN, 0 };
|
||
if (poll(&pfd, 1, 0) > 0) {
|
||
unsigned char ev[16];
|
||
while (read(tfd, ev, sizeof ev) == 16) {
|
||
uint16_t type = *(uint16_t *)(ev + 8), code = *(uint16_t *)(ev + 10);
|
||
int32_t val = *(int32_t *)(ev + 12);
|
||
if (type == EV_ABS) {
|
||
if (code == ABS_X || code == ABS_MT_POSITION_X) dlx = val;
|
||
else if (code == ABS_Y || code == ABS_MT_POSITION_Y) {
|
||
dly = val;
|
||
if (dtd) {
|
||
if (dd_oy < 0) dd_oy = val; /* 首个有效纵坐标 = 按下原点 */
|
||
if (scrollable) {
|
||
int ddy = val - dd_oy; /* 手指纵向位移(正=下移) */
|
||
if (!dd_scrolling && (ddy > S_SCROLL_SLOP || ddy < -S_SCROLL_SLOP)) {
|
||
dd_scrolling = 1;
|
||
dd_scroll_base = dd_scroll; /* 触发瞬间锁基准,不跳变 */
|
||
}
|
||
if (dd_scrolling) {
|
||
/* 标准方向:手指上移(ddy<0)→内容上移/看下方→scroll 增;swipe_invert 反向 */
|
||
int t = dd_scroll_base + (swipe_inv ? ddy : -ddy);
|
||
if (t < 0) t = 0;
|
||
if (t > dd_scroll_max) t = dd_scroll_max;
|
||
if (t != dd_scroll) { dd_scroll = t; need = 1; }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} else if (type == EV_KEY && code == BTN_TOUCH) {
|
||
if (val) {
|
||
/* 按下边沿:只标「已按下」,绝不锁原点(goodix 时序 BTN_TOUCH 先于
|
||
* ABS,此刻 dlx/dly 可能还是 -1 或上一手势残留)。 */
|
||
dtd = 1; dd_oy = -1; dd_scrolling = 0;
|
||
} else if (dtd) {
|
||
dtd = 0;
|
||
/* 抬手判定:此刻 dlx/dly 必为本次最新有效坐标(抬起时 ABS 已先到)。
|
||
* 卡片内命中 → 返回选项下标;卡片外 → 取消(-1);从未见有效坐标 → 丢弃。
|
||
* M2.5x:滚动手势抬手不选(不 launch);命中行换算叠加 dd_scroll。 */
|
||
if (dd_scrolling) {
|
||
dd_scrolling = 0;
|
||
} else if (dlx >= 0 && dly >= 0) {
|
||
if (dlx >= d_x && dlx < d_x + d_w && dly >= d_y && dly < d_y + d_h) {
|
||
int i = (dly - d_y - title_h + dd_scroll) / opt_row;
|
||
if (i >= 0 && i < n) { sel = i; result = sel; goto ddone; }
|
||
/* 卡片内但落在标题/内边距:保持弹出态,等待下一次点选 */
|
||
} else {
|
||
result = -1; goto ddone; /* 点卡片外=取消 */
|
||
}
|
||
}
|
||
/* dlx/dly 为 -1:ghost/残留抬手,丢弃不动作 */
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (need) {
|
||
/* 重绘覆盖层+选项(仅 sel 高亮/滚动变化);sel 经音量键移动时滚动跟随选中行。 */
|
||
if (sel_moved && scrollable) {
|
||
int ry0 = content_top + sel * opt_row - dd_scroll;
|
||
if (ry0 < content_top) dd_scroll = sel * opt_row;
|
||
else if (ry0 + opt_row > content_bot) dd_scroll = sel * opt_row - (dd_view_h - opt_row);
|
||
if (dd_scroll < 0) dd_scroll = 0;
|
||
if (dd_scroll > dd_scroll_max) dd_scroll = dd_scroll_max;
|
||
}
|
||
sel_moved = 0;
|
||
dd_modal_paint(th, W, H, d_x, d_y, d_w, d_h, title_h, opt_row, title, item, n,
|
||
sel, cur, content_top, content_bot, dd_scroll);
|
||
meow_dirty_full(); meow_ui_flush();
|
||
}
|
||
usleep(POLL_MS * 1000);
|
||
}
|
||
ddone:
|
||
if (tfd >= 0) close(tfd);
|
||
kp_close(&kp_power); kp_close(&kp_keys);
|
||
return result;
|
||
}
|
||
|
||
|
||
/* ---- 字体行「family→字重」两级子菜单(M2.6 字重结构)----
|
||
* 一级:列出扫描到的去重 family(class 行首项「跟随全局」=空值);选 family 后自动进入
|
||
* 二级:该 family 的可用字重(regular/bold/italic…)。选中写 out_val = 卡根相对路径
|
||
* MeowOS/fonts/<family>/<weight>/font.png(或 class 行「跟随全局」=空串)。
|
||
* 复用 s_dd_modal(与 WiFi/关于 同款模态)。返回 1=已选中(out_val 已填),0=取消。 */
|
||
static int s_font_dropdown(const srow_t *r, char *out_val, int vlen)
|
||
{
|
||
if (!r) return 0;
|
||
int is_cls = r->key && !strncmp(r->key, "font.class_", 11);
|
||
char cur[MEOW_SET_MAX_LEN];
|
||
meow_settings_get_str(r->key, cur, sizeof cur);
|
||
char curfam[64] = "", curwt[32] = "";
|
||
if (cur[0])
|
||
font_family_weight_from_png(cur, curfam, sizeof curfam, curwt, sizeof curwt);
|
||
|
||
/* ---- 一级:去重 family 列表(class 行首项「跟随全局」)---- */
|
||
char fam_slug[S_NET_MAX][64], fam_disp[S_NET_MAX][64];
|
||
int famn = 0;
|
||
if (is_cls) {
|
||
if (famn < S_NET_MAX) { snprintf(fam_slug[famn], sizeof fam_slug[0], "");
|
||
snprintf(fam_disp[famn], sizeof fam_disp[0], "跟随全局字体"); famn++; }
|
||
}
|
||
for (int i = 0; i < font_presets_n && famn < S_NET_MAX; i++) {
|
||
const char *f = font_preset_fams[i] ? font_preset_fams[i] : "";
|
||
int dup = 0;
|
||
for (int k = 0; k < famn; k++) if (!strcmp(fam_slug[k], f)) { dup = 1; break; }
|
||
if (dup) continue;
|
||
snprintf(fam_slug[famn], sizeof fam_slug[0], "%s", f);
|
||
snprintf(fam_disp[famn], sizeof fam_disp[0], "%s",
|
||
(font_preset_names[i] && font_preset_names[i][0]) ? font_preset_names[i] : f);
|
||
famn++;
|
||
}
|
||
if (famn <= 0) return 0;
|
||
const char *fitem[S_NET_MAX];
|
||
char ftext[S_NET_MAX][64];
|
||
for (int i = 0; i < famn; i++) { snprintf(ftext[i], sizeof ftext[i], "%s", fam_disp[i]); fitem[i] = ftext[i]; }
|
||
int cur_fam = is_cls ? 0 : -1;
|
||
for (int i = is_cls ? 1 : 0; i < famn; i++)
|
||
if (curfam[0] && !strcmp(fam_slug[i], curfam)) cur_fam = i;
|
||
int pick = s_dd_modal(r->label, fitem, famn, cur_fam);
|
||
if (pick < 0) return 0;
|
||
if (is_cls && pick == 0) { snprintf(out_val, vlen, ""); return 1; } /* 跟随全局 */
|
||
const char *fam = fam_slug[pick];
|
||
|
||
/* ---- 二级:该 family 的字重列表(值=完整卡根相对路径,显示=label)---- */
|
||
char wt_val[S_NET_MAX][MEOW_SET_MAX_LEN];
|
||
char wt_text[S_NET_MAX][96];
|
||
int wn = 0;
|
||
for (int i = 0; i < font_presets_n && wn < S_NET_MAX; i++) {
|
||
if (!font_preset_fams[i] || strcmp(font_preset_fams[i], fam)) continue;
|
||
snprintf(wt_val[wn], sizeof wt_val[0], "%s", font_presets[i]);
|
||
snprintf(wt_text[wn], sizeof wt_text[0], "%s", font_preset_labels[i]);
|
||
wn++;
|
||
}
|
||
if (wn <= 0) return 0; /* 该 family 无字重(理论不出现:能进 family 必已探到字重) */
|
||
const char *witem[S_NET_MAX];
|
||
for (int i = 0; i < wn; i++) witem[i] = wt_text[i];
|
||
int cur_wt = -1;
|
||
for (int i = 0; i < wn; i++) if (cur[0] && !strcmp(wt_val[i], cur)) cur_wt = i;
|
||
int wp = s_dd_modal(r->label, witem, wn, cur_wt);
|
||
if (wp < 0) return 0;
|
||
snprintf(out_val, vlen, "%s", wt_val[wp]);
|
||
return 1;
|
||
}
|
||
|
||
|
||
/* ---- 运行设置页(独立子循环;run_menu 触摸命中设置行后调用)---- */
|
||
void run_settings_page(void)
|
||
{
|
||
int sel = s_first_setting();
|
||
int scroll_off = 0;
|
||
int H = fb_yres;
|
||
|
||
/* M2.6:进设置页扫描一次 TF 卡 MeowOS/fonts/<family>/<weight>/,刷新「字体文件」下拉。
|
||
* 进设置时卡通常在位;此步只枚举/校验,不加载(加载由 meow_font_ensure 按 font.file 决定)。 */
|
||
meow_font_scan_presets();
|
||
|
||
struct keypoll kp_power, kp_keys;
|
||
kp_open(&kp_power, DEV_POWER); kp_open(&kp_keys, DEV_KEYS);
|
||
struct kd_state kd_vold, kd_volu, kd_play, kd_pwr;
|
||
memset(&kd_vold, 0, sizeof kd_vold); memset(&kd_volu, 0, sizeof kd_volu);
|
||
memset(&kd_play, 0, sizeof kd_play); memset(&kd_pwr, 0, sizeof kd_pwr);
|
||
int m_prev_vold = 0, m_prev_volu = 0, m_prev_play = 0, m_prev_pwr = 0;
|
||
for (int w = 0; w < KEYBUF_WORDS; w++) {
|
||
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEDOWN % 32))) m_prev_vold = 1;
|
||
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEUP % 32))) m_prev_volu = 1;
|
||
if (kp_keys.cur[w] & (1UL << (KEY_PLAYPAUSE % 32))) m_prev_play = 1;
|
||
if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) m_prev_pwr = 1;
|
||
}
|
||
int tfd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK);
|
||
if (tfd >= 0) { unsigned char ev[16]; while (read(tfd, ev, sizeof ev) == 16) {} }
|
||
unsigned long long touch_cooldown_until = now_ms() + 300;
|
||
unsigned long long last_tick = now_ms();
|
||
unsigned long long last_act = now_ms(); /* 最近一次交互时间(自动息屏用) */
|
||
int after_modal = 0; /* 选择器子对话框刚关闭:其全屏 dim 覆盖过状态栏,需整帧重绘还原 */
|
||
|
||
/* 触摸拖动滑条状态 */
|
||
int td = 0, td_ok = 0, tdx = -1, tdy = -1, tlx = -1, tly = -1;
|
||
int drag = -1; /* 被拖动的行索引;-1=无 */
|
||
/* M2.37 列表拖动滚动状态 */
|
||
int scroll_drag = 0; /* 是否处于列表拖动滚动手势 */
|
||
int scroll_base = 0; /* 进入滚动时对应的 scroll_off 基准(连续跟手) */
|
||
int scroll_y0 = 0; /* 按下原点纵向坐标 */
|
||
int scroll_invert = 0; /* M2.38 Bug4:滑动方向切换(input.swipe_invert;0=标准方向) */
|
||
int sel_changed = 0; /* 本轮是否改变了选中行(仅此场景才强制选中行可见) */
|
||
int wake_suppress = 0; /* 自动息屏唤醒后:吸收唤醒键边沿,待全部抬起再接管 */
|
||
/* M2.x(KEY_CONTROL_DESIGN §2)电源键状态:设置页电源键 = 系统级语义(非返回),
|
||
* 对照主界面 run_menu 3130-3137 的同名变量。单击=息屏/亮屏、长按=电源菜单、8s=强关。 */
|
||
unsigned long long pwr_hold_at = 0; /* 本次电源按下计时起点 */
|
||
int pwr_menu_state = 0; /* 是否已因此次 hold 弹过电源菜单 */
|
||
int pwr_woke = 0; /* 本次按下=自动息屏唤醒(释放只唤醒不 toggle) */
|
||
int pwr_suppress_click = 0; /* 菜单刚关闭:松开不得误判单击熄屏(M2.34 同款) */
|
||
|
||
/* 首次全帧绘制 */
|
||
s_net_sync_from_sys(); /* M2.39:WiFi/蓝牙开关行同步真实硬件态 + 刷新网络信息缓存 */
|
||
scroll_off = s_clamp_scroll(sel, scroll_off);
|
||
draw_settings_page(sel, scroll_off);
|
||
meow_dirty_full(); meow_ui_flush();
|
||
|
||
for (;;) {
|
||
unsigned long long now = now_ms();
|
||
if (sigterm_flag) break;
|
||
/* 重绘触发:值/选中/滚动变化 */
|
||
int need_paint = 0;
|
||
|
||
/* ---- M2.37 自动息屏态:留在设置页,等待任意输入唤醒,恢复后重绘(不丢上下文)----
|
||
* 对照 run_menu 主循环 meow_auto_slept 的唤醒重绘(M2.17/2.18)。休眠期间仅探测
|
||
* 输入(按键原始按下 / black 模式触摸按下);一旦唤醒:亮屏、清自动态并整帧重绘。 */
|
||
if (meow_auto_slept && !meow_screen_on) {
|
||
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
|
||
int wake = 0;
|
||
for (int w = 0; w < KEYBUF_WORDS; w++) {
|
||
if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) wake = 1;
|
||
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEDOWN % 32))) wake = 1;
|
||
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEUP % 32))) wake = 1;
|
||
if (kp_keys.cur[w] & (1UL << (KEY_PLAYPAUSE % 32))) wake = 1;
|
||
}
|
||
if (!wake && screen_off_mode == 1 && tfd >= 0) { /* black 模式:面板仍上电,触摸可唤醒 */
|
||
struct pollfd spfd = { tfd, POLLIN, 0 };
|
||
if (poll(&spfd, 1, 0) > 0) {
|
||
unsigned char ev[16];
|
||
while (read(tfd, ev, sizeof ev) == 16) {
|
||
uint16_t type = *(uint16_t *)(ev + 8), code = *(uint16_t *)(ev + 10);
|
||
int32_t val = *(int32_t *)(ev + 12);
|
||
if (type == EV_KEY && code == BTN_TOUCH && val) wake = 1;
|
||
}
|
||
}
|
||
}
|
||
if (wake) {
|
||
meow_panel_on();
|
||
meow_auto_slept = 0;
|
||
wake_suppress = 1;
|
||
last_act = now_ms();
|
||
/* 重置按键防抖与 m_prev_*(含唤醒键原始态对齐),并丢唤醒的这次触摸 */
|
||
memset(&kd_pwr, 0, sizeof kd_pwr); memset(&kd_vold, 0, sizeof kd_vold);
|
||
memset(&kd_volu, 0, sizeof kd_volu); memset(&kd_play, 0, sizeof kd_play);
|
||
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
|
||
m_prev_pwr = kp_is_down(&kp_power, KEY_POWER);
|
||
m_prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN);
|
||
m_prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP);
|
||
m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE);
|
||
touch_cooldown_until = now_ms() + 300;
|
||
td = 0; td_ok = 0; drag = -1; scroll_drag = 0; tlx = tly = -1;
|
||
logmsg("settings: auto screen-off -> input wake, stayed in settings page");
|
||
scroll_off = s_clamp_scroll_range(scroll_off);
|
||
draw_settings_page(sel, scroll_off);
|
||
meow_dirty_full(); meow_ui_flush();
|
||
}
|
||
usleep(POLL_MS * 1000);
|
||
continue;
|
||
}
|
||
|
||
/* ---- 按键 ---- */
|
||
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
|
||
int vold = 0, volu = 0, play = 0, pwr = 0;
|
||
for (int w = 0; w < KEYBUF_WORDS; w++) {
|
||
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEDOWN % 32))) vold = 1;
|
||
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEUP % 32))) volu = 1;
|
||
if (kp_keys.cur[w] & (1UL << (KEY_PLAYPAUSE % 32))) play = 1;
|
||
if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) pwr = 1;
|
||
}
|
||
(void)kd_tick(&kd_vold, vold); (void)kd_tick(&kd_volu, volu);
|
||
(void)kd_tick(&kd_play, play); (void)kd_tick(&kd_pwr, pwr);
|
||
vold = kd_vold.stable; volu = kd_volu.stable;
|
||
play = kd_play.stable; pwr = kd_pwr.stable;
|
||
if (wake_suppress) {
|
||
/* 刚被唤醒:吸收唤醒键的边沿,等所有按键完全抬起后再接管动作,
|
||
* 避免「电源唤醒→抬起→被当成电源单击返回」或音量唤醒→误导航。 */
|
||
if (!pwr && !vold && !volu && !play) wake_suppress = 0;
|
||
} else {
|
||
/* M2.39:设置页音量键 = 调系统音量(写 DAC),不做光标移动(用户约束:
|
||
* 设置页仅触摸导航;音量键在电菜单/应用列表的系统级导航保持不动)。
|
||
* 按下边沿即起线性渐变(vol_ramp_begin),下方 vol_ramp_tick 逐 1% 爬 + 刷状态栏。 */
|
||
if (vold && !m_prev_vold) { settings_volume_adj(-settings_volume_step); need_paint = 1; }
|
||
if (volu && !m_prev_volu) { settings_volume_adj(+settings_volume_step); need_paint = 1; }
|
||
/* M2.x(KEY_CONTROL_DESIGN §2/§3)播放键在设置页同样被系统「抓住」:
|
||
* 捕获+日志+消费,不再映射为「返回/确认」(原 play 边沿=break 已删,播放键不再退出设置页)。
|
||
* 仍视为输入(重置自动息屏计时,与主界面 play 的媒体预留/any_input 一致)。 */
|
||
if (play && !m_prev_play) {
|
||
logmsg("settings: play key caught (media-reserved, no exit)");
|
||
last_act = now;
|
||
}
|
||
/* M2.x(KEY_CONTROL_DESIGN §2)电源键 = 系统级语义,对齐主界面 run_menu 3284-3366:
|
||
* 单击(按<长按阈值后松开)= 息屏/亮屏;长按>=longpress_menu_ms = 电源菜单;
|
||
* 按住>=forceoff_ms(且未开过菜单)= 强制关机;自动息屏按下只唤醒。
|
||
* ※ 电源键在设置页不再映射为「返回」——退出只能靠触摸(底部返回按钮 + 左缘左滑)。 */
|
||
if (pwr && !m_prev_pwr) {
|
||
pwr_hold_at = now; pwr_menu_state = 0;
|
||
pwr_suppress_click = 0; /* M2.34:新一次电源按下清除旧的抑制标志 */
|
||
last_act = now; /* 视为输入,防按住期间误触发自动息屏 */
|
||
/* M2.18 Bug 2 同款:按下正落在自动息屏态(auto_slept 且屏已关)→ 本次只做唤醒 */
|
||
if (meow_auto_slept && !meow_screen_on) pwr_woke = 1;
|
||
}
|
||
if (pwr && pwr_hold_at) {
|
||
/* M2.10/M2.11 B/M2.37 同款:8s 强关只在该「按住」从未开过菜单(!pwr_menu_state)
|
||
* 时触发;开过菜单则交给菜单内确认/取消,杜绝「开菜单->取消->仍按住->强关」残留。 */
|
||
if (now - pwr_hold_at >= power_forceoff_ms && !pwr_menu_state) {
|
||
if (sim_no_exec) { logmsg("settings pm: [sim-no-exec] POWER HOLD %dms -> would force poweroff", power_forceoff_ms); }
|
||
else { logmsg("settings pm: POWER HOLD %dms -> force poweroff", power_forceoff_ms); meow_power_action(POWER_SHUTDOWN); }
|
||
/* 关机一般不复返;若 reboot 失败返回仍按按住处理(m_prev 由下方统一复写) */
|
||
} else if (now - pwr_hold_at >= power_longpress_menu_ms && !pwr_menu_state) {
|
||
pwr_menu_state = 1;
|
||
logmsg("settings pm: POWER HOLD %dms -> show power menu", power_longpress_menu_ms);
|
||
show_power_menu(); /* 动作在函数内 system();取消则续循环 */
|
||
last_act = now_ms(); /* 菜单返回即视为「刚有输入」,重置自动息屏计时 */
|
||
/* M2.11 B 同款:菜单已关闭。清空按住计时与菜单态,绝不把已互动的 hold 再累计到 8s 强关。 */
|
||
pwr_hold_at = 0; pwr_menu_state = 0; pwr_woke = 0;
|
||
pwr_suppress_click = 1; /* M2.34:菜单返回后抑制本次电源松开被误判单击息屏 */
|
||
/* M2.25 同款:阻塞式 show_power_menu() 期间 kd_* 冻结,m_prev_* 与稳定值可能错配,
|
||
* 重播 kd_* 并以当前 raw 对齐 m_prev_*,杜绝「菜单内触摸点取消->返回后被误判单击息屏」。 */
|
||
memset(&kd_pwr, 0, sizeof kd_pwr);
|
||
memset(&kd_vold, 0, sizeof kd_vold);
|
||
memset(&kd_volu, 0, sizeof kd_volu);
|
||
memset(&kd_play, 0, sizeof kd_play);
|
||
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
|
||
m_prev_pwr = kp_is_down(&kp_power, KEY_POWER);
|
||
m_prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN);
|
||
m_prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP);
|
||
m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE);
|
||
after_modal = 1; /* 全屏 dim 覆盖过状态栏,需整帧重绘还原 */
|
||
need_paint = 1;
|
||
/* 已按 raw 对齐 m_prev_*,跳过下方基线复写,防把 raw 值覆盖回旧稳定值 → 假单边沿 */
|
||
goto settings_keys_seeded;
|
||
}
|
||
}
|
||
if (!pwr && m_prev_pwr) {
|
||
/* 松开:短按(<长按阈值且菜单未开)→ 单击 = 息屏/亮屏切换 */
|
||
if (pwr_suppress_click) {
|
||
logmsg("settings pm: power release after power-menu -> no toggle (suppress)");
|
||
} else if (pwr_hold_at && (now - pwr_hold_at < power_longpress_menu_ms) && !pwr_menu_state && !pwr_woke) {
|
||
logmsg("settings pm: power single-click -> toggle panel (was %s)", meow_screen_on ? "on" : "off");
|
||
meow_toggle_panel();
|
||
meow_auto_slept = 0; /* M2.17:手动/自动息屏后电源单击唤醒均清自动态 */
|
||
last_act = now_ms();
|
||
need_paint = 1; /* 亮屏重绘 / 息屏留屏(面板已黑,下次亮屏重绘) */
|
||
} else if (pwr_woke) {
|
||
logmsg("settings pm: power release after auto screen-off wake -> no toggle (wake only)");
|
||
}
|
||
pwr_hold_at = 0; pwr_menu_state = 0; pwr_woke = 0;
|
||
pwr_suppress_click = 0; /* M2.34:电源已松开,抑制标志一次性用完即清 */
|
||
}
|
||
}
|
||
/* M2.39:驱动音量线性渐变(长按续爬/松开即止)+ 刷新状态栏 VOL;「音量」行随 need_paint 更新 */
|
||
if (vol_ramp_dir != 0) {
|
||
int held = (vol_ramp_dir > 0) ? volu : vold;
|
||
int going = vol_ramp_tick(held);
|
||
g_sys.vol = settings_volume_get();
|
||
paint_sb_right();
|
||
need_paint = 1;
|
||
if (!going) logmsg("settings vol ramp end -> %d", g_sys.vol);
|
||
}
|
||
m_prev_vold = vold; m_prev_volu = volu; m_prev_play = play; m_prev_pwr = pwr;
|
||
settings_keys_seeded:
|
||
;
|
||
|
||
/* ---- 触摸 ---- */
|
||
if (tfd >= 0 && meow_screen_on) { /* M2.x:手动电源息屏后不处理触摸(与主界面一致,且防屏黑时误退出/误改值) */
|
||
struct pollfd pfd = { tfd, POLLIN, 0 };
|
||
if (poll(&pfd, 1, 0) > 0) {
|
||
unsigned char ev[16];
|
||
while (read(tfd, ev, sizeof ev) == 16) {
|
||
uint16_t type = *(uint16_t *)(ev + 8), code = *(uint16_t *)(ev + 10);
|
||
int32_t val = *(int32_t *)(ev + 12);
|
||
if (type == EV_ABS) {
|
||
if (code == ABS_X || code == ABS_MT_POSITION_X) tlx = val;
|
||
else if (code == ABS_Y || code == ABS_MT_POSITION_Y) tly = val;
|
||
if (td && !td_ok && tlx >= 0 && tly >= 0) {
|
||
tdx = tlx; tdy = tly; td_ok = 1;
|
||
scroll_y0 = tdy; /* 列表拖动基准:按下原点(触发时才锁 scroll_base) */
|
||
/* 按下原点落在选中滑条 track 带内 → 开始拖动 */
|
||
if (SMENU[sel].ctrl == SC_SLIDER && s_sel_track_y >= 0 &&
|
||
tlx >= S_SLIDER_X - 12 && tlx <= S_SLIDER_X + S_SLIDER_W + 12 &&
|
||
tly >= s_sel_track_y - 14 && tly <= s_sel_track_y + 20) {
|
||
drag = sel;
|
||
const srow_t *r = &SMENU[sel];
|
||
int v = s_slider_val_from_x(r, tlx);
|
||
s_slider_set_from_track(r, v, 0);
|
||
need_paint = 1;
|
||
}
|
||
}
|
||
/* M2.37 列表拖动滚动:非滑条行按下后纵向位移超阈值 → 进入列表滚动。
|
||
* scroll_base 在触发瞬间反推,保证进入滚动那一刻不跳变(连续跟手)。
|
||
* M2.38 Bug4:默认标准滑动(手指上滑 → scroll_off 增大 = 内容往下翻看后面项);
|
||
* 方向可经 input.swipe_invert 切换(1=反向:手指上滑 → 内容上移)。 */
|
||
if (td_ok && drag < 0 && !scroll_drag) {
|
||
int ddy = tly - scroll_y0;
|
||
if (ddy >= S_SCROLL_SLOP || ddy <= -S_SCROLL_SLOP) {
|
||
scroll_drag = 1;
|
||
meow_settings_get_bool("input.swipe_invert", &scroll_invert);
|
||
scroll_base = scroll_off + (scroll_invert ? -ddy : ddy);
|
||
}
|
||
}
|
||
if (scroll_drag && td_ok) {
|
||
int dydt = tly - scroll_y0;
|
||
scroll_off = s_clamp_scroll_range(scroll_base + (scroll_invert ? dydt : -dydt));
|
||
need_paint = 1;
|
||
}
|
||
if (drag >= 0 && td_ok) {
|
||
const srow_t *r = &SMENU[drag];
|
||
int v = s_slider_val_from_x(r, tlx);
|
||
s_slider_set_from_track(r, v, 0); /* 拖动实时 set_*,抬起才落盘 */
|
||
need_paint = 1;
|
||
}
|
||
} else if (type == EV_KEY && code == BTN_TOUCH) {
|
||
if (val && !td) {
|
||
if (now_ms() < touch_cooldown_until) {
|
||
tlx = tly = -1; /* 冷却丢弃 */
|
||
} else {
|
||
td = 1; td_ok = 0; tlx = -1; tly = -1; drag = -1; scroll_drag = 0;
|
||
}
|
||
} else if (!val && td) {
|
||
td = 0;
|
||
if (drag >= 0) { /* 滑条拖动结束 → 落盘 */
|
||
const srow_t *r = &SMENU[drag];
|
||
s_commit(r, 1);
|
||
drag = -1;
|
||
need_paint = 1;
|
||
} else if (scroll_drag) {
|
||
scroll_drag = 0; /* 列表滚动结束(已实时重绘),不再当 tap */
|
||
scroll_base = 0; scroll_y0 = 0;
|
||
need_paint = 1;
|
||
} else if (td_ok) {
|
||
int dx = tlx - tdx, dy = tly - tdy;
|
||
/* M2.39 左缘右滑返回:复用全局左缘返回判定 meow_edge_back()
|
||
* (与 SDK 全局左缘返回 A-0 同语义:左缘起点 + 右滑≥MEOW_EDGE_SWIPE
|
||
* 且横向主导),避免设置页自造一套重复判定。 */
|
||
if (meow_edge_back(tdx, dx, dy)) {
|
||
if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} }
|
||
goto done;
|
||
}
|
||
int adx = dx < 0 ? -dx : dx, ady = dy < 0 ? -dy : dy;
|
||
if (adx < 60 && ady < 60) { /* 视为 tap */
|
||
int tx = tdx, ty = tdy;
|
||
/* 底部返回按钮 */
|
||
if (ty >= H - 52 && tx >= SET_CARD_X && tx <= SET_CARD_X + 120) {
|
||
if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} }
|
||
goto done;
|
||
}
|
||
int cy = ty - SET_CONTENT_Y + scroll_off;
|
||
int idx = s_row_at_content_y(cy);
|
||
if (idx >= 0 && !s_setting_idx(idx)) {
|
||
/* 点分组标题行 → 仅选中下一设置行 */
|
||
int ns = s_next_setting(idx);
|
||
sel = ns; need_paint = 1; sel_changed = 1;
|
||
} else if (idx >= 0) {
|
||
const srow_t *r = &SMENU[idx];
|
||
sel = idx; need_paint = 1; sel_changed = 1;
|
||
if (r->ctrl == SC_SWITCH) {
|
||
int on = 0; meow_settings_get_switch(r->key, &on);
|
||
meow_settings_set_switch(r->key, !on);
|
||
s_commit(r, 1);
|
||
} else if (r->ctrl == SC_LIST) {
|
||
/* M2.39 网络列表行:开下拉(重新解析)+ 重绘 + 排空本页 tfd
|
||
* (复刻 s_dropdown 后的穿透防护,见下方 ENUM/PRESET 分支)。 */
|
||
after_modal = 1;
|
||
s_net_open(r);
|
||
if (tfd >= 0) {
|
||
unsigned char e[16];
|
||
while (read(tfd, e, sizeof e) == 16) {}
|
||
usleep(40000);
|
||
while (read(tfd, e, sizeof e) == 16) {}
|
||
}
|
||
td = 0; td_ok = 0; drag = -1; scroll_drag = 0;
|
||
scroll_base = 0; scroll_y0 = 0;
|
||
tdx = tdy = tlx = tly = -1;
|
||
touch_cooldown_until = now_ms() + 300;
|
||
memset(&kd_pwr, 0, sizeof kd_pwr);
|
||
memset(&kd_vold, 0, sizeof kd_vold);
|
||
memset(&kd_volu, 0, sizeof kd_volu);
|
||
memset(&kd_play, 0, sizeof kd_play);
|
||
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
|
||
m_prev_pwr = kp_is_down(&kp_power, KEY_POWER);
|
||
m_prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN);
|
||
m_prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP);
|
||
m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE);
|
||
need_paint = 1;
|
||
} else if (r->ctrl == SC_ENUM || r->ctrl == SC_PRESET) {
|
||
/* 单次点击即开选择器子对话框 */
|
||
after_modal = 1; /* 关闭后整帧重绘(还原被 dim 的状态栏) */
|
||
if (s_row_is_font(r)) {
|
||
/* M2.6 字重:字体行走「family→字重」两级子菜单 */
|
||
char fval[MEOW_SET_MAX_LEN];
|
||
if (s_font_dropdown(r, fval, sizeof fval)) {
|
||
meow_settings_set_str(r->key, fval);
|
||
s_commit(r, 1);
|
||
}
|
||
} else {
|
||
int pick = s_dropdown(r);
|
||
if (pick >= 0) {
|
||
if (r->ctrl == SC_ENUM) meow_settings_set_enum(r->key, pick);
|
||
else meow_settings_set_str(r->key, s_preset_value(r, pick));
|
||
s_commit(r, 1);
|
||
}
|
||
}
|
||
/* M2.38 Bug3:s_dropdown 返回后彻底排空本页 tfd +
|
||
* 重置触摸基线。根因:input 子系统把事件广播到所有
|
||
* 打开的 fd —— s_dropdown 用独立 fd 消费选项 tap,但
|
||
* 本页 tfd 同样收到了整套下拉会话事件;若不清空,
|
||
* 外层 while(read) 会把它们当新 tap → 穿透到下层设置项。
|
||
* 故 排空→40ms→再排空 + 重置触摸状态 + 冷却窗口吸收长尾。 */
|
||
if (tfd >= 0) {
|
||
unsigned char e[16];
|
||
while (read(tfd, e, sizeof e) == 16) {}
|
||
usleep(40000);
|
||
while (read(tfd, e, sizeof e) == 16) {}
|
||
}
|
||
td = 0; td_ok = 0; drag = -1; scroll_drag = 0;
|
||
scroll_base = 0; scroll_y0 = 0;
|
||
tdx = tdy = tlx = tly = -1;
|
||
touch_cooldown_until = now_ms() + 300;
|
||
/* 重置设置页按键防抖基线,防残边 */
|
||
memset(&kd_pwr, 0, sizeof kd_pwr);
|
||
memset(&kd_vold, 0, sizeof kd_vold);
|
||
memset(&kd_volu, 0, sizeof kd_volu);
|
||
memset(&kd_play, 0, sizeof kd_play);
|
||
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
|
||
m_prev_pwr = kp_is_down(&kp_power, KEY_POWER);
|
||
m_prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN);
|
||
m_prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP);
|
||
m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE);
|
||
need_paint = 1;
|
||
} else if (r->ctrl == SC_SLIDER && tlx >= 0) {
|
||
int v = s_slider_val_from_x(r, tlx);
|
||
s_slider_set_from_track(r, v, 1);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
td_ok = 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ---- 滚动 + 重绘 ---- */
|
||
if (need_paint) {
|
||
last_act = now;
|
||
/* 仅选中行变化时强制其可见;列表拖动滚动时保留用户滚到的位置,不把选中拉回视野 */
|
||
if (sel_changed) { scroll_off = s_clamp_scroll(sel, scroll_off); sel_changed = 0; }
|
||
draw_settings_page(sel, scroll_off);
|
||
/* 局部标脏:标题+卡片+滚动条+底部(y=42 以下全部,覆盖主题/选中/值变化),
|
||
* 状态栏(0-28)由 draw_statusbar/draw_clock 各自维护,不在此列。
|
||
* 选择器关闭后(after_modal)其全屏 dim 把状态栏也压暗了,须整帧重绘还原。 */
|
||
if (after_modal) { meow_dirty_full(); after_modal = 0; }
|
||
else meow_dirty_add(0, 42, fb_xres, fb_yres - 42);
|
||
meow_ui_flush();
|
||
}
|
||
/* M2.37 超时自动息屏(与主界面一致):空闲 > screen_off_timeout → 关屏,但留在设置页:
|
||
* 置 meow_auto_slept 进入休眠态,由顶部睡眠门等待任意输入唤醒并重绘设置页、不丢上下文
|
||
* (对照 run_menu 外层 meow_auto_slept 的唤醒重绘,M2.17/2.18 同款语义)。 */
|
||
if (screen_off_timeout > 0 && meow_screen_on && !meow_auto_slept &&
|
||
(unsigned long long)(now - last_act) >= (unsigned long long)screen_off_timeout * 1000) {
|
||
meow_panel_off();
|
||
meow_auto_slept = 1;
|
||
wake_suppress = 1; /* 唤醒输入边沿待完全抬起后再接管,避免误动作 */
|
||
logmsg("settings: idle %ds -> auto screen-off (stay in settings)", screen_off_timeout);
|
||
}
|
||
/* ~1s 状态栏时钟刷新 */
|
||
if (now - last_tick >= 1000) {
|
||
last_tick = now;
|
||
draw_clock();
|
||
fb_norm_buf0();
|
||
meow_ui_flush();
|
||
}
|
||
usleep(POLL_MS * 1000);
|
||
}
|
||
done:
|
||
if (tfd >= 0) close(tfd);
|
||
kp_close(&kp_power); kp_close(&kp_keys);
|
||
logmsg("settings page closed");
|
||
}
|
||
|