Files
MeowOS-For-R3P2/patch_v2/meowpanel/src/mp_ams.c
T
AskaEth f40abbd494 feat: M6.43 — reader分段流式读取(Phase1,只txt) + 面板前台管理/返回回桌面按钮
- reader分段(只txt): core/seg.{h,c} 512KB码位对齐切段(尾字完整)+manifest+按需读单段; book.c rd_book_open只布局每页所在段(治CPU100%/返回无效) load_seg只读当前段(非txt原路径); reader.c 进度实时保存(翻页/刮擦/字号/编码dirty+on_tick节流落盘+退出即时写,锚点存段号,崩溃不丢进度); rdtxt.c fread替代fgetc(去开书扫描卡顿); Phase2(双段拼接+4%释放)留待
- 面板: /api/ams/bg加fg/fg_count(后台保持向后兼容)+POST /api/ams/:pkg/killfg(前台救命:优先am kill,launcher拒则pid直信号SIGTERM->SIGKILL绕过门控)+前端/#/bg前台组'终止'绿徽标+确认弹窗(一键清仍只后台不误杀前台); POST /api/input/gesture(返回=左缘右滑/回桌面=底部上滑,注入滑动轨迹复用SDK手势)+前端屏幕控制页加返回/回桌面MD3按钮

0warning;宿主test_reader_core 72-0/seg_smoke 19-0/loaders_smoke/book_smoke/formats_smoke PASS;面板selftest/node check PASS
2026-11-04 19:47:10 +08:00

