Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/test_bgpage.c
T
AskaEth 6b52740ff1 fix: 后台页杀按钮点不动 — app.json framework=1 被误当「不可杀」门控 + am kill 无前台会话不可执行
根因(2026-11-04 真机 dev.player.ncm pid=7520):
- bg_page.c bgm_app_meta 把 app.json 的 framework 字段映射进 bgmgr_row.framework,而
  bgmgr_can_kill 把它当「系统/框架 App 禁止 kill」。但 framework=1 在 MeowOS 里意为「用
  meow_ui.a 的 SDK 框架 App」(docs/APP_DEV_GUIDE.md §9,推荐值,真机 4 个 App 全写 1)
  → 后台页每行「杀」按钮被置灰,bgm_row_tap 命中后直接 return 1、不调 meow_bg_terminate;
  「一键清除后台」同理 0 行可杀。整条链路无日志,故只能靠翻 app.json 才定位(全部设备日志里
  "后台管理页终止后台App" 出现 0 次 = 4 次进页从未真正杀过)。
- 面板 killfg 正常是因为走 am kill → amsd → MSG_AMS_KILL,无 framework 门控;但它其实靠
  amsd 1200ms×2 超时兜底 kill(pid,SIGTERM) 才杀成:util.c 原本在无前台 App 会话时只回
  ACK -2((uint32_t)MEOW_ERR_NOENT),ACK 及时到达时 amsd 判「launcher refused」完全不杀
  (ams_20260915_031339.log 实证),ACK 迟到才侥幸成功(2026-11-04 真机)。

修复:
- meow_bgmgr: 门控位 framework → protected_app(系统级受保护标记;当前无 App 置位,launcher
  自身组件本就不在 App 表/后台表内)。bg_page 不再把 a->framework 送进门控。
- bg_page: 行副文本改存 ctx 持久缓冲(原 rbuf[].sub 指向 bgm_rows 栈帧 → 悬垂,副标题乱码);
  门控拒绝/未命中后台表各留一行 logmsg,「点了没反应」从此可查。
- util.c MSG_AMS_KILL: 先查后台保活表 → meow_bg_terminate 就地走真退序列(与页面「杀」按钮
  同一通道,故 am kill 与页面按钮行为对称),无前台会话也能杀;否则仍走 pending / ACK -2。
- amsd cmd_kill/cmd_start: ipc 失败原因细分(connect failed / timeout, no reply / ...),日志
  不再笼统写 "launcher offline"(响应串保留该字样,MeowPanel api_ams_ctl 依此映射 503)。
- meow_bgplay: meow_bg_terminate 注释/日志写明「后台管理页 / am kill」双入口同通道。

测试(宿主 gcc,产物在 $TMPDIR):
- test_bgpage 40 checks:新增 framework=1 仍判可杀且按钮真 terminate(本 bug 回归)、
  受保护行按钮不杀但留诊断日志、sub 持久化 + 栈抖动不变(悬垂回归)。
- test_bgmgr 30 checks:protected_app 门控矩阵 + framework=1 可杀回归。
- test_bgplay 92 PASS:新增 4b meow_bg_terminate 全链路(受理→term_at_ms→tick SIGTERM→
  on_bg_dead→清槽;空/不在表分别 INVAL/NOENT);TEST_SVC_PATH 改取 $TMPDIR
  (Termux 无 /tmp,原 fputs(NULL) 直接 abort)。
- amsd 宿主集成(fake launcher 桩,4 例):ACK 0→terminate 且不盲杀;ACK -2→refused 不盲杀;
  无 launcher→direct SIGTERM (connect failed);挂起不回→direct SIGTERM (timeout, no reply)。
0warning:util.c 与 main 同告警逐行一致(仅 SDK 头既有 typedef 重定义),其余文件干净。
2026-11-04 21:00:40 +08:00

