feat(launcher): M3导航栈+M5通知点击跳转 (agent-202) — nav_stack(≤16深)+MSG_NAV REPORT/EXIT+launch_activity链路注入(MEOW_ACTIVITY/MEOW_CHAIN)+栈根退出回主屏; 通知点击按route_status(0拉起/1 NO ROUTE/2 SOURCE NOT FOUND/-1 NONE关面板不动作); picker协议位预留(0x0070/0x0071); 裸App兼容

launcher 4fe4103a→a42171a1; qemu实测 declare/query/nav report/exit/notify+querynotif 全过
发现: §10.5 App前台通知中心叠加会话(MSG_NOTIF_OPEN)实际未实现,文档超前,待补
This commit is contained in:
MeowOS committed 2026-08-31 06:27:10 +08:00
1 parent 955dbe1228
commit 85b4aea24f
9 files changed
+321 -39

No files matched your search

+112 -4
View File
@@ -124,6 +124,72 @@ int resolve_chain(const char *app_id, const char *activity, char *out, int *dept
}
/* ---- M3 导航栈(FRAMEWORK_DESIGN §3):每 app ≤16 深,v1 同一时刻单前台应用 ----
* launcher 侧镜像:launch_* 冷启动时以链路深度初始化;App 经 MSG_NAV{REPORT,depth,current}
* 上报栈深变化(launcher 更新);MSG_NAV{EXIT} → 置退出请求,前台 waitpid 循环终止 app 回主屏。
* 单一写者=主循环(meow_ipc_process 排空处 / launch_* 前台循环),无需锁。 */
static struct {
char app_id[MEOW_APP_ID_MAX];
int depth; /* 0=无/未上报;1..MEOW_NAV_MAX_DEPTH */
char top[MEOW_ACT_MAX]; /* 当前顶层 activity_id(REPORT 上报) */
int active; /* 1=前台应用有导航栈 */
} g_nav;
static volatile int g_nav_exit_req = 0;
void meow_nav_begin(const char *app_id, const char *chain)
{
if (!app_id) return;
memset(&g_nav, 0, sizeof g_nav);
strncpy(g_nav.app_id, app_id, MEOW_APP_ID_MAX - 1);
g_nav.app_id[MEOW_APP_ID_MAX - 1] = 0;
if (chain && chain[0]) {
g_nav.depth = chain_depth(chain);
if (g_nav.depth > MEOW_NAV_MAX_DEPTH) g_nav.depth = MEOW_NAV_MAX_DEPTH;
const char *top = strrchr(chain, ';');
top = top ? top + 1 : chain;
strncpy(g_nav.top, top, MEOW_ACT_MAX - 1);
g_nav.top[MEOW_ACT_MAX - 1] = 0;
}
g_nav.active = 1;
logmsg("nav: begin app=%s chain=\"%s\" depth=%d top=%s",
app_id, chain ? chain : "(bare)", g_nav.depth, g_nav.top);
}
void meow_nav_report(const char *app_id, uint32_t depth, const char *current)
{
if (!app_id) return;
if (depth == 0 || depth > MEOW_NAV_MAX_DEPTH) { meow_nav_exit(); return; }
g_nav.active = 1;
strncpy(g_nav.app_id, app_id, MEOW_APP_ID_MAX - 1);
g_nav.app_id[MEOW_APP_ID_MAX - 1] = 0;
g_nav.depth = (int)depth;
if (current) { strncpy(g_nav.top, current, MEOW_ACT_MAX - 1); g_nav.top[MEOW_ACT_MAX - 1] = 0; }
logmsg("nav: report app=%s depth=%d top=%s", app_id, g_nav.depth, g_nav.top);
}
void meow_nav_exit(void)
{
g_nav.active = 0;
g_nav.depth = 0;
g_nav.top[0] = 0;
logmsg("nav: exit (cleared)");
}
void meow_nav_request_exit(void)
{
g_nav_exit_req = 1;
meow_nav_exit();
logmsg("nav: EXIT requested");
}
int meow_nav_exit_pending(void)
{
int r = g_nav_exit_req;
g_nav_exit_req = 0;
return r;
}
/* ---- M2.17 keep_awake:前台当前顶层 activity 追踪 ----
* 前台应用经 IPC MSG_ACT_FOCUS/BACK 上报其当前顶层 activity;fg_keep_awake() 依注册表判断该
* activity 是否声明 keep_awake(app.json awake.<id>=1 / route.<id>.keep_awake=1;运行时同类)。
@@ -815,10 +881,13 @@ static void notify_app_crash(const char *pkg, int status)
close(fd);
}
int launch_app(struct app *a)
/* launch_app_chain:launch_app / launch_activity 的共享核心。
* chain 非空 → 向子进程注入 MEOW_ACTIVITY/MEOW_CHAIN 并初始化 nav_stack(M3);空/NULL → 裸启动。 */
static int launch_app_chain(struct app *a, const char *chain)
{
logmsg("launch: %s (%s)", a->name, a->exec);
logmsg("launch: %s (%s) chain=\"%s\"", a->name, a->exec, chain ? chain : "");
if (!a->ok) { logmsg("launch blocked: exec not ok"); return -1; }
meow_nav_begin(a->id, chain); /* M3:冷启动/裸启动都以当前链深初始化导航栈(chain 空→depth 0) */
/* M2.12:启动前准备 MeowData/<pkg>/{data,cache}(幂等;卡不在/失败只 log 不崩)。
* 应用经环境变量 MEOW_DATA_DIR/MEOW_CACHE_DIR 定位数据(exFAT 无 symlink)。 */
@@ -863,7 +932,9 @@ int launch_app(struct app *a)
char env_tier[24];
char env_fs[20];
char env_cls[MEOW_FCLS_COUNT][2][400];
char *envp[40]; /* PATH,HOME,log_dir,log_app,log_lvl,font 全局(3),多字体(1),tier(1),class(≤12),fullscreen(1),data,cache */
char env_act[MEOW_ACT_MAX + 16]; /* M3:MEOW_ACTIVITY=<target>(链路顶端) */
char env_chain[MEOW_CHAIN_MAX + 16]; /* M3:MEOW_CHAIN=<id0>;<id1>;...;<top> */
char *envp[48]; /* PATH,HOME,log_dir,log_app,log_lvl,font 全局(3),多字体(1),tier(1),class(≤12),fullscreen(1),data,cache,act,chain */
int ne = 0;
const char *cp = getenv("PATH");
snprintf(env_path, sizeof env_path, "PATH=%s", cp ? cp : "/usr/bin:/bin");
@@ -961,6 +1032,18 @@ int launch_app(struct app *a)
envp[ne++] = env_data;
envp[ne++] = env_cache;
}
/* M3:冷启动/通知跳转注入 activity 链(FRAMEWORK_DESIGN §3)。chain 非空才注入;
* MEOW_ACTIVITY=链路顶端(最后一段=target),MEOW_CHAIN=完整链路。裸启动不注入
* (App 走 SDK 默认首页)。 */
if (chain && chain[0]) {
const char *top = strrchr(chain, ';');
top = top ? top + 1 : chain;
snprintf(env_act, sizeof env_act, "MEOW_ACTIVITY=%s", top);
snprintf(env_chain, sizeof env_chain, "MEOW_CHAIN=%s", chain);
envp[ne++] = env_act;
envp[ne++] = env_chain;
logmsg("launch: M3 env %s / %s", env_act, env_chain);
}
envp[ne] = NULL;
execve(a->exec, argv, envp);
int e = errno;
@@ -1019,6 +1102,7 @@ int launch_app(struct app *a)
* never fires on this unit, §18.12). Volume is snapped on the first
* tap and restored when the sequence completes. */
int status = 0;
int nav_clean_exit = 0; /* M3:MSG_NAV{EXIT} 主动退出 → 不按崩溃上报 */
int app_prev_vold = 0;
int app_seq_hits = 0;
unsigned long long app_seq_last = 0;
@@ -1054,6 +1138,17 @@ int launch_app(struct app *a)
if (r == pid) break;
if (r < 0 && errno != EINTR) break;
if (sigterm_flag) { logmsg("sigterm during app"); kill(pid, SIGTERM); break; }
/* M3 导航栈:前台会话排空 IPC(App 经 MSG_NAV REPORT/EXIT 上报导航/请求退出)。
* REPORT 更新 nav_stack(meow_ipc_process 内单写者);EXIT → 终止应用回主屏。 */
meow_ipc_process();
if (meow_nav_exit_pending()) {
logmsg("nav: app %s MSG_NAV{EXIT} -> terminate, return to home", a->name);
nav_clean_exit = 1;
kill(pid, SIGTERM);
usleep(300000);
if (waitpid(pid, &status, WNOHANG) != pid) { kill(pid, SIGKILL); waitpid(pid, &status, 0); }
break;
}
/* M2.11 D:卡被拔(设备节点消失)且前台应用在卡上 → 主动 SIGTERM 干净退出,
* 避免应用等 SIGBUS 崩。回菜单走正常应用退出路径。 */
if (app_on_card && !sd_device_present()) {
@@ -1181,7 +1276,7 @@ int launch_app(struct app *a)
/* M2.x:App 异常退出(信号杀 / 非 0 退出码)→ best-effort 直报 meow_crash。
* 判据与 svcd reap_children 一致:failed = !(WIFEXITED && WEXITSTATUS==0)。干净 exit(0)
* 不上报;sigterm 关停/电源菜单重启路径 status 保持 0(未 waitpid)→ 视为干净退出不上报。 */
if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0))
if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0) && !nav_clean_exit)
notify_app_crash(a->id, status);
if (wpid > 0) {
/* reap the watchdog: SIGTERM then wait up to 2s (it exits when the
@@ -1205,7 +1300,20 @@ int launch_app(struct app *a)
/* M2.24:应用运行期音量由 watchdog 写(settings.conf 同源)。返回菜单前 re-load,
* 让菜单音量/状态栏与真实 DAC 一致(watchdog 可能已改)。 */
settings_load();
meow_nav_exit(); /* M3:前台应用会话结束,清导航栈 */
logmsg("app %s exited status=%d", a->name, WEXITSTATUS(status));
return 0;
}
/* M3:裸启动(无 activity 链)。保留旧签名,向下兼容不发 MSG_NAV 的 App。 */
int launch_app(struct app *a)
{
return launch_app_chain(a, NULL);
}
/* M3:以 activity 链路冷启动。注入 MEOW_ACTIVITY/MEOW_CHAIN + 设 nav_stack 栈深。 */
int launch_activity(struct app *a, const char *chain)
{
return launch_app_chain(a, chain);
}
+49 -13
View File
@@ -420,6 +420,7 @@ static void menu_cell_rect(int i, int layout, int apps_top,
static int menu_hit_app(int x, int y, struct app *apps, int napps, int state,
int layout, int apps_top)
{
(void)apps; /* 命中判定只依赖几何;apps 保留作接口(0-warning) */
if (state != 0 || napps <= 0) return -1;
for (int i = 0; i < napps; i++) {
int cx, cy, cw, ch;
@@ -1151,8 +1152,11 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
t_last_y - loy);
t_down = 0; t_down_valid = 0; /* 消费本次手势:返回后不再判 tap/滑动/重开 */
menu_arm_touch(TAP_ARM_DELAY_MS);
int want_launch = -1;
run_notif_center(apps, napps, &want_launch, 1, t_down_x, loy);
notif_click_t click;
memset(&click, 0, sizeof click);
click.app_idx = -1;
click.status = NOTIF_CLICK_STATUS_NONE;
run_notif_center(apps, napps, &click, 1, t_down_x, loy);
/* 重播按键防抖基线(同原 top-swipe 路径),防面板内电源/音量边沿被 run_menu 误判 */
memset(&kd_pwr, 0, sizeof kd_pwr);
memset(&kd_vold, 0, sizeof kd_vold);
@@ -1167,17 +1171,32 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
last_input_ms = now_ms();
menu_arm_touch(800);
dirty = 1;
if (want_launch >= 0 && want_launch < napps) {
logmsg("menu: notification launch app idx=%d", want_launch);
int ret = launch_app(&apps[want_launch]);
/* M5:通知点击按 route_status 产出 click(0 跳转 / 1 NO ROUTE / 2 SOURCE NOT FOUND) */
if (click.status == NOTIF_CLICK_STATUS_OK &&
click.app_idx >= 0 && click.app_idx < napps) {
logmsg("menu: notif click -> launch idx=%d chain=\"%s\"",
click.app_idx, click.chain);
int ret = click.chain[0]
? launch_activity(&apps[click.app_idx], click.chain)
: launch_app(&apps[click.app_idx]);
menu_after_app_exit(ret == 0);
last_input_ms = now_ms();
dirty = 1;
if (ret != 0) {
snprintf(toast, sizeof toast, "CANNOT START: %s",
apps[want_launch].name);
apps[click.app_idx].name);
toast_ms = TOAST_MS;
}
} else if (click.status == NOTIF_CLICK_STATUS_NOROUTE) {
logmsg("menu: notif click NO ROUTE");
snprintf(toast, sizeof toast, "NO ROUTE");
toast_ms = TOAST_MS;
dirty = 1;
} else if (click.status == NOTIF_CLICK_STATUS_NOSRC) {
logmsg("menu: notif click SOURCE NOT FOUND");
snprintf(toast, sizeof toast, "SOURCE NOT FOUND");
toast_ms = TOAST_MS;
dirty = 1;
}
continue; /* 本圈不再按 tap/其余处理(剩余事件因 t_down=0 成 no-op) */
}
@@ -1228,8 +1247,11 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
if (mnp_top_swipe_down(tx, ty, dx, dy, SB_BAR_H, MENU_SWIPE_THRESH)) {
logmsg("menu: top-edge swipe-down -> notification center (release edge, fallback)");
menu_arm_touch(TAP_ARM_DELAY_MS);
int want_launch = -1;
run_notif_center(apps, napps, &want_launch, 0, 0, 0); /* ⑨ live_open=0 → 触发式自动展开(防御) */
notif_click_t click;
memset(&click, 0, sizeof click);
click.app_idx = -1;
click.status = NOTIF_CLICK_STATUS_NONE;
run_notif_center(apps, napps, &click, 0, 0, 0); /* ⑨ live_open=0 → 触发式自动展开(防御) */
/* 重播按键防抖基线,防面板内电源/音量边沿被 run_menu 误判(M2.25 同款)。 */
memset(&kd_pwr, 0, sizeof kd_pwr);
memset(&kd_vold, 0, sizeof kd_vold);
@@ -1244,18 +1266,32 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
last_input_ms = now_ms();
menu_arm_touch(800);
dirty = 1;
/* 点击通知命中可启动 app → 走标准启动链路(与触摸行同路径)。 */
if (want_launch >= 0 && want_launch < napps) {
logmsg("menu: notification launch app idx=%d", want_launch);
int ret = launch_app(&apps[want_launch]);
/* M5:通知点击按 route_status 产出 click(0 跳转 / 1 NO ROUTE / 2 SOURCE NOT FOUND) */
if (click.status == NOTIF_CLICK_STATUS_OK &&
click.app_idx >= 0 && click.app_idx < napps) {
logmsg("menu: notif click -> launch idx=%d chain=\"%s\"",
click.app_idx, click.chain);
int ret = click.chain[0]
? launch_activity(&apps[click.app_idx], click.chain)
: launch_app(&apps[click.app_idx]);
menu_after_app_exit(ret == 0);
last_input_ms = now_ms();
dirty = 1;
if (ret != 0) {
snprintf(toast, sizeof toast, "CANNOT START: %s",
apps[want_launch].name);
apps[click.app_idx].name);
toast_ms = TOAST_MS;
}
} else if (click.status == NOTIF_CLICK_STATUS_NOROUTE) {
logmsg("menu: notif click NO ROUTE");
snprintf(toast, sizeof toast, "NO ROUTE");
toast_ms = TOAST_MS;
dirty = 1;
} else if (click.status == NOTIF_CLICK_STATUS_NOSRC) {
logmsg("menu: notif click SOURCE NOT FOUND");
snprintf(toast, sizeof toast, "SOURCE NOT FOUND");
toast_ms = TOAST_MS;
dirty = 1;
}
continue; /* 本圈不再按 tap/其余处理 */
}
Binary file not shown.
+44 -3
View File
@@ -221,9 +221,38 @@ static int cmd_back(void)
return 1;
}
/* M3:上报导航 → MSG_NAV,期望 MSG_ACK。
* nav report <app_id> <depth> <top> 上报栈深/顶层(MEOW_NAV_REPORT)
* nav exit <app_id> 请求退出(MEOW_NAV_EXIT) */
static int cmd_nav(const char *app_id, int exit_req, uint32_t depth, const char *top)
{
int fd = connect_path(g_sock);
if (fd < 0) return 1;
m_nav_t n;
memset(&n, 0, sizeof n);
strncpy(n.app_id, app_id, MEOW_APP_ID_MAX - 1);
n.nav_type = exit_req ? MEOW_NAV_EXIT : MEOW_NAV_REPORT;
n.depth = depth;
if (top) strncpy(n.current, top, MEOW_ACT_MAX - 1);
uint8_t rbuf[256];
int rt = xchg(fd, MSG_NAV, 10, &n, sizeof n, rbuf, sizeof rbuf);
close(fd);
if (rt == MSG_ACK) {
m_ack_t a;
memcpy(&a, rbuf, sizeof a);
printf("NAV ok: app=%s type=%u depth=%u top=\"%s\" code=%d\n",
app_id, n.nav_type, depth, n.current, a.code);
return a.code == MEOW_OK ? 0 : 1;
}
if (rt == MSG_ERR) { print_err((const m_err_t *)rbuf); return 1; }
fprintf(stderr, "NAV: unexpected resp type 0x%04x\n", rt);
return 1;
}
/* M2a:推送通知 → MSG_NOTIFY,期望 MSG_NOTIFY_ACK。 */
static int cmd_notify(const char *app_id, const char *category, const char *summary,
const char *body, uint32_t replaces_id, int ongoing, int media, int progress)
const char *body, uint32_t replaces_id, int ongoing, int media, int progress,
const char *target_activity)
{
int fd = connect_path(g_sock);
if (fd < 0) return 1;
@@ -233,6 +262,7 @@ static int cmd_notify(const char *app_id, const char *category, const char *summ
strncpy(n.category, category, sizeof n.category - 1);
strncpy(n.summary, summary, sizeof n.summary - 1);
if (body) strncpy(n.body, body, sizeof n.body - 1);
if (target_activity) strncpy(n.target_activity, target_activity, MEOW_ACT_MAX - 1);
n.replaces_id = replaces_id;
n.ongoing = ongoing ? 1 : 0;
n.media = media ? 1 : 0;
@@ -356,14 +386,16 @@ int main(int argc, char **argv)
return cmd_back();
}
if (!strcmp(cmd, "notify")) {
/* notify <app_id> <category> <summary> [body] [replaces_id] [ongoing] [media] [progress] */
/* notify <app_id> <category> <summary> [body] [replaces_id] [ongoing] [media] [progress] [target_activity] */
if (i + 2 >= argc) { fprintf(stderr, "notify needs <app_id> <category> <summary>\n"); return 1; }
const char *body = (i + 4 < argc) ? argv[i + 4] : "";
uint32_t rid = (i + 5 < argc) ? (uint32_t)strtoul(argv[i + 5], NULL, 0) : 0;
int ongoing = (i + 6 < argc) ? atoi(argv[i + 6]) : 0;
int media = (i + 7 < argc) ? atoi(argv[i + 7]) : 0;
int progress = (i + 8 < argc) ? atoi(argv[i + 8]) : -1;
return cmd_notify(argv[i + 1], argv[i + 2], argv[i + 3], body, rid, ongoing, media, progress);
const char *tgt = (i + 9 < argc) ? argv[i + 9] : "";
return cmd_notify(argv[i + 1], argv[i + 2], argv[i + 3], body, rid, ongoing, media, progress,
tgt);
}
if (!strcmp(cmd, "dismiss")) {
if (i + 1 >= argc) { fprintf(stderr, "dismiss needs <id>\n"); return 1; }
@@ -376,6 +408,15 @@ int main(int argc, char **argv)
if (!strcmp(cmd, "dismissall")) {
return cmd_dismissall();
}
if (!strcmp(cmd, "nav")) {
/* nav report <app_id> <depth> <top> | nav exit <app_id> */
if (i + 1 >= argc) { fprintf(stderr, "nav needs <app_id> and report|exit\n"); return 1; }
int exit_req = (!strcmp(argv[i + 1], "exit") || !strcmp(argv[i + 1], "EXIT"));
if (i + 2 >= argc) { fprintf(stderr, "nav needs <app_id>\n"); return 1; }
uint32_t depth = (i + 3 < argc) ? (uint32_t)strtoul(argv[i + 3], NULL, 0) : 0;
const char *top = (i + 4 < argc) ? argv[i + 4] : "";
return cmd_nav(argv[i + 2], exit_req, depth, top);
}
fprintf(stderr, "unknown cmd %s; try --help\n", cmd);
return 1;
}
+24 -1
View File
@@ -13,7 +13,9 @@
* uint32_t len; = payload 字节数
* };
*
* 本阶段(D · M0+M1)通讯子集:MSG_HELLO/ACK/ERR + MSG_ACT_DECLARE/QUERY/RESULT。
* 本阶段(D · M0+M1+M2+M3 前置)通讯子集:MSG_HELLO/ACK/ERR + MSG_ACT_DECLARE/QUERY/RESULT +
* MSG_ACT_FOCUS/BACK + MSG_NOTIFY/PROGRESS/QUERY/DISMISS* + MSG_NAV(导航栈上报/退出)。
* picker(0x0070/0x0071)仅预留协议位,本批不实现。
*/
#ifndef MEOW_IPC_H
#define MEOW_IPC_H
@@ -59,6 +61,12 @@ enum {
MSG_PERF_RESULT = 0x0051, /* ↓ 聚合 JSON(perf/system 子集) */
MSG_POWER_ACTION = 0x0060, /* ↑ 电源/系统动作信号(专项服务 meow_svc_power) */
MSG_POWER_STATUS = 0x0061, /* ↓ 动作受理/结果 + 状态文本 */
/* ---- 【预留 · 本批不实现】picker:带返回值的跨 App 调用(M3 延伸,只留协议位)----
* 语义:App A 经 MSG_PICKER_CALL 请求 App B 弹一个 selector 并返回所选值(如文件/音轨
* 选择器)。属「跨 App 带返回值」调用,与单向业务通知(MSG_NOTIFY)不同;负载/应答结构
* 待 M3 延伸阶段定义(m_picker_call_t / m_picker_result_t),此处仅固定消息号与方向。 */
MSG_PICKER_CALL = 0x0070, /* ↑ 请求跨 App picker 调用(发起方 A → 持有方 B) */
MSG_PICKER_RESULT = 0x0071, /* ↓ picker 返回值(候选结果回 A) */
MSG_SYS = 0x00F0 /* ↓ 系统事件(守护关闭/服务死亡/播放器状态) */
};
@@ -141,6 +149,21 @@ typedef struct m_act_declare {
char entries[MEOW_IPC_CARGO - MEOW_APP_ID_MAX - 4];
} m_act_declare_t;
/* MSG_NAV nav_type 取值(FRAMEWORK_DESIGN §3 导航栈) */
enum { MEOW_NAV_REPORT = 0, /* 应用内逐屏回退/前进:上报当前栈深 + 顶层 activity */
MEOW_NAV_EXIT = 1 }; /* 应用在根屏按返回 → 请求启动器终止应用、回主屏 */
/* MSG_NAV ↑:上报导航/请求退出(M3 导航栈;launcher 主循环单写者消费)。
* - REPORT:应用弹/推自己的屏后上报;depth=当前逻辑深度(1..16),current=当前顶层 activity_id;
* - EXIT :应用在根屏再按返回;depth 置 0,current 可空。
* launcher 据此维护每应用 nav_stack(≤16 深),并在 EXIT 时终止前台应用会话。 */
typedef struct m_nav {
char app_id[MEOW_APP_ID_MAX];
uint32_t nav_type; /* MEOW_NAV_REPORT / MEOW_NAV_EXIT */
uint32_t depth; /* REPORT: 1..16;EXIT: 0 */
char current[MEOW_ACT_MAX]; /* REPORT: 当前顶层 activity_id;EXIT: 可空 */
} m_nav_t;
/* MSG_NOTIFY ↑:推送/更新通知(freedesktop 对齐 + MeowOS 扩展;定长 LE)。
* - summary/body/app_icon/expire_timeout 直通 freedesktop 字段;app_icon 即 SDK `icon`
* (icon name 或 path;映射为空串时 launcher 用该 app 默认图标回退);
@@ -471,6 +471,7 @@ enum { NCP_CHIP_WIFI=0, NCP_CHIP_BT=1, NCP_CHIP_THEME=2, NCP_CHIP_BRIGHT=3,
typedef struct {
uint32_t id;
char app_id[MEOW_NOTIF_APP_ID_MAX];
char category[MEOW_NOTIF_CAT_MAX]; /* 通知类型(媒体="media")——点击 route_status 判定用 */
char summary[MEOW_NOTIF_SUMMARY_MAX];
char body[MEOW_NOTIF_BODY_MAX];
char icon[MEOW_NOTIF_ICON_MAX];
@@ -481,6 +482,22 @@ typedef struct {
meow_img *img; /* 缓存图标(meow_img_load_file 结果;NULL=回退首字符圆点) */
} ncp_row_t;
/* M5 通知中心点击产出:run_notif_center 经 *click 回报,run_menu 消费。
* app_idx: -1=无启动;>=0 = apps[] 下标(本批 run_menu 传入同源)。
* status : route_status(FRAMEWORK_DESIGN §4):-1=NONE(面板已关,未点任何通知,run_menu 不动);
* 0=OK 跳转;1=NO ROUTE;2=SOURCE NOT FOUND。
* chain : status==0 时解析出的 activity 链路(';' 分隔);空=裸启动/整 app 级(媒体→主屏)。
* status==1/2/NONE 时置空。 */
#define NOTIF_CLICK_STATUS_NONE (-1)
#define NOTIF_CLICK_STATUS_OK 0
#define NOTIF_CLICK_STATUS_NOROUTE 1
#define NOTIF_CLICK_STATUS_NOSRC 2
typedef struct notif_click {
int app_idx;
int status;
char chain[MEOW_CHAIN_MAX];
} notif_click_t;
/* ---- ⑧ 一键清除(批量):先转储留档 → 再清空显示(红线:保留 ongoing/media/progress)---- */
typedef struct { uint32_t ids[NCP_MAX_ROWS]; int n; } ncp_clear_ctx_t;
@@ -598,6 +615,9 @@ int kp_open(struct keypoll *kp, const char *dev);
int kp_poll(struct keypoll *kp, int (*cb)(int code, int down, void *ctx),
void *ctx);
int launch_app(struct app *a);
/* M3:以指定 activity 链路冷启动某 app(注入 MEOW_ACTIVITY/MEOW_CHAIN + 设 nav_stack 栈深)。
* chain=NULL 或空 = 裸启动(进 App 主屏/首页,不注入 activity env)。 */
int launch_activity(struct app *a, const char *chain);
void load_config(void);
void log_cleanup_old(void);
int log_open_card(void);
@@ -607,6 +627,14 @@ void meow_acct_declare(const char *app_id, const char *entries, int count);
struct meow_acct *meow_acct_find(const char *app_id);
void meow_acct_hello(const char *app_id, uint32_t version, uint32_t flags);
void meow_dac_vol_flush(void);
/* M3 导航栈(每 app ≤16 深;v1 单前台应用)。launcher 侧镜像,主循环单写者。 */
#define MEOW_NAV_MAX_DEPTH 16
void meow_nav_begin(const char *app_id, const char *chain); /* chain=NULL→depth 0(裸启动) */
void meow_nav_report(const char *app_id, uint32_t depth, const char *current);
void meow_nav_exit(void); /* 清栈(应用退出/EXIT) */
void meow_nav_request_exit(void); /* MSG_NAV{EXIT}:置位让前台 waitpid 循环终止 app */
int meow_nav_exit_pending(void); /* 读取并清除退出请求(前台 launch_* 循环轮询) */
void meow_dac_vol_write(int pct);
void meow_dac_vol_write_throttled(void);
void meow_dirty_add(int x, int y, int w, int h);
@@ -656,7 +684,7 @@ int read_file_int(const char *path);
int resolve_chain(const char *app_id, const char *activity, char *out, int *depth);
void restore_backlight(void);
uint16_t rgb565(uint32_t c);
void run_notif_center(struct app *apps, int napps, int *want_launch,
void run_notif_center(struct app *apps, int napps, notif_click_t *click,
int live_open, int lox, int loy);
void run_settings_page(void);
void s_about_open(void);
Binary file not shown.
+42 -17
View File
@@ -69,6 +69,7 @@ static int ncp_collect_cb(const meow_notif_t *n, void *ctx)
memset(r, 0, sizeof *r);
r->id = n->id;
ncp_strncpy(r->app_id, n->app_id, sizeof r->app_id);
ncp_strncpy(r->category, n->category, sizeof r->category);
ncp_strncpy(r->summary, n->summary, sizeof r->summary);
ncp_strncpy(r->body, n->body, sizeof r->body);
ncp_strncpy(r->icon, n->app_icon, sizeof r->icon);
@@ -713,32 +714,56 @@ static int ncp_slider_apply(int which, int tx, int save_now)
}
/* 通知点击:dismiss(标记阅读)+ 若 app 有 entry 记录则请求启动。返回 1=应关闭面板。 */
static int ncp_row_tap(int tx, int ty, int scroll, int *want_launch)
/* 通知点击:dismiss(标记阅读)+ 按 route_status(FRAMEWORK_DESIGN §4)产出 notif_click_t *click。
* status 0=OK 跳转(chain 解析出链路;媒体无 target 也 status=0,chain 置空=裸启动主屏);
* status 1=NO ROUTE(源 app 在,但无 target_activity / 路由未注册);
* status 2=SOURCE NOT FOUND(源 app 不在 apps[] 或 exec 不可用)。
* 返回 1=应关闭面板(点击即关;status 1/2 由 run_menu 弹 toast,不崩)。 */
static int ncp_row_tap(int tx, int ty, int scroll, notif_click_t *click)
{
(void)tx; /* 行命中仅依赖 ty/scroll;tx 保留作接口一致(0-warning) */
int eff = ty - NCP_LIST_TOP + scroll;
int row = mnp_row_at(eff, 0, g_nrows * NCP_ROW_H, NCP_ROW_H);
if (row < 0 || row >= g_nrows) return 0;
const ncp_row_t *r = &g_rows[row];
meow_notif_dismiss(r->id); /* 点击 = 阅读,撤下 */
/* 跳转:app entry 记录(apps[] 内且 ok)→ 记 want_launch(run_menu 走标准启动)。
* target_activity 为后续 M3 导航预留下的跳转目标(activity 级路由后置)。 */
if (want_launch) *want_launch = -1;
if (click) {
click->app_idx = -1;
click->status = NOTIF_CLICK_STATUS_NOROUTE;
click->chain[0] = 0;
}
/* 源 app 定位(apps[] 内且 exec ok):找不到 → SOURCE NOT FOUND(不崩,toast) */
int src = -1;
if (g_ncp_apps)
for (int i = 0; i < g_ncp_napps; i++)
if (!strcmp(g_ncp_apps[i].id, r->app_id) && g_ncp_apps[i].ok) {
if (want_launch) *want_launch = i;
break;
}
return 1; /* 点击即关(启动由 run_menu 执行;无记录则仅关闭) */
if (!strcmp(g_ncp_apps[i].id, r->app_id) && g_ncp_apps[i].ok) { src = i; break; }
if (src < 0) {
if (click) click->status = NOTIF_CLICK_STATUS_NOSRC;
return 1;
}
/* 跳转判定:M5 接 M3 —— target_activity 解析出链路则跳;媒体通知(无 target)整 app 拉起主屏;
* 其余无路由 → NO ROUTE(不崩,toast)。 */
int depth = 0;
char chain[MEOW_CHAIN_MAX] = "";
if (r->target[0] && resolve_chain(r->app_id, r->target, chain, &depth) == MEOW_OK) {
if (click) { click->app_idx = src; click->status = NOTIF_CLICK_STATUS_OK;
strncpy(click->chain, chain, MEOW_CHAIN_MAX - 1);
click->chain[MEOW_CHAIN_MAX - 1] = 0; }
} else if (!strcmp(r->category, MEOW_NOTIF_MEDIA_CAT)) {
if (click) { click->app_idx = src; click->status = NOTIF_CLICK_STATUS_OK;
click->chain[0] = 0; } /* 媒体:整 app 前台(无 activity 链则进主屏) */
} else {
if (click) click->status = NOTIF_CLICK_STATUS_NOROUTE;
}
return 1; /* 点击即关(status 0 由 run_menu 走 launch_activity/launch_app;1/2 弹 toast) */
}
/* ---- 通知中心面板主循环(阻塞;参照 run_settings_page 交互惯用法)---- */
void run_notif_center(struct app *apps, int napps, int *want_launch,
void run_notif_center(struct app *apps, int napps, notif_click_t *click,
int live_open, int lox, int loy)
{
if (want_launch) *want_launch = -1;
if (click) { click->app_idx = -1; click->status = NOTIF_CLICK_STATUS_NONE; click->chain[0] = 0; }
int list_scroll = 0;
unsigned char evbuf16[16];
g_ncp_apps = apps; g_ncp_napps = napps;
@@ -759,7 +784,7 @@ void run_notif_center(struct app *apps, int napps, int *want_launch,
if (tfd >= 0) { unsigned char ev[16]; while (read(tfd, ev, sizeof ev) == 16) {} }
unsigned long long touch_cooldown_until = now_ms() + 300;
unsigned long long last_tick = now_ms();
unsigned long long last_act = now_ms();
unsigned long long last_act = now_ms(); (void)last_act; /* 预留 idle 计时;当前未读(0-warning) */
unsigned long long last_listchk = now_ms();
int td = 0, td_ok = 0, tdx = -1, tdy = -1, tlx = -1, tly = -1;
@@ -928,10 +953,10 @@ void run_notif_center(struct app *apps, int napps, int *want_launch,
if (meow_auto_slept && !meow_screen_on) pwr_woke = 1;
}
if (pwr && pwr_hold_at) {
if (now - pwr_hold_at >= power_forceoff_ms && !pwr_menu_state) {
if (now - pwr_hold_at >= (unsigned long long)power_forceoff_ms && !pwr_menu_state) {
if (sim_no_exec) logmsg("notif-center pm: [sim-no-exec] hold %dms -> would forceoff", power_forceoff_ms);
else { logmsg("notif-center pm: hold %dms -> force poweroff", power_forceoff_ms); meow_power_action(POWER_SHUTDOWN); }
} else if (now - pwr_hold_at >= power_longpress_menu_ms && !pwr_menu_state) {
} else if (now - pwr_hold_at >= (unsigned long long)power_longpress_menu_ms && !pwr_menu_state) {
pwr_menu_state = 1;
logmsg("notif-center pm: hold %dms -> power menu", power_longpress_menu_ms);
show_power_menu();
@@ -951,7 +976,7 @@ void run_notif_center(struct app *apps, int napps, int *want_launch,
if (!pwr && m_prev_pwr) {
if (pwr_suppress_click)
logmsg("notif-center pm: release after menu -> no toggle");
else if (pwr_hold_at && (now - pwr_hold_at < power_longpress_menu_ms) && !pwr_menu_state && !pwr_woke) {
else if (pwr_hold_at && (now - pwr_hold_at < (unsigned long long)power_longpress_menu_ms) && !pwr_menu_state && !pwr_woke) {
logmsg("notif-center pm: single-click -> toggle panel");
meow_toggle_panel(); meow_auto_slept = 0; last_act = now_ms(); need_paint = 1;
} else if (pwr_woke) {
@@ -1197,7 +1222,7 @@ void run_notif_center(struct app *apps, int napps, int *want_launch,
hit = 1; need_paint = 1;
}
if (!hit && ty >= NCP_LIST_TOP && ty < NCP_LIST_BOT) {
if (ncp_row_tap(tx, ty, list_scroll, want_launch)) {
if (ncp_row_tap(tx, ty, list_scroll, click)) {
hit = 1;
if (anim_ok) anim_target = 0; /* 点击通知:dismiss + 记录启动 → 滑出收回 */
else closed = 1;
+21
View File
@@ -1133,6 +1133,27 @@ static void ipc_dispatch(struct meow_ipc_msg *m)
logmsg("ipc: ACT_BACK -> ACK");
break;
}
/* M3 导航栈(FRAMEWORK_DESIGN §3):前台应用上报导航(REPORT)或请求退出(EXIT)。
* REPORT → 更新 nav_stack 深度/顶层;EXIT → 置退出请求,前台 waitpid 循环终止应用回主屏。
* 均在主循环单写者上下文执行(meow_ipc_process 排空处;launch_* 前台循环也调 meow_ipc_process)。 */
case MSG_NAV: {
m_nav_t n;
memset(&n, 0, sizeof n);
memcpy(&n, m->payload, m->plen < (uint16_t)sizeof n ? m->plen : (uint16_t)sizeof n);
n.app_id[MEOW_APP_ID_MAX - 1] = 0;
n.current[MEOW_ACT_MAX - 1] = 0;
if (n.nav_type == MEOW_NAV_REPORT)
meow_nav_report(n.app_id, n.depth, n.current);
else
meow_nav_request_exit(); /* MEOW_NAV_EXIT(含未知值防御性按退出处理) */
m_ack_t a;
a.code = MEOW_OK;
a.status = (uint32_t)n.depth;
ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a);
logmsg("ipc: NAV app=%s type=%u depth=%u top=\"%s\" -> ACK",
n.app_id, n.nav_type, n.depth, n.current);
break;
}
/* M2a:通知存储(NOTIFICATION_SYSTEM.md §4/§7/§8)。IPC 线程只入队,此处在主循环
* 单写者落存储 + 回 ACK(与 HELLO/QUERY 同一线程模型,绝不在 socket IO 阻塞)。 */
case MSG_NOTIFY: {