Files
MeowOS-For-R3P2/patch_v2/sdk/test_meow_ui_home_gesture.c
T
AskaEth 837cd47e7c fix(sdk+launcher): M39 点击输入门 — 固定时长吞窗改为「启动排空 + 短系统手势防穿透」
根因(M36 已查实):不是布局竞态,而是固定时长的输入封闭窗口且窗口起点在首帧之后。
- SDK meow_ui_run:M2.22(C) 500ms 内丢弃 TAP/SWIPE/HOME/NOTIF_PULL/TOUCH_DOWN/MOVE/LONG_PRESS,
  窗口在 App 画完首帧后才 arm → 用户看到界面后第一下必被吞。
  改为入口 meow_input_drain() 排空一次(清环形队列 + 复位触摸状态机),
  TAP/TOUCH_DOWN/TOUCH_MOVE/LONG_PRESS 立即生效;仅系统手势(左缘 SWIPE、底部上滑 HOME、
  顶部下拉 NOTIF_PULL、SWIPE_PROGRESS)保留 STARTUP_GESTURE_GUARD_MS=150 防穿透。
- launcher:menu_after_app_exit 1200ms→150ms;子页返回 800ms→150ms;菜单/子页打开
  300ms→150ms(统一 MENU_TOUCH_GUARD_MS)。menu_drain_touch() 同时复位 t_down/原点/坐标:
  只清队列会把「按下已被 drain、抬手稍后才到」误判成一次 tap(幽灵重放的另一半)。
  历史理由已核对:旧窗口是为 Bug2/4(App 运行期事件堆积在 launcher 自身 touch_fd 里被重放)
  加的,真正的机制就是排空本进程 fd —— evdev 每 fd 各收一份,App/子页运行期全量事件都在
  这个 fd 里,drain 后无历史事件可重放;窗口只兜面板重配尾巴。
- 单测:test_meow_ui_home_gesture 新增 T4(11 断言:立即点击生效/立即上滑被守卫挡+守卫后生效/
  立即左缘 SWIPE 同理/TOUCH_DOWN+MOVE 立即下发/LONG_PRESS 立即下发/run 入口 drain 丢弃预置残留;
  旧 SDK 下 8 条红、新 SDK 30 pass/0 fail);test_meow_ui_dispatch 新增 M5(drain 清队列+复位在途
  手势+新点击不受影响,ALL PASS);swipe 35/0、view_event ALL PASS(回归)。
- 重编重链(铁律):meow_ui.a 8525aa6b(meow_ui_input.o 8248a6c6,旧 e54903ca)→
  meow_launcherd e342d24f(891164) / files 17edac0f(383072) / hello 65c1c575(499380) /
  ncm 1c2f5ff7(1225676) / reader a463645f(1716212,gitignore 不入库)。
  picbrowser 仓库内 0 个 tracked 文件(源码不存在)→ 无法重链,见 finding。
- 真机:未验证(纪律禁止覆盖设备已装组件;uinput 注入是 M36 设备失联诱因)。
2026-11-10 21:58:13 +08:00

