diff --git a/patch_v2/meow_launcherd/app_launcher.c b/patch_v2/meow_launcherd/app_launcher.c index 1dec427..a8db6c2 100644 --- a/patch_v2/meow_launcherd/app_launcher.c +++ b/patch_v2/meow_launcherd/app_launcher.c @@ -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.=1 / route..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//{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=(链路顶端) */ + char env_chain[MEOW_CHAIN_MAX + 16]; /* M3:MEOW_CHAIN=;;...; */ + 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); +} + diff --git a/patch_v2/meow_launcherd/main_menu.c b/patch_v2/meow_launcherd/main_menu.c index fd7fd36..139bbec 100644 --- a/patch_v2/meow_launcherd/main_menu.c +++ b/patch_v2/meow_launcherd/main_menu.c @@ -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/其余处理 */ } diff --git a/patch_v2/meow_launcherd/meow_ipc b/patch_v2/meow_launcherd/meow_ipc index 9ebc470..9c89700 100755 Binary files a/patch_v2/meow_launcherd/meow_ipc and b/patch_v2/meow_launcherd/meow_ipc differ diff --git a/patch_v2/meow_launcherd/meow_ipc.c b/patch_v2/meow_launcherd/meow_ipc.c index ad5b299..81ed824 100644 --- a/patch_v2/meow_launcherd/meow_ipc.c +++ b/patch_v2/meow_launcherd/meow_ipc.c @@ -221,9 +221,38 @@ static int cmd_back(void) return 1; } +/* M3:上报导航 → MSG_NAV,期望 MSG_ACK。 + * nav report 上报栈深/顶层(MEOW_NAV_REPORT) + * nav exit 请求退出(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 [body] [replaces_id] [ongoing] [media] [progress] */ + /* notify [body] [replaces_id] [ongoing] [media] [progress] [target_activity] */ if (i + 2 >= argc) { fprintf(stderr, "notify needs \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 \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 | nav exit */ + if (i + 1 >= argc) { fprintf(stderr, "nav needs 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 \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; } diff --git a/patch_v2/meow_launcherd/meow_ipc.h b/patch_v2/meow_launcherd/meow_ipc.h index ad9dba2..43da2b8 100644 --- a/patch_v2/meow_launcherd/meow_ipc.h +++ b/patch_v2/meow_launcherd/meow_ipc.h @@ -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 默认图标回退); diff --git a/patch_v2/meow_launcherd/meow_launcher_internal.h b/patch_v2/meow_launcherd/meow_launcher_internal.h index 784dddd..b01c700 100644 --- a/patch_v2/meow_launcherd/meow_launcher_internal.h +++ b/patch_v2/meow_launcherd/meow_launcher_internal.h @@ -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); diff --git a/patch_v2/meow_launcherd/meow_launcherd b/patch_v2/meow_launcherd/meow_launcherd index 00da89a..969f0f3 100755 Binary files a/patch_v2/meow_launcherd/meow_launcherd and b/patch_v2/meow_launcherd/meow_launcherd differ diff --git a/patch_v2/meow_launcherd/notif_center.c b/patch_v2/meow_launcherd/notif_center.c index 4ae1d35..59ef49b 100644 --- a/patch_v2/meow_launcherd/notif_center.c +++ b/patch_v2/meow_launcherd/notif_center.c @@ -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; diff --git a/patch_v2/meow_launcherd/util.c b/patch_v2/meow_launcherd/util.c index ae87f94..0100474 100644 --- a/patch_v2/meow_launcherd/util.c +++ b/patch_v2/meow_launcherd/util.c @@ -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: {