Files
MeowOS-For-R3P2/patch_v2/sdk/meow_ui_input.c
T

524 lines
27 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.
/*
* meow_ui_input.c — 输入层(B1.4)。
*
* 建立输入线程,读 /dev/input/event*(event1=触摸 goodix-ts、event0=物理键
* md-gpio-keys、event2=ADC 键),翻译成语义事件(KEY/TAP/SWIPE)压入环形队列;
* meow_ui_run / IME 循环弹出分发。应用不再自读 event*。
*
* 触摸:BTN_TOUCH 按下记录原点,抬起时位移 < 阈值 → TAP,否则按主导轴 → SWIPE。
* 物理键:EV_KEY 值 1=按下 / 0=抬起(重复值 2 忽略)。M2.24 全面按键接管:电源/音量/播放/
* 上下曲全部由系统(watchdog/launcher)消费,SDK 吞噬不发给 APP(应用只收 tap/swipe/tick)。
*/
#include "meow_ui.h"
#include "meow_ui_internal.h"
#include "meow_ui_sys.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <poll.h>
#include <pthread.h>
/* linux input 事件常量(本设备实测) */
#define EV_SYN 0
#define EV_KEY 1
#define EV_ABS 3
#define BTN_TOUCH 0x14a
#define ABS_X 0x00
#define ABS_Y 0x01
#define ABS_MT_POSITION_X 0x35
#define ABS_MT_POSITION_Y 0x36
#define SWIPE_THRESH 65 /* px:抬起位移超过视为滑动(GESTURE_BACK: 90→65,用户实测 90 偏多,65 更顺手) */
#define EDGE_W 24 /* 左滑返回的「左缘起点」宽度(t_dx < EDGE_W 才认返回手势) */
#define MEOW_LONG_PRESS_MS 500u /* §6.3 通用长按默认阈值(ms;meow_ui_set_long_press_ms 可配) */
#define MEOW_LONG_PRESS_SLOP 12 /* §6.3 按下后「仍算按住」的最大位移(px);超此取消长按 */
#define QUEUE_N 128
#define MAX_DEVS 8
#define MEOW_APP_ACTIVITY_FILE "/tmp/meow_app_activity" /* M2.71-bug5:App 活动→launcher 重置息屏计时 */
/* ── 内部事件:系统级「下拉展开通知中心」(M2.5x 接线)──
* 触摸状态机在抬手判定处识别「SWIPE_DOWN 起始于顶部热区」后压入;事件循环按系统手势处理
* (路由 meow_ui_sys_notif_pull),不下发给 APP 的 on_swipe(顶部热区为系统保留手势)。
* 与左缘返回(SWIPE_LEFT)/ meow_ui_swipe_hint 互不影响——后者仍全屏生效,详见下方触摸机。 */
#define MEOW_EV_NOTIF_PULL 5
/* 顶部下滑 → 通知中心判定:纵向 DOWN 手势的起点(origin_y)是否落在顶部热区内。
* 热区由 meow_ui_statusbar_hotzone() 给出(非全屏=状态栏高、全屏=MEOW_SBAR_HOTZONE_FS、
* 收合=提示线高)。供宿主单测直调(判据与触摸状态机共用)。 */
int meow_ui_input_is_notif_pull(int origin_y, int hotzone)
{
return origin_y >= 0 && origin_y < hotzone;
}
static int g_thread_started = 0;
static pthread_t g_thread;
static volatile int g_thread_run = 1;
/* 环形队列(互斥保护) */
static meow_input_ev g_q[QUEUE_N];
static volatile int g_qh = 0, g_qt = 0;
static pthread_mutex_t g_qm = PTHREAD_MUTEX_INITIALIZER;
/* 事件循环状态 */
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
static uint64_t now_ms(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000u + (uint64_t)ts.tv_nsec / 1000000u;
}
void meow_input_drain(void)
{
pthread_mutex_lock(&g_qm);
g_qh = 0; g_qt = 0;
pthread_mutex_unlock(&g_qm);
}
static void push_ev(int type, int a, int b)
{
pthread_mutex_lock(&g_qm);
int n = (g_qt + 1) % QUEUE_N;
if (n == g_qh) { /* 满:丢弃 */
pthread_mutex_unlock(&g_qm);
return;
}
g_q[g_qt].type = (uint8_t)type; g_q[g_qt].a = a; g_q[g_qt].b = b;
g_qt = n;
pthread_mutex_unlock(&g_qm);
}
int meow_input_pop(meow_input_ev *ev)
{
if (!ev) return 0;
pthread_mutex_lock(&g_qm);
if (g_qh == g_qt) { pthread_mutex_unlock(&g_qm); return 0; }
*ev = g_q[g_qh];
g_qh = (g_qh + 1) % QUEUE_N;
pthread_mutex_unlock(&g_qm);
return 1;
}
void meow_input_inject(int type, int a, int b)
{
push_ev(type, a, b);
}
/* ---- 触摸状态机(M2.22 B:手势级干净起点 + 原点严格锁定,杜绝残留坐标污染)----
* 背景:goodix 事件时序不保证 BTN_TOUCH 与 ABS 的先后;旧版在按下边沿用 t_cx/t_cy
* 立即锁原点,而 t_cx/t_cy 是上一手势的残留 —— 上一次「中部 tap」留下的坐标被本次
* 按下当原点(>EDGE_W),导致后续左缘侧滑不返回、进度条不出。且进程内不自愈。
* 修法:每次抬手彻底清空全部轨迹状态;原点只在「本次按下之后」出现的首个有效 ABS
* 锁定(touch_update,gated 于 t_down && !t_origin_valid && x>=0 && y>=0)。 */
static int t_down = 0;
static int t_press_valid = 0; /* BTN_TOUCH=1 已见(本次按下的 gate),等首个有效 ABS 才锁原点 */
static int t_dx = 0, t_dy = 0; /* 按下原点(BTN_TOUCH 后首个有效 ABS 锁定) */
static int t_cx = 0, t_cy = 0; /* 当前坐标(-1=本次尚未见到有效坐标) */
static int t_origin_valid = 0; /* 原点是否已锁定 */
static uint64_t t_down_ms = 0; /* 按下时刻(monotonic ms),供实时按住时长用 */
/* M2.71-bug2/6:拖动过程事件上次上报坐标(坐标变化才入队 MEOW_EV_TOUCH_MOVE,防刷屏)。 */
static int t_mv_x = -1, t_mv_y = -1;
/* §6.3 通用长按:本次按压是否已触发过长按(触发后抬手不再产生 TAP/SWIPE)。 */
static int t_long_fired = 0;
/* §6.3 通用长按:阈值(默认 MEOW_LONG_PRESS_MS;App 可经 meow_ui_set_long_press_ms 配)。 */
static int g_long_press_ms = MEOW_LONG_PRESS_MS;
/* §6.3 注册的长按回调(NULL=未注册;触发时以 (x,y) 调用,坐标=触发时刻按下点)。 */
static meow_long_press_fn g_long_press_cb = NULL;
/* 按下边沿:只标记按下,绝不用传入坐标立即锁原点(避免用上一手势残留坐标当原点)。
* t_cx/t_cy 归 -1 —— 本次手势以「无坐标」起步,只接受随后到来的有效 ABS。 */
static void touch_press(int x, int y)
{
(void)x; (void)y;
t_down = 1;
t_press_valid = 1;
t_origin_valid = 0;
t_cx = -1; t_cy = -1;
t_dx = 0; t_dy = 0;
t_down_ms = now_ms(); /* 记按下时刻:供实时按住时长/按住视觉 */
t_mv_x = -1; t_mv_y = -1; /* M2.71:拖动过程起点未定 */
t_long_fired = 0; /* §6.3:每次新按压复位长按触发标记 */
}
/* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。
* M2.71-bug2/6:原点锁定即报 MEOW_EV_TOUCH_DOWN(拖动起点);随后坐标变化报 MEOW_EV_TOUCH_MOVE
* (坐标变了才报,省队列)。这两类事件供 slider 拖动 / 列表&页面滚动跟手。 */
static void touch_update(int x, int y)
{
if (t_down && !t_origin_valid && x >= 0 && y >= 0) {
t_dx = x; t_dy = y; t_origin_valid = 1;
t_mv_x = x; t_mv_y = y;
push_ev(MEOW_EV_TOUCH_DOWN, x, y);
}
if (x >= 0) t_cx = x;
if (y >= 0) t_cy = y;
if (t_down && t_origin_valid && t_cx >= 0 && t_cy >= 0 &&
(t_cx != t_mv_x || t_cy != t_mv_y)) {
t_mv_x = t_cx; t_mv_y = t_cy;
push_ev(MEOW_EV_TOUCH_MOVE, t_cx, t_cy);
}
}
static void touch_release(void)
{
if (!t_down) return;
t_down = 0; t_press_valid = 0;
int was_long = t_long_fired; /* §6.3:本次按压是否已触发过长按(抬手不再产生点击/滑动) */
t_long_fired = 0;
/* 本次按下从未锁到有效坐标(幽灵触摸/坐标未就绪):丢弃,且清空全部轨迹。 */
if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; t_mv_x = t_mv_y = -1; return; }
t_origin_valid = 0;
int mdx = t_cx - t_dx, mdy = t_cy - t_dy;
int adx = mdx < 0 ? -mdx : mdx, ady = mdy < 0 ? -mdy : mdy;
/* 长按已触发:本次手势由长按消费,抬手不再产生 TAP/SWIPE/NOTIF_PULL(抑制点击,§6.3)。 */
if (was_long) {
t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; t_mv_x = t_mv_y = -1;
return;
}
/* 手势判定只在「抬手(release)边沿」做:按下/移动过程不触发 SWIPE,仅报 PROGRESS。
*
* 返回手势("左滑"语义):原点确实在左缘 + 向右移动 ≥SWIPE_THRESH。设备实际为
* 「左缘向内(右)划」≥65px(M2.21:90→65 用户实测更顺手;与 watchdog 兜底一致)。
* 原点 t_dx 是本次按下后首个有效 ABS 锁定的真实按下点(非残留),且 t_cx/t_dx 已
* 被手势级清空,绝不继承上一手势坐标,确保:
* ① 中部右滑不会误判成左缘返回(t_dx>=EDGE_W);
* ② 先点中部再侧滑不返回(原点锁定在中部,>=EDGE_W),符合「必须从左缘起划」;
* ③ 右移严格正向(t_cx-t_dx>=0),杜绝「向左划也出条/误返回」。
* SDK 统一上报为 MEOW_SWIPE_LEFT(设计"左滑=返回"契约,hello 用 on_swipe(LEFT) 退出)。
*
* 屏幕中部滑动不是返回手势:中部右滑→RIGHT(内容导航),中部左滑→非返回(落 TAP,
* 避免任意处左滑误退出)。纵向滑动→UP/DOWN 照旧。 */
if (adx >= SWIPE_THRESH || ady >= SWIPE_THRESH) {
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) {
push_ev(MEOW_EV_SWIPE, MEOW_SWIPE_RIGHT, adx); /* 中部右滑:内容 */
} else {
push_ev(MEOW_EV_TAP, t_cx, t_cy); /* 中部左滑:非返回 */
}
} 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);
}
} else {
push_ev(MEOW_EV_TAP, t_cx, t_cy);
}
/* 手势级干净起点:清空全部轨迹,下一手势从零开始,杜绝污染无自愈。 */
t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; t_mv_x = t_mv_y = -1;
}
/* ---- 实时触摸态(内部:控件绘制按压/按住视觉用)----
* 直接读触摸状态机当前值(与语义事件队列无关;无按下/坐标未就绪时 x/y 返回 -1)。 */
int meow_input_touch_down(void) { return t_down; }
int meow_input_touch_x(void) { return t_cx; }
int meow_input_touch_y(void) { return t_cy; }
unsigned int meow_input_touch_hold_ms(void)
{
if (!t_down) return 0;
uint64_t e = now_ms() - t_down_ms;
if (e > 0xFFFFFFFFu) e = 0xFFFFFFFFu;
return (unsigned int)e;
}
/* 宿主单测/内部注入:模拟按压(down=1 时 x,y=压点、hold_ms=已按住时长),设备运行时无调用方。 */
void meow_input_touch_set(int down, int x, int y, unsigned hold_ms)
{
if (down) {
t_down = 1; t_press_valid = 1; t_origin_valid = 1; t_long_fired = 0;
t_cx = x; t_cy = y; t_dx = x; t_dy = y;
uint64_t now = now_ms();
t_down_ms = (now > (uint64_t)hold_ms) ? (now - (uint64_t)hold_ms) : 0;
} else {
t_down = 0; t_press_valid = 0; t_origin_valid = 0; t_long_fired = 0;
t_cx = -1; t_cy = -1; t_dx = 0; t_dy = 0; t_down_ms = 0;
t_mv_x = -1; t_mv_y = -1;
}
}
/* 宿主单测/内部注入:模拟按下后移动到 (x,y)(保原点、更新当前坐标;位移超长按容忍即取消)。
* 对应输入线程 touch_update 的位移分支(这里不产生语义事件,仅更新坐标状态)。 */
void meow_input_touch_move(int x, int y)
{
if (!t_down || !t_origin_valid) return;
t_cx = x; t_cy = y;
t_mv_x = x; t_mv_y = y;
}
/* 宿主单测/内部注入:模拟一次抬手(真实 touch_release;含长按后抑制 TAP/SWIPE)。 */
void meow_input_touch_release(void) { touch_release(); }
/* ---- §6.3 通用长按公共 API + 阈值跨越检测 ----
* meow_input_long_press_poll:主循环(meow_ui_run 每帧)与宿主单测调用。读触摸状态机:
* 按下、原点/坐标已就绪、未触发过长按、位移 ≤ 容忍、按住时长 ≥ 阈值 → 置 t_long_fired,
* 入队 MEOW_EV_LONG_PRESS(a=x,b=y=当前按下点),返回 1;否则返回 0。幂等(一次按压一次)。 */
void meow_ui_set_long_press_cb(meow_long_press_fn cb) { g_long_press_cb = cb; }
void meow_ui_set_long_press_ms(int ms)
{ g_long_press_ms = (ms > 0) ? ms : (int)MEOW_LONG_PRESS_MS; }
int meow_ui_long_press_ms(void) { return g_long_press_ms; }
int meow_input_long_press_poll(void)
{
if (!g_long_press_cb) return 0; /* 未注册回调:长按整体关闭,保持旧点击语义(短按/长按都 TAP) */
if (!t_down || t_long_fired || !t_origin_valid || t_cx < 0 || t_cy < 0) return 0;
int mdx = t_cx - t_dx, mdy = t_cy - t_dy;
int amx = mdx < 0 ? -mdx : mdx, amy = mdy < 0 ? -mdy : mdy;
int move = amx > amy ? amx : amy;
if (move > MEOW_LONG_PRESS_SLOP) return 0; /* 位移超容忍:取消 */
if (meow_input_touch_hold_ms() < (unsigned)g_long_press_ms) return 0; /* 未达阈值 */
t_long_fired = 1;
push_ev(MEOW_EV_LONG_PRESS, t_cx, t_cy);
return 1;
}
/* ---- 输入线程:poll 所有 event 设备 + 解析 ---- */
static void *input_thread(void *arg)
{
(void)arg;
struct pollfd fds[MAX_DEVS];
int nfd = 0;
char dev[32];
for (int i = 0; i < MAX_DEVS; i++) {
snprintf(dev, sizeof dev, "/dev/input/event%d", i);
int e = open(dev, O_RDONLY | O_NONBLOCK);
if (e >= 0) { fds[nfd].fd = e; fds[nfd].events = POLLIN; fds[nfd].revents = 0; nfd++; }
}
while (g_thread_run) {
if (nfd == 0) { usleep(20000); continue; }
int r = poll(fds, nfd, 20);
if (r < 0) { if (errno == EINTR) continue; usleep(20000); continue; }
if (r == 0) continue;
for (int i = 0; i < nfd; i++) {
if (!(fds[i].revents & POLLIN)) continue;
unsigned char ev[16];
while (read(fds[i].fd, ev, sizeof ev) == (ssize_t)sizeof ev) {
uint16_t type = *(uint16_t *)(ev + 8);
uint16_t code = *(uint16_t *)(ev + 10);
int32_t val = *(int32_t *)(ev + 12);
if (type == EV_ABS) {
if (code == ABS_X || code == ABS_MT_POSITION_X) {
if (t_down) touch_update(val, t_cy); /* 锁原点+更新当前(t_down 时才驱动) */
else t_cx = val;
/* 左滑进度实时上报(视觉条):原点确实在左缘(t_dx<EDGE_W) 且 向右移动
* (t_cx>t_dx)——grow/回缩全程由「非全屏已验证的同一条路径」驱动,全屏仅
* 垂直位置 hy 不同(hy=meow_ui_statusbar_height()),别无全屏特判。
* 抬手触发仍由 touch_release 判定;这里只报过程(事件载荷 b=当前X)。
* t_dx 是本次按下后首个有效 ABS 锁定的真实按下点(非残留),且每次抬手已
* 清空,杜绝「中部点一下出条 / 向左划也出条」。 */
if (t_down && t_origin_valid && t_dx >= 0 && t_dx < EDGE_W && t_cx > t_dx)
push_ev(MEOW_EV_SWIPE_PROGRESS, t_dx, t_cx);
}
else if (code == ABS_Y || code == ABS_MT_POSITION_Y) {
if (t_down) touch_update(t_cx, val); else t_cy = val;
}
} else if (type == EV_KEY) {
if (code == BTN_TOUCH) {
if (val && !t_down) touch_press(t_cx, t_cy);
else if (!val && t_down) touch_release();
} else if (code == MEOW_KEY_POWER || code == MEOW_KEY_VOLUMEDOWN ||
code == MEOW_KEY_VOLUMEUP || code == MEOW_KEY_PLAYPAUSE ||
code == MEOW_KEY_PREVIOUS || code == MEOW_KEY_NEXT || code == 19) {
/* M2.22(A)/M2.24 纪律:物理键(电源 116 / 音量 114,115 / 播放 164 /
* 上一曲 165(MEOW_KEY_PREVIOUS)/19(旧码) / 下一曲 163)全部是系统语义 —— watchdog/launcher 统一消费,
* 绝不下发 APP。应用不碰物理键、不把任何物理键映射成确认/exit/返回/翻页
* (KEY_CONTROL_DESIGN §1.3 / APPS.md §3.4.1);APP 只接收 tap/swipe/tick。
* 忽略该键,不发 MEOW_EV_KEY。 */
} else if (val == 1) { /* 按下边沿(忽略重复=2)——仅未知键(本设备无) */
push_ev(MEOW_EV_KEY, code, 1);
} else if (val == 0) { /* 抬起 */
push_ev(MEOW_EV_KEY, code, 0);
}
}
}
}
}
for (int i = 0; i < nfd; i++) close(fds[i].fd);
return NULL;
}
void meow_input_start(void)
{
if (g_thread_started) return;
g_thread_started = 1;
g_thread_run = 1;
pthread_create(&g_thread, NULL, input_thread, NULL);
}
/* ---- 事件循环 ---- */
void meow_ui_request_exit(void) { g_exit = 1; }
/* M2.71-bug5:App 活动信号(节流 ≤2/s 写标记文件,限 I/O)。
* 仅真机写(宿主/qemu 无 launcher 消费,避免噪音);launcher 前台 app 循环消费它重置息屏计时。 */
static uint64_t g_activity_last_ms = 0;
void meow_ui_activity_ping(void)
{
uint64_t now = now_ms();
if (now - g_activity_last_ms < 500) return; /* 节流:任意交互连续触摸也只 ~2 次/s */
g_activity_last_ms = now;
if (!meow_gfx_is_device()) return; /* 离屏/宿主:无 launcher 消费 */
int fd = open(MEOW_APP_ACTIVITY_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd >= 0) { if (write(fd, "1", 1) < 0) { } close(fd); }
}
void meow_ui_run(const meow_ui_events *ev)
{
if (ev) { g_events = *ev; g_events_set = 1; }
g_exit = 0;
meow_input_start();
last_tick_ms = now_ms();
startup_cooldown_until = now_ms() + STARTUP_COOLDOWN_MS; /* M2.22(C):启动窗口丢弃 SWIPE */
while (!g_exit) {
/* agent-119:实时触摸按压/按住/释放 → 即时重绘命中按钮区(不等 app 重绘)。
* IME 打开时软键盘为覆盖层且拦截触摸,不驱动背后按钮视觉,跳过。
* §6.3 通用长按:每帧检测「按下 ≥ 阈值且位移 ≤ 容忍」跨越(IME 打开不驱动;启动窗口不触发)。 */
if (!meow_ime_active()) {
meow_widget_touch_refresh();
if (now_ms() >= startup_cooldown_until) meow_input_long_press_poll();
}
meow_input_ev e;
int got = 0;
while (meow_input_pop(&e)) {
if (g_exit) break; /* 已请求退出:不再分发剩余事件,杜绝「退出后又触发」的偶发再进/退 */
got = 1;
meow_ui_activity_ping(); /* M2.71-bug5:任意交互事件都喂 launcher 重置息屏计时(节流) */
int in_startup = (now_ms() < startup_cooldown_until); /* M2.22(C) */
if (e.type == MEOW_EV_TAP) {
if (in_startup) continue; /* 启动窗口也吞 TAP:旧手势残留点按(如上一次左滑抬起)
* 在进入瞬间命中 UI 按钮(如全屏按钮)会误触发——丢弃。 */
/* 抬手未达标:清视觉条 */
meow_ui_swipe_hint(-1);
meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6);
if (meow_ime_active() && meow_ime_handle_tap(e.a, e.b)) continue;
/* §6.1 触摸分流:③ 系统全局(IME/状态栏…已在上面消费) → ② App 全局控件
* (命中返回 1=消费,否则落到 ① active Activity 页面控件)。 */
if (meow_ui_global_hit(e.a, e.b)) continue;
if (g_events_set && g_events.on_tap) g_events.on_tap(e.a, e.b);
} else if (e.type == MEOW_EV_KEY) {
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; /* 启动残留被当左缘滑动:丢弃,防进入即返回 */
/* 抬手达标:清视觉条(左滑返回),触发行消失 */
meow_ui_swipe_hint(-1);
meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6);
/* A-0 全局左缘返回(方案A):
* - APP 注册 on_swipe → APP 接管,自行决定(meow_ui_request_exit / 页内返回);
* - APP 未注册 on_swipe 且非全屏(sb_active)→ SDK 默认退出 app(返回=退出);
* - 全屏/沉浸 → 无系统返回手势,仅 APP 自己处理。
* 扩展点:阶段D M3 导航栈落地后,「默认退出」升级为「按 nav_stack 回退」。 */
int sb_active = meow_ui_sys_statusbar_active();
if (g_events_set && g_events.on_swipe) {
g_events.on_swipe(e.a, e.b, 0);
} else if (e.a == MEOW_SWIPE_LEFT && sb_active) {
meow_ui_request_exit();
}
} else if (e.type == MEOW_EV_NOTIF_PULL) {
if (in_startup) continue; /* 启动窗口顶区误触残留:丢弃,防进入即开通知中心 */
/* 系统级「下拉展开通知中心」:起始于顶部热区的下滑为系统保留手势,不下发
* APP on_swipe;路由到系统钩子(App 进程只检测+经 IPC 上报 MSG_NOTIF_OPEN,
* 通知中心由 launcher 渲染,见 FRAMEWORK_DESIGN §10.5)。 */
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)期间本主循环被阻塞,天然不在此分发。 */
if (in_startup) continue;
if (meow_ime_active()) continue;
if (g_events_set && g_events.on_touch_down && e.type == MEOW_EV_TOUCH_DOWN)
g_events.on_touch_down(e.a, e.b);
if (g_events_set && g_events.on_touch_move && e.type == MEOW_EV_TOUCH_MOVE)
g_events.on_touch_move(e.a, e.b);
got = 1; /* 交互事件:不睡眠,保持跟手帧同步 */
} else if (e.type == MEOW_EV_SWIPE_PROGRESS) {
if (in_startup) continue; /* 启动窗口进度条静音 */
int x0 = e.a, xc = e.b, dx = xc - x0; /* dx=当前X-按下X */
/* SDK 自动画顶部条:长度=手指当前 X(=距左缘距离,向内划长度随位移增长)。
* 全屏/收合下「左缘返回手势保留」,顶部动态提示条(meow_ui_swipe_hint)照常绘制
* (与 agent-108:不再以 meow_ui_sys_statusbar_active()(全屏/隐藏=0)开关,否则
* 全屏时滑条完全不显示)。条位 y=meow_ui_statusbar_height():非全屏=状态栏下沿
* (28)、全屏=顶部、收合=提示线下缘(MUI_SBAR_COLLAPSE_H),与提示线不重叠加——
* 全屏与非全屏共用同一条绘制/进度路径,只 hy 不同,无全屏特有的坐标/方向/最大值逻辑。
* APP 若注册 on_swipe_progress 仍可自绘(系统条照常画;x_start+dx==当前X,幂等)。 */
meow_ui_swipe_hint(xc);
if (g_events_set && g_events.on_swipe_progress)
g_events.on_swipe_progress(x0, dx);
meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6);
} 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;
if (g_long_press_cb) g_long_press_cb(e.a, e.b);
}
}
uint64_t now = now_ms();
if (g_events_set && g_events.on_tick) {
if (now - last_tick_ms >= 16) {
last_tick_ms = now;
g_events.on_tick();
}
}
/* A-0 全局状态栏 1s 轻刷:电量/WiFi/音量 + 时钟。时钟每秒变 → 重画顶条并上屏,
* 即使 APP 空闲(未主动 flush)也保持时钟走动。
* 「同一个状态栏」闭环:仅系统进程(launcher)刷新顶条;App 进程不渲染状态栏,
* 此处不再 flush 顶条(避免把 launcher 叠画的系统顶条冲成 App 背景)。数据仍刷新
* (meow_ui_sys_refresh 无副作用),App 侧 inset/热区不受影响。 */
static uint64_t last_sys_ms = 0;
if (now - last_sys_ms >= 1000) {
last_sys_ms = now;
meow_ui_sys_refresh();
if (meow_ui_sys_render_enabled() && meow_ui_sys_statusbar_active())
meow_ui_flush_region(0, 0, meow_ui_width(), MUI_SBAR_H);
}
if (meow_ime_active()) { /* 软键盘覆盖层随帧绘制(30fps 节流,省 CPU) */
static uint64_t last_ime_ms = 0;
uint64_t n = now_ms();
if (n - last_ime_ms >= 33) {
last_ime_ms = n;
meow_ime_draw();
meow_ui_flush();
}
if (!got) usleep(16000);
} else if (!got) {
usleep(16000);
}
}
}
/* M2.71-IME-fix:供 meow_ui_ime.c 的阻塞 IME 循环周期性喂 App on_tick。
* 阻塞 IME 循环不经过 meow_ui_run,无法靠主循环喂 tick,故在此开独立入口;
* 与 meow_ui_run 共用 last_tick_ms(二者互斥运行,无竞争)。调用方在 meow_ime_draw 之前调用,
* 使 App 重绘先进 back_buf、IME 面板随后覆盖(IME 始终最上,不被 App 重绘盖住)。 */
void meow_ui_feed_tick(void)
{
if (!(g_events_set && g_events.on_tick)) return;
uint64_t now = now_ms();
if (now - last_tick_ms < 16) return;
last_tick_ms = now;
g_events.on_tick();
}