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

+3
View File
@@ -292,7 +292,10 @@ int txt_tokens(char *buf, char **toks, int maxtoks);
void join_tokens(char **toks, int from, int to, char *out, size_t cap);
void ams_fill(sbuf_t *s);
void ams_fg_fill(sbuf_t *s);
void ams_bg_fill(sbuf_t *s);
void ams_bg_parse(char *list_text, sbuf_t *s); /* 已 strip 的 am list 文本 → bg 列表 JSON(含 /proc 采样) */
int api_ams(int sock);
int api_ams_bg(int sock);
int ams_pkg_ok(const char *pkg);
void ams_history_parse(char *out, sbuf_t *s, const char *pkg);
void ams_history_fill(sbuf_t *s, const char *pkg);
+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;
}
+31
View File
@@ -289,6 +289,7 @@ int handle(int sock, req_t *r)
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/power/history")) return api_power_history(sock);
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/power/events")) return api_power_events(sock);
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/ams")) return api_ams(sock);
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/ams/bg")) return api_ams_bg(sock); /* 批④ 后台活动列表(只读) */
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/svcd")) return api_svcd(sock);
if (!strcmp(r->method, "GET") && !strcmp(p, "/api/overview")) return api_overview(sock);
if (!strcmp(r->method, "GET")) {
@@ -536,6 +537,36 @@ int panel_selftest(void)
sbuf_free(&h2);
}
/* 6) ams_bg_parse:喂含 bg/fg 混合 state 的 am list 文本 → 仅 bg 条目、合法 JSON,
* cpu_pct/rss_kb 为 null(读不到/首采样)或数字——绝不编造不一致字段。 */
{
char list[] =
"# amd register+state\n"
"APP os.meow.a 名A bg 1 1 0 clean 0\n"
"APP os.meow.b 名B fg 9999992 1 0 clean 0\n"
"APP os.meow.c 名C bg 9999991 2 1 exit 1\n";
sbuf_t b = {0};
ams_bg_parse(list, &b);
if (!b.b || !utf8_valid_n(b.b, b.n)) { printf("SELFTEST FAIL: ams_bg_parse 产物非法字节/为空\n"); sbuf_free(&b); return 1; }
jv_t *br = jv_parse(b.b, (int)b.n);
if (!br) { printf("SELFTEST FAIL: ams_bg_parse 产物 JSON 无法解析\n"); sbuf_free(&b); return 1; }
jv_t *bapps = jv_obj_get(br, "apps");
if (!bapps || bapps->type != J_ARR) { printf("SELFTEST FAIL: ams_bg_parse 缺 apps 数组\n"); jv_free(br); sbuf_free(&b); return 1; }
int bcnt = 0;
for (jv_t *e = bapps->child; e; e = e->next) {
bcnt++;
jv_t *st = jv_obj_get(e, "state");
if (!st || st->type != J_STR || strcmp(st->str, "bg") != 0) { printf("SELFTEST FAIL: ams_bg_parse 条目 state 非 bg\n"); jv_free(br); sbuf_free(&b); return 1; }
jv_t *cp = jv_obj_get(e, "cpu_pct");
jv_t *rs = jv_obj_get(e, "rss_kb");
if (!cp || (cp->type != J_NULL && cp->type != J_NUM)) { printf("SELFTEST FAIL: ams_bg_parse cpu_pct 非 null/数字\n"); jv_free(br); sbuf_free(&b); return 1; }
if (!rs || (rs->type != J_NULL && rs->type != J_NUM)) { printf("SELFTEST FAIL: ams_bg_parse rss_kb 非 null/数字\n"); jv_free(br); sbuf_free(&b); return 1; }
}
if (bcnt != 2) { printf("SELFTEST FAIL: ams_bg_parse bg 条目应 2 个,实际 %d\n", bcnt); jv_free(br); sbuf_free(&b); return 1; }
jv_free(br);
sbuf_free(&b);
}
printf("SELFTEST PASS: 截断 UTF-8「流媒体客户\\xE7」+ net/power/ams 新增端点池 → 合法 JSON + 无非法字节\n");
return 0;
}
+5
View File
@@ -69,6 +69,10 @@
<span class="nav-ico"><svg viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><circle cx="9" cy="6" r="2.2" fill="var(--bg)"/><circle cx="15" cy="12" r="2.2" fill="var(--bg)"/><circle cx="7" cy="18" r="2.2" fill="var(--bg)"/></svg></span>
<span class="nav-label">进程·服务</span>
</button>
<button class="nav-item" data-route="bg" title="后台活动(App 后台常驻:CPU/内存占用 + 一键清后台)">
<span class="nav-ico"><svg viewBox="0 0 24 24"><path d="M12 3 3 8l9 5 9-5-9-5z" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/><path d="M3 12l9 5 9-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/><path d="M3 16l9 5 9-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/></svg></span>
<span class="nav-label">后台</span>
</button>
<button class="nav-item" data-route="system" title="系统信息 / 升级">
<span class="nav-ico"><svg viewBox="0 0 24 24"><rect x="6" y="3" width="12" height="18" rx="2" fill="none" stroke="currentColor" stroke-width="2"/><path d="M9 8h6M9 12h6M9 16h3" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></span>
<span class="nav-label">系统</span>
@@ -92,6 +96,7 @@
</button>
<h1 class="topbar-title" id="page-title">首页仪表盘</h1>
<div class="topbar-actions">
<button class="btn topbar-btn" id="bg-clear" hidden>一键清后台</button>
<span class="conn" id="conn-dot" data-state="unknown" title="后端连接">
<span class="conn-dot"></span><span class="conn-text" id="conn-text">检测中…</span>
</span>
+15
View File
@@ -171,6 +171,8 @@ body{
.conn[data-state="bad"] .conn-dot{ background:var(--error); }
.conn[data-state="ok"]{ color:var(--success); }
.conn[data-state="bad"]{ color:var(--error); }
.topbar-actions{ display:flex; align-items:center; gap:8px; }
.topbar-btn{ padding:6px 12px; font-size:12px; }
/* 内容区 */
.content{ flex:1; overflow-y:auto; padding:20px; }
@@ -269,6 +271,19 @@ input[type=range]{ width:100%; accent-color:var(--primary); }
.app-card .app-meta{ font-size:12px; color:var(--on-surface-variant); }
.app-card .app-actions{ display:flex; gap:6px; margin-top:auto; flex-wrap:wrap; }
/* 后台活动列表(批④,与启动器后台管理页观感呼应:MD3 圆角卡 + 行右杀按钮) */
.bg-list{ display:flex; flex-direction:column; gap:8px; }
.bg-row{
display:flex; align-items:center; gap:12px;
padding:12px 14px; background:var(--surface-variant);
border-radius:var(--radius-l); border:var(--outline-w) solid var(--outline-variant);
}
.bg-row .bg-ico{ width:40px; height:40px; border-radius:var(--radius-m); background:var(--primary); color:var(--on-primary); display:flex; align-items:center; justify-content:center; font-weight:700; font-size:16px; flex:0 0 40px; }
.bg-row .bg-info{ flex:1; min-width:0; }
.bg-row .bg-name{ font-weight:600; }
.bg-row .bg-sub{ font-size:12px; color:var(--on-surface-variant); margin-top:2px; display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
.bg-row .btn{ flex:0 0 auto; }
/* 实时屏幕(屏幕页) */
.screen-frame{ display:flex; justify-content:center; max-width:480px; margin:0 auto; }
.screen-frame img{
+8 -3
View File
@@ -3,7 +3,7 @@
(function () {
'use strict';
var MP = window.MP;
const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode, authRequired, barHtml, bindLogin, bindPwDlg, bindSlider, bindThemeUi, browserMode, confirmDlg, dbPct, dbg, dbgErr, debounce, deciToDb, devStyleLabel, devThemeLabel, doLogin, drawSpark, emptyState, errText, esc, fmtBytes, fmtDb, fmtTime, fmtUptime, hideLogin, homeCard, homeKv, homeUnavailable, initTheme, isAvail, levelMeterHtml, loginErr, modePill, passwordDlg, pct, pushSpark, refreshAuthGate, resetSpark, resolveMode, resolveStyle, saveSetting, setConn, showLogin, slur, sparkPeak, sparkRms, sparklineHtml, sparklinePoints, stat, state, storageDel, storageGet, storageSet, storedMode, storedStyle, toast, toastTimer, updateLevelMeter, volTimer, absFor, appInfo, appStatePill, appUninstall, applyScreenDeltaDom, audioCard, audioCtlHtml, audioFlowHtml, audioPortName, audioRouteLabel, batteryCard, batteryCardBody, bindApps, bindAudio, bindFiles, bindFonts, bindHome, bindLogs, bindNet, bindNotif, bindPin, bindPower, bindProc, bindScreen, bindSettings, bindSystem, breakdownCardBody, btCard, createImgFromBlob, crumbHtml, deviceCard, doDownload, ensureScreenCanvas, escSafeAttr, estimateCardBody, eventsBar, fetchAudioOnce, fetchHomeLevel, fetchHomeSystem, fetchNetConns, fetchNetLog, fetchNetOnce, fetchOverviewOnce, fetchPerfOnce, fetchPowerOnce, fetchProcOnce, fetchScreen, fmtDur, fmtFrames, groupName, handleRemoteGesture, healthCard, importFont, importFontPage, installFromFile, installFromPath, joinRel, loadFonts, loadFontsPage, loadPinStatus, logsCrashCard, mkdirFile, navFiles, netBadge, netBannerErr, netCard, netConnsBody, netDnsBody, netIfaceCards, netPolicyCard, netStateCls, netStatusCards, newestStdout, perfCard, pinEdit, pinEditDlg, powerEventsCard, powerHistoryCard, powerPolicyCard, powerProfileLabel, powerSummaryCard, pubBytesCard, remoteBtn, remotePadHtml, remotePointerDown, remotePointerUp, removeFile, renameFile, renderApps, renderAudio, renderBright, renderFiles, renderFontsPage, renderHome, renderHomeCards, renderLogs, renderLogsFiles, renderNet, renderNotif, renderPerf, renderPerfData, renderPower, renderPowerBody, renderProc, renderProcBody, renderScreen, renderSettings, renderSystem, restartScreenPoll, row, screenTap, seedScreenCanvas, segments, sendKey, sendKeyLabel, sendKeyRepeat, sendTheme, setBrightCtl, setPanelPassword, settingControl, srcPill, stopAudio, stopHome, stopNet, stopPerf, stopPower, stopProc, stopScreenPoll, storageCard, svcStatePill, uploadFiles, viewAppStdout, viewLogsCrashTail, volSteps } = MP;
const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode, authRequired, barHtml, bindLogin, bindPwDlg, bindSlider, bindThemeUi, browserMode, confirmDlg, dbPct, dbg, dbgErr, debounce, deciToDb, devStyleLabel, devThemeLabel, doLogin, drawSpark, emptyState, errText, esc, fmtBytes, fmtDb, fmtTime, fmtUptime, hideLogin, homeCard, homeKv, homeUnavailable, initTheme, isAvail, levelMeterHtml, loginErr, modePill, passwordDlg, pct, pushSpark, refreshAuthGate, resetSpark, resolveMode, resolveStyle, saveSetting, setConn, showLogin, slur, sparkPeak, sparkRms, sparklineHtml, sparklinePoints, stat, state, storageDel, storageGet, storageSet, storedMode, storedStyle, toast, toastTimer, updateLevelMeter, volTimer, absFor, appInfo, appStatePill, appUninstall, applyScreenDeltaDom, audioCard, audioCtlHtml, audioFlowHtml, audioPortName, audioRouteLabel, batteryCard, batteryCardBody, bindApps, bindAudio, bindBg, bindFiles, bindFonts, bindHome, bindLogs, bindNet, bindNotif, bindPin, bindPower, bindProc, bindScreen, bindSettings, bindSystem, breakdownCardBody, btCard, createImgFromBlob, crumbHtml, deviceCard, doDownload, ensureScreenCanvas, escSafeAttr, estimateCardBody, eventsBar, fetchAudioOnce, fetchHomeLevel, fetchHomeSystem, fetchNetConns, fetchNetLog, fetchNetOnce, fetchOverviewOnce, fetchPerfOnce, fetchPowerOnce, fetchProcOnce, fetchScreen, fmtDur, fmtFrames, groupName, handleRemoteGesture, healthCard, importFont, importFontPage, installFromFile, installFromPath, joinRel, loadFonts, loadFontsPage, loadPinStatus, logsCrashCard, mkdirFile, navFiles, netBadge, netBannerErr, netCard, netConnsBody, netDnsBody, netIfaceCards, netPolicyCard, netStateCls, netStatusCards, newestStdout, perfCard, pinEdit, pinEditDlg, powerEventsCard, powerHistoryCard, powerPolicyCard, powerProfileLabel, powerSummaryCard, pubBytesCard, remoteBtn, remotePadHtml, remotePointerDown, remotePointerUp, removeFile, renameFile, renderApps, renderAudio, renderBg, renderBgBody, renderBright, renderFiles, renderFontsPage, renderHome, renderHomeCards, renderLogs, renderLogsFiles, renderNet, renderNotif, renderPerf, renderPerfData, renderPower, renderPowerBody, renderProc, renderProcBody, renderScreen, renderSettings, renderSystem, restartScreenPoll, row, screenTap, seedScreenCanvas, segments, sendKey, sendKeyLabel, sendKeyRepeat, sendTheme, setBrightCtl, setPanelPassword, settingControl, srcPill, stopAudio, stopBg, stopHome, stopNet, stopPerf, stopPower, stopProc, stopScreenPoll, storageCard, svcStatePill, uploadFiles, viewAppStdout, viewLogsCrashTail, volSteps } = MP;
const ROUTES = {
home: { title: '首页仪表盘', render: renderHome, bind: bindHome },
@@ -20,7 +20,8 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
logs: { title: '系统日志', render: renderLogs, bind: bindLogs },
notif: { title: '通知中心', render: renderNotif, bind: bindNotif },
power: { title: '电源 / 功耗', render: renderPower, bind: bindPower },
proc: { title: '进程 / 服务', render: renderProc, bind: bindProc }
proc: { title: '进程 / 服务', render: renderProc, bind: bindProc },
bg: { title: '后台管理', render: renderBg, bind: bindBg }
};
let started = false;
@@ -33,7 +34,7 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
bindNav();
bindThemeUi();
bindSlider();
bindFiles(); bindApps(); bindFonts(); bindSettings(); bindScreen(); bindSystem(); bindLogs(); bindNotif(); bindNet(); bindHome(); bindProc(); bindPin(); bindAudio();
bindFiles(); bindApps(); bindFonts(); bindSettings(); bindScreen(); bindSystem(); bindLogs(); bindNotif(); bindNet(); bindHome(); bindProc(); bindBg(); bindPin(); bindAudio();
bindLogin();
bindPwDlg();
// 首次进入:访问级 PIN 鉴权——已登录直接进;未设 PIN(且无旧密码)→ 首次设置流程;已设 → 登录
@@ -58,8 +59,12 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
stopHome();
stopPower();
stopProc();
stopBg();
$$('.nav-item').forEach(b => b.classList.toggle('active', b.dataset.route === id));
$('#page-title').textContent = r.title;
// 顶栏「一键清后台」仅后台管理页显示(内容由 fetchBgOnce 按数据更新)
const bc = $('#bg-clear');
if (bc) { bc.hidden = (id !== 'bg'); if (id !== 'bg') bc.disabled = false; }
r.render();
}
+100 -1
View File
@@ -885,6 +885,105 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
});
}
/* ---- 批④ 后台活动页(#/bg):列表 + 行内「杀死」 + 顶栏「一键清后台」(复用 /api/ams/bg 只读 + /api/ams/:pkg/kill) ---- */
let bgTimer = null;
let bgData = null; // {available,count,apps:[{pkg,name,pid,state,cpu_pct,rss_kb}]}
function stopBg() { if (bgTimer) { clearInterval(bgTimer); bgTimer = null; } }
function bgIconHtml(a) {
const ch = esc((a.name || a.pkg || '?').charAt(0).toUpperCase());
return '<span class="bg-ico">' + ch + '</span>';
}
function renderBgBody(d) {
let h = '<div class="page" id="bg-page">';
h += '<div class="card"><h3 class="card-title">后台活动 App(meow_amsd bg · 只读)</h3>';
if (!d || d.available === false) {
h += homeUnavailable();
} else {
const apps = d.apps || [];
if (!apps.length) {
h += '<div class="empty">无后台 App</div>';
} else {
h += '<div class="bg-list">';
apps.forEach(a => {
const cpu = (a.cpu_pct == null) ? '—' : a.cpu_pct.toFixed(1) + '%';
const mem = (a.rss_kb == null) ? '—' : fmtBytes(Number(a.rss_kb) * 1024);
const sub = 'CPU ' + cpu + ' · ' + mem + ' · 后台' +
(a.pid > 0 ? ' · pid ' + esc(a.pid) : '');
h += '<div class="bg-row">' + bgIconHtml(a) +
'<div class="bg-info"><div class="bg-name">' + esc(a.name || a.pkg || '未命名') + '</div>' +
'<div class="bg-sub">' + sub + '<span class="mono">' + esc(a.pkg) + '</span></div></div>' +
'<button class="btn btn-danger" data-bgkill="' + esc(a.pkg) + '">杀死</button></div>';
});
h += '</div>';
}
h += '<p class="muted">占用数据读自 /proc/&lt;pid&gt;(与启动器后台管理页同源、同口径);「杀死」经 amsd(require_auth)。CPU 首采样为「—」,轮询后显示真实占用;数据不可得时如实显示「—」,绝不编造。</p>';
}
h += '</div></div>';
return h;
}
async function fetchBgOnce() {
const el = $('#bg-page');
if (!el || state.route !== 'bg') return;
let d = null;
try { d = await api('GET', '/api/ams/bg'); }
catch (e) {
if (state.route === 'bg') el.innerHTML = '<div class="page" id="bg-page">' + emptyState('加载失败:' + errText(e), e.code) + '</div>';
const bc = $('#bg-clear'); if (bc) bc.disabled = true;
return;
}
if (state.route !== 'bg') return;
bgData = d;
el.innerHTML = renderBgBody(d);
const bc = $('#bg-clear');
if (bc) {
const n = (d && d.apps && d.apps.length) ? d.apps.length : 0;
bc.disabled = !n;
bc.textContent = '一键清后台' + (n ? ' (' + n + ')' : '');
}
}
function renderBg() {
stopBg();
$('#content').innerHTML = '<div class="page" id="bg-page"><p class="placeholder">加载后台 App…</p></div>';
fetchBgOnce();
bgTimer = setInterval(fetchBgOnce, 4000);
}
async function bgKill(pkg) {
const ok = await confirmDlg('结束 App', '确认结束后台 App ' + pkg + '?(SIGTERM 语义)', true);
if (!ok) return;
try { await api('POST', '/api/ams/' + encodeURIComponent(pkg) + '/kill'); toast('已请求结束 ' + pkg); }
catch (e) { toast('结束失败:' + errText(e)); }
fetchBgOnce();
}
async function bgClearAll() {
const list = (bgData && bgData.apps) ? bgData.apps : [];
if (!list.length) { toast('无后台 App 可清'); return; }
const ok = await confirmDlg('一键清除后台', '将结束 ' + list.length + ' 个后台 App,确认?(SIGTERM 语义)', true);
if (!ok) return;
let done = 0, fail = 0;
for (const a of list) {
try { await api('POST', '/api/ams/' + encodeURIComponent(a.pkg) + '/kill'); done++; }
catch (e) { fail++; }
}
toast('已清 ' + done + ' 个后台 App' + (fail ? ',' + fail + ' 个失败' : ''));
fetchBgOnce();
}
function bindBg() {
$('#content').addEventListener('click', async function (e) {
const kl = e.target.closest('[data-bgkill]');
if (kl) { await bgKill(kl.dataset.bgkill); return; }
});
const bc = $('#bg-clear');
if (bc) bc.addEventListener('click', bgClearAll);
}
function stopNet() { if (netTimer) { clearInterval(netTimer); netTimer = null; } }
function fmtDur(sec) { // 连接寿命(秒级精度)
@@ -2376,5 +2475,5 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
});
}
Object.assign(MP, { absFor, appInfo, appStatePill, appUninstall, applyScreenDeltaDom, audioCard, audioCtlHtml, audioFlowHtml, audioPortName, audioRouteLabel, batteryCard, batteryCardBody, bindApps, bindAudio, bindFiles, bindFonts, bindHome, bindLogs, bindNet, bindNotif, bindPin, bindPower, bindProc, bindScreen, bindSettings, bindSystem, breakdownCardBody, btCard, createImgFromBlob, crumbHtml, deviceCard, doDownload, ensureScreenCanvas, escSafeAttr, estimateCardBody, eventsBar, fetchAudioOnce, fetchHomeLevel, fetchHomeSystem, fetchNetConns, fetchNetLog, fetchNetOnce, fetchOverviewOnce, fetchPerfOnce, fetchPowerOnce, fetchProcOnce, fetchScreen, fmtDur, fmtFrames, groupName, handleRemoteGesture, healthCard, importFont, importFontPage, installFromFile, installFromPath, joinRel, loadFonts, loadFontsPage, loadPinStatus, logsCrashCard, mkdirFile, navFiles, netBadge, netBannerErr, netCard, netConnsBody, netDnsBody, netIfaceCards, netPolicyCard, netStateCls, netStatusCards, newestStdout, perfCard, pinEdit, pinEditDlg, powerEventsCard, powerHistoryCard, powerPolicyCard, powerProfileLabel, powerSummaryCard, pubBytesCard, remoteBtn, remotePadHtml, remotePointerDown, remotePointerUp, removeFile, renameFile, renderApps, renderAudio, renderBright, renderFiles, renderFontsPage, renderHome, renderHomeCards, renderLogs, renderLogsFiles, renderNet, renderNotif, renderPerf, renderPerfData, renderPower, renderPowerBody, renderProc, renderProcBody, renderScreen, renderSettings, renderSystem, restartScreenPoll, row, screenTap, seedScreenCanvas, segments, sendKey, sendKeyLabel, sendKeyRepeat, sendTheme, setBrightCtl, setPanelPassword, settingControl, srcPill, stopAudio, stopHome, stopNet, stopPerf, stopPower, stopProc, stopScreenPoll, storageCard, svcStatePill, uploadFiles, viewAppStdout, viewLogsCrashTail, volSteps });
Object.assign(MP, { absFor, appInfo, appStatePill, appUninstall, applyScreenDeltaDom, audioCard, audioCtlHtml, audioFlowHtml, audioPortName, audioRouteLabel, batteryCard, batteryCardBody, bgClearAll, bgIconHtml, bindBg, bindApps, bindAudio, bindFiles, bindFonts, bindHome, bindLogs, bindNet, bindNotif, bindPin, bindPower, bindProc, bindScreen, bindSettings, bindSystem, breakdownCardBody, btCard, createImgFromBlob, crumbHtml, deviceCard, doDownload, ensureScreenCanvas, escSafeAttr, estimateCardBody, eventsBar, fetchAudioOnce, fetchBgOnce, fetchHomeLevel, fetchHomeSystem, fetchNetConns, fetchNetLog, fetchNetOnce, fetchOverviewOnce, fetchPerfOnce, fetchPowerOnce, fetchProcOnce, fetchScreen, fmtDur, fmtFrames, groupName, handleRemoteGesture, healthCard, importFont, importFontPage, installFromFile, installFromPath, joinRel, loadFonts, loadFontsPage, loadPinStatus, logsCrashCard, mkdirFile, navFiles, netBadge, netBannerErr, netCard, netConnsBody, netDnsBody, netIfaceCards, netPolicyCard, netStateCls, netStatusCards, newestStdout, perfCard, pinEdit, pinEditDlg, powerEventsCard, powerHistoryCard, powerPolicyCard, powerProfileLabel, powerSummaryCard, pubBytesCard, remoteBtn, remotePadHtml, remotePointerDown, remotePointerUp, removeFile, renameFile, renderApps, renderAudio, renderBg, renderBgBody, renderBright, renderFiles, renderFontsPage, renderHome, renderHomeCards, renderLogs, renderLogsFiles, renderNet, renderNotif, renderPerf, renderPerfData, renderPower, renderPowerBody, renderProc, renderProcBody, renderScreen, renderSettings, renderSystem, restartScreenPoll, row, screenTap, seedScreenCanvas, segments, sendKey, sendKeyLabel, sendKeyRepeat, sendTheme, setBrightCtl, setPanelPassword, settingControl, srcPill, stopAudio, stopBg, stopHome, stopNet, stopPerf, stopPower, stopProc, stopScreenPoll, storageCard, svcStatePill, uploadFiles, viewAppStdout, viewLogsCrashTail, volSteps });
})();