fix(watchdog+launcher+sdk): M40 电源键 time64 ABI + 锁屏/叠加残留链

- sdk/meow_time32: 抽出可独立链接的 o32 time32 取时/睡眠设施(不依赖 meow_ui.a,
  watchdog 等独立静态组件可直接链;可选 -DMEOW_TIME32_LIBC_OVERRIDES 接管 libc 入口)
- watchdog: now_ms/usleep 改走 time32(修复 held 变天文数字→每次单击都弹菜单);
  阈值读 power.longpress_menu_ms/forceoff_ms(缺省 3000/8000),删除
  PWR_TOGGLE_MS==PWR_MENU_MS;新增 wd_power.h 纯判定 + 宿主行为矩阵测试
- launcher: App 前台电源菜单补齐 §10.5(notif 全抑制 + 帧恢复 + SIGCONT 排空 +
  标记翻回前台);show_power_menu 恢复进入前背景帧(关闭后不再停留菜单画面);
  重绘挪到首轮按键采样之后;activity 标记排空窗 + 会话入口清理 + manual_off 去闩锁
- lock_screen: 电源改按下边沿 + 进场 300ms 才熄屏;电源熄屏退出也恢复底层帧;
  launcher 在 SIGCONT 前写 /tmp/meow_app_repaint,SDK present 消费并 dirty_full
- power_menu: 菜单内强关接 power.forceoff_ms(按 longpress 抵扣),日志不再谎称 8s
- 重编 meow_ui.a 并重链 meow_launcherd/ncm/files/hello/reader(picbrowser 无源码)
- fw_content.conf: 同步 launcherd/watchdog 新 md5;顺带修正 M35 遗留的 nyai 失效钉值

Host: test_wd_fw 66/66、test_overlay_gate 40/40、test_meow_time64/time/repaint PASS。
真机未验证(设备关机);未刷机、未写设备。
This commit is contained in:
AskaEth committed 2026-11-10 22:51:00 +08:00
1 parent b1acc9096b
commit 63b278e13e
28 files changed
+977 -217

No files matched your search

+131 -20
View File
@@ -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 全屏收合接入)。