Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/ui/login.c
T
MeowOS bdd9e898f4 feat(ncm §20): 音质体系全量——八档UI/VIP检测缓存/逐级降级下首恢复/fee门禁/默认极高HQ
- core: 9档枚举(顺序=降级链, DOLBY映射保留不进UI/链) + level_key(sky/sky_c51)
  + level_gate(CVIP/SVIP门槛) + song_url surround档附加immerseType=c51
  + song_url_auto全档表降级(out_level回填/响应level静默降档采信/try_fn可注入)
  + vip_info(weapi v2优先v1兜底) + parse_vip_info离线可测 + NCM_ERR_VIP(-15)
- app/logic/account.c(新): account.json会员态缓存(uid/nick/vip/双expire/checked_at)
  刷新=登录后/超1天/到期后各一次不轮询; account_vip本地到期降档视图
- player_manager: 用户档按会员钳顶后逐级降级(不硬拒,缓存键仍用户档),
  fee∈{1,4}且非会员→NCM_ERR_VIP零请求(缓存命中优先), eff_level回填
- UI: 设置页八档(规格+门槛+未解锁标注,允许选定), 播放页时间行中央
  生效档/已降级至xx小字, VIP专享文案; 默认档Hi-Res→极高HQ(存量设置保留)
- 验证(0真实请求): degrade_sim 90例矩阵全PASS; player_flow_sim P1-P8全PASS;
  test_ncm_core vip解析9例宿主+qemu全PASS; 窗口/分页/缓存/封面/audiod sim全PASS;
  mipsel -Wall -Wextra 0 warning; 6页离屏rc=0。未部署真机(留主会话)
2026-09-01 15:55:42 +08:00

