From b988d64653888bfcc0b17c612b01ecc5bc28627b Mon Sep 17 00:00:00 2001 From: AskaEth Date: Fri, 4 Sep 2026 00:07:34 +0800 Subject: [PATCH] =?UTF-8?q?fix(launcher):=20=E2=91=A0=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E9=A1=B5=E5=BA=95=E9=83=A8=E6=AD=8C=E8=AF=8D?= =?UTF-8?q?=E5=8D=A0=E4=BD=8D=E7=AC=A6(=E6=AD=8C=E8=AF=8D=E9=A1=B5?= =?UTF-8?q?=E5=B7=B2=E5=81=9A,=E5=88=A0NP=5FLYRIC=5FY+=E5=8D=A0=E4=BD=8D?= =?UTF-8?q?=E5=9D=97)=20=E2=91=A1=E5=B0=81=E9=9D=A2'=E7=A9=BA=E2=86=92?= =?UTF-8?q?=E5=A4=8D=E5=8E=9F'=E4=B8=8D=E6=81=A2=E5=A4=8D=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D(meta=5Fchanged=E8=A1=A5artwork/np=5Fcover=5Fload?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E5=8E=BB=E9=87=8D=E6=94=B9=E4=B8=BA=E7=A9=BA?= =?UTF-8?q?=E4=B9=9F=E9=87=8D=E8=BD=BD/=E6=AD=8C=E8=AF=8D=E9=A1=B5?= =?UTF-8?q?=E8=A1=A5=E8=BD=BD=E5=B0=81=E9=9D=A2);=E5=AE=BF=E4=B8=BBtest=5F?= =?UTF-8?q?np=5Fcover=2014=E9=A1=B9PASS,0warning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- patch_v2/meow_launcherd/now_playing.c | 30 ++-- patch_v2/meow_launcherd/test_np_cover.c | 199 ++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 11 deletions(-) create mode 100644 patch_v2/meow_launcherd/test_np_cover.c diff --git a/patch_v2/meow_launcherd/now_playing.c b/patch_v2/meow_launcherd/now_playing.c index feedb3b..3cd6a7b 100644 --- a/patch_v2/meow_launcherd/now_playing.c +++ b/patch_v2/meow_launcherd/now_playing.c @@ -31,7 +31,6 @@ #define NP_NEXT_CX (NP_PLAY_CX + 78) #define NP_PLAY_R 30 #define NP_PREV_R 22 -#define NP_LYRIC_Y 640 static meow_img *g_np_cover = NULL; static char g_np_cover_path[200] = ""; @@ -53,15 +52,17 @@ static int np_lyric_width(const char *u, int size, void *ctx) return ui_width(u, size); } -/* 封面解码缓存(artwork 路径变化才重载)。 */ +/* 封面解码缓存(artwork 路径变化才重载;路径未变但上次加载失败→允许重试)。 + * M6.33 修「artwork 空→复原」封面不重载:原「路径未变即跳过」在「空 artwork 清空封面后再 + * 以同路径复原」或「首载失败同路径重报」时封面永不恢复(g_np_cover==NULL 时即使路径未变也重载)。 */ static void np_cover_load(const meow_media_session_t *s) { if (!s) return; - if (strcmp(g_np_cover_path, s->artwork) == 0) return; + if (g_np_cover && strcmp(g_np_cover_path, s->artwork) == 0) return; /* 已载且未变 → 跳过 */ if (g_np_cover) { meow_img_free(g_np_cover); g_np_cover = NULL; } snprintf(g_np_cover_path, sizeof g_np_cover_path, "%s", s->artwork); if (!s->artwork[0]) return; - char path[200]; + char path[240]; /* 相对 artwork 加前缀最坏 191+19=210 < 240,防 -Wformat-truncation 实际截断 */ if (s->artwork[0] == '/') snprintf(path, sizeof path, "%s", s->artwork); else snprintf(path, sizeof path, "/usr/data/mnt/sd_0/%s", s->artwork); g_np_cover = meow_img_load_file(path); @@ -347,7 +348,10 @@ static void np_draw_lyric_page(int redraw_sb) if (redraw_sb) fill_rect(0, 0, W, SB_BAR_H, th->surface); /* 批5:封面取色背景(主→次渐变)或主题面;并注入歌词页 set_colors。 * 批6:有封面时以封面作整页背景(衬在 lyric_view 半透明 scrim 之下),取色只做附加; - * 无封面回退渐变/主题面。 */ + * 无封面回退渐变/主题面。 + * M6.33:先 np_cover_load(artwork 空→复原/换源时重载封面)再取色——顺序不能反, + * 取色读 g_np_cover,须在封面载入后(否则歌词页在 artwork 复原后仍无封面)。 */ + np_cover_load(meow_media_current()); np_cover_theme_update(); np_apply_lyric_colors(); if (g_np_cover) @@ -420,9 +424,8 @@ static void np_draw_page(int redraw_sb) np_draw_progress(s, pos, th); /* 控制按钮(SDK meow_iconbtn,命中/状态走 SDK) */ np_draw_ctl(s); - /* 歌词占位(M7.0b 文案走 meow_str) */ - const char *ly = meow_str_zh("np.lyric_placeholder", "歌词(占位)"); - ui_draw((W - ui_width(ly, 16)) / 2, NP_LYRIC_Y, ly, th->on_surface_variant, 16); + /* 播放页底部干净(M6.33:歌词占位已移除——已有独立歌词页,占位多余; + * 歌词页无歌词时的空态由歌词页自身承载,播放页不再显示歌词占位文案)。 */ if (redraw_sb) draw_statusbar(); } @@ -504,6 +507,7 @@ void run_now_playing(struct app *apps, int napps) char last_title[128] = ""; uint32_t last_state = 0xFFFFFFFFu; char last_app[MEOW_APP_ID_MAX] = ""; + char last_art[200] = ""; /* M6.33:上次已刷新封面 artwork 路径(artwork 变化也触发整页重绘) */ meow_ui_activity_push(NP_SENTINEL_ACT); /* M7.0b:进入 push 哨兵(与函数尾 pop 配对)。 * 控件 owner 一直钉哨兵,命中要走 meow_widget_hit 的 @@ -644,16 +648,20 @@ void run_now_playing(struct app *apps, int napps) /* 息屏期只低频轮询按键(唤醒用),不重画不读触摸(FBIOBLANK 已挂起 goodix) */ if (!meow_screen_on) { usleep(100 * 1000); continue; } - /* 媒体变化检测(元数据/源/状态):立即整页重画 */ + /* 媒体变化检测(元数据/源/状态/artwork):立即整页重画。 + * M6.33 补 artwork:ncm 状态切换可能先报空 artwork 再复原(标题/源/状态未变), + * 原检测漏 artwork → 封面清空后不再触发整页重绘 → 播放页/歌词页封面永不恢复。 */ const meow_media_session_t *s = meow_media_current(); int meta_changed = !s || strcmp(last_app, s->app_id) || - strcmp(last_title, s->title) || last_state != s->state; + strcmp(last_title, s->title) || strcmp(last_art, s->artwork) || + last_state != s->state; if (meta_changed) { if (s) { snprintf(last_app, sizeof last_app, "%s", s->app_id); snprintf(last_title, sizeof last_title, "%s", s->title); + snprintf(last_art, sizeof last_art, "%s", s->artwork); last_state = s->state; - } else { last_app[0] = 0; last_title[0] = 0; last_state = 0xFFFFFFFFu; } + } else { last_app[0] = 0; last_title[0] = 0; last_art[0] = 0; last_state = 0xFFFFFFFFu; } need_redraw = 1; } /* M2.92 修死循环:空态/停止态只在「变化」时画一次(meta_changed 已覆盖 diff --git a/patch_v2/meow_launcherd/test_np_cover.c b/patch_v2/meow_launcherd/test_np_cover.c new file mode 100644 index 0000000..5927da1 --- /dev/null +++ b/patch_v2/meow_launcherd/test_np_cover.c @@ -0,0 +1,199 @@ +/* test_np_cover.c — M6.33 播放页/歌词页封面 lifecycle 宿主验证。 + * + * 背景:ncm 状态切换可能先上报空 artwork(媒体会话 artwork 短暂为空/换源),随后复原; + * launcher 侧封面渲染在 now_playing.c(静态 np_cover_load / run_now_playing 的 meta_changed)。 + * 本测试验证两处 M6.33 修复的语义: + * np_cover_load 缓存决策:artwork 变化重载;artwork 为空→清封面(回退主题占位); + * 同路径但封面为空(此前加载失败/空清空后)→ 允许重试重载(旧逻辑「路径未变即跳过」会卡死)。 + * run_now_playing meta_changed:补 artwork 追踪,artwork-only 变化也触发整页重绘。 + * + * 说明:np_cover_load 为 now_playing.c 内 static(launcher TU,宿主不可链),故本测试按项目 + * mirror 惯例(同 test_now_playing_widgets 镜像几何)逐字镜像其决策,用**真实 SDK 图片加载**( + * meow_img_load_file/meow_img_free + stb_image)驱动真实 BMP,验证 artwork 生命周期。 + * + * 编译(在 patch_v2/meow_launcherd 下;链法同 test_now_playing_widgets + meow_img_color): + * gcc -Wall -Wextra -o test_np_cover test_np_cover.c \ + * ../sdk/test_host_stub.c ../sdk/meow_ui_gfx.c ../sdk/meow_ui_sys.c \ + * ../sdk/meow_ui_theme.c ../sdk/meow_ui_vol.c ../sdk/meow_ui_log.c \ + * ../sdk/meow_ui_widget.c ../sdk/meow_ui_input.c ../sdk/meow_ui_activity.c \ + * ../sdk/meow_ui_dialog.c ../sdk/meow_ui_ime.c ../sdk/meow_ui_str.c ../sdk/meow_pinyin.c \ + * ../sdk/meow_ui_draw.c ../sdk/meow_ui_view.c ../sdk/meow_ui_box.c ../sdk/meow_ui_anim.c \ + * ../sdk/meow_ui_scroll.c ../sdk/meow_ui_img.c ../sdk/meow_ui_media.c \ + * ../sdk/meow_notify_ipc.c ../sdk/stb_image.c ../sdk/meow_lyric.c ../sdk/meow_lyric_view.c \ + * ../sdk/meow_img_color.c \ + * -I../sdk -I. -DMEOW_UI_HOST_TEST -pthread -lm + * 运行:./test_np_cover + */ +#include "meow_ui.h" +#include "meow_media.h" /* meow_media_session_t(取 artwork 字段) */ +#include +#include +#include + +/* 局部补全 meow_img(与 meow_ui_img.c / meow_img_color.c 布局一致):读取已加载封面尺寸。 */ +struct meow_img { int w, h; unsigned char *rgba; }; +static int img_w(meow_img *im) { return im ? ((struct meow_img *)im)->w : 0; } +static int img_h(meow_img *im) { return im ? ((struct meow_img *)im)->h : 0; } + +static int g_fail = 0; +#define CHECK(cond, name) do { \ + if (cond) printf(" PASS %s\n", name); \ + else { printf(" FAIL %s (%s:%d)\n", name, __FILE__, __LINE__); g_fail++; } } while (0) + +/* ================= M6.33 镜像 np_cover_load(now_playing.c 逐字语义) ================= */ +static meow_img *g_np_cover = NULL; +static char g_np_cover_path[200] = ""; + +static void np_cover_load(const meow_media_session_t *s) +{ + if (!s) return; + if (g_np_cover && strcmp(g_np_cover_path, s->artwork) == 0) return; /* 已载且未变 → 跳过 */ + if (g_np_cover) { meow_img_free(g_np_cover); g_np_cover = NULL; } + snprintf(g_np_cover_path, sizeof g_np_cover_path, "%s", s->artwork); + if (!s->artwork[0]) return; + char path[240]; + if (s->artwork[0] == '/') snprintf(path, sizeof path, "%s", s->artwork); + else snprintf(path, sizeof path, "/usr/data/mnt/sd_0/%s", s->artwork); + g_np_cover = meow_img_load_file(path); +} + +/* ================= M6.33 镜像 run_now_playing meta_changed(补 artwork 追踪) ========= */ +static int meta_changed_needs_redraw(const char *last_app, const char *last_title, + uint32_t last_state, const char *last_art, + const meow_media_session_t *s) +{ + if (!s) return 1; /* 源消失 → 空态重画 */ + return strcmp(last_app, s->app_id) || strcmp(last_title, s->title) || + strcmp(last_art, s->artwork) || last_state != s->state; +} + +/* ============ 小工具:写 24-bit 无压缩 BMP 到 path(stb_image 可读),纯色填充 ============ + * 结构:BITMAPFILEHEADER(14) + BITMAPINFOHEADER(40) + 像素(bottom-up,BGR,行对齐 4B)。 */ +static void write_bmp(const char *path, int w, int h, int r, int g, int b) +{ + int rowPitch = (w * 3 + 3) & ~3; + int imgSize = rowPitch * h; + int fileSize = 54 + imgSize; + unsigned char hdr[54]; + memset(hdr, 0, sizeof hdr); + hdr[0] = 'B'; hdr[1] = 'M'; + hdr[2] = (unsigned char)(fileSize & 0xFF); + hdr[3] = (unsigned char)((fileSize >> 8) & 0xFF); + hdr[4] = (unsigned char)((fileSize >> 16) & 0xFF); + hdr[5] = (unsigned char)((fileSize >> 24) & 0xFF); + hdr[10] = 54; + hdr[14] = 40; + hdr[18] = (unsigned char)(w & 0xFF); + hdr[19] = (unsigned char)((w >> 8) & 0xFF); + hdr[22] = (unsigned char)(h & 0xFF); + hdr[23] = (unsigned char)((h >> 8) & 0xFF); + hdr[24] = (unsigned char)((h >> 16) & 0xFF); + hdr[25] = (unsigned char)((h >> 24) & 0xFF); + hdr[26] = 1; hdr[28] = 24; /* planes=1, bpp=24 */ + hdr[34] = (unsigned char)(imgSize & 0xFF); + hdr[35] = (unsigned char)((imgSize >> 8) & 0xFF); + hdr[36] = (unsigned char)((imgSize >> 16) & 0xFF); + hdr[37] = (unsigned char)((imgSize >> 24) & 0xFF); + FILE *f = fopen(path, "wb"); + if (!f) { printf(" !! cannot write %s\n", path); exit(1); } + fwrite(hdr, 1, sizeof hdr, f); + unsigned char *row = (unsigned char *)malloc((size_t)rowPitch); + memset(row, 0, (size_t)rowPitch); + for (int x = 0; x < w; x++) { row[x * 3 + 0] = (unsigned char)b; row[x * 3 + 1] = (unsigned char)g; row[x * 3 + 2] = (unsigned char)r; } + for (int y = 0; y < h; y++) fwrite(row, 1, (size_t)rowPitch, f); + free(row); + fclose(f); +} + +int main(void) +{ + setbuf(stdout, NULL); + setenv("MEOW_UI_DUMP", "/tmp/npcover.ppm", 1); + meow_ui_opts o = { 480, 720 }; + if (meow_ui_init(&o) != 0) { printf(" FAIL meow_ui_init\n"); return 1; } + meow_ui_activity_push(0x57); + + /* 两张真实封面文件:A=蓝,B=红 */ + const char *pa = "/tmp/np_cover_a.bmp", *pb = "/tmp/np_cover_b.bmp"; + const char *pgone = "/tmp/np_cover_missing.bmp"; /* 先不存在 → 加载失败 */ + write_bmp(pa, 16, 16, 0x40, 0x80, 0xC0); /* 蓝 16x16 */ + write_bmp(pb, 12, 12, 0xC0, 0x40, 0x40); /* 红 12x12(尺寸不同,便于断言「已替换」) */ + + meow_media_session_t s; memset(&s, 0, sizeof s); + s.used = 1; s.state = MEOW_MEDIA_PLAYING; + strcpy(s.app_id, "ncm"); strcpy(s.title, "song"); strcpy(s.artist, "artist"); + + /* ============ C1 artwork 有 → 封面载入(播放页/歌词页共用 g_np_cover) ============ */ + printf("C1 artwork present -> cover loads\n"); + memset(g_np_cover_path, 0, sizeof g_np_cover_path); + strcpy(s.artwork, pa); + np_cover_load(&s); + CHECK(g_np_cover != NULL, "artwork present -> cover loaded (non-NULL)"); + CHECK(img_w(g_np_cover) == 16 && img_h(g_np_cover) == 16, "cover is 16x16"); + /* 同路径未变 → 缓存命中,不重载(指针不变) */ + meow_img *keep = g_np_cover; + np_cover_load(&s); + CHECK(g_np_cover == keep, "same path unchanged -> cache hit (no reload)"); + + /* ============ C2 artwork 空 → 封面清空(回退主题占位) ============ */ + printf("C2 artwork empty -> cover cleared (theme placeholder)\n"); + memset(s.artwork, 0, sizeof s.artwork); + np_cover_load(&s); + CHECK(g_np_cover == NULL, "artwork empty -> cover NULL (theme placeholder)"); + CHECK(g_np_cover_path[0] == 0, "empty -> cached path reset to \"\""); + + /* ============ C3 artwork 从空复原(同路径)→ 封面重载(M6.33 修复) ============ */ + printf("C3 artwork restored (same path) -> cover reloads (M6.33)\n"); + strcpy(s.artwork, pa); + np_cover_load(&s); + CHECK(g_np_cover != NULL, "restore from empty -> cover reloaded"); + CHECK(img_w(g_np_cover) == 16, "reloaded cover intact"); + + /* ============ C4 换源(A→B 另一路径)→ 封面替换 ============ */ + printf("C4 artwork switch (A->B) -> cover replaced\n"); + strcpy(s.artwork, pb); + np_cover_load(&s); + CHECK(g_np_cover != NULL, "switch to B -> cover loaded"); + /* 用尺寸(而非指针身份,malloc 会复用地址)证明确实换成 B */ + CHECK(img_w(g_np_cover) == 12 && img_h(g_np_cover) == 12, "cover replaced by B (12x12)"); + CHECK(strcmp(g_np_cover_path, pb) == 0, "cache path updated to B"); + + /* ============ C5 首载失败同路径重报 → 重试成功(M6.33 修复) ============ */ + printf("C5 first load fails then same-path re-report -> retry succeeds (M6.33)\n"); + memset(g_np_cover_path, 0, sizeof g_np_cover_path); + if (g_np_cover) { meow_img_free(g_np_cover); g_np_cover = NULL; } + /* 缺失文件首载失败:cover NULL, cache path=missing */ + strcpy(s.artwork, pgone); + np_cover_load(&s); + CHECK(g_np_cover == NULL, "nonexistent path -> cover NULL"); + CHECK(strcmp(g_np_cover_path, pgone) == 0, "cache path = missing (recorded)"); + /* 文件此刻出现,仍同路径重报 → 旧逻辑「路径未变即跳过」卡死;新逻辑 g_np_cover==NULL 重试 */ + write_bmp(pgone, 8, 8, 0x20, 0xE0, 0x20); /* 绿 */ + np_cover_load(&s); + CHECK(g_np_cover != NULL, "same path + cover NULL -> reload succeeds"); + CHECK(img_w(g_np_cover) == 8, "retried cover is 8x8"); + remove(pgone); + + /* ============ C6 meta_changed 补 artwork:artwork-only 变化也触发整页重绘 ============ */ + printf("C6 meta_changed tracks artwork (M6.33)\n"); + memset(&s, 0, sizeof s); s.used = 1; s.state = MEOW_MEDIA_PLAYING; + strcpy(s.app_id, "ncm"); strcpy(s.title, "song"); strcpy(s.artwork, pa); + CHECK(meta_changed_needs_redraw("ncm", "song", MEOW_MEDIA_PLAYING, pa, &s) == 0, + "no change -> no redraw"); + /* 仅 artwork 变化(app/title/state 全同)→ 旧逻辑=0(不重绘→封面卡空);新逻辑=1 */ + CHECK(meta_changed_needs_redraw("ncm", "song", MEOW_MEDIA_PLAYING, "", &s) == 1, + "artwork-only change -> redraw (was missed before M6.33)"); + CHECK(meta_changed_needs_redraw("ncm", "song", MEOW_MEDIA_PLAYING, pb, &s) == 1, + "artwork path switch -> redraw"); + CHECK(meta_changed_needs_redraw("ncm", "song", MEOW_MEDIA_PLAYING, pa, &s) == 0, + "same art same title -> no redraw"); + + /* 清理 */ + if (g_np_cover) { meow_img_free(g_np_cover); g_np_cover = NULL; } + remove(pa); remove(pb); + + meow_ui_activity_pop(); + meow_ui_shutdown(); + printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail); + return g_fail; +}