328 lines
16 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* test_meow_ui_home_gesture.c — 批② SDK 全局「App 内底部热区上滑回桌面」宿主验证。
*
* 覆盖(并同 meow_ui_input_is_home_swipe 判据、touch_release 分类、meow_ui_run 分发三层):
* T1 判定矩阵(meow_ui_input_is_home_swipe,镜像 launcher np_gesture.h np_home_gesture):
* 底部热区上滑=1;底部左缘上滑=0(左缘返回互斥);中部上滑=0(App 滚动);底部横向=0;
* 底部下滑=0;上移不足=0;横向主导=0;屏底边界(down_y=fb_h-HOME_EDGE_H)之外=0。
* T2 触摸状态机分类(真实 touch_release → 队列 pop 断言):
* 底部热区上滑→MEOW_EV_HOME(a=MEOW_SWIPE_UP);中部上滑→MEOW_EV_SWIPE(UP)(不被剥回桌面);
* 底部左缘横滑→MEOW_EV_SWIPE(LEFT)(原左缘返回,不被剥)。
* T3 分发(meow_ui_run 主循环,run-loop 复用 test_meow_ui_view_event 的 driver 线程范式):
* 3a 未注册 on_swipe:注入 MEOW_EV_HOME → meow_ui_nav_request_exit 被触发(一次)——回桌面;
* 3b 未注册 on_swipe:注入 MEOW_EV_SWIPE(UP)(中部上滑)→ nav_request_exit 不触发(App 滚动);
* 3c 注册 on_swipe:注入 MEOW_EV_HOME → nav_request_exit 触发(系统级手势优先于 App on_swipe),
* on_swipe 不被调用——屏底热区上滑是系统保留手势,任意 App 内生效(用户 2026-09-04 批②)。
* T4 启动输入门(M39,取代 M2.22(C) 的 500ms 全量吞窗:启动 drain + ≤150ms 系统手势防穿透):
* 4a 启动后立即 TAP → on_tap 立即生效(旧窗口会吞,就是「要点两次」的根因);
* 4b 启动后立即 HOME → 守卫期不触发(防穿透);守卫过后再上滑 → 触发一次;
* 4c 启动后立即 SWIPE(LEFT) → 守卫期不下发;守卫过后 → 下发一次;
* 4d 启动后立即 TOUCH_DOWN / TOUCH_MOVE → 跟手回调立即收到;
* 4e 启动后立即 LONG_PRESS → 长按回调立即收到;
* 4f meow_ui_run 入口 drain → run 之前已入队的 TAP/HOME/SWIPE 全部丢弃(旧时间窗要解决的
* 「残留输入」改由一次性排空解决,且不影响 4a 的新输入)。
*
* 编译(宿主 gcc,链真实 SDK 源全集 + 打桩 font/global;**不链 meow_ui_nav.c**——本测以测试内
* meow_ui_nav_request_exit 桩计数验证触发):
* gcc -Wall -Wextra -o test_meow_ui_home_gesture test_meow_ui_home_gesture.c test_host_stub.c \
* meow_ui_gfx.c meow_ui_time.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_str.c \
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c meow_jpeg.c meow_png_stream.c \
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm -lz
* 运行:./test_meow_ui_home_gesture
*/
#include "meow_ui.h"
#include "meow_ui_internal.h" /* meow_input_touch_set/move/release + meow_input_pop + MEOW_EV_* */
#include "meow_ui_nav.h" /* meow_ui_nav_request_exit 原型(本测以桩计数验证) */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <assert.h>
/* MEOW_EV_HOME 是 meow_ui_input.c 内部常量(未入公共头,同 MEOW_EV_NOTIF_PULL 惯例),按同值声明。 */
#ifndef MEOW_EV_HOME
#define MEOW_EV_HOME 9
#endif
static int g_pass = 0, g_fail = 0;
#define CHECK(cond, ...) do { \
if (cond) { g_pass++; } \
else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \
} while (0)
/* fb 高度(宿主 480x720) */
#define FBW 480
#define FBH 720
/* 判据常量镜像(np_gesture.h NP_HOME_EDGE_H=28 / NP_HOME_SWIPE=65 / NP_EDGE_W=24)。 */
#define H_EDGE 28
#define H_SWIPE 65
#define EDGE_W 24
/* ---- nav_request_exit 桩(不链 meow_ui_nav.c;计数验证 SDK 触发) ---- */
static int g_nav_exit_calls = 0;
void meow_ui_nav_request_exit(void) { g_nav_exit_calls++; }
/* ---- on_swipe 桩(App 接管验证) ---- */
static int g_swipe_calls = 0;
static int g_swipe_dir = -1;
static int g_swipe_dx = 0;
static void app_on_swipe(int dir, int dx, int dy) { (void)dy; g_swipe_calls++; g_swipe_dir = dir; g_swipe_dx = dx; }
/* 从队列 pop 一个事件并回填 (type,a,b);无事件返回 0。 */
static int pop_ev(int *type, int *a, int *b)
{
meow_input_ev e;
if (!meow_input_pop(&e)) return 0;
*type = e.type; *a = e.a; *b = e.b;
return 1;
}
/* ============ T1 判定矩阵(meow_ui_input_is_home_swipe) ============ */
static void test_predicate(void)
{
printf("T1 meow_ui_input_is_home_swipe matrix\n");
int bz = FBH - H_EDGE; /* 底部热区上边界 y = 720-28 = 692 */
CHECK(meow_ui_input_is_home_swipe(240, bz + 10, 0, -120, FBH) == 1, "T1 bottom-hotzone up -> 1");
CHECK(meow_ui_input_is_home_swipe(100, FBH - 1, 20, -80, FBH) == 1, "T1 screen-bottom up (x>EDGE_W) -> 1");
CHECK(meow_ui_input_is_home_swipe(240, bz + 10, 0, -H_SWIPE, FBH) == 1, "T1 exactly threshold up -> 1");
CHECK(meow_ui_input_is_home_swipe(240, bz + 10, -30, -70, FBH) == 1, "T1 up w/ small left dx (adx<dy) -> 1");
/* 左缘互斥(down_x<EDGE_W) */
CHECK(meow_ui_input_is_home_swipe(10, bz + 10, 0, -120, FBH) == 0, "T1 left-edge origin -> 0 (edge-back reserved)");
/* 中部上滑(App 滚动) */
CHECK(meow_ui_input_is_home_swipe(240, 300, 0, -120, FBH) == 0, "T1 mid-screen up -> 0 (App scroll)");
/* 底部横向(左缘返回/横向逻辑保留) */
CHECK(meow_ui_input_is_home_swipe(240, bz + 10, 120, 0, FBH) == 0, "T1 bottom horizontal -> 0");
/* 底部下滑 */
CHECK(meow_ui_input_is_home_swipe(240, bz + 10, 0, 120, FBH) == 0, "T1 bottom down -> 0");
/* 上移不足(<H_SWIPE) */
CHECK(meow_ui_input_is_home_swipe(240, bz + 10, 0, -(H_SWIPE - 1), FBH) == 0, "T1 up below threshold -> 0");
/* 竖向未主导(横向位移≥上移量,adx>=dy) */
CHECK(meow_ui_input_is_home_swipe(240, bz + 10, -120, -80, FBH) == 0, "T1 up w/ big dx (not vertical-dominant) -> 0");
/* 热区边界之外(down_y < bz) */
CHECK(meow_ui_input_is_home_swipe(240, bz - 1, 0, -120, FBH) == 0, "T1 just above hotzone -> 0");
}
/* ============ T2 触摸状态机分类(touch_release → 队列) ============ */
static void test_touch_classify(void)
{
printf("T2 touch_release classification\n");
meow_input_drain();
/* 底部热区上滑 → MEOW_EV_HOME */
meow_input_touch_set(1, 240, FBH - 5, 0); /* 起点 y=715 ∈ [692,720) */
meow_input_touch_move(240, FBH - 5 - 130); /* 上移 130 */
meow_input_touch_release();
{
int t, a, b;
CHECK(pop_ev(&t, &a, &b) == 1 && t == MEOW_EV_HOME && a == MEOW_SWIPE_UP,
"T2 bottom-hotzone up -> MEOW_EV_HOME(UP) got type=%d a=%d", t, a);
}
/* 中部上滑 → 普通 MEOW_EV_SWIPE(UP)(不被剥回桌面) */
meow_input_touch_set(1, 240, 300, 0);
meow_input_touch_move(240, 300 - 130);
meow_input_touch_release();
{
int t, a, b;
CHECK(pop_ev(&t, &a, &b) == 1 && t == MEOW_EV_SWIPE && a == MEOW_SWIPE_UP,
"T2 mid up -> SWIPE(UP) got type=%d a=%d", t, a);
}
/* 底部左缘横滑 → 原有左缘返回(不被剥) */
meow_input_touch_set(1, 5, FBH - 5, 0);
meow_input_touch_move(5 + 100, FBH - 5);
meow_input_touch_release();
{
int t, a, b;
CHECK(pop_ev(&t, &a, &b) == 1 && t == MEOW_EV_SWIPE && a == MEOW_SWIPE_LEFT,
"T2 bottom-left horizontal -> SWIPE(LEFT) got type=%d a=%d", t, a);
}
}
/* ============ T3 分发(meow_ui_run 主循环) ============ */
static int g_inj_type, g_inj_a, g_inj_b;
static void *driver(void *arg)
{
(void)arg;
usleep(700000); /* 等启动守卫(M39: 150ms,原 500ms cooldown)过后再注入 */
meow_input_inject(g_inj_type, g_inj_a, g_inj_b);
usleep(80000);
meow_ui_request_exit();
return NULL;
}
static void run_scenario(int inj_type, int inj_a, int inj_b, int register_swipe)
{
g_inj_type = inj_type; g_inj_a = inj_a; g_inj_b = inj_b;
g_nav_exit_calls = 0;
g_swipe_calls = 0; g_swipe_dir = -1; g_swipe_dx = 0;
meow_input_drain(); /* 防上一次残留事件影响本次分发 */
meow_ui_events ev; memset(&ev, 0, sizeof ev);
if (register_swipe) ev.on_swipe = app_on_swipe;
pthread_t thd; pthread_create(&thd, NULL, driver, NULL);
meow_ui_run(&ev);
pthread_join(thd, NULL);
}
static void test_dispatch(void)
{
printf("T3 meow_ui_run dispatch (home = nav_request_exit fallback)\n");
/* 3a 未注册 on_swipe:底部热区上滑 → nav_request_exit 触发一次(回桌面) */
run_scenario(MEOW_EV_HOME, MEOW_SWIPE_UP, 120, 0);
CHECK(g_nav_exit_calls == 1, "T3a no-on_swipe MEOW_EV_HOME -> nav_request_exit fired (got %d)", g_nav_exit_calls);
CHECK(g_swipe_calls == 0, "T3a no-on_swipe -> on_swipe NOT called");
/* 3b 未注册 on_swipe:中部上滑(普通 MEOW_EV_SWIPE UP)→ nav_request_exit 不触发(App 滚动) */
run_scenario(MEOW_EV_SWIPE, MEOW_SWIPE_UP, 120, 0);
CHECK(g_nav_exit_calls == 0, "T3b mid-swipe-up -> nav_request_exit NOT fired (App scroll), got %d", g_nav_exit_calls);
/* 3c 注册 on_swipe:底部热区上滑 → 系统优先,nav_request_exit 触发(回桌面),on_swipe 不被调用 */
run_scenario(MEOW_EV_HOME, MEOW_SWIPE_UP, 120, 1);
CHECK(g_nav_exit_calls == 1, "T3c on_swipe registered -> nav_request_exit STILL fired (system gesture wins), got %d", g_nav_exit_calls);
CHECK(g_swipe_calls == 0, "T3c system-priority home -> on_swipe NOT called");
}
/* ============ T4 启动输入门(M39) ============
* 语义:meow_ui_run 入口 meow_input_drain() 排空一次(清队列 + 复位触摸状态机),随后只对
* 「系统手势」(SWIPE / HOME / NOTIF_PULL / SWIPE_PROGRESS)保留 ≤150ms 防穿透;
* TAP / TOUCH_DOWN / TOUCH_MOVE / LONG_PRESS 立即生效。 */
#define T4_GUARD_WAIT_MS 260 /* > STARTUP_GESTURE_GUARD_MS(150),留调度余量 */
static volatile int g_ticks = 0; /* run 循环 tick 计数(注入时序锚点) */
static void tick_marker(void) { g_ticks++; }
static int g_taps = 0, g_tap_x = -1, g_tap_y = -1;
static void tap_marker(int x, int y) { g_taps++; g_tap_x = x; g_tap_y = y; }
static int g_downs = 0, g_moves = 0;
static void down_marker(int x, int y) { (void)x; (void)y; g_downs++; }
static void move_marker(int x, int y) { (void)x; (void)y; g_moves++; }
static int g_longs = 0;
static void long_marker(int x, int y) { (void)x; (void)y; g_longs++; }
static int g_inj2_type, g_inj2_a, g_inj2_b, g_inj_later;
/* 首个 tick(run 循环已进入、run 入口 drain 已过)注入一次;g_inj_later=1 时越过守卫再注入一次。 */
static void *drv_startup(void *arg)
{
(void)arg;
while (g_ticks < 1) usleep(1000);
meow_input_inject(g_inj_type, g_inj_a, g_inj_b);
if (g_inj_later) {
usleep(T4_GUARD_WAIT_MS * 1000);
meow_input_inject(g_inj2_type, g_inj2_a, g_inj2_b);
}
usleep(80000);
meow_ui_request_exit();
return NULL;
}
static void run_startup_scenario(int type, int a, int b, int later,
void (*tap_cb)(int, int), void (*swipe_cb)(int, int, int),
void (*td_cb)(int, int), void (*tm_cb)(int, int))
{
g_inj_type = type; g_inj_a = a; g_inj_b = b;
g_inj2_type = type; g_inj2_a = a; g_inj2_b = b;
g_inj_later = later;
g_ticks = 0; g_taps = 0; g_tap_x = g_tap_y = -1;
g_downs = 0; g_moves = 0; g_longs = 0;
g_nav_exit_calls = 0;
g_swipe_calls = 0; g_swipe_dir = -1; g_swipe_dx = 0;
meow_input_drain();
meow_ui_events ev; memset(&ev, 0, sizeof ev);
ev.on_tick = tick_marker;
ev.on_tap = tap_cb;
ev.on_swipe = swipe_cb;
ev.on_touch_down = td_cb;
ev.on_touch_move = tm_cb;
pthread_t thd; pthread_create(&thd, NULL, drv_startup, NULL);
meow_ui_run(&ev);
pthread_join(thd, NULL);
}
/* run 之前已入队(模拟「进入 App 前就堆积的残留输入」),run 入口 drain 必须全部丢掉。 */
static void *drv_predrain(void *arg)
{
(void)arg;
usleep(T4_GUARD_WAIT_MS * 1000); /* 越过守卫:证明不是被窗口吞,而是被 drain 清掉 */
meow_ui_request_exit();
return NULL;
}
static void run_predrain_scenario(void)
{
g_taps = 0; g_nav_exit_calls = 0; g_swipe_calls = 0;
meow_input_drain();
meow_input_inject(MEOW_EV_TAP, 120, 200);
meow_input_inject(MEOW_EV_HOME, MEOW_SWIPE_UP, 120);
meow_input_inject(MEOW_EV_SWIPE, MEOW_SWIPE_LEFT, 80);
meow_ui_events ev; memset(&ev, 0, sizeof ev);
ev.on_tap = tap_marker;
ev.on_swipe = app_on_swipe;
pthread_t thd; pthread_create(&thd, NULL, drv_predrain, NULL);
meow_ui_run(&ev);
pthread_join(thd, NULL);
}
static void test_startup_gate(void)
{
printf("T4 startup input gate (drain + <=150ms system-gesture guard)\n");
/* 4a 启动后立即点击:必须生效(旧 500ms 窗口在首帧后 arm → 会吞掉这一下) */
run_startup_scenario(MEOW_EV_TAP, 120, 200, 0, tap_marker, NULL, NULL, NULL);
CHECK(g_taps == 1, "T4a immediate TAP after start -> on_tap fired (got %d)", g_taps);
CHECK(g_taps == 1 && g_tap_x == 120 && g_tap_y == 200, "T4a tap coords preserved (%d,%d)", g_tap_x, g_tap_y);
/* 4b 启动后立即上滑回桌面:守卫期不触发;守卫过后同一手势触发一次 */
run_startup_scenario(MEOW_EV_HOME, MEOW_SWIPE_UP, 120, 1, NULL, NULL, NULL, NULL);
CHECK(g_nav_exit_calls == 1, "T4b immediate HOME blocked by guard, post-guard HOME fires exactly once (got %d)", g_nav_exit_calls);
/* 4c 启动后立即左缘返回 SWIPE:守卫期不下发;守卫过后下发一次(行为与修改前一致) */
run_startup_scenario(MEOW_EV_SWIPE, MEOW_SWIPE_LEFT, 80, 1, NULL, app_on_swipe, NULL, NULL);
CHECK(g_swipe_calls == 1, "T4c immediate SWIPE(LEFT) blocked by guard, post-guard delivers exactly once (got %d)", g_swipe_calls);
CHECK(g_swipe_dir == MEOW_SWIPE_LEFT, "T4c swipe dir == LEFT (got %d)", g_swipe_dir);
/* 4d 启动后立即跟手拖动:DOWN/MOVE 立即下发(不再被启动窗口吞) */
run_startup_scenario(MEOW_EV_TOUCH_DOWN, 240, 300, 0, NULL, NULL, down_marker, move_marker);
CHECK(g_downs == 1, "T4d immediate TOUCH_DOWN delivered (got %d)", g_downs);
run_startup_scenario(MEOW_EV_TOUCH_MOVE, 240, 350, 0, NULL, NULL, down_marker, move_marker);
CHECK(g_moves == 1, "T4d immediate TOUCH_MOVE delivered (got %d)", g_moves);
/* 4e 启动后立即长按:长按回调立即收到(长按本身需按住达阈值,启动窗口门控已移除) */
meow_ui_set_long_press_cb(long_marker);
run_startup_scenario(MEOW_EV_LONG_PRESS, 200, 420, 0, NULL, NULL, NULL, NULL);
CHECK(g_longs == 1, "T4e immediate LONG_PRESS delivered (got %d)", g_longs);
meow_ui_set_long_press_cb(NULL);
/* 4f run 入口 drain:进入前堆积的 TAP/HOME/SWIPE 全部丢弃(替代旧时间窗的防残留语义) */
run_predrain_scenario();
CHECK(g_taps == 0, "T4f pre-queued TAP drained at run entry (got %d)", g_taps);
CHECK(g_nav_exit_calls == 0, "T4f pre-queued HOME drained at run entry (got %d)", g_nav_exit_calls);
CHECK(g_swipe_calls == 0, "T4f pre-queued SWIPE drained at run entry (got %d)", g_swipe_calls);
}
int main(void)
{
meow_ui_opts o = { FBW, FBH };
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
test_predicate();
test_touch_classify();
test_dispatch();
test_startup_gate();
meow_ui_shutdown();
printf("home_gesture: %d pass / %d fail\n", g_pass, g_fail);
return g_fail ? 1 : 0;
}