• 全新单提交:清除含敏感信息的旧历史(sudo 口令、内网 IP、SSH 密钥路径、热点 SSID、设备 MAC) • 本地开发文档/测试记录/构建脚本全部移入 gitignored 的 devdocs/ 与 scripts/build/(不入库) • 保留:MeowOS 源码(launcher/watchdog/bootmenu/meow_svcd/SDK)、项目规范文档、mips-repo 增补组件:SDK 统一日志+通知 API、meow_svcd 服务管理器、mips-repo 软件仓库
558 lines
24 KiB
C
558 lines
24 KiB
C
/* 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;
|
||
}
|