diff --git a/patch_v2/files_app/files b/patch_v2/files_app/files index 0d16eaa..89083d5 100755 Binary files a/patch_v2/files_app/files and b/patch_v2/files_app/files differ diff --git a/patch_v2/hello_app/hello b/patch_v2/hello_app/hello index 8d52a75..98af913 100755 Binary files a/patch_v2/hello_app/hello and b/patch_v2/hello_app/hello differ diff --git a/patch_v2/ncm_app/ncm b/patch_v2/ncm_app/ncm index cb4e620..92f9260 100755 Binary files a/patch_v2/ncm_app/ncm and b/patch_v2/ncm_app/ncm differ diff --git a/patch_v2/sdk/meow_ui_draw.c b/patch_v2/sdk/meow_ui_draw.c index 7ffcd4e..4d4f05d 100644 --- a/patch_v2/sdk/meow_ui_draw.c +++ b/patch_v2/sdk/meow_ui_draw.c @@ -345,10 +345,15 @@ static void paint(int full, int x, int y, int w, int h) meow_ui_flush(); } else { int r[4]; + /* 区域重画(meow_ui_invalidate):把绘制 clip 收敛到本次失效矩形,只画「真正相交且可见」的 + * 部分——大绘制物(整页 card/布局)仅擦边相交时不再整块重画,而是裁到矩形内,避免 overdraw。 + * 控件内部还会再 push 自身矩形(嵌套求交 = 矩形∩控件,语义不变:越界仍被裁)。 */ + meow_gfx_clip_push(x, y, w, h); for (int i = 0; i < nn; i++) { if (!entry_rect(order[i], r)) continue; if (rect_hit(r[0], r[1], r[2], r[3], x, y, w, h)) draw_one(order[i]); } + meow_gfx_clip_pop(); meow_ui_flush_region(x, y, w, h); } } diff --git a/patch_v2/sdk/meow_ui_gfx.c b/patch_v2/sdk/meow_ui_gfx.c index a25a41f..45c1a07 100644 --- a/patch_v2/sdk/meow_ui_gfx.c +++ b/patch_v2/sdk/meow_ui_gfx.c @@ -77,7 +77,10 @@ static void clip_cur(int out[4]) { /* 当前 clip 边界(无 clip = 全屏 out[2] = g_clip_stack[g_clip_n - 1][2]; out[3] = g_clip_stack[g_clip_n - 1][3]; } else { out[0] = 0; out[1] = 0; out[2] = g_xres; out[3] = g_yres; } } -static int clip_contains(int x, int y) +/* 逐像素 clip 判定:须 inline —— MIPS -Os 若抽成函数调用,每一像素(ring/circle/圆角/ + * alpha/blit/5x7)都付一次 call+prologue,软渲上被显著放大(宿主 x86 -Os 会内联故测不出)。 + * 强制内联后为 4 次整数比较,与旧「越界检查」同价;顶层(g_clip_n==0)= 全屏零开销直通。 */ +static inline int clip_contains(int x, int y) { if (g_clip_n == 0) return x >= 0 && x < g_xres && y >= 0 && y < g_yres; return x >= g_clip_stack[g_clip_n - 1][0] && x < g_clip_stack[g_clip_n - 1][2] && @@ -1136,6 +1139,7 @@ int meow_ui_init(const meow_ui_opts *opts) { if (g_init_ok) return 0; meow_log_install_exit_handler(); /* M2.78:SIGTERM/SIGINT 兜底写 exit: 日志 */ + meow_ui_install_sigusr1_guard(); /* M2.87:SIGUSR1 无害护栏(防泄漏 SIGUSR1 杀 App) */ int fw = 0, fh = 0; if (opts) { fw = opts->width; fh = opts->height; } diff --git a/patch_v2/sdk/meow_ui_input.c b/patch_v2/sdk/meow_ui_input.c index c7630fc..eb5c0dc 100644 --- a/patch_v2/sdk/meow_ui_input.c +++ b/patch_v2/sdk/meow_ui_input.c @@ -261,7 +261,17 @@ static void touch_release(void) * 屏幕中部滑动不是返回手势:中部右滑→RIGHT(内容导航),中部左滑→非返回(落 TAP, * 避免任意处左滑误退出)。纵向滑动→UP/DOWN 照旧。 */ if (adx >= SWIPE_THRESH || ady >= SWIPE_THRESH) { - if (adx >= ady) { + /* 顶部下滑 → 通知中心(系统保留手势,不下发 APP):起点在顶部热区 + 有真实向下位移。 + * 在主导轴判定(adx vs ady)之前先行识别并置最高优先级——顶起「起点同时落在顶部热区与 + * 左缘」的边界(定案:起点 y 在顶部热区 → 顶部手势优先)。若无此优先,下落时含右向分量的 + * 手势会被水平分量抢先判成 MEOW_SWIPE_LEFT → 漏给 App on_swipe → 根页退出(真机闪退链)。 + * 热区高度随状态变化(非全屏=整条状态栏、全屏=MEOW_SBAR_HOTZONE_FS 缩小、收合=提示线高) + * ——全屏 app 只在顶部极窄热区被系统占用,故「不影响全屏 app」。 + * 仅当真「向下拉」(mdy>0 且 ady≥SWIPE_THRESH)才认:纯水平右滑(左缘返回)或上滑不误判。 */ + if (mdy > 0 && ady >= SWIPE_THRESH && + meow_ui_input_is_notif_pull(t_dy, meow_ui_statusbar_hotzone())) { + push_ev(MEOW_EV_NOTIF_PULL, t_dy, ady > adx ? ady : adx); + } else if (adx >= ady) { if (t_dx >= 0 && t_dx < EDGE_W && (t_cx - t_dx) >= SWIPE_THRESH) { push_ev(MEOW_EV_SWIPE, MEOW_SWIPE_LEFT, adx); /* 左缘返回手势 */ } else if (mdx > 0) { @@ -271,14 +281,7 @@ static void touch_release(void) } } else { int dir = mdy > 0 ? MEOW_SWIPE_DOWN : MEOW_SWIPE_UP; - /* 顶部下滑 → 通知中心(系统手势,不下发 APP):起点在顶部热区内才认。 - * 热区高度随状态变化(非全屏=整条状态栏、全屏=MEOW_SBAR_HOTZONE_FS 缩小、 - * 收合=提示线高)——全屏 app 只在顶部极窄热区被系统占用,故「不影响全屏 app」。 */ - if (dir == MEOW_SWIPE_DOWN && - meow_ui_input_is_notif_pull(t_dy, meow_ui_statusbar_hotzone())) - push_ev(MEOW_EV_NOTIF_PULL, t_dy, ady > adx ? ady : adx); - else - push_ev(MEOW_EV_SWIPE, dir, ady > adx ? ady : adx); + push_ev(MEOW_EV_SWIPE, dir, ady > adx ? ady : adx); } } else { push_ev(MEOW_EV_TAP, t_cx, t_cy); @@ -569,7 +572,10 @@ void meow_ui_run(const meow_ui_events *ev) if (in_startup) continue; /* 启动窗口顶区误触残留:丢弃,防进入即开通知中心 */ /* 系统级「下拉展开通知中心」:起始于顶部热区的下滑为系统保留手势,不下发 * APP on_swipe;路由到系统钩子(App 进程只检测+经 IPC 上报 MSG_NOTIF_OPEN, - * 通知中心由 launcher 渲染,见 FRAMEWORK_DESIGN §10.5)。 */ + * 通知中心由 launcher 渲染,见 FRAMEWORK_DESIGN §10.5)。 + * 起点若同时落在左缘,拖动期可能已画过 MEOW_EV_SWIPE_PROGRESS 左缘指示条, + * 抬手定判为 NOTIF_PULL 后须清掉(否则通知中心打开后残留返回条)。 */ + meow_ui_swipe_hint(-1); meow_ui_sys_notif_pull(e.a); /* e.a = 手势按下原点 Y */ } else if (e.type == MEOW_EV_TOUCH_DOWN || e.type == MEOW_EV_TOUCH_MOVE) { /* M2.71-bug2/6:拖动过程(跟手)。IME 弹出时触摸优先喂键盘(键盘覆盖层拦截, diff --git a/patch_v2/sdk/meow_ui_internal.h b/patch_v2/sdk/meow_ui_internal.h index 73a46a2..a091f01 100644 --- a/patch_v2/sdk/meow_ui_internal.h +++ b/patch_v2/sdk/meow_ui_internal.h @@ -105,6 +105,11 @@ void meow_gfx_flush_hold(int on); /* M2.78:安装 SIGTERM/SIGINT 退出日志兜底处理器(meow_ui_log.c;meow_ui_init 调用)。 */ void meow_log_install_exit_handler(void); +/* M2.87:安装 SIGUSR1 无害护栏处理器(meow_ui_log.c;meow_ui_init 调用)。 + * SDK 框架 App 的左缘返回由 SDK on_swipe 处理,watchdog 对框架 App 不再发 SIGUSR1; + * 此护栏兜底任何漏进的 SIGUSR1(看门狗判定误差/竞态/测试路径):记日志+忽略,绝不杀 App。 */ +void meow_ui_install_sigusr1_guard(void); + /* 绘制期裁剪(MEOW_APP_SDK.md §3,2026-08-31):当前 clip 矩形内才写像素,默认全屏。 * meow_widget_draw(w) 画任何控件前 push 该控件矩形、画完 pop——任何控件(含 W_CUSTOM) * 画不出自身边界,根治自定义画布越界脏区。可嵌套(入栈/出栈,push 与当前 clip 求交)。 diff --git a/patch_v2/sdk/meow_ui_log.c b/patch_v2/sdk/meow_ui_log.c index 73b4fc0..d5125ba 100644 --- a/patch_v2/sdk/meow_ui_log.c +++ b/patch_v2/sdk/meow_ui_log.c @@ -143,6 +143,35 @@ void meow_log_install_exit_handler(void) signal(SIGINT, log_sig_exit); } +/* M2.87:SIGUSR1 无害护栏(防线冗余)。watchdog 对「左缘起点+右滑」发 SIGUSR1 当返回事件; + * SDK 框架 App(framework=1)自己经 on_swipe 处理左缘返回(meow_ui_input.c),不应被 SIGUSR1 + * 终止。但看门狗对 framework 判定有误/竞态/测试注入等**任何**路径漏进 SIGUSR1,默认动作都会 + * 杀死 App(ncm signal=16 崩溃根因之一)。本节安装一个不做任何致命动作的处理器:记日志+忽略 + * ——框架 App 的返回/退出语义由 SDK on_swipe 统一决策,SIGUSR1 仅作「绝不杀 App」的保活护栏, + * 不重复触发返回(避免与 on_swipe 双重返回)。App 若自装 SIGUSR1 处理器(hello/ncm 均未装, + * 裸沉浸 App 自绘处理)会覆盖本处理器;`--sim-swipe` 测试路径也随之被吞。 */ +static void sigusr1_guard(int sig) +{ + (void)sig; + if (!g_log_open_tried) { g_log_open_tried = 1; log_open(); } + if (g_log_fd >= 0) { + char line[128]; + int n = snprintf(line, sizeof line, "%llu [%s] WARN sigusr1: ignored (guard)\n", + (unsigned long long)meow_ui_time_ms(), g_app_id); + if (n > 0) { ssize_t w = write(g_log_fd, line, (size_t)n); (void)w; } + } + /* 不 exit、不返回处理:返回交给 on_swipe/SDK。这里仅记录并吞掉。 */ +} +void meow_ui_install_sigusr1_guard(void) +{ + struct sigaction sa; + memset(&sa, 0, sizeof sa); + sa.sa_handler = sigusr1_guard; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; /* 不设 SA_RESTART:被中断的 poll/read 返回按 EINTR 处理后继续 */ + sigaction(SIGUSR1, &sa, NULL); +} + /* mkdir -p:逐分量 mkdir(已存在 EEXIST 视为成功)。返回 0=成功,-1=失败。 */ static int log_mkdir_p(const char *path) { diff --git a/patch_v2/sdk/test_ime_fix b/patch_v2/sdk/test_ime_fix index 4a9a69a..0e17466 100755 Binary files a/patch_v2/sdk/test_ime_fix and b/patch_v2/sdk/test_ime_fix differ diff --git a/patch_v2/sdk/test_meow_ui_vol b/patch_v2/sdk/test_meow_ui_vol new file mode 100755 index 0000000..2b2b3b7 Binary files /dev/null and b/patch_v2/sdk/test_meow_ui_vol differ diff --git a/patch_v2/sdk/test_meow_ui_widgets b/patch_v2/sdk/test_meow_ui_widgets index acde7c0..54c000f 100755 Binary files a/patch_v2/sdk/test_meow_ui_widgets and b/patch_v2/sdk/test_meow_ui_widgets differ diff --git a/patch_v2/sdk/test_notif_gesture b/patch_v2/sdk/test_notif_gesture new file mode 100755 index 0000000..feb309b Binary files /dev/null and b/patch_v2/sdk/test_notif_gesture differ diff --git a/patch_v2/sdk/test_notif_gesture.c b/patch_v2/sdk/test_notif_gesture.c new file mode 100644 index 0000000..2f7c206 --- /dev/null +++ b/patch_v2/sdk/test_notif_gesture.c @@ -0,0 +1,120 @@ +/* test_notif_gesture.c — M2.87 宿主验证「顶部下滑 → 通知中心」手势优先分类(meow_ui_input.c): + * 复现并断言修复:起点在顶部热区 + 有真实向下位移(mdy>0 且 ady≥SWIPE_THRESH)的下滑,必须 + * 分类为 MEOW_EV_NOTIF_PULL(系统保留手势,路由 meow_ui_sys_notif_pull → MSG_NOTIF_OPEN), + * 即便该手势同时落在左缘且向右位移 ≥ ady(旧代码被水平分量抢判为 MEOW_SWIPE_LEFT → 漏给 + * App on_swipe → 根页退出)。纯水平右滑/起点不在热区的下滑不误判。 + * + * 用例(经 meow_input_touch_set/move/release 驱动真实 touch_release + 队列,pop 断言): + * T1 顶部+左缘+向下右侧滑 (10,5)->(110,75):adx=100≥ady=70, 热区内, ady≥65 → NOTIF_PULL(旧代码→LEFT) + * T2 顶部+左缘 竖直下滑 (240,5)->(240,120):ady=115, 热区内 → NOTIF_PULL + * T3 顶部+左缘 下移<65 (10,5)->(100,40):ady=35<65 → 非 NOTIF_PULL → LEFT(水平占优) + * T4 顶部+左缘 纯水平右滑 (5,5)->(110,5):ady=0<65 → 非 NOTIF_PULL → LEFT + * T5 中部(非热区)下滑 (240,100)->(240,220):ady=120 但原点 y=100≥热区 → SWIPE DOWN(内容滚动) + * T6 左缘中部 右滑返回 (5,300)->(110,300):非热区, 水平占优 → SWIPE LEFT(返回) + * T7 顶部中央 下滑后回弹 (240,5)->(240,-... 不适用;改用 (240,5)->(240,90) 大 ady) → NOTIF_PULL + * + * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/ime/media/notify_ipc + 打桩 font/global): + * gcc -Wall -Wextra -o test_notif_gesture test_notif_gesture.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_ime.c meow_ui_media.c meow_notify_ipc.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm + * 运行:./test_notif_gesture + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include "meow_ui_sys.h" + +#include +#include +#include + +/* MEOW_EV_NOTIF_PULL 是 meow_ui_input.c 内部常量(未入公共头),宿主测试按同值声明。 */ +#ifndef MEOW_EV_NOTIF_PULL +#define MEOW_EV_NOTIF_PULL 5 +#endif + +/* 与 meow_ui_input.c 同阈值(防漂移:直接引用会打破封装,这里显式对齐注释值)。 */ +#define SWIPE_THRESH 65 +#define EDGE_W 24 +#define HOTZONE 28 /* meow_ui_statusbar_hotzone():非全屏=状态栏高 MUI_SBAR_H */ + +static int g_fail = 0; + +/* 驱动一次手势并返回队列中首个(应唯一)语义事件类型;无事件返回 -1。 + * 顺序:drain → touch_set(按下) → touch_move(终点) → touch_release(抬手) → pop。 */ +static int gesture_ev(int x0, int y0, int x1, int y1, int *a, int *b) +{ + meow_input_drain(); + meow_input_touch_set(1, x0, y0, 0); /* 按下锁定原点 (x0,y0) */ + meow_input_touch_move(x1, y1); /* 移动到终点 */ + meow_input_touch_release(); /* 抬手:touch_release 做手势分类 */ + meow_input_touch_set(0, 0, 0, 0); /* 复位触摸状态机 */ + meow_input_ev e; + if (!meow_input_pop(&e)) { *a = *b = -1; return -1; } + *a = e.a; *b = e.b; + return e.type; +} + +static void expect(const char *name, int got, int want, int a, int b) +{ + if (got == want) { + printf("PASS %s: type=%d (a=%d b=%d)\n", name, got, a, b); + } else { + printf("FAIL %s: got type=%d want=%d (a=%d b=%d)\n", name, got, want, a, b); + g_fail = 1; + } +} + +int main(void) +{ + meow_ui_opts o = { 480, 720 }; + assert(meow_ui_init(&o) == 0); + /* 默认非全屏 / 状态栏激活 → 热区 28 */ + assert(meow_ui_statusbar_hotzone() == HOTZONE); + + int a = 0, b = 0, t = 0; + + /* T1 顶部+左缘 向下右侧滑:旧代码 → SWIPE_LEFT;修复后 → NOTIF_PULL(顶部优先)。 + * adx=100 ≥ ady=70,且 t_dx=10 NOTIF_PULL", t, MEOW_EV_NOTIF_PULL, a, b); + assert(a == 5); /* e.a = 按下原点 Y(热区判定复核用) */ + + /* T2 顶部中央 竖直下滑(纯纵向):NOTIF_PULL */ + t = gesture_ev(240, 5, 240, 120, &a, &b); + expect("T2 top-center vertical down -> NOTIF_PULL", t, MEOW_EV_NOTIF_PULL, a, b); + + /* T3 顶部+左缘 下移 LEFT", t, MEOW_EV_SWIPE, a, b); + assert(a == MEOW_SWIPE_LEFT); + + /* T4 顶部+左缘 纯水平右滑:ady=0 → 非下拉 → LEFT */ + t = gesture_ev(5, 5, 110, 5, &a, &b); + expect("T4 top-left pure right -> LEFT", t, MEOW_EV_SWIPE, a, b); + assert(a == MEOW_SWIPE_LEFT); + + /* T5 中部(原点 y=100 ≥ 热区 28)大下滑:非 NOTIF_PULL → SWIPE DOWN(内容滚动不误判) */ + t = gesture_ev(240, 100, 240, 220, &a, &b); + expect("T5 mid down (origin below hotzone) -> SWIPE DOWN", t, MEOW_EV_SWIPE, a, b); + assert(a == MEOW_SWIPE_DOWN); + + /* T6 左缘中部 右滑返回(非热区):SWIPE LEFT */ + t = gesture_ev(5, 300, 110, 300, &a, &b); + expect("T6 left-edge mid right -> SWIPE LEFT", t, MEOW_EV_SWIPE, a, b); + assert(a == MEOW_SWIPE_LEFT); + + /* T7 顶部中央 大幅竖直下滑:NOTIF_PULL */ + t = gesture_ev(240, 5, 240, 90, &a, &b); + expect("T7 top-center vertical down (large) -> NOTIF_PULL", t, MEOW_EV_NOTIF_PULL, a, b); + + /* T8 SIGUSR1 护栏:meow_ui_init 已装无害处理器。raise(SIGUSR1)(等价 watchdog/测试路径 + * 泄漏进来)——若无护栏,默认动作会终止本进程(return 走不到下一行);护栏记日志+忽略 → 存活。 */ + raise(SIGUSR1); + printf("PASS T8 sigusr1-guard: survived raise(SIGUSR1)\n"); + + meow_ui_shutdown(); + if (g_fail) { printf("RESULT: FAIL\n"); return 1; } + printf("RESULT: ALL PASS\n"); + return 0; +} diff --git a/patch_v2/sdk/test_notif_open b/patch_v2/sdk/test_notif_open index 6e4a004..5ea8079 100755 Binary files a/patch_v2/sdk/test_notif_open and b/patch_v2/sdk/test_notif_open differ diff --git a/patch_v2/sdk/test_notify_integration b/patch_v2/sdk/test_notify_integration new file mode 100755 index 0000000..cb3e3be Binary files /dev/null and b/patch_v2/sdk/test_notify_integration differ diff --git a/patch_v2/sdk/test_notify_ipc b/patch_v2/sdk/test_notify_ipc new file mode 100755 index 0000000..a663107 Binary files /dev/null and b/patch_v2/sdk/test_notify_ipc differ diff --git a/patch_v2/sdk/test_pinyin b/patch_v2/sdk/test_pinyin index 143017a..ce9fedc 100755 Binary files a/patch_v2/sdk/test_pinyin and b/patch_v2/sdk/test_pinyin differ diff --git a/patch_v2/sdk/test_sb_collapse b/patch_v2/sdk/test_sb_collapse index 08d259d..02fe5c9 100755 Binary files a/patch_v2/sdk/test_sb_collapse and b/patch_v2/sdk/test_sb_collapse differ