206 lines
10 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.
/* test_bgpage.c — 批③ 后台管理页 UI 按钮化 宿主子测(bg_page.c 按钮命中/顶栏清除命中)。
*
* 通过 #include bg_page.c 把其 FILE-STATIC 的 bgm_rows/bgm_btn_rect/bgm_row_tap/bgm_top_action
* 平移进本 TU;桩掉 launcher 后台表(meow_bg_count/at/terminate)+ 时钟 + meow_str/logmsg,
* 直接验证:
* P1 bgm_rows 行骨架:后台 App 行(act=i)+ 空态行(act=-1);name 经 app_meta 正确;
* **行副文本指向 ctx 持久缓冲且扛得住栈抖动**(曾指向 bgm_rows 栈帧 → 真机副标题乱码);
* P2 bgm_btn_rect 按钮几何:行右 46×34 圆角钮(rx+rw-12-46 定位);
* P3 bgm_row_tap 行内命中:**仅按钮区 kill(bgm_terminate 一次),行体/按钮外不误杀**;
* **app.json framework=1(SDK 框架 App)照样可杀**(2026-11-04 真机回归);
* 受保护/前台行按钮点击 → 不 terminate(门控,留诊断日志);空态行不响应;
* P4 bgm_top_action 顶栏清除:kill_all 不杀受保护/前台、杀普通后台(含 framework=1 的);
* P5 门控矩阵:bgmgr_can_kill(protected_app/is_fg/pid<=0/普通)。
* 不调用行自绘 bgm_row_draw(纯逻辑),故无需桩 SDK 绘制;行自绘段经 -ffunction-sections/--gc-sections 剔除。
*
* 编译(在 patch_v2/meow_launcherd 下):
* gcc -I../sdk -I. -Wall -Wextra -fsanitize=undefined \
* -ffunction-sections -fdata-sections test_bgpage.c meow_bgmgr.c \
* -Wl,--gc-sections -o test_bgpage
* 运行:./test_bgpage
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "meow_launcher_internal.h"
#include "bg_page.c" /* FILE-STATIC 函数平移进本 TU */
/* ---------- 桩:launcher 后台表(meow_bgplay 视角) ---------- */
static meow_bg_entry_t g_bg[MEOW_BG_MAX];
static int g_bg_n;
static int g_term_calls;
static char g_terminated[16][64];
int meow_bg_count(void) { return g_bg_n; }
const meow_bg_entry_t *meow_bg_at(int i)
{
if (i < 0 || i >= g_bg_n) return NULL;
return &g_bg[i];
}
int meow_bg_terminate(const char *app_id)
{
if (g_term_calls < 16) snprintf(g_terminated[g_term_calls++], 64, "%s", app_id);
return 0;
}
unsigned long long now_ms(void) { return 0; }
const char *meow_str(const char *id) { (void)id; return NULL; } /* 未知 id → meow_str_zh 回退 zh */
static int g_log_n;
void logmsg(const char *fmt, ...) { (void)fmt; g_log_n++; } /* 只计数:门控/未命中应留诊断日志 */
/* 栈抖动:bgm_rows 返回后调用,用来暴露「sub 指向栈帧」的悬垂指针。 */
static void stack_clobber(void)
{
volatile char buf[256];
memset((void *)buf, 'X', sizeof buf);
buf[0] = buf[0]; /* 防优化掉 */
}
/* ---------- 测试态:launcher 应用表(app_meta 用) ---------- */
static struct app g_apps[4];
static void set_app(int i, const char *id, const char *name, char fw)
{
memset(&g_apps[i], 0, sizeof g_apps[i]);
snprintf(g_apps[i].id, sizeof g_apps[i].id, "%s", id);
snprintf(g_apps[i].name, sizeof g_apps[i].name, "%s", name);
g_apps[i].framework = fw;
}
static void bg_reset(int n, const char *id0, pid_t pid0, const char *id1, pid_t pid1)
{
g_bg_n = n; g_term_calls = 0;
memset(g_bg, 0, sizeof g_bg);
if (n >= 1 && id0) {
g_bg[0].used = 1; snprintf(g_bg[0].app_id, sizeof g_bg[0].app_id, "%s", id0);
g_bg[0].pid = pid0; g_bg[0].keepalive = 0;
}
if (n >= 2 && id1) {
g_bg[1].used = 1; snprintf(g_bg[1].app_id, sizeof g_bg[1].app_id, "%s", id1);
g_bg[1].pid = pid1; g_bg[1].keepalive = 0;
}
}
static int g_fails = 0;
static int g_checks = 0;
#define CHECK(cond, msg) do { \
g_checks++; \
if (cond) printf("PASS: %s\n", msg); \
else { printf("FAIL: %s\n", msg); g_fails++; } } while (0)
static int term_count_app(const char *app_id)
{
int c = 0;
for (int i = 0; i < g_term_calls; i++) if (!strcmp(g_terminated[i], app_id)) c++;
return c;
}
int main(void)
{
static bgm_ctx_t b;
memset(&b, 0, sizeof b);
b.apps = g_apps; b.napps = 3;
set_app(0, "os.meow.hello", "播放", 0);
set_app(1, "dev.player.ncm", "流媒体客户端", '1'); /* SDK 框架 App(真机 app.json framework:1) */
set_app(2, "os.meow.sysguard", "系统守护", 0);
static sub_row_t rows[SUB_ROWS_MAX];
printf("=== P1) bgm_rows 行骨架 + 副文本持久化 ===\n");
bg_reset(2, "os.meow.hello", 1001, "dev.player.ncm", 7520);
int rn = bgm_rows(&b, rows, SUB_ROWS_MAX);
CHECK(rn == 2, "2 后台 App → 2 行(无空态/无清除动作行)");
CHECK(rows[0].kind == SUB_ROW && rows[0].act == 0, "row0 act=0");
CHECK(!strcmp(rows[0].label, "播放"), "row0 label=名字(播放)");
CHECK(rows[1].kind == SUB_ROW && rows[1].act == 1, "row1 act=1");
CHECK(!strcmp(rows[1].label, "流媒体客户端"), "row1 label=流媒体客户端");
CHECK(rows[1].sub != NULL && !strncmp(rows[1].sub, "CPU ", 4), "row1 sub=CPU/内存/状态");
CHECK(b.n == 2, "bgm_ctx.n=2");
/* 悬垂指针回归:sub 必须落在 ctx 缓冲里,且栈被改写后内容不变 */
uintptr_t subp = (uintptr_t)rows[1].sub;
uintptr_t lob = (uintptr_t)(char *)&b;
uintptr_t hib = lob + sizeof b;
CHECK(subp >= lob && subp < hib, "sub 指向 ctx 持久缓冲(不是 bgm_rows 栈帧)");
char sub_before[80];
snprintf(sub_before, sizeof sub_before, "%s", rows[1].sub);
stack_clobber();
CHECK(!strcmp(rows[1].sub, sub_before), "栈抖动后 sub 内容不变(无悬垂)");
printf("=== P2) bgm_btn_rect 按钮几何(行右 46×34) ===\n");
{
int bx, by, bw, bh; bgm_btn_rect(0, 0, 480, 56, &bx, &by, &bw, &bh);
CHECK(bw == 46 && bh == 34, "按钮 46×34");
CHECK(bx == 480 - 12 - 46, "按钮左缘=行宽-12-46");
CHECK(by == (56 - 34) / 2, "按钮垂直居中");
}
printf("=== P3) bgm_row_tap 行内杀按钮命中(仅按钮区 kill,行区不误杀) ===\n");
bg_reset(2, "os.meow.hello", 1001, "dev.player.ncm", 7520);
bgm_rows(&b, rows, SUB_ROWS_MAX);
/* 按钮区命中:行(0,0,480,56),按钮 x 422-468, y 11-45 */
CHECK(bgm_row_tap(&b, 0, &rows[0], 445, 28, 0, 0, 480, 56) == 1, "按钮点击→返回1(重绘)");
CHECK(term_count_app("os.meow.hello") == 1, "按钮点击→terminate hello 一次");
/* 行体(按钮左/其下区)命中 → 不 kill */
CHECK(bgm_row_tap(&b, 0, &rows[0], 200, 28, 0, 0, 480, 56) == 0, "行体点击→返回0(不杀)");
CHECK(term_count_app("os.meow.hello") == 1, "行体点击→不误杀(仍1次)");
CHECK(bgm_row_tap(&b, 0, &rows[0], 410, 28, 0, 0, 480, 56) == 0, "按钮左侧缘(410)→不杀");
CHECK(bgm_row_tap(&b, 0, &rows[0], 475, 28, 0, 0, 480, 56) == 0, "按钮右侧(475)→不杀");
CHECK(bgm_row_tap(&b, 0, &rows[0], 445, 60, 0, 0, 480, 56) == 0, "按钮下方(60)→不杀");
/* 回归(2026-11-04 真机「杀后台按钮按下去没反应」):dev.player.ncm 的 app.json 写 framework:1
* (= 用 SDK 框架,现有 App 全写 1),**必须照杀**——旧实现把它当受保护位,按钮置灰点不动。 */
CHECK(b.rows[1].killable == 1, "framework=1(SDK 框架)App 仍判可杀");
CHECK(bgm_row_tap(&b, 1, &rows[1], 445, 28, 0, 0, 480, 56) == 1, "framework=1 App 按钮→返回1");
CHECK(term_count_app("dev.player.ncm") == 1, "framework=1 App 按钮→真 terminate(无反应 bug 回归)");
/* 真·受保护行(未来系统组件/前台):按钮命中但不 terminate,且留诊断日志 */
bg_reset(2, "os.meow.hello", 1001, "os.meow.sysguard", 1002);
bgm_rows(&b, rows, SUB_ROWS_MAX);
b.rows[1].protected_app = '1';
g_log_n = 0;
CHECK(bgm_row_tap(&b, 1, &rows[1], 445, 28, 0, 0, 480, 56) == 1, "受保护行按钮→返回1(重绘)");
CHECK(term_count_app("os.meow.sysguard") == 0, "受保护行按钮→不 terminate");
CHECK(g_log_n >= 1, "受保护行按钮→留一行诊断日志(可查「点了没反应」)");
printf("=== P3b) 空态行不响应 ===\n");
bg_reset(0, NULL, 0, NULL, 0);
int rn0 = bgm_rows(&b, rows, SUB_ROWS_MAX);
CHECK(rn0 == 1 && rows[0].act == -1, "0 后台 App → 1 空态行(act=-1)");
CHECK(bgm_row_tap(&b, 0, &rows[0], 445, 28, 0, 0, 480, 56) == 0, "空态行点击→返回0(不杀)");
g_term_calls = 0;
printf("=== P4) 顶栏清除(一键清除后台)不杀受保护/前台 ===\n");
bg_reset(2, "os.meow.hello", 1001, "os.meow.sysguard", 1002);
bgm_rows(&b, rows, SUB_ROWS_MAX);
b.rows[1].protected_app = '1';
CHECK(bgm_top_action(&b) == 1, "顶栏清除按钮:返回 1(重绘约定)");
CHECK(term_count_app("os.meow.hello") == 1, "清除→terminate 普通后台 hello");
CHECK(term_count_app("os.meow.sysguard") == 0, "清除→不杀受保护 sysguard");
/* 回归:framework=1 的 SDK 框架 App 也在「一键清」范围内 */
bg_reset(2, "os.meow.hello", 1001, "dev.player.ncm", 7520);
bgm_rows(&b, rows, SUB_ROWS_MAX);
g_term_calls = 0;
CHECK(bgm_top_action(&b) == 1, "framework=1 App:一键清返回 1");
CHECK(term_count_app("dev.player.ncm") == 1, "一键清→terminate framework=1 App(回归)");
/* 前台 App(is_fg)不杀:给 hello 标前台再清 */
bg_reset(1, "os.meow.hello", 1001, NULL, 0);
bgm_rows(&b, rows, SUB_ROWS_MAX);
for (int i = 0; i < b.n; i++) if (!strcmp(b.rows[i].app_id, "os.meow.hello")) b.rows[i].is_fg = 1;
g_term_calls = 0;
CHECK(bgm_top_action(&b) == 1, "前台 App 仍在:顶栏清除返回 1(重绘)");
CHECK(g_term_calls == 0, "前台 App → kill_all 不杀(0 terminate)");
CHECK(bgm_top_action(&b) == 1, "二次清除(仍前台/不可杀):返回 1");
CHECK(g_term_calls == 0, "二次清除 → 仍不 terminate");
printf("=== P5) 门控矩阵(bgmgr_can_kill) ===\n");
{
bgmgr_row_t r; memset(&r, 0, sizeof r);
r.pid = 5; CHECK(bgmgr_can_kill(&r) == 1, "普通后台可杀");
r.protected_app = '1'; CHECK(bgmgr_can_kill(&r) == 0, "protected_app 不可杀");
r.protected_app = 0; r.is_fg = 1; CHECK(bgmgr_can_kill(&r) == 0, "前台不可杀");
r.is_fg = 0; r.pid = 0; CHECK(bgmgr_can_kill(&r) == 0, "pid<=0 不可杀");
}
if (g_fails == 0) printf("\nALL PASS (%d checks)\n", g_checks);
else printf("\n%d FAILED\n", g_fails);
return g_fails ? 1 : 0;
}