461 lines
23 KiB
C
461 lines
23 KiB
C
/*
|
||
* 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 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;
|
||
|
||
/* 按下边沿:只标记按下,绝不用传入坐标立即锁原点(避免用上一手势残留坐标当原点)。
|
||
* 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:拖动过程起点未定 */
|
||
}
|
||
/* 由 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;
|
||
/* 本次按下从未锁到有效坐标(幽灵触摸/坐标未就绪):丢弃,且清空全部轨迹。 */
|
||
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;
|
||
/* 手势判定只在「抬手(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_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_cx = -1; t_cy = -1; t_dx = 0; t_dy = 0; t_down_ms = 0;
|
||
t_mv_x = -1; t_mv_y = -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 打开时软键盘为覆盖层且拦截触摸,不驱动背后按钮视觉,跳过。 */
|
||
if (!meow_ime_active()) meow_widget_touch_refresh();
|
||
|
||
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;路由到系统钩子(当前为记录占位,通知中心 UI 为后续里程碑)。 */
|
||
meow_ui_sys_notif_pull();
|
||
} 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);
|
||
}
|
||
}
|
||
|
||
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();
|
||
}
|