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:
1 parent
955dbe1228
commit
85b4aea24f
9 files changed
+321
-39
No files matched your search
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
@@ -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;
|
||||
}
|
||||
@@ -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.
@@ -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;
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in new issue
Block a user