From c0f7a95719a859c1f48cb7157e2a5e82c3ad0339 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Mon, 31 Aug 2026 13:28:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(font+sys):=20=E5=AD=97=E5=BA=93=E5=86=85?= =?UTF-8?q?=E5=AD=98=E7=BA=AA=E5=BE=8B(agent-215)+sysui=E6=8E=A5=E7=AE=A1l?= =?UTF-8?q?auncher=E7=9B=91=E7=AE=A1(agent-216)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 215: meow_font.c 流式分块解码(gbk峰值40→23.5MB, 逐字节校验一致) + 多字体槽5→4+16MB驻留预算; 量化: gbk稳态驻留17.3MB vs common 4.2MB(OOM主因); meow_font.a→67113c03 meow_ui.a→223d5401 216: bootmenu让位(sysui在则start launcher让位, 不在则exec兜底) + S70预置launcher.conf enabled=1(+旧设备升级迁移); launcher崩溃可自动拉起; bootmenu 4441822d / S70 e435affc / S90 e0ace111 216注: S70早启动与选单共存为真机重点观察项 --- font/meow_font.c | 149 +++++++++++++++++++---------- font/meow_font.h | 7 +- font/test_budget.c | 39 ++++++++ font/test_mem_trace.c | 119 +++++++++++++++++++++++ font/test_plane_dump.c | 32 +++++++ patch_v2/bootmenu/S90meow_bootmenu | 9 +- patch_v2/bootmenu/meow_bootmenu | Bin 149468 -> 150924 bytes patch_v2/bootmenu/meow_bootmenu.c | 111 +++++++++++++++++---- patch_v2/meow_sysui/S70meow_sysui | 38 +++++--- patch_v2/meow_sysui/meow_sysui.md | 22 +++-- 10 files changed, 434 insertions(+), 92 deletions(-) create mode 100644 font/test_budget.c create mode 100644 font/test_mem_trace.c create mode 100644 font/test_plane_dump.c diff --git a/font/meow_font.c b/font/meow_font.c index 9283978..410ca41 100644 --- a/font/meow_font.c +++ b/font/meow_font.c @@ -122,26 +122,13 @@ static unsigned char *read_file(const char *path, size_t *out_len) { return buf; } -/* ---------------- zlib inflate ---------------- */ -static int inflate_mem(const unsigned char *src, size_t srclen, - unsigned char *dst, size_t dstlen, size_t *out) { - mzf_stream s; - memset(&s, 0, sizeof(s)); - s.next_in = (mzf_Bytef *)src; - s.avail_in = (mzf_uInt)srclen; - s.next_out = (mzf_Bytef *)dst; - s.avail_out = (mzf_uInt)dstlen; - s.zalloc = 0; s.zfree = 0; s.opaque = 0; - if (inflateInit_(&s, zlibVersion(), (int)sizeof(s)) != MZF_OK) - return -1; - int ret = inflate(&s, MZF_FINISH); - *out = (size_t)(dstlen - s.avail_out); - inflateEnd(&s); - return (ret == MZF_STREAM_END) ? 0 : -1; -} - -/* ---------------- 最小 PNG 解码(灰度 non-interlaced) ---------------- */ -/* 返回打包像素面(调用方释放),并输出 width/height/depth/plane_rowbytes。失败返回 NULL。 */ +/* ---------------- 最小 PNG 解码(灰度 non-interlaced) ---------------- + * 流式/分块解码,消除「整幅原始扫描行 raw + 整段 idat 拷贝」双份驻留: + * - idat 不再另外 malloc 拷贝,直接以 png 文件缓冲内 IDAT 区间作 zlib 输入; + * - 逐行 inflate 到单行小缓冲 rowbuf(rowbytes+1 字节),随即反滤波写入打包面 out; + * 不再分配整幅 raw(= 扫描行数×(rowbytes+1))——对大字库这是 6-16MB 级的一次性峰值。 + * 大字库 gbk 解码面本身很大(行数×rowbytes),流式后 load 峰值 ≈ 文件缓冲 + idx + 打包面, + * 而非 文件缓冲 + idat + raw + 打包面。*/ static unsigned char *png_decode_gray(const unsigned char *png, size_t len, unsigned *out_w, unsigned *out_h, unsigned *out_depth, size_t *out_rowbytes) { @@ -150,14 +137,14 @@ static unsigned char *png_decode_gray(const unsigned char *png, size_t len, size_t i = 8; unsigned w = 0, h = 0, depth = 0, color = 0, interlace = 1, compression = 1, filt = 1; int have_ihdr = 0; - size_t idat_len = 0; - unsigned char *idat = NULL; + size_t idat_off = 0, idat_len = 0; /* IDAT 在 png 内的连续区间(偏移/长度) */ + int have_idat = 0; while (i + 8 <= len) { uint32_t clen = be32(png + i); const unsigned char *type = png + i + 4; if (i + 8 + (size_t)clen > len) break; if (memcmp(type, "IHDR", 4) == 0) { - if (clen < 13) { free(idat); return NULL; } + if (clen < 13) return NULL; w = be32(png + i + 8); h = be32(png + i + 12); depth = png[i + 16]; @@ -167,43 +154,61 @@ static unsigned char *png_decode_gray(const unsigned char *png, size_t len, interlace = png[i + 20]; have_ihdr = 1; } else if (memcmp(type, "IDAT", 4) == 0) { - unsigned char *na = (unsigned char *)realloc(idat, idat_len + clen); - if (!na) { free(idat); return NULL; } - idat = na; - memcpy(idat + idat_len, png + i + 8, clen); - idat_len += clen; + if (!have_idat) { idat_off = i + 8; idat_len = clen; have_idat = 1; } + else { + /* 要求 IDAT 连续(合法 PNG/单段压缩满足;gen_font.py 亦单段) */ + if (idat_off + idat_len != i + 8) return NULL; + idat_len += clen; + } } else if (memcmp(type, "IEND", 4) == 0) { break; } i += 12 + (size_t)clen; } - if (!have_ihdr || !idat || + if (!have_ihdr || !have_idat || color != 0 || compression != 0 || filt != 0 || interlace != 0 || - (depth != 4 && depth != 8) || w == 0 || h == 0) { - free(idat); + (depth != 4 && depth != 8) || w == 0 || h == 0) return NULL; - } size_t rowbytes = ((size_t)w * depth + 7) / 8; size_t scanlen = rowbytes + 1; /* 每扫描行 = 1 filter 字节 + rowbytes 像素 */ - size_t rawlen = scanlen * h; - unsigned char *raw = (unsigned char *)malloc(rawlen); - if (!raw) { free(idat); return NULL; } - size_t outlen = 0; - if (inflate_mem(idat, idat_len, raw, rawlen, &outlen) != 0 || outlen != rawlen) { - free(raw); free(idat); return NULL; + size_t rawlen = scanlen * h; /* 期望的解压总量(校验用,不据此分配) */ + + unsigned char *out = (unsigned char *)malloc(rowbytes * h); + if (!out) return NULL; + unsigned char *rowbuf = (unsigned char *)malloc(scanlen); + if (!rowbuf) { free(out); return NULL; } + + mzf_stream s; + memset(&s, 0, sizeof(s)); + s.next_in = (mzf_Bytef *)(png + idat_off); + s.avail_in = (mzf_uInt)idat_len; + s.zalloc = 0; s.zfree = 0; s.opaque = 0; + if (inflateInit_(&s, zlibVersion(), (int)sizeof(s)) != MZF_OK) { + free(out); free(rowbuf); return NULL; } - free(idat); - /* 反滤波(bpp=1:灰度 depth 4|8) → out 为每行 rowbytes 的打包像素 */ - size_t outlen_px = rowbytes * h; - unsigned char *out = (unsigned char *)malloc(outlen_px); - if (!out) { free(raw); return NULL; } const unsigned bpp = 1; - for (size_t y = 0; y < h; y++) { - unsigned char ft = raw[y * scanlen]; - const unsigned char *c = raw + y * scanlen + 1; - unsigned char *dst = out + y * rowbytes; - unsigned char *prev = (y == 0) ? NULL : out + (y - 1) * rowbytes; + size_t produced = 0; + int ok = 1, stream_end = 0; + for (unsigned y = 0; y < h; y++) { + /* 逐行填满 rowbuf(zlib 可能分批产出,内层循环补足到 scanlen) */ + size_t filled = 0; + while (filled < scanlen) { + s.next_out = rowbuf + filled; + s.avail_out = (mzf_uInt)(scanlen - filled); + int ret = inflate(&s, MZF_NO_FLUSH); + size_t got = (scanlen - filled) - s.avail_out; + filled += got; + if (ret == MZF_STREAM_END) { stream_end = 1; break; } + if (ret != MZF_OK || got == 0) { ok = 0; break; } + } + if (!ok || filled != scanlen) { ok = 0; break; } + + /* 反滤波(bpp=1:灰度 depth 4|8)→ out 为每行 rowbytes 的打包像素 */ + unsigned char ft = rowbuf[0]; + const unsigned char *c = rowbuf + 1; + unsigned char *dst = out + (size_t)y * rowbytes; + unsigned char *prev = (y == 0) ? NULL : out + (size_t)(y - 1) * rowbytes; for (size_t x = 0; x < rowbytes; x++) { unsigned char left = (x >= bpp) ? dst[x - bpp] : 0; unsigned char up = prev ? prev[x] : 0; @@ -223,11 +228,17 @@ static unsigned char *png_decode_gray(const unsigned char *png, size_t len, dst[x] = v + pred; break; } - default: free(raw); free(out); return NULL; + default: ok = 0; break; } } + if (!ok) break; + produced += scanlen; + if (stream_end) break; } - free(raw); + inflateEnd(&s); + free(rowbuf); + if (!ok || produced != rawlen) { free(out); return NULL; } + *out_w = w; *out_h = h; *out_depth = depth; *out_rowbytes = rowbytes; return out; } @@ -493,6 +504,18 @@ void meow_font_unload(void) { for (int i = 0; i < MEOW_FCLS_COUNT; i++) g_class_slot[i] = 0; } +#ifdef MEOW_FONT_ENABLE_DEBUG +/* 宿主调试/校验钩子:取某槽已装载(打包)像素面 + 几何。仅宿主单测用, + * mipsel 构建不含 MEOW_FONT_ENABLE_DEBUG,无此符号、零开销。 */ +const unsigned char *meow_font_debug_plane(int slot, unsigned *w, unsigned *h, + unsigned *depth, size_t *rowbytes) { + meow_font_slot *st = &g_slots[slot]; + if (!st->loaded) return NULL; + *w = st->plane_w; *h = st->plane_h; *depth = st->depth; *rowbytes = st->plane_rowbytes; + return st->plane; +} +#endif + int meow_font_load_slot(int slot, const char *png_path, const char *idx_path) { if (slot < 0 || slot >= MEOW_MAX_SLOTS) return -9; meow_font_unload_slot(slot); @@ -672,7 +695,23 @@ void meow_font_drawtext_outline(uint16_t *fb, int x, int y, const char *utf8, meow_font_drawtext(fb, x, y, utf8, color, size); } -/* ---------------- 多字体配置(去重 + ≤4 套上限,launcher 与 SDK 共用) ---------------- */ +/* ---------------- 多字体配置(去重 + ≤3 套上限 + 内存预算,launcher 与 SDK 共用) ---------------- */ +/* 多字体全部槽(含 slot0)「打包像素面」总字节预算:超出时新增类别字库被拒、回退全局并用 + * *truncated=1 通知调用方 WARN。设定依据(真机 56MB,launcher+App 常态化紧张): + * slot0 若为 gbk 大字库,其 plane 已 ≈16.6MiB(>预算)→ 自动拒绝任何额外类别字库, + * 杜绝「大字库 + 多字体」双驻留 OOM;slot0 为 common(≈4.2MiB)时允许 1-3 个额外常用类 + * (+小类拉丁/数字等),封顶后单进程字库总驻留可控制在 ~16MiB 内。 + * 值 16 MiB:56MiB 总内存 − launcher 基线(估 26-28MiB)− 应用运行时 → 字库红利带。 */ +#define MEOW_MULTIFONT_MEM_BUDGET (16u * 1024 * 1024) + +static size_t mfont_total_plane_bytes(void) { + size_t t = 0; + for (int i = 0; i < MEOW_MAX_SLOTS; i++) { + meow_font_slot *st = &g_slots[i]; + if (st->loaded) t += (size_t)st->plane_rowbytes * st->plane_h; + } + return t; +} int meow_font_config_multifont(int on, const char *global_png, const char *global_idx, const meow_font_class_cfg cfg[MEOW_FCLS_COUNT], @@ -716,6 +755,14 @@ int meow_font_config_multifont(int on, g_class_slot[c] = 0; continue; } + /* 内存预算:装载成功后,若全部槽 plane 总字节超预算 → 卸载该新槽 + 回退全局 + 截断通知。 + * 防「大字库 slot0 + 多个大字库类别字库」同时驻留(真机 56MB 会 OOM)。 */ + if (mfont_total_plane_bytes() > MEOW_MULTIFONT_MEM_BUDGET) { + meow_font_unload_slot(slot); + if (truncated) *truncated = 1; + g_class_slot[c] = 0; + continue; + } d_png[d_n] = png; d_slot[d_n] = slot; d_n++; g_class_slot[c] = slot; } diff --git a/font/meow_font.h b/font/meow_font.h index 7860d54..431cf32 100644 --- a/font/meow_font.h +++ b/font/meow_font.h @@ -84,8 +84,11 @@ int meow_font_has_glyph(uint32_t cp); * meow_font_drawtext/width_text/line_height/... 在 meow_font_enable_multifont(1) 开启时 * 逐字符分类 → 用该类别对应槽的字库取字形与度量;缺该类别字库/未加载 → 回退 slot 0。 * 关闭(默认)时一律走 slot 0,行为与单字体完全一致(零额外开销)。 - * 同 family 去重、≤4 套上限由 meow_font_config_multifont() 统一处理(launcher 与 SDK 共用)。 */ -#define MEOW_MAX_SLOTS 5 /* slot0(全局) + ≤4 套类别字库;超出则截断并置 *truncated=1(调用方 WARN) */ + * 同 family 去重、类别字库 ≤3 套上限 + 打包面内存预算由 meow_font_config_multifont() 统一处理(launcher 与 SDK 共用)。 */ +#define MEOW_MAX_SLOTS 4 /* slot0(全局) + ≤3 套类别字库。真机 56MB:多字体每套字库都驻留整幅 + * 打包面(common≈4.2MiB / gbk≈16.6MiB),5 套太多会叠加 OOM;故硬上限 + * 收到 4,且 meow_font_config_multifont 另按 meow_font.c 内 + * MEOW_MULTIFONT_MEM_BUDGET 做「总打包面字节」预算(超出→新增类别回退全局)。 */ int meow_font_load_slot(int slot, const char *png_path, const char *idx_path); /* 0=成功,负值失败 */ void meow_font_unload_slot(int slot); diff --git a/font/test_budget.c b/font/test_budget.c new file mode 100644 index 0000000..d9ceb89 --- /dev/null +++ b/font/test_budget.c @@ -0,0 +1,39 @@ +/* + * test_budget.c — 宿主校验 meow_font_config_multifont 的「打包面内存预算」: + * slot0=common(≈4.2MiB)+ 给 CJK 类别装 gbk(≈16.6MiB)→ 合计 > MEOW_MULTIFONT_MEM_BUDGET + * → gbk 应被拒绝(distinct=1,truncated=1),防止大字库 + 多字体双驻留 OOM。 + * 用法:gcc -I. test_budget.c meow_font.c -lz -o test_budget + * ./test_budget + */ +#include "meow_font.h" +#include +#include + +static int g_fail = 0; +#define CHECK(cond, msg) do { \ + if (!(cond)) { g_fail++; fprintf(stderr, "FAIL: %s\n", msg); } \ + else printf("ok : %s\n", msg); \ +} while (0) + +int main(int argc, char **argv) { + if (argc < 5) { fprintf(stderr, "usage: %s \n", argv[0]); return 2; } + int r = meow_font_load(argv[1], argv[2]); /* slot0 = common */ + CHECK(r == 0, "load slot0 common"); + meow_font_class_cfg cfg[MEOW_FCLS_COUNT]; + memset(cfg, 0, sizeof cfg); + cfg[MEOW_FCLS_CJK].png = argv[3]; /* CJK 类别 → gbk 大字库 */ + cfg[MEOW_FCLS_CJK].idx = argv[4]; + int distinct = 0, truncated = 0; + r = meow_font_config_multifont(1, argv[1], argv[2], cfg, &distinct, &truncated); + CHECK(r == 0, "config_multifont ok"); + printf(" distinct=%d truncated=%d\n", distinct, truncated); + /* slot0 common 4.2MiB + gbk 16.6MiB = 20.8MiB > 16MiB 预算 → gbk 被拒 → distinct 仅全局 */ + CHECK(distinct == 1, "distinct==1 (gbk rejected by MEM_BUDGET)"); + CHECK(truncated == 1, "truncated==1 (budget exceeded -> class fell back to global)"); + /* gbk 槽应未驻留:distinct==1(仅全局 common)即证明 gbk 未被 commit; + * 注意 common 4k 亦含「中」,故不能以 has_glyph(中)==0 断言——改以「common 仍在」佐证。 */ + CHECK(meow_font_has_glyph('A') == 1, "has_glyph(A)==1 (common still there)"); + meow_font_unload(); + printf("\nRESULT: %s\n", g_fail ? "FAIL" : "PASS"); + return g_fail ? 1 : 0; +} diff --git a/font/test_mem_trace.c b/font/test_mem_trace.c new file mode 100644 index 0000000..0758d7f --- /dev/null +++ b/font/test_mem_trace.c @@ -0,0 +1,119 @@ +/* + * test_mem_trace.c — MeowOS 字库加载内存驻留量测(宿主导出单测)。 + * + * 目的:精确量化 meow_font_load / meow_font_unload / meow_font_reload_tier: + * (1) load 过程中堆峰值(含 PNG 解码中间:文件缓冲 + idat + 原始扫描行 + 打包面); + * (2) load 后稳态驻留(plane + glyphs); + * (3) unload 后 live 归零(free 真正归还、无碎片驻留)。 + * + * 机制:-Wl,--wrap=malloc/free/calloc/realloc 把 meow_font.c 的分配纳入统计。 + * __wrap_* 用「指针→字节数」记账表精确统计 live / peak(无论 libc 是否支持 usable_size)。 + * + * 用法: + * gcc -I. -O0 -g test_mem_trace.c meow_font.c -lz \ + * -Wl,--wrap=malloc -Wl,--wrap=free -Wl,--wrap=calloc -Wl,--wrap=realloc \ + * -o test_mem_trace + * ./test_mem_trace + * 退出码 0=全过;非 0=某断言失败。 + */ +#define _GNU_SOURCE +#include "meow_font.h" +#include +#include +#include +#include +#include + +/* ---------------- 指针→大小 记账表 ---------------- */ +#define TREESZ 4096 +static struct { void *p; size_t sz; } g_tab[TREESZ]; +static size_t g_live = 0, g_peak = 0; +static int g_tracking = 0; + +static void note_add(void *p, size_t sz) { + if (!g_tracking || !p) return; + g_live += sz; + if (g_live > g_peak) g_peak = g_live; + for (int i = 0; i < TREESZ; i++) if (!g_tab[i].p) { g_tab[i].p = p; g_tab[i].sz = sz; return; } +} +static void note_sub(void *p) { + if (!g_tracking || !p) return; + for (int i = 0; i < TREESZ; i++) { + if (g_tab[i].p == p) { g_live -= g_tab[i].sz; g_tab[i].p = NULL; g_tab[i].sz = 0; return; } + } +} +static size_t note_size(void *p) { + if (!p) return 0; + for (int i = 0; i < TREESZ; i++) if (g_tab[i].p == p) return g_tab[i].sz; + return 0; +} + +void *__real_malloc(size_t n); +void *__real_calloc(size_t n, size_t s); +void *__real_realloc(void *p, size_t n); +void __real_free(void *p); + +void *__wrap_malloc(size_t n) { void *p = __real_malloc(n); if (p) note_add(p, n); return p; } +void *__wrap_calloc(size_t n, size_t s) { void *p = __real_calloc(n, s); if (p) note_add(p, n * s); return p; } +void *__wrap_realloc(void *p, size_t n) { + if (!p) { void *np = __real_realloc(NULL, n); if (np) note_add(np, n); return np; } + size_t old = note_size(p); + void *np = __real_realloc(p, n); + if (np) { note_sub(p); note_add(np, n); } + else { /* realloc 失败:原指针仍有效,表项保留 */ } + (void)old; + return np; +} +void __wrap_free(void *p) { if (p) note_sub(p); __real_free(p); } + +static void reset_peak(void) { g_peak = g_live; } + +static int g_fail = 0; +#define CHECK(cond, msg) do { \ + if (!(cond)) { g_fail++; fprintf(stderr, "FAIL: %s (live=%zu peak=%zu)\n", msg, g_live, g_peak); } \ + else printf("ok : %s\n", msg); \ +} while (0) + +static double MiB(size_t b) { return (double)b / 1048576.0; } +static double KiB(size_t b) { return (double)b / 1024.0; } + +int main(int argc, char **argv) { + if (argc < 3) { fprintf(stderr, "usage: %s \n", argv[0]); return 2; } + const char *png = argv[1], *idx = argv[2]; + + g_tracking = 1; + reset_peak(); + printf("== meow_font 内存量测(精确指针记账)==\n"); + printf("png=%s idx=%s\n", png, idx); + + int r = meow_font_load(png, idx); + CHECK(r == 0, "meow_font_load ok"); + printf(" >> load 后稳态驻留 live = %.2f MiB (%.2f KiB)\n", MiB(g_live), KiB(g_live)); + printf(" >> load 期间堆峰值 peak = %.2f MiB (%.2f KiB)\n", MiB(g_peak), KiB(g_peak)); + + static uint16_t fb[480 * 320]; + meow_font_set_fb(480, 320, 480 * 2); + printf(" drawtext smoke...\n"); + meow_font_drawtext(fb, 0, 0, "中文测试 Abc 123,", 0xFFFF, 28); + meow_font_drawtext(fb, 0, 60, "试渲染第20号 face 中文", 0xF800, 20); + printf(" drawtext 后 live = %.2f MiB\n", MiB(g_live)); + + /* 反复 load/unload 验证无泄漏累增(每次 load 后的 live 应一致,unload 后归零) */ + size_t live_after_load[3]; + for (int k = 0; k < 3; k++) { + meow_font_unload(); + if (g_live != 0) { printf(" [iter %d] unload 后 live=%zu (非0! -> leak)\n", k, g_live); } + meow_font_load(png, idx); + live_after_load[k] = g_live; + } + printf(" 三次 load 后 live = %.2f / %.2f / %.2f MiB\n", + MiB(live_after_load[0]), MiB(live_after_load[1]), MiB(live_after_load[2])); + CHECK(live_after_load[0] == live_after_load[1] && live_after_load[1] == live_after_load[2], + "三轮回 load/unload 稳定(无泄漏累增)"); + meow_font_unload(); + printf(" >> unload 后 live = %.2f MiB (峰值 %.2f MiB)\n", MiB(g_live), MiB(g_peak)); + CHECK(g_live == 0, "unload 后 live 归零(free 真正归还)"); + + printf("\nRESULT: %s\n", g_fail ? "FAIL" : "PASS"); + return g_fail ? 1 : 0; +} diff --git a/font/test_plane_dump.c b/font/test_plane_dump.c new file mode 100644 index 0000000..d539d80 --- /dev/null +++ b/font/test_plane_dump.c @@ -0,0 +1,32 @@ +/* + * test_plane_dump.c — 宿主解码正确性校验:把 meow_font 解码出的打包像素面 dump 成二进制, + * 供 Python 参考解码(zlib IDAT + 反滤波)逐字节比对,验证流式解码与参考解码完全一致。 + * 需以 -DMEOW_FONT_ENABLE_DEBUG 编译 meow_font.c 暴露 meow_font_debug_plane。 + */ +#include "meow_font.h" +#include +#include + +/* 宿主调试钩子:定义于 meow_font.c(MEOW_FONT_ENABLE_DEBUG 下才编译);此测试自带声明。 */ +extern const unsigned char *meow_font_debug_plane(int slot, unsigned *w, unsigned *h, + unsigned *depth, size_t *rowbytes); + +int main(int argc, char **argv) { + if (argc < 4) { fprintf(stderr, "usage: %s \n", argv[0]); return 2; } + int r = meow_font_load(argv[1], argv[2]); + if (r != 0) { fprintf(stderr, "load failed %d\n", r); return 1; } + unsigned w, h, depth; size_t rb; + const unsigned char *p = meow_font_debug_plane(0, &w, &h, &depth, &rb); + if (!p) { fprintf(stderr, "no plane\n"); return 1; } + size_t total = (size_t)rb * h; + FILE *f = fopen(argv[3], "wb"); + if (!f) { return 1; } + /* 先写出几何头(小端),再写平面字节 */ + unsigned hdr[5] = { w, h, depth, (unsigned)rb, (unsigned)total }; + fwrite(hdr, sizeof(unsigned), 5, f); + fwrite(p, 1, total, f); + fclose(f); + printf("dumped w=%u h=%u depth=%u rowbytes=%zu bytes=%zu\n", w, h, depth, rb, total); + meow_font_unload(); + return 0; +} diff --git a/patch_v2/bootmenu/S90meow_bootmenu b/patch_v2/bootmenu/S90meow_bootmenu index f26ae7f..ecbaed5 100755 --- a/patch_v2/bootmenu/S90meow_bootmenu +++ b/patch_v2/bootmenu/S90meow_bootmenu @@ -8,10 +8,11 @@ # * M2: meow_bootmenu is launched with --fork, so: # 选 HibyOS -> bootmenu forks /usr/bin/hiby_player (最小启动契约), # parent becomes supervisor/热键守护, blocks rcS. -# 选 MeowOS -> bootmenu exec /usr/bin/meow_launcherd (MeowOS 基础主屏), -# blocks rcS. -# Either way rcS is blocked by S90 until整机重启/热键切换, so S92 (兜底 patch) -# is normally never reached; SSH was moved to S84 so it is already online. +# 选 MeowOS -> launcher 生命周期已交 meow_sysui(S70 起):sysui 在 → bootmenu +# 让 sysui 拉起 launcher 并让位退出(rcS 推进,S92 见 mode=meow 跳过 +# 播放器);sysui 不在 → bootmenu 照旧 exec /usr/bin/meow_launcherd(兜底)。 +# HibyOS 模式 rcS 被 S90 阻塞;MeowOS(sysui 托管)rcS 推进到 S92(兜底 patch,mode=meow 跳过)。 +# SSH was moved to S84 so it is already online. # * rcS runs S??* (non-.sh) as fork-ed subprocesses, so `exec` here replaces # ONLY this script's process. # * 红线: 启动前清 /data/recovery_all、禁经 hiby_player.sh、禁 killall、 diff --git a/patch_v2/bootmenu/meow_bootmenu b/patch_v2/bootmenu/meow_bootmenu index e0e062ee3d96277eab9ada2dec41b28624a880fa..7fd84e7b305295a500ca4678ca836dfe99af461d 100755 GIT binary patch delta 40245 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%0Aojp$bR{+EW}q7 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 HRNnC)kHWU( delta 38707 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�Url(bp8XP?OO9wq&eG8o!mVfN>c5zH#0VD;sdL(x zhwL)UNALh zbJK&1QFMd$#LHikK`DW*!+P&B+w4qH51)7x4u{9NtaJr8lYpJxB^z<{JWEjeUJ-m8 zkDq7Ll#(9_z%&3g%g}j=rsq`*Tp&j8Yl3FStXs@Rf1~K4Hn98{M@8w#Mc-9oU5U(bdl4=UCqs=t)of)ZKAUD&NE|lA5=Gey#YweDMap&bI_zI>(o;)rJ!X zyI4&0Izdn{+(n+PL*GU6sNsF|2+x|=Ys&f34U&K9MP>&71{@_OCGMwEu=fI+1`E8J z1h4iQIQRZc>k(g-2Y4_F5?G#8=P1$5XFv?-b)lLcHG&Q!$k`Rq%ZqhTbBiUx(OuHA zWw(JN&H!M>ZnSlf!|8ja!{rOX7OmH{i8JpLgvnSSda`_LjV`4~OnGY~8elIq_Aq<2)~08Tq+>WLguV5KPHd?cQhJgZ)&OoB|%!x9XAH3ON0rEFt== z5YdOeO#>9s*I62@X+v8dDIYn9PAd6=AwwS?-9sNWD$Tr(Q$(YT3UiSL!Y9x_0jAN3<-j-Tp~q>DO_`xM4ZE&W zny5;{z;#mXa-#DKmT7+7DYS4Cy1pR8=|+ikO@ddab38a5vaw()JcX7{mdxE55kAG5 zDgDU0M3Ji1?OK<)H!H%$tBs)B2;43Mo7Vu82A9;Vt?U5}WMKhq7tt>Xf?Zof-2YvU zTg8Bg{dIf@mF9&4=-bZnMbw2WY$h*W%<*bFRlMJqE#~4Sf-fv%iWj~b@x^Riu8~e) z-vt)0Y*`_Y3GMw9m@BlQQ7msrBbtM54aj89J98t7xMd~B7s)DE-ombbQ66hpE9CUa zE?$Sueu@m!&kZ8o2B4&FlxFOczy_N*PVXRHeVfp8li8Kcf<_bX>Syu1e@jG%Qu`}G z(s!5kt>~FRF|mzf{dpR!&D#WV`~u?F0+?iiYSV#z5$#s_lCYAZ+`L8uG*o^IOpEqM zM7Yb()vdQ#BEy1V=2F9ppa3vuduiblJeye*=K1L$kP3>|*e zc9-@q$bJWHPeCpoxW^V@a0+$VueB`1@_X#Cnmo*s84tZ9McX@R>1-FS(6XlCQDj{= z;NKoG!25KdZQhTzqwPK&wn;BkyYAD)sZv!am4q+SGUzWv&j?X#cA)J`maio36hw~Y z6T!xIV%?YI=fKX0Zdg_%S6mUthqN)UBJ_>2F`o3Ui7k`W*qZMMEPumC^dU%`&)^Dl6`>H+W8&-t95!cJLlMqI%8X2Jn`NnazZbGA;Lj zC5X;R&17AT7Q1Hm~N?Uz{^kFShO z3r{ryuMVuSki-v3pjro5EMVg(Md|p53mb2-01u7J(qOAJ*v_Y6kS;?KAr~xvP@T_0 zV-!!V(v97wv!~_LAxjO?gGr*yt}5YCfu2;>SL?$eq97P%PJTmgSt=&7=OKNxeGQV$ zHHVF24K;?I%=q-NrF1NIKr&eWgmn%GLaK_MuR{-CRxde9QelZ$b{=kRkS%!*3cQ4w z;KGDdu<`5$NC3((0FoIh0Usqi=8GyWnasA&o2&W04V_}k!Se4F0B)CEPny-{~X zTsY?(e=L!LV*s)0KpJ=%AJN-Oo`D%^@ryM3xZoQwi@Q$h!qoG9W5k}E2?LBb>&;}i z6x&?j=8h(fQFVL=lA@G>CNVNhn_^{4vj7ws*C-P=W8Ff?R}xQ&wk)|L#GjF}?Tf(7 zvGx{QrFJfce)e3&^FmO*W5mQOo!+v1aA211gciG;9uP^ m7pNSgGPHh+WUy;NB diff --git a/patch_v2/bootmenu/meow_bootmenu.c b/patch_v2/bootmenu/meow_bootmenu.c index 70ab59c..191b092 100644 --- a/patch_v2/bootmenu/meow_bootmenu.c +++ b/patch_v2/bootmenu/meow_bootmenu.c @@ -35,8 +35,12 @@ * ============================================================================ * WHY (设计见 BOOT_SELECTOR_DESIGN.md §3) * ============================================================================ - * - 开机最先显示选单(HibyOS / MeowOS),选择后 fork 播放器(HibyOS)或 - * exec MeowOS 主屏(MeowOS);HibyOS 模式热键(电源+音量减)杀播放器接管。 + * - 开机最先显示选单(HibyOS / MeowOS),选择后 fork 播放器(HibyOS)或进入 + * MeowOS 主屏;HibyOS 模式热键(电源+音量减)杀播放器接管。 + * - M2.83 launcher 监管迁移:MeowOS 主屏(meow_launcherd)生命周期已交 meow_sysui + * (S70 起)。故选 MeowOS 时——sysui 在 → 让 sysui 拉起/监管 launcher,本进程让位退出 + * (rcS 推进,S92 见 mode=meow 跳过播放器);sysui 不在 → 本进程照旧 exec launcher(兜底, + * 防开不了机)。选 HibyOS 时 sysui 在 → 让 sysui 停 launcher(不与播放器抢 fb0)。 * - 本二进制 = 选单本体(M0)。M0 只做二进制自检:SSH 推 /data 独立跑, * **不进启动链**(不碰 S90/S92/播放器/SSH 顺序)。 * @@ -898,8 +902,81 @@ static int sim_confirm_done = 0; static int sim_hk_done = 0; static int sim_hotkey_strict = 0; /* M2.14:--sim-hotkey-strict = 让 sim 热键走武装延迟/忙判门(测 pending) */ +/* ====================================================================== + * M2.83 launcher 监管迁移:launcher 生命周期由 bootmenu 移交给 meow_sysui + * - MeowOS 模式:sysui 在 → 让 sysui 拉起/监管 launcher,bootmenu 让位不 exec + * (rcS 推进 → S92 见 mode=meow 跳过播放器);sysui 不在 → bootmenu 照旧 exec launcher(兜底)。 + * - HibyOS 模式:sysui 在 → 让 sysui 停 launcher(会话级 user_stopped,不自动重启),避免与播放器抢 fb0。 + * 双保险:sysui 拉 + launcher 崩 sysui 重启(restart=always);bootmenu 仅在 sysui 不在时兜底。 + * ====================================================================== */ +static int sysui_active(void) +{ + /* sysui daemon 是否在跑:/tmp/meow_sysui.pid 存活,且 /proc//comm=meow_sysui(防 pid 复用误判) */ + FILE *f = fopen("/tmp/meow_sysui.pid", "r"); + if (!f) return 0; + long pid = -1; + int n = fscanf(f, "%ld", &pid); + fclose(f); + if (n != 1 || pid <= 1) return 0; + if (kill((pid_t)pid, 0) != 0) return 0; /* ESRCH → 不在 */ + char comm[64] = {0}, path[64]; + snprintf(path, sizeof path, "/proc/%ld/comm", pid); + int fd = open(path, O_RDONLY); + if (fd >= 0) { + ssize_t r = read(fd, comm, sizeof comm - 1); + close(fd); + if (r > 0) { + char *nl = strchr(comm, '\n'); + if (nl) *nl = '\0'; + if (strncmp(comm, "meow_sysui", 10) != 0) return 0; + } + } + return 1; +} + +static void sysui_cmd_launcher(const char *op) +{ + /* 调 /usr/bin/meow_sysui start|stop launcher(best-effort:sysui 缺失/失效 → 无害退出)。 + * 等 CLI 返回(CLI 连 socket 快速回),避免僵尸;失败不致命。 */ + pid_t pid = fork(); + if (pid < 0) { + fprintf(stderr, "bootmenu: sysui %s launcher: fork failed: %s\n", op, strerror(errno)); + return; + } + if (pid == 0) { + execl("/usr/bin/meow_sysui", "/usr/bin/meow_sysui", op, "launcher", (char *)NULL); + _exit(127); /* sysui 缺失/不可执行 → 无害退出(fallback 路径会用本地 exec) */ + } + int st = 0; + (void)waitpid(pid, &st, 0); +} + +static int enter_meow_launcher(void) +{ + /* MeowOS 决策点:返回 0=已让位(launcher 由 sysui 继续管理,调用方正常退出/让 rcS 推进); + * 否则已 exec launcher(本函数不返回)。 */ + if (sysui_active()) { + fprintf(stderr, "bootmenu: sysui owns launcher -> hand off (start via sysui)\n"); + fflush(NULL); + sysui_cmd_launcher("start"); + return 0; + } + /* 回退:sysui 不在 → bootmenu 照旧 exec launcher(防开不了机) */ + fprintf(stderr, "bootmenu: no sysui -> exec %s (fallback)\n", LAUNCHER_BIN); + fflush(NULL); + execv(LAUNCHER_BIN, (char *[]){ "meow_launcherd", NULL }); + fprintf(stderr, "bootmenu: exec launcher failed: %s\n", strerror(errno)); + _exit(127); +} + static void fork_hiby_player(void) { + /* M2.83 迁移:HibyOS 模式下 launcher 不应与播放器抢 fb0——sysui 在则让 sysui 停 launcher */ + if (sysui_active()) { + fprintf(stderr, "bootmenu: HibyOS selected -> stop launcher via sysui\n"); + fflush(NULL); + sysui_cmd_launcher("stop"); + } pid_t pid = fork(); if (pid < 0) { fprintf(stderr, "fork player failed: %s\n", strerror(errno)); return; } if (pid == 0) { @@ -1278,13 +1355,17 @@ static void bootmenu_supervisor(int player_pid) } player_alive = 0; } - /* 播放器已死:面板已在确认框点亮(brightness=saved)。写 mode=meow, - * 然后 exec MeowOS 基础主屏(接管 fb0 画 buffer0+pan0)。 */ + /* 播放器已死:面板已在确认框点亮(brightness=saved)。写 mode=meow。 + * M2.83 迁移:MeowOS 基础主屏(launcher)已交 sysui 托管——sysui 在则让 sysui + * 拉起 launcher(本 supervisor 让位退出,rcS 推进,S92 见 mode=meow 跳过播放器); + * sysui 不在则照旧 exec launcher(兜底)。 */ write_mode(0); - fprintf(stderr, "supervisor: exec /usr/bin/meow_launcherd\n"); - fflush(NULL); - execv(LAUNCHER_BIN, (char *[]){ "meow_launcherd", NULL }); - fprintf(stderr, "supervisor: exec launcher failed: %s\n", strerror(errno)); + if (enter_meow_launcher() == 0) { + fprintf(stderr, "supervisor: launcher handed off to sysui, supervisor exits\n"); + fflush(NULL); + _exit(0); + } + /* 不可达:enter_meow_launcher 在无 sysui 时已 exec launcher(不返回) */ _exit(127); } else { /* ===== 否 / -1:留在 HibyOS ===== @@ -1528,15 +1609,14 @@ static int run_menu(int sim_sel, int do_fork) fb_restore_player_frame(); fb_close(); /* M2(--fork):HibyOS→fork 播放器(父进程转监督者,阻塞 rcS); - * MeowOS→exec meow_launcherd(MeowOS 基础主屏,接管 fb0)。 */ + * MeowOS→sysui 在则让 sysui 拉起 launcher 并让位(rcS 推进,S92 见 mode=meow 跳过播放器); + * sysui 不在则 exec meow_launcherd(MeowOS 基础主屏,接管 fb0;兜底)。返回=已让位。 */ if (do_fork) { if (choice == 1) { fork_hiby_player(); /* 父进程陷入 supervisor,永不返回 */ } else { set_boot_default_meow(); /* Bug 1:选 MeowOS → 持久为开机默认 */ - execv(LAUNCHER_BIN, (char *[]){ "meow_launcherd", NULL }); - fprintf(stderr, "bootmenu: exec launcher failed: %s\n", strerror(errno)); - _exit(127); + enter_meow_launcher(); /* 让位返回 0,或 exec launcher(不返回) */ } } return 0; /* M0/M1(无 --fork):选单退出,rcS 推进 */ @@ -1556,7 +1636,8 @@ static void usage(void) " --boot-only 跳过选单,直接取默认(boot_default)\n" " --timeout-ms N 覆写超时毫秒(测试钩子)\n" " --fork M2 开启:选 HibyOS 时 fork 播放器并转监督者;\n" - " 选 MeowOS 时 exec /usr/bin/meow_launcherd(默认关)\n" + " 选 MeowOS 时 sysui 在→让 sysui 拉起 launcher(本进程让位退出),\n" + " 否则 exec /usr/bin/meow_launcherd(默认关)\n" " --sim-hotkey N 监督者启动 N ms 后模拟热键(M2 真机 verif)\n" " --sim-confirm Y|N 确认框打开 2s 后自动答 Y=是 / N=否\n" " --version 打印版本并退出\n", @@ -1617,9 +1698,7 @@ int main(int argc, char **argv) if (choice == 1) fork_hiby_player(); else { set_boot_default_meow(); /* Bug 1:选 MeowOS → 持久为开机默认 */ - execv(LAUNCHER_BIN, (char *[]){ "meow_launcherd", NULL }); - fprintf(stderr, "bootmenu: exec launcher failed: %s\n", strerror(errno)); - _exit(127); + enter_meow_launcher(); /* sysui 在→让位返回 0;否则 exec launcher(不返回) */ } } return 0; diff --git a/patch_v2/meow_sysui/S70meow_sysui b/patch_v2/meow_sysui/S70meow_sysui index 93a6c54..5a4e76b 100644 --- a/patch_v2/meow_sysui/S70meow_sysui +++ b/patch_v2/meow_sysui/S70meow_sysui @@ -4,12 +4,13 @@ # # 位置:**最早**(早于 S84ssh / S89meow_svcd / S90meow_bootmenu)。 # * 定案启动链:meow_sysui 先起(S70)→ meow_svcd(由 sysui 拉起)→ 各项服务子进程。 -# * S90meow_bootmenu 里 `exec /usr/bin/meow_bootmenu --fork` 会阻塞 rcS,故任何 S90 之后 -# 的 init.d 都不执行——sysui 必须排在 S90 之前(S70 满足)。 +# * S90meow_bootmenu `exec /usr/bin/meow_bootmenu --fork`:HibyOS 模式 fork 播放器并转监督者, +# 阻塞 rcS;MeowOS 模式在 sysui 托管下让位退出(rcS 推进,S92 见 mode=meow 跳过播放器)。 +# 故 sysui 必须排在 S90 之前(S70 满足)——launcher 已在 S70 由 sysui 拉起。 # # 职责:监管系统进程(launcher / meow_svcd / meow_mdns / …),一个崩只拉起它、绝不级联。 # 本脚本只做三件事: -# 1) 预置板载监管配置 /usr/data/meow/sysui.d/*.conf(首启建,已存在不覆盖); +# 1) 预置板载监管配置 /usr/data/meow/sysui.d/*.conf(首启建;除 launcher.conf 迁移升级外已存在不覆盖); # 2) 拉起 meow_sysui(默认后台 daemon); # 3) stop 时优雅停 sysui(其会 SIGTERM 被监管的 svcd 并收尸)。 # @@ -39,24 +40,37 @@ EOF chmod 644 /usr/data/meow/sysui.d/svcd.conf echo -n "provisioned svcd.conf " fi - # launcher 当前由 S90meow_bootmenu 的 exec 拥有生命周期;待 bootmenu 让位后交 sysui。 - # 此处仅登记(enabled=0),避免双重拉起。mdns 是 svcd 的服务子进程,由 svcd 负责, - # 本阶段不直接监管(或作兜底条目,默认关)。 + # launcher 监管(M2.83 迁移):已交 sysui 托管——bootmenu 在 MeowOS 模式让位(sysui 在则 + # 不再 exec launcher,lifecycle 归 sysui:崩→sysui 按 restart=always 重启;bootmenu 仅在 + # sysui 不在时兜底 exec)。故此处 enabled=1。检测到旧版(enabled=0,bootmenu 时代)→ 升级。 + # mdns 是 svcd 的服务子进程,由 svcd 负责,本阶段不直接监管(或作兜底条目,默认关)。 + LAUNCHER_UPGRADE='' if [ -f /usr/data/meow/sysui.d/launcher.conf ]; then - echo -n "launcher.conf exists " - else + if grep -qE '^[[:space:]]*enabled[[:space:]]*=[[:space:]]*0([[:space:]]|#|$)' /usr/data/meow/sysui.d/launcher.conf; then + LAUNCHER_UPGRADE=1 + else + echo -n "launcher.conf exists " + fi + fi + if [ -n "$LAUNCHER_UPGRADE" ] || [ ! -f /usr/data/meow/sysui.d/launcher.conf ]; then cat > /usr/data/meow/sysui.d/launcher.conf <<'EOF' -# 预留:launcher(系统 UI 渲染宿主)。现网由 S90meow_bootmenu exec 拥有 → 启用前需 bootmenu 让位。 -# 真把 launcher 交给 sysui 监管后,把 enabled=1(并停用 bootmenu 的 exec launcher 路径)。 +# meow_sysui 监管条目:launcher(系统 UI 渲染宿主)。系统进程总监 → launcher 被监管。 +# 语义同 services.d:key=value,'#'/';' 注释,未知键忽略。无 trigger——enabled=1 即常驻监护。 +# M2.83 launcher 监管迁移:bootmenu 在 MeowOS 模式让位(sysui 在则不 exec launcher), +# launcher 生命周期归 sysui:崩→sysui 按 restart=always 重启;bootmenu 仅在 sysui 不在时兜底。 exec=/usr/bin/meow_launcherd args= -enabled=0 +enabled=1 restart=always backoff_ms=3000 max_restart=5 EOF chmod 644 /usr/data/meow/sysui.d/launcher.conf - echo -n "provisioned launcher.conf " + if [ -n "$LAUNCHER_UPGRADE" ]; then + echo -n "upgraded launcher.conf (enabled 0->1) " + else + echo -n "provisioned launcher.conf " + fi fi else echo -n "skip provision (/usr/data/meow not writable) " diff --git a/patch_v2/meow_sysui/meow_sysui.md b/patch_v2/meow_sysui/meow_sysui.md index 4281dff..55cd63f 100644 --- a/patch_v2/meow_sysui/meow_sysui.md +++ b/patch_v2/meow_sysui/meow_sysui.md @@ -16,9 +16,10 @@ - **只监护自己 fork+exec 出来的进程**:`waitpid` 需为父进程,这是「一个崩只拉起它」的可靠前提; sysui **不做**「adopt 外部 pid」式监护(那只 probe `kill(pid,0)`、无法真正 waitpid/重启)。 -- 要监管什么由配置决定;**默认配置只开启 `meow_svcd`**(符合定案「sysui 总监 → svcd 被监管 → - 服务为 svcd 子进程」)。`launcher` 当前由 S90meow_bootmenu 的 `exec` 拥有,配置中登记但 - `enabled=0`(避免双重拉起;真正移交需 bootmenu 让位,见 §6)。 +- 要监管什么由配置决定;**默认配置开启 `meow_svcd` + `meow_launcherd`**(`launcher` 于 M2.83 + launcher 监管迁移落地:由 S70 预置 `launcher.conf` 且 `enabled=1`,bootmenu 在 MeowOS 模式让位)。 + bootmenu 仅在 sysui 不在时兜底 exec launcher;HibyOS 模式下 bootmenu 会让 sysui 停 launcher + (不与播放器抢 fb0)。详见 §6。 - `meow_mdns` 是 svcd 的服务子进程(svcd 负责 spawn/restart),sysui 本阶段**不直接监管**它 (避免与 svcd 双重管理);`§10.1` 的「mdns 崩→svcd 拉起(或 sysui 兜底)」——兜底可后续 在 `sysui.d/` 加一条 meow_mdns 条目实现(默认关)。 @@ -111,9 +112,14 @@ meow_sysui pid ## 6. 迁移说明 / 边界(诚实标注) -- **launcher 监管**:现网 launcher 由 `S90meow_bootmenu` 的 `exec` 拉起(bootmenu 拥有其生命周期)。 - sysui **本阶段不拉起 launcher**(`sysui.d/launcher.conf` 默认 `enabled=0` + 注释); - launcher 真正归 sysui 需 bootmenu 让位(后续迁移)。sysui 代码完全支持加一条 launcher 条目即可。 +- **launcher 监管(✅ M2.83 已落地)**:launcher 生命周期已由 `S90meow_bootmenu` 的 `exec` 移交 + sysui。`sysui.d/launcher.conf` 现 `enabled=1`(`restart=always`/`backoff_ms=3000`/`max_restart=5`)。 + - sysui 在 S70 拉起并监管 launcher(崩→按策略重启,稳定 30s 清零次数,超 max_restart→FAILED 停)。 + - bootmenu(`meow_bootmenu.c`)MeowOS 模式:sysui 在 → `meow_sysui start launcher` + 让位退出 + (不 exec launcher);sysui 不在 → bootmenu 照旧 exec launcher(兜底,防开不了机)。 + - bootmenu HibyOS 模式:sysui 在 → `meow_sysui stop launcher`(会话级 user_stopped)再 fork 播放器, + 避免 launcher 与播放器抢 fb0。 + - 已部署设备兼容:S70 检测旧 `launcher.conf` `enabled=0` → 升级为 `enabled=1`。 - **mdns 监管**:由 svcd 负责;sysui 兜底条目默认关。 ## 7. 编译 @@ -125,6 +131,8 @@ meow_sysui pid ## 后续(不在本阶段) -- launcher 正式移交 sysui 监管(依赖 bootmenu 让位)。 - `MeowOS/sysui.d/` TF 双源扩展(复用 svcd 板载优先语义)。 - 与 meow_watchdog / 崩溃捕获的职责对齐(见 WORK_STATE SDK 缺失清单 #5)。 +- launcher 已在 S70 由 sysui 拉起(bootmenu 让位,见 §6);**真机待验证**:①MeowOS 模式 bootmenu + 让位后 rcS 推进到 S92 是否稳定跳过播放器 ②HibyOS 模式 `sysui stop launcher` 与播放器 fork 的 + 短暂共存无 fb0 冲突 ③launcher 在 S70 早启动(先于选单)与 boot_menu=1 选单的 fb 共存。