Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/test_np_cover.c
T
AskaEth a2aec783a7 feat(ui): M6.38批 — P1 MD3细化(SDK对话框28)+批②底部上滑回桌面+批6 ncm顶栏回播放页+批④面板后台列表+launcher UI微调(歌词页黑半透明底/播放页布局下移)+封面空artwork保留修复
- 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 宿主测试无回归。二进制留批量批次未入库
2026-09-04 13:09:03 +08:00

201 lines
11 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.
/* 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;
}