diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index 45c1a07..f459632 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -42,6 +42,11 @@ #define MUI_DEFAULT_W 480 #define MUI_DEFAULT_H 720 +/* §10.5 叠加让位标记:与 launcher meow_launcher_internal.h NOTIF_ACTIVE 同路径。 + * 通知中心叠加会话期存在;宿主/单测可经 MEOW_NOTIF_ACTIVE_FILE 覆写路径(进程内只解析一次)。 */ +#define MEOW_NOTIF_ACTIVE_DEFAULT "/tmp/meow_notif_active" +#define MEOW_OVERLAY_CHK_MS 200 /* 存在性检查节流:别每帧 stat */ + /* 脏矩形上限(超出合并为全屏) */ #define MUI_DIRTY_MAX 12 @@ -939,10 +944,42 @@ static int statusbar_touched(void) return 0; } +/* ---- §10.5 叠加让位自抑(present 层)---- + * /tmp/meow_notif_active 存在 = 通知中心叠加会话进行中(launcher 正在 fb 上渲染面板)。 + * App 进程(非系统 UI 渲染者)此时不上屏;200ms 节流缓存存在性(别每帧 stat)。 + * 计数 g_overlay_suppressed 供宿主单测断言(meow_gfx_overlay_suppressed_count)。 */ +static unsigned long long g_overlay_suppressed = 0; + +static int overlay_present_suppressed(void) +{ + if (meow_ui_sys_render_enabled()) return 0; /* launcher(系统 UI)渲染面板,照常上屏 */ + static char s_path[108]; + static int s_path_init = 0; + static unsigned long long s_last_chk = 0; + static int s_last_res = 0; + if (!s_path_init) { + const char *e = getenv("MEOW_NOTIF_ACTIVE_FILE"); + snprintf(s_path, sizeof s_path, "%s", (e && e[0]) ? e : MEOW_NOTIF_ACTIVE_DEFAULT); + s_path_init = 1; + } + unsigned long long now = now_ms(); + if (s_last_chk && now - s_last_chk < MEOW_OVERLAY_CHK_MS) return s_last_res; + s_last_chk = now; + s_last_res = (access(s_path, F_OK) == 0) ? 1 : 0; + return s_last_res; +} + +unsigned long long meow_gfx_overlay_suppressed_count(void) { return g_overlay_suppressed; } + static void present(void) { if (!g_init_ok || !g_back_buf) return; if (!(g_dirty_full || g_ndirty > 0)) return; + /* §10.5 叠加让位自抑(仅 App 进程):App 前台播放中唤起通知中心时 launcher 不 SIGSTOP + * (保 PCM 供流,ncm 播放线程同进程),改由 App 进程在此自抑上屏——绘制照常进 back_buf, + * 只是不 present 到 fb,防 App 重绘穿透 launcher 叠加层。系统 UI 进程(launcher 渲染面板) + * 不抑制。抑制期脏区保留累积(超上限自然升级整帧),标记消失后下一次 present 一次上屏。 */ + if (overlay_present_suppressed()) { g_overlay_suppressed++; return; } /* 帧率门控(A-1/A-2,从 launcher 迁入):present 距上次 ≥ period(1000/eff_rate) 才呈现; * 未满周期则保留脏区(不 dirty_reset),下一轮再试。refresh_always_max=1 时跳过门控(恒满速)。 */ unsigned long long now = now_ms(); diff --git a/meow_ui_input.c b/meow_ui_input.c index 11dac2d..e105f5a 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -136,6 +136,30 @@ static int meow_input_jack_debounce(void) return g_jack_changed_at && (now - g_jack_changed_at) < 800; } +/* ---- §10.5 叠加让位:通知中心叠加会话期输入排空(播放中 App 不被 SIGSTOP 的配套)---- + * 媒体播放中的 App 前台唤起通知中心时 launcher 不冻结 App(保 PCM 供流,ncm 播放线程同进程), + * App 输入线程仍在读同一批 evdev 设备(每 fd 各收一份事件副本,与 launcher 互不剥夺)——若不 + * 抑制,面板上的一次点击会被 App 再消费一次(如面板媒体卡片「暂停」与 App 同坐标控件双重触发)。 + * 故标记存在期:排空本进程 fd 队列并丢弃、EVIOCGKEY 位图静默对齐(不产生边沿语义,系统键动作 + * 叠加期由 launcher/watchdog 单侧承担)。200ms 节流缓存存在性(与 present 层同口径,别每圈 stat)。 */ +static int overlay_input_suppressed(void) +{ + static char s_path[108]; + static int s_path_init = 0; + static unsigned long long s_last_chk = 0; + static int s_last_res = 0; + if (!s_path_init) { + const char *e = getenv("MEOW_NOTIF_ACTIVE_FILE"); /* 宿主/单测覆写(进程内只解析一次) */ + snprintf(s_path, sizeof s_path, "%s", (e && e[0]) ? e : "/tmp/meow_notif_active"); + s_path_init = 1; + } + unsigned long long now = now_ms(); + if (s_last_chk && now - s_last_chk < 200) return s_last_res; + s_last_chk = now; + s_last_res = (access(s_path, F_OK) == 0) ? 1 : 0; + return s_last_res; +} + /* ---- 系统按键语义(M2.55:SDK 输入层消化,App 前台也生效)---- * 物理键是系统资源(APPS.md §3.4.1),语义全局统一。本函数在输入线程执行系统动作: * 电源 116 → 息屏/亮屏/长按菜单(内核/sys_server/watchdog),SDK 不动(维持现状); @@ -451,9 +475,34 @@ static void *input_thread(void *arg) } } + int overlay_was = 0; /* §10.5:上一圈是否处于叠加抑制(进入沿复位触摸状态机用) */ while (g_thread_run) { if (nfd == 0) { usleep(20000); continue; } int r = poll(fds, nfd, 20); + /* §10.5 叠加让位:通知中心叠加会话期(标记存在)排空并丢弃本进程事件副本——面板输入 + * 由 launcher 接管,App 不得再消费(防面板点击被 App 双重触发);EVIOCGKEY 位图静默 + * 对齐(不产生边沿语义/系统键动作,解除后不会把面板期按键误判为新边沿)。 */ + if (overlay_input_suppressed()) { + if (!overlay_was) { /* 进入沿:复位触摸状态机,防抑制期丢失抬手致 t_down 卡死 */ + overlay_was = 1; + t_down = 0; t_press_valid = 0; t_origin_valid = 0; + t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; t_mv_x = t_mv_y = -1; + t_long_fired = 0; + } + if (r > 0) + for (int i = 0; i < nfd; i++) { + if (!(fds[i].revents & POLLIN)) continue; + unsigned char junk[64]; + while (read(fds[i].fd, junk, sizeof junk) > 0) { /* 整批丢弃 */ } + } + for (int i = 0; i < nfd; i++) { + unsigned char cur[KEY_MAX / 8 + 1]; + if (ioctl(fds[i].fd, EVIOCGKEY(sizeof cur), cur) == 0) + memcpy(prev_key[i], cur, sizeof cur); + } + continue; + } + overlay_was = 0; if (r < 0) { if (errno != EINTR) usleep(20000); } else if (r > 0) { diff --git a/meow_ui_internal.h b/meow_ui_internal.h index 013c73c..c1f895a 100644 --- a/meow_ui_internal.h +++ b/meow_ui_internal.h @@ -148,6 +148,10 @@ void meow_gfx_ring_arc(int cx, int cy, int r, int th, int start_deg, /* 是否运行于真实 fb 之上(0 = 头less/离屏,qemu/宿主)。 */ int meow_gfx_is_device(void); +/* §10.5 叠加让位:present 因 /tmp/meow_notif_active 存在而被自抑的累计次数 + * (仅 App 进程会自抑;宿主单测断言用,设备上无调用方)。 */ +unsigned long long meow_gfx_overlay_suppressed_count(void); + /* 中文/日文字库(mfont)是否已加载(状态栏「蓝牙」CJK 与回退 "BT" 二选一)。 */ int meow_gfx_mfont_ready(void); diff --git a/test_ime_fix b/test_ime_fix index 0e17466..6d6f73b 100755 Binary files a/test_ime_fix and b/test_ime_fix differ diff --git a/test_meow_ui_widgets b/test_meow_ui_widgets index d793a4f..ba5e99a 100755 Binary files a/test_meow_ui_widgets and b/test_meow_ui_widgets differ diff --git a/test_notif_gesture b/test_notif_gesture index fdbfced..d8de637 100755 Binary files a/test_notif_gesture and b/test_notif_gesture differ diff --git a/test_notif_open b/test_notif_open index 46ec474..76292e8 100755 Binary files a/test_notif_open and b/test_notif_open differ diff --git a/test_overlay_suppress.c b/test_overlay_suppress.c new file mode 100644 index 0000000..4fa5893 --- /dev/null +++ b/test_overlay_suppress.c @@ -0,0 +1,97 @@ +/* test_overlay_suppress.c — §10.5 叠加让位 present 自抑宿主验证(离屏 480x720,16bpp)。 + * + * 背景:媒体播放中的 App 前台唤起通知中心时 launcher 不 SIGSTOP(保 PCM 供流),改由 SDK + * present 层见 /tmp/meow_notif_active(宿主用 MEOW_NOTIF_ACTIVE_FILE 覆写)自抑上屏。 + * + * 覆盖: + * T1 无标记:flush 正常上屏(frames_presented 递增,抑制计数 0); + * T2 有标记:flush 被抑制(只画进 back_buf 不 present:frames 不变、抑制计数递增); + * T2b 抑制期第二次 flush 仍被抑制(200ms 节流缓存内行为一致); + * T3 标记消失:下一次 flush 恢复上屏(抑制期累积脏区一次上屏,无需重画); + * T4 系统 UI 进程(launcher,system_mode=1)即使有标记也不抑制(面板必须照常上屏)。 + * + * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global): + * gcc -Wall -Wextra -o test_overlay_suppress test_overlay_suppress.c test_host_stub.c \ + * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ + * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ + * meow_ui_draw.c meow_ui_view.c meow_notify_ipc.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include +#include +#include +#include +#include +#include + +#define MARKER "/tmp/test_notif_active_sdk" + +static void marker_write(void) +{ + int fd = open(MARKER, O_WRONLY | O_CREAT | O_TRUNC, 0644); + assert(fd >= 0); + if (write(fd, "1", 1) < 0) assert(0); + close(fd); +} + +int main(void) +{ + setenv("MEOW_UI_DUMP", "/tmp/overlay_suppress.ppm", 1); + setenv("MEOW_NOTIF_ACTIVE_FILE", MARKER, 1); + unlink(MARKER); + meow_ui_opts o = { 480, 720 }; + assert(meow_ui_init(&o) == 0); + meow_ui_set_refresh_always_max(1); /* 绕过帧率门控:每次 flush 都直达 present 判定 */ + + /* T1:无标记正常上屏(默认 App 进程模式:render_enabled==0) */ + assert(meow_ui_sys_render_enabled() == 0); + meow_ui_clear(0x112233); + meow_ui_flush(); + assert(meow_ui_frames_presented() == 1); + assert(meow_gfx_overlay_suppressed_count() == 0); + printf("T1 PASS: no marker -> presented=1 suppressed=0\n"); + + /* T2:有标记 → 抑制(等 >200ms 越过存在性节流缓存) */ + marker_write(); + usleep(250000); + unsigned long long f0 = meow_ui_frames_presented(); + meow_ui_clear(0x223344); + meow_ui_flush(); + assert(meow_ui_frames_presented() == f0); + assert(meow_gfx_overlay_suppressed_count() == 1); + printf("T2 PASS: marker present -> suppressed (frames frozen at %llu)\n", f0); + + /* T2b:抑制期再次 flush 仍抑制(节流缓存内一致) */ + meow_ui_clear(0x334455); + meow_ui_flush(); + assert(meow_ui_frames_presented() == f0); + assert(meow_gfx_overlay_suppressed_count() == 2); + printf("T2b PASS: still suppressed within throttle window\n"); + + /* T3:标记消失 → 恢复上屏(抑制期累积的整帧脏一次上屏,无需重画) */ + unlink(MARKER); + usleep(250000); + meow_ui_flush(); + assert(meow_ui_frames_presented() == f0 + 1); + assert(meow_gfx_overlay_suppressed_count() == 2); + printf("T3 PASS: marker gone -> present resumed (accumulated dirty flushed)\n"); + + /* T4:系统 UI 进程(launcher)不抑制——通知中心面板本身必须照常上屏 */ + meow_ui_sys_set_system_mode(1); + assert(meow_ui_sys_render_enabled() == 1); + marker_write(); + usleep(250000); + unsigned long long f1 = meow_ui_frames_presented(); + meow_ui_clear(0x445566); + meow_ui_flush(); + assert(meow_ui_frames_presented() == f1 + 1); + assert(meow_gfx_overlay_suppressed_count() == 2); /* 不再增长 */ + unlink(MARKER); + printf("T4 PASS: system-UI process (launcher) never suppressed\n"); + + meow_ui_shutdown(); + printf("ALL PASS\n"); + return 0; +} diff --git a/test_sb_collapse b/test_sb_collapse index 02fe5c9..385d32b 100755 Binary files a/test_sb_collapse and b/test_sb_collapse differ