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 设备失联诱因)。
This commit is contained in:
AskaEth committed 2026-11-10 21:58:13 +08:00
1 parent 46278fd051
commit 837cd47e7c
11 files changed
+256 -52

No files matched your search

+39 -20
View File
@@ -117,10 +117,23 @@ static volatile int g_exit = 0;
static meow_ui_events g_events;
static int g_events_set = 0;
static uint64_t last_tick_ms = 0;
/* M2.22(C):启动 cooldown。应用刚进入时,用户点列表那几下(BTN_TOUCH 残留/抬手尾巴)
* 可能被当作「左缘滑动」→ 立即返回。窗口期丢弃 SWIPE(尤其 LEFT),仍接受 TAP。 */
static uint64_t startup_cooldown_until = 0;
#define STARTUP_COOLDOWN_MS 500
/* M39:启动输入门 = 一次性排空 + 极短「系统手势」防穿透(取代 M2.22(C) 的 500ms 全量吞窗)。
*
* 旧实现从 meow_ui_run 入口起 500ms 内丢弃 TAP/SWIPE/HOME/NOTIF_PULL/TOUCH_DOWN/MOVE/LONG_PRESS。
* 窗口起点在 App 画完首帧之后(reader/files 都是先 page_push 再 run),用户看到界面后伸手的
* 第一下必然落在窗口里被吞 → launcher/reader「要点两次」、files 概率、ncm/picbrowser 因首屏
* 更晚(LOADING/缩略图)点得晚才稳定成功。
*
* 残留输入的真实来源只有一处:evdev fd 在 input 线程启动时才 open,App 之前根本收不到;
* 能进队列的只有「fd 打开 → 进入分发」之间极短的尾巴 —— meow_input_drain() 一次性清掉即可
* (清环形队列 + 复位触摸状态机,见 meow_input_drain)。故:
* - TAP / TOUCH_DOWN / TOUCH_MOVE / LONG_PRESS 不再按时间吞(用户第一下立即生效);
* - 系统手势(左缘返回 SWIPE、底部上滑回桌面 HOME、顶部下拉通知中心、滑动进度条)保留
* STARTUP_GESTURE_GUARD_MS 防穿透:启动瞬间的幽灵手势不得把刚进入的 App 直接弹回桌面。
* 跨启动的在途手势仍不会产生 TAP:fd 打开前发生的按下 App 看不到(t_down=0),其抬手被
* touch_release 的首行 !t_down 早退丢弃。 */
static uint64_t startup_gesture_guard_until = 0;
#define STARTUP_GESTURE_GUARD_MS 150
/* settings.conf 路径(可被 env MEOW_SETTINGS_FILE 覆盖,宿主测试用;设备默认同 meow_ui_sys.c)。 */
static const char *meow_settings_path(void)
@@ -708,19 +721,23 @@ void meow_ui_run(const meow_ui_events *ev)
if (ev) { g_events = *ev; g_events_set = 1; }
g_exit = 0;
meow_input_start();
/* M39:启动排空一次(清掉 evdev fd 打开到进入分发之间堆积的事件尾巴 + 复位触摸状态机),
* 取代 M2.22(C) 的 500ms 按时间吞输入窗口。窗口只保留给系统手势(防穿透),见下。 */
meow_input_drain();
last_tick_ms = meow_ui_time_ms();
startup_cooldown_until = meow_ui_time_ms() + STARTUP_COOLDOWN_MS; /* M2.22(C):启动窗口丢弃 SWIPE */
startup_gesture_guard_until = meow_ui_time_ms() + STARTUP_GESTURE_GUARD_MS;
while (!g_exit) {
/* agent-119:实时触摸按压/按住/释放 → 即时重绘命中按钮区(不等 app 重绘)。
* IME 打开时软键盘为覆盖层且拦截触摸,不驱动背后按钮视觉,跳过。
* §6.3 通用长按:每帧检测「按下 ≥ 阈值且位移 ≤ 容忍」跨越(IME 打开不驱动;启动窗口不触发)。 */
* §6.3 通用长按:每帧检测「按下 ≥ 阈值且位移 ≤ 容忍」跨越(IME 打开不驱动)。
* M39:长按不再受启动窗口门控(长按需要按住 ≥ 阈值,启动残留手势已由 drain 清掉)。 */
if (!meow_ime_active()) {
meow_widget_touch_refresh();
if (meow_ui_time_ms() >= startup_cooldown_until) meow_input_long_press_poll();
meow_input_long_press_poll();
} else if (meow_ime_mode_pinyin()) {
/* 拼音 IME 打开:喂长按轮询(长按退格清空字段)。英文模式不喂(保持既有行为)。 */
if (meow_ui_time_ms() >= startup_cooldown_until) meow_input_long_press_poll();
meow_input_long_press_poll();
}
meow_input_ev e;
@@ -729,10 +746,10 @@ void meow_ui_run(const meow_ui_events *ev)
if (g_exit) break; /* 已请求退出:不再分发剩余事件,杜绝「退出后又触发」的偶发再进/退 */
got = 1;
meow_ui_activity_ping(); /* M2.71-bug5:任意交互事件都喂 launcher 重置息屏计时(节流) */
int in_startup = (meow_ui_time_ms() < startup_cooldown_until); /* M2.22(C) */
int in_gesture_guard = (meow_ui_time_ms() < startup_gesture_guard_until); /* M39 */
if (e.type == MEOW_EV_TAP) {
if (in_startup) continue; /* 启动窗口也吞 TAP:旧手势残留点按(如上一次左滑抬起)
* 在进入瞬间命中 UI 按钮(如全屏按钮)会误触发——丢弃。 */
/* M39:TAP 不再按时间吞——用户看到界面后的第一下立即生效(旧 500ms 窗口在
* 首帧之后 arm,正是「要点两次」的根因;残留手势已在 run 入口 drain)。 */
/* 抬手未达标:清视觉条 */
meow_ui_swipe_hint(-1);
meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6);
@@ -754,7 +771,8 @@ void meow_ui_run(const meow_ui_events *ev)
if (meow_view_dispatch_event(MEOW_EV_KEY, e.a, e.b)) continue;
if (g_events_set && g_events.on_key) g_events.on_key(e.a, e.b);
} else if (e.type == MEOW_EV_SWIPE) {
if (in_startup) continue; /* 启动残留被当左缘滑动:丢弃,防进入即返回 */
if (in_gesture_guard) continue; /* M39:仅系统手势防穿透(启动瞬间幽灵手势不得把
* 刚进入的 App 直接弹回桌面);TAP 已不在此门控。 */
/* 抬手达标:清视觉条(左滑返回),触发行消失 */
meow_ui_swipe_hint(-1);
meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6);
@@ -784,7 +802,7 @@ void meow_ui_run(const meow_ui_events *ev)
* 批① meow_bg_add 保活,进程保留转后台、SDK present 自抑);**不**调
* meow_ui_request_exit()——那会断送保活(进程 exit(0))。launcher 据 app 声明
* (background_keepalive/background_audio+媒体活跃)决定无限期/宽限。 */
if (in_startup) continue; /* 启动窗口误触残留:丢弃 */
if (in_gesture_guard) continue; /* M39:系统手势防穿透(与 SWIPE 同窗口) */
meow_ui_swipe_hint(-1);
meow_ui_gesture_end(); /* §6.4:抬手边沿,释放手势捕获 */
/* IME 打开时软键盘覆盖底部:键盘拥有底部区,把事件交给 IME 且**绝不**回桌面
@@ -792,7 +810,7 @@ void meow_ui_run(const meow_ui_events *ev)
if (meow_ime_active()) { (void)meow_ime_handle_event(&e); continue; }
meow_ui_nav_request_exit();
} else if (e.type == MEOW_EV_NOTIF_PULL) {
if (in_startup) continue; /* 启动窗口顶区误触残留:丢弃,防进入即开通知中心 */
if (in_gesture_guard) continue; /* M39:系统手势防穿透(防进入即开通知中心) */
/* 系统级「下拉展开通知中心」:起始于顶部热区的下滑为系统保留手势,不下发
* APP on_swipe;路由到系统钩子(App 进程只检测+经 IPC 上报 MSG_NOTIF_OPEN,
* 通知中心由 launcher 渲染,见 FRAMEWORK_DESIGN §10.5)。
@@ -803,10 +821,11 @@ void meow_ui_run(const meow_ui_events *ev)
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 弹出时触摸优先喂键盘(键盘覆盖层拦截,
* on_touch_* 丢弃,避免按到键盘却在背后控件上锁拖拽);启动窗口也吞(防残留点按
* 误锁拖拽)。模态(dialog/sheet)期间本主循环被阻塞,天然不在此分发。
* on_touch_* 丢弃,避免按到键盘却在背后控件上锁拖拽)。模态(dialog/sheet)期间
* 本主循环被阻塞,天然不在此分发。
* M39:不再受启动窗口门控——跟手拖动是用户可感知的第一下交互;启动残留点按
* 已由 run 入口 meow_input_drain()(含触摸状态机复位)清掉,不会锁错拖拽。
* IME 打开时先把压点喂给拼音(候选栏滑动翻页判定 g_ime_swipe_y),再丢弃。 */
if (in_startup) continue;
if (meow_ime_active()) { meow_ime_handle_event(&e); continue; }
/* §6.4:down 捕获——命中目标捕获整个手势直到 up;随后 move/up 只发给该目标
* (防滑出后误触发下层)。真实触发放置捕获(宿主直调 on_touch_* 不受影响)。 */
@@ -817,7 +836,7 @@ void meow_ui_run(const meow_ui_events *ev)
g_events.on_touch_move(e.a, e.b);
got = 1; /* 交互事件:不睡眠,保持跟手帧同步 */
} else if (e.type == MEOW_EV_SWIPE_PROGRESS) {
if (in_startup) continue; /* 启动窗口进度条静音 */
if (in_gesture_guard) continue; /* M39:系统手势防穿透(启动进度条静音) */
int x0 = e.a, xc = e.b, dx = xc - x0; /* dx=当前X-按下X */
/* SDK 自动画顶部条:长度=手指当前 X(=距左缘距离,向内划长度随位移增长)。
* 全屏/收合下「左缘返回手势保留」,顶部动态提示条(meow_ui_swipe_hint)照常绘制
@@ -833,8 +852,8 @@ void meow_ui_run(const meow_ui_events *ev)
} else if (e.type == MEOW_EV_LONG_PRESS) {
/* §6.3 通用长按:按下 ≥ 阈值未抬手触发。坐标=触发时刻按下点(App 用
* meow_widget_hit/meow_listview_hit 判定命中控件/行)。已注册回调则分发;
* 其抬手已被 touch_release 抑制(不再产生 TAP/SWIPE),此处无需吞后续 tap。 */
if (in_startup) continue;
* 其抬手已被 touch_release 抑制(不再产生 TAP/SWIPE),此处无需吞后续 tap。
* M39:不再受启动窗口门控(长按需按住达阈值,残留手势已由 drain 清掉)。 */
/* 拼音 IME 打开:长按退格清空字段(英文模式 handle_event 返回 0 → 走既有长按回调)。 */
if (meow_ime_active() && meow_ime_handle_event(&e)) continue;
meow_ui_gesture_end(); /* §6.4:长按已触发,其抬手不再 TAP/SWIPE——顺手释放捕获 */