diff --git a/patch_v2/meow_launcherd/bg_page.c b/patch_v2/meow_launcherd/bg_page.c index d899d3f..aaccbc6 100644 --- a/patch_v2/meow_launcherd/bg_page.c +++ b/patch_v2/meow_launcherd/bg_page.c @@ -1,25 +1,28 @@ /* 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 重扫,实时增删): - * - 首行「一键清除后台」SUB_ACT(act 回调 → bgmgr_kill_all,**不杀框架/当前前台/非后台**); - * - 每后台 App 一行 SUB_ROW:label=名字,sub=「CPU x% · yMB · 状态」,tap(detail 回调)→ 确认后 - * kill(走 meow_bg_terminate 的 SIGTERM→SIGKILL 真退序列,**带 !framework 门控**)。 + * - 每后台 App 一行 SUB_ROW:label=名字,sub=「CPU x% · yMB · 状态」,**行最右一个「杀」按钮** + * (sub_page_t.row_draw 行自绘 + row_tap 自定义命中:命中按钮区=kill 该 App;行体不响应)。 * - 空态「无后台 App」。 * 数据源=amsd FG/BG 注册表(后台常驻集=launcher meow_bg 表,EV BG/FG 上报进 amsd)+ 每进程 * pcpu/rss(meow_svc_perf 同源 /proc 聚合,本页在 launcher 内同口径取)。 * - * 说明(与「每行最右 kill 按钮 + 顶栏右侧清除按钮」的像素级差异):sub_page_t 行无内嵌按钮、 - * 顶栏仅返回+开关;故 kill 用「点行→确认」,清除用「顶置动作行」。像素级自绘版见 design 备忘。 + * 按钮化(相对 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 -#define BGMR_CLR_ACT 9999 /* 一键清除动作码 */ -#define BGMR_KILL_BASE 1000 /* kill app i:BGMR_KILL_BASE+i */ +/* 行内杀按钮尺寸(圆心/圆角定位用;命中与绘制共用同一几何,保证所见即所点) */ +#define BGM_BTN_W 46 +#define BGM_BTN_H 34 typedef struct { struct app *apps; @@ -135,7 +138,7 @@ static int bgm_proc_sample(void *ctx, pid_t pid, double *pcpu, long *rss_kb) return 0; } -/* ---- rows(填 sub_row_t 列表)---- */ +/* ---- rows(填 sub_row_t 列表;每后台 App 一行,空态一行)---- */ static int bgm_rows(void *ctx, sub_row_t *rbuf, int max) { bgm_ctx_t *b = ctx; @@ -148,13 +151,6 @@ static int bgm_rows(void *ctx, sub_row_t *rbuf, int max) b->n = bgmgr_collect(&ops, b->rows, MEOW_BGMR_ROW_MAX); int r = 0; - if (r < max) { - rbuf[r].kind = SUB_ACT; - rbuf[r].label = "一键清除后台"; - rbuf[r].sub = NULL; - rbuf[r].act = BGMR_CLR_ACT; - r++; - } if (b->n == 0) { if (r < max) { rbuf[r].kind = SUB_ROW; @@ -172,7 +168,7 @@ static int bgm_rows(void *ctx, sub_row_t *rbuf, int max) 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 = sub; - rbuf[r].act = i; /* detail 回调用 i 定位行 */ + rbuf[r].act = i; /* row_tap/row_draw 用 i 定位行 */ r++; } return r; @@ -181,13 +177,59 @@ static int bgm_rows(void *ctx, sub_row_t *rbuf, int max) /* kill 通道(bgmgr_kill_all 的函数指针签名:void(*)(const char*)) */ static void bgm_kill_fn(const char *app_id) { meow_bg_terminate(app_id); } -/* ---- detail(点 SUB_ROW 行=kill 该后台 App)---- */ -static int bgm_detail(void *ctx, int idx, int act) +/* ---- 行内杀按钮矩形(绘制与命中共用;行视口矩形 rx,ry,rw,rh → 右侧 46×34 圆角钮)---- */ +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 = ry + (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; - int i = act; - if (i < 0 || i >= b->n) return 0; + const meow_theme *mt = meow_theme_get(); + int i = r->act; + if (i < 0 || i >= b->n) { /* 空态行 */ + int th = meow_ui_text_height(18); + meow_ui_text_center(x + w / 2, y + (h - th) / 2, + r->label ? r->label : meow_str_zh("bgmgr.empty", "无后台 App"), + 18, mt->onSurfaceVariant); + return; + } + if (state & MEOW_LISTVIEW_ST_PRESSED) + meow_ui_fill_rect_alpha(x, y, w, h, mt->primary, 26); /* 按压态(实时) */ + int th = meow_ui_text_height(20), tsub = meow_ui_text_height(16); + int rx = x + 16; /* 左内边距 */ + int ry = y + (h - (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); + /* 行内杀按钮(圆角框 + 「杀」;error=红=杀) */ + int bx, by, bw, bh; bgm_btn_rect(x, y, w, h, &bx, &by, &bw, &bh); + meow_ui_draw_rect_round(bx, by, bw, bh, 10, mt->error); + int bth = meow_ui_text_height(16); + if (bth > bh) bth = bh; + meow_ui_text_center(bx + bw / 2, by + (bh - bth) / 2, "杀", 16, mt->error); + /* 底部分隔线 */ + if (y + h - 1 <= fb_yres) + meow_ui_fill_rect(x, y + h - 1, w, 1, mt->outlineVariant); +} + +/* ---- 行内自定义命中(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])) { snprintf(b->toast, sizeof b->toast, "该 App 不可杀(框架/前台)"); b->toast_ms = (long)now_ms(); @@ -199,25 +241,28 @@ static int bgm_detail(void *ctx, int idx, int act) return 1; } -/* ---- act(点 SUB_ACT:一键清除)---- */ -static int bgm_act(void *ctx, int idx, int act) +/* ---- 行体点击(sub_page_t.detail):不响应(kill 只认行内按钮)---- */ +static int bgm_detail(void *ctx, int idx, int act) { - (void)idx; - bgm_ctx_t *b = ctx; - if (act == BGMR_CLR_ACT) { - bgmgr_ops_t ops; - memset(&ops, 0, sizeof ops); - ops.ctx = b; - ops.enumerate = bgm_enumerate; /* 行已由 bgm_rows 建 */ - int k = bgmgr_kill_all(&ops, b->rows, b->n, bgm_kill_fn); - if (k > 0) snprintf(b->toast, sizeof b->toast, "已清 %d 个后台 App", k); - else snprintf(b->toast, sizeof b->toast, "无后台 App 可清"); - b->toast_ms = (long)now_ms(); - return 1; - } + (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 建 */ + int k = bgmgr_kill_all(&ops, b->rows, b->n, bgm_kill_fn); + if (k > 0) snprintf(b->toast, sizeof b->toast, "已清 %d 个后台 App", k); + else snprintf(b->toast, sizeof b->toast, "无后台 App 可清"); + b->toast_ms = (long)now_ms(); + return 1; +} + /* ---- 入口:运行后台管理页(阻塞到返回)---- */ int run_bg_page(struct app *apps, int napps) { @@ -227,12 +272,15 @@ int run_bg_page(struct app *apps, int napps) b.napps = napps; sub_page_t p; memset(&p, 0, sizeof p); - p.title = "后台管理"; - p.rows = bgm_rows; - p.detail = bgm_detail; - p.act = bgm_act; - p.live = 1; /* 后台表/进程变化实时重扫 */ - p.ctx = &b; + 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 差分缓存(页面会话结束) */ diff --git a/patch_v2/meow_launcherd/meow_launcher_internal.h b/patch_v2/meow_launcherd/meow_launcher_internal.h index 2befe3a..4df5d43 100644 --- a/patch_v2/meow_launcherd/meow_launcher_internal.h +++ b/patch_v2/meow_launcherd/meow_launcher_internal.h @@ -493,6 +493,18 @@ typedef struct sub_page { int (*detail)(void *ctx, int idx, int act);/* 点 SUB_ROW 行(如连接/设备详情);返回 1=重绘 */ int live; /* 1=驻留时周期性(约2s)重扫行(蓝牙/附近扫描刷新用) */ void *ctx; /* 页面私有上下文 */ + + /* ---- 批③ 后台管理页按钮化(可选;NULL/0=按旧路径,未填页/WiFi/蓝牙/锁屏等不受影响)---- + * 提供下方任一回调即启用对应渲染/命中定制;全 NULL 时行为与旧版完全一致。 */ + const char *top_action; /* 顶栏标题栏右侧动作按钮文案(NULL=无按钮) */ + int (*top_action_cb)(void *ctx); /* 点顶栏动作按钮;返回 1=重绘 */ + /* 行完全自绘(覆盖默认 renderer;页内由 r->kind 区分行型)。state=MEOW_LISTVIEW_ST_*(选中/按压/禁用)。 */ + void (*row_draw)(void *ctx, int idx, sub_row_t *r, + int x, int y, int w, int h, int state); + /* 点 SUB_ROW 行(自定义命中:页内自行区分「行内按钮区 vs 行体」;x,y=tap,rx..rh=行视口矩形); + * 返回 1=重绘。仅 kind==SUB_ROW 且本回调非空时走此路径,否则走 detail。 */ + int (*row_tap)(void *ctx, int idx, sub_row_t *r, int x, int y, + int rx, int ry, int rw, int rh); } sub_page_t; /* 运行子页(阻塞到返回;返回 0)。由 wifi_page.c / bt_page.c 各自实现。 */ diff --git a/patch_v2/meow_launcherd/subpage.c b/patch_v2/meow_launcherd/subpage.c index 271488c..7766880 100644 --- a/patch_v2/meow_launcherd/subpage.c +++ b/patch_v2/meow_launcherd/subpage.c @@ -589,6 +589,42 @@ static void sp_lv_bind(int idx, meow_lv_item *out, void *ctx) } } +/* ---- 行完全自绘(批③:页面经 sub_page_t.row_draw 定制整行;默认 renderer 被覆盖)---- */ +static void sp_lv_draw(int idx, int x, int y, int w, int h, int state, void *ctx) +{ + (void)ctx; + if (!g_sp || !g_sp->row_draw) return; + if (idx < 0 || idx >= g_nrows) return; + g_sp->row_draw(g_sp->ctx, idx, &g_rows[idx], x, y, w, h, state); +} + +/* ---- 顶栏标题栏右侧动作按钮矩形(批③:sub_page_t.top_action)---- */ +static void sp_top_action_rect(int *x, int *y, int *w, int *h) +{ + if (x) *x = 0; if (y) *y = 0; if (w) *w = 0; if (h) *h = 0; + if (!g_sp || !g_sp->top_action) return; + int tw = ui_width(g_sp->top_action, 20); + if (tw <= 0) return; + int bw = tw + 24, bh = 40; /* 含内边距的圆角按钮 */ + *x = fb_xres - 12 - bw; + *y = SUB_TB_Y + (SUB_TB_H - bh) / 2; + *w = bw; *h = bh; +} + +/* 绘制顶栏动作按钮(sp_paint_page 内调用)。 */ +static void sp_paint_top_action(void) +{ + if (!g_sp || !g_sp->top_action) return; + int x, y, w, h; sp_top_action_rect(&x, &y, &w, &h); + if (w <= 0 || h <= 0) return; + const meow_theme *mt = meow_theme_get(); + uint32_t col = mt->primary; + meow_ui_draw_rect_round(x, y, w, h, 10, col); /* 圆角描边按钮 */ + int th = meow_ui_text_height(20); + if (th > h) th = h; + meow_ui_text_center(x + w / 2, y + (h - th) / 2, g_sp->top_action, 20, col); +} + /* ---- meow_scroll 回调(包 listview)---- */ static void sp_sc_set(void *ctx, int off) { (void)ctx; if (g_lv) meow_listview_set_scroll(g_lv, off); } static int sp_sc_max(void *ctx) { (void)ctx; return g_lv ? meow_listview_max_scroll(g_lv) : 0; } @@ -648,6 +684,8 @@ static void sp_configure_page(const sub_page_t *p, int top_sw_en, int top_on) meow_switch_set(g_sw, top_on); } } + /* 批③:行完全自绘 on/off —— 页提供 row_draw 用自绘;否则清 NULL 回默认 renderer。 */ + if (g_lv) meow_listview_set_item_draw(g_lv, p->row_draw ? sp_lv_draw : NULL, NULL); } /* 整页绘制(进 back_buf;调用方 meow_dirty_full + flush 上屏)。 */ @@ -659,6 +697,7 @@ static void sp_paint_page(void) draw_statusbar(); if (g_tb) meow_widget_draw(g_tb); /* topappbar(含底部分隔线) */ if (g_sw && g_top_sw_en) meow_widget_draw(g_sw); /* 顶开关(叠顶栏右缘) */ + sp_paint_top_action(); /* 批③:顶栏右侧动作按钮 */ int lvx, lvy, lvw, lvh; sp_lv_rect(&lvx, &lvy, &lvw, &lvh); meow_ui_fill_rect(lvx, lvy, lvw, lvh, mt->surface); /* 内容底 surface */ @@ -929,6 +968,18 @@ int meow_subpage_run(sub_page_t *p) if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} } goto subdone; } + /* 批③:顶栏标题栏右侧动作按钮(一键清除;先于顶开关判定,二者页间互斥) */ + if (g_sp && g_sp->top_action) { + int ax, ay, aw, ah; sp_top_action_rect(&ax, &ay, &aw, &ah); + if (aw > 0 && tx >= ax && tx < ax + aw && ty >= ay && ty < ay + ah) { + if (g_sp->top_action_cb) g_sp->top_action_cb(g_sp->ctx); + last_act = now; + sp_refresh_rows(); /* 清后立即重扫行 */ + need_paint = 1; + if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} } + continue; + } + } if (g_top_sw_en && g_sw && meow_widget_hit(g_sw, tx, ty)) { sp_top_sw_toggle(); last_act = now; @@ -940,7 +991,13 @@ int meow_subpage_run(sub_page_t *p) int row_hit = g_lv ? meow_listview_hit(g_lv, tx, ty) : -1; if (row_hit >= 0) { sub_kind_t kind = g_rows[row_hit].kind; - if (kind == SUB_SW || kind == SUB_ACT) { + if (kind == SUB_ROW && g_sp->row_tap) { + /* 批③:行内自定义命中(行内杀按钮 vs 行体;页自判,返回 1=重绘) */ + int rry = SUB_CONT_TOP + row_hit * SUB_ITEM_H - + (g_lv ? meow_listview_scroll(g_lv) : 0); + g_sp->row_tap(g_sp->ctx, row_hit, &g_rows[row_hit], + tx, ty, 0, rry, fb_xres, SUB_ITEM_H); + } else if (kind == SUB_SW || kind == SUB_ACT) { if (g_sp->act) g_sp->act(g_sp->ctx, row_hit, g_rows[row_hit].act); } else if (kind == SUB_ROW) { if (g_sp->detail) g_sp->detail(g_sp->ctx, row_hit, g_rows[row_hit].act); diff --git a/patch_v2/meow_launcherd/test_bgpage.c b/patch_v2/meow_launcherd/test_bgpage.c new file mode 100644 index 0000000..9399e70 --- /dev/null +++ b/patch_v2/meow_launcherd/test_bgpage.c @@ -0,0 +1,164 @@ +/* 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, + * 直接验证: + * P1 bgm_rows 行骨架:后台 App 行(act=i)+ 空态行(act=-1);name/framework 经 app_meta 正确; + * P2 bgm_btn_rect 按钮几何:行右 46×34 圆角钮(rx+rw-12-46 定位); + * P3 bgm_row_tap 行内命中:**仅按钮区 kill(bgm_terminate 一次),行体/按钮外不误杀**;框架 App + * 按钮点击 → 不 terminate(!framework 门控);空态行不响应; + * P4 bgm_top_action 顶栏清除:kill_all **不杀框架 App**(sysguard 不 terminate),杀普通后台; + * P5 门控矩阵:bgmgr_can_kill(framework/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 +#include +#include +#include + +#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 */ + +/* ---------- 测试态: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; +#define CHECK(cond, msg) do { \ + 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 = 2; + set_app(0, "os.meow.hello", "播放", 0); + set_app(1, "os.meow.sysguard", "系统守护", '1'); + static sub_row_t rows[SUB_ROWS_MAX]; + + printf("=== P1) bgm_rows 行骨架 ===\n"); + bg_reset(2, "os.meow.hello", 1001, "os.meow.sysguard", 1002); + 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(!strcmp(rows[1].sub, "") || rows[1].sub[0] != '\0', "row1 sub 非空(CPU/内存/状态)"); + CHECK(b.n == 2, "bgm_ctx.n=2"); + + 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, "os.meow.sysguard", 1002); + 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)→不杀"); + /* 框架 App:按钮命中但不 terminate(!framework 门控) */ + CHECK(bgm_row_tap(&b, 1, &rows[1], 445, 28, 0, 0, 480, 56) == 1, "框架App按钮→返回1(不可杀提示)"); + CHECK(term_count_app("os.meow.sysguard") == 0, "框架App按钮点击→不 terminate"); + + 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); + 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"); + /* 前台 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.framework = '1'; CHECK(bgmgr_can_kill(&r) == 0, "framework 不可杀"); + r.framework = 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", 32); + else printf("\n%d FAILED\n", g_fails); + return g_fails ? 1 : 0; +}