fix(launcher,lock): §310 锁屏/叠加三连 bug——输入穿透·状态栏消失·App死亡残影

用户真机 M6.2 实测三症状,全部位于锁屏/通知中心叠加路径。根因与修法:

S1 锁屏输入穿透(PIN 触摸打到下层 App / 解锁后「启动奇怪界面」)
  根因:run_app_lock_overlay / run_app_notif_overlay 关闭路径先 unlink
  /tmp/meow_notif_active 再 SIGCONT App——App 被 SIGSTOP 冻结期间缓冲的 evdev
  触摸事件在解除瞬间被 SDK 输入线程在无隔离态回放成 App 内点击(穿透触发)。
  修法:反序——标记仍在时先 SIGCONT,让 SDK 输入线程排空冻结期缓冲事件 +
  EVIOCGKEY 对齐,等 OVERLAY_DRAIN_WINDOW_MS(300ms > SDK 200ms 节流) 再 unlink。

S2 解锁后状态栏消失(App 无状态栏全屏残留)
  根因:S1 的回放点击常把 App 导航进全屏页(状态栏随 app.json/fullscreen 隐掉);
  叠加关闭路径也未强制状态栏重同步。
  修法:解锁后 g_sb_mode_last=-1 + 立即 app_overlay_statusbar() 重叠加(不等 1s 慢刷)。

S3 App 异常死亡(OOM sig=9)残影——fb0 停 App 死帧、无状态栏、launcher 不重绘主屏
  根因:锁屏叠加(run_app_lock_overlay→run_lock_screen)阻塞于 lock_screen 内,
  App 叠加期崩溃不被感知(notif 叠加有 run_notif_center 的 proc_is_zombie 探测,
  锁屏独缺),launcher 卡在锁屏会话永不回 menu 重绘。
  修法:run_app_lock_overlay 置位 g_lock_overlay_pid,run_lock_screen 每圈
  proc_is_zombie 探测,App 死即中止锁屏(LCK_OVERLAY_RET_DIED)走统一退出路径
  (reap_wait 回收 + ams_status_valid + break → run_menu 重绘主屏含状态栏)。

新增 overlay_gate.h(纯决策,宿主可单测)+ test_overlay_gate.c(10 断言)+
test_lock_screen.c 扩展(4 断言 overlay-abort)。launcher 全量宿主回归 +
SDK 叠加/状态栏回归(overlay_suppress/sb_collapse/notif_open/notif_gesture/
sdk_fixes/sdk_input_keys)全 PASS;mipsel 交叉 -Wall -Wextra 0 warning。
md5 136dc67c086a9d75c0ad9c83d519ff0d(/usr/bin 只读,待固件打包;fw_content.conf 未动)。
This commit is contained in:
AskaEth committed 2026-09-02 03:41:39 +08:00
1 parent 3611cbbbde
commit bcef8e092e
8 files changed
+203 -11

No files matched your search

