Compare commits

...
4 Commits
15 changed files with 1066 additions and 69 deletions

No files matched your search

+290
View File
@@ -0,0 +1,290 @@
/* 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;行体不响应)。
* - 空态「无后台 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
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/<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++) {
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; /* 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 圆角钮)---- */
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;
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();
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;
}
/* ---- 行体点击(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 建 */
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)
{
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;
}
+2 -1
View File
@@ -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
+11 -10
View File
@@ -116,8 +116,8 @@ static void lk_key_render(meow_widget *w, const meow_theme *th, void *ctx)
const char *label;
int size;
if (key >= 0) { size = 30; label = lk_key_digit(key); }
else if (key == LK_ACT_CLR) { size = 22; label = mfont_ready ? "清空" : "C"; }
else { size = 22; label = mfont_ready ? "退格" : "DEL"; }
else if (key == LK_ACT_CLR) { size = 22; label = mfont_ready ? meow_str_zh("lock.clear", "清空") : "C"; }
else { size = 22; label = mfont_ready ? meow_str_zh("lock.backspace", "退格") : "DEL"; }
int cx = x + ww / 2;
int cy = y + hh / 2 - meow_ui_text_height(size) / 2;
meow_ui_text_center(cx, cy, label, size, th->onSurface);
@@ -252,12 +252,13 @@ static void lk_draw(int entered, const char *status, time_t locked_until)
meow_ui_fill_rect(0, 0, fb_xres, fb_yres, mt->surface); /* 全屏不透明表面底 */
lk_widgets_build(); /* 懒创建 12 键 */
/* 时钟/提示(居中,SDK meow_font):锁定倒计时标题 */
meow_ui_text_center(fb_xres / 2, 120, locked_until > 0 ? "已锁定" : "输入 PIN 解锁",
meow_ui_text_center(fb_xres / 2, 120, locked_until > 0 ? meow_str_zh("lock.locked", "已锁定")
: meow_str_zh("lock.prompt", "输入 PIN 解锁"),
24, mt->onSurface);
/* PIN 输入圆点(● 遮罩) */
lk_draw_pin_dots(entered, mt);
/* 状态行:错误提示(PIN 错误)着 error;倒计时/其它信息用 onSurfaceVariant */
int is_err = status[0] && (strstr(status, "PIN 错误") != NULL ||
int is_err = status[0] && (strstr(status, meow_str_zh("lock.pin_wrong", "PIN 错误")) != NULL ||
strstr(status, "WRONG PIN") != NULL);
uint32_t status_col = status[0] ? (is_err ? mt->error : mt->onSurfaceVariant)
: mt->onSurfaceVariant;
@@ -303,7 +304,7 @@ int run_lock_screen(void)
time_t lock_until = 0;
{
time_t rem = lck_lock_remaining();
if (rem > 0) { lock_until = time(NULL) + rem; snprintf(status, sizeof status, "已锁定"); }
if (rem > 0) { lock_until = time(NULL) + rem; snprintf(status, sizeof status, "%s", meow_str_zh("lock.locked", "已锁定")); }
}
time_t last_countdown = 0;
@@ -392,7 +393,7 @@ int run_lock_screen(void)
if (lock_until > 0 && now != last_countdown) {
last_countdown = now;
time_t rem = lock_until - now;
if (mfont_ready) snprintf(status, sizeof status, "已锁定,请 %d:%02d 后重试",
if (mfont_ready) snprintf(status, sizeof status, meow_str_zh("lock.locked_wait", "已锁定,请 %d:%02d 后重试"),
(int)(rem / 60), (int)(rem % 60));
else snprintf(status, sizeof status, "LOCKED %d:%02d", (int)(rem / 60), (int)(rem % 60));
redraw = 1;
@@ -545,9 +546,9 @@ int run_lock_screen(void)
if (rem > 0) {
lock_until = time(NULL) + rem;
last_countdown = 0;
snprintf(status, sizeof status, "已锁定");
snprintf(status, sizeof status, "%s", meow_str_zh("lock.locked", "已锁定"));
} else {
if (mfont_ready) snprintf(status, sizeof status, "PIN 错误");
if (mfont_ready) snprintf(status, sizeof status, "%s", meow_str_zh("lock.pin_wrong", "PIN 错误"));
else snprintf(status, sizeof status, "WRONG PIN");
}
}
@@ -671,10 +672,10 @@ int lck_input_pin_modal(const char *title, char *out, int outcap)
draw_text(x + w / 2 - 7, y + LK_ROW_H / 2 - 14, d, 2, th->on_surface);
}
} else if (key == LK_ACT_CLR) {
const char *t2 = "C"; if (mfont_ready) t2 = "清空";
const char *t2 = "C"; if (mfont_ready) t2 = meow_str_zh("lock.clear", "清空");
lk_text(x + w / 2, y + LK_ROW_H / 2 - 9, t2, 22, th->on_surface);
} else {
const char *t2 = "DEL"; if (mfont_ready) t2 = "退格";
const char *t2 = "DEL"; if (mfont_ready) t2 = meow_str_zh("lock.backspace", "退格");
lk_text(x + w / 2, y + LK_ROW_H / 2 - 9, t2, 22, th->on_surface);
}
}
+29 -29
View File
@@ -20,30 +20,30 @@ static char g_ls_status[48]; /* 状态行摘要(rows 填,paint
static void ls_status_text(char *buf, int len)
{
if (!lck_pin_configured()) { snprintf(buf, len, "未设置"); return; }
if (!lck_pin_configured()) { snprintf(buf, len, "%s", meow_str_zh("lock.status_unset", "未设置")); return; }
time_t rem = lck_lock_remaining();
if (rem > 0) snprintf(buf, len, "锁定中(%d:%02d)", (int)(rem / 60), (int)(rem % 60));
else snprintf(buf, len, "已设置");
if (rem > 0) snprintf(buf, len, meow_str_zh("lock.status_locked", "锁定中(%d:%02d)"), (int)(rem / 60), (int)(rem % 60));
else snprintf(buf, len, "%s", meow_str_zh("lock.status_set", "已设置"));
}
static int ls_rows(void *ctx, sub_row_t *rbuf, int max)
{
(void)ctx;
int n = 0;
if (n < max) { rbuf[n].kind = SUB_HDR; rbuf[n].label = "锁屏 PIN"; rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++; }
if (n < max) { rbuf[n].kind = SUB_HDR; rbuf[n].label = meow_str_zh("lock.title", "锁屏 PIN"); rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = 0; n++; }
ls_status_text(g_ls_status, sizeof g_ls_status);
if (n < max) { rbuf[n].kind = SUB_ROW; rbuf[n].label = "状态"; rbuf[n].sub = g_ls_status; rbuf[n].on = 0; rbuf[n].act = LS_ACT_STATUS; n++; }
if (n < max) { rbuf[n].kind = SUB_ROW; rbuf[n].label = meow_str_zh("lock.status_row", "状态"); rbuf[n].sub = g_ls_status; rbuf[n].on = 0; rbuf[n].act = LS_ACT_STATUS; n++; }
int configured = lck_pin_configured();
if (!configured) {
if (n < max) { rbuf[n].kind = SUB_ACT; rbuf[n].label = "设置 PIN"; rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = LS_ACT_SET; n++; }
if (n < max) { rbuf[n].kind = SUB_ACT; rbuf[n].label = meow_str_zh("lock.act_set", "设置 PIN"); rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = LS_ACT_SET; n++; }
} else {
if (n < max) { rbuf[n].kind = SUB_ACT; rbuf[n].label = "修改 PIN"; rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = LS_ACT_MODIFY; n++; }
if (n < max) { rbuf[n].kind = SUB_ACT; rbuf[n].label = "清除 PIN"; rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = LS_ACT_CLEAR; n++; }
if (n < max) { rbuf[n].kind = SUB_ACT; rbuf[n].label = meow_str_zh("lock.act_modify", "修改 PIN"); rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = LS_ACT_MODIFY; n++; }
if (n < max) { rbuf[n].kind = SUB_ACT; rbuf[n].label = meow_str_zh("lock.act_clear", "清除 PIN"); rbuf[n].sub = NULL; rbuf[n].on = 0; rbuf[n].act = LS_ACT_CLEAR; n++; }
}
if (n < max) {
int wake = 1;
if (meow_settings_get_int("system.pin_lock_on_wake", &wake) != 0) wake = 1;
rbuf[n].kind = SUB_SW; rbuf[n].label = "唤醒后锁屏"; rbuf[n].sub = NULL;
rbuf[n].kind = SUB_SW; rbuf[n].label = meow_str_zh("lock.wake_lock", "唤醒后锁屏"); rbuf[n].sub = NULL;
rbuf[n].on = wake; rbuf[n].act = LS_ACT_WAKE; n++;
}
return n;
@@ -58,35 +58,35 @@ static void ls_modal(const char *title, const char *line)
static int ls_do_modify(void)
{
char old[LCK_PIN_LEN + 1];
if (!lck_input_pin_modal("输入当前 PIN", old, sizeof old)) return 0;
if (!lck_pin_check(old)) { ls_modal("修改 PIN", "当前 PIN 错误"); return 1; }
if (!lck_input_pin_modal(meow_str_zh("lock.enter_cur", "输入当前 PIN"), old, sizeof old)) return 0;
if (!lck_pin_check(old)) { ls_modal(meow_str_zh("lock.act_modify", "修改 PIN"), meow_str_zh("lock.cur_wrong", "当前 PIN 错误")); return 1; }
char a[LCK_PIN_LEN + 1], b[LCK_PIN_LEN + 1];
if (!lck_input_pin_modal("设置新 PIN", a, sizeof a)) return 0;
if (!lck_input_pin_modal("再次确认新 PIN", b, sizeof b)) return 0;
if (strcmp(a, b) != 0) { ls_modal("修改 PIN", "两次输入不一致"); return 1; }
if (lck_pin_set(a) == MEOW_OK) ls_modal("修改 PIN", "已修改");
else ls_modal("修改 PIN", "设置失败");
if (!lck_input_pin_modal(meow_str_zh("lock.set_new", "设置新 PIN"), a, sizeof a)) return 0;
if (!lck_input_pin_modal(meow_str_zh("lock.confirm_new", "再次确认新 PIN"), b, sizeof b)) return 0;
if (strcmp(a, b) != 0) { ls_modal(meow_str_zh("lock.act_modify", "修改 PIN"), meow_str_zh("lock.mismatch", "两次输入不一致")); return 1; }
if (lck_pin_set(a) == MEOW_OK) ls_modal(meow_str_zh("lock.act_modify", "修改 PIN"), meow_str_zh("lock.modified", "已修改"));
else ls_modal(meow_str_zh("lock.act_modify", "修改 PIN"), meow_str_zh("lock.set_failed", "设置失败"));
return 1;
}
static int ls_do_set(void)
{
char a[LCK_PIN_LEN + 1], b[LCK_PIN_LEN + 1];
if (!lck_input_pin_modal("设置 4 位 PIN", a, sizeof a)) return 0;
if (!lck_input_pin_modal("再次输入确认", b, sizeof b)) return 0;
if (strcmp(a, b) != 0) { ls_modal("设置 PIN", "两次输入不一致"); return 1; }
if (lck_pin_set(a) == MEOW_OK) ls_modal("设置 PIN", "已设置");
else ls_modal("设置 PIN", "设置失败");
if (!lck_input_pin_modal(meow_str_zh("lock.enter_4", "设置 4 位 PIN"), a, sizeof a)) return 0;
if (!lck_input_pin_modal(meow_str_zh("lock.confirm_input", "再次输入确认"), b, sizeof b)) return 0;
if (strcmp(a, b) != 0) { ls_modal(meow_str_zh("lock.act_set", "设置 PIN"), meow_str_zh("lock.mismatch", "两次输入不一致")); return 1; }
if (lck_pin_set(a) == MEOW_OK) ls_modal(meow_str_zh("lock.act_set", "设置 PIN"), meow_str_zh("lock.status_set", "已设置"));
else ls_modal(meow_str_zh("lock.act_set", "设置 PIN"), meow_str_zh("lock.set_failed", "设置失败"));
return 1;
}
static int ls_do_clear(void)
{
const char *items[2]; items[0] = "是"; items[1] = "否";
int pick = s_dd_modal("确定清除锁屏 PIN?", items, 2, 1); /* 默认「否」(安全) */
const char *items[2]; items[0] = meow_str_zh("power.confirm_yes", "是"); items[1] = meow_str_zh("power.confirm_no", "否");
int pick = s_dd_modal(meow_str_zh("lock.clear_confirm", "确定清除锁屏 PIN?"), items, 2, 1); /* 默认「否」(安全) */
if (pick != 0) return 1;
if (lck_pin_clear() == MEOW_OK) ls_modal("清除 PIN", "已清除");
else ls_modal("清除 PIN", "清除失败(settings 不可写)");
if (lck_pin_clear() == MEOW_OK) ls_modal(meow_str_zh("lock.act_clear", "清除 PIN"), meow_str_zh("lock.cleared", "已清除"));
else ls_modal(meow_str_zh("lock.act_clear", "清除 PIN"), meow_str_zh("lock.clear_failed", "清除失败(settings 不可写)"));
return 1;
}
@@ -118,10 +118,10 @@ static int ls_detail(void *ctx, int idx, int act)
int rem = (int)lck_lock_remaining();
if (lck_pin_configured() && rem > 0) {
char buff[96];
snprintf(buff, sizeof buff, "连续错误已锁定,剩余 %d:%02d 后重试", rem / 60, rem % 60);
ls_modal("锁屏 PIN", buff);
snprintf(buff, sizeof buff, meow_str_zh("lock.locked_retry", "连续错误已锁定,剩余 %d:%02d 后重试"), rem / 60, rem % 60);
ls_modal(meow_str_zh("lock.title", "锁屏 PIN"), buff);
} else {
ls_modal("锁屏 PIN", line);
ls_modal(meow_str_zh("lock.title", "锁屏 PIN"), line);
}
return 1;
}
@@ -133,7 +133,7 @@ int run_lock_settings_page(void)
{
sub_page_t p;
memset(&p, 0, sizeof p);
p.title = "锁屏 PIN";
p.title = meow_str_zh("lock.title", "锁屏 PIN");
p.top_sw = NULL;
p.top_sw_set = NULL;
p.rows = ls_rows;
+41
View File
@@ -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) {
+69
View File
@@ -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 <stdio.h>
#include <string.h>
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;
}
+70
View File
@@ -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 <stdint.h>
#include <sys/types.h> /* 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 */
+21
View File
@@ -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)
+4
View File
@@ -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。 */
@@ -493,12 +493,26 @@ 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 各自实现。 */
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) */
+118 -26
View File
@@ -23,6 +23,26 @@ static const char *const out_presets[] = { "hw:0,0" };
static const char *const out_preset_labels[] = { "CS43198 (hw:0,0)" };
/* i18n:SC_PRESET 选项中文标签键(meow_str_zh:未知 id → 中文兜底,行为/显示不变)。
* 字体行(font.file/font.class_*)用运行时扫描表 font_presets/font_preset_labels,无键表,原样。 */
static const char *const bg_preset_i18n[] = { "settings.bg_black", "settings.bg_blue", "settings.bg_green" };
static const char *const hk_preset_i18n[] = { "settings.hk_powervol_pr", "settings.hk_sequence" };
static const char *const seq_preset_i18n[] = { "settings.seq_volup3", "settings.seq_voldown3" };
static const char *const out_preset_i18n[] = { "settings.out_cs43198" };
/* SC_PRESET 行选项标签本地化:按该行 presets 表匹配对应键表(字体行用运行时动态表,无键 → 原样)。 */
static const char *s_preset_label(const srow_t *r, int i)
{
const char *const *keys = NULL;
if (r->presets == bg_presets) keys = bg_preset_i18n;
else if (r->presets == hk_presets) keys = hk_preset_i18n;
else if (r->presets == seq_presets) keys = seq_preset_i18n;
else if (r->presets == out_presets) keys = out_preset_i18n;
const char *lbl = (r->preset_labels && r->preset_labels[i]) ? r->preset_labels[i] : "";
if (keys && keys[i]) return meow_str_zh(keys[i], lbl);
return lbl;
}
/* M2.37:息屏时间滑条改为「离散档位」而非线性 0~86400 秒(线性步进 1 根本调不到 30s,
* 上限 24h 过于抽象)。档位 0=常亮;从 5s 起步的细粒度段可精调「30s 左右」,随后按
@@ -123,6 +143,77 @@ static const srow_t SMENU[] = {
{ "关于本机", NULL, SC_LIST, 0,0,0, NULL,NULL,0, NET_LIST_ABOUT },
};
/* 设置行标题 i18n 键:与 SMENU 一一对应(含分组标题 / SC_LIST 导航行),settings.* 域。
* 默认值取 SMENU 行 label 原中文,行为/显示不变;TF 覆盖表/后续 SDK 内嵌并入后自动换语。 */
static const char *const s_row_i18n[SMENU_N] = {
"settings.sec_display", /* 显示 */
"settings.brightness", /* 亮度 */
"settings.screen_off_mode", /* 息屏模式 */
"settings.screen_off_timeout",/* 息屏时间 */
"settings.refresh_rate", /* 刷新率 */
"settings.refresh_always_max",/* 最高刷新率 */
"settings.theme_mode", /* 主题 */
"settings.theme", /* 主题深浅 */
"settings.theme_dark_start", /* 深色时段开始 */
"settings.theme_dark_end", /* 深色时段结束 */
"settings.background", /* 背景 */
"settings.bg_overlay_alpha", /* 背景叠加强度 */
"settings.home_layout", /* 主界面布局 */
"settings.sec_network", /* 网络 */
"settings.wifi", /* WiFi */
"settings.bt", /* 蓝牙 */
"settings.sec_power", /* 电源 */
"settings.longpress_menu", /* 长按弹菜单 */
"settings.forceoff", /* 长按强关 */
"settings.defeat_hotkey", /* 退出热键 */
"settings.sec_boot", /* 开机 */
"settings.boot_default", /* 默认系统 */
"settings.boot_menu", /* 显示选单 */
"settings.boot_timeout", /* 选单超时 */
"settings.sec_input", /* 输入 */
"settings.hotkey_seq", /* 热键序列 */
"settings.hotkey_window", /* 热键窗口 */
"settings.touch_arm", /* 触摸布防 */
"settings.swipe_invert", /* 滑动方向 */
"settings.sec_font", /* 字体主题 */
"settings.font_file", /* 字体文件 */
"settings.font_load_full", /* 加载大字库 */
"settings.multi_font", /* 多字体 */
"settings.font_cjk", /* 中文(CJK) */
"settings.font_cjk_sym", /* 中文标点 */
"settings.font_num", /* 数字 */
"settings.font_latin", /* 拉丁字母 */
"settings.font_latin_sym", /* 半角标点 */
"settings.font_other", /* 其他字符 */
"settings.font_title_size", /* 标题字号 */
"settings.font_body_size", /* 正文字号 */
"settings.ui_style", /* UI风格 */
"settings.sec_notify", /* 通知 */
"settings.notify_center", /* 通知中心 */
"settings.notify_app_quota", /* 应用配额 */
"settings.sec_log", /* 日志 */
"settings.log_level", /* 日志级别 */
"settings.log_to_card", /* 写入TF卡 */
"settings.sec_audio", /* 音频 */
"settings.volume", /* 音量 */
"settings.volume_max", /* 音量上限 */
"settings.volume_step", /* 音量跳步 */
"settings.audio_output", /* 输出设备 */
"settings.sec_security", /* 安全 */
"settings.pin", /* 锁屏 PIN */
"settings.sec_about", /* 关于 */
"settings.about", /* 关于本机 */
};
/* SMENU 行标题本地化:能定位到 SMENU 行 → 按对应键取文案(默认=原中文);否则原样返回。 */
static const char *s_row_label(const srow_t *r)
{
for (int i = 0; i < SMENU_N; i++) {
if (&SMENU[i] == r) return meow_str_zh(s_row_i18n[i], r->label);
}
return meow_str_zh("settings.unknown", r->label);
}
/* M2.39 左缘返回手势已抽为共享函数 meow_edge_back()(meow_touch.h MEOW_EDGE_W/SWIPE 由
* 其定义),设置页运行期调用;此处不再定义独立 S_EDGE_W/S_SWIPE_THRESH。 */
@@ -252,10 +343,10 @@ static int s_row_at_content_y(int cy)
/* ---- 值格式化/展示 ---- */
static void s_fmt_time(int sec, char *buf, int len)
{
if (sec <= 0) { snprintf(buf, len, "常亮"); return; }
if (sec < 60) { snprintf(buf, len, "%d 秒", sec); return; }
if (sec < 3600) { snprintf(buf, len, "%d 分", sec / 60); return; }
snprintf(buf, len, "%d 时", sec / 3600);
if (sec <= 0) { snprintf(buf, len, "%s", meow_str_zh("settings.always_on", "常亮")); return; }
if (sec < 60) { snprintf(buf, len, meow_str_zh("settings.time_sec", "%d 秒"), sec); return; }
if (sec < 3600) { snprintf(buf, len, meow_str_zh("settings.time_min", "%d 分"), sec / 60); return; }
snprintf(buf, len, meow_str_zh("settings.time_hr", "%d 时"), sec / 3600);
}
/* B2b 主题时段:分钟(0-1439) → "HH:MM"(如 1140→"19:00")。注意与 s_fmt_time 的秒语义区分。 */
@@ -298,14 +389,14 @@ static void s_fmt_value(const srow_t *r, char *buf, int len)
int s2 = 0, e2 = 0;
meow_settings_get_int("display.theme_dark_start", &s2);
meow_settings_get_int("display.theme_dark_end", &e2);
if (s2 == e2) { snprintf(buf, len, "未设置"); return; }
if (s2 == e2) { snprintf(buf, len, "%s", meow_str_zh("settings.unset", "未设置")); return; }
s_fmt_minutes(v, buf, len);
return;
}
/* 长按弹菜单/强关:毫秒换算成秒(0.5 步进,如 "2.5 秒") */
if (!strcmp(r->key, "power.longpress_menu_ms") || !strcmp(r->key, "power.forceoff_ms")) {
if (v % 1000 == 0) snprintf(buf, len, "%d 秒", v / 1000);
else snprintf(buf, len, "%.1f 秒", v / 1000.0);
if (v % 1000 == 0) snprintf(buf, len, meow_str_zh("settings.sec_val", "%d 秒"), v / 1000);
else snprintf(buf, len, meow_str_zh("settings.sec_val_f", "%.1f 秒"), v / 1000.0);
return;
}
snprintf(buf, len, "%d", v);
@@ -324,14 +415,14 @@ static void s_display_value(const srow_t *r, char *buf, int len)
if (meow_settings_get_str(r->key, cur, sizeof cur) != 0) { snprintf(buf, len, "-"); return; }
/* M2.5x 多字体:class 行空值 = 跟随全局 font.file */
if (r->key && !strncmp(r->key, "font.class_", 11) && !cur[0]) {
snprintf(buf, len, "跟随全局");
snprintf(buf, len, "%s", meow_str_zh("settings.follow_global", "跟随全局"));
return;
}
int pn = 0;
const char *const *pps = s_row_presets(r, &pn);
for (int i = 0; i < pn; i++) {
if (!strcmp(pps[i], cur)) {
snprintf(buf, len, "%s", (r->preset_labels && r->preset_labels[i][0]) ? r->preset_labels[i] : cur);
snprintf(buf, len, "%s", (r->preset_labels && r->preset_labels[i][0]) ? s_preset_label(r, i) : cur);
return;
}
}
@@ -655,14 +746,14 @@ static int s_dropdown(const srow_t *r)
n = base + cnt;
if (is_cls) {
names[0] = "";
snprintf(labels[0], sizeof labels[0], "跟随全局字体");
snprintf(labels[0], sizeof labels[0], "%s", meow_str_zh("settings.follow_global_font", "跟随全局字体"));
item[0] = labels[0];
if (!cstr[0]) cur = 0;
}
for (int i = 0; i < cnt; i++) {
names[base + i] = pps[i];
snprintf(labels[base + i], sizeof labels[base + i], "%s",
(r->preset_labels && r->preset_labels[i][0]) ? r->preset_labels[i] : pps[i]);
(r->preset_labels && r->preset_labels[i][0]) ? s_preset_label(r, i) : pps[i]);
item[base + i] = labels[base + i];
if (!strcmp(pps[i], cstr)) cur = base + i;
}
@@ -671,7 +762,7 @@ static int s_dropdown(const srow_t *r)
if (cur < 0 || cur >= n) cur = 0;
/* M7.2:枚举/预设选择器迁 SDK meow_select_show(BottomSheet+单选列表,批次2 新控件),
* 替换自绘 s_dd_modal(后者仍供 about/网络列表/字体两级子菜单用,勿删)。 */
return meow_select_show(r->label, item, n, cur);
return meow_select_show(s_row_label(r), item, n, cur);
}
@@ -906,8 +997,9 @@ static int s_font_dropdown(const srow_t *r, char *out_val, int vlen)
char fam_slug[S_NET_MAX][64], fam_disp[S_NET_MAX][64];
int famn = 0;
if (is_cls) {
if (famn < S_NET_MAX) { snprintf(fam_slug[famn], sizeof fam_slug[0], "");
snprintf(fam_disp[famn], sizeof fam_disp[0], "跟随全局字体"); famn++; }
if (famn < S_NET_MAX) { snprintf(fam_slug[famn], sizeof fam_slug[0], "%s", "");
snprintf(fam_disp[famn], sizeof fam_disp[0], "%s",
meow_str_zh("settings.follow_global_font", "跟随全局字体")); famn++; }
}
for (int i = 0; i < font_presets_n && famn < S_NET_MAX; i++) {
const char *f = font_preset_fams[i] ? font_preset_fams[i] : "";
@@ -926,9 +1018,9 @@ static int s_font_dropdown(const srow_t *r, char *out_val, int vlen)
int cur_fam = is_cls ? 0 : -1;
for (int i = is_cls ? 1 : 0; i < famn; i++)
if (curfam[0] && !strcmp(fam_slug[i], curfam)) cur_fam = i;
int pick = s_dd_modal(r->label, fitem, famn, cur_fam);
int pick = s_dd_modal(s_row_label(r), fitem, famn, cur_fam);
if (pick < 0) return 0;
if (is_cls && pick == 0) { snprintf(out_val, vlen, ""); return 1; } /* 跟随全局 */
if (is_cls && pick == 0) { snprintf(out_val, vlen, "%s", ""); return 1; } /* 跟随全局 */
const char *fam = fam_slug[pick];
/* ---- 二级:该 family 的字重列表(去重:同 (family,weight) 只显一行)----
@@ -972,7 +1064,7 @@ static int s_font_dropdown(const srow_t *r, char *out_val, int vlen)
for (int i = 0; i < wn; i++)
if (cwt2[0] && !strcmp(wt_pick[i], cwt2)) { cur_wt = i; break; }
}
int wp = s_dd_modal(r->label, witem, wn, cur_wt);
int wp = s_dd_modal(s_row_label(r), witem, wn, cur_wt);
if (wp < 0) return 0;
snprintf(out_val, vlen, "%s", wt_val[wp]);
return 1;
@@ -1029,7 +1121,7 @@ static void set_lv_bind(int idx, meow_lv_item *out, void *ctx)
out->enabled = 1;
if (idx < 0 || idx >= s_vis_count()) { out->title = ""; return; }
const srow_t *r = &SMENU[s_vis_at(idx)];
out->title = r->label ? r->label : "";
out->title = r->label ? s_row_label(r) : "";
out->section = (r->ctrl == SC_HEADER) ? 1 : 0;
if (r->ctrl == SC_SWITCH) {
int on = 0; meow_settings_get_switch(r->key, &on);
@@ -1066,7 +1158,7 @@ static void set_lv_draw(int idx, int x, int y, int w, int h, int state, void *ct
if (r->ctrl == SC_HEADER) {
meow_ui_fill_rect(x, y, w, SET_ITEM_H, mt->surfaceContainerHigh);
int ty = y + (SET_ITEM_H - thh) / 2;
if (ty >= 0) meow_ui_text(x + 16, ty, r->label, mt->font_body, mt->primary);
if (ty >= 0) meow_ui_text(x + 16, ty, s_row_label(r), mt->font_body, mt->primary);
meow_ui_fill_rect(x, y + SET_ITEM_H - 1, w, 1, mt->outlineVariant);
return;
}
@@ -1076,7 +1168,7 @@ static void set_lv_draw(int idx, int x, int y, int w, int h, int state, void *ct
int ly = y + (SET_ITEM_H - thh) / 2; /* label 单行垂直居中 */
switch (r->ctrl) {
case SC_SWITCH: {
meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol);
meow_ui_text(x + 16, ly, s_row_label(r), mt->font_body, textcol);
meow_widget *sw = set_sw_take();
if (sw) {
int on = 0; meow_settings_get_switch(r->key, &on);
@@ -1088,7 +1180,7 @@ static void set_lv_draw(int idx, int x, int y, int w, int h, int state, void *ct
}
case SC_SLIDER: {
/* label 上移一行,track 放行下方(中心=SET_SLIDER_YOFF+3=48),与拖动命中带对齐 */
meow_ui_text(x + 16, y + SET_SLIDER_LY, r->label, mt->font_body, textcol);
meow_ui_text(x + 16, y + SET_SLIDER_LY, s_row_label(r), mt->font_body, textcol);
char vb[32]; s_fmt_value(r, vb, sizeof vb);
meow_ui_text(x + w - 16 - meow_ui_text_width(vb, mt->font_body), y + SET_SLIDER_LY, vb, mt->font_body, subcol);
int v = 0; meow_settings_get_int(r->key, &v);
@@ -1099,13 +1191,13 @@ static void set_lv_draw(int idx, int x, int y, int w, int h, int state, void *ct
break;
}
case SC_INFO: {
meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol);
meow_ui_text(x + 16, ly, s_row_label(r), mt->font_body, textcol);
char vb[48]; s_net_get(r, vb, sizeof vb);
meow_ui_text(x + w - 16 - meow_ui_text_width(vb, mt->font_body), ly, vb, mt->font_body, subcol);
break;
}
case SC_LIST: {
meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol);
meow_ui_text(x + 16, ly, s_row_label(r), mt->font_body, textcol);
char vb[48]; s_net_get(r, vb, sizeof vb);
int vx = x + w - 16 - 12 - meow_ui_text_width(vb, mt->font_body);
meow_ui_text(vx, ly, vb, mt->font_body, subcol);
@@ -1113,7 +1205,7 @@ static void set_lv_draw(int idx, int x, int y, int w, int h, int state, void *ct
break;
}
default: { /* SC_ENUM / SC_PRESET */
meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol);
meow_ui_text(x + 16, ly, s_row_label(r), mt->font_body, textcol);
char vb[40]; s_display_value(r, vb, sizeof vb);
int vx = x + w - 16 - meow_ui_text_width(vb, mt->font_body);
meow_ui_text(vx, ly, vb, mt->font_body, subcol);
@@ -1179,7 +1271,7 @@ static void set_paint_page(int sel)
int W = fb_xres, H = fb_yres;
fill_rect(0, 0, W, H, th->surface);
draw_statusbar();
if (mfont_ready) ui_draw(24, 50, "系统设置", th->on_surface, 28);
if (mfont_ready) ui_draw(24, 50, meow_str_zh("menu.settings", "系统设置"), th->on_surface, 28);
else draw_text(24, 30, "SETTINGS", 3, th->on_surface);
fill_rect(0, 50 + (mfont_ready ? meow_font_line_height(28) : 24) + 2, W, 2, th->outline);
fill_rect(0, SET_CARD_Y, W, H - SET_CARD_Y, th->surface_variant);
@@ -1193,7 +1285,7 @@ static void set_paint_page(int sel)
snprintf(ver, sizeof ver, "MeowOS v%s dev", g_meowos_version);
int foot_y = fb_yres - meow_font_line_height(20) - 8;
s_draw_chevron_left(SET_CARD_X + 12, foot_y + meow_font_line_height(20) / 2, th->on_surface_variant);
ui_draw(SET_CARD_X + 24, foot_y, "返回", th->on_surface_variant, 20);
ui_draw(SET_CARD_X + 24, foot_y, meow_str_zh("settings.back", "返回"), th->on_surface_variant, 20);
ui_draw(fb_xres - 8 - ui_width(ver, 20), foot_y, ver, th->outline, 20);
}
+58 -1
View File
@@ -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);
+173
View File
@@ -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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#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;
}
+164
View File
@@ -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 <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 */
/* ---------- 测试态: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;
}
+2 -2
View File
@@ -28,7 +28,7 @@
# ---- 用户态/服务守护二进制(usr/bin,全部 chmod 755 静态 MIPS)----
patch_v2/bootmenu/meow_bootmenu|/usr/bin/meow_bootmenu|8de51144538d698c3ff82ee6651b8b1a|引导选单 M6.25(开机菜单/第二屏亮度按用户设置恢复:显示前读 settings.conf 写回,方案A)
patch_v2/meow_launcherd/meow_launcherd|/usr/bin/meow_launcherd|27e10b8cf02f6a6f66729cc3f4b817b5|M6.34 批①后台常驻框架+宽限期(meow_bgplay/meow_media/app_launcher/internal.h,MEOW_BG_GRACE_MS)+链新meow_ui.a(sdk meow_lyric/lyric_view/meow_img_color,meow_img_pixels访问器);0warning
patch_v2/meow_launcherd/meow_launcherd|/usr/bin/meow_launcherd|9515c9d8fce4bdd997da3873897395f9|M6.35 批③后台管理页(按钮化:每行右'杀'+顶栏'一键清除后台',sub_page_t row_draw/row_tap/top_action/top_action_cb)+i18n(settings/lock剩余中文补抽 meow_str_zh)+批①后台常驻框架+宽限期;链meow_ui.a(sdk meow_lyric/lyric_view/meow_img_color,meow_img_pixels访问器);0warning
patch_v2/meow_watchdog/meow_watchdog|/usr/bin/meow_watchdog|34ddbf0275526eaf9be988134845c3af|应用看门狗 M6.23(亮屏亮度按用户设置 display.brightness 恢复,修 panel ON 固定60跳变)
patch_v2/meow_svcd/meow_svcd|/usr/bin/meow_svcd|605646c4cc93f2baf29071ea6bc870ea|服务管理器 M2.77(+崩溃上报 meow_crash agent-187)
mips-repo/bins/meow_mdns/bin/meow_mdns|/usr/bin/meow_mdns|d6b0151bda26768f19952dae1342610d|mDNS 应答器 M2.78(启动竞态+换IP重检 agent-194)
@@ -48,7 +48,7 @@ patch_v2/meow_netmgrd/meow_netmgrd|/usr/bin/meow_net|9c345b2e43138bc0299c749ce0f
# ---- 开机 init 脚本(etc/init.d,数字决定启动顺序,按文件名自然序)----
patch_v2/meow_sysui/S70meow_sysui|/etc/init.d/S70meow_sysui|e435affcb56499e5229728c6ae2524b9|sysui 自启 M2.85(launcher.conf enabled=1+迁移)
patch_v2/meow_svcd/S89meow_svcd|/etc/init.d/S89meow_svcd|a1ef9d658d37bb7ed012cc9326840ac4|svcd 自启 M6.2(+amsd.conf 热更 exec→/usr/bin 迁移)
patch_v2/meow_amsd/meow_amsd|/usr/bin/meow_amsd|026cad52a855e693303882ed2b5224ee|活动管理器 M6.2(注册表自愈+busy 识别+显示名 UTF-8 整序列 name[49] 修面板 overview 非法 UTF-8)
patch_v2/meow_amsd/meow_amsd|/usr/bin/meow_amsd|57e19cde49f296de41b4b9146fe81e82|活动管理器 M6.35(注册表自愈+busy 识别+显示名 UTF-8 整序列 name[49];批① FG/BG 收编:handle_bg/handle_fg+AMS_FG/BG 状态供批③后台管理页)
patch_v2/meow_amsd/am|/usr/bin/am|9e323faf1aeb0c6a6fb0425565cf525d|amsd 用户侧CLI(+resolve/picker/rescan)
patch_v2/bootmenu/S90meow_bootmenu|/etc/init.d/S90meow_bootmenu|e0ace111ce5ee446a1e3c6605ac1e9c9|bootmenu 自启 M2.85(注释同步)
patch_v2/bootmenu/S92_03_start_music_player|/etc/init.d/S92_03_start_music_player|c17257aa3d93f75da72903b57cbe1034|§4.3 兜底 patch(rcS 正常路径被 S90 阻塞不执行)