diff --git a/patch_v2/files_app/files b/patch_v2/files_app/files index fed28a8..6354258 100755 Binary files a/patch_v2/files_app/files and b/patch_v2/files_app/files differ diff --git a/patch_v2/hello_app/hello b/patch_v2/hello_app/hello index 79c6281..fa446e1 100755 Binary files a/patch_v2/hello_app/hello and b/patch_v2/hello_app/hello differ diff --git a/patch_v2/meow_launcherd/app_launcher.c b/patch_v2/meow_launcherd/app_launcher.c index cb4721e..bc9a835 100644 --- a/patch_v2/meow_launcherd/app_launcher.c +++ b/patch_v2/meow_launcherd/app_launcher.c @@ -1432,6 +1432,15 @@ static void notif_active_set(const char *owner) } } +/* M40 F5/P5-P6:请求 App 在恢复前台后整帧重画一次(SDK present 层消费)。 + * launcher 侧恢复的「底层帧」只能是快照;锁屏/菜单期间 App 若被冻结,它自己的 back_buf + * 仍是旧内容,只按脏区上屏会留下 PIN/菜单残影。写这个文件让 App dirty_full 一次。 */ +static void meow_app_repaint_request(void) +{ + int fd = open(MEOW_APP_REPAINT_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd >= 0) { (void)write(fd, "1", 1); close(fd); } +} + /* §10.5:App 前台顶部下滑 → 展开「由 launcher 渲染」的通知中心叠加会话。 * 复用电源菜单对 App 的冻结模板(show_power_menu 的 SIGSTOP/读回/叠加/接管/恢复): * ① SIGSTOP App → ② 写 /tmp/meow_notif_active(+ /tmp/meow_power_menu_active 让 watchdog 让位) @@ -1570,6 +1579,9 @@ static int run_app_lock_overlay(struct app *a, pid_t pid) pm_capture_app_frame(); meow_ui_flush(); + /* M40 F5/P5-P6:先写整帧重画请求,再 SIGCONT/放行——App 恢复后的第一次 present 会 + * 消费它并 dirty_full(),用自身完整帧盖掉 launcher 快照恢复可能留下的任何残影。 */ + meow_app_repaint_request(); if (!keep_running) { /* §310 输入穿透定案(overlay_gate.h):App 被 SIGSTOP 冻结过 → 在 /tmp/meow_notif_active * 仍未更新时**先 SIGCONT**,让 SDK 输入线程排空冻结期缓冲的 evdev 事件副本 + 对齐 @@ -1577,10 +1589,10 @@ static int run_app_lock_overlay(struct app *a, pid_t pid) * 防解锁瞬间把积累的触摸回放成 App 内点击(锁屏穿透→「启动奇怪界面」→ 状态栏消失的触发源)。 */ kill(pid, SIGCONT); usleep(OVERLAY_DRAIN_WINDOW_MS * 1000); - logmsg("lock-overlay: closed (lr=%d) -> SIGCONT app, drain %dms, then mark fg", + logmsg("lock-overlay: closed (lr=%d) -> SIGCONT app, drain %dms, repaint req, then mark fg", lr, OVERLAY_DRAIN_WINDOW_MS); } else { - logmsg("lock-overlay: closed (lr=%d) -> app never stopped (media keep-alive), no SIGCONT", lr); + logmsg("lock-overlay: closed (lr=%d) -> app never stopped (media keep-alive), no SIGCONT, repaint req", lr); } /* 标记翻回前台归属(a->id):本 App 解除自抑恢复前台;后台常驻 App(如 ncm)随之继续自抑 * (不叠帧 / 不穿输入——修复「锁屏解锁后 ncm 歌单帧叠加在 Hello 上 + 交互双响应」)。 */ @@ -1595,6 +1607,72 @@ static int run_app_lock_overlay(struct app *a, pid_t pid) return 0; } +/* M40 F2/P2:App 前台「电源菜单」会话——补齐 §10.5 协议。 + * 旧实现(M2.24)只有 SIGSTOP → show_power_menu → SIGCONT: + * - 没有 notif_active 抑制标记(后台常驻 App 可能在这期间上屏/穿输入); + * - 没有帧快照/恢复(菜单把「压暗 App 帧 + 菜单卡」present 后,关闭时 launcher 又 + * pm_capture_app_frame() 把**菜单帧**拍回 back_buf 再上屏 → 屏幕仍是菜单画面, + * 用户看到「按了没反应」,而 App 只按脏区重绘、永远不会重画整屏(P2 主因); + * - 没有 SIGCONT→300ms 排空→标记翻回前台 的输入隔离(冻结期触摸回放)。 + * show_power_menu() 现在会在所有退出路径恢复进入前的背景帧(power_menu.c),所以这里 + * 的 pm_capture_app_frame() 读到的是 App 帧而不是菜单帧。 + * 返回:0=取消(继续 App);1=已执行动作(调用方退出循环);2=App 在菜单期死亡。 */ +static int run_app_power_menu(struct app *a, pid_t pid) +{ + int keep_running = meow_media_app_playing(a->id); + if (keep_running) { + logmsg("power-menu: open (app=%s pid=%d) PLAYING -> no SIGSTOP (SDK present suppressed)", + a->name, pid); + } else { + logmsg("power-menu: open (app=%s pid=%d) SIGSTOP", a->name, pid); + if (kill(pid, SIGSTOP) < 0) { + logmsg("power-menu: SIGSTOP %d fail: %s", pid, strerror(errno)); + } else { + for (int i = 0; i < 100; i++) { + if (proc_is_stopped(pid)) break; + usleep(10000); + } + } + } + /* 叠加期全抑制:菜单盖在前台 App + 后台常驻 App 上;同时写 POWMENU_ACTIVE 让 watchdog + * 对电源长按/强关让位(与通知中心/锁屏同款双标记)。 */ + notif_active_set(""); + int pfd = open(POWMENU_ACTIVE, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (pfd >= 0) { write(pfd, "1", 1); close(pfd); } + + meow_sys_light(); + meow_theme_reload(); + pm_capture_app_frame(); /* 背景 = App 帧(fb0 buffer0) */ + + int pm_ret = show_power_menu(); + + if (proc_is_zombie(pid)) { + /* App 在菜单期死亡:不 SIGCONT、不把菜单帧当 App 帧恢复;交回 waitpid 统一回收。 */ + notif_active_set(""); + unlink(POWMENU_ACTIVE); + logmsg("power-menu: app pid %d died during menu -> unified exit path", pid); + return 2; + } + + /* 恢复 App 帧(show_power_menu 已还原进入前背景;再拍一次兜底覆盖任何非标准退出路径), + * 然后按 §10.5:先请求整帧重画 → SIGCONT → 排空窗 → 标记翻回前台归属。 */ + pm_capture_app_frame(); + meow_ui_flush(); + meow_app_repaint_request(); + if (!keep_running) { + kill(pid, SIGCONT); + usleep(OVERLAY_DRAIN_WINDOW_MS * 1000); + logmsg("power-menu: closed (ret=%d) -> SIGCONT app, drain %dms, repaint req, then mark fg", + pm_ret, OVERLAY_DRAIN_WINDOW_MS); + } else { + logmsg("power-menu: closed (ret=%d) -> app never stopped (media keep-alive), no SIGCONT, repaint req", + pm_ret); + } + notif_active_set(a ? a->id : ""); + unlink(POWMENU_ACTIVE); + return pm_ret; +} + /* launch_app_chain:launch_app / launch_activity 的共享核心。 * chain 非空 → 向子进程注入 MEOW_ACTIVITY/MEOW_CHAIN 并初始化 nav_stack(M3);空/NULL → 裸启动。 */ static int launch_app_chain(struct app *a, const char *chain) @@ -1963,6 +2041,11 @@ static int launch_app_chain(struct app *a, const char *chain) * 修法:本循环以「背光==0 且非超时」补判手动息屏,唤醒时按 meow_auto_slept||manual 一并触发 * lck_wake_should_lock 锁屏,两条路径对齐。 */ int app_panel_manual_off = 0; + /* M40 F3/P3:叠加层/电源菜单返回后的排空窗。watchdog 在电源菜单期间让位,菜单关闭后 + * 被 SIGCONT 的 App 会把冻结期缓存的触摸重放成 /tmp/meow_app_activity;若把它当用户 + * 触摸,会立刻「亮屏 + 弹锁屏」(P3 的 ACTIVITY 唤醒路径)。窗口内的标记只消费不唤醒。 */ + unsigned long long overlay_return_ms = now_ms(); + int overlay_return_valid = 0; /* M2.11 D:应用二进制/资源若在卡上,卡被拔可能 SIGBUS 崩。启动前记录,运行期监测卡移除。 * (拷贝到内存/板载 staging 的方案先不做,仅记录 APPS.md 规范。) */ int app_on_card = (strncmp(a->exec, MEOWAPPS, strlen(MEOWAPPS)) == 0); @@ -1986,9 +2069,12 @@ static int launch_app_chain(struct app *a, const char *chain) g_sb_mode_last = -1; /* 强制首帧同步(进入新 App 会话清零,避免沿用上一 App 的缓存态) */ unlink(MEOW_SBAR_MODE_FILE); /* 清可能残留的上一 App 顶条模式(崩溃/被杀未清),防新 App 误收合 */ if (!a->fullscreen) app_overlay_statusbar(); - /* M2.24:清上一次会话可能残留的电源菜单标记,只认本次新 watchdog 生成的请求。 */ + /* M2.24:清上一次会话可能残留的电源菜单标记,只认本次新 watchdog 生成的请求。 + * M40 F3:同时清 /tmp/meow_app_activity——上一会话(或启动手势)遗留的标记不能在本会话 + * 首圈被当成「用户触摸」而触发息屏计时重置/唤醒+锁屏。 */ unlink(POWMENU_MARKER); unlink(POWMENU_ACTIVE); + unlink(MEOW_APP_ACTIVITY_FILE); /* §10.5:进入 App 前台会话。置位让 ipc_dispatch 的 MSG_NOTIF_OPEN 可入队(否则回 ERR); * 清残留 pending(防上一会话未消费的 OPEN 被本会话误展开)。 */ g_app_session_active = 1; @@ -2057,6 +2143,8 @@ static int launch_app_chain(struct app *a, const char *chain) m_notif_open_t nopen; if (meow_notif_open_take(&nopen)) { int nr = run_app_notif_overlay(a, pid); + overlay_return_ms = now_ms(); /* M40 F3:排空窗起点(activity 标记忽略窗) */ + overlay_return_valid = 1; /* 输入基线重播(与电源菜单同款):面板期间按键可能已变化,重播防误判 key_edge */ kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL); @@ -2109,9 +2197,23 @@ static int launch_app_chain(struct app *a, const char *chain) * M6.5 §310 问题1:watchdog 电源键手动息屏(背光 0、不置 meow_auto_slept)→ 唤醒时按 * app_panel_manual_off 一并锁屏,与超时路径(meow_auto_slept)对齐。 */ { - /* 背光==0 => 面板已关(超时路径已置 auto_slept;手动路径靠此补判)。 - * read_brightness() 失败返回 -1(≠0),不误置位。 */ - if (read_brightness() == 0) app_panel_manual_off = 1; + /* M40 F3:去掉单向闩锁。旧实现只在 watchdog 手动息屏时置 1、只有本循环唤醒分支清 1; + * watchdog 之后把屏点亮(pwr_woke)闩锁仍是 1 → 任何 activity 标记都会再次 + * 「亮屏+锁屏」(P3 放大器)。改为每圈按真实背光复位: + * - 背光==0 → 置 1(手动/超时熄屏都算); + * - 背光>0 且**没有任何键按住** → 复位 0(屏已被 watchdog 点亮,不再残留闩锁); + * - 背光>0 但电源键仍按住(本次唤醒按压)→ 保留一拍,让下面的 key-wake 分支仍能 + * 按 lck_wake_should_lock 弹锁屏(不牺牲「手动息屏后电源唤醒要锁屏」语义)。 + * read_brightness() 失败返回 -1(≠0),不误置/误清。 */ + int bri_now = read_brightness(); + if (bri_now == 0) { + app_panel_manual_off = 1; + } else if (bri_now > 0) { + int any_key_down = 0; + for (int w = 0; w < KEYBUF_WORDS; w++) + if (kp_power.cur[w] || kp_keys.cur[w]) { any_key_down = 1; break; } + if (!any_key_down) app_panel_manual_off = 0; + } int key_edge = 0; for (int w = 0; w < KEYBUF_WORDS; w++) if (kp_power.cur[w] != save_pow[w] || kp_keys.cur[w] != save_keys[w]) { key_edge = 1; break; } @@ -2141,6 +2243,8 @@ static int launch_app_chain(struct app *a, const char *chain) * (launcher 不崩、run_menu 重绘主屏),同 run_app_notif_overlay 的 died 分支。 */ if (lck_wake_should_lock()) { int lr2 = run_app_lock_overlay(a, pid); + overlay_return_ms = now_ms(); /* M40 F3:排空窗起点 */ + overlay_return_valid = 1; if (lr2 != 0) { reap_wait(pid, &status); ams_status_valid = 1; @@ -2161,11 +2265,15 @@ static int launch_app_chain(struct app *a, const char *chain) } } /* M2.71-bug5:App 前台触摸/交互(launcher 看不到应用内触摸,SDK 写标记)→ 重置自动息屏 - * 计时;若已自动息屏,按 key-wake 同款路径唤醒(面板上电 + 落 buffer0)。 */ + * 计时;若已自动息屏,按 key-wake 同款路径唤醒(面板上电 + 落 buffer0)。 + * M40 F3:叠加层/电源菜单刚返回的排空窗内,该标记是 App 被 SIGCONT 后重放的冻结期 + * 输入,只消费不唤醒(否则「亮屏 + 弹 PIN」,P3)。 */ if (access(MEOW_APP_ACTIVITY_FILE, F_OK) == 0) { unlink(MEOW_APP_ACTIVITY_FILE); app_last_input = now_ms(); - if (meow_auto_slept || app_panel_manual_off) { + if (overlay_activity_drain_active(overlay_return_valid, overlay_return_ms, now_ms())) { + logmsg("activity marker consumed in overlay drain window -> no wake"); + } else if (meow_auto_slept || app_panel_manual_off) { logmsg("screen-off (app): touch wake (auto=%d manual=%d)", meow_auto_slept, app_panel_manual_off); meow_panel_on(); @@ -2176,6 +2284,8 @@ static int launch_app_chain(struct app *a, const char *chain) * §310:叠加期 App 崩溃 → 回收僵尸走统一退出路径(launcher 不崩、run_menu 重绘主屏)。 */ if (lck_wake_should_lock()) { int lr2 = run_app_lock_overlay(a, pid); + overlay_return_ms = now_ms(); /* M40 F3:排空窗起点 */ + overlay_return_valid = 1; if (lr2 != 0) { reap_wait(pid, &status); ams_status_valid = 1; @@ -2216,20 +2326,16 @@ static int launch_app_chain(struct app *a, const char *chain) app_prev_vold = vold; if (app_seq_hits > 0 && now_ms() - app_seq_last > (unsigned)seq_gap_ms * 2) app_seq_hits = 0; - /* M2.24 全面按键接管:watchdog 检测到电源长按≥3s 写 /tmp/meow_power_menu → 此处消费: - * SIGSTOP 应用冻结 → 写 powmenu_active 让 watchdog 让位 → 统一电源菜单(launcher 一处绘制, - * 复用 show_power_menu,含触摸/音量导航/电源确认/8s 强关/pm_interacted 保护)→ 菜单关 → - * 清 active → SIGCONT 应用续跑。 */ + /* M2.24/M40 全面按键接管:watchdog 检测到电源长按≥power.longpress_menu_ms 写 + * /tmp/meow_power_menu → 此处消费。M40 F2 起走 run_app_power_menu() 的完整 §10.5 + * 协议(SIGSTOP + 全抑制标记 + 帧恢复 + SIGCONT→排空→标记翻回前台 + 整帧重画请求), + * 不再只 SIGSTOP/show_power_menu/SIGCONT(旧实现关闭后屏幕仍是菜单帧 = P2 主因)。 */ if (access(POWMENU_MARKER, F_OK) == 0) { unlink(POWMENU_MARKER); - logmsg("watchdog: power menu request (app %s) -> SIGSTOP + menu", a->name); - kill(pid, SIGSTOP); - int mfd = open(POWMENU_ACTIVE, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (mfd >= 0) { write(mfd, "1", 1); close(mfd); } - int pm_ret = show_power_menu(); - unlink(POWMENU_ACTIVE); - kill(pid, SIGCONT); - logmsg("watchdog: power menu closed (ret=%d) -> SIGCONT app", pm_ret); + logmsg("watchdog: power menu request (app %s) -> overlay session", a->name); + int pm_ret = run_app_power_menu(a, pid); + overlay_return_ms = now_ms(); /* M40 F3:排空窗起点(activity 标记忽略窗) */ + overlay_return_valid = 1; /* M2.25:菜单期间按键可能已变化,重播 save_pow/save_keys 防误判 key_edge * (避免菜单关闭瞬间误触发「键唤醒」或刷新自动息屏计时)。 */ kp_poll(&kp_power, NULL, NULL); @@ -2237,6 +2343,11 @@ static int launch_app_chain(struct app *a, const char *chain) memcpy(save_pow, kp_power.cur, sizeof save_pow); memcpy(save_keys, kp_keys.cur, sizeof save_keys); app_last_input = now_ms(); /* M2.17:菜单交互即输入,重置应用会话自动息屏计时 */ + if (pm_ret == 2) { /* App 在菜单期死亡:回收僵尸走统一退出路径 */ + reap_wait(pid, &status); + ams_status_valid = 1; + break; + } if (pm_ret != 0) break; /* 关机/重启/回HibyOS:系统通常已 reboot,防御性退出 */ } /* 「同一个状态栏」闭环:常驻顶条叠加(agent-146 全屏收合接入)。 diff --git a/patch_v2/meow_launcherd/lock_screen_ui.c b/patch_v2/meow_launcherd/lock_screen_ui.c index 354195c..79ffe32 100644 --- a/patch_v2/meow_launcherd/lock_screen_ui.c +++ b/patch_v2/meow_launcherd/lock_screen_ui.c @@ -315,11 +315,12 @@ int run_lock_screen(void) return 1; } - /* M7.1 解锁恢复:锁屏是整帧不透明覆盖(lk_draw fill_rect 全屏),退出后底层界面不会自动重绘 - * —— 只有 App 自绘的控件区域刷新,未刷新区残留锁屏 PIN 最后一帧(解锁后整屏脏区根因)。此处 - * 在锁屏首扑前把底层帧(SDK back_buf = 进入前的 App 帧/主界面帧)快照下来;解锁(unlocked=1) - * 时整帧回写 back_buf + present,强制底层界面完整重绘(app_launcher.c 的 lock-overlay 关闭路径 - * 随后 pm_capture_app_frame 读 fb0 即得到正确 App 帧,不再读到锁帧)。 */ + /* M7.1/M40 F5 解锁/熄屏恢复:锁屏是整帧不透明覆盖(lk_draw fill_rect 全屏),退出后底层界面 + * 不会自动重绘 —— 只有 App 自绘的控件区域刷新,未刷新区残留锁屏 PIN 最后一帧。 + * 此处把锁屏首扑前的底层帧(SDK back_buf = 进入前的 App 帧/主界面帧)快照下来; + * **任何退出路径**(解锁 unlocked=1 或电源键熄屏 unlocked=0)都整帧回写 back_buf + present, + * 保证 app_launcher.c 的 lock-overlay 关闭路径随后 pm_capture_app_frame 读 fb0 得到的是 + * 正确 App 帧,而不是 PIN 帧(后者会被下一次锁屏当成底层帧 → P5 残影链)。 */ unsigned char *lk_bg_snap = NULL; size_t lk_bg_len = 0; if (back_buf_ok && fb && fb_line > 0 && fb_yres > 0) { @@ -400,7 +401,7 @@ int run_lock_screen(void) } /* 电源键/音量键轮询 + 输入活动检测(重置自睡计时) + 自睡/唤醒切换。 - * 活跃态:电源键按下 → 息屏并退出锁屏(回 App/主屏,下次唤醒再锁)——沿用原语义; + * 活跃态:电源键**按下边沿**(进场 ≥300ms)→ 息屏并退出锁屏(回 App/主屏,下次唤醒再锁); * 空闲超 lk_off_ms → 自睡(asleep=1,锁态保持,仍在本循环)。 * asleep:电源键按下边沿 / 触摸活动 → meow_panel_on + 重画锁屏页(本循环唤醒链回锁)。 * 音量键本页不动(仅视为输入活动,重置计时)。 */ @@ -429,9 +430,14 @@ int run_lock_screen(void) continue; } } else { - /* 电源键=息屏(允许再睡;下次唤醒按 lck_wake_should_lock 再锁) */ - if (pwr_down) { - logmsg("lock: power key -> screen off (stay locked)"); + /* 电源键=息屏(允许再睡;下次唤醒按 lck_wake_should_lock 再锁)。 + * M40 F4/P4:必须用**按下边沿**且进场 ≥300ms 才生效。旧实现用电平 + * (pwr_down):打开电源菜单的那次长按在 PIN 页出现时仍按着,页面被同一次 + * 按住立刻熄灭(用户症状「手动锁屏不熄屏而是进 PIN,此时点电源才熄屏」)。 + * 边沿判定在 overlay_gate.h(宿主单测同源)。 */ + if (overlay_lock_power_off_now(pwr_edge, now_ms() - entry_ms)) { + logmsg("lock: power key edge (%llums after entry) -> screen off (stay locked)", + now_ms() - entry_ms); meow_panel_off(); break; /* return 0 */ } @@ -569,13 +575,17 @@ int run_lock_screen(void) done: meow_ui_activity_pop(); /* M7.1b:锁屏控件 owner 闸门关闭(与进场 push 对称) */ g_lk_down_key = LK_DOWN_NONE; - /* M7.1 解锁恢复:把锁屏首扑前快照的底层帧整帧回写 back_buf + present,强制底层 App/主界面 - * 完整重绘(消除「解锁后整屏 = PIN 最后一帧,仅 App 控件自绘区刷新」的脏区残留)。 */ - if (unlocked && lk_bg_snap && back_buf_ok && fb && lk_bg_len > 0) { + /* M7.1/M40 F5:把锁屏首扑前快照的底层帧整帧回写 back_buf + present。 + * M40 起**不再限定 unlocked**:电源键熄屏退出(lr=0)也必须恢复,否则 fb0 会停留在 + * PIN 最后一帧;launcher 的 lock-overlay 关闭路径紧接着 pm_capture_app_frame() 会把 + * 这帧 PIN 抄进 back_buf 并上屏,下一次锁屏再把它当「底层帧」快照 —— P5 残影链的起点。 + * 屏黑时 present 无害(面板已下电),解锁时则同时消除「整屏 = PIN」的脏区残留。 + * 「解锁后 App 整帧重画」由 launcher 写 /tmp/meow_app_repaint(SDK present 消费)完成。 */ + if (lk_bg_snap && back_buf_ok && fb && lk_bg_len > 0) { memcpy(fb, lk_bg_snap, lk_bg_len); meow_dirty_full(); meow_ui_flush(); - logmsg("lock: unlock -> restore underlying frame (full repaint)"); + logmsg("lock: exit (unlocked=%d) -> restore underlying frame (full repaint)", unlocked); } if (lk_bg_snap) { free(lk_bg_snap); lk_bg_snap = NULL; lk_bg_len = 0; } if (tfd >= 0) { diff --git a/patch_v2/meow_launcherd/meow_launcher_internal.h b/patch_v2/meow_launcherd/meow_launcher_internal.h index 7240f80..cd1cacf 100644 --- a/patch_v2/meow_launcherd/meow_launcher_internal.h +++ b/patch_v2/meow_launcherd/meow_launcher_internal.h @@ -268,6 +268,9 @@ static inline const char *meow_str_zh(const char *id, const char *zh) #define MEOW_SBAR_MODE_FILE "/tmp/meowapp_sbar" /* M2.71-bug5:App 前台触摸/交互 → SDK 写该标记;launcher 前台 app 循环消费(unlink + 重置息屏计时) */ #define MEOW_APP_ACTIVITY_FILE "/tmp/meow_app_activity" +/* M40 F5/P5-P6:解锁/菜单关闭后强制 App 整帧重画的请求文件。launcher 在 SIGCONT 前写, + * SDK present 层(sdk/meow_ui_repaint.c)看到就 dirty_full() 一次并删除。 */ +#define MEOW_APP_REPAINT_FILE "/tmp/meow_app_repaint" /* ---- 共享结构体/枚举(来自 meow_launcherd.c,拆分后统一放置)---- */ struct kd_state { int raw, stable, down; int longpressed; unsigned long long since, down_at; }; diff --git a/patch_v2/meow_launcherd/meow_launcherd b/patch_v2/meow_launcherd/meow_launcherd index 82eb9bf..90440f7 100755 Binary files a/patch_v2/meow_launcherd/meow_launcherd and b/patch_v2/meow_launcherd/meow_launcherd differ diff --git a/patch_v2/meow_launcherd/overlay_gate.h b/patch_v2/meow_launcherd/overlay_gate.h index 056d3ed..5f823c0 100644 --- a/patch_v2/meow_launcherd/overlay_gate.h +++ b/patch_v2/meow_launcherd/overlay_gate.h @@ -91,4 +91,26 @@ static inline int overlay_lock_wake_now(int pwr_edge, int touch_ready) return (pwr_edge || touch_ready) ? 1 : 0; } +/* M40 F4/P4:锁屏页「电源按下=息屏退出」必须在**按下边沿**生效,且进场后 ≥300ms 才允许。 + * 旧实现用电平(kp_is_down)且无防抖:打开电源菜单的那次按压在 PIN 页出现时仍按着, + * PIN 页会被同一次按住立刻熄灭(用户症状「手动锁屏不熄屏而是进 PIN,此时点电源才熄屏」)。 + * 300ms 也避开 launcher 进入锁屏页时的按键基线窗口。 */ +#define OVERLAY_LOCK_POWER_GRACE_MS 300 + +static inline int overlay_lock_power_off_now(int pwr_down_edge, unsigned long long since_entry_ms) +{ + return (pwr_down_edge && since_entry_ms >= OVERLAY_LOCK_POWER_GRACE_MS) ? 1 : 0; +} + +/* M40 F3/P3:叠加层/电源菜单返回后的排空窗内,`/tmp/meow_app_activity` 是 App 被 SIGCONT + * 后重放的冻结期输入(不是用户新触摸),必须消费掉但不得触发「亮屏+锁屏」。 + * 返回 1=仍在排空窗(忽略 activity 标记)。 */ +static inline int overlay_activity_drain_active(int have_return_mark, + unsigned long long since_return_ms, + unsigned long long now_ms) +{ + if (!have_return_mark) return 0; + return (now_ms - since_return_ms < OVERLAY_DRAIN_WINDOW_MS) ? 1 : 0; +} + #endif /* MEOW_OVERLAY_GATE_H */ diff --git a/patch_v2/meow_launcherd/power_menu.c b/patch_v2/meow_launcherd/power_menu.c index e02bdca..8db04a4 100644 --- a/patch_v2/meow_launcherd/power_menu.c +++ b/patch_v2/meow_launcherd/power_menu.c @@ -355,22 +355,59 @@ int meow_power_action(uint32_t cmd) } +/* M40 F2/P2:关闭菜单时恢复进入前的背景帧并 present。 + * dim_screen() 就地压暗 back_buf;若不恢复,app_launcher 关闭会话时的 pm_capture_app_frame() + * 会把「压暗 App 帧 + 菜单卡」当成 App 帧拍回并上屏 → 菜单关闭后屏幕仍是菜单画面, + * 用户看到「再次单击没反应」(P2 主因)。所有退出路径(取消/锁定/关机/强关)统一恢复。 */ +static void pm_restore_bg(unsigned char *snap, size_t len) +{ + if (!snap || !len || !back_buf_ok || !fb) return; + memcpy(fb, snap, len); + meow_dirty_full(); + meow_ui_flush(); +} + /* 电源菜单(M2.6):2×2 网格,dirty 绘制消频闪;输入修复: * - 触摸按下捕获坐标、抬起命中按钮即确认(同 run_menu MT 防抖); * - 音量上/下导航;播放=确认;电源新按(松开长按后)=确认当前钮(非取消); - * - 取消=选「取消」钮;8s 持续按住=强制关机(绕过)。 + * - 取消=选「取消」钮;按住 power.forceoff_ms(缺省 8s,从按下起算)=强制关机(绕过)。 * 返回 1=采取动作,0=取消。 */ int show_power_menu(void) { int fbfd = open(FB_DEV, O_RDWR); if (fbfd >= 0) { ioctl(fbfd, FBIOBLANK, 0); close(fbfd); } /* 亮起面板 */ - pm_capture_app_frame(); /* A-0:菜单背景 = app 当前帧(fb0 buffer0),再压暗绘制其上 */ + meow_screen_on = 1; /* 菜单打开即视为亮屏 */ + + /* M40 F2:先开输入设备并采样第一轮基线。旧顺序把这一步放在 pm_capture/dim/建卡之后 + * (真机实测菜单请求→keypoll ready 相隔 75ms),一次完整的按下+抬起若落在这段阻塞绘制里 + * 就永远看不到边沿(EVIOCGKEY 是电平采样,不回溯事件队列)。重活挪到采样之后。 */ + meow_touch mt; + mt_reset(&mt); + int tfd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK); + struct keypoll kp_power, kp_keys; + kp_open(&kp_power, DEV_POWER); + kp_open(&kp_keys, DEV_KEYS); + int prev_vold = 0, prev_volu = 0, prev_pwr = 0; + kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL); + prev_pwr = kp_is_down(&kp_power, KEY_POWER) ? 1 : 0; + prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN) ? 1 : 0; + prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP) ? 1 : 0; + + /* 背景帧:先把 app 当前帧读回 back_buf,快照后再压暗绘制 */ + pm_capture_app_frame(); /* A-0:菜单背景 = app 当前帧(fb0 buffer0) */ + unsigned char *pm_bg_snap = NULL; + size_t pm_bg_len = 0; + if (back_buf_ok && fb && fb_line > 0 && fb_yres > 0) { + pm_bg_len = (size_t)fb_line * (size_t)fb_yres; + pm_bg_snap = malloc(pm_bg_len); + if (pm_bg_snap) memcpy(pm_bg_snap, fb, pm_bg_len); + else { pm_bg_len = 0; logmsg("pm: bg snapshot malloc fail (len=%zu)", pm_bg_len); } + } dim_screen(); /* M2.8 Bug 3-residual:若电源菜单是在「息屏」态打开(按住 3s),此处 FBIOBLANK(0) * 刚上电,LCDC 扫描地址可能非 buffer0;强制 re-pan 让压暗的应用列表 + 菜单落在被显示的 * 缓冲,否则菜单画在非显示缓冲上不可见。 */ fb_pan_buf0_force(); - meow_screen_on = 1; /* 菜单打开即视为亮屏 */ /* M2.11 B:默认光标 = 「取消」(安全项,index 4)。用户打开菜单后未导航就按电源/播放, * 只会取消关菜单,绝不因「按电源键」本身关机/重启。要真关机必须先导航到「关机」再确认。 */ @@ -383,29 +420,25 @@ int show_power_menu(void) pm_draw_card(); /* 卡片 + 标题 POWER MENU + 底部提示(SDK 主题色,i18n 文案) */ meow_dirty_full(); /* M2.16:把压暗列表+卡片+标题+(下方圆钮)作为一整帧呈现 */ - /* 触摸状态(按下→抬起):统一 meow_touch 接口(goodix 时序免疫,原点由首个有效 ABS 锁) */ - meow_touch mt; - mt_reset(&mt); - int tfd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK); - - struct keypoll kp_power, kp_keys; - kp_open(&kp_power, DEV_POWER); - kp_open(&kp_keys, DEV_KEYS); - int prev_vold = 0, prev_volu = 0, prev_pwr = 0; - kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL); - prev_pwr = kp_is_down(&kp_power, KEY_POWER) ? 1 : 0; - prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN) ? 1 : 0; - prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP) ? 1 : 0; - int act = -1; + int pm_rc = 0; unsigned long long pm_t0 = now_ms(); + /* 强关阈值:power.forceoff_ms 是**从按下起算**的总时长;菜单在 longpress 阈值处打开, + * 因此菜单内剩余等待 = forceoff - longpress(缺省 8000-3000=5000ms,与原硬编码值一致, + * 但不再忽略 settings)。配置异常(剩余<1s)时退回完整 forceoff。 */ + unsigned long long pm_forceoff_after_ms = 5000; + if (power_forceoff_ms > power_longpress_menu_ms && + power_forceoff_ms - power_longpress_menu_ms >= 1000) + pm_forceoff_after_ms = (unsigned long long)(power_forceoff_ms - power_longpress_menu_ms); + else if (power_forceoff_ms > 0) + pm_forceoff_after_ms = (unsigned long long)power_forceoff_ms; unsigned long long pm_last_interact = pm_t0; /* M2.33:菜单空闲自动关闭基准(任何互动刷新) */ int pm_init = 0, last_sel = -1; - /* M2.10 Bug:电源菜单 8s 强关(按住电源≥8s=绕过强关)会覆盖用户的菜单操作 —— 用户按住 - * 电源打开菜单后(尤其仍按着电源)点选「回到HibyOS/重启」等按钮,8s 一到先触发强关 → 关机 + /* M2.10 Bug:电源菜单强关(按住电源到 forceoff)会覆盖用户的菜单操作 —— 用户按住 + * 电源打开菜单后(尤其仍按着电源)点选「回到HibyOS/重启」等按钮,强关一到先触发 → 关机 * 而非执行所选动作。修法:一旦用户与菜单互动(音量导航/触摸/播放/电源确认),置 pm_interacted=1 - * 取消 8s 强关,让所选动作(如「回到HibyOS」→ reboot RESTART)生效。仅当全程未互动才保留「按住 - * 8s=强关」。*/ + * 取消强关,让所选动作(如「回到HibyOS」→ reboot RESTART)生效。仅当全程未互动才保留「按住 + * 到 forceoff=强关」。*/ int pm_interacted = 0; int pm_press_idx = -1; /* M7.0b:当前按下钮(按压态走 SDK 状态机位) */ for (;;) { @@ -488,17 +521,25 @@ int show_power_menu(void) } } - /* 8s 强关(绕过):仅当电源持续按住、且用户全程未与菜单互动才触发 —— 放在触摸/按键处理 - * 之后,避免「用户已点选按钮」的同一轮被强关抢先(M2.10 Bug)。 */ - if (pwr && !pm_interacted && now_ms() - pm_t0 >= 5000) { - if (sim_no_exec) { logmsg("pm: [sim-no-exec] POWER HOLD 8s -> would force poweroff"); meow_ui_activity_pop(); return 1; } - logmsg("pm: POWER HOLD 8s -> force poweroff"); + /* 强关(绕过):仅当电源持续按住、且用户全程未与菜单互动才触发 —— 放在触摸/按键处理 + * 之后,避免「用户已点选按钮」的同一轮被强关抢先(M2.10 Bug)。 + * M40:阈值接 power.forceoff_ms(从按下起算的剩余时长),不再硬编码 5000/日志谎称 8s。 */ + if (pwr && !pm_interacted && now_ms() - pm_t0 >= pm_forceoff_after_ms) { + if (sim_no_exec) { + logmsg("pm: [sim-no-exec] POWER HOLD %dms (menu-open + %llums) -> would force poweroff", + power_forceoff_ms, pm_forceoff_after_ms); + pm_rc = 1; + goto out; + } + logmsg("pm: POWER HOLD %dms (menu-open + %llums) -> force poweroff", + power_forceoff_ms, pm_forceoff_after_ms); if (meow_power_action(POWER_SHUTDOWN) != MEOW_OK) { logmsg("pm: power service unavailable, abort force-off (menu closed, no action)"); - meow_ui_activity_pop(); return 0; + pm_rc = 0; + goto out; } - meow_ui_activity_pop(); - return 1; + pm_rc = 1; + goto out; } /* M2.33 菜单空闲超时自动关闭:菜单受 MeowOS screen_off_timeout 管理(系统界面不豁免)。 @@ -516,57 +557,64 @@ int show_power_menu(void) if (act >= 0) break; usleep(POLL_MS * 1000); } - if (tfd >= 0) close(tfd); - meow_ui_activity_pop(); /* M7.0b:退出电源菜单,回主 activity(控件 owner 仍钉哨兵,复用) */ - /* M2.11 B:电源键在菜单 = 按当前项确认。act 与电源动作逐项核对: - * act==PM_NUM-1(取消) -> 只关菜单,绝不关机(return 0,永不到电源动作); + * act==PM_NUM-1(取消) -> 只关菜单,绝不关机(rc=0,永不到电源动作); * act==4(锁定) -> 手动锁(meow_lock_screen_engage,未配 PIN 则 no-op),关菜单; * act==0(关机)/act==1(重启)/act==2(回HibyOS)/act==3(升级模式) -> 才经服务 IPC 执行电源动作。 * 只有 act 为 0/1/2/3 才可能发电源动作;power 键本身绝不触发关机。 */ logmsg("pm: act=%d -> %s", act, act == 0 ? "关机" : act == 1 ? "重启" : act == 2 ? "回HibyOS" : act == 3 ? "升级模式" : act == 4 ? "锁定" : "取消"); - if (act == PM_NUM - 1) { logmsg("pm: cancel (close menu only)"); return 0; } + if (act == PM_NUM - 1) { logmsg("pm: cancel (close menu only)"); pm_rc = 0; goto out; } if (act == 4) { logmsg("pm: LOCK SCREEN"); meow_lock_screen_engage(); - return 0; /* 关菜单;run_menu 继续(锁屏页已阻塞到解锁或电源息屏) */ + pm_rc = 0; /* 关菜单;run_menu 继续(锁屏页已阻塞到解锁或电源息屏) */ + goto out; } /* 动作统一经电源管理服务 IPC(meow_svc_power);launcher 不再直连 poweroff/reboot(2)/写 mtd5/ * 写 boot 默认——关机/重启/升级/回HiBy 的动作原语都在服务内实现(§3.2 单一动作链路)。 */ /* 公共服务:执行动作;服务不可达 → 按「取消」返回(launcher 存活,绝不误报已动作卡死)。 * meow_power_action 内部已先 meow_notif_before_poweroff()(转储未上卡通知)。 */ + pm_rc = 1; if (act == 0) { logmsg("pm: POWER OFF"); - if (sim_no_exec) { logmsg("pm: [sim-no-exec] would poweroff"); return 1; } + if (sim_no_exec) { logmsg("pm: [sim-no-exec] would poweroff"); goto out; } if (meow_power_action(POWER_SHUTDOWN) != MEOW_OK) { logmsg("pm: power service unavailable, abort (menu closed, no action)"); - return 0; + pm_rc = 0; } } else if (act == 1) { logmsg("pm: REBOOT"); - if (sim_no_exec) { logmsg("pm: [sim-no-exec] would reboot"); return 1; } + if (sim_no_exec) { logmsg("pm: [sim-no-exec] would reboot"); goto out; } if (meow_power_action(POWER_REBOOT) != MEOW_OK) { logmsg("pm: power service unavailable, abort (menu closed, no action)"); - return 0; + pm_rc = 0; } } else if (act == 2) { logmsg("pm: RETURN TO HIBYOS"); - if (sim_no_exec) { logmsg("pm: [sim-no-exec] would reboot to hiby"); return 1; } + if (sim_no_exec) { logmsg("pm: [sim-no-exec] would reboot to hiby"); goto out; } if (meow_power_action(POWER_BACK_HIBY) != MEOW_OK) { logmsg("pm: power service unavailable, abort (menu closed, no action)"); - return 0; + pm_rc = 0; } } else if (act == 3) { logmsg("pm: ENTER UPGRADE"); - if (sim_no_exec) { logmsg("pm: [sim-no-exec] would enter upgrade"); return 1; } + if (sim_no_exec) { logmsg("pm: [sim-no-exec] would enter upgrade"); goto out; } if (meow_power_action(POWER_UPGRADE) != MEOW_OK) { logmsg("pm: power service unavailable, abort (menu closed, no action)"); - return 0; + pm_rc = 0; } } - return 1; +out: + if (tfd >= 0) close(tfd); + meow_ui_activity_pop(); /* M7.0b:退出电源菜单,回主 activity(控件 owner 仍钉哨兵,复用) */ + /* M40 F2:恢复进入前的背景帧(否则 app_launcher 会把菜单帧当 App 帧上屏 = P2)。 + * 放在动作执行之后:锁屏动作已 meow_panel_off(),此时恢复只写 fb0(面板黑), + * 下次唤醒 / App 整帧重画时得到的是正确底层帧而不是 PIN/菜单帧。 */ + pm_restore_bg(pm_bg_snap, pm_bg_len); + if (pm_bg_snap) free(pm_bg_snap); + return pm_rc; } diff --git a/patch_v2/meow_launcherd/test_overlay_gate.c b/patch_v2/meow_launcherd/test_overlay_gate.c index 5f5cc53..f4effd3 100644 --- a/patch_v2/meow_launcherd/test_overlay_gate.c +++ b/patch_v2/meow_launcherd/test_overlay_gate.c @@ -18,6 +18,9 @@ * S4 锁屏页自睡(294 遗留) → overlay_lock_idle_off_sec():阈值 = min(系统 screen_off_timeout, 30s) * (0/超30→30,尊重更短);overlay_lock_should_sleep()/overlay_lock_wake_now() * 判定超时灭屏(锁态保持)与唤醒回锁屏。 + * M40 F3/F4 → overlay_activity_drain_active():叠加/菜单返回后的排空窗内忽略 + * activity 标记(防冻结期输入重放成「亮屏+锁屏」); + * overlay_lock_power_off_now():锁屏电源必须按下边沿 + 进场≥300ms。 * * 编译运行(在 patch_v2/meow_launcherd 下,无其它依赖): * gcc -Wall -Wextra -o test_overlay_gate test_overlay_gate.c && ./test_overlay_gate @@ -80,6 +83,19 @@ int main(void) CHECK(overlay_lock_wake_now(0, 1) == 1, "S4 touch -> wake to lock"); CHECK(overlay_lock_wake_now(0, 0) == 0, "S4 no wake input -> stay asleep"); + /* ---- M40 F4/P4:锁屏页电源「按下边沿 + 进场 ≥300ms」才熄屏 ---- + * 电平判定会让「打开菜单的那次长按」在 PIN 页出现时立刻把页面熄灭(用户症状)。 */ + CHECK(OVERLAY_LOCK_POWER_GRACE_MS == 300, "F4 lock power grace = 300ms"); + CHECK(overlay_lock_power_off_now(1, 299) == 0, "F4 press edge inside 300ms grace -> no off"); + CHECK(overlay_lock_power_off_now(1, 300) == 1, "F4 press edge at 300ms -> off"); + CHECK(overlay_lock_power_off_now(0, 5000) == 0, "F4 held level (no edge) -> never re-fires"); + CHECK(overlay_lock_power_off_now(1, 5000) == 1, "F4 new press edge after grace -> off"); + + /* ---- M40 F3/P3:叠加层/菜单返回后的 activity 标记排空窗 ---- */ + CHECK(overlay_activity_drain_active(0, 0, 999999) == 0, "F3 no overlay return mark -> activity honored"); + CHECK(overlay_activity_drain_active(1, 1000, 1299) == 1, "F3 marker inside 300ms drain -> ignored"); + CHECK(overlay_activity_drain_active(1, 1000, 1300) == 0, "F3 marker after drain -> honored"); + /* ---- S3 App 死亡触发主屏重绘:崩溃必须走统一退出路径 ---- */ CHECK(lock_overlay_app_died_exit(1) == 1, "S3 app died -> unified exit path (redraw home)"); CHECK(lock_overlay_app_died_exit(0) == 0, "S3 app alive -> normal restore"); diff --git a/patch_v2/meow_watchdog/build_watchdog.sh b/patch_v2/meow_watchdog/build_watchdog.sh index 1b934dc..5259142 100755 --- a/patch_v2/meow_watchdog/build_watchdog.sh +++ b/patch_v2/meow_watchdog/build_watchdog.sh @@ -12,13 +12,16 @@ INC="$ROOT/patch_v2/meow_launcherd" # meow_ipc.h(音量收编 v2:MSG_AUDIO if [ "$1" = "host" ]; then gcc -Wall -Wextra -Os -I"$INC" -DWd_NO_MAIN -Wno-unused-function -Wno-unused-parameter \ - -Wno-unused-variable test_wd_fw.c -o test_wd_fw -lm + -Wno-unused-variable test_wd_fw.c ../sdk/meow_time32.c -o test_wd_fw -lm ./test_wd_fw exit $? fi echo "== meow_watchdog (mipsel static) ==" -"$CC" -Os -Wall -Wextra -static -I"$INC" meow_watchdog.c -o meow_watchdog +# meow_time32.c:独立 time32 取时/睡眠(watchdog 不链 meow_ui.a)。LIBC_OVERRIDES 让 +# 未逐个改写的 libc 取时/睡眠入口也一并修正(静态链接,本对象强定义覆盖 libc 归档成员)。 +"$CC" -Os -Wall -Wextra -static -I"$INC" -DMEOW_TIME32_LIBC_OVERRIDES \ + meow_watchdog.c ../sdk/meow_time32.c -o meow_watchdog ls -la meow_watchdog md5sum meow_watchdog file meow_watchdog diff --git a/patch_v2/meow_watchdog/meow_watchdog b/patch_v2/meow_watchdog/meow_watchdog index 2e6c4ab..c2b0108 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 48b4a80..fa1c272 100644 --- a/patch_v2/meow_watchdog/meow_watchdog.c +++ b/patch_v2/meow_watchdog/meow_watchdog.c @@ -12,13 +12,14 @@ * run — short press (<3s release) = toggle screen off/on (brightness 0 + * FBIOBLANK(4) / FBIOBLANK(0)+brightness restore, with a force re-pan to * buffer0 on wake). - * - M2.24 全面按键接管(KEY_CONTROL_DESIGN):应用运行期所有物理键由本进程统一消费, + * - M2.24/M40 全面按键接管(KEY_CONTROL_DESIGN):应用运行期所有物理键由本进程统一消费, * 应用绝不碰物理键。 - * 电源单击(<2s) -> 息屏/亮屏(沿用 M2.22); - * 电源长按 ≥2s -> 写 /tmp/meow_power_menu,由 launcher SIGSTOP app 后弹统一电源菜单 - * (launcher 在 waitpid 轮询中消费;菜单期该进程靠 /tmp/meow_power_menu_active - * 让位,绝不与 launcher 抢屏/抢键);launcher 画菜单,本进程不自绘。 - * 电源长按 ≥8s -> 强制关机兜底(仅当菜单未生效/launcher 未接管)。 + * 电源单击(松开时 held < power.longpress_menu_ms,缺省 3000)-> 息屏/亮屏(沿用 M2.22); + * 电源长按 ≥power.longpress_menu_ms -> 写 /tmp/meow_power_menu,由 launcher SIGSTOP app + * 后弹统一电源菜单(launcher 在 waitpid 轮询中消费;菜单期该进程靠 + * /tmp/meow_power_menu_active 让位,绝不与 launcher 抢屏/抢键); + * launcher 画菜单,本进程不自绘。 + * 电源长按 ≥power.forceoff_ms(缺省 8000)-> 强制关机兜底(仅当菜单未被 launcher 接管)。 * 音量±(115/114) -> 音量收编 v2(FUTURE_AUDIO 事项三):首选发 audiod 权威 * MSG_AUDIO_VOLUME(audiod 写 DAC numid=1/2 + DSD_VOLUME + 持久化 * settings.conf,同源同键单写者);audiod 未起(HibyOS/开机早期) @@ -62,6 +63,8 @@ #include #include #include "meow_ipc.h" /* MSG_AUDIO_VOLUME/MSG_ACK/帧格式(音量收编 v2:首选 audiod 权威) */ +#include "../sdk/meow_time32.h" /* o32 time32 取时/睡眠(watchdog 不链 meow_ui.a) */ +#include "wd_power.h" /* 电源阈值/边沿纯判定(宿主单测同源) */ /* device-verified: VSCREENINFO=0x4600, FSCREENINFO=0x4602 (do not swap) */ #define FBIOGET_VSCREENINFO 0x4600 @@ -95,11 +98,10 @@ #define KEY_NEXTSONG 163 #define KEY_PREVIOUS 19 #define BACKLIGHT "/sys/class/backlight/backlight_pwm0/brightness" -/* M2.24 全面按键接管:电源单击(<2s)=息屏/亮屏;长按≥2s=通知 launcher 弹统一电源菜单; - * ≥8s=强制关机兜底(仅当 launcher 未接管菜单时)。音量± = 写真实 DAC;播放/上下曲 = 抓消费。 */ -#define PWR_TOGGLE_MS 2000 /* 短按上限:<2s 松开=单击(镜像 launcher 单单击语义) */ -#define PWR_MENU_MS 2000 /* 长按≥2s -> 写 /tmp/meow_power_menu 请 launcher 弹菜单 */ -#define PWR_FORCE_MS 8000 /* 长按≥8s -> 强制关机(launcher 未接管时的兜底) */ +/* M2.24 全面按键接管:电源单击(launcher 菜单请求 */ #define PWR_MENU_ACTIVE "/tmp/meow_power_menu_active" /* launcher 画菜单中,watchdog 让位 */ #define MEOW_SETTINGS_FILE "/usr/data/meow/settings.conf" /* 音量持久化(与 launcher settings.conf 同源) */ @@ -150,11 +152,14 @@ static int read_state_file(void) * stderr when unset (e.g. no TF card at launch time). */ static int wd_log_fd = -1; +/* 单调毫秒:**必须**走 meow_time32(o32 内核 time32 取时)。 + * zig musl 的 time64 ABI 在 mipsel 上自相矛盾:直接 clock_gettime() 时结构按 16 字节写、 + * libc 只写 8 字节 time32,tv_sec 高 32 位会读成 nsec → held 变天文数字,每次单击都被 + * 判成长按并立刻弹菜单(真机日志 `power hold 12392415168036864000ms`,反汇编证实只有 + * 4263/无 4403)。meow_time32.c 不依赖 meow_ui.a,静态链入即可修正。 */ static unsigned long long now_ms(void) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (unsigned long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; + return (unsigned long long)meow_time32_monotonic_ms(); } static void wd_log_init(void) @@ -216,6 +221,9 @@ static int wd_fbfd = -1; /* fb0 fd(main 里打开,供 FB #define WD_SETTINGS_GAP_MS 10 static int wd_saved_bri = WD_BRI_UNKNOWN; /* 熄屏前亮度快照;未知=WD_BRI_UNKNOWN(不再用 60) */ +/* 电源阈值(启动时读 settings.conf;缺省 3000/8000,与 launcher 主屏一致)。 */ +static unsigned long long wd_pwr_longpress_ms = WD_PWR_LONGPRESS_DEFAULT_MS; +static unsigned long long wd_pwr_forceoff_ms = WD_PWR_FORCEOFF_DEFAULT_MS; static int pwr_down = 0; /* 电源当前按住 */ static unsigned long long pwr_down_ms = 0; static int pwr_woke = 0; /* 按下时屏已黑 → 本次只作唤醒,松开不再 toggle */ @@ -278,7 +286,7 @@ static int wd_settings_brightness(void) 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 (i + 1 < WD_SETTINGS_TRIES) meow_time32_sleep_us(WD_SETTINGS_GAP_MS * 1000); } if (!f) return -1; int b = 101; /* display.brightness 默认(与 launcher 设置表同值,键缺失时 clamp→100) */ @@ -301,6 +309,42 @@ static int wd_settings_brightness(void) return b; } +/* 读电源阈值(power.longpress_menu_ms / power.forceoff_ms,缺省 3000/8000)。 + * 与 wd_settings_brightness 同款重试窗口;文件不可读时**保留缺省**(绝不退回 2000—— + * 旧硬编码会让 App 内 2s 出菜单、主屏 3s,且 PWR_TOGGLE==PWR_MENU 时「长按不出菜单」 + * 的区间不存在)。解析后经 pure helper 清洗(非正数→缺省;forceoff≤longpress→抬 1s)。 */ +static void wd_power_settings_load(void) +{ + 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) meow_time32_sleep_us(WD_SETTINGS_GAP_MS * 1000); + } + if (!f) { + wd_pwr_resolve_thresholds(0, 0, &wd_pwr_longpress_ms, &wd_pwr_forceoff_ms); + wd_log("power: settings unreadable -> defaults longpress=%llums forceoff=%llums", + wd_pwr_longpress_ms, wd_pwr_forceoff_ms); + return; + } + int lp = 0, fo = 0; + char buf[128]; + while (fgets(buf, sizeof buf, f)) { + char *eq = strchr(buf, '='); + if (!eq) continue; + *eq = 0; + char *k = buf; int kl = (int)strlen(k); + while (kl > 0 && (k[kl-1] == ' ' || k[kl-1] == '\t')) k[--kl] = 0; + char *v = eq + 1; while (*v == ' ' || *v == '\t') v++; + if (!strcmp(k, "power.longpress_menu_ms")) lp = atoi(v); + else if (!strcmp(k, "power.forceoff_ms")) fo = atoi(v); + } + fclose(f); + wd_pwr_resolve_thresholds(lp, fo, &wd_pwr_longpress_ms, &wd_pwr_forceoff_ms); + wd_log("power: settings longpress=%llums forceoff=%llums (raw %d/%d)", + wd_pwr_longpress_ms, wd_pwr_forceoff_ms, lp, fo); +} + /* 强制 re-pan 到 buffer0:FBIOBLANK(4)->(0) 后 LCDC 扫描地址可能已复位到非 buffer0/失效 * 位置(launcher M2.8 Bug-3-residual 根因),而应用始终画 buffer0,必须无条件把显示翻回 * buffer0,否则唤醒后黑屏/旧帧。 */ @@ -378,15 +422,17 @@ static void wd_do_poweroff(void) syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_POWER_OFF, NULL); wd_log("sys_reboot returned: %s", strerror(errno)); /* 正常不会返回 */ - for (;;) usleep(1000000); + for (;;) meow_time32_sleep_us(1000000); } -/* 电源键(KEY_POWER)边沿驱动:单击(短按松开 <3s)= 息屏/亮屏;按下时屏已黑 = 只唤醒。 - * 长按(≥3s)由 wd_power_tick() 在轮询中检测 → 写 /tmp/meow_power_menu 请 launcher 弹菜单; - * 松开边沿不再 toggle(菜单/长按语义归 launcher 菜单,防「长按后仍 toggle」)。 */ +/* 电源键(KEY_POWER)边沿驱动:单击(松开时 held < power.longpress_menu_ms,缺省 3s)= 息屏/亮屏; + * 按下时屏已黑 = 只唤醒。长按(≥longpress)由 wd_power_tick() 在轮询中检测 → 写 + * /tmp/meow_power_menu 请 launcher 弹菜单;松开边沿不再 toggle(菜单/长按语义归 launcher + * 菜单,防「长按后仍 toggle」)。 */ static void handle_power(int val) { if (wd_menu_active()) return; /* 电源菜单期:launcher 独占物理键语义 */ + if (val == 2) return; /* EV_KEY 重复值:本设备 event0 无 EV_REP,防御性忽略 */ if (val) { /* 按下边沿 */ pwr_down = 1; @@ -396,28 +442,40 @@ static void handle_power(int val) int br = read_brightness(); pwr_woke = (br <= 0) ? 1 : 0; /* 屏已黑:本次电源=唤醒,松开不 toggle */ if (pwr_woke) wd_panel_on(); /* 立即亮屏(launcher 自动息屏唤醒路径同步醒,双保险幂等) */ - wd_log("power down (bri=%d woke=%d)", br, pwr_woke); + wd_log("power down (bri=%d woke=%d longpress=%llums)", br, pwr_woke, + wd_pwr_longpress_ms); return; } /* 松开边沿 */ if (!pwr_down) return; /* 未跟踪到按下(启动前已按住/残留):忽略,防误 toggle */ pwr_down = 0; unsigned long long held = now_ms() - pwr_down_ms; - if (pwr_woke) { pwr_woke = 0; wd_log("power release after wake (%llums) -> no toggle", held); return; } + int br = read_brightness(); + wd_pwr_release_act act = wd_pwr_release_action(pwr_woke, pwr_menu_notified, held, + wd_pwr_longpress_ms, br); + if (pwr_woke) { + pwr_woke = 0; + wd_log("power release after wake (%llums) -> no toggle", held); + return; + } if (pwr_menu_notified) { - /* 本次已请求电源菜单(≥3s):菜单是否画出由 launcher 负责,这里绝不 toggle。 */ + /* 本次已请求电源菜单(≥longpress):菜单是否画出由 launcher 负责,这里绝不 toggle。 */ wd_log("power release after menu req (%llums) -> menu owns toggle", held); pwr_menu_notified = 0; return; } - if (held >= PWR_TOGGLE_MS) { wd_log("power hold %llums -> no-op (menu/force-off in tick)", held); return; } - int br = read_brightness(); - if (br > 0) wd_panel_off(); - else wd_panel_on(); /* 屏已黑仍短按:亮屏 */ + if (act == WD_PWR_REL_NONE) { + /* held ≥ longpress 但 tick 还没发出菜单(轮询粒度/边界):仍按长按处理,不 toggle。 */ + wd_log("power hold %llums -> no-op (menu/force-off in tick)", held); + return; + } + if (act == WD_PWR_REL_TOGGLE_OFF) wd_panel_off(); + else wd_panel_on(); /* 屏已黑仍短按:亮屏 */ wd_log("power short-press %llums -> toggle panel (bri was %d)", held, br); } -/* 电源长按检测(每轮询 tick 调):≥3s 写菜单请求;≥8s 强制关机兜底(仅当菜单未生效时)。 */ +/* 电源长按检测(每轮询 tick 调):≥longpress 写菜单请求;≥forceoff 强制关机兜底 + * (launcher 已接管菜单时顶部即让位,不会走到这里)。阈值来自 settings。 */ static void wd_power_tick(void) { /* launcher 已接管电源菜单:本进程完全让位,清空电源状态,下一手势从零开始。 */ @@ -428,14 +486,15 @@ static void wd_power_tick(void) } if (!pwr_down) return; unsigned long long held = now_ms() - pwr_down_ms; - if (held >= PWR_MENU_MS && !pwr_menu_notified) { + wd_pwr_tick_act act = wd_pwr_tick_action(0, pwr_down, pwr_menu_notified, pwr_force_done, + held, wd_pwr_longpress_ms, wd_pwr_forceoff_ms); + if (act == WD_PWR_TICK_MENU) { int fd = open(PWR_MENU_MARKER, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd >= 0) { write(fd, "1", 1); close(fd); } pwr_menu_notified = 1; wd_log("power hold %llums -> %s (launcher draws menu)", held, PWR_MENU_MARKER); - } - if (held >= PWR_FORCE_MS && !pwr_force_done && !pwr_menu_notified) { - /* 长按 8s 仍无菜单接管(launcher 未响应/未消费)→ 强制关机兜底。 */ + } else if (act == WD_PWR_TICK_FORCEOFF) { + /* 长按 ≥forceoff 仍无菜单接管(launcher 未响应/未消费)→ 强制关机兜底。 */ pwr_force_done = 1; wd_log("power hold %llums -> FORCE POWER OFF", held); wd_do_poweroff(); @@ -805,6 +864,7 @@ int main(int argc, char **argv) setvbuf(stderr, NULL, _IONBF, 0); wd_log_init(); wd_vol_load(); /* M2.24:音量初始值读自与 launcher 同源 settings.conf */ + wd_power_settings_load(); /* M40:电源长按/强关阈值(缺省 3000/8000,与 launcher 主屏同源) */ /* M2.86(226):SDK 框架 App 判定(app.json framework=1)-> 音量/媒体键让位 SDK。 */ g_fw_app = wd_framework_from_pid(app_pid); wd_log("app_pid=%d sim_tap=%d sim_swipe=%d framework=%d%s", @@ -870,7 +930,7 @@ int main(int argc, char **argv) if (sim_tap && elapsed >= (long)sim_tap * 1000) { wd_log("SIM TAP"); kill(app_pid, SIGTERM); - usleep(1000000); + meow_time32_sleep_us(1000000); kill(app_pid, SIGKILL); break; } diff --git a/patch_v2/meow_watchdog/test_wd_fw b/patch_v2/meow_watchdog/test_wd_fw index 9ec00be..29338cc 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 a62f46c..805ed34 100644 --- a/patch_v2/meow_watchdog/test_wd_fw.c +++ b/patch_v2/meow_watchdog/test_wd_fw.c @@ -1,9 +1,10 @@ -/* test_wd_fw.c — 宿主单测:meow_watchdog 的纯逻辑(M2.86 框架 App 判定 + M20 亮屏亮度)。 +/* test_wd_fw.c — 宿主单测:meow_watchdog 的纯逻辑(M2.86 框架 App 判定 + M20 亮屏亮度 + + * M40 time32 取时/睡眠 + 电源阈值行为矩阵)。 * * 编译:#include meow_watchdog.c(去 main 拿 wd_app_is_framework/json_str/from_pid、 - * wd_bri_decide、wd_settings_brightness、wd_panel_off/on)。 + * wd_bri_decide、wd_settings_brightness、wd_panel_off/on;wd_power.h 的纯判定)。 * gcc -Wall -Wextra -Os -DWd_NO_MAIN -Wno-unused-function -Wno-unused-parameter \ - * test_wd_fw.c -o test_wd_fw + * test_wd_fw.c ../sdk/meow_time32.c -o test_wd_fw * (meow_watchdog.c 含仅 main 使用的静态函数,宿主去 main 后须 -Wno-unused-function 静音; * mips 目标构建不定义 WD_NO_MAIN,不受影响。) * 运行:./test_wd_fw;全 PASS -> exit 0。 @@ -223,6 +224,84 @@ static void test_panel_brightness_e2e(void) unsetenv("MEOW_SETTINGS_FILE"); } +/* ---- M40:time32 取时/睡眠(与 libc 正常取时一致;o32 syscall 路径由交叉二进制反汇编验证)---- */ +static void test_time32(void) +{ + struct meow_time32 t; + check("time32 gettime ok", meow_time32_clock_gettime(CLOCK_MONOTONIC, &t) == 0, 1); + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + check("time32 sec == libc sec", t.tv_sec == (int32_t)ts.tv_sec, 1); + long dn = (long)t.tv_nsec - (long)ts.tv_nsec; + if (dn < 0) dn = -dn; + check("time32 nsec == libc nsec (<1ms)", dn < 1000000, 1); + + uint64_t a = meow_time32_monotonic_ms(); + uint64_t b = meow_time32_monotonic_ms(); + check("monotonic nonzero", a > 0, 1); + check("monotonic nondecreasing", b >= a, 1); + check("ms from parts 1135.008s", meow_time_ms_from_parts(1135, 8000000) == 1135008ULL, 1); + + unsigned long long s0 = now_ms(); + meow_time32_sleep_us(20000); + unsigned long long el = now_ms() - s0; + check("sleep 20ms actually sleeps (>=15ms)", el >= 15, 1); +} + +/* ---- M40:电源阈值/边沿行为矩阵(settings 取不同值)---- */ +static void test_power_matrix(void) +{ + unsigned long long lp = 0, fo = 0; + + wd_pwr_resolve_thresholds(0, 0, &lp, &fo); + check("pwr default longpress=3000", lp == 3000, 1); + check("pwr default forceoff=8000", fo == 8000, 1); + + wd_pwr_resolve_thresholds(1500, 5000, &lp, &fo); + check("pwr settings longpress=1500", lp == 1500, 1); + check("pwr settings forceoff=5000", fo == 5000, 1); + + wd_pwr_resolve_thresholds(-1, 0, &lp, &fo); + check("pwr invalid -> default longpress", lp == 3000, 1); + check("pwr invalid -> default forceoff", fo == 8000, 1); + + wd_pwr_resolve_thresholds(3000, 2000, &lp, &fo); + check("pwr forceoff<=longpress lifted to lp+1000", fo == 4000, 1); + + /* 松开边沿:单击 / 长按但未到阈值 / 到阈值 / 唤醒 / 菜单已请求 */ + check("release 100ms screen-on -> toggle off", + wd_pwr_release_action(0, 0, 100, 3000, 50) == WD_PWR_REL_TOGGLE_OFF, 1); + check("release 100ms screen-off -> toggle on", + wd_pwr_release_action(0, 0, 100, 3000, 0) == WD_PWR_REL_TOGGLE_ON, 1); + check("release 2500ms (<3000) still click -> toggle off", + wd_pwr_release_action(0, 0, 2500, 3000, 50) == WD_PWR_REL_TOGGLE_OFF, 1); + check("release 3000ms (>=longpress) -> no toggle", + wd_pwr_release_action(0, 0, 3000, 3000, 50) == WD_PWR_REL_NONE, 1); + check("release after wake -> no toggle", + wd_pwr_release_action(1, 0, 100, 3000, 0) == WD_PWR_REL_NONE, 1); + check("release after menu req -> no toggle", + wd_pwr_release_action(0, 1, 3500, 3000, 50) == WD_PWR_REL_NONE, 1); + + /* 按住 tick:单击窗 / 长按出菜单 / 强关兜底 / 菜单已接管 / 已强关 */ + check("tick 2999ms -> none", + wd_pwr_tick_action(0, 1, 0, 0, 2999, 3000, 8000) == WD_PWR_TICK_NONE, 1); + check("tick 3000ms -> menu", + wd_pwr_tick_action(0, 1, 0, 0, 3000, 3000, 8000) == WD_PWR_TICK_MENU, 1); + check("tick 8000ms menu requested, no takeover -> forceoff", + wd_pwr_tick_action(0, 1, 1, 0, 8000, 3000, 8000) == WD_PWR_TICK_FORCEOFF, 1); + check("tick 8000ms launcher menu active -> none", + wd_pwr_tick_action(1, 1, 1, 0, 8000, 3000, 8000) == WD_PWR_TICK_NONE, 1); + check("tick forceoff already done -> none", + wd_pwr_tick_action(0, 1, 1, 1, 9000, 3000, 8000) == WD_PWR_TICK_NONE, 1); + check("tick not down -> none", + wd_pwr_tick_action(0, 0, 0, 0, 9000, 3000, 8000) == WD_PWR_TICK_NONE, 1); + /* settings 取短值:1000/2000 */ + check("tick custom 1000ms -> menu", + wd_pwr_tick_action(0, 1, 0, 0, 1000, 1000, 2000) == WD_PWR_TICK_MENU, 1); + check("tick custom 2000ms no takeover -> forceoff", + wd_pwr_tick_action(0, 1, 1, 0, 2000, 1000, 2000) == WD_PWR_TICK_FORCEOFF, 1); +} + int main(void) { signal(SIGUSR1, on_sigusr1); /* 捕获 handle_touch 可能发出的 SIGUSR1(测命中) */ @@ -270,6 +349,10 @@ int main(void) test_settings_brightness(); test_panel_brightness_e2e(); + /* ---- M40:time32 取时/睡眠 + 电源阈值行为矩阵 ---- */ + test_time32(); + test_power_matrix(); + /* 清理 */ { char path[560]; diff --git a/patch_v2/meow_watchdog/wd_power.h b/patch_v2/meow_watchdog/wd_power.h new file mode 100644 index 0000000..a250e88 --- /dev/null +++ b/patch_v2/meow_watchdog/wd_power.h @@ -0,0 +1,73 @@ +/* wd_power.h — watchdog 电源键判定(纯函数,宿主可单测,无设备/无文件系统依赖)。 + * + * 语义与 launcher 主屏(main_menu.c)统一: + * 单击(松开时 held < power.longpress_menu_ms) → 息屏/亮屏切换; + * 长按 held ≥ power.longpress_menu_ms → 写菜单请求(watchdog 让位 launcher); + * 长按 held ≥ power.forceoff_ms 且菜单未被接管 → 强制关机兜底。 + * 旧实现把 PWR_TOGGLE_MS 与 PWR_MENU_MS 都硬编码成 2000,导致「长按但不出菜单」的区间 + * 不存在,且 App 内 2s / launcher 主屏 3s 不一致;现在两者都读 settings(缺省 3000/8000)。 + * + * 阈值清洗: + * - 非正数 → 默认 3000 / 8000(settings 允许 0,但 0ms 阈值会让每次按下立刻出菜单,无意义); + * - forceoff ≤ longpress → 抬到 longpress+1000,避免「强关先于菜单」的自相矛盾配置。 + */ +#ifndef WD_POWER_H +#define WD_POWER_H + +#define WD_PWR_LONGPRESS_DEFAULT_MS 3000 +#define WD_PWR_FORCEOFF_DEFAULT_MS 8000 + +static inline void wd_pwr_resolve_thresholds(int cfg_longpress, int cfg_forceoff, + unsigned long long *longpress_ms, + unsigned long long *forceoff_ms) +{ + unsigned long long lp = (cfg_longpress > 0) + ? (unsigned long long)cfg_longpress + : WD_PWR_LONGPRESS_DEFAULT_MS; + unsigned long long fo = (cfg_forceoff > 0) + ? (unsigned long long)cfg_forceoff + : WD_PWR_FORCEOFF_DEFAULT_MS; + if (fo <= lp) fo = lp + 1000; + *longpress_ms = lp; + *forceoff_ms = fo; +} + +/* 松开边沿的动作。bright>0=屏亮 → 息屏;否则亮屏。 + * woke(按下时屏已黑,本次只唤醒)/ menu_notified(已请求菜单)→ 不再 toggle。 */ +typedef enum { + WD_PWR_REL_NONE = 0, + WD_PWR_REL_TOGGLE_OFF, + WD_PWR_REL_TOGGLE_ON +} wd_pwr_release_act; + +static inline wd_pwr_release_act wd_pwr_release_action(int woke, int menu_notified, + unsigned long long held_ms, + unsigned long long longpress_ms, + int bright) +{ + if (woke || menu_notified) return WD_PWR_REL_NONE; + if (held_ms >= longpress_ms) return WD_PWR_REL_NONE; /* 长按已由 tick 处理 */ + return (bright > 0) ? WD_PWR_REL_TOGGLE_OFF : WD_PWR_REL_TOGGLE_ON; +} + +/* 轮询 tick(按住中)的动作。menu_active=launcher 已接管 → 全让位。 + * 强关不要求 menu_notified:launcher 未接管菜单时 8s 兜底仍须生效。 */ +typedef enum { + WD_PWR_TICK_NONE = 0, + WD_PWR_TICK_MENU, + WD_PWR_TICK_FORCEOFF +} wd_pwr_tick_act; + +static inline wd_pwr_tick_act wd_pwr_tick_action(int menu_active, int down, + int menu_notified, int force_done, + unsigned long long held_ms, + unsigned long long longpress_ms, + unsigned long long forceoff_ms) +{ + if (menu_active || !down) return WD_PWR_TICK_NONE; + if (held_ms >= forceoff_ms && !force_done) return WD_PWR_TICK_FORCEOFF; + if (held_ms >= longpress_ms && !menu_notified) return WD_PWR_TICK_MENU; + return WD_PWR_TICK_NONE; +} + +#endif /* WD_POWER_H */ diff --git a/patch_v2/ncm_app/ncm b/patch_v2/ncm_app/ncm index ef51ff8..a7952b5 100755 Binary files a/patch_v2/ncm_app/ncm and b/patch_v2/ncm_app/ncm differ diff --git a/patch_v2/sdk/CLOCK_ABI.md b/patch_v2/sdk/CLOCK_ABI.md index 81b4932..d52047a 100644 --- a/patch_v2/sdk/CLOCK_ABI.md +++ b/patch_v2/sdk/CLOCK_ABI.md @@ -316,3 +316,33 @@ llvm-nm probe_sdk | grep -c __timedwait_cp # 1(libc 那份未被拉入) 真机验证仍待设备(本单未碰真机):`pthread_cond_timedwait` 传 100ms 超时的等待应真的等 约 100ms 再超时,而不是立即返回。 + +## 9. M40:独立链接的 time32 基础设施(系统组件解药) + +M16 的全部修复都只在 **链接 meow_ui.a** 的进程里生效。不链 SDK 的系统组件(watchdog / +sysui / svcd / bootmenu / amsd / audiod / netmgrd / crash / svc_perf / powermgr / meowpanel) +仍然各自中招:它们的 `now_ms()`/超时/退避全部按 16 字节 time64 读、libc 只写 8 字节 time32。 + +M40 把与 SDK 无关的部分抽成可独立链接的 TU: + +| 文件 | 内容 | 依赖 | +| --- | --- | --- | +| `sdk/meow_time32.h` | o32 结构、纯换算、`meow_time32_clock_gettime()` / `meow_time32_monotonic_ms()` / `meow_time32_sleep_us()` | 仅 stdint | +| `sdk/meow_time32.c` | 上述实现;mips o32 走 `SYS_clock_gettime32` / `SYS_nanosleep`,宿主回退 libc | libc | +| `sdk/meow_ui_time.c` | 保留 futex/timerfd/clock_settime 接管;换算/取时改为调用 `meow_time32.c` | SDK | + +系统组件构建脚本只需把 `../sdk/meow_time32.c` 加进链接命令;若希望链接期整体接管 +libc 的取时/睡眠入口(不必逐个改调用点),再编译该 TU 时加 `-DMEOW_TIME32_LIBC_OVERRIDES` +(**不要**与 meow_ui.a 同时链接,否则 `__clock_gettime` 等重复定义)。 + +当前已接入:`meow_watchdog`(`build_watchdog.sh`)。待接入清单与各自脚本见 M40 报文; +本单不改那些组件的源码/脚本(scope 限制),只提供设施与接入范式。 + +宿主单测: + +``` +cd patch_v2/meow_watchdog +bash build_watchdog.sh host # time32 取时/睡眠 + 电源阈值矩阵(66 项) +cd ../sdk +gcc -Wall -Wextra -I. test_meow_repaint.c -o /tmp/tr && /tmp/tr +``` diff --git a/patch_v2/sdk/build_sdk.sh b/patch_v2/sdk/build_sdk.sh index d4e65a3..c89b029 100755 --- a/patch_v2/sdk/build_sdk.sh +++ b/patch_v2/sdk/build_sdk.sh @@ -7,8 +7,9 @@ # 产物 sdk/vendor/freetype-build/libfreetype.a,其 .o 编入 meow_ui.a —— 应用只链 meow_ui.a # 即获得矢量字能力;不调 meow_ft_* 的 App 链接器不会拉取 freetype 成员(零增量)。 # -# 时钟(meow_ui_time):mipsel 上 zig 0.17 musl 的 time64 ABI 自相矛盾(头 64 位 / 实现 -# time32),该 TU 按内核 o32 ABI 取时并强定义 __clock_gettime*/睡眠入口,链接期接管。 +# 时钟(meow_ui_time + meow_time32):mipsel 上 zig 0.17 musl 的 time64 ABI 自相矛盾(头 64 位 / +# 实现 time32)。meow_time32.c 是可独立链接的 o32 取时/睡眠设施(系统组件不链 SDK 也能用); +# meow_ui_time.c 在其上做链接期接管(__clock_gettime*/futex/timerfd/clock_settime)。 # 根因与验证见 sdk/CLOCK_ABI.md;改动后必须重编 meow_ui.a 并重链四 App。 set -e ROOT="$HOME/Project/R3ProII" @@ -44,7 +45,7 @@ FT_INCS="-I$FT_BUILD -I$FT_BUILD/include -I$FT_SRC/include" rm -f meow_ui.a *.o # -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移) -OBJS="meow_ui_time meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_jpeg meow_png_stream meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_pinyin meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view meow_ui_nav meow_ui_media meow_ui_audio stb_image meow_ui_box meow_ui_anim meow_ui_scroll meow_lazygrid meow_select meow_ui_str meow_ui_ft meow_net_ipc meow_cachegov" +OBJS="meow_time32 meow_ui_time meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_jpeg meow_png_stream meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_pinyin meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view meow_ui_nav meow_ui_media meow_ui_audio stb_image meow_ui_box meow_ui_anim meow_ui_scroll meow_lazygrid meow_select meow_ui_str meow_ui_ft meow_net_ipc meow_cachegov" CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd" for f in $OBJS; do echo "== cc $f ==" diff --git a/patch_v2/sdk/meow_time32.c b/patch_v2/sdk/meow_time32.c new file mode 100644 index 0000000..c5a5f2d --- /dev/null +++ b/patch_v2/sdk/meow_time32.c @@ -0,0 +1,191 @@ +/* meow_time32.c — mipsel o32 内核 time32 ABI 兜底(独立链接,不依赖 meow_ui.a)。 + * + * 根因与逐项证据:sdk/CLOCK_ABI.md(M11/M16)。核心一句话: + * zig 0.17 的 musl 在 mipsel 上按 `#if defined(SYS_x_time64) && !defined(__ILP32__)` + * 守卫 time64 回退,而 zig clang 又预定义了 __ILP32__,于是「time64 入口」被编成 + * 老的 time32 实现:内核只写 8 字节 {int32 sec; int32 nsec;},调用方却按 16 字节 + * 结构读。本文件不猜 libc 头的结构宽度,**直接按内核 ABI 取数/睡眠**。 + * + * 本 TU 不引用 SDK 符号;系统组件(watchdog 等)可单独链接它。宿主(非 mips o32) + * 自动退回 libc 的 clock_gettime/nanosleep,行为等价,宿主单测可直接编译运行。 + */ +#include +#include +#include +#include + +#include "meow_time32.h" + +#if defined(__mips__) && !defined(__mips64) && (__SIZEOF_LONG__ == 4) +#define MEOW_TIME32_KERNEL_ABI 1 +#include +#else +#define MEOW_TIME32_KERNEL_ABI 0 +#endif + +/* ---- 纯换算 ---- */ + +void meow_time32_rel_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out) +{ + if (sec > INT32_MAX) sec = INT32_MAX; + else if (sec < INT32_MIN) sec = INT32_MIN; + out->tv_sec = (int32_t)sec; + out->tv_nsec = (int32_t)nsec; +} + +int meow_time32_abs_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out) +{ + if (sec > INT32_MAX || sec < INT32_MIN) return -1; + out->tv_sec = (int32_t)sec; + out->tv_nsec = (int32_t)nsec; + return 0; +} + +int meow_time32_itimerspec_from_64(const struct meow_time64_itimerspec *in, + struct meow_itimerspec32 *out) +{ + struct meow_itimerspec32 t; + if (meow_time32_abs_from_parts(in->it_interval.tv_sec, in->it_interval.tv_nsec, + &t.it_interval)) return -1; + if (meow_time32_abs_from_parts(in->it_value.tv_sec, in->it_value.tv_nsec, + &t.it_value)) return -1; + *out = t; + return 0; +} + +void meow_time64_itimerspec_from_32(const struct meow_itimerspec32 *in, + struct meow_time64_itimerspec *out) +{ + out->it_interval.tv_sec = in->it_interval.tv_sec; + out->it_interval.tv_nsec = in->it_interval.tv_nsec; + out->it_value.tv_sec = in->it_value.tv_sec; + out->it_value.tv_nsec = in->it_value.tv_nsec; +} + +uint64_t meow_time_ms_from_parts(int64_t sec, int64_t nsec) +{ + return (uint64_t)sec * 1000u + (uint64_t)nsec / 1000000u; +} + +/* ---- 内核取时 ---- */ + +int meow_time32_clock_gettime(int clk, struct meow_time32 *out) +{ + if (!out) { errno = EFAULT; return -1; } +#if MEOW_TIME32_KERNEL_ABI + out->tv_sec = 0; + out->tv_nsec = 0; + if (syscall(SYS_clock_gettime32, clk, out) != 0) return -1; + return 0; +#else + struct timespec ts; + if (clock_gettime((clockid_t)clk, &ts) != 0) return -1; + out->tv_sec = (int32_t)ts.tv_sec; + out->tv_nsec = (int32_t)ts.tv_nsec; + return 0; +#endif +} + +uint64_t meow_time32_monotonic_ms(void) +{ + struct meow_time32 t; + if (meow_time32_clock_gettime(CLOCK_MONOTONIC, &t) != 0) return 0; + return meow_time_ms_from_parts(t.tv_sec, t.tv_nsec); +} + +/* ---- 睡眠(usleep 同源受损:16 字节 time64 直接交给 time32 系统调用时 nsec 恒 0, + * 亚秒睡眠立即返回 → 轮询/退避变忙等空转)---- */ + +int meow_time32_sleep_us(unsigned usec) +{ +#if MEOW_TIME32_KERNEL_ABI + struct meow_time32 rq, rm; + rq.tv_sec = (int32_t)(usec / 1000000u); + rq.tv_nsec = (int32_t)((usec % 1000000u) * 1000u); + for (;;) { + long r = syscall(SYS_nanosleep, &rq, &rm); + if (r == 0) return 0; + if (errno != EINTR) return -1; + rq = rm; /* 被信号打断:按剩余时间续睡 */ + } +#else + struct timespec rq, rm; + rq.tv_sec = (time_t)(usec / 1000000u); + rq.tv_nsec = (long)((usec % 1000000u) * 1000u); + for (;;) { + if (nanosleep(&rq, &rm) == 0) return 0; + if (errno != EINTR) return -1; + rq = rm; + } +#endif +} + +/* ---- 可选:链接期接管 libc 取时/睡眠入口 ---- + * 系统组件加 -DMEOW_TIME32_LIBC_OVERRIDES 编译本文件即可让未改写的 clock_gettime()/ + * usleep() 调用点也恢复正确(静态链接时本对象的强定义覆盖 libc 归档成员)。 + * 不要在同时链接 meow_ui.a(meow_ui_time.o) 的进程里启用——两者符号重复。 + * 与 meow_ui_time.c 的差别:这里只接管取时 + nanosleep;futex 相对超时 / timerfd / + * clock_settime 的接管仍在 meow_ui_time.c(SDK 消费方自动获得)。 */ +#if MEOW_TIME32_KERNEL_ABI && defined(MEOW_TIME32_LIBC_OVERRIDES) + +static int32_t meow_time32_clamp(int64_t v) +{ + if (v > INT32_MAX) return INT32_MAX; + if (v < 0) return 0; + return (int32_t)v; +} + +int __clock_gettime(clockid_t clk, struct timespec *ts) +{ + if (!ts) { errno = EFAULT; return -1; } + struct meow_time32 t; + if (meow_time32_clock_gettime(clk, &t) != 0) return -1; + ts->tv_sec = t.tv_sec; + ts->tv_nsec = t.tv_nsec; + return 0; +} + +int __clock_gettime64(clockid_t clk, struct timespec *ts) +{ + return __clock_gettime(clk, ts); +} + +int __clock_nanosleep(clockid_t clk, int flags, const struct timespec *req, struct timespec *rem) +{ + struct meow_time32 rq, rm; + long r; + + if (clk == CLOCK_THREAD_CPUTIME_ID) return EINVAL; + if (!req) return EFAULT; + if (req->tv_nsec < 0 || req->tv_nsec >= 1000000000L) return EINVAL; + rq.tv_sec = meow_time32_clamp(req->tv_sec); + rq.tv_nsec = (int32_t)req->tv_nsec; + rm.tv_sec = 0; + rm.tv_nsec = 0; + + if (clk == CLOCK_REALTIME && !flags) + r = syscall(SYS_nanosleep, &rq, rem ? &rm : 0); + else + r = syscall(SYS_clock_nanosleep_time32, clk, flags, &rq, rem ? &rm : 0); + if (r != 0) return errno; + if (rem) { + rem->tv_sec = rm.tv_sec; + rem->tv_nsec = rm.tv_nsec; + } + return 0; +} + +int __clock_nanosleep_time64(clockid_t clk, int flags, const struct timespec *req, + struct timespec *rem) +{ + return __clock_nanosleep(clk, flags, req, rem); +} + +int __nanosleep_time64(const struct timespec *req, struct timespec *rem) +{ + int r = __clock_nanosleep(CLOCK_REALTIME, 0, req, rem); + if (r) { errno = r; return -1; } + return 0; +} + +#endif /* MEOW_TIME32_KERNEL_ABI && MEOW_TIME32_LIBC_OVERRIDES */ diff --git a/patch_v2/sdk/meow_time32.h b/patch_v2/sdk/meow_time32.h new file mode 100644 index 0000000..1ab4b26 --- /dev/null +++ b/patch_v2/sdk/meow_time32.h @@ -0,0 +1,78 @@ +/* meow_time32.h — mipsel o32 内核 time32 ABI 兜底(**可独立链接**的公共设施)。 + * + * 为什么单独成文件(M40): + * zig 0.17 给 32 位 mips 预定义 __ILP32__,打穿 musl 的 time64 回退守卫:调用方按 + * 16 字节 time64 结构取数,libc 却只发 time32 系统调用(只写前 8 字节)。结果是 + * 「秒在低 32 位、毫秒位恒 0、高 32 位是 nsec」的垃圾时间,任何 now_ms()/超时/退避 + * 都会失真。meow_ui_time.c 的修复只对**链接 meow_ui.a** 的进程生效;系统组件 + * (watchdog / sysui / svcd / bootmenu / amsd / audiod / netmgrd / crash / svc_perf / + * powermgr / meowpanel)各自独立静态链接,不链 SDK,因此需要一份不依赖 SDK 的实现。 + * + * 本 TU 只依赖 libc(stdint/unistd/time/syscall),不引用 meow_ui.a 的任何符号: + * - 纯换算函数(与内核结构宽度解耦,宿主可直接单测); + * - 内核 o32 time32 取时(meow_time32_clock_gettime / meow_time32_monotonic_ms); + * - 亚秒睡眠(meow_time32_sleep_us;usleep 同源受损,见 meow_ui_time.c 注释)。 + * + * 用法(系统组件的构建脚本): + * $CC ... meow_watchdog.c ../sdk/meow_time32.c -o meow_watchdog + * 若希望**链接期整体接管 libc 的取时/睡眠入口**(不再逐个改调用点),额外加宏 + * -DMEOW_TIME32_LIBC_OVERRIDES + * 该宏只在 mipsel o32 上生效;此时**不要**同时链接 meow_ui.a 里的 meow_ui_time.o + * (两者都会强定义 __clock_gettime 等符号)。 + */ +#ifndef MEOW_TIME32_H +#define MEOW_TIME32_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* mipsel o32 内核结构(与 libc 头里的 time_t 宽度无关): + * { int32 tv_sec; int32 tv_nsec; } —— 固定 8 字节。 */ +struct meow_time32 { + int32_t tv_sec; + int32_t tv_nsec; +}; +struct meow_itimerspec32 { + struct meow_time32 it_interval; + struct meow_time32 it_value; +}; +/* 与 libc 结构解耦的 64 位镜像(宿主/交叉两端 time_t 宽度不同,测试不依赖它)。 */ +struct meow_time64_spec { + int64_t tv_sec; + int64_t tv_nsec; +}; +struct meow_time64_itimerspec { + struct meow_time64_spec it_interval; + struct meow_time64_spec it_value; +}; + +/* ---- 纯换算(无系统调用;宿主单测直接覆盖)---- */ +/* 相对超时(futex / nanosleep 的「等多久」):sec 超出 int32 时饱和 + * (正→INT32_MAX、负→INT32_MIN,与 musl CLAMP 一致),nsec 原样。 */ +void meow_time32_rel_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out); +/* 绝对时刻(clock_settime / timerfd):sec 落不进 int32 返回 -1,nsec 原样。 */ +int meow_time32_abs_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out); +/* itimerspec 转换;任一侧越界返回 -1 且 *out 不变。 */ +int meow_time32_itimerspec_from_64(const struct meow_time64_itimerspec *in, + struct meow_itimerspec32 *out); +void meow_time64_itimerspec_from_32(const struct meow_itimerspec32 *in, + struct meow_time64_itimerspec *out); +/* 秒/纳秒 → 单调毫秒的唯一换算点。 */ +uint64_t meow_time_ms_from_parts(int64_t sec, int64_t nsec); + +/* ---- 内核取时 / 睡眠 ---- */ +/* 按内核 o32 ABI 取时(clk 用 的 CLOCK_*)。返回 0=成功 / -1=失败(errno)。 */ +int meow_time32_clock_gettime(int clk, struct meow_time32 *out); +/* CLOCK_MONOTONIC 毫秒;失败返回 0。 */ +uint64_t meow_time32_monotonic_ms(void); +/* 睡眠 usec 微秒(EINTR 自动续睡)。返回 0=正常 / -1=失败(errno)。 */ +int meow_time32_sleep_us(unsigned usec); + +#ifdef __cplusplus +} +#endif + +#endif /* MEOW_TIME32_H */ diff --git a/patch_v2/sdk/meow_ui_gfx.c b/patch_v2/sdk/meow_ui_gfx.c index de16eea..0461523 100644 --- a/patch_v2/sdk/meow_ui_gfx.c +++ b/patch_v2/sdk/meow_ui_gfx.c @@ -1140,6 +1140,9 @@ unsigned long long meow_gfx_overlay_suppressed_count(void) { return g_overlay_su static void present(void) { if (!g_init_ok || !g_back_buf) return; + /* M40 F5/P5-P6:launcher 关闭锁屏/菜单前写的「强制整帧重画」请求。必须在脏区早退与 + * 叠加抑制早退**之前**消费:抑制期只置 dirty_full(标记翻回前台后的下一帧整屏上屏)。 */ + if (meow_ui_repaint_requested()) dirty_full(); if (!(g_dirty_full || g_ndirty > 0)) return; /* §10.5 叠加让位自抑(仅 App 进程):App 前台播放中唤起通知中心时 launcher 不 SIGSTOP * (保 PCM 供流,ncm 播放线程同进程),改由 App 进程在此自抑上屏——绘制照常进 back_buf, diff --git a/patch_v2/sdk/meow_ui_internal.h b/patch_v2/sdk/meow_ui_internal.h index ba2f6bb..10c0019 100644 --- a/patch_v2/sdk/meow_ui_internal.h +++ b/patch_v2/sdk/meow_ui_internal.h @@ -169,6 +169,12 @@ unsigned long long meow_gfx_overlay_suppressed_count(void); * 系统 UI(launcher)恒 0;200ms 节流缓存。meow_ui_gfx.c 实现,input 层复用。 */ int meow_ui_overlay_suppressed_active(void); +/* M40 F5/P5-P6:解锁后强制 App 整帧重画。launcher 在 SIGCONT 前写请求文件;present 层 + * 调用 meow_ui_repaint_requested():文件存在 → 删除并返回 1(调用方 dirty_full 一次)。 + * 抑制期也消费(dirty_full 保留到标记翻回前台后的下一帧)。实现见 meow_ui_repaint.h + * (static inline,宿主单测只 include 头即可,不必链接渲染栈)。 */ +#include "meow_ui_repaint.h" + /* 中文/日文字库(mfont)是否已加载(状态栏「蓝牙」CJK 与回退 "BT" 二选一)。 */ int meow_gfx_mfont_ready(void); @@ -300,42 +306,9 @@ int meow_ime_handle_event(const meow_input_ev *e); /* 当前 IME 是否拼音模式(供 meow_ui_input.c 决定是否喂长按轮询)。 */ int meow_ime_mode_pinyin(void); -/* ---- 时钟(meow_ui_time.c)---- */ -/* 秒/纳秒 → 单调毫秒的唯一换算点(meow_ui_time_ms 与宿主单测共用)。 */ -uint64_t meow_time_ms_from_parts(int64_t sec, int64_t nsec); - -/* ---- mipsel o32 的 time32 结构(内核布局,与 libc 头的 time_t 宽度无关)---- - * 背景:zig clang 给 32 位目标预定义 __ILP32__,打穿 musl 的 time64 回退守卫, - * 一批封装把 16 字节 time64 结构直接交给 time32 系统调用(CLOCK_ABI.md §6)。 - * 接管这些入口时统一走下面这几个纯转换函数——它们不依赖平台,宿主单测直接覆盖。 */ -struct meow_time32 { - int32_t tv_sec; - int32_t tv_nsec; -}; -struct meow_itimerspec32 { - struct meow_time32 it_interval; - struct meow_time32 it_value; -}; -/* 与 libc 结构解耦的 64 位镜像(避免宿主/交叉两端 time_t 宽度差异进测试)。 */ -struct meow_time64_spec { - int64_t tv_sec; - int64_t tv_nsec; -}; -struct meow_time64_itimerspec { - struct meow_time64_spec it_interval; - struct meow_time64_spec it_value; -}; - -/* 相对超时(futex / poll / select 的「等多久」):sec 超出 int32 时饱和 - * (正→INT32_MAX、负→INT32_MIN,与 musl CLAMP 一致),nsec 原样。 */ -void meow_time32_rel_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out); -/* 绝对时刻(clock_settime / timerfd / utimensat):sec 落不进 int32 返回 -1 - * (调用方回 ENOTSUP,与 musl time64 路径的判定一致),nsec 原样。 */ -int meow_time32_abs_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out); -/* itimerspec(timerfd / POSIX timer)转换;任一侧越界返回 -1 且 *out 不变。 */ -int meow_time32_itimerspec_from_64(const struct meow_time64_itimerspec *in, - struct meow_itimerspec32 *out); -void meow_time64_itimerspec_from_32(const struct meow_itimerspec32 *in, - struct meow_time64_itimerspec *out); +/* ---- 时钟(meow_ui_time.c + meow_time32.c)---- + * 换算/内核 time32 取时/睡眠的唯一实现在 meow_time32.c(独立 TU,系统组件可单独链接); + * 本头只把它带进来,SDK 内部仍按原名字使用。 */ +#include "meow_time32.h" #endif /* MEOW_UI_INTERNAL_H */ diff --git a/patch_v2/sdk/meow_ui_repaint.h b/patch_v2/sdk/meow_ui_repaint.h new file mode 100644 index 0000000..d529181 --- /dev/null +++ b/patch_v2/sdk/meow_ui_repaint.h @@ -0,0 +1,39 @@ +/* meow_ui_repaint.h — 解锁后「强制 App 整帧重画」请求文件(M40 F5/P5-P6)。 + * + * 背景:锁屏/电源菜单是整帧不透明覆盖。关闭后 launcher 会恢复它快照的底层帧,但 App + * 进程只按自己的脏矩形 present;锁屏期间它可能被 SIGSTOP 冻结,从未观测到 + * /tmp/meow_notif_active 的抑制态,因此 SDK 的「抑制→恢复」整帧重画边沿不会触发 —— + * PIN 帧/菜单帧会残留在屏幕上,直到用户点到某个控件产生新脏区才刷新(真机 P6)。 + * + * 机制:launcher 在 SIGCONT 前写 /tmp/meow_app_repaint;App 的 SDK present 看到文件就 + * dirty_full() 一次并删除(消费一次即失效)。请求在 present 最前面检查,**抑制期也消费** + * (先置 dirty_full,等标记翻回前台后的下一帧整屏上屏),避免排空窗口把请求吃掉后不生效。 + * + * 实现放在头文件里(static inline):present 层(meow_ui_gfx.c)直接调用;宿主单测只 + * include 本头即可验证写/读/删语义,不必链接整个渲染栈。路径可用 MEOW_APP_REPAINT_FILE + * 覆盖(宿主单测隔离;真机无 env 走默认)。 + */ +#ifndef MEOW_UI_REPAINT_H +#define MEOW_UI_REPAINT_H + +#include +#include + +#define MEOW_APP_REPAINT_DEFAULT "/tmp/meow_app_repaint" + +static inline const char *meow_ui_repaint_path(void) +{ + const char *e = getenv("MEOW_APP_REPAINT_FILE"); + return (e && e[0]) ? e : MEOW_APP_REPAINT_DEFAULT; +} + +/* 返回 1=消费了一次重画请求(调用方应 dirty_full);0=无请求。 */ +static inline int meow_ui_repaint_requested(void) +{ + const char *p = meow_ui_repaint_path(); + if (access(p, F_OK) != 0) return 0; + unlink(p); /* 一次性请求:读到即消费,防每帧整帧重画 */ + return 1; +} + +#endif /* MEOW_UI_REPAINT_H */ diff --git a/patch_v2/sdk/meow_ui_time.c b/patch_v2/sdk/meow_ui_time.c index 1b1139a..98a5097 100644 --- a/patch_v2/sdk/meow_ui_time.c +++ b/patch_v2/sdk/meow_ui_time.c @@ -43,6 +43,7 @@ #include "meow_ui.h" #include "meow_ui_internal.h" +#include "meow_time32.h" #if defined(__mips__) && !defined(__mips64) && (__SIZEOF_LONG__ == 4) #define MEOW_TIME32_KERNEL_ABI 1 @@ -54,64 +55,11 @@ #define MEOW_TIME32_KERNEL_ABI 0 #endif -/* ---- 平台无关的 time64 → 内核 o32 time32 换算(宿主单测直接覆盖)---- - * 这几个函数不碰系统调用,只为“结构宽度”这件事提供唯一实现;mips 分支的接管入口 - * 全部经它们转换,宿主 test_meow_time64.c 用同一份代码验边界。 */ -void meow_time32_rel_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out) -{ - if (sec > INT32_MAX) sec = INT32_MAX; - else if (sec < INT32_MIN) sec = INT32_MIN; - out->tv_sec = (int32_t)sec; - out->tv_nsec = (int32_t)nsec; -} - -int meow_time32_abs_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out) -{ - if (sec > INT32_MAX || sec < INT32_MIN) return -1; - out->tv_sec = (int32_t)sec; - out->tv_nsec = (int32_t)nsec; - return 0; -} - -int meow_time32_itimerspec_from_64(const struct meow_time64_itimerspec *in, - struct meow_itimerspec32 *out) -{ - struct meow_itimerspec32 t; - if (meow_time32_abs_from_parts(in->it_interval.tv_sec, in->it_interval.tv_nsec, - &t.it_interval)) return -1; - if (meow_time32_abs_from_parts(in->it_value.tv_sec, in->it_value.tv_nsec, - &t.it_value)) return -1; - *out = t; - return 0; -} - -void meow_time64_itimerspec_from_32(const struct meow_itimerspec32 *in, - struct meow_time64_itimerspec *out) -{ - out->it_interval.tv_sec = in->it_interval.tv_sec; - out->it_interval.tv_nsec = in->it_interval.tv_nsec; - out->it_value.tv_sec = in->it_value.tv_sec; - out->it_value.tv_nsec = in->it_value.tv_nsec; -} - -uint64_t meow_time_ms_from_parts(int64_t sec, int64_t nsec) -{ - return (uint64_t)sec * 1000u + (uint64_t)nsec / 1000000u; -} - +/* 换算/取时的唯一实现已抽到 meow_time32.c(不依赖 SDK,系统组件可单独链接)。 + * 本 TU 只保留「libc 入口接管」与 futex/timerfd/clock_settime 的 time64 修复。 */ uint64_t meow_ui_time_ms(void) { -#if MEOW_TIME32_KERNEL_ABI - struct meow_time32 t; - t.tv_sec = 0; - t.tv_nsec = 0; - if (syscall(SYS_clock_gettime32, CLOCK_MONOTONIC, &t) != 0) return 0; - return meow_time_ms_from_parts(t.tv_sec, t.tv_nsec); -#else - struct timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) return 0; - return meow_time_ms_from_parts((int64_t)ts.tv_sec, (int64_t)ts.tv_nsec); -#endif + return meow_time32_monotonic_ms(); } #if MEOW_TIME32_KERNEL_ABI @@ -134,9 +82,7 @@ int __clock_gettime(clockid_t clk, struct timespec *ts) { struct meow_time32 t; if (!ts) { errno = EFAULT; return -1; } - t.tv_sec = 0; - t.tv_nsec = 0; - if (syscall(SYS_clock_gettime32, clk, &t) != 0) return -1; + if (meow_time32_clock_gettime(clk, &t) != 0) return -1; ts->tv_sec = t.tv_sec; ts->tv_nsec = t.tv_nsec; return 0; diff --git a/patch_v2/sdk/test_meow_repaint.c b/patch_v2/sdk/test_meow_repaint.c new file mode 100644 index 0000000..00fd784 --- /dev/null +++ b/patch_v2/sdk/test_meow_repaint.c @@ -0,0 +1,67 @@ +/* test_meow_repaint.c — M40 F5:解锁后「强制 App 整帧重画」请求文件的写/读/删除语义。 + * + * 编译(patch_v2/sdk 下): + * gcc -Wall -Wextra -I. test_meow_repaint.c -o test_meow_repaint + * 运行:./test_meow_repaint (exit 0=PASS) + * + * 语义:launcher 写文件 → SDK present 调 meow_ui_repaint_requested() 读到即删并返回 1 + * (dirty_full 一次);文件不存在返回 0。路径可用 MEOW_APP_REPAINT_FILE 覆盖(测试隔离)。 + */ +#include +#include +#include +#include +#include + +#include "meow_ui_repaint.h" + +static int g_fail = 0; + +static void check(const char *name, int ok) +{ + if (ok) { + printf("ok %s\n", name); + } else { + printf("FAIL %s\n", name); + g_fail = 1; + } +} + +static int write_file(const char *path) +{ + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) return -1; + if (write(fd, "1", 1) != 1) { close(fd); return -1; } + close(fd); + return 0; +} + +int main(void) +{ + const char *tmp = getenv("TMPDIR"); + char path[512]; + snprintf(path, sizeof path, "%s/m40_repaint_XXXXXX", (tmp && tmp[0]) ? tmp : "/tmp"); + int fd = mkstemp(path); + if (fd < 0) { perror("mkstemp"); return 2; } + close(fd); + unlink(path); /* 先清空,测试从「无请求」开始 */ + + setenv("MEOW_APP_REPAINT_FILE", path, 1); + check("path follows env", !strcmp(meow_ui_repaint_path(), path)); + + check("no request file -> 0", meow_ui_repaint_requested() == 0); + + check("write request", write_file(path) == 0); + check("request present", access(path, F_OK) == 0); + check("first check consumes -> 1", meow_ui_repaint_requested() == 1); + check("file deleted after consume", access(path, F_OK) != 0); + check("second check -> 0 (one-shot)", meow_ui_repaint_requested() == 0); + + /* 再写一次仍能消费(launcher 每次解锁/菜单关闭都会写) */ + check("write again", write_file(path) == 0); + check("consume again -> 1", meow_ui_repaint_requested() == 1); + + unlink(path); + printf("\n%s\n", g_fail ? "SOME TESTS FAILED" : "ALL PASS"); + return g_fail ? 1 : 0; +} diff --git a/patch_v2/sdk/test_meow_time.c b/patch_v2/sdk/test_meow_time.c index 8e1b0c5..f476ef8 100644 --- a/patch_v2/sdk/test_meow_time.c +++ b/patch_v2/sdk/test_meow_time.c @@ -7,7 +7,9 @@ * * 编译(patch_v2/sdk 目录下): * gcc -Os -Wall -Wextra -I. -Iinclude -c meow_ui_time.c -o /tmp/meow_ui_time.o - * gcc -Os -Wall -Wextra -I. -Iinclude test_meow_time.c /tmp/meow_ui_time.o -o test_meow_time + * gcc -Os -Wall -Wextra -I. -Iinclude -c meow_time32.c -o /tmp/meow_time32.o + * gcc -Os -Wall -Wextra -I. -Iinclude test_meow_time.c /tmp/meow_ui_time.o /tmp/meow_time32.o \ + * -o test_meow_time * 运行:./test_meow_time (exit 0=PASS / 1=FAIL) */ #include diff --git a/patch_v2/sdk/test_meow_time64.c b/patch_v2/sdk/test_meow_time64.c index 151de83..3effbf7 100644 --- a/patch_v2/sdk/test_meow_time64.c +++ b/patch_v2/sdk/test_meow_time64.c @@ -7,7 +7,8 @@ * * 编译(patch_v2/sdk 目录下): * gcc -Os -Wall -Wextra -I. -Iinclude -c meow_ui_time.c -o /tmp/meow_ui_time.o - * gcc -Os -Wall -Wextra -I. -Iinclude test_meow_time64.c /tmp/meow_ui_time.o \ + * gcc -Os -Wall -Wextra -I. -Iinclude -c meow_time32.c -o /tmp/meow_time32.o + * gcc -Os -Wall -Wextra -I. -Iinclude test_meow_time64.c /tmp/meow_ui_time.o /tmp/meow_time32.o \ * -o test_meow_time64 * 运行:./test_meow_time64 (exit 0=PASS / 1=FAIL) */ diff --git a/scripts/build/fw_content.conf b/scripts/build/fw_content.conf index 31173de..c3c6345 100644 --- a/scripts/build/fw_content.conf +++ b/scripts/build/fw_content.conf @@ -28,8 +28,8 @@ # ---- 用户态/服务守护二进制(usr/bin,全部 chmod 755 静态 MIPS)---- patch_v2/bootmenu/meow_bootmenu|/usr/bin/meow_bootmenu|4441822d87e76bfce2ce584604ac3830|引导选单 M6.25(开机菜单/第二屏亮度按用户设置恢复:显示前读 settings.conf 写回,方案A) -patch_v2/meow_launcherd/meow_launcherd|/usr/bin/meow_launcherd|e342d24fa9fd878c840e5b3e918cfbdf|M39 点击输入门(launcher 触摸守卫 1200ms→drain+MENU_TOUCH_GUARD_MS 150ms(App退出)/800ms×6(子页返回)/300ms×8(菜单/子页打开)统一收窄,drain 同时复位 t_down/原点防幽灵抬手重放;SDK meow_ui_run 500ms 全量吞窗→启动 meow_input_drain 一次+TAP/TOUCH_DOWN/MOVE/LONG_PRESS 不再按时间吞,仅系统手势(SWIPE/HOME/NOTIF_PULL/SWIPE_PROGRESS)保留 150ms 防穿透), 链 meow_ui.a 8525aa6b;继承 M6.46 编入杀后台修复(M7:bgmgr_can_kill 门控 framework→protected_app+后台页 sub 指针持久化(悬垂修正)+am kill 走 meow_bg_terminate 与页面按钮同通道)+SDK 时钟 ABI 修复(M11:meow_ui_time 按 o32 time32 取时并链接期接管, 旧链 meow_ui.a 4c4a1554);继承M6.41 收编启动失败原因透传(subpage_t/meow_launcher_internal启动失败原因)+后台管理按钮点不动修复(bg_page)+主菜单字体/样式微调;继承M6.40(系统级音量翻页映射+P1 MD3 select面板28/对话框按钮胶囊)+M6.39(SDK LazyGrid/LazyColumn/LazyRow MD3容器化+SDK全局App内底部上滑回桌面)+M6.38(批②上滑回桌面+SDK对话框MD3细化+UI微调+空artwork)+M6.37(SDK默认列表MD3容器化)+M6.36(批①叠加/穿透根治+MD3全面对齐+后台图标/圆角卡+批①后台常驻框架+批③后台管理页+i18n);链meow_ui.a(sdk meow_lyric/lyric_view/meow_img_color,meow_img_pixels访问器);0warning -patch_v2/meow_watchdog/meow_watchdog|/usr/bin/meow_watchdog|ad8c1f2bfe5481d2069466e22d5e1cf6|应用看门狗(亮屏亮度按 settings 恢复,四支决策不再回退固定 60;M6.23 源码修复首次真正编入固件) +patch_v2/meow_launcherd/meow_launcherd|/usr/bin/meow_launcherd|605e13a9d154d179f4131e0c5e85c90f|M40 电源键/锁屏链修复(App 前台电源菜单补齐 §10.5:notif 全抑制+帧恢复+SIGCONT 排空+标记翻回,关闭后不再停留菜单画面;show_power_menu 恢复进入前背景帧+重绘挪到首轮按键采样后;activity 标记排空窗+会话入口清理+manual_off 去单向闩锁;锁屏电源改按下边沿+进场 300ms,熄屏退出也恢复底层帧;SIGCONT 前写 /tmp/meow_app_repaint), 链 meow_ui.a 86800633;继承 M39 点击输入门(M39 触摸守卫/MENU_TOUCH_GUARD_MS 150ms + SDK 启动排空);继承 M6.46 杀后台修复+SDK 时钟 ABI 修复(M11/M40);0warning +patch_v2/meow_watchdog/meow_watchdog|/usr/bin/meow_watchdog|37c57e0feb9cd220978bfd5deff75a1f|应用看门狗 M40:o32 time32 取时/睡眠(修 held 天文数字→单击误判长按)、电源阈值读 power.longpress_menu_ms/forceoff_ms(缺省 3000/8000,与 launcher 主屏统一);继承 M20 亮屏亮度按 settings 恢复(四支决策不再回退固定 60) patch_v2/meow_svcd/meow_svcd|/usr/bin/meow_svcd|605646c4cc93f2baf29071ea6bc870ea|服务管理器 M2.77(+崩溃上报 meow_crash agent-187) mips-repo/bins/meow_mdns/bin/meow_mdns|/usr/bin/meow_mdns|d6b0151bda26768f19952dae1342610d|mDNS 应答器 M2.78(启动竞态+换IP重检 agent-194) patch_v2/meow_sysui/meow_sysui|/usr/bin/meow_sysui|173013cc8f0899b3001d0b894ac0d9e6|系统 UI(sysui)