+58 -10
View File
@@ -2,6 +2,7 @@
/* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/
#include "meow_launcher_internal.h"
#include "meow_ams_start.h" /* MSG_AMS_START pending/拉起决策(meow_ams_start.c) */
#include "overlay_gate.h" /* SIGSTOP 叠加会话关闭顺序/死亡回退纯决策(§310 三连 bug 收敛点) */
static struct meow_acct g_accts[MEOW_MAX_ACTS]; /* 主循环单写者 */
@@ -1420,14 +1421,19 @@ static int run_app_notif_overlay(struct app *a, pid_t pid)
* 避免「标记先消失 → App 抢在恢复帧前 present 新帧 → 又被旧帧盖回」的一帧回闪。 */
pm_capture_app_frame();
meow_ui_flush();
unlink(NOTIF_ACTIVE);
unlink(POWMENU_ACTIVE);
if (!keep_running) {
/* §310 输入穿透定案(与 lock-overlay 同源):App 被 SIGSTOP 冻结过 → 在标记仍存在时
* 先 SIGCONT,让 SDK 输入线程排空冻结期缓冲的 evdev 事件 + 对齐 EVIOCGKEY,再等
* OVERLAY_DRAIN_WINDOW_MS 后 unlink——防关面板瞬间把积累触摸回放成 App 内点击。 */
kill(pid, SIGCONT);
logmsg("notif-overlay: closed -> SIGCONT app");
usleep(OVERLAY_DRAIN_WINDOW_MS * 1000);
logmsg("notif-overlay: closed -> SIGCONT app, drain %dms, then unlink mark",
OVERLAY_DRAIN_WINDOW_MS);
} else {
logmsg("notif-overlay: closed -> app never stopped (media keep-alive), no SIGCONT");
}
unlink(NOTIF_ACTIVE);
unlink(POWMENU_ACTIVE);
return 0;
}
@@ -1463,18 +1469,43 @@ static int run_app_lock_overlay(struct app *a, pid_t pid)
meow_theme_reload();
pm_capture_app_frame();
/* 置位叠加期 App pid:run_lock_screen 每圈探测该 App 是否在锁屏期崩溃(§310 残影),
* 崩溃即中止锁屏返回 LCK_OVERLAY_RET_DIED,走下方统一退出路径。 */
g_lock_overlay_pid = (int)pid;
int lr = run_lock_screen();
g_lock_overlay_pid = 0;
if (lr == LCK_OVERLAY_RET_DIED) {
/* 叠加期 App 已死:不 SIGCONT(死进程)、不恢复死帧;unlink 标记交回 waitpid 循环回收,
* run_menu 统一重绘主屏(含状态栏)。launcher 不崩。 */
unlink(NOTIF_ACTIVE);
unlink(POWMENU_ACTIVE);
logmsg("lock-overlay: app pid %d died during lock -> unified exit path", pid);
return 1;
}
pm_capture_app_frame();
meow_ui_flush();
unlink(NOTIF_ACTIVE);
unlink(POWMENU_ACTIVE);
if (!keep_running) {
/* §310 输入穿透定案(overlay_gate.h):App 被 SIGSTOP 冻结过 → 在 /tmp/meow_notif_active
* 仍未 unlink 时**先 SIGCONT**,让 SDK 输入线程排空冻结期缓冲的 evdev 事件副本 + 对齐
* EVIOCGKEY,再等 OVERLAY_DRAIN_WINDOW_MS(> SDK 200ms 节流窗)后 unlink——防解锁瞬间
* 把积累的触摸回放成 App 内点击(锁屏穿透→「启动奇怪界面」→ 状态栏消失的触发源)。 */
kill(pid, SIGCONT);
logmsg("lock-overlay: closed (lr=%d) -> SIGCONT app", lr);
usleep(OVERLAY_DRAIN_WINDOW_MS * 1000);
logmsg("lock-overlay: closed (lr=%d) -> SIGCONT app, drain %dms, then unlink mark",
lr, OVERLAY_DRAIN_WINDOW_MS);
} else {
logmsg("lock-overlay: closed (lr=%d) -> app never stopped (media keep-alive), no SIGCONT", lr);
}
unlink(NOTIF_ACTIVE);
unlink(POWMENU_ACTIVE);
/* §310 状态栏恢复:解锁后强制 g_sb_mode_last=-1 + 立即重叠加顶条(不等 app 循环 1s 慢刷),
* 消除「解锁回 App 无状态栏」的残留态。overlay_force_sb_resync() = 定案恒真。 */
if (overlay_force_sb_resync()) {
g_sb_mode_last = -1;
if (!a->fullscreen) app_overlay_statusbar();
}
return 0;
}
@@ -1956,8 +1987,17 @@ static int launch_app_chain(struct app *a, const char *chain)
meow_panel_on();
meow_auto_slept = 0;
fb_pan_buf0_force();
/* M2.x 锁屏:App 前台唤醒后若需锁屏 → SIGSTOP 叠加锁屏页;解锁/SIGCONT 回应用 */
if (lck_wake_should_lock()) run_app_lock_overlay(a, pid);
/* M2.x 锁屏:App 前台唤醒后若需锁屏 → SIGSTOP 叠加锁屏页;解锁/SIGCONT 回应用。
* §310:叠加期 App 崩溃(LR==LCK_OVERLAY_RET_DIED)→ 回收僵尸走统一退出路径
* (launcher 不崩、run_menu 重绘主屏),同 run_app_notif_overlay 的 died 分支。 */
if (lck_wake_should_lock()) {
int lr2 = run_app_lock_overlay(a, pid);
if (lr2 != 0) {
reap_wait(pid, &status);
ams_status_valid = 1;
break;
}
}
}
}
if (screen_off_timeout > 0 && meow_screen_on && !meow_auto_slept && !fg_keep_awake()) {
@@ -1980,8 +2020,16 @@ static int launch_app_chain(struct app *a, const char *chain)
meow_panel_on();
meow_auto_slept = 0;
fb_pan_buf0_force();
/* M2.x 锁屏:App 前台唤醒后若需锁屏 → SIGSTOP 叠加锁屏页;解锁/SIGCONT 回应用 */
if (lck_wake_should_lock()) run_app_lock_overlay(a, pid);
/* M2.x 锁屏:App 前台唤醒后若需锁屏 → SIGSTOP 叠加锁屏页;解锁/SIGCONT 回应用。
* §310:叠加期 App 崩溃 → 回收僵尸走统一退出路径(launcher 不崩、run_menu 重绘主屏)。 */
if (lck_wake_should_lock()) {
int lr2 = run_app_lock_overlay(a, pid);
if (lr2 != 0) {
reap_wait(pid, &status);
ams_status_valid = 1;
break;
}
}
}
}
if (vold && !app_prev_vold) {
+14
View File
@@ -88,6 +88,20 @@ void lck_manual_lock_clear(void); /* 删标志(幂等) */
* 取代 lck_lock_required(1) 作为唤醒门——主动锁强制锁屏,覆盖「关闭唤醒锁」的手动锁场景。 */
int lck_wake_should_lock(void);
/* ── 锁屏叠加期 App 存活探针(§310 残影修复,纯判定,宿主可单测;header-only inline)----
* 锁屏运行在 App 前台叠加会话中(overlay_pid>0,run_app_lock_overlay 置位)且该进程已成僵尸
* (调用方 proc_is_zombie 判定后传入 proc_zombie=1)→ 应中止锁屏走统一退出(返回 1)。
* 菜单直接锁屏(overlay_pid=0,无 App 可死)恒 0;此谓词不实际读 /proc。 */
static inline int lck_overlay_should_abort(int overlay_pid, int proc_zombie)
{
return (overlay_pid > 0 && proc_zombie) ? 1 : 0;
}
/* run_lock_screen() 返回值:1=已解锁,0=锁屏期间电源键息屏。叠加期 App 崩溃时返回
* LCK_OVERLAY_RET_DIED(-1)——调用方 run_app_lock_overlay 据此外走「统一退出路径」
* (不再 SIGCONT 死进程/不恢复死帧,交回 waitpid 循环回收,run_menu 重绘主屏)。 */
#define LCK_OVERLAY_RET_DIED (-1)
#ifdef __cplusplus
}
#endif
+23
View File
@@ -12,6 +12,12 @@
*/
#include "meow_launcher_internal.h"
/* §310 残影:锁屏处于 App 前台叠加会话时置位当前前台 App pid(run_app_lock_overlay 写入)。
* run_lock_screen 每圈据此探测 App 是否已死(组合 App 冻结缓冲 evdev 会在解锁瞬间回放成点击 /
* 叠加期 OOM 杀让 fb0 留残帧——探到死即中止锁屏,交回 waitpid 循环回收 + run_menu 重绘主屏)。
* 菜单直接锁屏(无 App)时保持 0 = 不探测。 */
int g_lock_overlay_pid = 0;
/* ---- 键盘几何(480×720):3 列×4 行圆角按钮 ---- */
#define LK_X0 32 /* 首列左缘 */
#define LK_COLW 130 /* 列宽 */
@@ -156,6 +162,17 @@ int run_lock_screen(void)
goto done;
}
/* §310 残影:锁屏叠加期 App 崩溃(僵尸)→ 中止锁屏走统一退出路径(交回 waitpid 循环
* 回收 + run_menu 重绘主屏含状态栏)。仅当 run_app_lock_overlay 置位 g_lock_overlay_pid
* 才探(菜单直接锁屏无 App 可死,恒不中止)。 */
if (g_lock_overlay_pid > 0 &&
lck_overlay_should_abort(g_lock_overlay_pid, proc_is_zombie(g_lock_overlay_pid))) {
logmsg("lock: overlay app pid %d died during lock -> abort (unified exit path)",
g_lock_overlay_pid);
g_lock_overlay_pid = 0;
goto died;
}
/* 锁定到期自动解禁(清计数+戳),仅重画状态行 */
if (lock_until > 0 && now >= lock_until) {
lck_lock_remaining();
@@ -260,6 +277,12 @@ done:
kp_close(&kp_keys);
logmsg("lock: exit unlocked=%d screen_on=%d", unlocked, meow_screen_on);
return unlocked;
died:
if (tfd >= 0) close(tfd);
kp_close(&kp_power);
kp_close(&kp_keys);
logmsg("lock: exit overlay-app-died -> LCK_OVERLAY_RET_DIED");
return LCK_OVERLAY_RET_DIED;
}
/* 电源菜单「锁定」:**主动锁屏**(用户定案 2026-09-02 强化)。
@@ -827,10 +827,14 @@ void settings_sync_cache_listener(const char *key);
void settings_volume_adj(int delta);
int settings_volume_get(void);
int show_power_menu(void);
/* M2.x 锁屏:阻塞式锁屏页(渲染+输入);返回 1=已解锁(回主屏),0=锁屏期间电源键息屏(屏已关)。
/* M2.x 锁屏:阻塞式锁屏页(渲染+输入);返回 1=已解锁(回主屏),0=锁屏期间电源键息屏(屏已关),
* -1(LCK_OVERLAY_RET_DIED)=锁屏叠加期 App 崩溃(run_app_lock_overlay 走统一退出路径)。
* meow_lock_screen_engage() 供电源菜单「锁定」与唤醒路径用;未配凭据时 no-op。 */
int run_lock_screen(void);
int meow_lock_screen_engage(void);
/* §310 残影:锁屏处于 App 前台叠加会话时置位当前 App pid(run_app_lock_overlay 写入,
* run_lock_screen 每圈据此 proc_is_zombie 探测;菜单直接锁屏时保持 0=不探)。 */
extern int g_lock_overlay_pid;
/* 设置页「锁屏 PIN」数字输入模态(系统级 PIN 设置/修改输入):满 4 位返回 1 并拷贝到 out,电源=取消返回 0。 */
int lck_input_pin_modal(const char *title, char *out, int outcap);
/* 设置页「锁屏 PIN」独立子页面(subpage.c 脚手架;阻塞到返回)。 */
Binary file not shown.
+45
View File
@@ -0,0 +1,45 @@
/* overlay_gate.h — SIGSTOP 叠加会话(通知中心 §10.5 / 锁屏 M2.x)关闭与死亡回退的**纯决策**。
*
* 宿主可单测(无信号/无设备/无文件系统依赖,全部 static inline);app_launcher.c 的两处叠加
* 关闭路径(run_app_notif_overlay / run_app_lock_overlay)调用,避免把「排空窗口」「中止条件」
* 这类时序/判定散落内联难验证。三个 bug 的定案语义都收敛到这里:
*
* ① 输入穿透(S1 根因):App 被 SIGSTOP 冻结过 → 关闭时必须在 /tmp/meow_notif_active 仍存在
* 时先 SIGCONT,让 SDK 输入线程(meow_ui_input.c overlay_input_suppressed)排空冻结期缓冲的
* evdev 事件副本 + 对齐 EVIOCGKEY,再等 OVERLAY_DRAIN_WINDOW_MS 后 unlink 标记。
* 若反序(unlink->SIGCONT,改动前写法),App 在无隔离态恢复 → 解冻瞬间把积累的触摸回放成
* App 内点击(锁屏穿透 /「启动奇怪界面」/ 解锁后状态栏消失的触发源)。
* ② 状态栏恢复(S2):解锁后强制 g_sb_mode_last=-1 让 app 循环立即重叠加,不等 1s 慢刷。
* ③ 死亡重绘(S3):锁屏叠加中 App 崩溃(/proc/<pid>/stat==Z)→ 中止锁屏走统一退出路径,
* 由 waitpid 循环回收 + run_menu dirty=1 重绘主屏(含状态栏)。
*/
#ifndef MEOW_OVERLAY_GATE_H
#define MEOW_OVERLAY_GATE_H
/* SDK overlay_input_suppressed() 的标记存在性节流窗是 200ms;排空窗须 > 该值(留 100ms 余量),
* 保证 App 恢复后 SDK 输入线程确实重新 stat 到标记再整批丢弃缓冲事件。 */
#define OVERLAY_DRAIN_WINDOW_MS 300
/* ① 排空是否必要:App 被 SIGSTOP 冻结过(非保活)才需要「SIGCONT 保持隔离 → 排窗 → unlink」;
* 媒体保活(keep_running,从未 STOP)直接「恢复帧上屏 → unlink」(自抑随标记消失自然解除)。 */
static inline int overlay_drain_required(int keep_running)
{
return !keep_running ? 1 : 0;
}
/* ③ 锁屏叠加中 App 崩溃是否触发「统一退出路径」(= 中止锁屏、交回 waitpid 循环回收、run_menu 重绘)。
* proc_zombie=1(/proc/<pid>/stat==Z)→ 走退出(返回 1);否则正常恢复(返回 0)。 */
static inline int lock_overlay_app_died_exit(int proc_zombie)
{
return proc_zombie ? 1 : 0;
}
/* ② 解锁后是否需要强制状态栏重同步(返回 1):始终需要——把 g_sb_mode_last 拨回 -1,
* 使 run_menu / app 循环的「模式变化→立即重叠加」分支命中(-1 != m 恒真除非 m==-1,
* 但 m==-1 表示无模式文件=展开态,同样被 app_overlay_statusbar 兜底到展开整条)。 */
static inline int overlay_force_sb_resync(void)
{
return 1;
}
#endif /* MEOW_OVERLAY_GATE_H */
@@ -75,6 +75,14 @@ int main(void)
meow_settings_save();
CHECK(lck_wake_should_lock() == 1, "wake_should_lock=1 (wake=1)");
/* ---- §310 残影:锁屏叠加期 App 崩溃中止探针(纯判定)----
* 锁屏在 App 前台叠加会话(overlay_pid>0)且该 App 已成僵尸 → 中止锁屏(返回 1);
* 菜单直接锁屏(overlay_pid=0)恒不中止;叠加期 App 仍活(proc_zombie=0)也不中止。 */
CHECK(lck_overlay_should_abort(0, 0) == 0, "overlay abort=0 (no overlay pid, not zombie)");
CHECK(lck_overlay_should_abort(1234, 0) == 0, "overlay abort=0 (overlay pid, app alive)");
CHECK(lck_overlay_should_abort(0, 1) == 0, "overlay abort=0 (no overlay pid, zombie ignored)");
CHECK(lck_overlay_should_abort(1234, 1) == 1, "overlay abort=1 (overlay pid + zombie -> unified exit)");
/* ---- 面板一次性解锁恢复口(bypass 标志)---- */
CHECK(lck_unlock_once_consume() == 0, "unlock_once_consume=0 when flag absent");
{
@@ -0,0 +1,50 @@
/* test_overlay_gate.c — §310 三连 bug 的**纯决策**宿主单测(overlay_gate.h + lock_screen.h 谓词)。
*
* 覆盖三个症状对应的关闭/恢复决策,不依赖信号/设备/文件系统:
* S1 锁屏期触摸不穿透 → overlay_drain_required():App 被 SIGSTOP 过(非保活)必须在
* unlink /tmp/meow_notif_active 前先 SIGCONT + 排窗,让 SDK 输入线程
* 排空冻结期缓冲的 evdev 事件副本;OVERLAY_DRAIN_WINDOW_MS > SDK 200ms 节流。
* S2 解锁状态栏恢复 → overlay_force_sb_resync():解锁后必须强制 g_sb_mode_last=-1 重叠加,
* 不等 app 循环 1s 慢刷(消除「解锁回 App 无状态栏」残留)。
* S3 App 死亡触发主屏重绘 → lock_overlay_app_died_exit():锁屏叠加期 App 崩溃(僵尸)必须走
* 统一退出路径(交回 waitpid 循环回收 + run_menu 重绘主屏含状态栏)。
*
* 编译运行(在 patch_v2/meow_launcherd 下,无其它依赖):
* gcc -Wall -Wextra -o test_overlay_gate test_overlay_gate.c && ./test_overlay_gate
* 全 PASS -> exit 0。
*/
#include <stdio.h>
#include "overlay_gate.h"
#include "lock_screen.h"
static int g_pass = 0, g_fail = 0;
#define CHECK(cond, msg) do { \
if (cond) { g_pass++; printf("PASS %s\n", msg); } \
else { g_fail++; printf("FAIL %s\n", msg); } \
} while (0)
int main(void)
{
/* ---- S1 输入穿透:SIGSTOP 过的 App 关闭时必须先排空再 unlink ---- */
CHECK(overlay_drain_required(0) == 1, "S1 drain required (app was SIGSTOP/frozen)");
CHECK(overlay_drain_required(1) == 0, "S1 no drain needed (media keep-alive, never stopped)");
/* 排空窗必须大于 SDK overlay_input_suppressed 的 200ms 标记节流窗,保证恢复后确实重新 stat 到
* 标记再整批丢弃缓冲事件;此处断言契约不被踩线。 */
CHECK(OVERLAY_DRAIN_WINDOW_MS > 200, "S1 drain window > SDK 200ms throttle");
/* ---- S2 解锁状态栏恢复:必须强制重同步 ---- */
CHECK(overlay_force_sb_resync() == 1, "S2 force status bar re-sync after unlock");
/* ---- 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");
/* ---- §310 锁屏叠加期中止谓词(lock_screen.h 纯判定)---- */
CHECK(lck_overlay_should_abort(0, 0) == 0, "S3 abort=0 (menu lock, no overlay pid)");
CHECK(lck_overlay_should_abort(0, 1) == 0, "S3 abort=0 (no overlay pid, zombie ignored)");
CHECK(lck_overlay_should_abort(4321, 0) == 0, "S3 abort=0 (overlay pid, app alive)");
CHECK(lck_overlay_should_abort(4321, 1) == 1, "S3 abort=1 (overlay pid + zombie -> abort lock)");
printf("\n%s (%d passed, %d failed)\n", g_fail ? "SOME TESTS FAILED" : "ALL PASS", g_pass, g_fail);
return g_fail ? 1 : 0;
}