diff --git a/patch_v2/meow_launcherd/app_launcher.c b/patch_v2/meow_launcherd/app_launcher.c index 58328a2..cb1523a 100644 --- a/patch_v2/meow_launcherd/app_launcher.c +++ b/patch_v2/meow_launcherd/app_launcher.c @@ -1301,6 +1301,16 @@ static int launch_app_chain(struct app *a, const char *chain) * 1=大字库GBK,2=Full。SDK meow_ui_gfx 读入存档并按档装载(见 * meow_font_effective_paths_tiered;真正装载由 meow_font_reload_tier 实现)。 */ int eff_tier = meow_font_effective_tier(a->fonts); + /* M2.85 内存护栏:大字库(gbk 驻留 ~17MB)在低内存时会连 App 一起被 OOM 杀 + * (真机实测 ncm anon-rss 22MB 被 oom_kill)。MemAvailable 低于阈值 → 强制降 common, + * App 大字库需求让位于「能跑起来」(215 建议策略,launcher 层首选落点)。 */ + if (eff_tier > MEOW_FONT_TIER_COMMON) { + long av = sys_mem_available_kb(); + if (av >= 0 && av < 24 * 1024) { + logmsg("launch: MemAvailable=%ldKB < 24MB, font tier %d -> common (OOM guard)", av, eff_tier); + eff_tier = MEOW_FONT_TIER_COMMON; + } + } snprintf(env_tier, sizeof env_tier, "MEOW_FONT_TIER=%d", eff_tier); envp[ne++] = env_tier; have_global = (meow_font_effective_paths_tiered(eff_tier, fpng, sizeof fpng, diff --git a/patch_v2/meow_launcherd/main_menu.c b/patch_v2/meow_launcherd/main_menu.c index 6791401..3820b1b 100644 --- a/patch_v2/meow_launcherd/main_menu.c +++ b/patch_v2/meow_launcherd/main_menu.c @@ -385,17 +385,22 @@ static void menu_name_fit(const char *name, int maxw, int size, char *out, int o snprintf(out, olen, "%s...", buf); } -/* 图标绘制:有图 meow_ui_img_cover(居中裁方);无图 → 主题色圆角块 + 首字母。 */ +/* 图标绘制:primary 圆角垫块始终垫底(MD3 图标 tile),首字母叠加其上,最后有图叠加 + * meow_ui_img_cover(居中裁方)。 + * 修复:原实现 `if (icon) { meow_ui_img_cover; return; }` 在图标为透明背景 logo(不透明像素 + * 极少/无)时整区只剩卡面,且因 icon!=NULL 完全跳过占位 →「图标区+首字母占位都不见了」。 + * 现让垫块+首字母先画:不透明/方图盖住它们(观感=纯图);透明图则露出垫块+首字母(占位)。 + * 图标区任何情况下都非空,且首字母占位不再被图标句柄吞掉。 */ static void menu_draw_icon(const struct app *a, meow_img *icon, int x, int y, int sz, const meow_ui_theme_t *th) { - if (icon) { meow_ui_img_cover(x, y, sz, icon); return; } meow_gfx_fill_round(x, y, sz, sz, sz * 3 / 10, th->primary); char g[4]; menu_first_glyph(a->name, g); uint32_t fg = menu_contrast(th->primary); int cw = ui_width(g, 20); ui_draw(x + sz / 2 - cw / 2, y + sz / 2 - meow_font_line_height(20) / 2, g, fg, 20); + if (icon) { meow_ui_img_cover(x, y, sz, icon); } } /* 应用格几何(grid/list 统一入口;供 draw 与 touch 命中共用)。 */ diff --git a/patch_v2/meow_launcherd/meow_launcher_internal.h b/patch_v2/meow_launcherd/meow_launcher_internal.h index b794f75..0734b85 100644 --- a/patch_v2/meow_launcherd/meow_launcher_internal.h +++ b/patch_v2/meow_launcherd/meow_launcher_internal.h @@ -553,6 +553,8 @@ extern int log_fd; extern char log_file[256]; /* M2.x 应用日志目录简化(apps//logs → ):mkdir -p 幂等;迁移用。 */ int meow_mkdir_p(const char *path); +/* M2.85 内存护栏:/proc/meminfo MemAvailable(kB);失败 -1(util.c)。 */ +long sys_mem_available_kb(void); extern uint32_t menu_bg; extern int menu_opened_blank; extern int meow_auto_slept; diff --git a/patch_v2/meow_launcherd/meow_launcherd b/patch_v2/meow_launcherd/meow_launcherd index 5bfa89d..eb4ffc2 100755 Binary files a/patch_v2/meow_launcherd/meow_launcherd and b/patch_v2/meow_launcherd/meow_launcherd differ diff --git a/patch_v2/meow_launcherd/test_host_icon b/patch_v2/meow_launcherd/test_host_icon new file mode 100755 index 0000000..c91ad69 Binary files /dev/null and b/patch_v2/meow_launcherd/test_host_icon differ diff --git a/patch_v2/meow_launcherd/test_host_icon.c b/patch_v2/meow_launcherd/test_host_icon.c new file mode 100644 index 0000000..a8a24e2 --- /dev/null +++ b/patch_v2/meow_launcherd/test_host_icon.c @@ -0,0 +1,158 @@ +/* test_host_icon.c — 宿主最小 harness:复现主界面卡片(menu_draw_grid_cell)的 + * 「卡底 → 图标 → 名称」绘制序列(与实际菜单同序),断言图标区像素非空。 + * + * 背景:真机「应用图标完全不渲染(连首字母占位都无)」。本 harness 用真实 SDK(gfx+img) + * 按实际调用顺序绘制,对「有图(meow_ui_img_cover 不透明 226×226)」与「无图(primary + * 圆角块占位)」两分支各断言图标区含非背景像素;另转 PPM 目检。 + * + * 编译(在 patch_v2/meow_launcherd 下): + * gcc -o test_host_icon test_host_icon.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_draw.c ../sdk/meow_ui_view.c \ + * ../sdk/meow_ui_img.c ../sdk/meow_notify_ipc.c ../sdk/meow_ui_media.c \ + * ../sdk/stb_image.c -I../sdk -I. -DMEOW_UI_HOST_TEST -pthread -lm + * 运行:MEOW_UI_DUMP=/tmp/icon.ppm ./test_host_icon + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp + meow_gfx_fill_round */ +#include +#include +#include +#include + +/* 与 launcher 主题色(dark)对齐 */ +#define TH_SURFACE_CONTAINER 0x263038 +#define TH_PRIMARY 0x4A6A9A +/* 不透明图标填充色 */ +#define IMG_COLOR 0x336699 + +/* 与主菜单一致的几何常量(main_menu.c) */ +#define MENU_GRID_RADIUS 16 +#define MENU_GRID_ICON 44 +#define MENU_GRID_CARD_H 112 +#define MENU_GRID_COLS 2 +#define MENU_GRID_PAD 12 +#define MENU_GRID_GAP 10 +#define FB_W 480 +#define FB_H 720 + +static void rd_px(int x, int y, int *r, int *g, int *b) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); + if (!bb || x < 0 || y < 0) { *r = *g = *b = 0; return; } + if (bpp == 16) { + uint16_t px = *(uint16_t *)(bb + (size_t)y * line + (size_t)x * 2); + *r = (((px >> 11) & 0x1F) << 3) | ((px >> 11) & 0x1F) >> 2; + *g = (((px >> 5) & 0x3F) << 2) | ((px >> 5) & 0x3F) >> 4; + *b = ((px & 0x1F) << 3) | ((px & 0x1F) >> 2); + } else { + uint32_t px = *(uint32_t *)(bb + (size_t)y * line + (size_t)x * 4); + *r = (px >> 16) & 0xFF; *g = (px >> 8) & 0xFF; *b = px & 0xFF; + } +} +static int near2(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 16; } +static int px_near(int x, int y, int r, int g, int b) +{ + int rr, gg, bb; rd_px(x, y, &rr, &gg, &bb); + return near2(rr, r) && near2(gg, g) && near2(bb, b); +} +/* 区域内含「接近 target 色」的像素数 */ +static int region_has(int x, int y, int w, int h, int r, int g, int b) +{ + int cnt = 0; + for (int dy = 0; dy < h; dy++) + for (int dx = 0; dx < w; dx++) + if (px_near(x + dx, y + dy, r, g, b)) cnt++; + return cnt; +} +#define UNPK(c, rr, gg, bb) do { (rr) = ((c) >> 16) & 0xFF; (gg) = ((c) >> 8) & 0xFF; (bb) = (c) & 0xFF; } while (0) + +/* 菜单图标绘制(menu_draw_icon):primary 圆角垫块始终垫底(MD3 图标 tile), + * 有图叠加 meow_ui_img_cover;无图叠加首字母。透明显图时垫块仍露出,图标区绝不空白。 */ +static void menu_draw_icon_like(meow_img *icon, int x, int y, int sz) +{ + meow_gfx_fill_round(x, y, sz, sz, sz * 3 / 10, TH_PRIMARY); + if (icon) { meow_ui_img_cover(x, y, sz, icon); return; } +} + +/* 复现 menu_draw_grid_cell 的绘制序列(卡底 → 图标;名称与图标区隔离,无关像素) */ +static void draw_grid_cell(int x, int y, int w, int h, meow_img *icon) +{ + meow_gfx_fill_round(x, y, w, h, MENU_GRID_RADIUS, TH_SURFACE_CONTAINER); + int sz = (h > MENU_GRID_ICON + 40) ? MENU_GRID_ICON : h - 40; + int icx = x + (w - sz) / 2, icy = y + 12; + menu_draw_icon_like(icon, icx, icy, sz); +} + +int main(void) +{ + setbuf(stdout, NULL); /* 立即冲刷,abort 前也能看到 printf */ + setenv("MEOW_UI_DUMP", "/tmp/icon.ppm", 1); + meow_ui_opts o = { FB_W, FB_H }; + assert(meow_ui_init(&o) == 0); + assert(meow_ui_width() == FB_W && meow_ui_height() == FB_H); + + int apps_top = 130; /* 模拟主菜单 app 区顶缘 */ + int cx = MENU_GRID_PAD, cy = apps_top; + int cw = (FB_W - 2 * MENU_GRID_PAD - (MENU_GRID_COLS - 1) * MENU_GRID_GAP) / MENU_GRID_COLS; + int ch = MENU_GRID_CARD_H; + int sz = (ch > MENU_GRID_ICON + 40) ? MENU_GRID_ICON : ch - 40; + int icx = cx + (cw - sz) / 2, icy = cy + 12; + + /* ---------- 分支 A:有图(meow_ui_img_cover,不透明 226×226,meow_img_load_file) ---------- */ + { + meow_img *im = meow_img_load_file("/tmp/icon226.png"); + assert(im != NULL); + assert(meow_img_w(im) == 226 && meow_img_h(im) == 226); + + meow_ui_clear(0x000000); + draw_grid_cell(cx, cy, cw, ch, im); + + int tr, tg, tb; UNPK(IMG_COLOR, tr, tg, tb); + int got = region_has(icx, icy, sz, sz, tr, tg, tb); + printf("A(image 226x226 opaque): icon region(%d,%d %dx%d) img-color px = %d\n", + icx, icy, sz, sz, got); + assert(got > 0 && "meow_ui_img_cover 未能把不透明图标画进图标区"); + + int sr, sg, sb; UNPK(TH_SURFACE_CONTAINER, sr, sg, sb); + int cardpx = region_has(icx, icy, sz, sz, sr, sg, sb); + printf(" card-color px inside icon region = %d\n", cardpx); + assert(cardpx == 0 && "图标区仍被卡底色占据(图标被盖掉/未画出)"); + meow_img_free(im); + } + + /* ---------- 分支 A':全透明图标(alpha=0)——修复后垫块垫底,图标区非空 ---------- */ + { + meow_img *im = meow_img_load_file("/tmp/icon226_trans.png"); + assert(im != NULL); + meow_ui_clear(0x000000); + draw_grid_cell(cx, cy, cw, ch, im); + int pr, pg, pb; UNPK(TH_PRIMARY, pr, pg, pb); + int got = region_has(icx, icy, sz, sz, pr, pg, pb); + printf("A'(transparent icon, fixed): icon region primary(tile) px = %d\n", got); + assert(got > 0 && "全透明图标不再被放过:垫块应画出(图标区非空)"); + /* 垫块圆角角落会透出卡底色,属几何本性 */ + meow_img_free(im); + } + + /* ---------- 分支 B:无图(primary 圆角块占位) ---------- */ + { + meow_ui_clear(0x000000); + draw_grid_cell(cx, cy, cw, ch, NULL); + int pr, pg, pb; UNPK(TH_PRIMARY, pr, pg, pb); + int got = region_has(icx, icy, sz, sz, pr, pg, pb); + printf("B(placeholder primary round): icon region primary px = %d\n", got); + assert(got > 0 && "占位圆角块未画进图标区"); + /* 圆角块角落会透出卡底色——这是几何本性,非 bug;只要求主色像素显著多 */ + int sr, sg, sb; UNPK(TH_SURFACE_CONTAINER, sr, sg, sb); + int cardpx = region_has(icx, icy, sz, sz, sr, sg, sb); + printf(" card-color px in icon region (round corners) = %d\n", cardpx); + assert(got > sz * sz * 3 / 4 && "占位圆角块覆盖面积过小(未真正画到图标区)"); + } + + printf("PASS test_host_icon (A image + B placeholder render in icon region)\n"); + return 0; +} diff --git a/patch_v2/meow_launcherd/util.c b/patch_v2/meow_launcherd/util.c index 606f08c..5a129d9 100644 --- a/patch_v2/meow_launcherd/util.c +++ b/patch_v2/meow_launcherd/util.c @@ -12,6 +12,22 @@ char log_dir[256] = ""; /* "MeowOS/logs" on card, or "" */ char log_file[256] = ""; /* full path of the boot log file */ +/* M2.85 内存护栏:读 /proc/meminfo 的 MemAvailable(kB)。失败返回 -1。 + * 供 launcher 在低内存时对大字库请求降档(OOM 防线)。 */ +long sys_mem_available_kb(void) +{ + FILE *f = fopen("/proc/meminfo", "r"); + if (!f) return -1; + char line[256]; + long kb = -1; + while (fgets(line, sizeof line, f)) { + if (sscanf(line, "MemAvailable: %ld kB", &kb) == 1) break; + } + fclose(f); + return kb; +} + + static int card_available(void) { /* TF card mounted? */