Files

263 lines
12 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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 <signal.h>
#include <ucontext.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
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;
}