From 9e4ec15e8512d4f752fd4f2fceadc31f106fec96 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Mon, 31 Aug 2026 18:11:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(sdk):=20App=E5=86=85=E7=89=A9=E7=90=86?= =?UTF-8?q?=E9=9F=B3=E9=87=8F/=E5=AA=92=E4=BD=93=E9=94=AE=E6=97=A0?= =?UTF-8?q?=E6=95=88=E7=9C=9F=E6=A0=B9=E5=9B=A0=20(agent-226)=20=E2=80=94?= =?UTF-8?q?=20=E6=9C=AC=E6=9C=BA=E7=89=A9=E7=90=86=E9=94=AE=E5=8F=AA?= =?UTF-8?q?=E6=9B=B4=E6=96=B0EVIOCGKEY=E4=BD=8D=E5=9B=BE=E4=B8=8D=E8=BF=9B?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E6=B5=81(launcher=E7=94=A8=E4=BD=8D=E5=9B=BE?= =?UTF-8?q?=E8=BD=AE=E8=AF=A2=E6=95=85=E6=AD=A3=E5=B8=B8,SDK=E8=AF=BB?= =?UTF-8?q?=E6=B5=81=E6=95=85App=E6=94=B6=E4=B8=8D=E5=88=B0);=20=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E7=BA=BF=E7=A8=8B=E5=8A=A0EVIOCGKEY=E4=BD=8D=E5=9B=BE?= =?UTF-8?q?=E8=BD=AE=E8=AF=A2+120ms=E5=90=8C=E7=A0=81=E7=AA=97=E5=8F=A3?= =?UTF-8?q?=E5=8E=BB=E9=87=8D(=E6=B5=81+=E4=BD=8D=E5=9B=BE=E5=8F=8C?= =?UTF-8?q?=E6=BA=90=E4=B8=8D=E5=8F=8C=E8=B7=B3);=20=E5=8D=A1=E4=B8=8A?= =?UTF-8?q?=E4=B8=89App=E5=B7=B2=E9=87=8D=E9=93=BE=E9=83=A8=E7=BD=B2(hello?= =?UTF-8?q?=202a1316f7/files=20ef0082bc/ncm=2042e67e15)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 真机验证: 音量键改settings.conf+媒体键IPC转发链路通; 宿主全PASS --- meow_ui_input.c | 123 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 96 insertions(+), 27 deletions(-) diff --git a/meow_ui_input.c b/meow_ui_input.c index 93e5a51..ecc08e0 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -6,7 +6,8 @@ * meow_ui_run / IME 循环弹出分发。应用不再自读 event*。 * * 触摸:BTN_TOUCH 按下记录原点,抬起时位移 < 阈值 → TAP,否则按主导轴 → SWIPE。 - * 物理键:EV_KEY 值 1=按下 / 0=抬起(重复值 2 忽略)。M2.55 起系统按键语义在 SDK 输入层消化: + * 物理键:read() 事件流 EV_KEY 值 1=按下 / 0=抬起(重复值 2 忽略)。M2.55/agent-217 起系统按键 + * 语义在 SDK 输入层消化: * 电源 116 → 维持现状(息屏/亮屏/长按菜单由内核/sys_server/watchdog),SDK 不动、不下发 APP; * 音量 114/115 → 调系统音量(meow_settings audio.volume + CS43198 DAC 写,volume_step/volume_max * 约束,与 launcher 设置页/watchdog 同源); @@ -14,6 +15,11 @@ * 否则经 IPC 发 launcher 转发)。 * 「App 收到的是语义事件」:上述音量/媒体键在 SDK 消化后**仍下发 MEOW_EV_KEY**(on_key),但 * App 不得把它们映射成自定义行为(APPS.md §3.4.1 语义不变)。 + * 真机链路兜底(本次修复):物理键的按下不一定进入 evdev 事件流(事件流可被 EVIOCGRAB 独占或 + * 驱动不投递;APPS.md §4.4 明确 launcher 用 EVIOCGKEY 状态轮询读键、「与 grab/投递无关」)。故 + * 输入线程除 read() 事件流外,每轮还对键设备做一次 EVIOCGKEY 状态位图轮询,把 0→1/1→0 边沿并入 + * 同一套系统键语义(poll_sys_keys_eviocgkey);事件流与位图双源经 sys_key_press/sys_key_release + * 的「同码 120ms 窗口」去重,一次物理按下只触发一步系统语义 + 一次 MEOW_EV_KEY 下发。 */ #include "meow_ui.h" #include "meow_ui_internal.h" @@ -30,6 +36,7 @@ #include #include #include +#include /* linux input 事件常量(本设备实测) */ #define EV_SYN 0 @@ -40,6 +47,12 @@ #define ABS_Y 0x01 #define ABS_MT_POSITION_X 0x35 #define ABS_MT_POSITION_Y 0x36 +#ifndef EVIOCGKEY +#define EVIOCGKEY(len) _IOC(_IOC_READ, 'E', 0x18, len) +#endif +#ifndef KEY_MAX +#define KEY_MAX 0x2ff +#endif #define SWIPE_THRESH 65 /* px:抬起位移超过视为滑动(GESTURE_BACK: 90→65,用户实测 90 偏多,65 更顺手) */ #define EDGE_W 24 /* 左滑返回的「左缘起点」宽度(t_dx < EDGE_W 才认返回手势) */ @@ -337,31 +350,90 @@ int meow_input_long_press_poll(void) return 1; } -/* ---- 输入线程:poll 所有 event 设备 + 解析 ---- */ +/* ---- 系统键(音量/媒体)去重 + EVIOCGKEY 状态轮询 ---- + * M2.55/agent-217 之后 SDK 在输入线程消化系统键语义;但真机物理键(jz adc keyboard event2、 + * md-gpio-keys event0、earpods_adc event3)的按下不一定进入 evdev 事件流(事件流可被其它进程 + * EVIOCGRAB 独占或驱动不投递),而 launcher 全程用 EVIOCGKEY 状态轮询读键(APPS.md §4.4, + * 「与 grab/投递无关」)。为让 App 前台物理音量/媒体键可靠生效,输入线程除 read() 事件流外, + * 还按固定节奏轮询内核按键位图(EVIOCGKEY),把 0→1/1→0 边沿并入同一套系统键语义。 + * 双源(事件流 + 状态位图)对同一次物理按下各报一次,用「同码 120ms 窗口」去重,保证 + * 一次按下只触发一步系统语义 + 一次 MEOW_EV_KEY 下发(App on_key 收到语义事件,不得映射)。 */ +static int g_sys_press_code = -1; +static uint64_t g_sys_press_ms = 0; +#define SYS_KEY_DEDUP_MS 120 + +static void sys_key_press(int code) +{ + uint64_t now = now_ms(); + if (code == g_sys_press_code && now - g_sys_press_ms < SYS_KEY_DEDUP_MS) + return; /* 事件流 + EVIOCGKEY 双源去重:同码窗口内只消化一次 */ + g_sys_press_code = code; + g_sys_press_ms = now; + meow_input_handle_sys_key(code, 1); + push_ev(MEOW_EV_KEY, code, 1); +} + +static void sys_key_release(int code) +{ + push_ev(MEOW_EV_KEY, code, 0); +} + +/* 对一个 event 设备做一次 EVIOCGKEY 状态边沿检测(prev 为该设备上一状态位图,原地更新)。 + * 只在系统键码(音量/媒体)上找边沿;电源(116)不进此表——SDK 维持「不动、不下发」纪律。 */ +static void poll_sys_keys_eviocgkey(int fd, unsigned char *prev) +{ + unsigned char cur[KEY_MAX / 8 + 1]; + if (ioctl(fd, EVIOCGKEY(sizeof cur), cur) < 0) return; + static const int sys_codes[] = { + MEOW_KEY_VOLUMEDOWN, MEOW_KEY_VOLUMEUP, MEOW_KEY_PLAYPAUSE, + MEOW_KEY_PREVIOUS, MEOW_KEY_NEXT, 19, + }; + for (unsigned ci = 0; ci < sizeof sys_codes / sizeof sys_codes[0]; ci++) { + int code = sys_codes[ci]; + int b = (cur[code / 8] >> (code % 8)) & 1; + int pb = (prev[code / 8] >> (code % 8)) & 1; + if (b == pb) continue; + prev[code / 8] = (unsigned char)(b ? (prev[code / 8] | (1u << (code % 8))) + : (prev[code / 8] & (unsigned char)~(1u << (code % 8)))); + if (b) sys_key_press(code); /* 0→1:按下(去重后触发一步系统语义) */ + else sys_key_release(code); /* 1→0:抬起 */ + } +} + +/* ---- 输入线程:poll 所有 event 设备 + EVIOCGKEY 系统键轮询 ---- */ static void *input_thread(void *arg) { (void)arg; struct pollfd fds[MAX_DEVS]; + unsigned char prev_key[MAX_DEVS][KEY_MAX / 8 + 1]; 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++; } + if (e >= 0) { + fds[nfd].fd = e; fds[nfd].events = POLLIN; fds[nfd].revents = 0; + /* EVIOCGKEY 上一状态初始化为当前按下位图:启动时已按住的键不误判为新按下 */ + memset(prev_key[nfd], 0, sizeof prev_key[nfd]); + if (ioctl(e, EVIOCGKEY(sizeof prev_key[nfd]), prev_key[nfd]) < 0) + memset(prev_key[nfd], 0, sizeof prev_key[nfd]); + 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 (r < 0) { + if (errno != EINTR) usleep(20000); + } else if (r > 0) { + 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 时才驱动) */ @@ -385,22 +457,14 @@ static void *input_thread(void *arg) } else if (code == MEOW_KEY_POWER) { /* M2.55(接 M2.24 纪律):电源=息屏/亮屏/长按菜单,由内核/sys_server/ * watchdog 统一处理,SDK 不动、也不下发 APP。维持现状。 */ - } else if (code == MEOW_KEY_VOLUMEDOWN || code == MEOW_KEY_VOLUMEUP) { - /* 音量键=系统音量(M2.55:SDK 输入层消化,App 前台也生效)。 - * 语义动作在 meow_input_handle_sys_key(仅按下边沿一步);随后仍下发 - * MEOW_EV_KEY(App on_key 收到语义事件,但不得映射成自定义行为)。 */ - if (val == 1 || val == 0) { - meow_input_handle_sys_key(code, val); - push_ev(MEOW_EV_KEY, code, (val == 1) ? 1 : 0); - } - } else if (code == MEOW_KEY_PLAYPAUSE || code == MEOW_KEY_PREVIOUS || + } else if (code == MEOW_KEY_VOLUMEDOWN || code == MEOW_KEY_VOLUMEUP || + code == MEOW_KEY_PLAYPAUSE || code == MEOW_KEY_PREVIOUS || code == MEOW_KEY_NEXT || code == 19) { - /* 媒体键=转发到当前媒体会话(M2.55:SDK 输入层消化)。语义动作在 - * meow_input_handle_sys_key;随后仍下发 MEOW_EV_KEY(App 不得映射)。 */ - if (val == 1 || val == 0) { - meow_input_handle_sys_key(code, val); - push_ev(MEOW_EV_KEY, code, (val == 1) ? 1 : 0); - } + /* 系统键(音量/媒体):真实物理键可能不进事件流,另经 EVIOCGKEY 状态轮询 + * 兜底(poll_sys_keys_eviocgkey);双源经 sys_key_press/sys_key_release 去重, + * 一次按下只一步系统语义 + 一次 MEOW_EV_KEY 下发(App on_key 不得映射)。 */ + if (val == 1) sys_key_press(code); + else if (val == 0) sys_key_release(code); } else if (val == 1) { /* 按下边沿(忽略重复=2)——仅未知键(本设备无) */ push_ev(MEOW_EV_KEY, code, 1); } else if (val == 0) { /* 抬起 */ @@ -409,6 +473,11 @@ static void *input_thread(void *arg) } } } + } + /* EVIOCGKEY 系统键状态轮询:无论事件流是否投递都跑(真实物理键可能不进事件流, + * 与 launcher kp_poll 同源的按键位图读取兜底;事件流 + 位图双源在 sys_key_press 去重)。 */ + for (int i = 0; i < nfd; i++) + poll_sys_keys_eviocgkey(fds[i].fd, prev_key[i]); } for (int i = 0; i < nfd; i++) close(fds[i].fd); return NULL;