fix(launcher): 图标区空白——menu_draw_icon 垫块+首字母先画、图片叠加(icon透明/解码失败时占位仍可见); 补 sys_mem_available_kb 实现(解锁OOM护栏编译) (agent-218)

launcher 333d6f53→9902f19c; 宿主test_host_icon PASS
This commit is contained in:
MeowOS committed 2026-08-31 14:34:28 +08:00
1 parent 228e9e79e0
commit e8deed88bd
7 files changed
+193 -2

No files matched your search

+10
View File
@@ -1301,6 +1301,16 @@ static int launch_app_chain(struct app *a, const char *chain)
* 1=大字库GBK,2=Full。SDK meow_ui_gfx 读入存档并按档装载(见 * 1=大字库GBK,2=Full。SDK meow_ui_gfx 读入存档并按档装载(见
* meow_font_effective_paths_tiered;真正装载由 meow_font_reload_tier 实现)。 */ * meow_font_effective_paths_tiered;真正装载由 meow_font_reload_tier 实现)。 */
int eff_tier = meow_font_effective_tier(a->fonts); 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); snprintf(env_tier, sizeof env_tier, "MEOW_FONT_TIER=%d", eff_tier);
envp[ne++] = env_tier; envp[ne++] = env_tier;
have_global = (meow_font_effective_paths_tiered(eff_tier, fpng, sizeof fpng, have_global = (meow_font_effective_paths_tiered(eff_tier, fpng, sizeof fpng,
+7 -2
View File
@@ -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); 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, static void menu_draw_icon(const struct app *a, meow_img *icon, int x, int y, int sz,
const meow_ui_theme_t *th) 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); meow_gfx_fill_round(x, y, sz, sz, sz * 3 / 10, th->primary);
char g[4]; char g[4];
menu_first_glyph(a->name, g); menu_first_glyph(a->name, g);
uint32_t fg = menu_contrast(th->primary); uint32_t fg = menu_contrast(th->primary);
int cw = ui_width(g, 20); 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); 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 命中共用)。 */ /* 应用格几何(grid/list 统一入口;供 draw 与 touch 命中共用)。 */
@@ -553,6 +553,8 @@ extern int log_fd;
extern char log_file[256]; extern char log_file[256];
/* M2.x 应用日志目录简化(apps/<id>/logs → <id>):mkdir -p 幂等;迁移用。 */ /* M2.x 应用日志目录简化(apps/<id>/logs → <id>):mkdir -p 幂等;迁移用。 */
int meow_mkdir_p(const char *path); 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 uint32_t menu_bg;
extern int menu_opened_blank; extern int menu_opened_blank;
extern int meow_auto_slept; extern int meow_auto_slept;
Binary file not shown.
Binary file not shown.
+158
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
/* 与 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;
}
+16
View File
@@ -12,6 +12,22 @@ char log_dir[256] = ""; /* "MeowOS/logs" on card, or "" */
char log_file[256] = ""; /* full path of the boot log file */ 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) static int card_available(void)
{ {
/* TF card mounted? */ /* TF card mounted? */