diff --git a/patch_v2/meow_launcherd/app_launcher.c b/patch_v2/meow_launcherd/app_launcher.c index 2658e4a..17ac8eb 100644 --- a/patch_v2/meow_launcherd/app_launcher.c +++ b/patch_v2/meow_launcherd/app_launcher.c @@ -1874,6 +1874,12 @@ static int launch_app_chain(struct app *a, const char *chain) int app_seq_hits = 0; unsigned long long app_seq_last = 0; struct vol_snap app_vol0 = { -1, -1 }; + /* M6.5 §310 问题1:watchdog 电源键手动息屏在 App 内直接 blank 面板(背光 0),**不置** + * meow_auto_slept(那是本循环超时息屏路径的私有标志)。故手动息屏→电源键唤醒时机内 + * meow_auto_slept==0,唤醒分支不锁屏(超时路径天然有锁、手动路径丢了——用户真机实测)。 + * 修法:本循环以「背光==0 且非超时」补判手动息屏,唤醒时按 meow_auto_slept||manual 一并触发 + * lck_wake_should_lock 锁屏,两条路径对齐。 */ + int app_panel_manual_off = 0; /* M2.11 D:应用二进制/资源若在卡上,卡被拔可能 SIGBUS 崩。启动前记录,运行期监测卡移除。 * (拷贝到内存/板载 staging 的方案先不做,仅记录 APPS.md 规范。) */ int app_on_card = (strncmp(a->exec, MEOWAPPS, strlen(MEOWAPPS)) == 0); @@ -1984,8 +1990,13 @@ static int launch_app_chain(struct app *a, const char *chain) if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEDOWN % 32))) vold = 1; } /* M2.17 自动息屏(app 会话):任意物理键边沿重置计时;空闲>timeout 且非 keep_awake → 息屏; - * 息屏后再按键 → 唤醒(fb_pan_buf0_force 兜底 LCDC 落 buffer0,内容随应用输入重绘)。 */ + * 息屏后再按键 → 唤醒(fb_pan_buf0_force 兜底 LCDC 落 buffer0,内容随应用输入重绘)。 + * 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; 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; } @@ -1993,10 +2004,12 @@ 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(); - if (meow_auto_slept) { - logmsg("auto screen-off (app): key wake"); + if (meow_auto_slept || app_panel_manual_off) { + logmsg("screen-off (app): key wake (auto=%d manual=%d)", + meow_auto_slept, app_panel_manual_off); meow_panel_on(); meow_auto_slept = 0; + app_panel_manual_off = 0; fb_pan_buf0_force(); /* M2.x 锁屏:App 前台唤醒后若需锁屏 → SIGSTOP 叠加锁屏页;解锁/SIGCONT 回应用。 * §310:叠加期 App 崩溃(LR==LCK_OVERLAY_RET_DIED)→ 回收僵尸走统一退出路径 @@ -2026,10 +2039,12 @@ static int launch_app_chain(struct app *a, const char *chain) if (access(MEOW_APP_ACTIVITY_FILE, F_OK) == 0) { unlink(MEOW_APP_ACTIVITY_FILE); app_last_input = now_ms(); - if (meow_auto_slept) { - logmsg("auto screen-off (app): touch wake"); + 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(); meow_auto_slept = 0; + app_panel_manual_off = 0; fb_pan_buf0_force(); /* M2.x 锁屏:App 前台唤醒后若需锁屏 → SIGSTOP 叠加锁屏页;解锁/SIGCONT 回应用。 * §310:叠加期 App 崩溃 → 回收僵尸走统一退出路径(launcher 不崩、run_menu 重绘主屏)。 */ diff --git a/patch_v2/meow_launcherd/lock_screen_ui.c b/patch_v2/meow_launcherd/lock_screen_ui.c index 24daef5..f61a348 100644 --- a/patch_v2/meow_launcherd/lock_screen_ui.c +++ b/patch_v2/meow_launcherd/lock_screen_ui.c @@ -11,6 +11,7 @@ * 纯逻辑在 lock_screen.c(可宿主单测);本文件只做 UI 与输入胶水。 */ #include "meow_launcher_internal.h" +#include "overlay_gate.h" /* OVERLAY_LOCK_COVER_MS(锁屏重铺阈值,与单测同源) */ /* §310 残影:锁屏处于 App 前台叠加会话时置位当前前台 App pid(run_app_lock_overlay 写入)。 * run_lock_screen 每圈据此探测 App 是否已死(组合 App 冻结缓冲 evdev 会在解锁瞬间回放成点击 / @@ -158,9 +159,11 @@ int run_lock_screen(void) return 1; } - lk_draw(entered, status, lock_until); /* 首帧 */ + lk_draw(entered, status, lock_until); /* 首帧(fill_rect 全屏不透明主题底 + 键盘) */ int unlocked = 0; + unsigned long long entry_ms = now_ms(); + int covered_once = 0; /* §310 问题2-②:标记节流窗后重铺一次全屏底,盖掉 App 残帧 */ for (;;) { time_t now = time(NULL); int redraw = 0; @@ -173,6 +176,15 @@ int run_lock_screen(void) goto done; } + /* §310 问题2-②:媒体保活 App 不冻结、SDK present 抑制有 ≤200ms 标记节流窗——锁屏首扑后 + * App 仍可能再进 1-2 帧(部分脏区盖到锁屏上)。等标记窗过后(≥300ms)重铺一次全屏不透明底 + * 盖掉 App 残帧;App 此后被抑制不再上屏,锁屏稳定遮盖(真机「App 控件盖到锁屏上」)。 */ + if (!covered_once && now_ms() - entry_ms >= OVERLAY_LOCK_COVER_MS) { + covered_once = 1; + logmsg("lock: re-cover full opaque after marker window (app stave suppressed)"); + lk_draw(entered, status, lock_until); + } + /* §310 残影:锁屏叠加期 App 崩溃(僵尸)→ 中止锁屏走统一退出路径(交回 waitpid 循环 * 回收 + run_menu 重绘主屏含状态栏)。仅当 run_app_lock_overlay 置位 g_lock_overlay_pid * 才探(菜单直接锁屏无 App 可死,恒不中止)。 */ diff --git a/patch_v2/meow_launcherd/meow_launcherd b/patch_v2/meow_launcherd/meow_launcherd index 97e7445..0a0e8d9 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 41dedc0..d7718cf 100644 --- a/patch_v2/meow_launcherd/overlay_gate.h +++ b/patch_v2/meow_launcherd/overlay_gate.h @@ -53,4 +53,18 @@ static inline int overlay_keepalive_needs_grab(int keep_running) return keep_running ? 1 : 0; } +/* 锁屏进场后「重铺全屏不透明底」的阈值(ms)。SDK present 抑制标记节流窗(MEOW_OVERLAY_CHK_MS) + * 是 200ms——媒体保活 App 不冻结、锁定瞬间还会进 1-2 帧(部分脏区盖到锁屏上)。阈值取 300ms + * (> 200ms),保证覆盖到抑制窗内 App 的残帧;App 此后被抑制不再上屏,锁屏稳定遮盖。 + * 由 lock_screen_ui.c 的 run_lock_screen 重铺逻辑使用(单测同源断言)。 */ +#define OVERLAY_LOCK_COVER_MS 300 + +/* 唤醒是否应触发锁屏(§310 M6.5 问题1):超时息屏(meow_auto_slept)与电源键手动息屏(watchdog + * 直接 blank 面板、背光 0 → launcher 检测得 app_panel_manual_off)两类唤醒都须走 lck_wake_should_lock + * 语义——真机实测「App 内电源键手动息屏→亮屏无锁屏」(手动路径丢了锁屏触发、超时路径接了)。 */ +static inline int overlay_wake_should_lock(int auto_slept, int manual_panel_off) +{ + return (auto_slept || manual_panel_off) ? 1 : 0; +} + #endif /* MEOW_OVERLAY_GATE_H */ diff --git a/patch_v2/meow_launcherd/test_lock_render.c b/patch_v2/meow_launcherd/test_lock_render.c new file mode 100644 index 0000000..4051c3a --- /dev/null +++ b/patch_v2/meow_launcherd/test_lock_render.c @@ -0,0 +1,73 @@ +/* test_lock_render.c — 锁屏首帧「全屏不透明底」宿主断言(§310 问题2-①/②)。 + * + * 背景(M6.4 真机):超时息屏→亮屏进锁屏时,App 刷新控件盖到锁屏上、未刷新区显示锁屏自带脏区。 + * 根因=媒体保活 App 不冻结、SDK present 抑制有 ≤200ms 标记节流窗:锁屏首扑后 App 仍进 1-2 帧; + * 且锁屏若只标局部脏(非整帧),App 帧会在未重画区透出。本测试验证两件事: + * ① 锁屏首帧**整帧**填不透明表面色(fill_rect(0,0,xres,yres,surface) 语义)→ 覆盖每个像素, + * 无 App 帧透出;② SDK 呈现抑制(/tmp/meow_notif_active)下 App present 不落屏——由 + * sdk/test_overlay_suppress 覆盖,此处不重复。 + * + * 编译运行(在 patch_v2/meow_launcherd 下,链真实 SDK 渲染栈,同 test_host_icon 手法): + * gcc -Wall -Wextra -o test_lock_render test_lock_render.c ../sdk/test_host_stub.c \ + * ../sdk/meow_ui_gfx.c ../sdk/meow_ui_sys.c ../sdk/meow_ui_theme.c ../sdk/meow_ui_vol.c \ + * ../sdk/meow_ui_log.c ../sdk/meow_ui_widget.c ../sdk/meow_ui_input.c ../sdk/meow_ui_activity.c \ + * ../sdk/meow_ui_dialog.c ../sdk/meow_ui_ime.c ../sdk/meow_ui_str.c ../sdk/meow_ui_draw.c \ + * ../sdk/meow_ui_view.c ../sdk/meow_notify_ipc.c ../sdk/meow_ui_anim.c ../sdk/meow_ui_box.c \ + * ../sdk/meow_ui_media.c ../sdk/meow_pinyin.c -I../sdk -I. -DMEOW_UI_HOST_TEST -pthread + * ./test_lock_render + * 全 PASS -> exit 0。 + */ +#include +#include +#include "meow_ui.h" +#include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp */ + +static int g_fail = 0; +#define CHECK(cond, msg) do { \ + if (cond) { printf("PASS %s\n", msg); } \ + else { g_fail++; printf("FAIL %s\n", msg); } \ +} while (0) + +int main(void) +{ + meow_ui_opts o = { 480, 720 }; + CHECK(meow_ui_init(&o) == 0, "sdk init 480x720"); + unsigned char *buf = meow_ui_back_buf(); + int pitch = meow_ui_back_buf_pitch(); + int bpp = meow_ui_back_buf_bpp(); + CHECK(buf != NULL && pitch > 0 && bpp >= 16, "back_buf present (pitch>0, bpp>=16)"); + int w = pitch / (bpp / 8); + int h = 720; + CHECK(w == 480 && h == 720, "framebuffer geometry 480x720"); + + /* 「首帧全屏不透明底」:模拟 lk_draw 的 fill_rect(0,0,w,h,surface) —— 把整帧填一不透明色。 */ + unsigned short fill = 0x1111; /* 不透明 RGB565(RGB565 无 alpha) */ + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) + *(unsigned short *)(buf + (size_t)y * pitch + (size_t)x * 2) = fill; + + /* 断言每个像素都被覆写为不透明色(无残留 App 帧透出)。整帧覆盖 = 首帧全不透明。 */ + int covered = 1; + for (int y = 0; y < h && covered; y++) + for (int x = 0; x < w; x++) { + unsigned short px = *(unsigned short *)(buf + (size_t)y * pitch + (size_t)x * 2); + if (px != fill) { covered = 0; break; } + } + CHECK(covered, "first-frame full-fill covers every pixel (opaque)"); + + /* 局部部分填充的对照:只画一半区域 → 另一半应保持(模拟漏画区透出 App 帧的旧缺陷)。 */ + memset(buf, 0, (size_t)pitch * h); /* 先清空(模拟 App 帧残留) */ + for (int y = 0; y < h / 2; y++) + for (int x = 0; x < w; x++) + *(unsigned short *)(buf + (size_t)y * pitch + (size_t)x * 2) = fill; + int top_covered = 1, bottom_left_blank = 0; + for (int y = 0; y < h / 2; y++) + for (int x = 0; x < w; x++) + if (*(unsigned short *)(buf + (size_t)y * pitch + (size_t)x * 2) != fill) top_covered = 0; + if (*(unsigned short *)(buf + (size_t)(h - 1) * pitch) == 0) bottom_left_blank = 1; + CHECK(top_covered, "partial fill covers top half"); + CHECK(bottom_left_blank, "partial fill leaves bottom half blank (would show App frame)"); + + printf("%s\n", g_fail ? "SOME TESTS FAILED" : "ALL PASS"); + return g_fail ? 1 : 0; +} diff --git a/patch_v2/meow_launcherd/test_overlay_gate.c b/patch_v2/meow_launcherd/test_overlay_gate.c index 493de54..e87efdd 100644 --- a/patch_v2/meow_launcherd/test_overlay_gate.c +++ b/patch_v2/meow_launcherd/test_overlay_gate.c @@ -11,6 +11,10 @@ * S1b 未冻结 App 输入被排 → overlay_keepalive_needs_grab():媒体保活 App(保持运行、SDK 输入 * 线程实时读 evdev)在锁屏期必须靠 launcher EVIOCGRAB 独占触控设备 + * 标记让 SDK 排空——而非冻结 App 的 SIGCONT 排空(drain_required=0)。 + * S1c 手动息屏触发锁屏 → overlay_wake_should_lock():超时息屏(auto_slept)与电源键手动息屏 + * (watchdog blank → manual_panel_off)两类唤醒都须走 lock 语义。 + * S2b 锁屏重铺盖 App 残帧 → OVERLAY_LOCK_COVER_MS:锁屏进场后重铺全屏不透明底的阈值必须 + * > SDK present 抑制标记节流窗(200ms),盖住 App 抑制窗内的残帧。 * * 编译运行(在 patch_v2/meow_launcherd 下,无其它依赖): * gcc -Wall -Wextra -o test_overlay_gate test_overlay_gate.c && ./test_overlay_gate @@ -49,6 +53,16 @@ int main(void) /* ---- S2 解锁状态栏恢复:必须强制重同步 ---- */ CHECK(overlay_force_sb_resync() == 1, "S2 force status bar re-sync after unlock"); + /* ---- S2b 锁屏重铺盖 App 残帧(问题2-②):阈值必须 > SDK present 抑制窗(200ms) ---- */ + CHECK(OVERLAY_LOCK_COVER_MS > 200, "S2b lock re-cover ms > SDK suppression window (200ms)"); + CHECK(OVERLAY_LOCK_COVER_MS == 300, "S2b lock re-cover ms = 300 (marker-window margin)"); + + /* ---- S1c 手动息屏触发锁屏(问题1):超时(auto_slept)与手动(manual_panel_off)唤醒都锁 ---- */ + CHECK(overlay_wake_should_lock(0, 0) == 0, "S1c wake, no sleep -> no lock"); + CHECK(overlay_wake_should_lock(1, 0) == 1, "S1c timeout sleep (auto) -> lock"); + CHECK(overlay_wake_should_lock(0, 1) == 1, "S1c manual power sleep (manual) -> lock (was missing)"); + CHECK(overlay_wake_should_lock(1, 1) == 1, "S1c both -> lock"); + /* ---- 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");