fix(sdk): App内物理音量/媒体键无效真根因 (agent-226) — 本机物理键只更新EVIOCGKEY位图不进事件流(launcher用位图轮询故正常,SDK读流故App收不到); 输入线程加EVIOCGKEY位图轮询+120ms同码窗口去重(流+位图双源不双跳); 卡上三App已重链部署(hello 2a1316f7/files ef0082bc/ncm 42e67e15)

真机验证: 音量键改settings.conf+媒体键IPC转发链路通; 宿主全PASS
This commit is contained in:
MeowOS committed 2026-08-31 18:11:04 +08:00
1 parent c041facd89
commit c7d02e66e9
1 file changed
+96 -27
+96 -27
View File
@@ -6,7 +6,8 @@
* meow_ui_run / IME 循环弹出分发。应用不再自读 event*。 * meow_ui_run / IME 循环弹出分发。应用不再自读 event*。
* *
* 触摸:BTN_TOUCH 按下记录原点,抬起时位移 < 阈值 → TAP,否则按主导轴 → SWIPE。 * 触摸: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; * 电源 116 → 维持现状(息屏/亮屏/长按菜单由内核/sys_server/watchdog),SDK 不动、不下发 APP;
* 音量 114/115 → 调系统音量(meow_settings audio.volume + CS43198 DAC 写,volume_step/volume_max * 音量 114/115 → 调系统音量(meow_settings audio.volume + CS43198 DAC 写,volume_step/volume_max
* 约束,与 launcher 设置页/watchdog 同源); * 约束,与 launcher 设置页/watchdog 同源);
@@ -14,6 +15,11 @@
* 否则经 IPC 发 launcher 转发)。 * 否则经 IPC 发 launcher 转发)。
* 「App 收到的是语义事件」:上述音量/媒体键在 SDK 消化后**仍下发 MEOW_EV_KEY**(on_key),但 * 「App 收到的是语义事件」:上述音量/媒体键在 SDK 消化后**仍下发 MEOW_EV_KEY**(on_key),但
* App 不得把它们映射成自定义行为(APPS.md §3.4.1 语义不变)。 * 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.h"
#include "meow_ui_internal.h" #include "meow_ui_internal.h"
@@ -30,6 +36,7 @@
#include <time.h> #include <time.h>
#include <poll.h> #include <poll.h>
#include <pthread.h> #include <pthread.h>
#include <sys/ioctl.h>
/* linux input 事件常量(本设备实测) */ /* linux input 事件常量(本设备实测) */
#define EV_SYN 0 #define EV_SYN 0
@@ -40,6 +47,12 @@
#define ABS_Y 0x01 #define ABS_Y 0x01
#define ABS_MT_POSITION_X 0x35 #define ABS_MT_POSITION_X 0x35
#define ABS_MT_POSITION_Y 0x36 #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 SWIPE_THRESH 65 /* px:抬起位移超过视为滑动(GESTURE_BACK: 90→65,用户实测 90 偏多,65 更顺手) */
#define EDGE_W 24 /* 左滑返回的「左缘起点」宽度(t_dx < EDGE_W 才认返回手势) */ #define EDGE_W 24 /* 左滑返回的「左缘起点」宽度(t_dx < EDGE_W 才认返回手势) */
@@ -337,31 +350,90 @@ int meow_input_long_press_poll(void)
return 1; 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) static void *input_thread(void *arg)
{ {
(void)arg; (void)arg;
struct pollfd fds[MAX_DEVS]; struct pollfd fds[MAX_DEVS];
unsigned char prev_key[MAX_DEVS][KEY_MAX / 8 + 1];
int nfd = 0; int nfd = 0;
char dev[32]; char dev[32];
for (int i = 0; i < MAX_DEVS; i++) { for (int i = 0; i < MAX_DEVS; i++) {
snprintf(dev, sizeof dev, "/dev/input/event%d", i); snprintf(dev, sizeof dev, "/dev/input/event%d", i);
int e = open(dev, O_RDONLY | O_NONBLOCK); 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) { while (g_thread_run) {
if (nfd == 0) { usleep(20000); continue; } if (nfd == 0) { usleep(20000); continue; }
int r = poll(fds, nfd, 20); int r = poll(fds, nfd, 20);
if (r < 0) { if (errno == EINTR) continue; usleep(20000); continue; } if (r < 0) {
if (r == 0) continue; if (errno != EINTR) usleep(20000);
for (int i = 0; i < nfd; i++) { } else if (r > 0) {
if (!(fds[i].revents & POLLIN)) continue; for (int i = 0; i < nfd; i++) {
unsigned char ev[16]; if (!(fds[i].revents & POLLIN)) continue;
while (read(fds[i].fd, ev, sizeof ev) == (ssize_t)sizeof ev) { unsigned char ev[16];
uint16_t type = *(uint16_t *)(ev + 8); while (read(fds[i].fd, ev, sizeof ev) == (ssize_t)sizeof ev) {
uint16_t code = *(uint16_t *)(ev + 10); uint16_t type = *(uint16_t *)(ev + 8);
int32_t val = *(int32_t *)(ev + 12); uint16_t code = *(uint16_t *)(ev + 10);
int32_t val = *(int32_t *)(ev + 12);
if (type == EV_ABS) { if (type == EV_ABS) {
if (code == ABS_X || code == ABS_MT_POSITION_X) { if (code == ABS_X || code == ABS_MT_POSITION_X) {
if (t_down) touch_update(val, t_cy); /* 锁原点+更新当前(t_down 时才驱动) */ 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) { } else if (code == MEOW_KEY_POWER) {
/* M2.55(接 M2.24 纪律):电源=息屏/亮屏/长按菜单,由内核/sys_server/ /* M2.55(接 M2.24 纪律):电源=息屏/亮屏/长按菜单,由内核/sys_server/
* watchdog 统一处理,SDK 不动、也不下发 APP。维持现状。 */ * watchdog 统一处理,SDK 不动、也不下发 APP。维持现状。 */
} else if (code == MEOW_KEY_VOLUMEDOWN || code == MEOW_KEY_VOLUMEUP) { } else if (code == MEOW_KEY_VOLUMEDOWN || code == MEOW_KEY_VOLUMEUP ||
/* 音量键=系统音量(M2.55:SDK 输入层消化,App 前台也生效)。 code == MEOW_KEY_PLAYPAUSE || code == MEOW_KEY_PREVIOUS ||
* 语义动作在 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 ||
code == MEOW_KEY_NEXT || code == 19) { code == MEOW_KEY_NEXT || code == 19) {
/* 媒体键=转发到当前媒体会话(M2.55:SDK 输入层消化)。语义动作在 /* 系统键(音量/媒体):真实物理键可能不进事件流,另经 EVIOCGKEY 状态轮询
* meow_input_handle_sys_key;随后仍下发 MEOW_EV_KEY(App 不得映射)。 */ * 兜底(poll_sys_keys_eviocgkey);双源经 sys_key_press/sys_key_release 去重,
if (val == 1 || val == 0) { * 一次按下只一步系统语义 + 一次 MEOW_EV_KEY 下发(App on_key 不得映射)。 */
meow_input_handle_sys_key(code, val); if (val == 1) sys_key_press(code);
push_ev(MEOW_EV_KEY, code, (val == 1) ? 1 : 0); else if (val == 0) sys_key_release(code);
}
} else if (val == 1) { /* 按下边沿(忽略重复=2)——仅未知键(本设备无) */ } else if (val == 1) { /* 按下边沿(忽略重复=2)——仅未知键(本设备无) */
push_ev(MEOW_EV_KEY, code, 1); push_ev(MEOW_EV_KEY, code, 1);
} else if (val == 0) { /* 抬起 */ } 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); for (int i = 0; i < nfd; i++) close(fds[i].fd);
return NULL; return NULL;