feat(ui): M6.38批 — P1 MD3细化(SDK对话框28)+批②底部上滑回桌面+批6 ncm顶栏回播放页+批④面板后台列表+launcher UI微调(歌词页黑半透明底/播放页布局下移)+封面空artwork保留修复

- P1: meow_ui_dialog.c 对话框底框 radius_card→MEOW_SHAPE_EXTRA_LARGE(28) 三处(:103填充/:104描边/:361 BottomSheet); meow_ui_widgets.h 新增 MEOW_SHAPE_EXTRA_LARGE 28 宏; TextField/FAB/Slider/Progress 按设计文档保留
- 批②: np_gesture.h 新增 NP_GEST_HOME+np_home_gesture(底部热区上滑,与左缘返回对称互斥); now_playing.c 新增 np_draw_home_bar(屏底左缘pill指示条)+手势衔接; test_now_playing_widgets G5b
- 批6: ncm home.c 顶栏加回播放页按钮(MEOW_ICON_QUEUE_MUSIC)→push(ACT_PLAYER), 无音频也生效
- 批④: meowpanel /api/ams/bg 只读端点+前端后台列表页+杀/清复用kill通道
- UI微调: now_playing.c 播放页布局宏下移(NP_COVER_Y 84/NP_BTN_Y 612消底部留白)+歌词页背景统一黑色半透明叠层(弃封面取色底,删 np_draw_lyric_coverbg)
- 封面保留: now_playing.c/notif_center.c 空artwork保留上一张已解码封面(防暂停/开歌单致META报空artwork后封面闪没); test_np_cover C2断言更新+mirror同步

0warning;宿主测试 test_np_cover ALL PASS;launcher/ncm/panel 宿主测试无回归。二进制留批量批次未入库
This commit is contained in:
AskaEth committed 2026-09-04 13:09:03 +08:00
1 parent b3ce719742
commit a2aec783a7
15 files changed
+418 -47

No files matched your search

