- P1: meow_ui_dialog.c 对话框底框 radius_card→MEOW_SHAPE_EXTRA_LARGE(28) 三处(:103填充/:104描边/:361 BottomSheet); meow_ui_widgets.h 新增 MEOW_SHAPE_EXTRA_LARGE 28 宏; TextField/FAB/Slider/Progress 按设计文档保留 - 批②: np_gesture.h 新增 NP_GEST_HOME+np_home_gesture(底部热区上滑,与左缘返回对称互斥); now_playing.c 新增 np_draw_home_bar(屏底左缘pill指示条)+手势衔接; test_now_playing_widgets G5b - 批6: ncm home.c 顶栏加回播放页按钮(MEOW_ICON_QUEUE_MUSIC)→push(ACT_PLAYER), 无音频也生效 - 批④: meowpanel /api/ams/bg 只读端点+前端后台列表页+杀/清复用kill通道 - UI微调: now_playing.c 播放页布局宏下移(NP_COVER_Y 84/NP_BTN_Y 612消底部留白)+歌词页背景统一黑色半透明叠层(弃封面取色底,删 np_draw_lyric_coverbg) - 封面保留: now_playing.c/notif_center.c 空artwork保留上一张已解码封面(防暂停/开歌单致META报空artwork后封面闪没); test_np_cover C2断言更新+mirror同步 0warning;宿主测试 test_np_cover ALL PASS;launcher/ncm/panel 宿主测试无回归。二进制留批量批次未入库
201 lines
11 KiB
C
201 lines
11 KiB
C
/* 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 <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
|
||
/* 局部补全 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 (!s->artwork[0]) return; /* 镜像 now_playing.c 2026-09-04:空 artwork 保留上一张封面(勿清) */
|
||
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 空 → 封面保留(用户 2026-09-04:勿因空 artwork 清封面) ============ */
|
||
printf("C2 artwork empty -> cover kept (NOT cleared)\n");
|
||
memset(s.artwork, 0, sizeof s.artwork);
|
||
np_cover_load(&s);
|
||
CHECK(g_np_cover == keep, "artwork empty -> cover kept (non-NULL, NOT cleared)");
|
||
CHECK(strcmp(g_np_cover_path, pa) == 0, "empty -> cached path kept (still pa)");
|
||
|
||
/* ============ 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;
|
||
}
|