feat(ncm): 网易云客户端 v1 UI+播放链路 (agent-205) — os.meow.ncm: 登录页(真实unikey二维码+轮询状态机)/我的歌单(listview)/歌单详情/播放页(封面+seek+滚动歌词+竖横屏切换)/设置(音质默认Hi-res+缓存上限默认10G); 播放线程(缓存LRU+minimp3→ALSA UAPI直开hw:0,0); 媒体会话MSG_META上报+PLAYCTL接收(首真实媒体源)
ncm 1e331964 (text 764KB); 0 warning; 宿主离屏6页截图目检+真实QR可扫+qemu-mipsel干净启动+解码446xRT 真机待联调: ALSA时序/803扫码闭环/媒体会话联动/gbk大字库渲染
This commit is contained in:
1 parent
32543ac616
commit
21519ce2e4
23 files changed
+3255
No files matched your search
@@ -0,0 +1,215 @@
|
||||
/* 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_cont;
|
||||
|
||||
/* ---- 绘制 ---- */
|
||||
static void login_draw(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (meow_ui_activity_active() != ACT_LOGIN) return;
|
||||
const meow_theme *th = meow_theme_get();
|
||||
|
||||
/* 标题 */
|
||||
meow_ui_text_center(r_cont.x + r_cont.w/2, r_cont.y + 24, "NetEase Cloud Music", 28, th->fg);
|
||||
meow_ui_text_center(r_cont.x + r_cont.w/2, r_cont.y + 64, "scan to sign in", 20, th->dim);
|
||||
|
||||
/* QR 位图 */
|
||||
int sz = g_st.qr_size;
|
||||
if (sz > 0) {
|
||||
int qs = sz * 6; /* 每模块 6px → 41x41=246,33x33=198 */
|
||||
if (qs > 320) qs = sz * 4;
|
||||
int ox = r_cont.x + (r_cont.w - qs) / 2;
|
||||
int oy = r_cont.y + 108;
|
||||
/* 白色底 + 边框 */
|
||||
meow_ui_fill_rect(ox - 8, oy - 8, qs + 16, qs + 16, 0xFFFFFF);
|
||||
meow_ui_draw_rect(ox - 8, oy - 8, qs + 16, qs + 16, th->outline);
|
||||
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*6, oy + y*6, 6, 6, 0x000000);
|
||||
} else {
|
||||
meow_ui_text_center(r_cont.x + r_cont.w/2, r_cont.y + 180,
|
||||
g_err ? "no network - tap to retry" : "fetching QR key...", 20, th->dim);
|
||||
}
|
||||
|
||||
/* 状态文字 */
|
||||
const char *st = login_status_str(g_st.login_state);
|
||||
if (g_st.login_state == NCM_LOGIN_OK && g_done) st = "success";
|
||||
meow_ui_text_center(r_cont.x + r_cont.w/2, r_cont.y + 108 + 320 + 44,
|
||||
st, 20, (g_st.login_state == NCM_LOGIN_OK) ? th->primary : th->dim);
|
||||
}
|
||||
|
||||
/* 重绘 login 内容(QR+状态变化时)。 */
|
||||
static void login_repaint(void)
|
||||
{
|
||||
meow_ui_invalidate(r_cont.x, r_cont.y, r_cont.w, r_cont.h);
|
||||
}
|
||||
|
||||
const char *login_status_str(int state)
|
||||
{
|
||||
switch (state) {
|
||||
case NCM_LOGIN_PENDING: return "waiting for scan...";
|
||||
case NCM_LOGIN_SCANNED: return "scanned - confirm on phone";
|
||||
case NCM_LOGIN_OK: return "signed in!";
|
||||
case NCM_LOGIN_EXPIRED: return "QR expired - refreshing";
|
||||
default: return "error, tap to retry";
|
||||
}
|
||||
}
|
||||
|
||||
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) return rc;
|
||||
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];
|
||||
if (get_unikey_and_qr(unikey, sizeof unikey) != NCM_OK) { g_err = 1; return NULL; }
|
||||
|
||||
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) { g_err = 1; usleep(2 * 1000 * 1000); continue; }
|
||||
g_st.login_state = r.state;
|
||||
|
||||
if (r.state == NCM_LOGIN_OK) {
|
||||
ncm_strlcpy(cookie, out_cookie, sizeof cookie);
|
||||
ncm_ctx_set_cookie(&g_st.ctx, cookie);
|
||||
/* 拿 uid/nickname */
|
||||
char uid[32], nick[128];
|
||||
if (ncm_login_status_check(g_st.ctx.device_id, cookie, uid, sizeof uid,
|
||||
nick, sizeof nick) == NCM_OK)
|
||||
appstate_set_logged_in(uid, nick);
|
||||
else
|
||||
appstate_set_logged_in("", "");
|
||||
g_st.logged_in = 1;
|
||||
g_done = 1;
|
||||
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_cont = ncm_content_rect();
|
||||
g_custom_id = meow_draw_custom_register(1, r_cont.x, r_cont.y, r_cont.w, r_cont.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);
|
||||
}
|
||||
Reference in new issue
Block a user