fix(watchdog): 框架App前台时音量/媒体键让位SDK (agent-231) — 启动时经/proc/pid/exe反查app.json framework=1判定,音量/媒体分支 !g_fw_app 门控; 裸App/无SDK/不可读仍兜底(不造成功能真空); 顺手修wd_json_str两边界bug+死代码清理
meow_watchdog→68850d9d; 宿主14项全过; 待跟进: fw_content.conf的watchdog md5打包时更新
This commit is contained in:
1 parent
40b8e33b94
commit
d76f8bc4ba
4 files changed
+207
-30
No files matched your search
Binary file not shown.
@@ -22,6 +22,10 @@
|
|||||||
* 音量±(115/114) -> 写真实 DAC(CS43198 amixer numid=1/2)+ 持久化 settings.conf(同 launcher 同源)。
|
* 音量±(115/114) -> 写真实 DAC(CS43198 amixer numid=1/2)+ 持久化 settings.conf(同 launcher 同源)。
|
||||||
* 播放/上下曲(164/165/163/19) -> 捕获 + 日志 + 消费,不路由(媒体会话落地后转活跃会话)。
|
* 播放/上下曲(164/165/163/19) -> 捕获 + 日志 + 消费,不路由(媒体会话落地后转活跃会话)。
|
||||||
* (8s 强关的「防误触 / pm_interacted」逻辑在 launcher 的 show_power_menu 内,此进程只做兜底。)
|
* (8s 强关的「防误触 / pm_interacted」逻辑在 launcher 的 show_power_menu 内,此进程只做兜底。)
|
||||||
|
* - M2.86(226) SDK 框架 App 让位:启动时经 /proc/<pid>/exe 读同目录 app.json 的 framework
|
||||||
|
* (true|1|yes)。判定为 SDK 框架 App -> 音量/媒体键让位 SDK 输入层(meow_ui_input.c 已消化,
|
||||||
|
* 防同一次按键双步进);电源仍由本进程兜底(SDK 对电源是 no-op)。裸自绘 App(framework=0/
|
||||||
|
* 无 framework)或 app.json 不可读 -> 音量/媒体键仍由本进程兜底(不造成功能真空)。
|
||||||
* Exits when the app process disappears (kill(pid,0) -> ESRCH) or on
|
* Exits when the app process disappears (kill(pid,0) -> ESRCH) or on
|
||||||
* SIGTERM from the parent.
|
* SIGTERM from the parent.
|
||||||
*
|
*
|
||||||
@@ -116,27 +120,6 @@
|
|||||||
static unsigned char *fb;
|
static unsigned char *fb;
|
||||||
static int xres, yres, line_len, bpp;
|
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)
|
static int read_state_file(void)
|
||||||
{
|
{
|
||||||
/* returns 1 if immersive */
|
/* returns 1 if immersive */
|
||||||
@@ -194,6 +177,10 @@ static int down_valid = 0; /* a press is currently being tracked */
|
|||||||
static int down_x = 0, down_y = 0;
|
static int down_x = 0, down_y = 0;
|
||||||
static unsigned long long down_ms = 0;
|
static unsigned long long down_ms = 0;
|
||||||
|
|
||||||
|
/* M2.86(226):1=当前前台 App 为 SDK 框架 App(音量/媒体键让位 SDK;M2.87 起左缘返回
|
||||||
|
* 手势 SIGUSR1 也照样让位 SDK——框架 App 经 SDK on_swipe 处理返回)。在 handle_touch 前声明。 */
|
||||||
|
static int g_fw_app = 0;
|
||||||
|
|
||||||
/* ---- M2.22(A)+ M2.24 电源键接管:息屏/亮屏 + 长按菜单/强关 ---- */
|
/* ---- M2.22(A)+ M2.24 电源键接管:息屏/亮屏 + 长按菜单/强关 ---- */
|
||||||
static int wd_fbfd = -1; /* fb0 fd(main 里打开,供 FBIOBLANK/FBIOPAN 用) */
|
static int wd_fbfd = -1; /* fb0 fd(main 里打开,供 FBIOBLANK/FBIOPAN 用) */
|
||||||
static int wd_saved_bri = 60; /* 熄屏前亮度快照 */
|
static int wd_saved_bri = 60; /* 熄屏前亮度快照 */
|
||||||
@@ -427,15 +414,96 @@ static void handle_touch(int immersive, int app_pid,
|
|||||||
down_valid = 0;
|
down_valid = 0;
|
||||||
if (now_ms() - down_ms > SHORT_PRESS_MS) return; /* held too long */
|
if (now_ms() - down_ms > SHORT_PRESS_MS) return; /* held too long */
|
||||||
/* M2.21:返回键彻底移除。watchdog 不再有「按钮命中->SIGTERM」路径(那是
|
/* M2.21:返回键彻底移除。watchdog 不再有「按钮命中->SIGTERM」路径(那是
|
||||||
* 虚拟返回键的年代)。返回手势统一为「左缘起点 + 向右滑 SWIPE_DX」→ SIGUSR1,
|
* 虚拟返回键的年代)。返回手势统一为「左缘起点 + 向右滑 SWIPE_DX」→ SIGUSR1。
|
||||||
* 不论 immersive 与否(返回键没了,所有 app 都靠左滑返回)。app 卡死仍可由
|
* M2.87:SIGUSR1 只发给「裸沉浸 App」(framework=0/无 framework 的自绘 App,
|
||||||
* 外层强杀序列(音量减x3)兜底。 */
|
* 其自己处理 SIGUSR1 当返回事件)。SDK 框架 App(g_fw_app=1,如 hello/files/ncm)
|
||||||
|
* 自己经 SDK on_swipe 处理左缘返回/顶部下拉(meow_ui_input.c),若 watchdog 也发
|
||||||
|
* SIGUSR1 → 默认动作杀死 App(ncm `signal=16` 崩溃根因)→ 本进程不发、避免双处理/误杀。
|
||||||
|
* 裸沉浸判定 = 非框架 且 /tmp/meowapp_state=1(app.json immersive 声明,launcher 写)。 */
|
||||||
if (down_x < EDGE_W && (last_x - down_x) > SWIPE_DX) {
|
if (down_x < EDGE_W && (last_x - down_x) > SWIPE_DX) {
|
||||||
wd_log("swipe -> SIGUSR1");
|
if (!g_fw_app && immersive) {
|
||||||
kill(app_pid, SIGUSR1);
|
wd_log("swipe -> SIGUSR1 (bare immersive, framework=%d)", g_fw_app);
|
||||||
|
kill(app_pid, SIGUSR1);
|
||||||
|
} else {
|
||||||
|
wd_log("swipe ignored (framework=%d immersive=%d) -> SDK owns back", g_fw_app, immersive);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- M2.86(226) SDK 框架 App 判定:app.json framework=1 -> 音量/媒体键让位 SDK ----
|
||||||
|
* 背景:App 前台时 SDK 输入层(meow_ui_input.c)已消化音量/媒体键;若 watchdog 也消化,
|
||||||
|
* 同一次物理按键会步进两次(冗余双处理)。判定「SDK 框架 App 在跑」用 app.json 的
|
||||||
|
* framework=true|1|yes —— 这是同类候选中更稳的一个:launcher 启动应用前已读同一 app.json
|
||||||
|
* 并成功 exec(app.json 必然可读、结果确定);SDK 侧无稳定「框架注册」标记
|
||||||
|
* (/tmp/meow_app_activity 交互节流、/tmp/meowapp_sbar 仅状态栏模式变化时写),不宜作判定源。
|
||||||
|
* 判定为框架 App -> watchdog 对音量/媒体键让位(交给 SDK),电源仍由本进程兜底(SDK 对电源
|
||||||
|
* 是 no-op,见 meow_ui_input.c);裸自绘 App(framework=0/无 framework)或 app.json 不可读 ->
|
||||||
|
* watchdog 保持兜底,绝不造成功能真空。 */
|
||||||
|
/* 极简 JSON/扁平值提取:与 launcher util.c json_str 同语义(键前缀须独立 + ':'/'=' + 可选引号)。 */
|
||||||
|
static int wd_json_str(const char *buf, const char *key, char *out, int olen)
|
||||||
|
{
|
||||||
|
out[0] = 0;
|
||||||
|
if (!buf || !key || olen <= 0) return 0;
|
||||||
|
const char *p = strstr(buf, key);
|
||||||
|
if (!p) return 0;
|
||||||
|
if (p > buf) { /* 键前缀须独立:防 "x_framework":1 被误命中为 framework */
|
||||||
|
unsigned char pc = (unsigned char)p[-1];
|
||||||
|
if ((pc >= 'a' && pc <= 'z') || (pc >= 'A' && pc <= 'Z') ||
|
||||||
|
(pc >= '0' && pc <= '9') || pc == '_' || pc == '.')
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
p += strlen(key);
|
||||||
|
if (*p == '"') p++; /* JSON 键右引号 */
|
||||||
|
while (*p == ' ' || *p == '\t') p++; /* 键与 ':'/'=' 间空白(宽松容错) */
|
||||||
|
while (*p == ':' || *p == '=') p++;
|
||||||
|
while (*p == ' ' || *p == '\t') p++;
|
||||||
|
if (*p == '"') p++; /* 值左引号 */
|
||||||
|
int i = 0;
|
||||||
|
while (*p && *p != '"' && *p != ',' && *p != '\n' && *p != '\r' &&
|
||||||
|
*p != '}' && *p != ']' && *p != ':' && *p != ' ' && *p != '\t' && i < olen - 1)
|
||||||
|
out[i++] = *p++;
|
||||||
|
out[i] = 0;
|
||||||
|
while (i > 0 && (out[i - 1] == ' ' || out[i - 1] == '\t')) out[--i] = 0;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 读 <dir>/app.json 的 framework 字段(true|1|yes = 1)。返回 1=SDK 框架 App。 */
|
||||||
|
static int wd_app_is_framework(const char *dir)
|
||||||
|
{
|
||||||
|
char path[576];
|
||||||
|
int n = snprintf(path, sizeof path, "%s/app.json", dir);
|
||||||
|
if (n < 0 || n >= (int)sizeof path) return 0;
|
||||||
|
FILE *f = fopen(path, "r");
|
||||||
|
if (!f) return 0;
|
||||||
|
int fw = 0;
|
||||||
|
char buf[256];
|
||||||
|
while (fgets(buf, sizeof buf, f)) {
|
||||||
|
if (!strstr(buf, "framework")) continue;
|
||||||
|
char v[16] = "";
|
||||||
|
if (wd_json_str(buf, "framework", v, sizeof v) > 0 &&
|
||||||
|
(!strcmp(v, "true") || !strcmp(v, "1") || !strcmp(v, "yes")))
|
||||||
|
fw = 1;
|
||||||
|
break; /* framework 每 app.json 至多一次,读到即定 */
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
return fw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 经 /proc/<pid>/exe 反查应用安装目录(exec 与 app.json 同目录),再判 framework。 */
|
||||||
|
static int wd_framework_from_pid(int pid)
|
||||||
|
{
|
||||||
|
char exe[576], link[64];
|
||||||
|
snprintf(link, sizeof link, "/proc/%d/exe", pid);
|
||||||
|
ssize_t r = readlink(link, exe, sizeof exe - 1);
|
||||||
|
if (r < 0 || r >= (ssize_t)sizeof exe) return 0;
|
||||||
|
exe[r] = 0;
|
||||||
|
char *slash = strrchr(exe, '/');
|
||||||
|
if (slash) *slash = 0; /* 目录 = exec 所在目录(app.json 同目录) */
|
||||||
|
if (!exe[0]) return 0;
|
||||||
|
return wd_app_is_framework(exe);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef WD_NO_MAIN
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
@@ -452,7 +520,11 @@ int main(int argc, char **argv)
|
|||||||
setvbuf(stderr, NULL, _IONBF, 0);
|
setvbuf(stderr, NULL, _IONBF, 0);
|
||||||
wd_log_init();
|
wd_log_init();
|
||||||
wd_vol_load(); /* M2.24:音量初始值读自与 launcher 同源 settings.conf */
|
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);
|
/* M2.86(226):SDK 框架 App 判定(app.json framework=1)-> 音量/媒体键让位 SDK。 */
|
||||||
|
g_fw_app = wd_framework_from_pid(app_pid);
|
||||||
|
wd_log("app_pid=%d sim_tap=%d sim_swipe=%d framework=%d%s",
|
||||||
|
app_pid, sim_tap, sim_swipe, g_fw_app,
|
||||||
|
g_fw_app ? " -> yield vol/media to SDK" : " -> watchdog owns vol/media");
|
||||||
|
|
||||||
/* cooldown anchor: ~the moment this watchdog started (== app launch). */
|
/* cooldown anchor: ~the moment this watchdog started (== app launch). */
|
||||||
unsigned long long start_ms = now_ms();
|
unsigned long long start_ms = now_ms();
|
||||||
@@ -540,12 +612,15 @@ int main(int argc, char **argv)
|
|||||||
} else if (type == EV_KEY && code == KEY_POWER) {
|
} else if (type == EV_KEY && code == KEY_POWER) {
|
||||||
handle_power(val); /* M2.22(A)/M2.24:电源单击=息屏/亮屏,长按 tick 里判菜单 */
|
handle_power(val); /* M2.22(A)/M2.24:电源单击=息屏/亮屏,长按 tick 里判菜单 */
|
||||||
} else if (type == EV_KEY && code == KEY_VOLUMEUP) {
|
} else if (type == EV_KEY && code == KEY_VOLUMEUP) {
|
||||||
if (val == 1) wd_vol_adj(+1); /* M2.24:音量+ = 系统音量 */
|
/* M2.86(226):SDK 框架 App 前台 -> 音量键让位 SDK(防双步进)。 */
|
||||||
|
if (val == 1 && !g_fw_app) wd_vol_adj(+1); /* M2.24:音量+ = 系统音量 */
|
||||||
} else if (type == EV_KEY && code == KEY_VOLUMEDOWN) {
|
} else if (type == EV_KEY && code == KEY_VOLUMEDOWN) {
|
||||||
if (val == 1) wd_vol_adj(-1); /* M2.24:音量- = 系统音量 */
|
if (val == 1 && !g_fw_app) wd_vol_adj(-1); /* M2.24:音量- = 系统音量 */
|
||||||
} else if (type == EV_KEY && (code == KEY_PLAYPAUSE || code == KEY_PREVIOUSSONG ||
|
} else if (type == EV_KEY && (code == KEY_PLAYPAUSE || code == KEY_PREVIOUSSONG ||
|
||||||
code == KEY_NEXTSONG || code == KEY_PREVIOUS)) {
|
code == KEY_NEXTSONG || code == KEY_PREVIOUS)) {
|
||||||
if (val == 1) wd_log("media key captured (code %d) -> consumed, no route", code);
|
/* M2.86(226):框架 App 前台 -> 媒体键让位 SDK(接媒体会话路由)。 */
|
||||||
|
if (val == 1 && !g_fw_app)
|
||||||
|
wd_log("media key captured (code %d) -> consumed, no route", code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -555,3 +630,4 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
#endif /* !WD_NO_MAIN */
|
||||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,101 @@
|
|||||||
|
/* test_wd_fw.c — 宿主单测:meow_watchdog 的「SDK 框架 App 判定」逻辑(M2.86, issue 226)。
|
||||||
|
*
|
||||||
|
* 编译:#include meow_watchdog.c(去 main 拿 wd_app_is_framework/wd_json_str/wd_framework_from_pid)。
|
||||||
|
* gcc -Wall -Wextra -Os -DWd_NO_MAIN -Wno-unused-function -Wno-unused-parameter \
|
||||||
|
* test_wd_fw.c -o test_wd_fw
|
||||||
|
* (meow_watchdog.c 含仅 main 使用的静态函数,宿主去 main 后须 -Wno-unused-function 静音;
|
||||||
|
* mips 目标构建不定义 WD_NO_MAIN,不受影响。)
|
||||||
|
* 运行:./test_wd_fw;全 PASS -> exit 0。
|
||||||
|
*/
|
||||||
|
#define WD_NO_MAIN
|
||||||
|
#include "meow_watchdog.c"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
static int g_pass = 0, g_fail = 0;
|
||||||
|
|
||||||
|
static void check(const char *name, int got, int want)
|
||||||
|
{
|
||||||
|
if (got == want) { g_pass++; printf("PASS %s\n", name); }
|
||||||
|
else { g_fail++; printf("FAIL %s: got %d want %d\n", name, got, want); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 在 <base>/<name> 建目录并写 app.json;写失败返回 -1。 */
|
||||||
|
static int fixture(const char *base, const char *name, const char *appjson)
|
||||||
|
{
|
||||||
|
char dir[512], path[560];
|
||||||
|
snprintf(dir, sizeof dir, "%s/%s", base, name);
|
||||||
|
if (mkdir(dir, 0755) != 0 && access(dir, F_OK) != 0) return -1;
|
||||||
|
snprintf(path, sizeof path, "%s/app.json", dir);
|
||||||
|
FILE *f = fopen(path, "w");
|
||||||
|
if (!f) return -1;
|
||||||
|
fputs(appjson, f);
|
||||||
|
fclose(f);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int expect_fw(const char *base, const char *name, const char *appjson, int want)
|
||||||
|
{
|
||||||
|
char dir[512];
|
||||||
|
snprintf(dir, sizeof dir, "%s/%s", base, name);
|
||||||
|
if (fixture(base, name, appjson) != 0) { g_fail++; printf("FAIL %s: cannot write fixture\n", name); return 0; }
|
||||||
|
char label[256];
|
||||||
|
snprintf(label, sizeof label, "is_fw(%s)", name);
|
||||||
|
check(label, wd_app_is_framework(dir), want);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
char dir[] = "/tmp/wdfwXXXXXX";
|
||||||
|
if (!mkdtemp(dir)) { perror("mkdtemp"); return 2; }
|
||||||
|
|
||||||
|
/* 框架 App:各值形态 */
|
||||||
|
expect_fw(dir, "v_num", "{\"name\":\"x\",\"framework\":1,\"exec\":\"/a/b\"}", 1); /* JSON 数值 1 */
|
||||||
|
expect_fw(dir, "v_true", "{\"name\":\"x\",\"framework\":true}", 1); /* true */
|
||||||
|
expect_fw(dir, "v_yes", "framework=yes", 1); /* 扁平 yes */
|
||||||
|
expect_fw(dir, "v_str", "{\"framework\":\"true\"}", 1); /* 字符串 "true" */
|
||||||
|
expect_fw(dir, "v_space","{\"name\":\"x\", \"framework\" : 1 }", 1); /* 空格容错 */
|
||||||
|
|
||||||
|
/* 非框架 App */
|
||||||
|
expect_fw(dir, "v_zero", "{\"name\":\"x\",\"framework\":0}", 0); /* 数值 0 */
|
||||||
|
expect_fw(dir, "v_false","{\"framework\":false}", 0); /* false */
|
||||||
|
expect_fw(dir, "v_none", "{\"name\":\"x\",\"exec\":\"/a/b\"}", 0); /* 无 framework 键 */
|
||||||
|
expect_fw(dir, "v_sub", "{\"x_framework\":1}", 0); /* 键前缀歧义:不误判 */
|
||||||
|
|
||||||
|
/* 多行 JSON(framework 单独一行,值独立) */
|
||||||
|
expect_fw(dir, "multiline",
|
||||||
|
"{\n \"name\": \"x\",\n \"framework\": 1,\n \"exec\": \"/a/b\"\n}\n", 1);
|
||||||
|
|
||||||
|
/* wd_json_str 直接断言(防 "myframework" / 前缀干扰) */
|
||||||
|
{
|
||||||
|
char out[16] = "";
|
||||||
|
check("json_str exact", wd_json_str("{\"framework\":1}", "framework", out, sizeof out) > 0 &&
|
||||||
|
!strcmp(out, "1"), 1);
|
||||||
|
check("json_str prefix-reject",
|
||||||
|
wd_json_str("{\"x_framework\":1}", "framework", out, sizeof out), 0);
|
||||||
|
check("json_str no-hit", wd_json_str("{\"name\":\"x\"}", "framework", out, sizeof out), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* wd_framework_from_pid:自进程 /proc/self/exe 目录无 app.json -> 0,不崩 */
|
||||||
|
check("from_pid(no app.json)", wd_framework_from_pid(getpid()), 0);
|
||||||
|
|
||||||
|
/* 清理 */
|
||||||
|
{
|
||||||
|
char path[560];
|
||||||
|
const char *names[] = { "v_num","v_true","v_yes","v_str","v_space","v_zero",
|
||||||
|
"v_false","v_none","v_sub","multiline" };
|
||||||
|
for (unsigned i = 0; i < sizeof names / sizeof names[0]; i++) {
|
||||||
|
snprintf(path, sizeof path, "%s/%s/app.json", dir, names[i]); unlink(path);
|
||||||
|
snprintf(path, sizeof path, "%s/%s", dir, names[i]); rmdir(path);
|
||||||
|
}
|
||||||
|
rmdir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n%d pass, %d fail\n", g_pass, g_fail);
|
||||||
|
return g_fail ? 1 : 0;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user