feat(launcher): M4 后台服务保活+后台播放(FRAMEWORK_DESIGN §5/§9 落地)
- app.json 新字段 background/background_audio 解析入 struct app/meow_acct 注册表
- meow_ipc.h:MSG_REGISTER(m_register_t)/SERVICE_OK(m_ack_t.status=pid)/SERVICE_STOP/
MSG_SYS(m_sys_t, SHUTDOWN=1/SVC_DEAD=2) 负载结构落地
- 新模块 meow_bgplay.c/h(自包含+ops 注入,仿 meow_media,宿主可单测):
· 后台播放保活——声明 background_audio 且媒体会话 PLAYING 的 App nav-exit 不终止,
进程+音频流保留;UI 冻结走 §10.5 /tmp/meow_notif_active SDK present 自抑(非字面
SIGSTOP:冻结同进程播放线程断供 PCM,§10.5 真机实证);点图标/媒体卡片回前台=
原进程恢复(chain/picker 忽略),停播后再退出=真退 SIGTERM
· 无头后台服务——MSG_REGISTER spawn/stop/reap + 退避重启 1/2/4/10s cap +
max_restart 熔断(清该 App 通知 + 注册连接广播 MSG_SYS{SVC_DEAD},重注册复位)
· 卡拔例外(卡上二进制后台 App/服务仍 SIGTERM 防 SIGBUS)、崩溃照收、
关机前 meow_bgplay_kill_all 有界回收(含 MSG_SYS{SHUTDOWN} 广播)
- util.c:MSG_REGISTER/MSG_SERVICE_STOP 分发(权限/声明校验链)+ ipc_conn_alive 连接
存活查询 + meow_ipc_process 末尾挂 meow_bg_housekeep(drain/reap/退避 tick)
- app_launcher.c:launch_app_chain 入口 resume(bg_take 原进程回前台)+ nav-exit
保活分支 + 转后台出口(杀看门狗/写自抑标记/复位顶条,跳过退出尾)+ 冷启动自动
拉起静态 keepalive(mode=0) 服务(exec 路径约定=App 同目录 <svc> 文件)
- main_menu.c:应用列表「·后台」标注(宫格/列表,1s tick 按 change_seq 刷新)+
两条退出尾 kill_all + unlink NOTIF_ACTIVE
- 宿主单测 test_bgplay.c 50 项全过(真 fork 子进程走状态机 + 假时钟退避熔断);
test_media/test_notif_panel/test_notif_tf/test_vol_ramp_jack 回归全过;
zig-mipsel-cc -Wall -Wextra 0 warning。launcher md5=c07d011c0769fca14d3bfb752d735463
This commit is contained in:
1 parent
7db8197f9c
commit
5aabede0cd
13 files changed
+1461
-40
No files matched your search
@@ -89,20 +89,27 @@ static void ams_report_launch(const struct app *a, const char *chain, pid_t pid)
|
||||
if (l > 0) ams_send(line, l);
|
||||
}
|
||||
|
||||
/* exit 上报:kind(exit|sig)/code/dur_ms。异常退出由 amsd 判 crash + 转发 meow_crash。 */
|
||||
static void ams_report_exit(const struct app *a, int status, long long dur_ms)
|
||||
/* exit 上报:kind(exit|sig)/code/dur_ms。异常退出由 amsd 判 crash + 转发 meow_crash。
|
||||
* _id 变体供 M4 后台保活回收路径用(彼时 struct app 可能已不在手边)。 */
|
||||
static void ams_report_exit_id(const char *app_id, int status, long long dur_ms)
|
||||
{
|
||||
if (!a) return;
|
||||
if (!app_id || !app_id[0]) return;
|
||||
const char *kind;
|
||||
int code;
|
||||
if (WIFSIGNALED(status)) { kind = "sig"; code = WTERMSIG(status); }
|
||||
else { kind = "exit"; code = WIFEXITED(status) ? WEXITSTATUS(status) : 1; }
|
||||
char line[256];
|
||||
int l = snprintf(line, sizeof line, "EV EXIT %s %s %d %lld %ld %ld\n",
|
||||
a->id, kind, code, dur_ms, (long)time(NULL), ams_uptime());
|
||||
app_id, kind, code, dur_ms, (long)time(NULL), ams_uptime());
|
||||
if (l > 0) ams_send(line, l);
|
||||
}
|
||||
|
||||
static void ams_report_exit(const struct app *a, int status, long long dur_ms)
|
||||
{
|
||||
if (!a) return;
|
||||
ams_report_exit_id(a->id, status, dur_ms);
|
||||
}
|
||||
|
||||
/* ---- stdout/stderr 管道捕获(AMSD_DESIGN §5):前台 waitpid 循环排空 + 按行转发 ---- */
|
||||
static char g_ams_out_line[1024]; /* 未完结行缓冲(无 \n) */
|
||||
static int g_ams_out_n = 0;
|
||||
@@ -119,17 +126,17 @@ static void ams_out_flush(void)
|
||||
}
|
||||
|
||||
/* 追加一行(line 为无 \n 的 NUL 串)到 flush 缓冲;满了先 flush。 */
|
||||
static void ams_out_line(const struct app *a, pid_t pid, const char *line)
|
||||
static void ams_out_line(const char *app_id, pid_t pid, const char *line)
|
||||
{
|
||||
if (!a || !line) return;
|
||||
if (!app_id || !line) return;
|
||||
int need = snprintf(NULL, 0, "EV OUT %s %d %ld %ld %s\n",
|
||||
a->id, (int)pid, (long)time(NULL), ams_uptime(), line);
|
||||
app_id, (int)pid, (long)time(NULL), ams_uptime(), line);
|
||||
if (g_ams_out_flush_n + need + 1 > (int)sizeof g_ams_out_flush) ams_out_flush();
|
||||
if (need < (int)sizeof g_ams_out_flush - 1 - g_ams_out_flush_n)
|
||||
g_ams_out_flush_n += snprintf(g_ams_out_flush + g_ams_out_flush_n,
|
||||
sizeof g_ams_out_flush - g_ams_out_flush_n,
|
||||
"EV OUT %s %d %ld %ld %s\n",
|
||||
a->id, (int)pid, (long)time(NULL), ams_uptime(), line);
|
||||
app_id, (int)pid, (long)time(NULL), ams_uptime(), line);
|
||||
}
|
||||
|
||||
/* 单次排空上限:App 被 OOM/信号杀后,其子孙进程可能仍继承持有 out_pipe 写端
|
||||
@@ -143,8 +150,11 @@ static void ams_out_line(const struct app *a, pid_t pid, const char *line)
|
||||
#define EXEC_CONFIRM_MS 2000
|
||||
|
||||
/* 排空读端尽可能多的字节,按行切分转发;超长行截断标记。有界:单次调用最多
|
||||
* 读 AMS_DRAIN_MAX 字节(见上)。读端已 O_NONBLOCK;App 死且写端全关 → read 返 0=EOF。 */
|
||||
static void ams_drain_stdout(const struct app *a, int fd, pid_t pid)
|
||||
* 读 AMS_DRAIN_MAX 字节(见上)。读端已 O_NONBLOCK;App 死且写端全关 → read 返 0=EOF。
|
||||
* linebuf/line_n:未完结行缓冲外提——前台会话用全局 g_ams_out_line/g_ams_out_n;
|
||||
* M4 后台保活 drain(meow_bg_housekeep)用独立缓冲,防与前台未完结行串扰。 */
|
||||
static void ams_drain_stdout(const char *app_id, int fd, pid_t pid,
|
||||
char *linebuf, int *line_n)
|
||||
{
|
||||
char tmp[2048];
|
||||
ssize_t n;
|
||||
@@ -155,19 +165,19 @@ static void ams_drain_stdout(const struct app *a, int fd, pid_t pid)
|
||||
for (ssize_t i = 0; i < n; i++) {
|
||||
char c = tmp[i];
|
||||
if (c == '\n') {
|
||||
g_ams_out_line[g_ams_out_n] = '\0';
|
||||
ams_out_line(a, pid, g_ams_out_line);
|
||||
g_ams_out_n = 0;
|
||||
linebuf[*line_n] = '\0';
|
||||
ams_out_line(app_id, pid, linebuf);
|
||||
*line_n = 0;
|
||||
} else if (c == '\r') {
|
||||
/* 去尾 \r */
|
||||
} else if (g_ams_out_n < (int)sizeof g_ams_out_line - 1) {
|
||||
g_ams_out_line[g_ams_out_n++] = c;
|
||||
} else if (*line_n < 1024 - 1) {
|
||||
linebuf[(*line_n)++] = c;
|
||||
} else {
|
||||
/* 超长行:截断标记 + 重置(当前字符丢弃) */
|
||||
g_ams_out_line[ (int)sizeof g_ams_out_line - 2 ] = '\0';
|
||||
ams_out_line(a, pid, g_ams_out_line);
|
||||
ams_out_line(a, pid, "[...trunc]");
|
||||
g_ams_out_n = 0;
|
||||
linebuf[1024 - 2] = '\0';
|
||||
ams_out_line(app_id, pid, linebuf);
|
||||
ams_out_line(app_id, pid, "[...trunc]");
|
||||
*line_n = 0;
|
||||
}
|
||||
}
|
||||
if (drained >= AMS_DRAIN_MAX) break; /* 有界:孤儿持续写也不卡死主循环 */
|
||||
@@ -311,6 +321,8 @@ static void meow_acct_sync(const char *app_id, const struct app *ap)
|
||||
struct meow_acct *a = meow_acct_upsert(app_id);
|
||||
if (!a) return;
|
||||
a->framework = ap->framework;
|
||||
a->background = ap->background; /* M4 */
|
||||
a->background_audio = ap->background_audio;
|
||||
strncpy(a->icon, ap->icon, 128);
|
||||
strncpy(a->name, ap->name, 16);
|
||||
strncpy(a->exec, ap->exec, 128);
|
||||
@@ -361,6 +373,119 @@ void meow_acct_declare(const char *app_id, const char *entries, int count)
|
||||
}
|
||||
|
||||
|
||||
/* ---- M4 后台保活 / 无头后台服务胶水(meow_bgplay ops 注入实现)----
|
||||
* 挂载:meow_bgplay_launcher_init() 由 util.c meow_ipc_init 末尾调一次;
|
||||
* meow_bg_housekeep() 挂 meow_ipc_process 末尾(主循环单写者排空点,四处循环都经它)。 */
|
||||
static void notify_app_crash(const char *pkg, int status); /* 定义见下(MEOW_CRASH_SOCK) */
|
||||
|
||||
/* 查静态声明的 service.<svc>(app.json 解析进 meow_acct.svcs);无返回 NULL。 */
|
||||
const struct meow_svc *meow_acct_svc_find(const struct meow_acct *ac, const char *svc)
|
||||
{
|
||||
if (!ac || !svc || !svc[0]) return NULL;
|
||||
for (int i = 0; i < ac->nsvc; i++)
|
||||
if (!strcmp(ac->svcs[i].name, svc)) return &ac->svcs[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 后台权限位:bit0=background(可注册无头服务)bit1=background_audio(后台音频)。 */
|
||||
int meow_acct_background_perm(const struct meow_acct *ac)
|
||||
{
|
||||
if (!ac) return 0;
|
||||
return (ac->background == '1' ? 1 : 0) | (ac->background_audio == '1' ? 2 : 0);
|
||||
}
|
||||
|
||||
/* M4 服务可执行路径约定:App 二进制同目录下的 <svc> 名文件
|
||||
* (app.json `service.<svc>=...` 只声明策略,二进制位置由此约定派生)。 */
|
||||
void meow_svc_exec_path(const char *app_exec, const char *svc, char *out, int olen)
|
||||
{
|
||||
char dir[144];
|
||||
char *sl;
|
||||
|
||||
snprintf(dir, sizeof(dir), "%s", app_exec ? app_exec : "");
|
||||
sl = strrchr(dir, '/');
|
||||
if (sl) sl[1] = 0;
|
||||
else dir[0] = 0;
|
||||
snprintf(out, (size_t)olen, "%s%s", dir, svc ? svc : "");
|
||||
}
|
||||
|
||||
/* 后台保活 drain 的独立行缓冲(防与前台会话的 g_ams_out_line 未完结行串扰,一 App 一组) */
|
||||
static char g_bg_out_line[MEOW_BG_MAX][1024];
|
||||
static int g_bg_out_n[MEOW_BG_MAX];
|
||||
|
||||
/* meow_bgplay on_bg_dead:后台保活 App 进程退出(崩溃照收/停播后自退/卡拔 SIGTERM 走完)。 */
|
||||
static void bg_on_dead(const char *app_id, pid_t pid, int status)
|
||||
{
|
||||
(void)pid;
|
||||
ams_report_exit_id(app_id, status, 0); /* dur 未知(保活起点未记),记 0 */
|
||||
if (WIFSIGNALED(status) ||
|
||||
(WIFEXITED(status) && WEXITSTATUS(status) != 0))
|
||||
notify_app_crash(app_id, status); /* 崩溃照收(SIGTERM=卡拔/关机回收也算信号,
|
||||
* meow_crash 侧自行判重;与前台同口径) */
|
||||
meow_media_clear(app_id); /* 媒体会话随源死清槽 */
|
||||
/* §10.5 自抑标记:仅当「再无后台保活 App 且无通知中心叠加会话」才清——
|
||||
* 多 bg App 共存时标记粒度为全局(v1 定案:叠加会话与后台保活共用同一标记)。 */
|
||||
if (meow_bg_count() == 0 && g_notif_ctrl_pid <= 0)
|
||||
unlink(NOTIF_ACTIVE);
|
||||
}
|
||||
|
||||
/* meow_bgplay notif_clear_app:熔断时清该 App 全部活跃通知(先收集 id 再 dismiss,
|
||||
* 防遍历时改表)。返回清除条数。 */
|
||||
struct bg_clear_ctx { uint32_t ids[16]; int n; };
|
||||
static int bg_notif_collect_cb(const meow_notif_t *nt, void *ctx)
|
||||
{
|
||||
struct bg_clear_ctx *c = ctx;
|
||||
if (c->n < 16) c->ids[c->n++] = nt->id;
|
||||
return c->n >= 16; /* 集满即中止遍历 */
|
||||
}
|
||||
static int bg_notif_clear_app(const char *app_id)
|
||||
{
|
||||
struct bg_clear_ctx c;
|
||||
|
||||
memset(&c, 0, sizeof(c));
|
||||
meow_notif_list(app_id, bg_notif_collect_cb, &c);
|
||||
for (int i = 0; i < c.n; i++) meow_notif_dismiss(c.ids[i]);
|
||||
return c.n;
|
||||
}
|
||||
|
||||
/* meow_bgplay send_frame:熔断广播投递前判 IPC 连接存活(注册方可能已断开)。 */
|
||||
static size_t bg_send_frame(int fd, uint16_t type, uint32_t req_id,
|
||||
const void *payload, uint32_t len)
|
||||
{
|
||||
if (!meow_ipc_conn_alive(fd)) return 0;
|
||||
return meow_ipc_send_pub(fd, type, req_id, payload, len);
|
||||
}
|
||||
|
||||
void meow_bgplay_launcher_init(void)
|
||||
{
|
||||
meow_bgplay_ops ops;
|
||||
|
||||
memset(&ops, 0, sizeof(ops));
|
||||
ops.log = logmsg;
|
||||
ops.now_ms = NULL; /* 内部 CLOCK_MONOTONIC */
|
||||
ops.card_present = sd_device_present;
|
||||
ops.on_bg_dead = bg_on_dead;
|
||||
ops.on_svc_dead = NULL; /* 服务消亡日志 meow_bgplay 内已记 */
|
||||
ops.notif_clear_app = bg_notif_clear_app;
|
||||
ops.send_frame = bg_send_frame;
|
||||
ops.log_dir = log_dir[0] ? log_dir : NULL;
|
||||
meow_bgplay_init(&ops);
|
||||
}
|
||||
|
||||
/* 后台表周期维护(meow_ipc_process 末尾调):drain 各 bg App stdout(防写满管道阻塞
|
||||
* 播放线程)+ 回收/卡拔 tick + 服务退避重启 tick。 */
|
||||
void meow_bg_housekeep(void)
|
||||
{
|
||||
for (int i = 0; i < MEOW_BG_MAX; i++) {
|
||||
const meow_bg_entry_t *e = meow_bg_at(i);
|
||||
if (e && e->out_fd >= 0)
|
||||
ams_drain_stdout(e->app_id, e->out_fd, e->pid,
|
||||
g_bg_out_line[i], &g_bg_out_n[i]);
|
||||
}
|
||||
meow_bg_tick();
|
||||
meow_svc_tick();
|
||||
}
|
||||
|
||||
|
||||
/* 链路深度 = ';' 分隔段数(根屏→目标屏的逻辑链长)。 */
|
||||
int chain_depth(const char *c)
|
||||
{
|
||||
@@ -525,6 +650,8 @@ void meow_parse_app_json(FILE *f, struct app *a)
|
||||
int fram = 0, immersive = 0;
|
||||
int fullscreen = 0; /* app.json fullscreen=<true|1>(注入 MEOW_FULLSCREEN env,SDK 预先设全屏) */
|
||||
int fonts = 0, fonts_set = 0; /* app.json "fonts":<档>(0=默认4K常用 1=大字库GBK 2=Full;缺省 0) */
|
||||
int background = 0; /* M4:app.json background=true(可注册无头后台服务) */
|
||||
int bg_audio = 0; /* M4:app.json background_audio=true(播放中退出转后台保活) */
|
||||
struct app tmp;
|
||||
memset(&tmp, 0, sizeof tmp);
|
||||
while (fgets(buf, sizeof buf, f)) {
|
||||
@@ -638,6 +765,24 @@ void meow_parse_app_json(FILE *f, struct app *a)
|
||||
fonts_set = 1;
|
||||
}
|
||||
}
|
||||
/* "background":true(M4:可注册无头后台服务,MSG_REGISTER 权限位0)。
|
||||
* 注意 "background" 是 "background_audio" 的前缀:反向误命中(本 key 匹配到
|
||||
* background_audio 行)时 json_str 读出 "_audio…" ≠ true,判定自然为 0,无害。 */
|
||||
if (!background && strstr(buf, "background")) {
|
||||
char v[8] = "";
|
||||
if (json_str(buf, "background", v, sizeof v) > 0) {
|
||||
if (!strcmp(v, "true") || !strcmp(v, "1") || !strcmp(v, "yes"))
|
||||
background = 1;
|
||||
}
|
||||
}
|
||||
/* "background_audio":true(M4:播放中退出转后台保活,进程+音频流保留,§9) */
|
||||
if (!bg_audio && strstr(buf, "background_audio")) {
|
||||
char v[8] = "";
|
||||
if (json_str(buf, "background_audio", v, sizeof v) > 0) {
|
||||
if (!strcmp(v, "true") || !strcmp(v, "1") || !strcmp(v, "yes"))
|
||||
bg_audio = 1;
|
||||
}
|
||||
}
|
||||
/* service.<svc>=mode:restart:max:window_ms(M1 解析存储,保活 M4) */
|
||||
char *sp = strstr(buf, "service.");
|
||||
if (sp && tmp.nsvc < MEOW_MAX_SVCS) {
|
||||
@@ -670,6 +815,8 @@ void meow_parse_app_json(FILE *f, struct app *a)
|
||||
a->immersive = immersive ? '1' : 0;
|
||||
a->fullscreen = fullscreen ? '1' : 0; /* 注入 MEOW_FULLSCREEN env */
|
||||
a->fonts = (char)(fonts < 0 ? 0 : (fonts > 2 ? 2 : fonts)); /* 注入 MEOW_FONT_TIER env */
|
||||
a->background = background ? '1' : 0; /* M4:后台服务注册权限 */
|
||||
a->background_audio = bg_audio ? '1' : 0; /* M4:后台播放保活 */
|
||||
strncpy(a->icon, icon, 128);
|
||||
a->nroute = tmp.nroute;
|
||||
if (tmp.nroute) memcpy(a->routes, tmp.routes, sizeof(a->routes[0]) * (size_t)tmp.nroute);
|
||||
@@ -1295,6 +1442,25 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
g_ams_pick_armed = 0;
|
||||
}
|
||||
if (!a->ok) { logmsg("launch blocked: exec not ok"); return -1; }
|
||||
|
||||
/* M4 后台保活恢复:该 App 驻留后台(播放中经 nav-exit 转过)→ 原进程回前台,
|
||||
* 不 fork/exec(进程仍存无法注入新 env:chain/picker 上下文忽略,记日志)。
|
||||
* pid/out_fd 外提:冷启动与恢复两路填充,其后 watchdog/waitpid 循环共用。 */
|
||||
pid_t pid = 0;
|
||||
int out_fd = -1;
|
||||
int resumed = 0;
|
||||
if (meow_bg_take(a->id, &out_fd, &pid) == 0) {
|
||||
resumed = 1;
|
||||
logmsg("launch: %s resume from background (pid=%d)", a->name, (int)pid);
|
||||
if (chain && chain[0]) logmsg("launch: resume ignores chain=\"%s\"", chain);
|
||||
if (pick_action[0]) logmsg("launch: resume ignores picker action=%s", pick_action);
|
||||
/* 回前台:清 §10.5 SDK present 自抑标记(无其他 bg App 且无叠加会话时) */
|
||||
if (meow_bg_count() == 0 && g_notif_ctrl_pid <= 0)
|
||||
unlink(NOTIF_ACTIVE);
|
||||
drain_input();
|
||||
meow_nav_begin(a->id, NULL); /* 转后台时导航栈已清;回前台从裸态重建 */
|
||||
}
|
||||
if (!resumed) {
|
||||
meow_nav_begin(a->id, chain); /* M3:冷启动/裸启动都以当前链深初始化导航栈(chain 空→depth 0) */
|
||||
|
||||
/* M2.12:启动前准备 MeowData/<pkg>/{data,cache}(幂等;卡不在/失败只 log 不崩)。
|
||||
@@ -1331,7 +1497,7 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
fcntl(out_pipe[1], F_SETFD, FD_CLOEXEC);
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
close(execpipe[0]); close(execpipe[1]);
|
||||
if (out_pipe[0] >= 0) close(out_pipe[0]);
|
||||
@@ -1494,7 +1660,7 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
}
|
||||
close(execpipe[1]);
|
||||
if (out_pipe[1] >= 0) close(out_pipe[1]); /* parent: 关闭写端 */
|
||||
int out_fd = out_pipe[0]; /* parent 保留读端,前台循环排空 */
|
||||
out_fd = out_pipe[0]; /* parent 保留读端,前台循环排空 */
|
||||
if (out_fd >= 0) {
|
||||
int fl = fcntl(out_fd, F_GETFL, 0);
|
||||
if (fl >= 0) fcntl(out_fd, F_SETFL, fl | O_NONBLOCK);
|
||||
@@ -1527,13 +1693,35 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
if (out_fd >= 0) close(out_fd);
|
||||
return -1;
|
||||
}
|
||||
} /* end if (!resumed):冷启动分支(prepare/fork/exec/确认) */
|
||||
|
||||
/* AMSD_DESIGN §4.1:启动上报(pkg/activity/chain/pid),amsd 建表项 + 开 stdout 捕获会话。 */
|
||||
ams_report_launch(a, chain, pid);
|
||||
/* AMSD_DESIGN §4.1:启动上报(pkg/activity/chain/pid),amsd 建表项 + 开 stdout 捕获会话。
|
||||
* M4:两路都报(恢复路径诚实记为新一次 LAUNCH;chain 被忽略故报 NULL)。 */
|
||||
ams_report_launch(a, resumed ? NULL : chain, pid);
|
||||
|
||||
/* GESTURE_BACK §2.4:应用启动即写 immersive 状态,watchdog 依此隐藏/保留虚拟返回键 */
|
||||
meow_write_app_state(a->immersive == '1' ? 1 : 0);
|
||||
|
||||
/* M4:冷启动成功且声明 background=true → 自动拉起其静态声明的 keepalive(mode=0)
|
||||
* 无头服务(reg_fd=-1=静态拉起无注册连接;同名在跑/退避排程中 → EXISTS 静默跳过)。
|
||||
* 服务 exec 路径约定 = App 二进制同目录下的 <svc> 名文件(见 meow_svc_exec_path)。 */
|
||||
if (!resumed && a->background == '1') {
|
||||
for (int i = 0; i < a->nsvc; i++) {
|
||||
char sexec[160];
|
||||
int rc;
|
||||
if (a->svcs[i].mode != 0) continue;
|
||||
meow_svc_exec_path(a->exec, a->svcs[i].name, sexec, sizeof sexec);
|
||||
rc = meow_svc_register(a->id, a->svcs[i].name, sexec,
|
||||
a->svcs[i].restart, a->svcs[i].max,
|
||||
a->svcs[i].window_ms,
|
||||
strncmp(sexec, MEOWAPPS, strlen(MEOWAPPS)) == 0,
|
||||
-1);
|
||||
if (rc == MEOW_ERR_EXISTS) continue; /* 在跑/排程中:静默跳过 */
|
||||
logmsg("launch: auto-svc %s/%s -> %s", a->id, a->svcs[i].name,
|
||||
rc > 0 ? "spawned" : "FAILED");
|
||||
}
|
||||
}
|
||||
|
||||
/* watchdog for virtual back key / gestures */
|
||||
char wd1[192], pidstr[16];
|
||||
pid_t wpid = 0;
|
||||
@@ -1580,6 +1768,7 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
unsigned long long ams_t0 = now_ms(); /* AMSD_DESIGN §4.1:运行时长起点 */
|
||||
int ams_status_valid = 0; /* 仅真实 waitpid 回收才置 1(规避 sigterm/重启路径 status=0 假干净退出) */
|
||||
int nav_clean_exit = 0; /* M3:MSG_NAV{EXIT} 主动退出 → 不按崩溃上报 */
|
||||
int to_background = 0; /* M4:nav-exit 转后台保活(进程保留,跳过退出尾) */
|
||||
int app_prev_vold = 0;
|
||||
int app_seq_hits = 0;
|
||||
unsigned long long app_seq_last = 0;
|
||||
@@ -1621,7 +1810,7 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
if (sigterm_flag) { logmsg("sigterm during app"); kill(pid, SIGTERM); break; }
|
||||
/* AMSD_DESIGN §5.1:排空 App stdout/stderr 管道(非阻塞),按行转发 amsd;App 写满不阻塞。 */
|
||||
g_ams_out_had = 0;
|
||||
if (out_fd >= 0) ams_drain_stdout(a, out_fd, pid);
|
||||
if (out_fd >= 0) ams_drain_stdout(a->id, out_fd, pid, g_ams_out_line, &g_ams_out_n);
|
||||
/* M3 导航栈:前台会话排空 IPC(App 经 MSG_NAV REPORT/EXIT 上报导航/请求退出)。
|
||||
* REPORT 更新 nav_stack(meow_ipc_process 内单写者);EXIT → 终止应用回主屏。 */
|
||||
meow_ipc_process();
|
||||
@@ -1635,6 +1824,18 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
break;
|
||||
}
|
||||
if (meow_nav_exit_pending()) {
|
||||
/* M4 后台播放保活:声明 background_audio 且该 App 媒体会话 PLAYING → 不终止,
|
||||
* 进程+音频流保留转后台(UI 冻结走 §10.5 SDK present 自抑,**非 SIGSTOP**——
|
||||
* SIGSTOP 会冻结同进程播放线程断供 PCM,§10.5 真机实证)。登记失败
|
||||
*(槽满/已存在)回落下方真退序列。卡拔例外不适用保活(bg 表 tick 照杀)。 */
|
||||
if (meow_bg_should_keep(a->background_audio == '1',
|
||||
meow_media_app_playing(a->id)) &&
|
||||
meow_bg_add(a->id, pid, out_fd, app_on_card) == 0) {
|
||||
logmsg("bg: app %s nav-exit -> 后台保活(播放中,pid=%d 保留,SDK present 自抑)",
|
||||
a->name, (int)pid);
|
||||
to_background = 1;
|
||||
break;
|
||||
}
|
||||
logmsg("nav: app %s MSG_NAV{EXIT} -> terminate, return to home", a->name);
|
||||
nav_clean_exit = 1;
|
||||
kill(pid, SIGTERM);
|
||||
@@ -1791,6 +1992,36 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
* AMSD_DESIGN §5.1:本轮排空到 stdout 数据 → 也睡短些,防止 App 写满管道被阻塞。 */
|
||||
usleep((meow_ui_statusbar_animating() || g_ams_out_had) ? 20000 : 100000);
|
||||
}
|
||||
/* M4:转后台保活出口 —— 跳过退出尾(不 ams EXIT/不 crash 上报/不清媒体会话/
|
||||
* 不关 out_fd:进程仍存活,out_fd 归 bg 表由 meow_bg_housekeep 继续 drain)。
|
||||
* 停播后再退出走上方正常退出尾 = 真退。 */
|
||||
if (to_background) {
|
||||
g_app_session_active = 0; /* 前台会话结束(ipc_dispatch 不再受理 NOTIF_OPEN) */
|
||||
meow_notif_open_clear();
|
||||
if (wpid > 0) {
|
||||
/* 看门狗随前台会话结束回收(后台无手势监听;有界等防挂死) */
|
||||
kill(wpid, SIGTERM);
|
||||
unsigned long long wstart = now_ms();
|
||||
while (now_ms() - wstart < 2000) {
|
||||
if (waitpid(wpid, NULL, WNOHANG) == wpid) break;
|
||||
usleep(50000);
|
||||
}
|
||||
}
|
||||
/* 写 §10.5 SDK 自抑标记:App 进程见 /tmp/meow_notif_active → present 画进
|
||||
* back_buf 不上屏 + 输入线程排空(=UI 冻结);音频经 audiod socket 天然续播。 */
|
||||
int nfd = open(NOTIF_ACTIVE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (nfd >= 0) { write(nfd, "1", 1); close(nfd); }
|
||||
meow_write_app_state(-1);
|
||||
unlink(MEOW_SBAR_MODE_FILE); /* 顶条模式文件由 App SDK 写;转后台清理复位菜单整条 */
|
||||
g_sb_mode_last = -1;
|
||||
meow_ui_statusbar_set_collapsed(0);
|
||||
meow_ui_statusbar_animate(0);
|
||||
meow_ui_set_fullscreen(0);
|
||||
meow_dirty_add(0, SB_BAR_Y, fb_xres, SB_BAR_H);
|
||||
settings_load();
|
||||
meow_nav_exit(); /* 前台会话导航栈清栈(App 本体栈内存活;回前台时裸态重建) */
|
||||
return 0;
|
||||
}
|
||||
/* §10.5:App 前台会话结束,清会话标记(ipc_dispatch 不再受理 MSG_NOTIF_OPEN) */
|
||||
g_app_session_active = 0;
|
||||
meow_notif_open_clear();
|
||||
@@ -1798,10 +2029,10 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
* 先转发 amsd,再报 EXIT / crash。仅当有读端(管道建立成功)才 drain。 */
|
||||
if (out_fd >= 0) {
|
||||
g_ams_out_had = 0;
|
||||
ams_drain_stdout(a, out_fd, pid); /* 排空全部剩余(App 死 → read 返回 0=EOF) */
|
||||
ams_drain_stdout(a->id, out_fd, pid, g_ams_out_line, &g_ams_out_n); /* 排空全部剩余(App 死 → read 返回 0=EOF) */
|
||||
if (g_ams_out_n > 0) { /* 未完结尾行:强制提交 */
|
||||
g_ams_out_line[g_ams_out_n] = '\0';
|
||||
ams_out_line(a, pid, g_ams_out_line);
|
||||
ams_out_line(a->id, pid, g_ams_out_line);
|
||||
g_ams_out_n = 0;
|
||||
}
|
||||
ams_out_flush();
|
||||
|
||||
Reference in new issue
Block a user