+120
View File
@@ -194,3 +194,123 @@ int api_ams_ctl(int sock, const char *pkg, int kill)
if (strstr(detail, "launcher offline")) return send_err(sock, 503, "AMS_UNAVAILABLE", "amsd/launcher 不可达");
return send_err(sock, 500, "INTERNAL", detail);
}
/* ---- 批④ 后台活动列表:/api/ams/bg(只读) ----
* 数据源=amsd FG/BG 注册表(am list 的 `state=bg` 行)+ 每进程 pcpu/rss(读 /proc/<pid>,
* 与 launcher bg_page 的 bgm_proc_sample 同源同口径,单线程静态差分缓存取 CPU 占用)。
* CPU/内存诚实原则:pid 读不到 / 首采样无差分基线 → 对应字段为 null(前端显示「—」),绝不编造。 */
#define BG_CACHE_MAX 8
static long g_bg_pid[BG_CACHE_MAX]; /* /proc/<pid>/stat utime+stime 差分缓存(单线程串行持有) */
static unsigned long long g_bg_tick[BG_CACHE_MAX];
/* 采样单进程 CPU/内存:*pcpu<0=未知(无基线/读不到)、*rss_kb<0=未知。 */
static void bg_proc_sample(int pid, double *pcpu, long *rss_kb)
{
*pcpu = -1.0;
*rss_kb = -1;
if (pid <= 0) return;
char path[64], line[1024];
/* 内存:VmRSS(KB) */
snprintf(path, sizeof path, "/proc/%d/status", pid);
if (read_small_file(path, line, sizeof line) >= 0) {
char *p = strstr(line, "VmRSS:");
if (p) {
p = strchr(p, ' ');
if (p) { while (*p == ' ' || *p == '\t') p++; if (*p) *rss_kb = atol(p); }
}
}
/* CPU:/proc/<pid>/stat 的 utime(14)+stime(15) 差分 / hz(%) */
snprintf(path, sizeof path, "/proc/%d/stat", pid);
if (read_small_file(path, line, sizeof line) >= 0) {
char *rp = strrchr(line, ')');
if (rp) {
char *p = rp + 2;
unsigned long long ut = 0, st = 0;
for (int f = 0; f < 14 && *p; f++) {
while (*p == ' ' || *p == '\t') p++;
if (!*p) break;
char *end;
unsigned long long v = strtoull(p, &end, 10);
if (end == p) { p++; continue; }
p = end;
if (f == 11) ut = v; /* utime */
if (f == 12) st = v; /* stime */
}
unsigned long long cur = ut + st;
static double hz = 0.0;
if (hz <= 0) { hz = (double)sysconf(_SC_CLK_TCK); if (hz <= 0) hz = 100.0; }
int slot = -1, had = 0;
unsigned long long prev = 0;
for (int i = 0; i < BG_CACHE_MAX; i++)
if (g_bg_pid[i] == pid) { had = 1; slot = i; prev = g_bg_tick[i]; break; }
if (!had) {
for (int i = 0; i < BG_CACHE_MAX; i++) if (g_bg_pid[i] == 0) { slot = i; break; }
if (slot < 0) slot = 0;
}
if (slot >= 0) { g_bg_pid[slot] = pid; g_bg_tick[slot] = cur; }
if (had) { *pcpu = 100.0 * (double)(cur - prev) / hz; if (*pcpu < 0) *pcpu = 0; }
}
}
}
/* 解析已 strip 的 `am list` 全文 → bg 列表 JSON(state=bg 的 APP 行)。 */
void ams_bg_parse(char *list_text, sbuf_t *s)
{
char *toks[256];
sbuf_t apps = {0};
int first = 1, count = 0;
char *ln = list_text;
while (*ln) {
char *nl = strchr(ln, '\n');
if (nl) *nl = 0;
if (strncmp(ln, "APP ", 4) == 0) {
int n = txt_tokens(ln, toks, 256);
if (n >= 9) {
/* 自右向左固定(与 ams_fill 同布局):state=tok[n-6], pid=n-5;pkg=tok[1],name=join(tok[2..n-6)) */
const char *st = toks[n - 6];
if (strcmp(st, "bg") == 0) {
char nm[128];
join_tokens(toks, 2, n - 6, nm, sizeof nm);
int pid = atoi(toks[n - 5]);
double pcpu; long rss;
bg_proc_sample(pid, &pcpu, &rss);
if (!first) sA(&apps, ",");
first = 0; count++;
sA(&apps, "{\"pkg\":"); sA_str(&apps, toks[1]);
sA(&apps, ",\"name\":"); sA_str(&apps, nm);
sA(&apps, ",\"pid\":%d", pid);
sA(&apps, ",\"state\":"); sA_str(&apps, st);
sA(&apps, ",\"cpu_pct\":");
if (pcpu < 0) sA(&apps, "null"); else sA(&apps, "%.1f", pcpu);
sA(&apps, ",\"rss_kb\":");
if (rss < 0) sA(&apps, "null"); else sA(&apps, "%ld", rss);
sA(&apps, "}");
}
}
}
if (nl) { *nl = '\n'; ln = nl + 1; } else break;
}
sA(s, "{\"available\":true,\"count\":%d,\"ts\":%ld,\"apps\":[%s]}",
count, (long)time(NULL), apps.b ? apps.b : "");
sbuf_free(&apps);
}
void ams_bg_fill(sbuf_t *s)
{
char out[16384];
char cmd[300];
snprintf(cmd, sizeof cmd, "%s list 2>/dev/null", ams_bin());
int code = run_cmd(cmd, out, sizeof out);
if (code != 0 || !strstr(out, "APP")) { sA(s, "{\"available\":false}"); return; }
panel_strip_ansi(out);
ams_bg_parse(out, s);
}
int api_ams_bg(int sock)
{
sbuf_t s = {0};
ams_bg_fill(&s);
int rc = send_json(sock, 200, s.b ? s.b : "{\"available\":false}");
sbuf_free(&s);
return rc;
}