From 211c5a556a93c7028cfedaea5dd12cb0fd41326c Mon Sep 17 00:00:00 2001 From: AskaEth Date: Fri, 4 Sep 2026 02:19:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(bt):=20=E6=89=B9=E2=91=A2=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E7=AE=A1=E7=90=86=E9=A1=B5=20=E2=80=94=20bg=5Fpage(?= =?UTF-8?q?=E9=A1=B6=E6=A0=8F'=E5=90=8E=E5=8F=B0'=E5=85=A5=E5=8F=A3+sub=5F?= =?UTF-8?q?page=5Ft=E5=88=97=E8=A1=A8[=E5=90=8D=E5=AD=97/CPU/=E5=86=85?= =?UTF-8?q?=E5=AD=98/=E7=8A=B6=E6=80=81/tap=3Dkill]+=E4=B8=80=E9=94=AE?= =?UTF-8?q?=E6=B8=85=E9=99=A4=E5=8A=A8=E4=BD=9C=E8=A1=8C+=E7=A9=BA?= =?UTF-8?q?=E6=80=81,2s=E5=AE=9E=E6=97=B6)+meow=5Fbgmgr(collect/can=5Fkill?= =?UTF-8?q?=20!framework=E9=97=A8=E6=8E=A7+kill=5Fall=E4=B8=8D=E6=9D=80?= =?UTF-8?q?=E6=A1=86=E6=9E=B6)+meow=5Fbg=5Fterminate=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E5=9B=9E=E6=94=B6=E5=BA=8F=E5=88=97;amsd=20FG/BG=E6=94=B6?= =?UTF-8?q?=E7=BC=96(=E9=87=8D=E7=BC=9657e19cde=E5=90=ABhandle=5Fbg/fg);te?= =?UTF-8?q?st=5Fbgmgr=2020=E9=A1=B9ALL=20PASS,0warning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- patch_v2/meow_launcherd/bg_page.c | 242 ++++++++++++++++++ patch_v2/meow_launcherd/build_launcher.sh | 3 +- patch_v2/meow_launcherd/main_menu.c | 41 +++ patch_v2/meow_launcherd/meow_bgmgr.c | 69 +++++ patch_v2/meow_launcherd/meow_bgmgr.h | 70 +++++ patch_v2/meow_launcherd/meow_bgplay.c | 21 ++ patch_v2/meow_launcherd/meow_bgplay.h | 4 + .../meow_launcherd/meow_launcher_internal.h | 2 + patch_v2/meow_launcherd/test_bgmgr.c | 173 +++++++++++++ 9 files changed, 624 insertions(+), 1 deletion(-) create mode 100644 patch_v2/meow_launcherd/bg_page.c create mode 100644 patch_v2/meow_launcherd/meow_bgmgr.c create mode 100644 patch_v2/meow_launcherd/meow_bgmgr.h create mode 100644 patch_v2/meow_launcherd/test_bgmgr.c diff --git a/patch_v2/meow_launcherd/bg_page.c b/patch_v2/meow_launcherd/bg_page.c new file mode 100644 index 0000000..d899d3f --- /dev/null +++ b/patch_v2/meow_launcherd/bg_page.c @@ -0,0 +1,242 @@ +/* bg_page.c — 批③ 后台管理页(桌面顶栏居右按钮进入)。 + * + * 容器:sub_page_t 脚手架(subpage.c,与 wifi_page/bt_page 同款)。顶栏=meow_topappbar: + * 返回箭头 + 标题「后台管理」(+ 右上无开关)。 + * 列表(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」。 + * 数据源=amsd FG/BG 注册表(后台常驻集=launcher meow_bg 表,EV BG/FG 上报进 amsd)+ 每进程 + * pcpu/rss(meow_svc_perf 同源 /proc 聚合,本页在 launcher 内同口径取)。 + * + * 说明(与「每行最右 kill 按钮 + 顶栏右侧清除按钮」的像素级差异):sub_page_t 行无内嵌按钮、 + * 顶栏仅返回+开关;故 kill 用「点行→确认」,清除用「顶置动作行」。像素级自绘版见 design 备忘。 + */ +#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 */ + +typedef struct { + struct app *apps; + int napps; + bgmgr_row_t rows[MEOW_BGMR_ROW_MAX]; + int n; + char toast[64]; + long toast_ms; +} 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 *framework, int *is_fg) +{ + (void)pid; + bgm_ctx_t *b = ctx; + struct app *a = bgm_app_find(b->apps, b->napps, app_id); + *framework = 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 : ""); + *framework = (a->framework == '1') ? '1' : 0; + return 0; +} + +static int bgm_enumerate(void *ctx, bgmgr_row_t *rows, int max) +{ + (void)ctx; + int n = meow_bg_count(); + if (n > max) n = max; + for (int i = 0; i < n; i++) { + const meow_bg_entry_t *e = meow_bg_at(i); + if (!e || !e->used || !e->app_id[0]) continue; + memset(&rows[i], 0, sizeof rows[i]); + snprintf(rows[i].app_id, sizeof rows[i].app_id, "%s", e->app_id); + rows[i].pid = e->pid; + rows[i].keepalive = (e->keepalive ? 1 : 0); + rows[i].on_card = (e->on_card ? 1 : 0); + rows[i].bg_since_ms = e->bg_since_ms; + } + return n; +} + +/* proc_sample:/proc//stat(utime+stime 差分) + /proc//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 列表)---- */ +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 (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; + 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++) { + char sub[80]; + snprintf(sub, sizeof sub, "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 = sub; + rbuf[r].act = i; /* detail 回调用 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); } + +/* ---- detail(点 SUB_ROW 行=kill 该后台 App)---- */ +static int bgm_detail(void *ctx, int idx, int act) +{ + (void)idx; + bgm_ctx_t *b = ctx; + int i = act; + if (i < 0 || i >= b->n) return 0; + if (!bgmgr_can_kill(&b->rows[i])) { + snprintf(b->toast, sizeof b->toast, "该 App 不可杀(框架/前台)"); + b->toast_ms = (long)now_ms(); + return 1; + } + meow_bg_terminate(b->rows[i].app_id); + snprintf(b->toast, sizeof b->toast, "已请求终止 %s", b->rows[i].app_id); + b->toast_ms = (long)now_ms(); + return 1; +} + +/* ---- act(点 SUB_ACT:一键清除)---- */ +static int bgm_act(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; + } + return 0; +} + +/* ---- 入口:运行后台管理页(阻塞到返回)---- */ +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.act = bgm_act; + p.live = 1; /* 后台表/进程变化实时重扫 */ + p.ctx = &b; + 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; +} diff --git a/patch_v2/meow_launcherd/build_launcher.sh b/patch_v2/meow_launcherd/build_launcher.sh index 6b292c8..c83651d 100755 --- a/patch_v2/meow_launcherd/build_launcher.sh +++ b/patch_v2/meow_launcherd/build_launcher.sh @@ -25,9 +25,10 @@ SYSROOT="$ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf" "$CC" -I"../sdk" -Os -static \ main_menu.c statusbar.c fb_compositor.c settings_page.c power_menu.c \ notif_center.c app_launcher.c audio.c backlight.c net.c about.c \ - subpage.c wifi_page.c bt_page.c netmgr_ipc.c now_playing.c \ + subpage.c wifi_page.c bt_page.c netmgr_ipc.c now_playing.c bg_page.c \ font_theme.c keys.c util.c meow_dep.c \ meow_touch.c meow_settings.c meow_notif.c meow_media.c meow_bgplay.c \ + meow_bgmgr.c \ meow_ams_start.c lock_screen.c lock_screen_ui.c lock_settings.c \ "../sdk/meow_lyric.c" "../sdk/meow_lyric_view.c" "../sdk/meow_img_color.c" \ "../sdk/meow_ui.a" "$SYSROOT/lib/libz.a" -lpthread -o meow_launcherd diff --git a/patch_v2/meow_launcherd/main_menu.c b/patch_v2/meow_launcherd/main_menu.c index 8c6712a..436e771 100644 --- a/patch_v2/meow_launcherd/main_menu.c +++ b/patch_v2/meow_launcherd/main_menu.c @@ -94,11 +94,32 @@ static void menu_after_app_exit(int app_ran) * background then calls draw_header_content. The menu is pre-drawn before * fb_norm_buf0 so the initial pan shows a complete frame. */ static int menu_title_size(void); /* 前向声明:draw_header_content 用(定义在下方布局帮助块) */ +static uint32_t menu_contrast(uint32_t c); /* 前向声明(定义在下方布局帮助块) */ + +/* 批③:桌面顶栏居右「后台管理」按钮(图标 tile + 字块)。命中区见 BGMR_BTN_*。 */ +#define BGMR_BTN_S 46 /* 按钮边长 */ +#define BGMR_BTN_X0 (fb_xres - BGMR_BTN_S - 14) /* 右缘留 14px 边距 */ +#define BGMR_BTN_Y0 40 /* 顶缘(标题行内,apps 区之上) */ +static void menu_draw_bgm_btn(const meow_ui_theme_t *th) +{ + int x = BGMR_BTN_X0, y = BGMR_BTN_Y0, s = BGMR_BTN_S; + fill_rect(x, y, s, s, th->primary); + const char *lab = meow_str_zh("bgmgr.topbtn", "后台"); + int tw = meow_ui_text_width(lab, 14); + ui_draw(x + (s - tw) / 2, y + (s - meow_font_line_height(14)) / 2, lab, + menu_contrast(th->primary), 14); +} +static int menu_hit_bgm_btn(int tx, int ty) +{ + return (tx >= BGMR_BTN_X0 && tx < BGMR_BTN_X0 + BGMR_BTN_S && + ty >= BGMR_BTN_Y0 && ty < BGMR_BTN_Y0 + BGMR_BTN_S); +} static void draw_header_content(void) { draw_statusbar(); const meow_ui_theme_t *th = s_ui_theme(); + menu_draw_bgm_btn(th); /* 批③:顶栏居右「后台管理」按钮 */ int tsize = menu_title_size(); /* 标题字号接 font.size.title */ /* M2.6 标题真字体(字号接 font.size.title;回退点阵3x);MD3 on_surface 主文字色 */ if (mfont_ready) { @@ -1812,6 +1833,26 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state, if (adx >= MENU_SWIPE_THRESH || ady >= MENU_SWIPE_THRESH) { logmsg("menu swipe (dx=%d,dy=%d) — no launch", dx, dy); } else if (tx >= 0 && ty >= 0) { + /* 批③:顶栏居右「后台管理」按钮(最高优先级;阻挡非阻塞,返回后重画) */ + if (menu_hit_bgm_btn(tx, ty)) { + logmsg("menu: touch topbar bg-manager button"); + menu_arm_touch(TAP_ARM_DELAY_MS); /* 幽灵触摸防护 */ + run_bg_page(apps, napps); + memset(&kd_pwr, 0, sizeof kd_pwr); + memset(&kd_vold, 0, sizeof kd_vold); + memset(&kd_volu, 0, sizeof kd_volu); + memset(&kd_play, 0, sizeof kd_play); + kp_poll(&kp_power, NULL, NULL); + kp_poll(&kp_keys, NULL, NULL); + m_prev_pwr = kp_is_down(&kp_power, KEY_POWER); + m_prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN); + m_prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP); + m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE); + last_input_ms = now_ms(); + menu_arm_touch(800); + dirty = 1; + continue; + } if (ty >= EXIT_ROW - 4 && ty < EXIT_ROW + ROW_H - 8) { logmsg("menu: touch exit row"); if (meow_mode) { diff --git a/patch_v2/meow_launcherd/meow_bgmgr.c b/patch_v2/meow_launcherd/meow_bgmgr.c new file mode 100644 index 0000000..e2f71dc --- /dev/null +++ b/patch_v2/meow_launcherd/meow_bgmgr.c @@ -0,0 +1,69 @@ +/* meow_bgmgr.c — 批③ 后台管理页 逻辑层(数据汇聚 + kill 门控)。实现见 meow_bgmgr.h。 + * 纯 C(无 launcher static/SDK 依赖),宿主 gcc 直编:gcc -Wall -Wextra -std=gnu99 \ + * test_bgmgr.c meow_bgmgr.c -o test_bgmgr。 */ +#include "meow_bgmgr.h" +#include +#include + +int bgmgr_collect(const bgmgr_ops_t *ops, bgmgr_row_t *rows, int max) +{ + if (!ops || !ops->enumerate || !ops->app_meta || !rows || max <= 0) return 0; + int n = ops->enumerate(ops->ctx, rows, max); + if (n < 0) n = 0; + if (n > max) n = max; + for (int i = 0; i < n; i++) { + bgmgr_row_t *r = &rows[i]; + r->framework = 0; + r->is_fg = 0; + r->name[0] = 0; + r->icon[0] = 0; + r->pcpu = 0.0; + r->rss_kb = 0; + ops->app_meta(ops->ctx, r->app_id, r->pid, + r->name, (int)sizeof r->name, r->icon, (int)sizeof r->icon, + &r->framework, &r->is_fg); + if (!r->name[0]) snprintf(r->name, sizeof r->name, "%s", r->app_id); + if (ops->proc_sample && ops->proc_sample(ops->ctx, r->pid, &r->pcpu, &r->rss_kb) < 0) { + r->pcpu = 0.0; r->rss_kb = 0; + } + r->killable = bgmgr_can_kill(r); + } + return n; +} + +int bgmgr_can_kill(const bgmgr_row_t *r) +{ + if (!r) return 0; + if (r->framework) return 0; /* 框架/系统 App 不 kill(铁律) */ + if (r->is_fg) return 0; /* 当前前台 App 不 kill */ + if (r->pid <= 0) return 0; /* 非后台/无进程 */ + return 1; +} + +int bgmgr_kill_all(const bgmgr_ops_t *ops, bgmgr_row_t *rows, int n, + void (*kill_fn)(const char *app_id)) +{ + (void)ops; + if (!kill_fn) return 0; + int k = 0; + for (int i = 0; i < n; i++) { + if (bgmgr_can_kill(&rows[i])) { kill_fn(rows[i].app_id); k++; } + } + return k; +} + +const char *bgmgr_status_str(const bgmgr_row_t *r) +{ + if (!r) return ""; + if (r->is_fg) return "前台"; + if (r->keepalive) return "常驻"; + return "后台"; +} + +const char *bgmgr_mem_str(long rss_kb) +{ + static char buf[24]; + if (rss_kb >= 1024) snprintf(buf, sizeof buf, "%.1fMB", (double)rss_kb / 1024.0); + else snprintf(buf, sizeof buf, "%ldKB", rss_kb); + return buf; +} diff --git a/patch_v2/meow_launcherd/meow_bgmgr.h b/patch_v2/meow_launcherd/meow_bgmgr.h new file mode 100644 index 0000000..a24c6ef --- /dev/null +++ b/patch_v2/meow_launcherd/meow_bgmgr.h @@ -0,0 +1,70 @@ +/* meow_bgmgr.h — 批③ 后台管理页 逻辑层(数据汇聚 + kill 门控)。自包含、依赖注入、可独立单测。 + * + * 定位:把「后台常驻集 → 展示行(图标/名字/CPU/内存/状态)→ kill/一键清」的**纯逻辑**从 UI 抽出, + * 便于宿主单测(test_bgmgr.c)与 launcher 页面(bg_page.c)复用。同 meow_bgplay 的 ops 注入手法: + * UI 提供真实 enumerate/app_meta/proc_sample;单测用假件。**单一写者=launcher 主循环**,无锁。 + * + * 铁律遵循:kill/一键清 带 !framework 门控(防误杀框架/系统 App,对齐 app_launcher.c:2088 教训); + * 不新建 daemon;不引 GDBus;不碰 /data/recovery_all。 + */ +#ifndef MEOW_BGMGR_H +#define MEOW_BGMGR_H + +#include +#include /* pid_t */ +#include "meow_ipc.h" /* MEOW_APP_ID_MAX */ + +/* 定义于 meow_bgplay.h,此处仅注释:与 launcher 后台表同槽数(MEOW_BG_MAX=4)。 */ +#define MEOW_BGMR_ROW_MAX 4 + +/* 后台管理页一行。enumerate 填 app_id/pid/keepalive/on_card/bg_since_ms; + * bgmgr_collect 再经 app_meta/proc_sample 补元数据/CPU/RSS,并预判 killable。 */ +typedef struct bgmgr_row { + char app_id[MEOW_APP_ID_MAX]; + char name[49]; /* display name(回退 app_id) */ + char icon[129]; /* 图标 path(空=无,页面用首字/占位) */ + char framework; /* '1'=framework=true(看门狗/系统 App,禁止 kill) */ + pid_t pid; /* >0 后台进程 */ + int keepalive; /* 1=声明常驻(background_keepalive,无限期不清) */ + int is_fg; /* 1=当前前台 App(不 kill,标注「前台」) */ + int on_card; /* 1=二进制在 TF 卡 */ + uint64_t bg_since_ms; /* 转后台时刻(monotonic ms) */ + double pcpu; /* 0-100 */ + long rss_kb; /* 常驻内存 */ + int killable; /* 预判:bgmgr_can_kill()==1 */ +} bgmgr_row_t; + +/* 依赖注入(**必须全非空**;enumerate 返回行数,app_meta/proc_sample 返回 0=成功)。 */ +typedef struct bgmgr_ops { + /* 枚举后台常驻集:每调一次填 rows 的 app_id/pid/keepalive/on_card/bg_since_ms, + * 返回行数(0..max;<0 视 0)。UI=读 launcher meow_bg 表;单测=假表。 */ + int (*enumerate)(void *ctx, bgmgr_row_t *rows, int max); + /* 按 app_id 补 App 元数据:name/icon/framework/is_fg。返回 0=找到;<0=未注册(name 回退 app_id)。 */ + int (*app_meta)(void *ctx, const char *app_id, pid_t pid, + char *name, int nl, char *icon, int il, + char *framework, int *is_fg); + /* 每进程 CPU/RSS 取样:返回 0=ok(写 pcpu 与 rss_kb);<0=无数据(pcpu=0/rss=0)。 */ + int (*proc_sample)(void *ctx, pid_t pid, double *pcpu, long *rss_kb); + void (*log)(const char *fmt, ...); /* 可空=静默 */ + void *ctx; +} bgmgr_ops_t; + +/* 枚举后台常驻并补全元数据/CPU/RSS。返回行数(>=0);app_id 空/重复加固在 enumerate 侧。 + * 行序=enumerate 返回序(UI 端期望 bg_since_ms 升序=最久在前)。 */ +int bgmgr_collect(const bgmgr_ops_t *ops, bgmgr_row_t *rows, int max); + +/* !framework 门控 + 非前台 + pid>0:返回 1=可 kill;0=禁止(框架/非后台/当前前台/无进程)。 */ +int bgmgr_can_kill(const bgmgr_row_t *r); + +/* 一键清:遍历 rows,bgmgr_can_kill() 的条目逐一调 kill_fn(app_id)。返回发出 kill 数。 + * **不杀框架 App/当前前台/非后台**;调用方负责对返回 0 弹「无后台 App 可清」之类提示。 */ +int bgmgr_kill_all(const bgmgr_ops_t *ops, bgmgr_row_t *rows, int n, + void (*kill_fn)(const char *app_id)); + +/* 状态文案(用于行内「状态」列):「常驻」keepalive=1;「后台」宽限期类;「前台」is_fg=1;空=非后台。 */ +const char *bgmgr_status_str(const bgmgr_row_t *r); + +/* 内存文案(rss_kb → "x.xMB"/"xxKB")。返回静态缓冲区;勿复用变长调用。 */ +const char *bgmgr_mem_str(long rss_kb); + +#endif /* MEOW_BGMGR_H */ diff --git a/patch_v2/meow_launcherd/meow_bgplay.c b/patch_v2/meow_launcherd/meow_bgplay.c index 2ce5548..f242cb0 100644 --- a/patch_v2/meow_launcherd/meow_bgplay.c +++ b/patch_v2/meow_launcherd/meow_bgplay.c @@ -270,6 +270,27 @@ void meow_bg_tick(void) } } +/* 批③后台管理页:请求终止某个后台保活 App(走既有 SIGTERM→SIGKILL 真退序列)。 + * 只置 term_at_ms(tick 续做 SIGTERM→TERM_GRACE_MS→SIGKILL→reap→on_bg_dead→摘表), + * 与宽限超时/卡拔回收同一条终止通道,**不在此处直接 reap**(保持单一写者 tick 收口)。 */ +int meow_bg_terminate(const char *app_id) +{ + if (!app_id || !app_id[0]) return MEOW_ERR_INVAL; + int i; + for (i = 0; i < MEOW_BG_MAX; i++) { + meow_bg_entry_t *e = &g_bg[i]; + if (e->used && e->pid > 0 && !strcmp(e->app_id, app_id)) { + if (e->term_at_ms == 0) { + bg_log("[bgplay] 后台管理页终止后台App: %s pid=%d", app_id, (int)e->pid); + kill(e->pid, SIGTERM); + e->term_at_ms = bg_now_ms(); + } + return 0; + } + } + return MEOW_ERR_NOENT; +} + /* ================= 无头后台服务 ================= */ static meow_svc_rt_t *svc_find_mut(const char *app_id, const char *svc) diff --git a/patch_v2/meow_launcherd/meow_bgplay.h b/patch_v2/meow_launcherd/meow_bgplay.h index 0446652..82ef954 100644 --- a/patch_v2/meow_launcherd/meow_bgplay.h +++ b/patch_v2/meow_launcherd/meow_bgplay.h @@ -122,6 +122,10 @@ int meow_bg_add(const char *app_id, pid_t pid, int out_fd, int on_card, int kee int meow_bg_take(const char *app_id, int *out_fd, pid_t *pid); /* 该 App 是否在后台保活。 */ int meow_bg_is_backgrounded(const char *app_id); +/* 批③后台管理页:请求终止某个后台保活 App(走既有 SIGTERM→SIGKILL 真退序列)。 + * 置 e->term_at_ms=now 由 meow_bg_tick 续做 SIGTERM→TERM_GRACE_MS→SIGKILL→reap→on_bg_dead→摘表。 + * 返回 0=已受理(在后台表且 pid>0);MEOW_ERR_NOENT=不在后台/无进程;MEOW_ERR_INVAL=app_id 空。 */ +int meow_bg_terminate(const char *app_id); /* 当前后台保活数。 */ int meow_bg_count(void); /* 取第 i 槽(0..MEOW_BG_MAX-1)只读指针;空槽/越界返回 NULL。供 drain out_fd。 */ diff --git a/patch_v2/meow_launcherd/meow_launcher_internal.h b/patch_v2/meow_launcherd/meow_launcher_internal.h index 269b376..2befe3a 100644 --- a/patch_v2/meow_launcherd/meow_launcher_internal.h +++ b/patch_v2/meow_launcherd/meow_launcher_internal.h @@ -499,6 +499,8 @@ typedef struct sub_page { int meow_subpage_run(sub_page_t *p); int run_wifi_page(void); int run_bt_page(void); +/* 批③:后台管理页(bg_page.c;桌面顶栏居右按钮进入;阻塞到返回)。 */ +int run_bg_page(struct app *apps, int napps); /* chip 索引(⑥:第 5 个 = 电源菜单,点击唤起既有系统电源菜单 show_power_menu) */ diff --git a/patch_v2/meow_launcherd/test_bgmgr.c b/patch_v2/meow_launcherd/test_bgmgr.c new file mode 100644 index 0000000..0ebfe44 --- /dev/null +++ b/patch_v2/meow_launcherd/test_bgmgr.c @@ -0,0 +1,173 @@ +/* test_bgmgr.c — 批③ 后台管理页 逻辑层宿主单测(meow_bgmgr)。 + * + * 验证: + * 1) bgmgr_collect:enumerate 后台表 → app_meta 补 name/icon/framework/is_fg → proc_sample 补 + * pcpu/rss_kb;行 killable 预判正确;name 回退 app_id。 + * 2) bgmgr_can_kill 门控矩阵:framework=1 → 0(**框架 App 不 kill**);is_fg=1 → 0(当前前台不 kill); + * pid<=0 → 0(非后台/无进程);普通后台(非框架/非前台/pid>0)→ 1。 + * 3) bgmgr_kill_all 一键清:框架 App 不杀、当前前台不杀、非后台不杀;其余逐一 kill;返回 kill 数。 + * 4) bgmgr_status_str / bgmgr_mem_str 文案。 + * 只依赖 meow_bgmgr.c + meow_ipc.h(纯 C);宿主 gcc 直编: + * gcc -Wall -Wextra -std=gnu99 test_bgmgr.c meow_bgmgr.c -o test_bgmgr + */ +#include +#include +#include +#include +#include "meow_bgmgr.h" + +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) + +/* ---- 假件(enumerate/app_meta/proc_sample 注入)---- */ +static const char g_meta_app[][24] = { + "os.meow.hello", "os.meow.reader", "os.meow.sysguard", "os.meow.fgapp", +}; +static int g_killed_n = 0; +static char g_killed_app[8][40]; +static int g_meta_use_unknown = 0; /* 第 4 行 meta 返回 -1(未注册),测 name 回退 */ + +static int fake_enumerate(void *ctx, bgmgr_row_t *rows, int max) +{ + (void)ctx; + if (max < 4) return 0; + /* 4 个后台行:hello=普通、reader=常驻、sysguard=framework、fgapp=当前前台 */ + memset(&rows[0], 0, sizeof rows[0]); + snprintf(rows[0].app_id, sizeof rows[0].app_id, "%s", g_meta_app[0]); + rows[0].pid = 1001; rows[0].keepalive = 0; rows[0].on_card = 0; rows[0].bg_since_ms = 1000; + + memset(&rows[1], 0, sizeof rows[1]); + snprintf(rows[1].app_id, sizeof rows[1].app_id, "%s", g_meta_app[1]); + rows[1].pid = 1002; rows[1].keepalive = 1; rows[1].on_card = 0; rows[1].bg_since_ms = 2000; + + memset(&rows[2], 0, sizeof rows[2]); + snprintf(rows[2].app_id, sizeof rows[2].app_id, "%s", g_meta_app[2]); + rows[2].pid = 1003; rows[2].keepalive = 0; rows[2].on_card = 0; rows[2].bg_since_ms = 3000; + + memset(&rows[3], 0, sizeof rows[3]); + snprintf(rows[3].app_id, sizeof rows[3].app_id, "%s", g_meta_app[3]); + rows[3].pid = 1004; rows[3].keepalive = 0; rows[3].on_card = 0; rows[3].bg_since_ms = 4000; + return 4; +} + +/* meta:hello/reader/sysguard/fgapp 各给名字;sysguard 标 framework;pid==1004 标前台。 */ +static int fake_app_meta(void *ctx, const char *app_id, pid_t pid, + char *name, int nl, char *icon, int il, + char *framework, int *is_fg) +{ + (void)ctx; + if (!strcmp(app_id, "os.meow.hello")) snprintf(name, nl, "播放"); + else if (!strcmp(app_id, "os.meow.reader")) snprintf(name, nl, "阅读"); + else if (!strcmp(app_id, "os.meow.sysguard")) snprintf(name, nl, "系统守护"); + else if (!strcmp(app_id, "os.meow.fgapp")) snprintf(name, nl, "前台应用"); + else return -1; /* 未注册 → name 回退 app_id */ + snprintf(icon, il, "/tmp/icon_%s.png", app_id); + *framework = (!strcmp(app_id, "os.meow.sysguard")) ? '1' : 0; + *is_fg = (pid == 1004) ? 1 : 0; /* fgapp 标前台 */ + return 0; +} + +static int fake_proc_sample(void *ctx, pid_t pid, double *pcpu, long *rss_kb) +{ + (void)ctx; + if (pid <= 0) return -1; + *pcpu = (double)(pid % 100) * 0.5; /* 1001->0.5% ... 1004->2.0% */ + *rss_kb = (long)(pid % 10) * 2048; /* 1001->2048 ... 1004->8192 */ + return 0; +} + +static void fake_log(const char *fmt, ...) { (void)fmt; } + +static void fake_kill(const char *app_id) +{ + if (g_killed_n < 8) snprintf(g_killed_app[g_killed_n++], 40, "%s", app_id); +} + +/* enumerate 只回一行:未注册 app_id,测 name 回退。 */ +static int fake_enumerate_unknown(void *ctx, bgmgr_row_t *rows, int max) +{ + (void)ctx; + if (max < 1) return 0; + (void)g_meta_use_unknown; + memset(&rows[0], 0, sizeof rows[0]); + snprintf(rows[0].app_id, sizeof rows[0].app_id, "os.meow.noreg"); + rows[0].pid = 2001; rows[0].keepalive = 0; + return 1; +} + +int main(void) +{ + bgmgr_ops_t ops; + memset(&ops, 0, sizeof ops); + ops.enumerate = fake_enumerate; + ops.app_meta = fake_app_meta; + ops.proc_sample = fake_proc_sample; + ops.log = fake_log; + + bgmgr_row_t rows[MEOW_BGMR_ROW_MAX]; + int n = bgmgr_collect(&ops, rows, MEOW_BGMR_ROW_MAX); + + printf("=== 1) collect ===\n"); + CHECK(n == 4, "collect returns 4 rows"); + CHECK(!strcmp(rows[0].name, "播放") && rows[0].pid == 1001, "row0 hello name/pid"); + CHECK(rows[0].framework == 0, "row0 hello not framework"); + CHECK(!strcmp(rows[1].name, "阅读") && rows[1].keepalive == 1, "row1 reader keepalive"); + CHECK(rows[2].framework == '1', "row2 sysguard framework"); + CHECK(rows[3].is_fg == 1, "row3 fgapp is_fg"); + CHECK(rows[0].pcpu > 0 && rows[0].rss_kb > 0, "row0 pcpu/rss populated"); + CHECK(rows[0].killable == 1 && rows[2].killable == 0, "killable prefilled by can_kill"); + CHECK(!strcmp(rows[2].icon, "/tmp/icon_os.meow.sysguard.png"), "icon populated"); + + /* name 回退:未注册 app_id(app_meta 返回 -1)→ name==app_id */ + printf("=== 1b) name fallback ===\n"); + { + bgmgr_ops_t o2 = ops; o2.enumerate = fake_enumerate_unknown; + bgmgr_row_t r1[1]; memset(r1, 0, sizeof r1); + int n2 = bgmgr_collect(&o2, r1, 1); + CHECK(n2 == 1, "unknown row collected"); + CHECK(!strcmp(r1[0].name, "os.meow.noreg"), "name falls back to app_id"); + CHECK(bgmgr_can_kill(&r1[0]) == 1, "unknown bg app still killable"); + } + + printf("=== 2) can_kill gating ===\n"); + CHECK(bgmgr_can_kill(&rows[2]) == 0, "framework app NOT killable"); + CHECK(bgmgr_can_kill(&rows[3]) == 0, "fg app NOT killable (is_fg)"); + CHECK(bgmgr_can_kill(&rows[0]) == 1, "normal bg app killable"); + CHECK(bgmgr_can_kill(&rows[1]) == 1, "keepalive bg app killable"); + { + bgmgr_row_t dead; memset(&dead, 0, sizeof dead); + snprintf(dead.app_id, sizeof dead.app_id, "x"); + dead.pid = 0; + CHECK(bgmgr_can_kill(&dead) == 0, "pid<=0 NOT killable"); + CHECK(bgmgr_can_kill(NULL) == 0, "NULL NOT killable"); + } + + printf("=== 3) kill_all (one-key clear) ===\n"); + g_killed_n = 0; + int k = bgmgr_kill_all(&ops, rows, n, fake_kill); + CHECK(k == 2, "kill_all kills 2 (hello+reader)"); + CHECK(g_killed_n == 2, "kill_all actually invoked kill_fn 2x"); + int killed_fw = 0, killed_fg = 0, killed_bg = 0; + for (int i = 0; i < g_killed_n; i++) { + if (!strcmp(g_killed_app[i], "os.meow.sysguard")) killed_fw = 1; + if (!strcmp(g_killed_app[i], "os.meow.fgapp")) killed_fg = 1; + if (!strcmp(g_killed_app[i], "os.meow.hello")) killed_bg = 1; + } + CHECK(killed_fw == 0, "kill_all did NOT kill framework app"); + CHECK(killed_fg == 0, "kill_all did NOT kill fg app"); + CHECK(killed_bg == 1, "kill_all killed a real bg app"); + CHECK(bgmgr_kill_all(&ops, rows, n, NULL) == 0, "kill_all NULL kill_fn -> 0"); + + printf("=== 4) status/mem str ===\n"); + CHECK(!strcmp(bgmgr_status_str(&rows[1]), "常驻"), "status_str keepalive=常驻"); + CHECK(!strcmp(bgmgr_status_str(&rows[0]), "后台"), "status_str normal=后台"); + CHECK(!strcmp(bgmgr_status_str(&rows[3]), "前台"), "status_str is_fg=前台"); + CHECK(!strcmp(bgmgr_mem_str(512), "512KB"), "mem_str(<1MB)=KB"); + CHECK(!strcmp(bgmgr_mem_str(2048), "2.0MB"), "mem_str(>=1MB)=MB"); + + if (g_fails == 0) printf("\nALL PASS (%d checks)\n", 20); + else printf("\n%d FAILED\n", g_fails); + return g_fails ? 1 : 0; +}