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 全屏收合接入)。
+23 -13
View File
@@ -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) {
@@ -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; };
Binary file not shown.
+22
View File
@@ -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 */
+92 -44
View File
@@ -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;
}
@@ -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");