/* app.c — dev.player.ncm 应用装配层:main + 组装 + 事件分发。 * * 职责(对齐 hello_app/app.c):main 只做初始化/建页/跑事件循环/清理;把 meow_ui_run 的平铺输入 * 回调(key/tap/swipe/tick/touch)路由到当前活动页。页面切换统一走 meow_view_page_push/pop/replace。 * 播放线程(player_manager)+ 媒体会话 IPC 线程(ipc_media)在 main 起动、退出时停。 * * 铁律(APPS.md §3.4.1):物理键禁映射——on_key 不处理任何键语义;返回=左缘左滑/返回钮/request_exit。 * 左缘返回:播放页→歌单→主页→退出(登录页→退出)。 */ #include "ncm_app_internal.h" #include #include #include #include #include #include ncm_app_state_t g_st; ncm_play_run_t g_play = { .mu = PTHREAD_MUTEX_INITIALIZER, .state = NCM_PC_STOPPED, .cur_song = -1 }; static int g_reported_act = -1; /* 任务4 keep_awake:已上报 launcher 的顶层 activity(差分防重发) */ volatile int g_app_quit = 0; volatile int g_net_ready = 0; int g_host_mock = 0; static meow_view *g_login_view, *g_home_view, *g_pl_view, *g_plr_view, *g_set_view, *g_search_view, *g_queue_view; static void on_sigterm(int s) { (void)s; meow_ui_request_exit(); } /* SIGSEGV/SIGBUS/SIGILL 定位处理器(真机复现调试用):**必须 async-signal-safe**(只 write+_exit, * 手写十六进制格式化,不用 snprintf/sprintf——后者内部取锁,在信号上下文可能自锁死)。把 * si_addr + 中断点 pc + ra/sp/fp(MIPS O32:mcontext_t.pc / gregs[31 ra][29 sp][30 fp]) * 写 stderr(amsd 捕获)+ 固定文件,配合 nm/链接布局定位崩溃函数。 */ static void fatal_sig_report(int sig, siginfo_t *si, void *ctx) { ucontext_t *uc = (ucontext_t *)ctx; unsigned long long v = 0; unsigned char b[192]; int o = 0; const char *tag = (sig == SIGSEGV) ? "SIGSEGV" : (sig == SIGBUS ? "SIGBUS" : "SIGILL"); #define FATAL_PUTS(s) do { const char *_p = (s); while (*_p) b[o++] = (unsigned char)*_p++; } while (0) #define FATAL_HEX(x) do { unsigned long long _v = (unsigned long long)(x); \ int _sh; \ for (_sh = 28; _sh >= 0; _sh -= 4) b[o++] = \ (unsigned char)"0123456789abcdef"[(_v >> _sh) & 15]; } while (0) #define FATAL_STR(h, x) do { FATAL_PUTS(h); FATAL_HEX(x); } while (0) FATAL_PUTS("\nFATAL "); FATAL_PUTS(tag); FATAL_PUTS(" sig="); FATAL_HEX(sig); v = (unsigned long long)(uintptr_t)si->si_addr; FATAL_STR(" addr=", v); v = uc->uc_mcontext.pc; FATAL_STR(" pc=", v); v = uc->uc_mcontext.gregs[31]; FATAL_STR(" ra=", v); v = uc->uc_mcontext.gregs[29]; FATAL_STR(" sp=", v); v = uc->uc_mcontext.gregs[30]; FATAL_STR(" fp=", v); FATAL_PUTS("\n"); #undef FATAL_PUTS #undef FATAL_HEX #undef FATAL_STR (void)write(2, b, (size_t)o); int fd = open("/usr/data/mnt/sd_0/MeowOS/logs/dev.player.ncm/crash_report.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd >= 0) { (void)write(fd, b, (size_t)o); close(fd); } _exit(128 + sig); } static void fatal_sig_install(void) { struct sigaction sa; memset(&sa, 0, sizeof sa); sa.sa_sigaction = fatal_sig_report; sa.sa_flags = SA_SIGINFO; /* 不置 SA_NODEFER:防信号内重入 */ sigemptyset(&sa.sa_mask); sigaction(SIGSEGV, &sa, NULL); sigaction(SIGBUS, &sa, NULL); sigaction(SIGILL, &sa, NULL); } /* ============================================================ */ /* 事件分发:meow_ui_run 平铺回调 → 当前页 */ /* ============================================================ */ static void app_on_key(int keycode, int is_down) { (void)keycode; (void)is_down; /* 物理键系统语义;App 不映射(§3.4.1) */ } static void app_on_tap(int x, int y) { /* mini 播放栏:非播放页底部常驻,顶层先拦(点钮 toggle / 点栏体进播放页),不落页面 on_tap。 */ if (mini_bar_hit(x, y)) return; if (login_is_active()) { login_on_tap(x, y); return; } if (player_is_active()) { player_on_tap(x, y); return; } if (settings_is_active()) { settings_on_tap(x, y); return; } if (playlist_is_active()) { playlist_on_tap(x, y); return; } if (queue_is_active()) { queue_on_tap(x, y); return; } if (search_is_active()) { search_on_tap(x, y); return; } home_on_tap(x, y); } static void app_on_swipe(int dir, int dx, int dy) { (void)dx; if (dir == MEOW_SWIPE_LEFT) { /* 搜索历史模式:左滑删单条(§23);未消费才走上/下一层返回语义。 */ if (search_is_active() && search_swipe_left()) return; if (player_is_active()) { meow_view_page_pop(); return; } if (settings_is_active()) { meow_view_page_pop(); return; } if (playlist_is_active()) { meow_view_page_pop(); return; } if (queue_is_active()) { meow_view_page_pop(); return; } if (search_is_active()) { meow_view_page_pop(); return; } if (home_is_active()) { /* 任务2:home 根页左滑不再退出进程 → 请求 launcher 退到桌面/后台(进程+媒体会话保留)。 * launcher M4 据 app.json background_audio:1 + 媒体会话 PLAYING 判保活;不保活才真终止。 * 这里**不**置 g_app_quit / 不 meow_ui_request_exit——有歌在播则后台继续播(§M4)。 */ meow_log(MEOW_LOG_INFO, "swipe home -> background (nav_exit)"); ipc_media_nav_exit(); return; } /* login 根(未登录):退出 */ meow_log(MEOW_LOG_INFO, "exit:swipe"); meow_ui_request_exit(); return; } if (home_is_active()) { home_swipe(dir, dy); return; } if (playlist_is_active()) { playlist_swipe(dir, dy); return; } if (queue_is_active()) { queue_swipe(dir, dy); return; } if (search_is_active()) { search_swipe(dir, dy); return; } if (player_is_active()) { player_on_swipe(dir, dy); return; } } static void app_on_touch_down(int x, int y) { if (player_is_active()) player_touch_down(x, y); /* §22 中部横滑页切换跟踪 */ else if (home_is_active()) home_touch_down(x, y); else if (playlist_is_active()) playlist_touch_down(x, y); else if (queue_is_active()) queue_touch_down(x, y); else if (settings_is_active()) settings_touch_down(x, y); else if (search_is_active()) search_touch_down(x, y); } static void app_on_touch_move(int x, int y) { if (player_is_active()) player_touch_move(x, y); else if (home_is_active()) home_touch_move(x, y); else if (playlist_is_active()) playlist_touch_move(x, y); else if (queue_is_active()) queue_touch_move(x, y); else if (settings_is_active()) settings_touch_move(x, y); else if (search_is_active()) search_touch_move(x, y); } static void app_on_swipe_progress(int x_start, int dx) { meow_ui_swipe_hint(x_start + dx); } static void app_on_tick(void) { /* 任务4 keep_awake:顶层 activity 变化 → 上报 launcher(MSG_ACT_FOCUS,差分一次)。 * 页面切统一走 meow_view_page_push/pop/replace(更新 meow_ui_activity_active),此处差分上报: * 进播放页 active=ACT_PLAYER → ipc_act_report("player") → launcher fg_keep_awake 判常亮; * 退出播放页(弹回 home/playlist)→ 上报对应活动名(未声明 awake)→ 相机照常超时息屏。 * 只在**当前顶层**变化时发一次(无 IPC 风暴;播放中 active 不变不重发)。 */ { int act = meow_ui_activity_active(); if (act != g_reported_act) { g_reported_act = act; ipc_act_report(act); } } uint64_t now = meow_ui_time_ms(); mini_bar_tick(now); /* mini 播放栏:可见性/歌曲/暂停态/进度变化 → 失效栏带并重绘 */ /* §NEW cookie 续期判会话失效 → 清登录态回登录页(worker 在后台续期发现 301/-460 时置标志; * 在此 UI 线程执行 logout+切页——meow_view_page_replace 非线程安全,只能 UI 线程做) */ if (account_cookie_need_relogin()) { account_cookie_relogin_consume(); meow_log(MEOW_LOG_WARN, "account: cookie invalid -> re-login"); appstate_logout(); meow_view_page_replace(ACT_LOGIN); } autoplay_maybe(); /* 真机联调 hook:/tmp/ncm_autoplay 存在则自动播已缓存曲目(一次性) */ /* autoplay 已在后台线程起播缓存曲 → 在此(UI 线程)打开播放页,复现用户点曲播放路径 */ { int ai = autoplay_ui_consume(); if (ai >= 0) player_play_song(ai); } /* 任务3:通知中心媒体卡片点卡片体(launcher 发 NCM_CTL_FOCUS)→ 回到播放页。 * ipc_media 线程只置标志(跨线程 page 导航非线程安全),UI 线程消费后 push ACT_PLAYER; * 无在播曲则忽略(聚焦无意义)。 */ if (ipc_media_focus_consume()) { if (player_manager_state() != NCM_PC_STOPPED && meow_ui_activity_active() != ACT_PLAYER) { meow_log(MEOW_LOG_INFO, "ipc: focus -> player page"); meow_view_page_push(ACT_PLAYER); } } if (login_is_active()) { login_tick(now); return; } if (player_is_active()) { player_tick(now); return; } if (search_is_active()) { search_tick(now); return; } if (queue_is_active()) { queue_tick(now); return; } if (home_is_active()) { home_tick(now); } if (playlist_is_active()) { playlist_tick(now); } chrome_tick(); } /* ============================================================ */ /* main */ /* ============================================================ */ int main(void) { meow_log(MEOW_LOG_INFO, "start"); meow_ui_opts o = { APP_W, APP_H }; if (meow_ui_init(&o) != 0) { meow_log(MEOW_LOG_ERROR, "init failed"); return 1; } /* 主题侦听:登记真实回调(旧代码 pass NULL 无效),系统明暗切换 → ncm_on_theme 实时换肤。 * 初始同步一次 g_dark(listener 只在变化时被调,初次状态需显式对齐)。 */ g_dark = (meow_theme_get_mode() == MEOW_THEME_DARK); meow_theme_add_listener(ncm_on_theme); signal(SIGTERM, on_sigterm); fatal_sig_install(); /* SIGSEGV/SIGBUS/SIGILL 写崩溃地址定位(真机复现调试) */ /* 应用数据/核心库上下文(cookie/deviceId/cache/设置) */ appstate_init(); /* M2.82-联调修复:设备无系统 CA 库(真机 rc=-5 TLS 错误根因)——App 自带 Mozilla CA bundle, * 显式指给 core HTTP 层(保持 verify=1,不降级为不校验) */ ncm_http_set_verify_default(1, "/usr/data/mnt/sd_0/MeowAPPs/dev.player.ncm/cacert.pem"); /* 播放 + 媒体会话线程(UI 不卡;无 launcher socket 时 ipc 优雅跳过) */ player_manager_start(); ipc_media_start(); g_net_ready = 1; /* 建页 view */ g_login_view = login_view_create(); g_home_view = home_view_create(); g_pl_view = playlist_view_create(); g_plr_view = player_view_create(); g_set_view = settings_view_create(); g_search_view = search_view_create(); g_queue_view = queue_view_create(); /* §NEW 当前播放队列页 */ mini_bar_init(); /* mini 播放栏:登记跨 Activity 绘制物(进 root 页前,首帧即参与重画) */ /* 进入根页:已登录→home,否则→login */ meow_view_page_push(g_st.logged_in ? ACT_HOME : ACT_LOGIN); meow_ui_events ev = { app_on_key, app_on_tap, app_on_swipe, app_on_tick, app_on_swipe_progress, app_on_touch_down, app_on_touch_move, }; meow_ui_run(&ev); /* 清理:停线程 → 释放页 */ g_app_quit = 1; player_manager_stop_thread(); ipc_media_stop(); mini_bar_shutdown(); meow_view_free(g_set_view); meow_view_free(g_plr_view); meow_view_free(g_pl_view); meow_view_free(g_home_view); meow_view_free(g_login_view); meow_view_free(g_search_view); meow_view_free(g_queue_view); meow_ui_shutdown(); meow_log(MEOW_LOG_INFO, "exit"); return 0; }