Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/bg_page.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

323 lines
15 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.
/* bg_page.c — 批③ 后台管理页(桌面顶栏居右按钮进入)。
*
* 容器:sub_page_t 脚手架(subpage.c,与 wifi_page/bt_page 同款)。顶栏=meow_topappbar:
* 返回箭头 + 标题「后台管理」+ **顶栏标题栏右侧「一键清除后台」按钮**(sub_page_t.top_action,
* act=bgm_top_action → bgmgr_kill_all,**不杀受保护/当前前台/非后台**,经 bgmgr_can_kill 门控)。
* 列表(live=1 约 2s 重扫,实时增删):
* - 每后台 App 一行 SUB_ROW:label=名字,sub=「CPU x% · yMB · 状态」,**行最右一个「杀」按钮**
* (sub_page_t.row_draw 行自绘 + row_tap 自定义命中:命中按钮区=kill 该 App;行体不响应)。
* - 行=MD3 圆角卡(meow_gfx_fill_round r=12 surfaceContainer;卡在行高内上下对称内缩
* BGM_CARD_GAP/2,行中心=卡中心,相邻卡 8px 外距——与主界面列表/ ncm 歌单同手法)。
* - 空态「无后台 App」。
* 数据源=amsd FG/BG 注册表(后台常驻集=launcher meow_bg 表,EV BG/FG 上报进 amsd)+ 每进程
* pcpu/rss(meow_svc_perf 同源 /proc 聚合,本页在 launcher 内同口径取)。
*
* 按钮化(相对 agent-425 的「点行 kill + 顶置清除动作行」):
* - kill:改「点整行」为「行最右独立杀按钮」——行自绘(bgm_row_draw)+ 自定义命中(bgm_row_tap),
* 命中几何=bgm_btn_rect(行右 46×34 圆角按钮);行体其它区域 point 回 0(不杀)。
* - 清:删顶置「一键清除后台」SUB_ACT 动作行,改为顶栏标题栏右侧 action 按钮(top_action)。
*/
#include "meow_launcher_internal.h"
#include "meow_bgplay.h"
#include "meow_bgmgr.h"
#include <ctype.h>
/* 行内杀按钮尺寸(圆心/圆角定位用;命中与绘制共用同一几何,保证所见即所点) */
#define BGM_BTN_W 46
#define BGM_BTN_H 34
/* 圆角卡列表项外边距(item margin/gap):相邻卡片间可见空隙 px。SDK listview 行距(=SUB_ITEM_H)
* 不变——卡片在其行高内上下对称内缩 BGM_CARD_GAP/2,故行内内容(按行高垂直居中)仍落卡中心;
* 相邻卡顶间距恒=SUB_ITEM_H=卡高+BGM_CARD_GAP。要求偶数,保证上下对称。 */
#define BGM_CARD_GAP 8
static inline int bgm_row_card_y(int row_top) { return row_top + BGM_CARD_GAP / 2; }
static inline int bgm_row_card_h(int h) { return h - BGM_CARD_GAP; }
typedef struct {
struct app *apps;
int napps;
bgmgr_row_t rows[MEOW_BGMR_ROW_MAX];
int n;
/* 行副文本存储:sub_row_t.sub 被 SDK 行池/自绘在整页会话内持续读取,
* 不能指向 bgm_rows 的栈帧(曾用函数内 char sub[80] → 悬垂,页面副标题为乱码)。 */
char subs[MEOW_BGMR_ROW_MAX][80];
} bgm_ctx_t;
/* 找 struct app(launcher 应用表)by id */
static struct app *bgm_app_find(struct app *apps, int napps, const char *app_id)
{
if (!apps || !app_id) return NULL;
for (int i = 0; i < napps; i++)
if (apps[i].id[0] && !strcmp(apps[i].id, app_id)) return &apps[i];
return NULL;
}
/* ---- meow_bgmgr ops 注入(UI 侧真实实现)---- */
static int bgm_app_meta(void *ctx, const char *app_id, pid_t pid,
char *name, int nl, char *icon, int il,
char *protected_app, int *is_fg)
{
(void)pid;
bgm_ctx_t *b = ctx;
struct app *a = bgm_app_find(b->apps, b->napps, app_id);
*protected_app = 0;
*is_fg = 0;
if (!a) return -1; /* 未注册 → name 回退 app_id */
if (a->name[0]) snprintf(name, nl, "%s", a->name);
if (icon && il > 0) snprintf(icon, il, "%s", a->icon[0] ? a->icon : "");
/* 刻意**不**用 a->framework:app.json 的 framework=1 意为「用 meow_ui.a 的 SDK 框架 App」
* (docs/APP_DEV_GUIDE.md §9,推荐且现有 App 全写 1),不是「系统 App」。曾把它当「禁止 kill」
* 门控 → 后台页所有行的「杀」按钮被置灰、点了不杀也不留日志(2026-11-04 真机 dev.player.ncm)。
* launcher 自身组件(状态栏/设置页/子页…)不在 App 表也不在后台表,无需此位保护。 */
return 0;
}
static int bgm_enumerate(void *ctx, bgmgr_row_t *rows, int max)
{
(void)ctx;
/* 逐槽扫描并**压缩**(g_bg 可能有空洞),跳过已发出终止的条目:
* meow_bg_terminate 只置 term_at_ms 发 SIGTERM,真正的 reap/摘表在 meow_bg_tick(launcher
* 主循环),它在本页 meow_subpage_run 阻塞期间**不跑**——若不在此过滤,kill/清后旧行会一直
* 残留(pid 已死/0 CPU),页面上看就是「点不动」。过滤后 kill/一键清当下即从列表消失(可见反馈)。 */
int k = 0;
for (int i = 0; i < MEOW_BG_MAX && k < max; i++) {
const meow_bg_entry_t *e = meow_bg_at(i);
if (!e || !e->used || !e->app_id[0]) continue;
if (e->term_at_ms) continue; /* 已进入终止序列(kill/宽限回收/卡拔)→ 行不再显示 */
memset(&rows[k], 0, sizeof rows[k]);
snprintf(rows[k].app_id, sizeof rows[k].app_id, "%s", e->app_id);
rows[k].pid = e->pid;
rows[k].keepalive = (e->keepalive ? 1 : 0);
rows[k].on_card = (e->on_card ? 1 : 0);
rows[k].bg_since_ms = e->bg_since_ms;
k++;
}
return k;
}
/* proc_sample:/proc/<pid>/stat(utime+stime 差分) + /proc/<pid>/status(VmRSS)。
* 与 meow_svc_perf 897-983 每进程 pcpu/rss 聚合同口径(同 /proc 源)。 */
static long g_cpu_pid[MEOW_BG_MAX] = {0};
static unsigned long long g_cpu_tick[MEOW_BG_MAX] = {0};
static int bgm_proc_sample(void *ctx, pid_t pid, double *pcpu, long *rss_kb)
{
(void)ctx;
if (pid <= 0) return -1;
char path[64], line[512];
long rss = 0;
snprintf(path, sizeof path, "/proc/%d/status", pid);
int fd = open(path, O_RDONLY);
if (fd >= 0) {
ssize_t nr = read(fd, line, sizeof line - 1); close(fd);
if (nr > 0) { line[nr] = 0;
char *p = strstr(line, "VmRSS:");
if (p) p = strchr(p, ' ');
if (p) { while (*p == ' ' || *p == '\t') p++; rss = atol(p); }
}
}
double pc = 0.0;
snprintf(path, sizeof path, "/proc/%d/stat", pid);
fd = open(path, O_RDONLY);
if (fd >= 0) {
ssize_t nr = read(fd, line, sizeof line - 1); close(fd);
if (nr > 0) {
line[nr] = 0;
char *rp = strrchr(line, ')');
if (rp) {
char *p = rp + 2;
unsigned long long ut[2] = {0, 0};
for (int f = 0; f < 14; f++) {
while (*p == ' ') p++;
char *end; unsigned long long v = strtoull(p, &end, 10);
if (end == p) { p++; continue; }
p = end;
if (f == 11) ut[0] = v; /* utime */
if (f == 12) ut[1] = v; /* stime */
}
unsigned long long cur = ut[0] + ut[1];
static double hz = 0.0;
if (hz <= 0) { hz = (double)sysconf(_SC_CLK_TCK); if (hz <= 0) hz = 100.0; }
unsigned long long prev = 0; int had = 0, slot = -1;
for (int i = 0; i < MEOW_BG_MAX; i++) {
if (g_cpu_pid[i] == pid) { prev = g_cpu_tick[i]; had = 1; slot = i; break; }
}
if (!had) {
for (int i = 0; i < MEOW_BG_MAX; i++) if (g_cpu_pid[i] == 0) { slot = i; break; }
if (slot < 0) slot = 0;
}
if (slot >= 0) { g_cpu_pid[slot] = pid; g_cpu_tick[slot] = cur; }
if (had) { pc = 100.0 * (double)(cur - prev) / hz; if (pc < 0) pc = 0; }
}
}
}
*pcpu = pc;
*rss_kb = rss;
return 0;
}
/* ---- rows(填 sub_row_t 列表;每后台 App 一行,空态一行)---- */
static int bgm_rows(void *ctx, sub_row_t *rbuf, int max)
{
bgm_ctx_t *b = ctx;
bgmgr_ops_t ops;
memset(&ops, 0, sizeof ops);
ops.ctx = b;
ops.enumerate = bgm_enumerate;
ops.app_meta = bgm_app_meta;
ops.proc_sample = bgm_proc_sample;
b->n = bgmgr_collect(&ops, b->rows, MEOW_BGMR_ROW_MAX);
int r = 0;
if (b->n == 0) {
if (r < max) {
rbuf[r].kind = SUB_ROW;
rbuf[r].label = meow_str_zh("bgmgr.empty", "无后台 App");
rbuf[r].sub = NULL;
rbuf[r].act = -1;
r++;
}
return r;
}
for (int i = 0; i < b->n && r < max; i++) {
/* 副文本写进 ctx 的持久缓冲(b->subs):sub_row_t.sub 会被 SDK 行池在整页会话内反复读取,
* 指向本函数栈帧的 buffer 会在返回后变成乱码/越界(真机实证:副标题全是乱码字节)。 */
snprintf(b->subs[i], sizeof b->subs[i], "CPU %.1f%% · %s · %s", b->rows[i].pcpu,
bgmgr_mem_str(b->rows[i].rss_kb), bgmgr_status_str(&b->rows[i]));
rbuf[r].kind = SUB_ROW;
rbuf[r].label = b->rows[i].name[0] ? b->rows[i].name : b->rows[i].app_id;
rbuf[r].sub = b->subs[i];
rbuf[r].act = i; /* row_tap/row_draw 用 i 定位行 */
r++;
}
return r;
}
/* kill 通道(bgmgr_kill_all 的函数指针签名:void(*)(const char*)) */
static void bgm_kill_fn(const char *app_id) { meow_bg_terminate(app_id); }
/* ---- 行内杀按钮矩形(绘制与命中共用;行视口矩形 rx,ry,rw,rh → 右侧 46×34 圆角钮)----
* 卡在行高内上下对称内缩 BGM_CARD_GAP/2(行中心=卡中心);按钮垂直居中于卡内,故其中心
* =卡中心=行中心。命中矩形仍为整行距(row_tap 传整行视口),按钮几何不变 → 卡内缩后命中不破。 */
static void bgm_btn_rect(int rx, int ry, int rw, int rh, int *bx, int *by, int *bw, int *bh)
{
*bw = BGM_BTN_W;
*bh = BGM_BTN_H;
*bx = rx + rw - 12 - BGM_BTN_W;
*by = bgm_row_card_y(ry) + (bgm_row_card_h(rh) - BGM_BTN_H) / 2;
}
/* ---- 行完全自绘(sub_page_t.row_draw;覆盖默认 renderer)----
* 空态行(act=-1)居中画「无后台 App」;后台 App 行画 名字 + CPU/内存/状态 + 行右「杀」按钮。 */
static void bgm_row_draw(void *ctx, int idx, sub_row_t *r,
int x, int y, int w, int h, int state)
{
(void)idx;
bgm_ctx_t *b = ctx;
const meow_theme *mt = meow_theme_get();
int i = r->act;
/* MD3 圆角卡:行高内上下对称内缩 BGM_CARD_GAP/2(行中心=卡中心),替代扁平行+底部分隔线。 */
int cy = bgm_row_card_y(y);
int ch = bgm_row_card_h(h);
meow_gfx_fill_round(x, cy, w, ch, 12, mt->surfaceContainer);
if (state & MEOW_LISTVIEW_ST_PRESSED)
meow_gfx_fill_round_alpha(x, cy, w, ch, 12, mt->primary, 26); /* 按压态(实时,限卡内) */
if (i < 0 || i >= b->n) { /* 空态行 */
int th = meow_ui_text_height(18);
meow_ui_text_center(x + w / 2, cy + (ch - th) / 2,
r->label ? r->label : meow_str_zh("bgmgr.empty", "无后台 App"),
18, mt->onSurfaceVariant);
return;
}
int th = meow_ui_text_height(20), tsub = meow_ui_text_height(16);
int rx = x + 16; /* 左内边距 */
int ry = cy + (ch - (th + tsub + 4)) / 2; /* 内容在卡内垂直居中(行中心=卡中心) */
if (r->label) meow_ui_text(rx, ry, r->label, 20, mt->onSurface);
if (r->sub) meow_ui_text(rx, ry + th + 4, r->sub, 16, mt->onSurfaceVariant);
/* 行内杀按钮(全形=胶囊(bh/2);「杀」;error=红=杀)——P1 按钮胶囊化。
* 不可杀行(受保护/前台/无进程,bgmgr_can_kill=0)→ 置灰禁点外观,避免误导(按钮可点但不可杀)。 */
int bx, by, bw, bh; bgm_btn_rect(x, y, w, h, &bx, &by, &bw, &bh);
uint32_t btn_col = mt->error; /* 可杀:error 红 */
if (i >= 0 && i < b->n && !b->rows[i].killable)
btn_col = mt->onSurfaceVariant; /* 禁点:置灰(onSurfaceVariant) */
meow_ui_draw_rect_round(bx, by, bw, bh, bh / 2, btn_col);
int bth = meow_ui_text_height(16);
if (bth > bh) bth = bh;
meow_ui_text_center(bx + bw / 2, by + (bh - bth) / 2, "杀", 16, btn_col);
}
/* ---- 行内自定义命中(sub_page_t.row_tap;仅按钮区 kill,行体不响应/不误杀)---- */
static int bgm_row_tap(void *ctx, int idx, sub_row_t *r, int x, int y,
int rx, int ry, int rw, int rh)
{
(void)idx;
bgm_ctx_t *b = ctx;
int i = r->act;
if (i < 0 || i >= b->n) return 0; /* 空态行无按钮 */
int bx, by, bw, bh; bgm_btn_rect(rx, ry, rw, rh, &bx, &by, &bw, &bh);
if (x < bx || x >= bx + bw || y < by || y >= by + bh) return 0; /* 行体/按钮外 → 不杀 */
if (!bgmgr_can_kill(&b->rows[i])) {
/* 禁点:受保护/前台/无进程 → 按钮已置灰,不 kill(返回 1=重绘)。
* 留一行日志:「按了没反应」当年因为没有这行日志,只能靠翻 app.json 才查出是门控。 */
logmsg("bgpage: kill 被门控 app=%s pid=%d fg=%d protected=%d",
b->rows[i].app_id, (int)b->rows[i].pid, b->rows[i].is_fg,
b->rows[i].protected_app ? 1 : 0);
return 1;
}
int rc = meow_bg_terminate(b->rows[i].app_id); /* 置 term_at_ms → bgm_enumerate 下一跳行消失(可见反馈) */
if (rc != 0)
logmsg("bgpage: kill app=%s 未命中后台表 rc=%d(列表/后台表不同步)", b->rows[i].app_id, rc);
return 1;
}
/* ---- 行体点击(sub_page_t.detail):不响应(kill 只认行内按钮)---- */
static int bgm_detail(void *ctx, int idx, int act)
{
(void)ctx; (void)idx; (void)act;
return 0;
}
/* ---- 顶栏右侧「一键清除后台」按钮(sub_page_t.top_action_cb)---- */
static int bgm_top_action(void *ctx)
{
bgm_ctx_t *b = ctx;
bgmgr_ops_t ops;
memset(&ops, 0, sizeof ops);
ops.ctx = b;
ops.enumerate = bgm_enumerate; /* 行已由 bgm_rows 建 */
/* kill 只置 term_at_ms(真退/摘表在 launcher 主循环 bgm_tick),此处由 bgm_enumerate 过滤
* term_at_ms 让清后的行当下消失(可见反馈;不可杀行本就在上/一键不杀)。 */
int k = bgmgr_kill_all(&ops, b->rows, b->n, bgm_kill_fn);
if (k < b->n)
logmsg("bgpage: 一键清除 杀 %d/%d(其余为前台/无进程/受保护)", k, b->n);
return 1; /* 重绘:重扫行(已清行消失) */
}
/* ---- 入口:运行后台管理页(阻塞到返回)---- */
int run_bg_page(struct app *apps, int napps)
{
static bgm_ctx_t b;
memset(&b, 0, sizeof b);
b.apps = apps;
b.napps = napps;
sub_page_t p;
memset(&p, 0, sizeof p);
p.title = "后台管理";
p.rows = bgm_rows;
p.detail = bgm_detail;
p.live = 1; /* 后台表/进程变化实时重扫 */
p.ctx = &b;
p.top_action = "一键清除后台"; /* 顶栏标题栏右侧按钮 */
p.top_action_cb = bgm_top_action;
p.row_draw = bgm_row_draw; /* 行自绘(名字/CPU/内存/状态 + 行右杀按钮) */
p.row_tap = bgm_row_tap; /* 行内自定义命中(仅按钮区 kill) */
meow_subpage_run(&p);
/* 清除 CPU 差分缓存(页面会话结束) */
memset(g_cpu_pid, 0, sizeof g_cpu_pid);
memset(g_cpu_tick, 0, sizeof g_cpu_tick);
return 0;
}