254 lines
9.5 KiB
C
Raw 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.
/* ui/login.c — 扫码登录页(ACT_LOGIN,根页)。
*
* 流程:进入即起后台 worker(login_worker):
* unikey(eapi) → 本地二维码矩阵(qrcodegen) → 轮询(801/802/803) → 803 捕获 set-cookie 加密落盘
* → login_status 拿 uid/nickname → 置 g_login_done。
* UI 线程只在 tick 读 g_st.qr 状态与 login_state 重绘并检查 g_login_done → 切入 home。
* 800(过期)自动刷新二维码。UI 不卡:网络全在 worker 线程。
*/
#include "../ncm_app_internal.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
static pthread_t g_thread;
static volatile int g_polling; /* worker 运行中 */
static volatile int g_done; /* 登录成功(UI tick 切入 home) */
static volatile int g_err; /* 最后一次错误(网络不可达等) */
static int g_active;
static int g_custom_id; /* 绘制物 */
static ncm_rect_t r_scr; /* 整屏(沉浸式,hero 从 y=0 起) */
/* ---- 绘制 ---- */
static void login_draw(void *ctx)
{
(void)ctx;
if (meow_ui_activity_active() != ACT_LOGIN) return;
const meow_theme *th = meow_theme_get();
const ncm_pal_t *pa = ncm_palette(g_dark);
int W = meow_ui_width(), H = meow_ui_height();
/* 品牌红 hero 带(沉浸,从 y=0 到 264;day=red / night=hero 深红压场)。 */
meow_ui_fill_rect(0, 0, W, 264, pa->hero);
/* 标题「网易云音乐」+ 红点 logo(用字体大小近似排版,host 字库用 ASCII 替文本)。 */
const char *title = "网易云音乐";
int tw = meow_ui_text_width(title, 34);
meow_ui_text_center(W/2, 112, title, 34, pa->on_red);
/* 24px 红点 logo(标题左前) */
ncm_fill_round(W/2 - tw/2 - 36, 112 + 4, 24, 24, 12, pa->red_lit);
meow_ui_text_center(W/2, 160, "扫码登录 · 音樂的力量", 22, pa->on_red_dim);
/* 弱分隔(hero 与内容衔接) */
meow_ui_fill_rect(0, 264, W, 1, th->outline);
/* QR 卡(128,228,224,224):day 纯白 / night surfaceContainer;圆角 16 + 内描边。 */
int qc = 224, qx = (W - qc) / 2, qy = 228;
ncm_fill_round(qx, qy, qc, qc, 16, th->surfaceContainer);
meow_ui_draw_rect_round(qx, qy, qc, qc, 16, th->outlineVariant);
int sz = g_st.qr_size;
if (sz > 0) {
/* 模块尺寸随内容缩放(二维码版本随内容长度变化,尺寸不固定——
* 按容器内边距算模块边长,绝不溢出卡片) */
int m = (qc - 32) / sz;
if (m < 1) m = 1;
int qs = m * sz;
int ox = qx + (qc - qs) / 2, oy = qy + (qc - qs) / 2;
/* 夜间:深灰底 + 浅灰码;白天:白底黑码 */
uint32_t qr_bg = g_dark ? 0x2A2B30 : 0xFFFFFF;
uint32_t qr_fg = g_dark ? 0xC9CAD1 : 0x000000;
meow_ui_fill_rect(ox, oy, qs, qs, qr_bg);
for (int y = 0; y < sz; y++)
for (int x = 0; x < sz; x++)
if (g_st.qr_mods[y*sz + x])
meow_ui_fill_rect(ox + x*m, oy + y*m, m, m, qr_fg);
} else {
meow_ui_text_center(W/2, qy + qc/2 - 10,
g_err ? "网络异常,点按重试" : "正在获取二维码...", 20, th->dim);
}
/* 状态文字:成功/等待用红(day red / night red_lit),失败红字提示。 */
const char *st = login_status_str(g_st.login_state);
if (g_st.login_state == NCM_LOGIN_OK && g_done) st = "登录成功";
uint32_t scol = (g_st.login_state == NCM_LOGIN_OK) ? pa->red : th->dim;
meow_ui_text_center(W/2, 476, st, 20, scol);
/* 底部提示 */
meow_ui_text_center(W/2, H - 56, "由 MeowOS 驱动 · 仅本地解码", 18, th->dim);
}
/* 重绘 login 内容(QR+状态变化时)。 */
static void login_repaint(void)
{
meow_ui_invalidate(r_scr.x, r_scr.y, r_scr.w, r_scr.h);
}
const char *login_status_str(int state)
{
switch (state) {
case NCM_LOGIN_PENDING: return "等待扫码...";
case NCM_LOGIN_SCANNED: return "已扫码,请在手机上确认";
case NCM_LOGIN_OK: return "登录成功";
case NCM_LOGIN_EXPIRED: return "二维码已过期,正在刷新";
default: return "出错了,点按重试";
}
}
static int get_unikey_and_qr(char *unikey, size_t usz)
{
int rc = ncm_login_unikey(g_st.ctx.device_id, unikey, usz);
if (rc != NCM_OK) { meow_log(MEOW_LOG_ERROR, "login: unikey fetch failed rc=%d", rc); return rc; }
meow_log(MEOW_LOG_INFO, "login: unikey ok, qr ready");
ncm_strlcpy(g_st.unikey, unikey, sizeof g_st.unikey);
ncm_login_qr_url(unikey, g_st.qr_url, sizeof g_st.qr_url);
int size = 0;
if (ncm_login_qr_matrix(g_st.qr_url, g_st.qr_mods, sizeof g_st.qr_mods / 177, &size) != NCM_OK)
g_st.qr_size = 0;
else
g_st.qr_size = size;
g_st.login_state = NCM_LOGIN_PENDING;
g_err = 0;
return NCM_OK;
}
static void *login_worker(void *arg)
{
(void)arg;
char unikey[64];
/* 自动重试:unikey 失败(网络未就绪/抖动)时 5s 退避重试,不再要求用户点重试 */
int retry = 0;
while (get_unikey_and_qr(unikey, sizeof unikey) != NCM_OK) {
g_err = 1;
if (g_app_quit) return NULL;
meow_log(MEOW_LOG_WARN, "login: unikey retry in 5s (attempt %d)", ++retry);
usleep(5 * 1000 * 1000);
}
meow_log(MEOW_LOG_INFO, "login: poll worker start");
char cookie[NCM_COOKIE_MAX];
ncm_strlcpy(cookie, g_st.ctx.cookie, sizeof cookie);
while (!g_app_quit) {
ncm_login_poll_t r;
char out_cookie[NCM_COOKIE_MAX];
int rc = ncm_login_poll(g_st.ctx.device_id, unikey, cookie, &r,
out_cookie, sizeof out_cookie);
if (rc != NCM_OK) { meow_log(MEOW_LOG_WARN, "login: poll failed rc=%d", rc); g_err = 1; usleep(2 * 1000 * 1000); continue; }
if (r.state != g_st.login_state)
meow_log(MEOW_LOG_INFO, "login: state -> %d", (int)r.state);
g_st.login_state = r.state;
if (r.state == NCM_LOGIN_OK) {
meow_log(MEOW_LOG_INFO, "login: 803 confirmed, saving cookie");
ncm_strlcpy(cookie, out_cookie, sizeof cookie);
ncm_ctx_set_cookie(&g_st.ctx, cookie);
/* 803 后同步取 uid/nickname 并落盘(weapi 已修复:不再挂死、cookie 全量且去重发送,
* 服务端认可会话)。此处在 worker 线程执行(不卡 UI);失败不阻断跳页——home 加载
* 时 appstate_ensure_uid 会再验一次(也设了硬超时上限,不阻塞歌单页)。 */
char uid[32] = "", nick[128] = "";
int src = ncm_login_status_check(g_st.ctx.device_id, cookie,
uid, sizeof uid, nick, sizeof nick);
meow_log(MEOW_LOG_INFO, "login: status_check rc=%d uid=%s nick=%s", src, uid, nick);
appstate_set_logged_in(src == NCM_OK ? uid : "", src == NCM_OK ? nick : "");
g_st.logged_in = 1;
account_vip_maybe_refresh(1); /* §20:登录后查一次会员态(本 worker 线程,不卡 UI) */
g_done = 1;
meow_log(MEOW_LOG_INFO, "login: -> home");
break;
}
if (r.state == NCM_LOGIN_EXPIRED) {
/* 刷新二维码 */
usleep(500 * 1000);
get_unikey_and_qr(unikey, sizeof unikey);
continue;
}
usleep(1500 * 1000);
}
return NULL;
}
int login_start(void)
{
if (g_polling) return 0;
g_polling = 1;
g_done = 0;
g_err = 0;
if (pthread_create(&g_thread, NULL, login_worker, NULL) != 0) {
g_polling = 0;
return -1;
}
pthread_detach(g_thread);
return 0;
}
int login_is_active(void) { return g_active; }
void login_on_tap(int x, int y)
{
(void)x; (void)y;
if (g_st.login_state == NCM_LOGIN_OK && g_done) return;
if (g_err) { login_start(); login_repaint(); } /* 网络失败重试 */
}
/* UI tick:重绘 QR/状态变化;登录成功 → 切 home。 */
void login_tick(uint64_t now)
{
(void)now;
chrome_tick();
static int last_state = -1, last_qr = 0;
if (g_st.login_state != last_state || g_st.qr_size != last_qr) {
last_state = g_st.login_state;
last_qr = g_st.qr_size;
login_repaint();
}
if (g_done)
meow_view_page_replace(ACT_HOME);
}
/* ---- 生命周期 ---- */
static void login_page_create(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
r_scr = NR(0, 0, meow_ui_width(), meow_ui_height());
/* 沉浸式:登录页整屏自绘(hero 从 y=0 起,覆盖状态栏区)。只画一次并监听绘制物重绘。 */
g_custom_id = meow_draw_custom_register(1, r_scr.x, r_scr.y, r_scr.w, r_scr.h,
login_draw, NULL);
g_active = 0;
}
static void login_page_enter(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
g_active = 1;
const meow_theme *th = meow_theme_get();
meow_ui_set_page_bg(th->bg);
meow_ui_paint_full();
if (!g_host_mock) login_start(); /* 进入即获取二维码 + 轮询 */
}
static void login_page_leave(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
g_active = 0;
}
static void login_page_destroy(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
if (g_custom_id) { meow_draw_unregister(g_custom_id); g_custom_id = 0; }
}
meow_view *login_view_create(void)
{
static const meow_view_hooks h = {
.on_create = login_page_create,
.on_enter = login_page_enter,
.on_leave = login_page_leave,
.on_destroy = login_page_destroy,
};
return meow_view_create(ACT_LOGIN, MEOW_VIEW_ACTIVITY, &h, NULL);
}