1766 lines
101 KiB
C
1766 lines
101 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)" };
|
||
|
||
/* i18n:SC_PRESET 选项中文标签键(meow_str_zh:未知 id → 中文兜底,行为/显示不变)。
|
||
* 字体行(font.file/font.class_*)用运行时扫描表 font_presets/font_preset_labels,无键表,原样。 */
|
||
static const char *const bg_preset_i18n[] = { "settings.bg_black", "settings.bg_blue", "settings.bg_green" };
|
||
static const char *const hk_preset_i18n[] = { "settings.hk_powervol_pr", "settings.hk_sequence" };
|
||
static const char *const seq_preset_i18n[] = { "settings.seq_volup3", "settings.seq_voldown3" };
|
||
static const char *const out_preset_i18n[] = { "settings.out_cs43198" };
|
||
|
||
/* SC_PRESET 行选项标签本地化:按该行 presets 表匹配对应键表(字体行用运行时动态表,无键 → 原样)。 */
|
||
static const char *s_preset_label(const srow_t *r, int i)
|
||
{
|
||
const char *const *keys = NULL;
|
||
if (r->presets == bg_presets) keys = bg_preset_i18n;
|
||
else if (r->presets == hk_presets) keys = hk_preset_i18n;
|
||
else if (r->presets == seq_presets) keys = seq_preset_i18n;
|
||
else if (r->presets == out_presets) keys = out_preset_i18n;
|
||
const char *lbl = (r->preset_labels && r->preset_labels[i]) ? r->preset_labels[i] : "";
|
||
if (keys && keys[i]) return meow_str_zh(keys[i], lbl);
|
||
return lbl;
|
||
}
|
||
|
||
|
||
/* 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,0 },
|
||
{ "亮度", "display.brightness", SC_SLIDER, 1,100,1, NULL,NULL,0,0 },
|
||
{ "息屏模式", "display.screen_off_mode", SC_ENUM, 0,0,0, NULL,NULL,0,0 },
|
||
{ "息屏时间", "display.screen_off_timeout", SC_SLIDER, 0,(S_TIME_TIERS_N-1),1, NULL,NULL,0,0 },
|
||
{ "刷新率", "display.refresh_rate", SC_ENUM, 0,0,0, NULL,NULL,0,0 },
|
||
{ "最高刷新率", "display.refresh_always_max", SC_SWITCH, 0,0,0, NULL,NULL,0,0 },
|
||
/* B2b 主题三方式:标题行「主题」改选 display.theme_mode(手动/时段自动),
|
||
* 手动深浅由下方「主题深浅」(display.theme) 控制;时段两键为分钟滑条(STEP=15)。 */
|
||
{ "主题", "display.theme_mode", SC_ENUM, 0,0,0, NULL,NULL,0,0 },
|
||
{ "主题深浅", "display.theme", SC_ENUM, 0,0,0, NULL,NULL,0,0 },
|
||
{ "深色时段开始", "display.theme_dark_start", SC_SLIDER, 0,1439,15, NULL,NULL,0,0 },
|
||
{ "深色时段结束", "display.theme_dark_end", SC_SLIDER, 0,1439,15, NULL,NULL,0,0 },
|
||
{ "背景", "display.background", SC_PRESET, 0,0,0, bg_presets, bg_preset_labels, BG_PRESETS_N,0 },
|
||
/* M2.79 主界面背景图片叠加层强度(图片背景上叠 黑/白 半透明遮罩,防文字/控件被图片淹没) */
|
||
{ "背景叠加强度", "display.bg_overlay_alpha", SC_SLIDER, 0,255,1, NULL,NULL,0,0 },
|
||
/* M2.79 主界面布局:卡片宫格 / 列表(写 ui.home_layout,回主界面即生效) */
|
||
{ "主界面布局", "ui.home_layout", SC_ENUM, 0,0,0, NULL,NULL,0,0 },
|
||
/* 网络:M2.76 WiFi/蓝牙升级为独立子页面(安卓风)。入口行为「列表行」+右侧 chevron,
|
||
* 摘要显示当前状态(s_net_get);点开进入对应子页(顶栏+返回+分组列表),详见
|
||
* wifi_page.c / bt_page.c。原来散落的开关/列表/信息行收敛进子页面。 */
|
||
{ "网络", NULL, SC_HEADER, 0,0,0, NULL,NULL,0,0 },
|
||
{ "WiFi", NULL, SC_LIST, 0,0,0, NULL,NULL,0, NET_LIST_WIFI_PAGE },
|
||
{ "蓝牙", NULL, SC_LIST, 0,0,0, NULL,NULL,0, NET_LIST_BT_PAGE },
|
||
/* 电源 */
|
||
{ "电源", NULL, SC_HEADER, 0,0,0, NULL,NULL,0,0 },
|
||
{ "长按弹菜单", "power.longpress_menu_ms", SC_SLIDER, 500,5000,500, NULL,NULL,0,0 },
|
||
{ "长按强关", "power.forceoff_ms", SC_SLIDER, 5500,15000,500, NULL,NULL,0,0 },
|
||
{ "退出热键", "power.defeat_hotkey", SC_PRESET, 0,0,0, hk_presets, hk_preset_labels, HK_PRESETS_N,0 },
|
||
/* 开机 */
|
||
{ "开机", NULL, SC_HEADER, 0,0,0, NULL,NULL,0,0 },
|
||
{ "默认系统", "boot.default", SC_ENUM, 0,0,0, NULL,NULL,0,0 },
|
||
{ "显示选单", "boot.menu_enabled", SC_SWITCH, 0,0,0, NULL,NULL,0,0 },
|
||
{ "选单超时", "boot.timeout_ms", SC_SLIDER, 0,30000,500, NULL,NULL,0,0 },
|
||
/* 输入 */
|
||
{ "输入", NULL, SC_HEADER, 0,0,0, NULL,NULL,0,0 },
|
||
{ "热键序列", "input.hotkey_seq", SC_PRESET, 0,0,0, seq_presets, seq_preset_labels, SEQ_PRESETS_N,0 },
|
||
{ "热键窗口", "input.hotkey_window_ms", SC_SLIDER, 0,5000,100, NULL,NULL,0,0 },
|
||
{ "触摸布防", "input.touch_arm_delay_ms", SC_SLIDER, 0,2000,50, NULL,NULL,0,0 },
|
||
{ "滑动方向", "input.swipe_invert", SC_SWITCH, 0,0,0, NULL,NULL,0,0 },
|
||
/* 字体主题 */
|
||
{ "字体主题", NULL, SC_HEADER, 0,0,0, NULL,NULL,0,0 },
|
||
{ "字体文件", "font.file", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0,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,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,0 },
|
||
{ "中文(CJK)", "font.class_cjk", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0,0 },
|
||
{ "中文标点", "font.class_cjk_sym", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0,0 },
|
||
{ "数字", "font.class_num", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0,0 },
|
||
{ "拉丁字母", "font.class_latin", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0,0 },
|
||
{ "半角标点", "font.class_latin_sym", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0,0 },
|
||
{ "其他字符", "font.class_other", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0,0 },
|
||
{ "标题字号", "font.size.title", SC_SLIDER, 16,40,1, NULL,NULL,0,0 },
|
||
{ "正文字号", "font.size.body", SC_SLIDER, 12,28,1, NULL,NULL,0,0 },
|
||
{ "UI风格", "ui.style", SC_ENUM, 0,0,0, NULL,NULL,0,0 },
|
||
/* 通知 */
|
||
{ "通知", NULL, SC_HEADER, 0,0,0, NULL,NULL,0,0 },
|
||
{ "通知中心", "notify.center_enabled", SC_SWITCH, 0,0,0, NULL,NULL,0,0 },
|
||
{ "应用配额", "notify.app_quota", SC_SLIDER, 1,64,1, NULL,NULL,0,0 },
|
||
/* 日志 */
|
||
{ "日志", NULL, SC_HEADER, 0,0,0, NULL,NULL,0,0 },
|
||
{ "日志级别", "log.level", SC_ENUM, 0,0,0, NULL,NULL,0,0 },
|
||
{ "写入TF卡", "log.to_card", SC_SWITCH, 0,0,0, NULL,NULL,0,0 },
|
||
/* 音频 */
|
||
{ "音频", NULL, SC_HEADER, 0,0,0, NULL,NULL,0,0 },
|
||
{ "音量", "audio.volume", SC_SLIDER, 0,100,1, NULL,NULL,0,0 },
|
||
{ "音量上限", "audio.volume_max", SC_SLIDER, 0,100,1, NULL,NULL,0,0 },
|
||
{ "音量跳步", "audio.volume_step", SC_SLIDER, 1,5,1, NULL,NULL,0,0 },
|
||
{ "输出设备", "audio.output", SC_PRESET, 0,0,0, out_presets, out_preset_labels, OUT_PRESETS_N,0 },
|
||
/* M6.x 锁屏:安全组「锁屏 PIN」独立子页面(状态/设置/修改/清除/唤醒锁开关,见 lock_settings.c) */
|
||
{ "安全", NULL, SC_HEADER, 0,0,0, NULL,NULL,0,0 },
|
||
{ "锁屏 PIN", NULL, SC_LIST, 0,0,0, NULL,NULL,0, NET_LIST_PIN_PAGE },
|
||
/* 关于(设置列表最底;「关于本机」点开 s_about_open 弹只读设备信息框) */
|
||
{ "关于", NULL, SC_HEADER, 0,0,0, NULL,NULL,0,0 },
|
||
{ "关于本机", NULL, SC_LIST, 0,0,0, NULL,NULL,0, NET_LIST_ABOUT },
|
||
};
|
||
|
||
/* 设置行标题 i18n 键:与 SMENU 一一对应(含分组标题 / SC_LIST 导航行),settings.* 域。
|
||
* 默认值取 SMENU 行 label 原中文,行为/显示不变;TF 覆盖表/后续 SDK 内嵌并入后自动换语。 */
|
||
static const char *const s_row_i18n[SMENU_N] = {
|
||
"settings.sec_display", /* 显示 */
|
||
"settings.brightness", /* 亮度 */
|
||
"settings.screen_off_mode", /* 息屏模式 */
|
||
"settings.screen_off_timeout",/* 息屏时间 */
|
||
"settings.refresh_rate", /* 刷新率 */
|
||
"settings.refresh_always_max",/* 最高刷新率 */
|
||
"settings.theme_mode", /* 主题 */
|
||
"settings.theme", /* 主题深浅 */
|
||
"settings.theme_dark_start", /* 深色时段开始 */
|
||
"settings.theme_dark_end", /* 深色时段结束 */
|
||
"settings.background", /* 背景 */
|
||
"settings.bg_overlay_alpha", /* 背景叠加强度 */
|
||
"settings.home_layout", /* 主界面布局 */
|
||
"settings.sec_network", /* 网络 */
|
||
"settings.wifi", /* WiFi */
|
||
"settings.bt", /* 蓝牙 */
|
||
"settings.sec_power", /* 电源 */
|
||
"settings.longpress_menu", /* 长按弹菜单 */
|
||
"settings.forceoff", /* 长按强关 */
|
||
"settings.defeat_hotkey", /* 退出热键 */
|
||
"settings.sec_boot", /* 开机 */
|
||
"settings.boot_default", /* 默认系统 */
|
||
"settings.boot_menu", /* 显示选单 */
|
||
"settings.boot_timeout", /* 选单超时 */
|
||
"settings.sec_input", /* 输入 */
|
||
"settings.hotkey_seq", /* 热键序列 */
|
||
"settings.hotkey_window", /* 热键窗口 */
|
||
"settings.touch_arm", /* 触摸布防 */
|
||
"settings.swipe_invert", /* 滑动方向 */
|
||
"settings.sec_font", /* 字体主题 */
|
||
"settings.font_file", /* 字体文件 */
|
||
"settings.font_load_full", /* 加载大字库 */
|
||
"settings.multi_font", /* 多字体 */
|
||
"settings.font_cjk", /* 中文(CJK) */
|
||
"settings.font_cjk_sym", /* 中文标点 */
|
||
"settings.font_num", /* 数字 */
|
||
"settings.font_latin", /* 拉丁字母 */
|
||
"settings.font_latin_sym", /* 半角标点 */
|
||
"settings.font_other", /* 其他字符 */
|
||
"settings.font_title_size", /* 标题字号 */
|
||
"settings.font_body_size", /* 正文字号 */
|
||
"settings.ui_style", /* UI风格 */
|
||
"settings.sec_notify", /* 通知 */
|
||
"settings.notify_center", /* 通知中心 */
|
||
"settings.notify_app_quota", /* 应用配额 */
|
||
"settings.sec_log", /* 日志 */
|
||
"settings.log_level", /* 日志级别 */
|
||
"settings.log_to_card", /* 写入TF卡 */
|
||
"settings.sec_audio", /* 音频 */
|
||
"settings.volume", /* 音量 */
|
||
"settings.volume_max", /* 音量上限 */
|
||
"settings.volume_step", /* 音量跳步 */
|
||
"settings.audio_output", /* 输出设备 */
|
||
"settings.sec_security", /* 安全 */
|
||
"settings.pin", /* 锁屏 PIN */
|
||
"settings.sec_about", /* 关于 */
|
||
"settings.about", /* 关于本机 */
|
||
};
|
||
|
||
/* SMENU 行标题本地化:能定位到 SMENU 行 → 按对应键取文案(默认=原中文);否则原样返回。 */
|
||
static const char *s_row_label(const srow_t *r)
|
||
{
|
||
for (int i = 0; i < SMENU_N; i++) {
|
||
if (&SMENU[i] == r) return meow_str_zh(s_row_i18n[i], r->label);
|
||
}
|
||
return meow_str_zh("settings.unknown", r->label);
|
||
}
|
||
|
||
/* M2.39 左缘返回手势已抽为共享函数 meow_edge_back()(meow_touch.h MEOW_EDGE_W/SWIPE 由
|
||
* 其定义),设置页运行期调用;此处不再定义独立 S_EDGE_W/S_SWIPE_THRESH。 */
|
||
|
||
#if 0 /* M7.2 回退位:旧「当前选中滑条行 track y」随旧手绘 draw_settings_page 退役
|
||
* (新路径在触摸按下时由 meow_listview hit 直接算 track 中心,无需全局记录)。 */
|
||
/* 当前选中行若是滑条:记录其 track 纵向位置,供触摸拖动命中判(x/w 为布局常量,
|
||
* 所有滑条行相同,可直接算;仅 y 随行变,故独立记录)。 */
|
||
static int s_sel_track_y = -1;
|
||
#endif
|
||
|
||
|
||
/* ---- 行高/位置/导航辅助 ---- */
|
||
/* 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;
|
||
}
|
||
|
||
|
||
#if 0 /* M7.2 回退位:旧「纯范围/选中可见滚动钳制 + 内容 y→行号」随旧手绘 draw_settings_page 退役
|
||
* (新路径滚动走 meow_scroll 包裹 meow_listview,钳制/命中/可见性由 SDK 接管)。
|
||
* 删除 #if 0 行并切回旧 draw_settings_page 路径即可恢复。 */
|
||
/* 滚动:纯范围钳制到 [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);
|
||
}
|
||
#endif /* M7.2 回退位:旧滚动钳制 + 内容 y→行号 */
|
||
|
||
|
||
/* ---- 值格式化/展示 ---- */
|
||
static void s_fmt_time(int sec, char *buf, int len)
|
||
{
|
||
if (sec <= 0) { snprintf(buf, len, "%s", meow_str_zh("settings.always_on", "常亮")); return; }
|
||
if (sec < 60) { snprintf(buf, len, meow_str_zh("settings.time_sec", "%d 秒"), sec); return; }
|
||
if (sec < 3600) { snprintf(buf, len, meow_str_zh("settings.time_min", "%d 分"), sec / 60); return; }
|
||
snprintf(buf, len, meow_str_zh("settings.time_hr", "%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, "%s", meow_str_zh("settings.unset", "未设置")); 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, meow_str_zh("settings.sec_val", "%d 秒"), v / 1000);
|
||
else snprintf(buf, len, meow_str_zh("settings.sec_val_f", "%.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, "%s", meow_str_zh("settings.follow_global", "跟随全局"));
|
||
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]) ? s_preset_label(r, 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, "settings_page bright_slider");
|
||
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);
|
||
int mstyle = 0; if (meow_settings_get_enum("ui.style", &mstyle) != 0) mstyle = 0;
|
||
if (mstyle == MEOW_STYLE_MIUIX) { /* MIUIx 暖色族(与 main_menu menu_bg_prepare 同色) */
|
||
if (!strcmp(bg, "blue")) menu_bg = 0x101A2E;
|
||
else if (!strcmp(bg, "green")) menu_bg = 0x14251A;
|
||
else menu_bg = 0x181310;
|
||
} else { /* MD3(既有) */
|
||
if (!strcmp(bg, "blue")) menu_bg = 0x0A1428; /* 深蓝 */
|
||
else if (!strcmp(bg, "green")) menu_bg = 0x0A2814; /* 深绿 */
|
||
else menu_bg = 0x000000; /* black(默认) */
|
||
}
|
||
/* menu_bg 仅主界面背景用(状态栏走 SDK meow_theme bg);退出设置页回主界面时经
|
||
* menu_bg_sig(含 ui.style)重build 缓存。 */
|
||
}
|
||
/* 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(); /* 状态栏文字/底色随主题刷新(整条标脏) */
|
||
}
|
||
/* §8.1.2 一键切换闭环:ui.style(md3/miuix)顶级风格族切换 → 全系统即时换肤,无需重启。
|
||
* meow_ui_set_style() 设 SDK g_style(launcher 同一进程内立即生效)+ 广播(App/状态栏下一帧换肤);
|
||
* paint_sb_right() → sb_sdk_refresh() 逼 SDK 状态栏重读 settings(含 display.background 派生)跟手换肤;
|
||
* meow_dirty_full() 让 launcher 主界面/设置页整帧重绘(s_ui_theme() 已接 ui.style → 换族配色)。
|
||
* 主界面背景缓存经 menu_bg_sig(含 ui.style,见 main_menu.c)在回主界面时自动重建。 */
|
||
else if (!strcmp(r->key, "ui.style")) {
|
||
int st = 0; meow_settings_get_enum("ui.style", &st);
|
||
meow_ui_set_style(st);
|
||
paint_sb_right();
|
||
meow_dirty_full();
|
||
}
|
||
/* 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;
|
||
}
|
||
|
||
|
||
#if 0 /* M7.2 回退位:旧手绘设置页主列表(手绘分组列表 + s_draw_switch/s_draw_slider + 自算滚动/命中)。
|
||
* 新路径走 meow_listview + meow_switch + meow_scroll + meow_select_show;删除 #if 0 行并把
|
||
* 下方「M7.2 SDK 路径」的 set_paint_page 调用切回 draw_settings_page 即可一键回退。 */
|
||
/* ---- 绘制设置页(整页画进 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);
|
||
}
|
||
}
|
||
#endif /* M7.2 回退位:旧手绘设置页主列表 */
|
||
|
||
|
||
/* 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], "%s", meow_str_zh("settings.follow_global_font", "跟随全局字体"));
|
||
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]) ? s_preset_label(r, 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;
|
||
/* M7.2:枚举/预设选择器迁 SDK meow_select_show(BottomSheet+单选列表,批次2 新控件),
|
||
* 替换自绘 s_dd_modal(后者仍供 about/网络列表/字体两级子菜单用,勿删)。 */
|
||
return meow_select_show(s_row_label(r), 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 ? meow_theme_get()->onPrimary : 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,weight) 下多 tier 的选档偏好分(越大越优先;设置页写 font.file/class 值用)。
|
||
* 关(font.load_full=0)→ common 优先(回退 gbk→full);开 → gbk 优先(回退 full→common)。 */
|
||
static int font_tier_pick_rank(const char *tier, int load_full)
|
||
{
|
||
int is_common = tier && !strcmp(tier, "common");
|
||
int is_gbk = tier && !strcmp(tier, "gbk");
|
||
int is_full = tier && !strcmp(tier, "full");
|
||
if (!load_full) {
|
||
if (is_common) return 3;
|
||
if (is_gbk) return 2;
|
||
if (is_full) return 1;
|
||
return 0;
|
||
}
|
||
if (is_gbk) return 3;
|
||
if (is_full) return 2;
|
||
if (is_common) return 1;
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* ---- 字体行「family→字重」两级子菜单(M2.6 字重结构;M2.8 三级适配)----
|
||
* 一级:列出扫描到的去重 family(class 行首项「跟随全局」=空值);选 family 后自动进入
|
||
* 二级:该 family 的可用字重(regular/bold/italic…)。选中写 out_val = 卡根相对路径
|
||
* MeowOS/fonts/<family>/<weight>/font.png(两级)或 .../<family>/<weight>/<tier>/font.png(三级,
|
||
* 或 class 行「跟随全局」=空串)。
|
||
* M2.8 三级冲突:同 (family,weight) 出现多个 tier → 选择器仍为 family→weight 两级、不新增 tier
|
||
* 级菜单;每 weight 只显一行(代表 = 按「当前加载大字库」开关选档:关=common 优先、开=gbk 优先,
|
||
* 见 font_tier_pick_rank),写值 = 该代表 tier 的卡根相对路径。
|
||
* 复用 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], "%s", "");
|
||
snprintf(fam_disp[famn], sizeof fam_disp[0], "%s",
|
||
meow_str_zh("settings.follow_global_font", "跟随全局字体")); 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(s_row_label(r), fitem, famn, cur_fam);
|
||
if (pick < 0) return 0;
|
||
if (is_cls && pick == 0) { snprintf(out_val, vlen, "%s", ""); return 1; } /* 跟随全局 */
|
||
const char *fam = fam_slug[pick];
|
||
|
||
/* ---- 二级:该 family 的字重列表(去重:同 (family,weight) 只显一行)----
|
||
* 三级结构下同 (family,weight) 出现多个 tier(common/gbk/full)→ 不新增 tier 级菜单,按
|
||
* 「当前加载大字库」开关定档:关=common 优先、开=gbk 优先(font_tier_pick_rank)。 */
|
||
int load_full = 0;
|
||
meow_settings_get_switch("font.load_full", &load_full);
|
||
char wt_pick[S_NET_MAX][32]; /* 该行代表的 weight slug */
|
||
int wt_rank[S_NET_MAX]; /* 该行当前档位偏好分(保留最高分 tier 为行代表) */
|
||
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; i++) {
|
||
if (!font_preset_fams[i] || strcmp(font_preset_fams[i], fam)) continue;
|
||
const char *wt = font_preset_wts[i] ? font_preset_wts[i] : "regular";
|
||
const char *tier = font_preset_tiers[i] ? font_preset_tiers[i] : "common";
|
||
int rank = font_tier_pick_rank(tier, load_full);
|
||
int slot = -1;
|
||
for (int k = 0; k < wn; k++) if (!strcmp(wt_pick[k], wt)) { slot = k; break; }
|
||
if (slot < 0) {
|
||
if (wn >= S_NET_MAX) break;
|
||
snprintf(wt_pick[wn], sizeof wt_pick[0], "%s", wt);
|
||
wt_rank[wn] = -1;
|
||
slot = wn; wn++;
|
||
}
|
||
if (rank > wt_rank[slot]) { /* 保留档位偏好最高的一套作为该 weight 行代表 */
|
||
wt_rank[slot] = rank;
|
||
snprintf(wt_val[slot], sizeof wt_val[0], "%s", font_presets[i]);
|
||
snprintf(wt_text[slot], sizeof wt_text[0], "%s", font_preset_labels[i]);
|
||
}
|
||
}
|
||
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];
|
||
/* 当前位置高亮:按当前 font.file 的 (family,weight) 命中所显示 weight 行(不依赖 tier 精确匹配,
|
||
* 因三级下 font.file 具体 tier 可能与该行代表 tier 不同)。 */
|
||
int cur_wt = -1;
|
||
{
|
||
char cfam2[64] = "", cwt2[32] = "";
|
||
if (cur[0]) font_family_weight_from_png(cur, cfam2, sizeof cfam2, cwt2, sizeof cwt2);
|
||
for (int i = 0; i < wn; i++)
|
||
if (cwt2[0] && !strcmp(wt_pick[i], cwt2)) { cur_wt = i; break; }
|
||
}
|
||
int wp = s_dd_modal(s_row_label(r), witem, wn, cur_wt);
|
||
if (wp < 0) return 0;
|
||
snprintf(out_val, vlen, "%s", wt_val[wp]);
|
||
return 1;
|
||
}
|
||
|
||
|
||
/* ==================================================================
|
||
* M7.2 设置页渲染层迁移(主列表迁 SDK 控件):
|
||
* - 分组列表 → meow_listview(虚拟化 set_vsource 固定 item 池 + section 段头 + set_item_draw 自绘);
|
||
* - 开关行 → 行内真 meow_switch(池化,SDK 绘制 + 按压态);
|
||
* - 滑条行 → 行内嵌 meow_slider(自绘 track/thumb,保留既有 s_draw_slider 语义色;
|
||
* 「行内嵌自绘」,M7 定案允许)——拖动实时写 settings.conf(读改写单键,
|
||
* 经 meow_settings_set_*,勿整文件覆盖——音量存盘教训);
|
||
* - 枚举/预设 → SDK meow_select_show(BottomSheet+单选列表,替换自绘 s_dd_modal);
|
||
* - 信息/列表行 → 行 right 值 + chevron(SDK 主题语义色)。
|
||
* 滚动 → meow_scroll(跟手 track + 松手 fling,同 subpage.c M7.1a 语义;刻意不 set_snap)。
|
||
* 控件 lazy 创建一次(owner=SET_SENTINEL_ACT 哨兵),跨多次进入复用;数据/动作(s_commit、
|
||
* s_set_int_value、s_net_*、s_font_dropdown、s_dropdown)逻辑层不动,仅换渲染层 + 命中;
|
||
* run_settings_page 会话骨架(触摸/滚动/返回/IPC/电源键语义)不改。旧手绘 draw_settings_page
|
||
* + s_clamp_scroll + s_row_at_content_y 保留 #if 0 作回退位(M7 红线:一键回退)。
|
||
* ================================================================== */
|
||
#define SET_SENTINEL_ACT 0x53 /* 设置页哨兵 activity(不与 launcher 其它页冲突) */
|
||
#define SET_LIST_Y SET_CONTENT_Y /* 100:列表顶缘(顶栏/标题下) */
|
||
#define SET_FOOT_H 40 /* 底部返回/版本带高 */
|
||
#define SET_ITEM_H 68 /* MD3 行高(滑条行给 track 留位) */
|
||
#define SET_SLIDER_YOFF 45 /* 滑条行内 track 顶缘相对行顶(÷s_draw_slider h=6,中心=顶+3) */
|
||
#define SET_TRACK_CY 48 /* 滑条 track 中心相对行顶 y(=SET_SLIDER_YOFF+3,与绘制对齐) */
|
||
#define SET_SLIDER_LY 12 /* 滑条行 label 顶相对行顶 y(上移给 track 留位) */
|
||
#define SET_SWITCH_POOL 12
|
||
|
||
static meow_widget *g_set_lv = NULL; /* meow_listview(虚拟化) */
|
||
static meow_scroll *g_set_sc = NULL; /* 滚动物理 */
|
||
static meow_widget *g_set_sw[SET_SWITCH_POOL]; /* 行内开关池(真 meow_switch) */
|
||
static int g_set_sw_n = 0, g_set_sw_use = 0; /* 池创建数 / 本轮已用(每次整绘复位) */
|
||
static int g_set_lv_h = 0; /* 列表视口高(选中可见性 clamp 用) */
|
||
static int g_set_init = 0;
|
||
|
||
static void set_lv_rect(int *x, int *y, int *w, int *h)
|
||
{
|
||
if (x) *x = 0;
|
||
if (y) *y = SET_LIST_Y;
|
||
if (w) *w = fb_xres;
|
||
if (h) { int t = fb_yres - SET_LIST_Y - SET_FOOT_H; if (t < 0) t = 0; *h = t; }
|
||
}
|
||
|
||
static int set_lv_count(void *ctx) { (void)ctx; return s_vis_count(); }
|
||
|
||
/* 行填充(虚拟化 pool bind;段头行 section=1、enabled=1 供 SDK 命中/段头绘制) */
|
||
static void set_lv_bind(int idx, meow_lv_item *out, void *ctx)
|
||
{
|
||
(void)ctx;
|
||
memset(out, 0, sizeof *out);
|
||
out->icon_kind = MEOW_LIST_ICON_NONE;
|
||
out->enabled = 1;
|
||
if (idx < 0 || idx >= s_vis_count()) { out->title = ""; return; }
|
||
const srow_t *r = &SMENU[s_vis_at(idx)];
|
||
out->title = r->label ? s_row_label(r) : "";
|
||
out->section = (r->ctrl == SC_HEADER) ? 1 : 0;
|
||
if (r->ctrl == SC_SWITCH) {
|
||
int on = 0; meow_settings_get_switch(r->key, &on);
|
||
out->right = MEOW_LIST_RIGHT_SWITCH; out->on = on;
|
||
}
|
||
}
|
||
|
||
/* 行内开关池:取下一个可用槽(每帧 set_lv_draw 顺序取用,整绘前复位 g_set_sw_use) */
|
||
static meow_widget *set_sw_take(void)
|
||
{
|
||
if (g_set_sw_use < g_set_sw_n) return g_set_sw[g_set_sw_use++];
|
||
if (g_set_sw_n >= SET_SWITCH_POOL) return NULL;
|
||
meow_widget *w = meow_switch_create(0, 0, 0);
|
||
if (w) { g_set_sw[g_set_sw_n++] = w; return g_set_sw[g_set_sw_use++]; }
|
||
return NULL;
|
||
}
|
||
|
||
/* 整行自绘(set_item_draw 覆盖默认 renderer;x=列表左缘、y=行顶、w/h=行全宽高,
|
||
* state=SELECTED/PRESSED/DISABLED 组合;颜色全取 meow_theme 语义色,无散落硬编码) */
|
||
static void set_lv_draw(int idx, int x, int y, int w, int h, int state, void *ctx)
|
||
{
|
||
(void)ctx; (void)h;
|
||
const meow_theme *mt = meow_theme_get();
|
||
if (idx < 0) return;
|
||
int si = s_vis_at(idx);
|
||
const srow_t *r = &SMENU[si];
|
||
int sel = (state & MEOW_LISTVIEW_ST_SELECTED);
|
||
int press = (state & MEOW_LISTVIEW_ST_PRESSED);
|
||
int dis = (state & MEOW_LISTVIEW_ST_DISABLED);
|
||
int thh = meow_ui_text_height(mt->font_body);
|
||
uint32_t textcol = sel ? mt->onPrimaryContainer : mt->onSurface;
|
||
uint32_t subcol = sel ? mt->onPrimaryContainer : mt->onSurfaceVariant;
|
||
|
||
if (r->ctrl == SC_HEADER) {
|
||
meow_ui_fill_rect(x, y, w, SET_ITEM_H, mt->surfaceContainerHigh);
|
||
int ty = y + (SET_ITEM_H - thh) / 2;
|
||
if (ty >= 0) meow_ui_text(x + 16, ty, s_row_label(r), mt->font_body, mt->primary);
|
||
meow_ui_fill_rect(x, y + SET_ITEM_H - 1, w, 1, mt->outlineVariant);
|
||
return;
|
||
}
|
||
if (sel) meow_ui_fill_rect(x, y, w, SET_ITEM_H, mt->primaryContainer);
|
||
else if (press) meow_ui_fill_rect_alpha(x, y, w, SET_ITEM_H, mt->primary, 26);
|
||
|
||
int ly = y + (SET_ITEM_H - thh) / 2; /* label 单行垂直居中 */
|
||
switch (r->ctrl) {
|
||
case SC_SWITCH: {
|
||
meow_ui_text(x + 16, ly, s_row_label(r), mt->font_body, textcol);
|
||
meow_widget *sw = set_sw_take();
|
||
if (sw) {
|
||
int on = 0; meow_settings_get_switch(r->key, &on);
|
||
meow_widget_set_rect(sw, x + w - 16 - 56, y + (SET_ITEM_H - 32) / 2, 56, 32);
|
||
meow_switch_set(sw, on);
|
||
meow_widget_draw(sw);
|
||
}
|
||
break;
|
||
}
|
||
case SC_SLIDER: {
|
||
/* label 上移一行,track 放行下方(中心=SET_SLIDER_YOFF+3=48),与拖动命中带对齐 */
|
||
meow_ui_text(x + 16, y + SET_SLIDER_LY, s_row_label(r), mt->font_body, textcol);
|
||
char vb[32]; s_fmt_value(r, vb, sizeof vb);
|
||
meow_ui_text(x + w - 16 - meow_ui_text_width(vb, mt->font_body), y + SET_SLIDER_LY, vb, mt->font_body, subcol);
|
||
int v = 0; meow_settings_get_int(r->key, &v);
|
||
/* 息屏时间为档位索引型(秒→索引 等距画);其余 clamp 到 [min,max] */
|
||
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; }
|
||
s_draw_slider(S_SLIDER_X, y + SET_SLIDER_YOFF, S_SLIDER_W, v, r->min, r->max, s_ui_theme());
|
||
break;
|
||
}
|
||
case SC_INFO: {
|
||
meow_ui_text(x + 16, ly, s_row_label(r), mt->font_body, textcol);
|
||
char vb[48]; s_net_get(r, vb, sizeof vb);
|
||
meow_ui_text(x + w - 16 - meow_ui_text_width(vb, mt->font_body), ly, vb, mt->font_body, subcol);
|
||
break;
|
||
}
|
||
case SC_LIST: {
|
||
meow_ui_text(x + 16, ly, s_row_label(r), mt->font_body, textcol);
|
||
char vb[48]; s_net_get(r, vb, sizeof vb);
|
||
int vx = x + w - 16 - 12 - meow_ui_text_width(vb, mt->font_body);
|
||
meow_ui_text(vx, ly, vb, mt->font_body, subcol);
|
||
s_draw_chevron(x + w - 16, y + SET_ITEM_H / 2, subcol);
|
||
break;
|
||
}
|
||
default: { /* SC_ENUM / SC_PRESET */
|
||
meow_ui_text(x + 16, ly, s_row_label(r), mt->font_body, textcol);
|
||
char vb[40]; s_display_value(r, vb, sizeof vb);
|
||
int vx = x + w - 16 - meow_ui_text_width(vb, mt->font_body);
|
||
meow_ui_text(vx, ly, vb, mt->font_body, subcol);
|
||
s_draw_chevron(vx - 8, y + SET_ITEM_H / 2, subcol);
|
||
break;
|
||
}
|
||
}
|
||
meow_ui_fill_rect(x, y + SET_ITEM_H - 1, w, 1, mt->outlineVariant);
|
||
if (dis) meow_ui_fill_rect_alpha(x, y, w, SET_ITEM_H, mt->onSurface, 90);
|
||
}
|
||
|
||
/* ---- meow_scroll 回调(包 listview)---- */
|
||
static void set_sc_set(void *ctx, int off) { (void)ctx; if (g_set_lv) meow_listview_set_scroll(g_set_lv, off); }
|
||
static int set_sc_max(void *ctx) { (void)ctx; return g_set_lv ? meow_listview_max_scroll(g_set_lv) : 0; }
|
||
static int set_sc_get(void *ctx) { (void)ctx; return g_set_lv ? meow_listview_scroll(g_set_lv) : 0; }
|
||
|
||
/* 列表区重绘(含内容底 + listview),供 fling/滚动局部刷。 */
|
||
static void set_redraw_region(void *ctx)
|
||
{
|
||
(void)ctx;
|
||
int lvx, lvy, lvw, lvh; set_lv_rect(&lvx, &lvy, &lvw, &lvh);
|
||
if (lvh <= 0) return;
|
||
const meow_ui_theme_t *th = s_ui_theme();
|
||
meow_ui_fill_rect(lvx, lvy, lvw, lvh, th->surface_variant);
|
||
if (g_set_lv) { g_set_sw_use = 0; meow_widget_draw(g_set_lv); }
|
||
meow_dirty_add(lvx, lvy, lvw, lvh);
|
||
meow_ui_flush();
|
||
}
|
||
|
||
/* 选中行可见性:滚动到选中行不越界(仅靠 tap 触发,选中行即在视野内,防御性兜底)。 */
|
||
static void set_ensure_sel_visible(int sel)
|
||
{
|
||
if (!g_set_lv) return;
|
||
int p = s_vis_of(sel); if (p < 0) return;
|
||
int scroll = meow_listview_scroll(g_set_lv);
|
||
int top = p * SET_ITEM_H, bot = top + SET_ITEM_H;
|
||
if (top < scroll) meow_listview_set_scroll(g_set_lv, top);
|
||
else if (bot > scroll + g_set_lv_h) meow_listview_set_scroll(g_set_lv, bot - g_set_lv_h);
|
||
}
|
||
|
||
/* 创建设置页控件集(owner=SET_SENTINEL_ACT;仅首进创建一次,跨次进入复用)。 */
|
||
static void set_widgets_create(void)
|
||
{
|
||
meow_ui_activity_push(SET_SENTINEL_ACT);
|
||
int lvx, lvy, lvw, lvh; set_lv_rect(&lvx, &lvy, &lvw, &lvh);
|
||
g_set_lv_h = lvh;
|
||
g_set_lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE);
|
||
if (g_set_lv) {
|
||
meow_listview_set_vsource(g_set_lv, set_lv_count, set_lv_bind, NULL);
|
||
meow_listview_set_item_draw(g_set_lv, set_lv_draw, NULL);
|
||
meow_listview_set_item_h(g_set_lv, SET_ITEM_H);
|
||
/* 注:刻意不 set_snap —— SDK meow_scroll 的 snap 以「fling 起点」为吸附目标会往回拉 */
|
||
}
|
||
g_set_sc = meow_scroll_create(set_sc_set, set_sc_max, set_sc_get, set_redraw_region, NULL);
|
||
meow_ui_activity_pop();
|
||
g_set_init = 1;
|
||
}
|
||
|
||
/* 整页绘制(进 back_buf;调用方 meow_dirty_full/meow_dirty_add + flush)。sel=当前选中(SMENU 下标)。 */
|
||
static void set_paint_page(int sel)
|
||
{
|
||
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();
|
||
if (mfont_ready) ui_draw(24, 50, meow_str_zh("menu.settings", "系统设置"), 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);
|
||
fill_rect(0, SET_CARD_Y, W, H - SET_CARD_Y, th->surface_variant);
|
||
if (g_set_lv) {
|
||
g_set_sw_use = 0;
|
||
int p = s_vis_of(sel); if (p >= 0) meow_listview_set_sel(g_set_lv, p);
|
||
meow_widget_draw(g_set_lv);
|
||
}
|
||
/* 底部:返回(左)+ 版本(右),复用应用列表底部对齐逻辑 */
|
||
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, meow_str_zh("settings.back", "返回"), th->on_surface_variant, 20);
|
||
ui_draw(fb_xres - 8 - ui_width(ver, 20), foot_y, ver, th->outline, 20);
|
||
}
|
||
|
||
/* ---- 运行设置页(独立子循环;run_menu 触摸命中设置行后调用)---- */
|
||
void run_settings_page(void)
|
||
{
|
||
int sel = s_first_setting();
|
||
int H = fb_yres;
|
||
unsigned long long t_sets = now_ms(); /* M3 取证:进设置页耗时分段(真机核对用) */
|
||
|
||
/* M2.6:进设置页扫描一次 TF 卡 MeowOS/fonts/<family>/<weight>/,刷新「字体文件」下拉。
|
||
* 进设置时卡通常在位;此步只枚举/校验,不加载(加载由 meow_font_ensure 按 font.file 决定)。
|
||
* M3:verify=0 —— 跳过逐套 sha256 校验(gbk 套 6.8MB 级,逐套双 sha256 真机耗数秒),
|
||
* 只枚举+读 font.json+落 index,入口不再被 sha256sum 拖住;完整性校验留给启动全量扫描。 */
|
||
meow_font_scan_presets(0);
|
||
logmsg("settings entry: font rescan %llums", now_ms() - t_sets);
|
||
|
||
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 覆盖过状态栏,需整帧重绘还原 */
|
||
unsigned long long net_refresh_pump_at = 0; /* M3:异步网络刷新下一推进时刻(0=立即) */
|
||
|
||
/* 触摸拖动滑条状态 */
|
||
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 drag_last_y = 0; /* M7.2:滚动跟手增量基准(meow_scroll_track 速度采样) */
|
||
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 同款) */
|
||
|
||
/* 首次全帧绘制 */
|
||
/* M2.39/M3:开关行同步真实硬件态(快)+ 异步 kick 网络刷新 —— 不再在入口同步跑
|
||
* wpa_cli/bluetoothctl 串行 popen(真机 ≥5s 卡顿根因);状态/列表由下方主循环逐步骤
|
||
* s_net_refresh_pump() 填充,draw 用缓存(g_net_*),数据变化即 need_paint。 */
|
||
s_net_sync_from_sys();
|
||
logmsg("settings entry: net toggle-sync+kick %llums", now_ms() - t_sets);
|
||
if (!g_set_init) set_widgets_create(); /* M7.2:lazy 建 SDK 控件集(一次,跨次进入复用) */
|
||
meow_ui_activity_push(SET_SENTINEL_ACT); /* 控件 owner 闸门打开(hit/按压态生效) */
|
||
set_paint_page(sel);
|
||
meow_dirty_full(); meow_ui_flush();
|
||
logmsg("settings entry: page shown in %llums", now_ms() - t_sets);
|
||
|
||
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");
|
||
set_paint_page(sel);
|
||
meow_dirty_full(); meow_ui_flush();
|
||
}
|
||
usleep(POLL_MS * 1000);
|
||
continue;
|
||
}
|
||
|
||
/* ---- M3:异步网络刷新(每 ~150ms 推进一步,每步一个有界 popen)----
|
||
* 入口已同步 draw(用 g_net_* 缓存),此处逐步填充 SSID/已保存/附近/蓝牙角色,
|
||
* 数据变化则 need_paint=1 让行值即时刷新,绝不在某一同步调用里连续跑数条 popen。 */
|
||
if (now >= net_refresh_pump_at) {
|
||
net_refresh_pump_at = now + 150;
|
||
if (s_net_refresh_pump()) need_paint = 1;
|
||
}
|
||
|
||
/* ---- 按键 ---- */
|
||
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 >= (unsigned long long)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 >= (unsigned long long)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 < (unsigned long long)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;
|
||
meow_input_touch_set(1, tdx, tdy, 0); /* 喂 SDK 输入态(行按压态渲染) */
|
||
scroll_y0 = tdy; /* 列表拖动基准:按下原点 */
|
||
/* 按下原点落在滑条行 track 带内 → 开始拖动(meow_listview hit 判行) */
|
||
int vp = g_set_lv ? meow_listview_hit(g_set_lv, tlx, tly) : -1;
|
||
if (vp >= 0 && drag < 0) {
|
||
const srow_t *rr = &SMENU[s_vis_at(vp)];
|
||
if (rr->ctrl == SC_SLIDER) {
|
||
int track_cy = SET_LIST_Y + vp * SET_ITEM_H -
|
||
meow_listview_scroll(g_set_lv) + SET_TRACK_CY;
|
||
if (tlx >= S_SLIDER_X - 12 && tlx <= S_SLIDER_X + S_SLIDER_W + 12 &&
|
||
tly >= track_cy - 14 && tly <= track_cy + 14) {
|
||
drag = s_vis_at(vp);
|
||
int v = s_slider_val_from_x(rr, tlx);
|
||
s_slider_set_from_track(rr, v, 0);
|
||
need_paint = 1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
/* M7.2 列表拖动滚动:非滑条行按下后纵向位移超阈值 → 进入列表滚动。
|
||
* 走 meow_scroll(跟手 track + 松手 fling),同 subpage.c M7.1a 语义;
|
||
* 方向经 input.swipe_invert 切换(默认手指上滑 → 内容下翻)。 */
|
||
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);
|
||
meow_input_touch_set(0, 0, 0, 0); /* 滚动中不压行(清按压态) */
|
||
drag_last_y = tly;
|
||
}
|
||
}
|
||
if (scroll_drag && td_ok) {
|
||
int dydt = tly - drag_last_y; /* 本次位移增量(跟手) */
|
||
drag_last_y = tly;
|
||
int scdy = scroll_invert ? dydt : -dydt;
|
||
if (g_set_sc && scdy) meow_scroll_track(g_set_sc, scdy, now);
|
||
/* 列表滚动不在此即时重绘:整批事件末尾由下方「if (scroll_drag && td_ok)
|
||
* set_redraw_region」合并为一次视口局部重绘。此处若 set_redraw_region 且再设
|
||
* need_paint=1,底部 set_paint_page 会再走一遍整页重绘 + dirty_add(0,42,...)
|
||
* 近整屏 flush —— 快滑每帧整页重绘 → 卡顿,违背「局部脏区不整屏重绘」铁律;
|
||
* 且一个 poll 多个移动事件各自 flush 会多次 FBIO_WAITFORVSYNC 阻塞放大延迟。 */
|
||
}
|
||
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;
|
||
meow_input_touch_set(0, 0, 0, 0); /* 抬手:清 SDK 按压态 */
|
||
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 */
|
||
if (g_set_sc) meow_scroll_fling(g_set_sc); /* 松手 fling(惯性 ease-out) */
|
||
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;
|
||
}
|
||
/* M7.2:行命中走 meow_listview(含滚动换算 + 禁用/越界行不响应);
|
||
* 段头行 enabled=1 故 hit 亦命中 → 下方按 ctrl==SC_HEADER 分流。 */
|
||
int vp = g_set_lv ? meow_listview_hit(g_set_lv, tx, ty) : -1;
|
||
int idx = (vp >= 0) ? s_vis_at(vp) : -1;
|
||
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 分支)。
|
||
* M2.76:WiFi/蓝牙升级为独立子页面 → 点开进 run_wifi_page/
|
||
* run_bt_page(阻塞,内部自开 fds;返回后仍须排空本页 tfd)。 */
|
||
after_modal = 1;
|
||
if (r->net_list == NET_LIST_WIFI_PAGE) { run_wifi_page(); s_net_refresh_kick(); }
|
||
else if (r->net_list == NET_LIST_BT_PAGE) { run_bt_page(); s_net_refresh_kick(); }
|
||
else if (r->net_list == NET_LIST_PIN_PAGE) { run_lock_settings_page(); s_net_refresh_kick(); }
|
||
else 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_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_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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
/* 列表拖动滚动:本批连续触摸事件合并为一次视口局部重绘(只刷列表区,
|
||
* 最多一次 present),不再触发 need_paint 整页重绘。scroll_drag/td_ok 在
|
||
* 抬手处置 0(抬手那轮已走 meow_scroll_fling + need_paint 整页),故此处只兜活动滚动。 */
|
||
if (scroll_drag && td_ok) set_redraw_region(NULL);
|
||
}
|
||
|
||
/* ---- 滚动 + 重绘 ---- */
|
||
if (need_paint) {
|
||
last_act = now;
|
||
/* 仅选中行变化时强制其可见;列表拖动滚动时保留用户滚到的位置,不把选中拉回视野 */
|
||
if (sel_changed) { set_ensure_sel_visible(sel); sel_changed = 0; }
|
||
set_paint_page(sel);
|
||
/* 局部标脏:标题+卡片+滚动条+底部(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();
|
||
}
|
||
/* M7.2: 推进 fling/回弹动画(meow_scroll 惯性停;无动画零开销) */
|
||
meow_anim_tick();
|
||
usleep(POLL_MS * 1000);
|
||
}
|
||
done:
|
||
meow_input_touch_set(0, 0, 0, 0); /* M7.2 防残留按压态 */
|
||
meow_ui_activity_pop(); /* 关 owner 闸门(控件 owner 仍钉哨兵,复用) */
|
||
if (tfd >= 0) close(tfd);
|
||
kp_close(&kp_power); kp_close(&kp_keys);
|
||
logmsg("settings page closed");
|
||
}
|
||
|