diff --git a/patch_v2/meow_watchdog/meow_watchdog b/patch_v2/meow_watchdog/meow_watchdog index 403541a..2e6c4ab 100755 Binary files a/patch_v2/meow_watchdog/meow_watchdog and b/patch_v2/meow_watchdog/meow_watchdog differ diff --git a/patch_v2/meow_watchdog/meow_watchdog.c b/patch_v2/meow_watchdog/meow_watchdog.c index 78ff440..48b4a80 100644 --- a/patch_v2/meow_watchdog/meow_watchdog.c +++ b/patch_v2/meow_watchdog/meow_watchdog.c @@ -31,6 +31,9 @@ * (true|1|yes)。判定为 SDK 框架 App -> 音量/媒体键让位 SDK 输入层(meow_ui_input.c 已消化, * 防同一次按键双步进);电源仍由本进程兜底(SDK 对电源是 no-op)。裸自绘 App(framework=0/ * 无 framework)或 app.json 不可读 -> 音量/媒体键仍由本进程兜底(不造成功能真空)。 + * - M20 亮屏亮度:settings.conf 的 display.brightness(clamp 到 brightness_min..100)为唯一 + * 权威;settings 瞬时不可读用**真实观测快照**;两者都未知时**不写背光**(不再回退固定 60, + * 那会覆盖 launcher 刚设好的用户值 → 开机/亮屏跳 60)。面板已黑且无任何值来源才写安全低亮。 * Exits when the app process disappears (kill(pid,0) -> ESRCH) or on * SIGTERM from the parent. * @@ -192,16 +195,44 @@ static int g_fw_app = 0; /* ---- M2.22(A)+ M2.24 电源键接管:息屏/亮屏 + 长按菜单/强关 ---- */ static int wd_fbfd = -1; /* fb0 fd(main 里打开,供 FBIOBLANK/FBIOPAN 用) */ -static int wd_saved_bri = 60; /* 熄屏前亮度快照 */ + +/* M20(远古 bug 回归:开机/亮屏背光跳到固定 60):0 是「未知」哨兵,不是亮度值 + * (0 = 面板全黑/息屏,任何情况下都不是有效用户亮度)。旧代码让 60 同时表达 + * 「未知快照」和「兜底值」,于是一旦没有可信来源就把 60 写进背光,覆盖 launcher + * 刚按 display.brightness 设好的用户值(真机 0.8.46:用户亮度 1,亮屏仍写 60)。 */ +#define WD_BRI_UNKNOWN 0 +/* 唯一保留的固定亮度:settings 读不到 + 快照未知 + **当前亮度确实是 0**(面板黑)时才用。 + * 这条退路上不存在任何可保留的有效用户值,不写就是永久黑屏;取低亮(≈10% of + * max_brightness 101)而不是 60 —— 宁可暗到用户手动调亮,也不要夜里突然刺眼。 + * 注意 60 是固件 S11jpeg_display_shell 画完开机 logo 后写死的值(/etc/init.d,不在本仓库), + * 本进程任何分支都不再写 60。 */ +#define WD_BRI_SAFE_LOW 10 +/* M20 短期修复 A:settings 读取重试。settings.conf 由 launcher/audiod 以 + * 「写 .tmp + rename」原子替换,开机早期 /usr/data(UBIFS) 也可能尚未就绪, + * fopen 存在瞬时失败窗口。3 次 × 10ms 只覆盖该窗口:最坏总阻塞 (3-1)*10 = 20ms, + * 且只在失败时发生(成功路径 0 开销)。亮屏轮询粒度本身是 REFRESH_MS=100ms, + * 20ms 在「按键→亮屏」路径上不可感知(见 M20 报文)。 */ +#define WD_SETTINGS_TRIES 3 +#define WD_SETTINGS_GAP_MS 10 + +static int wd_saved_bri = WD_BRI_UNKNOWN; /* 熄屏前亮度快照;未知=WD_BRI_UNKNOWN(不再用 60) */ static int pwr_down = 0; /* 电源当前按住 */ static unsigned long long pwr_down_ms = 0; static int pwr_woke = 0; /* 按下时屏已黑 → 本次只作唤醒,松开不再 toggle */ static int pwr_menu_notified = 0; /* 已写 /tmp/meow_power_menu 请 launcher 弹菜单 */ static int pwr_force_done = 0; /* 8s 强关已发(防重复) */ +/* 背光 sysfs 路径(缺省 BACKLIGHT;env MEOW_BACKLIGHT 覆盖,供宿主测试隔离——与 + * MEOW_SETTINGS_FILE 同款测试钩子,真机无 env 走默认,行为不变)。 */ +static const char *wd_backlight_path(void) +{ + const char *e = getenv("MEOW_BACKLIGHT"); + return (e && e[0]) ? e : BACKLIGHT; +} + static int read_brightness(void) { - int fd = open(BACKLIGHT, O_RDONLY); + int fd = open(wd_backlight_path(), O_RDONLY); if (fd < 0) return -1; char buf[16]; int n = read(fd, buf, sizeof buf - 1); @@ -215,7 +246,7 @@ static void write_brightness(int val) { char buf[8]; int l = snprintf(buf, sizeof buf, "%d\n", val); - int fd = open(BACKLIGHT, O_WRONLY); + int fd = open(wd_backlight_path(), O_WRONLY); if (fd < 0) return; if (write(fd, buf, l) < 0) wd_log("backlight write %d: %s", val, strerror(errno)); close(fd); @@ -231,16 +262,24 @@ static const char *wd_settings_path(void) /* M6.23:亮屏恢复亮度以**用户设置**为准(/usr/data/meow/settings.conf 的 display.brightness), * 而非 wd_saved_bri 快照——该快照可能被污染:panel OFF 前 read_brightness() 可能读到 launcher - * 先写好的 0(launcher meow_panel_off 已 write_brightness(0)),`wd_saved_bri=(br>0)?br:60` 便落到 + * 先写好的 0(launcher meow_panel_off 已 write_brightness(0)),旧代码 `(br>0)?br:60` 便落到 * 默认 60;panel ON 恢复 60 与 launcher 按用户设置恢复 7 竞态 → 亮屏亮度跳变(真机「跳高到 60 / - * 又降回 7」,2026-09-03)。 + * 又降回 7」,2026-09-03)。旧公式里的 `:60` 已在 M20 移除。 * 与 launcher meow_settings_brightness_clamped() 同源(同文件、同 clamp:下限 display.brightness_min * 默认 1=极暗但可见、上限 100=防闪屏);本进程自身 wd_vol_load 早已读该文件,不引入 launcher 全量 * 依赖,保持 watchdog 独立性。返回 clamped 值;settings 文件不可读(S70 早启动 /usr/data 未就绪) - * → 返回 -1,调用方回退 wd_saved_bri(保持旧行为,防开机竞态)。 */ + * M20 起:fopen 失败做 WD_SETTINGS_TRIES 次、间隔 WD_SETTINGS_GAP_MS 的短重试, + * 覆盖 settings.conf 原子替换 / 开机早期 /usr/data 未就绪的瞬时窗口;仍失败才返回 -1。 + * 只重试 open:rename 是原子的,已打开的 fd 指向旧 inode、内容完整,读内容无需重试。 + * 返回 -1 时调用方**不再**回退「默认 60」,取值语义统一由 wd_bri_decide() 决定。 */ static int wd_settings_brightness(void) { - FILE *f = fopen(wd_settings_path(), "r"); + FILE *f = NULL; + for (int i = 0; i < WD_SETTINGS_TRIES; i++) { + f = fopen(wd_settings_path(), "r"); + if (f) break; + if (i + 1 < WD_SETTINGS_TRIES) usleep(WD_SETTINGS_GAP_MS * 1000); + } if (!f) return -1; int b = 101; /* display.brightness 默认(与 launcher 设置表同值,键缺失时 clamp→100) */ int mn = 1; /* display.brightness_min 默认(极暗但可见;键缺失时保底) */ @@ -276,26 +315,53 @@ static void wd_pan_buf0_force(void) ioctl(wd_fbfd, FBIOPAN_DISPLAY, v); } +/* 亮屏亮度决策(纯函数:宿主单测直接断言,见 test_wd_fw.c)。 + * 返回 >0 = 要写入的背光值;返回 -1 = **不写背光**(保持当前值)。 + * *src 输出取值来源,原样写进日志供真机排查: + * settings settings.conf 的 display.brightness(已 clamp)—— 唯一权威 + * snapshot 本次运行期熄屏前读到的真实亮度(>0 才算数)—— settings 瞬时不可读时的过渡值 + * untouched settings 与快照都未知、但面板已亮 → 不覆盖现场(M20 核心修复) + * safe-low settings/快照都未知且面板当前为 0(黑)→ 点亮,用低亮而不是 60 + * 优先级:settings → 快照(真实近似值)→ 保持现状 → 安全低亮。 + * 任何分支都不再写「固定 60」:60 只在固件 S11 画完开机 logo 后写一次(固件侧问题,非本进程)。 */ +static int wd_bri_decide(int user, int saved, int cur, const char **src) +{ + if (user > 0) { *src = "settings"; return user; } /* 用户设置优先 */ + if (saved > 0) { *src = "snapshot"; return saved; } /* 真实观测到的近似值 */ + if (cur > 0) { *src = "untouched"; return -1; } /* 已有可见亮度:绝不覆盖 */ + *src = "safe-low"; return WD_BRI_SAFE_LOW; /* 面板黑 + 无任何值来源 */ +} + static void wd_panel_off(void) { int br = read_brightness(); - wd_saved_bri = (br > 0) ? br : 60; + /* M20:快照只记「真实观测到的正亮度」。br<=0(读失败,或 launcher meow_panel_off 抢先 + * 写成 0)时保留上一次已知真实值,绝不写死 60 —— 否则 60 会在下次亮屏被当成用户值回放 + * (远古 bug 的燃料)。真的一次都没观测到正亮度时保持 WD_BRI_UNKNOWN。 */ + if (br > 0) wd_saved_bri = br; write_brightness(0); if (wd_fbfd >= 0) ioctl(wd_fbfd, FBIOBLANK, 4); /* 面板下电 + 挂起 goodix 触摸 */ - wd_log("power: panel OFF (was %d -> 0, blank 4)", br); + wd_log("power: panel OFF (was %d -> 0, blank 4, saved=%d)", br, wd_saved_bri); } static void wd_panel_on(void) { if (wd_fbfd >= 0) ioctl(wd_fbfd, FBIOBLANK, 0); - /* M6.23:恢复亮度以**用户设置**为准(wd_settings_brightness),settings 不可用时才回退 - * wd_saved_bri 快照(再退默认 60)——与 launcher meow_panel_on 同语义,杜绝「亮屏恢复 60 - * 与 launcher 恢复 7 竞态 → 跳变」。 */ - int b = wd_settings_brightness(); - int restore = (b > 0) ? b : (wd_saved_bri > 0 ? wd_saved_bri : 60); - write_brightness(restore); + /* M6.23 + M20:用户设置为唯一权威;settings 瞬时不可读用真实快照;两者都未知时 + * **不写背光**(旧代码在这里写死 60,直接覆盖 launcher 刚按 display.brightness 设好的 + * 用户值 → 开机/亮屏跳 60,真机 0.8.46 实证)。取值分支见 wd_bri_decide()。 */ + int user = wd_settings_brightness(); /* >0=用户设置;-1=不可读 */ + int cur = read_brightness(); /* 当前系统背光:判定「不写」是否安全 + 日志 */ + const char *src = "?"; + int restore = wd_bri_decide(user, wd_saved_bri, cur, &src); + if (restore > 0) write_brightness(restore); wd_pan_buf0_force(); /* 唤醒后强制回 buffer0 */ - wd_log("power: panel ON (blank 0 + brightness %d, user=%d saved=%d)", restore, b, wd_saved_bri); + if (restore > 0) + wd_log("power: panel ON (blank 0 + brightness %d src=%s user=%d saved=%d cur=%d)", + restore, src, user, wd_saved_bri, cur); + else + wd_log("power: panel ON (blank 0 + brightness UNTOUCHED cur=%d src=%s user=%d saved=%d)", + cur, src, user, wd_saved_bri); } /* launcher 正在画电源菜单(/tmp/meow_power_menu_active 存在)→ watchdog 完全让位。 */ diff --git a/patch_v2/meow_watchdog/test_wd_fw b/patch_v2/meow_watchdog/test_wd_fw index 4da5ce5..9ec00be 100755 Binary files a/patch_v2/meow_watchdog/test_wd_fw and b/patch_v2/meow_watchdog/test_wd_fw differ diff --git a/patch_v2/meow_watchdog/test_wd_fw.c b/patch_v2/meow_watchdog/test_wd_fw.c index 567091c..a62f46c 100644 --- a/patch_v2/meow_watchdog/test_wd_fw.c +++ b/patch_v2/meow_watchdog/test_wd_fw.c @@ -1,11 +1,14 @@ -/* test_wd_fw.c — 宿主单测:meow_watchdog 的「SDK 框架 App 判定」逻辑(M2.86, issue 226)。 +/* test_wd_fw.c — 宿主单测:meow_watchdog 的纯逻辑(M2.86 框架 App 判定 + M20 亮屏亮度)。 * - * 编译:#include meow_watchdog.c(去 main 拿 wd_app_is_framework/wd_json_str/wd_framework_from_pid)。 + * 编译:#include meow_watchdog.c(去 main 拿 wd_app_is_framework/json_str/from_pid、 + * wd_bri_decide、wd_settings_brightness、wd_panel_off/on)。 * gcc -Wall -Wextra -Os -DWd_NO_MAIN -Wno-unused-function -Wno-unused-parameter \ * test_wd_fw.c -o test_wd_fw * (meow_watchdog.c 含仅 main 使用的静态函数,宿主去 main 后须 -Wno-unused-function 静音; * mips 目标构建不定义 WD_NO_MAIN,不受影响。) * 运行:./test_wd_fw;全 PASS -> exit 0。 + * M20 用例用 env MEOW_BACKLIGHT / MEOW_SETTINGS_FILE 把背光与 settings 指到临时文件, + * 真的走 wd_panel_off/wd_panel_on 的读写路径(真机无这两个 env,走 /sys 与 /usr/data 默认)。 */ #define WD_NO_MAIN #include "meow_watchdog.c" @@ -24,6 +27,14 @@ static void check(const char *name, int got, int want) else { g_fail++; printf("FAIL %s: got %d want %d\n", name, got, want); } } +/* 临时目录根:优先 $TMPDIR(Termux/Android 上 /tmp 属于 shell 用户、本进程不可写), + * 否则 /tmp(Linux 构建容器)。让 `build_watchdog.sh host` 在两种环境都能直接跑。 */ +static const char *tmp_root(void) +{ + const char *t = getenv("TMPDIR"); + return (t && t[0]) ? t : "/tmp"; +} + /* ---- M2.87:SIGUSR1 手势让位(handle_touch 左缘滑动分支)---- * 判定(meow_watchdog.c):`!g_fw_app && immersive` 才发 SIGUSR1(只给裸沉浸 App); * SDK 框架 App(g_fw_app=1)不发(返回由 SDK on_swipe 处理),防 SIGUSR1 杀死 App。 */ @@ -68,10 +79,155 @@ static int expect_fw(const char *base, const char *name, const char *appjson, in return 1; } +/* ---- M20:亮屏亮度决策(wd_bri_decide 纯函数)---- + * 真机 bug(0.8.46,2026-11-05):settings display.brightness=1,亮屏时 watchdog 写入固定 60 + * 覆盖它。新语义:settings 优先 → 真实快照 → 面板已亮则不写 → 面板黑才写安全低亮。 */ +static void test_bri_decide(void) +{ + const char *src = NULL; + int v; + + v = wd_bri_decide(1, 60, 60, &src); /* 用户值 1 对上被污染的 60/当前 60:必须写 1 */ + check("bri: settings wins over saved/cur", v == 1 && !strcmp(src, "settings"), 1); + v = wd_bri_decide(7, 0, 7, &src); + check("bri: settings 7", v == 7 && !strcmp(src, "settings"), 1); + v = wd_bri_decide(-1, 42, 3, &src); /* settings 不可读 → 用真实快照 */ + check("bri: snapshot fallback", v == 42 && !strcmp(src, "snapshot"), 1); + v = wd_bri_decide(-1, 0, 3, &src); /* 都未知但面板已亮 → 不写(核心修复) */ + check("bri: untouched keeps cur", v == -1 && !strcmp(src, "untouched"), 1); + v = wd_bri_decide(-1, 0, 0, &src); /* 都未知且面板黑 → 安全低亮,绝不 60 */ + check("bri: safe-low not 60", v == WD_BRI_SAFE_LOW && v != 60 && v > 0 && + !strcmp(src, "safe-low"), 1); + v = wd_bri_decide(-1, 0, -1, &src); /* 背光读失败按「黑」处理,仍要点亮 */ + check("bri: read-fail -> safe-low", v == WD_BRI_SAFE_LOW && !strcmp(src, "safe-low"), 1); + v = wd_bri_decide(0, -1, 5, &src); /* 非正值一律按「未知」,不当作亮度 */ + check("bri: non-positive treated unknown", v == -1 && !strcmp(src, "untouched"), 1); +} + +/* ---- M20:settings 解析 + 重试 ---- + * 用真机 /usr/data/meow/settings.conf 的关键行断言「路径/键名/解析/clamp 都是对的」, + * 证明真机写 60 不是本函数返回 -1(原因见报文:出货二进制根本没编进这段代码)。 */ +static void test_settings_brightness(void) +{ + char path[600]; + snprintf(path, sizeof path, "%s/wdsetXXXXXX", tmp_root()); + int fd = mkstemp(path); + if (fd < 0) { g_fail++; printf("FAIL settings: mkstemp\n"); return; } + FILE *f = fdopen(fd, "w"); + fputs("# meow-settings 1\n" + "display.brightness = 1\n" + "display.brightness_min = 1\n" + "audio.volume = 10\n", f); + fclose(f); + setenv("MEOW_SETTINGS_FILE", path, 1); + check("settings: device file -> 1", wd_settings_brightness(), 1); + + f = fopen(path, "w"); /* clamp 抬到 brightness_min */ + fputs("display.brightness = 0\ndisplay.brightness_min = 3\n", f); + fclose(f); + check("settings: clamp up to min", wd_settings_brightness(), 3); + + f = fopen(path, "w"); /* clamp 压到 100 */ + fputs("display.brightness = 200\ndisplay.brightness_min = 1\n", f); + fclose(f); + check("settings: clamp down to 100", wd_settings_brightness(), 100); + + f = fopen(path, "w"); /* 键缺失:与 launcher 设置表同默认 101 -> clamp 100 */ + fputs("audio.volume = 10\n", f); + fclose(f); + check("settings: missing key -> 100", wd_settings_brightness(), 100); + + /* 不可读(原子替换/未挂载窗口):open 重试 3 次仍失败才 -1,总耗时 ≈ 20ms */ + unlink(path); + unsigned long long t0 = now_ms(); + int r = wd_settings_brightness(); + unsigned long long dt = now_ms() - t0; + check("settings: missing -> -1", r, -1); + check("settings: open retried (>=15ms)", dt >= 15, 1); + unsetenv("MEOW_SETTINGS_FILE"); +} + +static void put_file(const char *path, const char *body) +{ + FILE *f = fopen(path, "w"); + if (!f) return; + fputs(body, f); + fclose(f); +} + +static int get_file_int(const char *path) +{ + FILE *f = fopen(path, "r"); + if (!f) return -1; + int v = -1; + if (fscanf(f, "%d", &v) != 1) v = -1; + fclose(f); + return v; +} + +/* ---- M20:panel OFF/ON 端到端(真的读写一个「背光文件」,不 mock 函数)---- + * 复现真机场景:settings display.brightness=1 → 熄屏 → 亮屏。旧代码亮屏写 60, + * 新代码必须写 1;且 settings 不可读、快照未知时保持当前值而不是回退 60。 */ +static void test_panel_brightness_e2e(void) +{ + char bl[600], st[600]; + snprintf(bl, sizeof bl, "%s/wdblXXXXXX", tmp_root()); + snprintf(st, sizeof st, "%s/wdstXXXXXX", tmp_root()); + int fd = mkstemp(bl); + if (fd < 0) { g_fail++; printf("FAIL e2e: mkstemp bl\n"); return; } + close(fd); + fd = mkstemp(st); + if (fd < 0) { g_fail++; printf("FAIL e2e: mkstemp st\n"); unlink(bl); return; } + close(fd); + setenv("MEOW_BACKLIGHT", bl, 1); + setenv("MEOW_SETTINGS_FILE", st, 1); + + /* 1) 真机 bug 场景:settings=1、屏亮 1 → OFF 快照 1;ON 必须写 1,绝不 60 */ + put_file(bl, "1\n"); + put_file(st, "display.brightness = 1\ndisplay.brightness_min = 1\n"); + wd_saved_bri = WD_BRI_UNKNOWN; + wd_panel_off(); + check("e2e: panel OFF writes 0", get_file_int(bl), 0); + check("e2e: panel OFF snapshot = 1", wd_saved_bri, 1); + wd_panel_on(); + check("e2e: panel ON restores settings 1 (not 60)", get_file_int(bl), 1); + + /* 2) 用户改了亮度:settings 仍是唯一权威(旧快照 1 不得覆盖 5) */ + put_file(st, "display.brightness = 5\ndisplay.brightness_min = 1\n"); + put_file(bl, "0\n"); + wd_panel_on(); + check("e2e: panel ON follows new settings 5", get_file_int(bl), 5); + + /* 3) settings 不可读 + 快照未知 + 屏亮 3 → 不写(保持 3,不再回退 60) */ + unlink(st); + put_file(bl, "3\n"); + wd_saved_bri = WD_BRI_UNKNOWN; + wd_panel_on(); + check("e2e: panel ON leaves 3 untouched", get_file_int(bl), 3); + + /* 4) settings 不可读 + 快照未知 + 屏黑 0 → 写安全低亮(>0 且 != 60) */ + put_file(bl, "0\n"); + wd_panel_on(); + check("e2e: panel ON dark -> safe-low", get_file_int(bl), WD_BRI_SAFE_LOW); + + /* 5) settings 不可读但本次运行期真观测到 7 → 快照优先于「不动」 */ + put_file(bl, "7\n"); + wd_saved_bri = WD_BRI_UNKNOWN; + wd_panel_off(); /* 观测到 7,写 0 */ + check("e2e: panel OFF snapshot = 7", wd_saved_bri, 7); + wd_panel_on(); + check("e2e: panel ON uses real snapshot 7", get_file_int(bl), 7); + + unlink(bl); + unsetenv("MEOW_BACKLIGHT"); + unsetenv("MEOW_SETTINGS_FILE"); +} + int main(void) { signal(SIGUSR1, on_sigusr1); /* 捕获 handle_touch 可能发出的 SIGUSR1(测命中) */ - char dir[] = "/tmp/wdfwXXXXXX"; + char dir[600]; + snprintf(dir, sizeof dir, "%s/wdfwXXXXXX", tmp_root()); if (!mkdtemp(dir)) { perror("mkdtemp"); return 2; } /* 框架 App:各值形态 */ @@ -109,6 +265,11 @@ int main(void) check("sigusr1 bare-immersive -> 1", sim_swipe_sigusr1(0, 1), 1); /* 裸沉浸:发(命中) */ check("sigusr1 bare-nonimmersive -> 0", sim_swipe_sigusr1(0, 0), 0); /* 裸非沉浸:不发 */ + /* ---- M20:亮屏亮度(决策 + settings 解析/重试 + panel OFF/ON 端到端)---- */ + test_bri_decide(); + test_settings_brightness(); + test_panel_brightness_e2e(); + /* 清理 */ { char path[560];