Files
MeowOS-For-R3P2/patch_v2/meow_watchdog/meow_watchdog.c
T
MeowOS Dev 245bee2946 MeowOS for HiBy R3 Pro II — 公共发布版本(历史已重置)
• 全新单提交:清除含敏感信息的旧历史(sudo 口令、内网 IP、SSH 密钥路径、热点 SSID、设备 MAC)
• 本地开发文档/测试记录/构建脚本全部移入 gitignored 的 devdocs/ 与 scripts/build/(不入库)
• 保留:MeowOS 源码(launcher/watchdog/bootmenu/meow_svcd/SDK)、项目规范文档、mips-repo
增补组件:SDK 统一日志+通知 API、meow_svcd 服务管理器、mips-repo 软件仓库
2026-08-30 01:12:34 +08:00

558 lines
24 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_watchdog.c — virtual back button + gesture watchdog for the
* R3 Pro II app launcher.
*
* Spawned by the launcher (hiby_player cave code) alongside an app:
* meow_watchdog <app_pid> [--sim-tap N] [--sim-swipe N]
*
* Behavior (M2.21+):
* - mmap /dev/fb0 (RGB565 auto-detect); no virtual back button (removed).
* - read /dev/input/event*; a swipe starting in the left 24px edge
* (rightward movement > 50px) sends SIGUSR1 to the app (app decides).
* - M2.22(A)key: read KEY_POWER (event0) and own the panel during app
* run — short press (<3s release) = toggle screen off/on (brightness 0 +
* FBIOBLANK(4) / FBIOBLANK(0)+brightness restore, with a force re-pan to
* buffer0 on wake).
* - M2.24 全面按键接管(KEY_CONTROL_DESIGN):应用运行期所有物理键由本进程统一消费,
* 应用绝不碰物理键。
* 电源单击(<2s) -> 息屏/亮屏(沿用 M2.22);
* 电源长按 ≥2s -> 写 /tmp/meow_power_menu,由 launcher SIGSTOP app 后弹统一电源菜单
* (launcher 在 waitpid 轮询中消费;菜单期该进程靠 /tmp/meow_power_menu_active
* 让位,绝不与 launcher 抢屏/抢键);launcher 画菜单,本进程不自绘。
* 电源长按 ≥8s -> 强制关机兜底(仅当菜单未生效/launcher 未接管)。
* 音量±(115/114) -> 写真实 DAC(CS43198 amixer numid=1/2)+ 持久化 settings.conf(同 launcher 同源)。
* 播放/上下曲(164/165/163/19) -> 捕获 + 日志 + 消费,不路由(媒体会话落地后转活跃会话)。
* (8s 强关的「防误触 / pm_interacted」逻辑在 launcher 的 show_power_menu 内,此进程只做兜底。)
* Exits when the app process disappears (kill(pid,0) -> ESRCH) or on
* SIGTERM from the parent.
*
* Test modes (no touch device needed):
* --sim-tap N : after N seconds, simulate a button hit
* --sim-swipe N : after N seconds, simulate an immersive swipe
*
* Static MIPS32r2 musl build:
* ~/Project/R3ProII/.tools/zig-mipsel-cc -Os -static meow_watchdog.c -o meow_watchdog
*/
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/poll.h>
#include <sys/syscall.h>
#include <stdint.h>
#include <time.h>
/* device-verified: VSCREENINFO=0x4600, FSCREENINFO=0x4602 (do not swap) */
#define FBIOGET_VSCREENINFO 0x4600
#define FBIOGET_FSCREENINFO 0x4602
#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 EV_SYN 0
#define EV_KEY 1
#define EV_ABS 3
#define EDGE_W 24 /* left-edge swipe start zone(返回键已删,左滑返回仍用) */
#define SWIPE_DX 50 /* rightward movement threshold */
#define REFRESH_MS 100
/* M2.22(A):watchdog 在应用运行时接管电源键 = 息屏/亮屏(仅系统组件可做)。
* launcher 应用运行循环只处理「自动息屏/唤醒」,电源键单击(短按松开)在应用内
* 是孤儿 → 无法息屏。本进程既已读 event0(md-gpio-keys)+ mmap fb0,故在此复刻
* launcher 的 meow_panel_off/on 轻量版:背光 brightness>0 亮、0 灭;FBIOBLANK(4/0)
* 面板下电/上电(4=挂起 goodix 触摸,防误触)。本轮只做息屏/亮屏,不做电源菜单/
* 长按关机(那是后续,且属于 launcher/系统层)。 */
#define FBIOBLANK 0x4611
#define FBIOPAN_DISPLAY 0x4606
#define KEY_POWER 116
#define KEY_VOLUMEUP 115
#define KEY_VOLUMEDOWN 114
#define KEY_PLAYPAUSE 164
#define KEY_PREVIOUSSONG 165
#define KEY_NEXTSONG 163
#define KEY_PREVIOUS 19
#define BACKLIGHT "/sys/class/backlight/backlight_pwm0/brightness"
/* M2.24 全面按键接管:电源单击(<2s)=息屏/亮屏;长按≥2s=通知 launcher 弹统一电源菜单;
* ≥8s=强制关机兜底(仅当 launcher 未接管菜单时)。音量± = 写真实 DAC;播放/上下曲 = 抓消费。 */
#define PWR_TOGGLE_MS 2000 /* 短按上限:<2s 松开=单击(镜像 launcher 单单击语义) */
#define PWR_MENU_MS 2000 /* 长按≥2s -> 写 /tmp/meow_power_menu 请 launcher 弹菜单 */
#define PWR_FORCE_MS 8000 /* 长按≥8s -> 强制关机(launcher 未接管时的兜底) */
#define PWR_MENU_MARKER "/tmp/meow_power_menu" /* watchdog->launcher 菜单请求 */
#define PWR_MENU_ACTIVE "/tmp/meow_power_menu_active" /* launcher 画菜单中,watchdog 让位 */
#define MEOW_SETTINGS_FILE "/usr/data/meow/settings.conf" /* 音量持久化(与 launcher settings.conf 同源) */
#define LINUX_REBOOT_MAGIC1 0xfee1dead
#define LINUX_REBOOT_MAGIC2 672274793 /* 0x28121969 */
#define LINUX_REBOOT_CMD_POWER_OFF 0x4321fedc
/* Debounce for the launch-tap remnant (2026-08-28 fix).
* On a TOUCH launch the app is killed a second or two later (log: 1.2–2.25s
* after the launch tap) even though the user pressed nothing in between and
* the launcher logged no further tap — a ghost/spurious touch the goodix
* emits as the app starts. The old toggle state machine read whatever it
* saw with the default coords 0,0 (exactly the top-left button region) and
* killed the freshly-launched app, so the app could never stay open.
*
* Guards, layered from most-specific to most-general:
* 1. last_x/last_y start at -1 and a press with no coordinate seen is
* dropped — the default (0,0) can never be latched as a corner press.
* 2. BTN_TOUCH edges are matched on the actual value: a stray/out-of-sync
* UP never starts a press; only a real DOWN then UP dispatches.
* 3. A "click" must be SHORT (SHORT_PRESS_MS); a held press is ignored.
* 4. The input queues are drained once at start, so the launch-tap tail
* is not replayed as a fresh press.
* 5. Every press within COOLDOWN_MS of app start is ignored. This is set
* to cover the measured ghost window (1.2–2.25s) — the "~800ms 残影"
* assumption was too short. */
#define COOLDOWN_MS 2500 /* cover the measured ghost touch window + margin */
#define SHORT_PRESS_MS 600 /* a "click"; any longer and it is not a tap */
static unsigned char *fb;
static int xres, yres, line_len, bpp;
static inline void put_px(int x, int y, uint32_t color)
{
if (x < 0 || x >= xres || y < 0 || y >= yres) return;
unsigned char *p = fb + y * line_len + x * (bpp / 8);
if (bpp == 16) {
uint16_t c = ((color >> 8) & 0xF800) | ((color >> 5) & 0x07E0) |
((color >> 3) & 0x001F);
*(uint16_t *)p = c;
} else {
*(uint32_t *)p = (color << 8) | 0xFF;
}
}
/* 5x7 "<" arrow + border */
static void fill_rect(int x0, int y0, int w, int h, uint32_t color)
{
for (int y = y0; y < y0 + h; y++)
for (int x = x0; x < x0 + w; x++)
put_px(x, y, color);
}
static int read_state_file(void)
{
/* returns 1 if immersive */
int fd = open("/tmp/meowapp_state", O_RDONLY);
if (fd < 0) return 0;
char buf[8];
int n = read(fd, buf, sizeof buf - 1);
close(fd);
if (n <= 0) return 0;
buf[n] = 0;
return buf[0] == '1';
}
/* unified log: write to the boot log file passed by the launcher via
* MEOW_LOG_FILE (TF card MeowOS/logs/meow_<bootts>.log); fall back to
* stderr when unset (e.g. no TF card at launch time). */
static int wd_log_fd = -1;
static unsigned long long now_ms(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (unsigned long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
static void wd_log_init(void)
{
const char *lf = getenv("MEOW_LOG_FILE");
if (lf && lf[0])
wd_log_fd = open(lf, O_WRONLY | O_APPEND | O_CREAT, 0644);
}
static void wd_log(const char *fmt, ...)
{
char buf[256];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof buf, fmt, ap);
va_end(ap);
char line[288];
int l = snprintf(line, sizeof line, "%llu [watchdog] %s\n", now_ms(), buf);
if (wd_log_fd >= 0) {
if (write(wd_log_fd, line, l) < 0) wd_log_fd = -1;
return;
}
fprintf(stderr, "%llu [watchdog] %s\n", now_ms(), buf);
}
/* touch tracking state.
* last_x/last_y start at -1 (never seen a coordinate) so a press that
* arrives before any ABS coordinate is not latched at the default 0,0 —
* which is exactly the corner the old code used to mis-fire on. */
static int last_x = -1, last_y = -1;
static int down_valid = 0; /* a press is currently being tracked */
static int down_x = 0, down_y = 0;
static unsigned long long down_ms = 0;
/* ---- M2.22(A)+ M2.24 电源键接管:息屏/亮屏 + 长按菜单/强关 ---- */
static int wd_fbfd = -1; /* fb0 fd(main 里打开,供 FBIOBLANK/FBIOPAN 用) */
static int wd_saved_bri = 60; /* 熄屏前亮度快照 */
static int pwr_down = 0; /* 电源当前按住 */
static unsigned long long pwr_down_ms = 0;
static int pwr_woke = 0; /* 按下时屏已黑 → 本次只作唤醒,松开不再 toggle */
static int pwr_menu_notified = 0; /* 已写 /tmp/meow_power_menu 请 launcher 弹菜单 */
static int pwr_force_done = 0; /* 8s 强关已发(防重复) */
static int read_brightness(void)
{
int fd = open(BACKLIGHT, O_RDONLY);
if (fd < 0) return -1;
char buf[16];
int n = read(fd, buf, sizeof buf - 1);
close(fd);
if (n <= 0) return -1;
buf[n] = 0;
return atoi(buf);
}
static void write_brightness(int val)
{
char buf[8];
int l = snprintf(buf, sizeof buf, "%d\n", val);
int fd = open(BACKLIGHT, O_WRONLY);
if (fd < 0) return;
if (write(fd, buf, l) < 0) wd_log("backlight write %d: %s", val, strerror(errno));
close(fd);
}
/* 强制 re-pan 到 buffer0:FBIOBLANK(4)->(0) 后 LCDC 扫描地址可能已复位到非 buffer0/失效
* 位置(launcher M2.8 Bug-3-residual 根因),而应用始终画 buffer0,必须无条件把显示翻回
* buffer0,否则唤醒后黑屏/旧帧。 */
static void wd_pan_buf0_force(void)
{
if (wd_fbfd < 0) return;
unsigned char v[160];
memset(v, 0, sizeof v);
if (ioctl(wd_fbfd, FBIOGET_VSCREENINFO, v) < 0) return;
*(uint32_t *)(v + 16) = 0; /* xoffset */
*(uint32_t *)(v + 20) = 0; /* yoffset -> buffer0 */
ioctl(wd_fbfd, FBIOPAN_DISPLAY, v);
}
static void wd_panel_off(void)
{
int br = read_brightness();
wd_saved_bri = (br > 0) ? br : 60;
write_brightness(0);
if (wd_fbfd >= 0) ioctl(wd_fbfd, FBIOBLANK, 4); /* 面板下电 + 挂起 goodix 触摸 */
wd_log("power: panel OFF (was %d -> 0, blank 4)", br);
}
static void wd_panel_on(void)
{
if (wd_fbfd >= 0) ioctl(wd_fbfd, FBIOBLANK, 0);
write_brightness(wd_saved_bri > 0 ? wd_saved_bri : 60);
wd_pan_buf0_force(); /* 唤醒后强制回 buffer0 */
wd_log("power: panel ON (blank 0 + brightness %d)", wd_saved_bri);
}
/* launcher 正在画电源菜单(/tmp/meow_power_menu_active 存在)→ watchdog 完全让位。 */
static int wd_menu_active(void)
{
return access(PWR_MENU_ACTIVE, F_OK) == 0;
}
/* 强制关机兜底:与 launcher sys_do_reboot 同路径(真机关机,正常不返回)。 */
static void wd_do_poweroff(void)
{
sync();
wd_log("sys_reboot POWER_OFF");
syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
LINUX_REBOOT_CMD_POWER_OFF, NULL);
wd_log("sys_reboot returned: %s", strerror(errno)); /* 正常不会返回 */
for (;;) usleep(1000000);
}
/* 电源键(KEY_POWER)边沿驱动:单击(短按松开 <3s)= 息屏/亮屏;按下时屏已黑 = 只唤醒。
* 长按(≥3s)由 wd_power_tick() 在轮询中检测 → 写 /tmp/meow_power_menu 请 launcher 弹菜单;
* 松开边沿不再 toggle(菜单/长按语义归 launcher 菜单,防「长按后仍 toggle」)。 */
static void handle_power(int val)
{
if (wd_menu_active()) return; /* 电源菜单期:launcher 独占物理键语义 */
if (val) {
/* 按下边沿 */
pwr_down = 1;
pwr_down_ms = now_ms();
pwr_menu_notified = 0;
pwr_force_done = 0;
int br = read_brightness();
pwr_woke = (br <= 0) ? 1 : 0; /* 屏已黑:本次电源=唤醒,松开不 toggle */
if (pwr_woke) wd_panel_on(); /* 立即亮屏(launcher 自动息屏唤醒路径同步醒,双保险幂等) */
wd_log("power down (bri=%d woke=%d)", br, pwr_woke);
return;
}
/* 松开边沿 */
if (!pwr_down) return; /* 未跟踪到按下(启动前已按住/残留):忽略,防误 toggle */
pwr_down = 0;
unsigned long long held = now_ms() - pwr_down_ms;
if (pwr_woke) { pwr_woke = 0; wd_log("power release after wake (%llums) -> no toggle", held); return; }
if (pwr_menu_notified) {
/* 本次已请求电源菜单(≥3s):菜单是否画出由 launcher 负责,这里绝不 toggle。 */
wd_log("power release after menu req (%llums) -> menu owns toggle", held);
pwr_menu_notified = 0;
return;
}
if (held >= PWR_TOGGLE_MS) { wd_log("power hold %llums -> no-op (menu/force-off in tick)", held); return; }
int br = read_brightness();
if (br > 0) wd_panel_off();
else wd_panel_on(); /* 屏已黑仍短按:亮屏 */
wd_log("power short-press %llums -> toggle panel (bri was %d)", held, br);
}
/* 电源长按检测(每轮询 tick 调):≥3s 写菜单请求;≥8s 强制关机兜底(仅当菜单未生效时)。 */
static void wd_power_tick(void)
{
/* launcher 已接管电源菜单:本进程完全让位,清空电源状态,下一手势从零开始。 */
if (wd_menu_active()) {
if (pwr_down) wd_log("power: launcher menu active -> watchdog inert");
pwr_down = 0; pwr_menu_notified = 0; pwr_force_done = 0; pwr_woke = 0;
return;
}
if (!pwr_down) return;
unsigned long long held = now_ms() - pwr_down_ms;
if (held >= PWR_MENU_MS && !pwr_menu_notified) {
int fd = open(PWR_MENU_MARKER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd >= 0) { write(fd, "1", 1); close(fd); }
pwr_menu_notified = 1;
wd_log("power hold %llums -> %s (launcher draws menu)", held, PWR_MENU_MARKER);
}
if (held >= PWR_FORCE_MS && !pwr_force_done && !pwr_menu_notified) {
/* 长按 8s 仍无菜单接管(launcher 未响应/未消费)→ 强制关机兜底。 */
pwr_force_done = 1;
wd_log("power hold %llums -> FORCE POWER OFF", held);
wd_do_poweroff();
}
}
/* ---- M2.24 音量接管(watchdog 自实现,与 launcher 同源路径:写 DAC + settings.conf)----
* launcher 菜单期由 launcher 调 settings_volume_adj;应用运行期 launcher 卡在 waitpid、
* 不调音量,故由本进程接管。音量值持久化到与 launcher 相同的 settings.conf,launcher 在
* 应用返回(launch_app 末尾)re-load,保证菜单音量与 DAC 一致。 */
static int wd_vol = 60; /* 当前音量 0-100(= 写入 DAC 的映射值) */
static int wd_vol_max = 100; /* 上限 */
static int wd_vol_step = 1; /* 跳步 */
static void wd_vol_load(void)
{
wd_vol = 60; wd_vol_max = 100; wd_vol_step = 1;
FILE *f = fopen(MEOW_SETTINGS_FILE, "r");
if (!f) { wd_log("vol: no settings, default %d", wd_vol); return; }
char buf[128];
while (fgets(buf, sizeof buf, f)) {
char *eq = strchr(buf, '=');
if (!eq) continue;
*eq = 0;
char *k = buf; int kl = (int)strlen(k);
while (kl > 0 && (k[kl-1] == ' ' || k[kl-1] == '\t')) k[--kl] = 0;
char *v = eq + 1; while (*v == ' ' || *v == '\t') v++;
int iv = atoi(v);
if (!strcmp(k, "audio.volume")) { if (iv >= 0 && iv <= 100) wd_vol = iv; }
else if (!strcmp(k, "audio.volume_max")) { if (iv >= 0 && iv <= 100) wd_vol_max = iv; }
else if (!strcmp(k, "audio.volume_step")){ if (iv >= 1 && iv <= 5) wd_vol_step = iv; }
}
fclose(f);
wd_log("vol: loaded volume=%d max=%d step=%d", wd_vol, wd_vol_max, wd_vol_step);
}
/* 写真实 DAC(CS43198, numid 1/2, 0-255),与 launcher meow_dac_vol_write 同路径。 */
static void wd_vol_write(int pct)
{
int dac = pct * 255 / 100;
if (dac < 0) dac = 0;
if (dac > 255) dac = 255;
char cmd[160];
snprintf(cmd, sizeof cmd,
"amixer -c 0 cset numid=1 %d >/dev/null 2>&1; amixer -c 0 cset numid=2 %d >/dev/null 2>&1",
dac, dac);
system(cmd);
wd_log("DAC vol -> L/R=%d (pct %d)", dac, pct);
}
static void wd_vol_save(void)
{
system("mkdir -p /usr/data/meow 2>/dev/null");
int fd = open(MEOW_SETTINGS_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) { wd_log("vol: open %s fail: %s", MEOW_SETTINGS_FILE, strerror(errno)); return; }
char buf[128];
int l = snprintf(buf, sizeof buf,
"# meow-settings 1\naudio.volume = %d\naudio.volume_max = %d\naudio.volume_step = %d\n",
wd_vol, wd_vol_max, wd_vol_step);
if (write(fd, buf, l) < 0) wd_log("vol: write fail: %s", strerror(errno));
close(fd);
wd_log("vol: saved volume=%d(max=%d,step=%d)", wd_vol, wd_vol_max, wd_vol_step);
}
/* 音量步进:+1/-1(每按下边沿一次;clamp 到极大/最小)。 */
static void wd_vol_adj(int dir)
{
int target = wd_vol + dir * wd_vol_step;
if (target < 0) target = 0;
if (target > wd_vol_max) target = wd_vol_max;
if (target == wd_vol) { wd_log("volume %s at limit (%d)", dir > 0 ? "UP" : "DOWN", wd_vol); return; }
wd_vol = target;
wd_vol_write(wd_vol);
wd_vol_save();
wd_log("volume %s -> %d", dir > 0 ? "UP" : "DOWN", wd_vol);
}
static void handle_touch(int immersive, int app_pid,
unsigned long long start_ms, int btn_val)
{
if (btn_val) {
/* press edge (BTN_TOUCH value 1).
* 1) Cooldown: any press within COOLDOWN_MS of app start is the
* remnant of the menu launch tap, not a deliberate back gesture.
* 2) No coordinate seen yet -> cannot be a corner press, ignore. */
if (now_ms() - start_ms < COOLDOWN_MS) return;
if (last_x < 0 || last_y < 0) return;
down_x = last_x; down_y = last_y;
down_ms = now_ms();
down_valid = 1;
return;
}
/* release edge (BTN_TOUCH value 0). A value-driven edge (not a toggle)
* means a stray/out-of-sync UP never latches coords; only a real press
* that was actually tracked (down_valid) can reach the dispatch below. */
if (!down_valid) return; /* stray UP: nothing tracked */
down_valid = 0;
if (now_ms() - down_ms > SHORT_PRESS_MS) return; /* held too long */
/* M2.21:返回键彻底移除。watchdog 不再有「按钮命中->SIGTERM」路径(那是
* 虚拟返回键的年代)。返回手势统一为「左缘起点 + 向右滑 SWIPE_DX」→ SIGUSR1,
* 不论 immersive 与否(返回键没了,所有 app 都靠左滑返回)。app 卡死仍可由
* 外层强杀序列(音量减x3)兜底。 */
if (down_x < EDGE_W && (last_x - down_x) > SWIPE_DX) {
wd_log("swipe -> SIGUSR1");
kill(app_pid, SIGUSR1);
}
}
int main(int argc, char **argv)
{
if (argc < 2) {
fprintf(stderr, "usage: %s <app_pid> [--sim-tap N] [--sim-swipe N]\n", argv[0]);
return 1;
}
int app_pid = atoi(argv[1]);
int sim_tap = 0, sim_swipe = 0;
for (int i = 2; i < argc; i++) {
if (!strcmp(argv[i], "--sim-tap") && i + 1 < argc) sim_tap = atoi(argv[++i]);
if (!strcmp(argv[i], "--sim-swipe") && i + 1 < argc) sim_swipe = atoi(argv[++i]);
}
setvbuf(stderr, NULL, _IONBF, 0);
wd_log_init();
wd_vol_load(); /* M2.24:音量初始值读自与 launcher 同源 settings.conf */
wd_log("app_pid=%d sim_tap=%d sim_swipe=%d", app_pid, sim_tap, sim_swipe);
/* cooldown anchor: ~the moment this watchdog started (== app launch). */
unsigned long long start_ms = now_ms();
/* fb;fd 存全局 wd_fbfd 供电源键面板 ioctl(FBIOBLANK/FBIOPAN)复用 */
wd_fbfd = open("/dev/fb0", O_RDWR);
if (wd_fbfd >= 0) {
unsigned char vinfo[160], finfo[68];
memset(vinfo, 0, sizeof vinfo);
memset(finfo, 0, sizeof finfo);
if (ioctl(wd_fbfd, FBIOGET_VSCREENINFO, vinfo) == 0 &&
ioctl(wd_fbfd, FBIOGET_FSCREENINFO, finfo) == 0) {
xres = *(uint32_t *)(vinfo + 0);
yres = *(uint32_t *)(vinfo + 4);
bpp = *(uint32_t *)(vinfo + 24);
line_len = *(uint32_t *)(finfo + 44);
if (!line_len) line_len = xres * (bpp / 8);
fb = mmap(NULL, (size_t)line_len * yres, PROT_READ | PROT_WRITE,
MAP_SHARED, wd_fbfd, 0);
if (fb == MAP_FAILED) fb = NULL;
} else {
fb = NULL;
}
}
/* input devices */
struct pollfd fds[8];
int nfd = 0;
char dev[32];
for (int i = 0; i < 8; 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; nfd++; }
}
/* drain once: anything already buffered (the launch tap's tail, which the
* launcher may not have fully consumed) is discarded so this process never
* sees a half-gesture as a clean press/release pair. */
for (int i = 0; i < nfd; i++) {
unsigned char ev[16];
while (read(fds[i].fd, ev, sizeof ev) == (ssize_t)sizeof ev) { /* discard */ }
}
long elapsed = 0;
for (;;) {
/* app gone? */
if (kill(app_pid, 0) < 0 && errno == ESRCH) {
wd_log("app gone, exit");
break;
}
int immersive = read_state_file();
/* M2.21:返回键彻底移除(用户要求)。不再画左上角 48x48 虚拟返回键,
* 也不再在非沉浸分支处理「按钮命中->SIGTERM」。返回手势统一由 SDK/
* 应用侧处理;watchdog 仅保留兜底(app 卡死强杀、存活检测)。 */
(void)immersive;
if (sim_tap && elapsed >= (long)sim_tap * 1000) {
wd_log("SIM TAP");
kill(app_pid, SIGTERM);
usleep(1000000);
kill(app_pid, SIGKILL);
break;
}
if (sim_swipe && elapsed >= (long)sim_swipe * 1000) {
wd_log("SIM SWIPE");
kill(app_pid, SIGUSR1);
sim_swipe = 0;
}
int r = poll(fds, nfd, 100);
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) last_x = val;
else if (code == ABS_Y || code == ABS_MT_POSITION_Y) last_y = val;
} else if (type == EV_KEY && code == BTN_TOUCH) {
handle_touch(immersive, app_pid, start_ms, val);
} else if (type == EV_KEY && code == KEY_POWER) {
handle_power(val); /* M2.22(A)/M2.24:电源单击=息屏/亮屏,长按 tick 里判菜单 */
} else if (type == EV_KEY && code == KEY_VOLUMEUP) {
if (val == 1) wd_vol_adj(+1); /* M2.24:音量+ = 系统音量 */
} else if (type == EV_KEY && code == KEY_VOLUMEDOWN) {
if (val == 1) wd_vol_adj(-1); /* M2.24:音量- = 系统音量 */
} else if (type == EV_KEY && (code == KEY_PLAYPAUSE || code == KEY_PREVIOUSSONG ||
code == KEY_NEXTSONG || code == KEY_PREVIOUS)) {
if (val == 1) wd_log("media key captured (code %d) -> consumed, no route", code);
}
}
}
}
wd_power_tick(); /* M2.24:电源长按检测(3s 菜单 / 8s 强关 / 菜单期让位) */
elapsed += 100;
}
return 0;
}