445 lines
18 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* mp_ams.c — App 运行表域:am list 解析 + amd 历史 + start/kill 控制。 */
#include "mp.h"
const char *ams_bin(void)
{
const char *e = getenv("MEOW_AMS_BIN");
if (e && e[0]) return e;
if (!access(AMS_BIN, X_OK)) return AMS_BIN;
return AMS_BIN_CARD;
}
void ams_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);
char *toks[256];
sbuf_t apps = {0};
int first = 1, count = 0;
char *ln = out;
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) {
/* 布局:APP <pkg> <name...> <state> <pid> <launches> <crashes> <last_kind> <last_code>
* tok[0]="APP" 前缀;自右向左固定:state=tok[n-6], pid=n-5, launches=n-4, crashes=n-3,
* last_kind=n-2, last_code=n-1;pkg=tok[1],name=join(tok[2..n-6))。 */
char nm[128];
join_tokens(toks, 2, n - 6, nm, sizeof nm);
if (!first) sA(&apps, ",");
first = 0; count++;
sA(&apps, "{\"pkg\":"); sA_str(&apps, toks[1]);
sA(&apps, ",\"name\":"); sA_str(&apps, nm);
sA(&apps, ",\"state\":"); sA_str(&apps, toks[n-6]);
sA(&apps, ",\"pid\":%d", atoi(toks[n-5]));
sA(&apps, ",\"launches\":%d", atoi(toks[n-4]));
sA(&apps, ",\"crashes\":%d", atoi(toks[n-3]));
sA(&apps, ",\"last_kind\":"); sA_str(&apps, toks[n-2]);
sA(&apps, ",\"last_code\":%d}", atoi(toks[n-1]));
}
}
if (nl) { *nl = '\n'; ln = nl + 1; } else break;
}
sA(s, "{\"available\":true,\"count\":%d,\"apps\":[%s]", count, apps.b ? apps.b : "");
sbuf_free(&apps);
ams_fg_fill(s); /* 追加 `,"fg":{..},"running":[..]`(首页前台 App 高亮;additive) */
sA(s, "}");
}
void ams_fg_fill(sbuf_t *s)
{
char out[16384];
char cmd[300];
snprintf(cmd, sizeof cmd, "%s status 2>/dev/null", ams_bin());
int code = run_cmd(cmd, out, sizeof out);
if (code != 0 || !strstr(out, "RUN")) { sA(s, ",\"fg\":null,\"running\":[]"); return; }
panel_strip_ansi(out);
char *toks[256];
char fg_pkg[128] = "", fg_act[160] = ""; int fg_pid = 0;
sbuf_t run = {0}; int first = 1;
char *ln = out;
while (*ln) {
char *nl = strchr(ln, '\n');
if (nl) *nl = 0;
if (strncmp(ln, "RUN ", 4) == 0) {
int n = txt_tokens(ln, toks, 256);
if (n >= 6) { /* RUN pkg state pid dur activity... */
char act[160];
join_tokens(toks, 5, n, act, sizeof act);
const char *st = toks[2];
if (!first) sA(&run, ",");
first = 0;
sA(&run, "{\"pkg\":"); sA_str(&run, toks[1]);
sA(&run, ",\"state\":"); sA_str(&run, st);
sA(&run, ",\"pid\":%d", atoi(toks[3]));
sA(&run, ",\"activity\":"); sA_str(&run, act);
sA(&run, "}");
if (strcmp(st, "fg") == 0 && !fg_pkg[0]) {
snprintf(fg_pkg, sizeof fg_pkg, "%s", toks[1]);
snprintf(fg_act, sizeof fg_act, "%s", act);
fg_pid = atoi(toks[3]);
}
}
}
if (nl) { *nl = '\n'; ln = nl + 1; } else break;
}
sA(s, ",\"fg\":");
if (fg_pkg[0]) { sA(s, "{\"pkg\":"); sA_str(s, fg_pkg); sA(s, ",\"activity\":"); sA_str(s, fg_act); sA(s, ",\"pid\":%d}", fg_pid); }
else sA(s, "null");
sA(s, ",\"running\":[%s]", run.b ? run.b : "");
sbuf_free(&run);
}
int api_ams(int sock)
{
sbuf_t s = {0};
ams_fill(&s);
int rc = send_json(sock, 200, s.b ? s.b : "{\"available\":false}");
sbuf_free(&s);
return rc;
}
int ams_pkg_ok(const char *pkg)
{
if (!pkg || !pkg[0]) return 0;
if (strlen(pkg) >= MEOW_APP_ID_MAX) return 0;
for (const char *p = pkg; *p; p++) {
unsigned char c = (unsigned char)*p;
if (c == '/' || c == ':' || c == ' ' || c == '\t' || c == '\n' || c == '\r') return 0;
if (c < 0x21 || c > 0x7e) return 0; /* 包名仅 ASCII 可见字符 */
}
return 1;
}
void ams_history_parse(char *out, sbuf_t *s, const char *pkg) /* out 被 panel_strip_ansi 就地改 */
{
if (!utf8_valid_n(out, strlen(out))) { sA(s, "{\"available\":false}"); return; } /* 崩溃 detail 坏字节 → 降级 */
panel_strip_ansi(out);
char *toks[256];
sbuf_t ev = {0};
int first = 1, count = 0;
char *ln = out;
while (*ln) {
char *nl = strchr(ln, '\n');
if (nl) *nl = 0;
if (strncmp(ln, "EVT ", 4) == 0) {
int n = txt_tokens(ln, toks, 256);
if (n >= 5) { /* EVT epoch up kind pkg detail... */
char det[256];
join_tokens(toks, 5, n, det, sizeof det);
if (!first) sA(&ev, ",");
first = 0; count++;
sA(&ev, "{\"ts\":%ld,\"uptime_sec\":%ld,\"kind\":", atol(toks[1]), atol(toks[2]));
sA_str(&ev, toks[3]);
sA(&ev, ",\"detail\":"); sA_str(&ev, det);
sA(&ev, "}");
}
}
if (nl) { *nl = '\n'; ln = nl + 1; } else break;
}
sA(s, "{\"available\":true,\"pkg\":"); sA_str(s, pkg);
sA(s, ",\"count\":%d,\"events\":[%s]}", count, ev.b ? ev.b : "");
sbuf_free(&ev);
}
void ams_history_fill(sbuf_t *s, const char *pkg)
{
char out[16384];
char cmd[320];
snprintf(cmd, sizeof cmd, "%s history %s 2>/dev/null", ams_bin(), pkg);
int code = run_cmd(cmd, out, sizeof out);
if (code != 0 || !strstr(out, "EVT")) { sA(s, "{\"available\":false}"); return; }
ams_history_parse(out, s, pkg);
}
int api_ams_history(int sock, const char *pkg)
{
if (!ams_pkg_ok(pkg)) return send_err(sock, 400, "BAD_REQUEST", "包名非法");
sbuf_t s = {0};
ams_history_fill(&s, pkg);
int rc = send_json(sock, 200, s.b ? s.b : "{\"available\":false}");
sbuf_free(&s);
return rc;
}
int api_ams_ctl(int sock, const char *pkg, int kill)
{
if (!ams_pkg_ok(pkg)) return send_err(sock, 400, "BAD_REQUEST", "包名非法");
char out[256];
char cmd[320];
snprintf(cmd, sizeof cmd, "%s %s %s 2>/dev/null", ams_bin(), kill ? "kill" : "start", pkg);
int code = run_cmd(cmd, out, sizeof out);
if (code == 0 && strstr(out, "OK ")) {
sbuf_t s = {0};
sA(&s, "{\"accepted\":true,\"op\":\"%s\",\"pkg\":", kill ? "kill" : "start");
sA_str(&s, pkg);
sA(&s, ",\"message\":"); sA_str(&s, out);
sA(&s, "}");
int rc2 = send_json(sock, 200, s.b ? s.b : "");
sbuf_free(&s);
return rc2;
}
/* am CLI 以文本报告失败(ERR ...);按内容映射 HTTP 码(如实,绝不掩盖)。 */
const char *detail = out[0] ? out : (kill ? "am kill 失败" : "am start 失败");
if (strstr(detail, "not registered")) return send_err(sock, 404, "NOT_FOUND", detail);
if (strstr(detail, "busy")) return send_err(sock, 409, "CONFLICT", detail);
if (strstr(detail, "not running")) return send_err(sock, 409, "CONFLICT", detail);
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; }
}
}
}
/* 发射单条 APP 行 JSON(复用于 bg/fg 列表;带 state + is_fg + cpu_pct/rss_kb)。
* toks 为已切分的 `APP ...` 行(n>=9);*first 控制数组元素逗号(调用方初始化为 1)。
* cpu_pct/rss_kb 诚实原则:pid 读不到/不作为可用进程 → null(前端显「—」),绝不编造。 */
static void ams_entry_emit(sbuf_t *out, char **toks, int n, int *first)
{
const char *st = toks[n - 6]; /* 自右向左:state=tok[n-6], pid=n-5(与 ams_fill 同布局) */
char nm[128];
join_tokens(toks, 2, n - 6, nm, sizeof nm);
int pid = atoi(toks[n - 5]);
double pcpu = -1.0; long rss = -1;
if (pid > 0) bg_proc_sample(pid, &pcpu, &rss);
if (!*first) sA(out, ",");
*first = 0;
sA(out, "{\"pkg\":"); sA_str(out, toks[1]);
sA(out, ",\"name\":"); sA_str(out, nm);
sA(out, ",\"pid\":%d", pid);
sA(out, ",\"state\":"); sA_str(out, st);
sA(out, ",\"is_fg\":%s", strcmp(st, "fg") == 0 ? "true" : "false");
sA(out, ",\"cpu_pct\":");
if (pcpu < 0) sA(out, "null"); else sA(out, "%.1f", pcpu);
sA(out, ",\"rss_kb\":");
if (rss < 0) sA(out, "null"); else sA(out, "%ld", rss);
sA(out, "}");
}
/* 解析已 strip 的 `am list` 全文 → bg 列表 JSON(state=bg 的 APP 行;向后兼容专用,selftest 覆盖)。 */
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) {
const char *st = toks[n - 6];
if (strcmp(st, "bg") == 0) {
count++;
ams_entry_emit(&apps, toks, n, &first);
}
}
}
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);
}
/* am list 全文 → bg+fg 双列表 JSON。`apps`(bg)保持向后兼容(count=bg 数量);
* 追加 `fg` + `fg_count`(前台 App,前端救命分组)。state=stop 无进程,不在本页(proc 页可见)。 */
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);
char *toks[256];
sbuf_t bg = {0}, fg = {0};
int bg_first = 1, fg_first = 1, bg_count = 0, fg_count = 0;
char *ln = out;
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) {
const char *st = toks[n - 6];
if (strcmp(st, "bg") == 0) { bg_count++; ams_entry_emit(&bg, toks, n, &bg_first); }
else if (strcmp(st, "fg") == 0) { fg_count++; ams_entry_emit(&fg, toks, n, &fg_first); }
}
}
if (nl) { *nl = '\n'; ln = nl + 1; } else break;
}
sA(s, "{\"available\":true,\"count\":%d,\"ts\":%ld,\"apps\":[%s]",
bg_count, (long)time(NULL), bg.b ? bg.b : "");
sA(s, ",\"fg_count\":%d,\"fg\":[%s]}", fg_count, fg.b ? fg.b : "");
sbuf_free(&bg);
sbuf_free(&fg);
}
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;
}
/* 从 am list 解析某 pkg 的当前 pid(未注册/无进程 → 0)。 */
static int ams_pkg_pid(const char *pkg)
{
char out[16384];
char cmd[300];
snprintf(cmd, sizeof cmd, "%s list 2>/dev/null", ams_bin());
if (run_cmd(cmd, out, sizeof out) != 0 || !strstr(out, "APP")) return 0;
panel_strip_ansi(out);
char *toks[256];
char *ln = out;
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 && strcmp(toks[1], pkg) == 0) {
int pid = atoi(toks[n - 5]);
if (nl) *nl = '\n';
return pid;
}
}
if (nl) { *nl = '\n'; ln = nl + 1; } else break;
}
return 0;
}
/* /proc/<pid>/stat 进程是否已消失/僵尸:-1=已消失,1=僵尸,0=存活。 */
static int proc_zombie(int pid)
{
char path[64], line[256];
snprintf(path, sizeof path, "/proc/%d/stat", pid);
if (read_small_file(path, line, sizeof line) < 0) return -1;
char *rp = strrchr(line, ')');
if (!rp) return -1;
const char *p = rp + 2;
while (*p == ' ' || *p == '\t') p++;
return (*p == 'Z') ? 1 : 0;
}
/* 前台强制终止(救命):优先走 `am kill`(launcher 干净 SIGTERM 序列,accepted);若 launcher 拒
* (framework / 无前台会话 → "launcher refused"),回退:解析 am list 拿当前 pid → 直接 SIGTERM,
* 宽限后仍存活走 SIGKILL(与 launcher 杀序列同语义)。这是用户明示「前台卡死救命」的强通道——
* 前端已二次确认 + 标注「前台」。require_auth 在路由层已护。 */
int api_ams_kill_fg(int sock, const char *pkg)
{
if (!ams_pkg_ok(pkg)) return send_err(sock, 400, "BAD_REQUEST", "包名非法");
/* ① 干净通道:am kill(launcher 执行 SIGTERM 序列)。 */
{
char out[256];
char cmd[320];
snprintf(cmd, sizeof cmd, "%s kill %s 2>/dev/null", ams_bin(), pkg);
int code = run_cmd(cmd, out, sizeof out);
if (code == 0 && strstr(out, "OK ")) {
sbuf_t s = {0};
sA(&s, "{\"accepted\":true,\"op\":\"kill-fg\",\"pkg\":");
sA_str(&s, pkg);
sA(&s, ",\"force\":false,\"message\":"); sA_str(&s, out);
sA(&s, "}");
int rc = send_json(sock, 200, s.b ? s.b : "");
sbuf_free(&s);
return rc;
}
panel_log("ams kill-fg %s via am refused: %s -> direct signal fallback", pkg,
out[0] ? out : "(empty)");
}
/* ② 回退:直接按 pid 信号(绕过 launcher 门控)。 */
int pid = ams_pkg_pid(pkg);
if (pid <= 0) return send_err(sock, 409, "CONFLICT", "该 App 当前无进程可终止");
kill(pid, SIGTERM);
int killed_hard = 0;
for (int i = 0; i < 5; i++) { /* SIGTERM 宽限 ~0.6s(与 launcher 杀序列同语义) */
struct timespec ts = {0, 120 * 1000 * 1000};
nanosleep(&ts, NULL);
int z = proc_zombie(pid);
if (z < 0 || z == 1) break; /* 消失/僵尸 → 已终止 */
}
if (proc_zombie(pid) == 0) { kill(pid, SIGKILL); killed_hard = 1; }
char msg[192];
snprintf(msg, sizeof msg, "force terminated pid %d via direct signal%s", pid,
killed_hard ? " (SIGKILL 强杀)" : " (SIGTERM)");
sbuf_t s = {0};
sA(&s, "{\"accepted\":true,\"op\":\"kill-fg\",\"pkg\":");
sA_str(&s, pkg);
sA(&s, ",\"force\":%s,\"pid\":%d,\"message\":", killed_hard ? "true" : "false", pid);
sA_str(&s, msg);
sA(&s, "}");
int rc = send_json(sock, 200, s.b ? s.b : "");
sbuf_free(&s);
return rc;
}