WIP checkpoint (M2.53→M2.54 工作区): 字体归MeowOS/fonts+SDK跟随+多字体(进行中)+boot板载+状态栏28+亮度/音量修复+文档刷新

This commit is contained in:
MeowOS Dev committed 2026-08-30 03:27:24 +08:00
1 parent 245bee2946
commit 064635b3c4
44 files changed
+2834 -308

No files matched your search

+56
View File
@@ -51,6 +51,62 @@ void meow_font_drawtext(uint16_t *fb, int x, int y, const char *utf8,
void meow_font_drawtext_outline(uint16_t *fb, int x, int y, const char *utf8,
uint16_t color, uint16_t outline_color, int ol, int size);
/* ---- 字符分类器(multi_font 逐字符路由的依据;按 codepoint 归类)---- */
typedef enum {
MEOW_FCLS_CJK = 0, /* CJK 表意文字 + 扩展 / 兼容表意(U+3400-4DBF/4E00-9FFF/20000-2FA1F/…) */
MEOW_FCLS_CJK_SYM = 1, /* 全角/中文标点(U+3000-303F、U+FF00-FFEF、U+2014/2018-201D/2026、U+00B7) */
MEOW_FCLS_NUM = 2, /* 0-9 */
MEOW_FCLS_LATIN = 3, /* A-Za-z(基本拉丁字母) */
MEOW_FCLS_LATIN_SYM = 4, /* 半角标点(ASCII 非字母数字:空格/括号/引号/运算符/控制符) */
MEOW_FCLS_OTHER = 5, /* 其他(假名/韩文/他文字/扩展区等) */
MEOW_FCLS_COUNT = 6
} meow_fclass;
/* 把一个 codepoint 归类为上述之一。 */
int meow_font_class_of(uint32_t cp);
/* 校验码位是否在「将用于渲染它的字库」中存在字形(1=有,0=缺字/字库未加载→渲染为豆腐)。
* multi_font 开启时按分类路由到对应字库,关闭时仅查全局字库(slot 0)。 */
int meow_font_has_glyph(uint32_t cp);
/* ---- 多字体(MEOW_FCLS* 逐字符路由)----
* 槽位模型:slot 0 = meow_font_load() 装载的全局/默认字库(font.file);额外字库经
* meow_font_load_slot() 装载后,用 meow_font_set_class_slot() 把某字符类指到某一槽。
* 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) */
int meow_font_load_slot(int slot, const char *png_path, const char *idx_path); /* 0=成功,负值失败 */
void meow_font_unload_slot(int slot);
void meow_font_enable_multifont(int on); /* 0=单字体(默认);1=逐字符分类路由 */
void meow_font_set_class_slot(meow_fclass cls, int slot); /* slot 越界/载入失败 → 该类别回退 slot 0 */
/* 一个字符类的候选字库路径(绝对路径,已由调用方按卡根解析;png=NULL/空 = 跟随全局)。 */
typedef struct {
const char *png;
const char *idx;
} meow_font_class_cfg;
/* 依据开关 + 各类候选配置多字体:同 png 路径去重(含与全局相同 → 直接用 slot 0),
* 独立字库数(含全局)> MEOW_MAX_SLOTS 时截断并置 *truncated=1(此前已装载的保留)。
* 返回 0 成功,负值失败;*distinct = 实际装载的不同字库数(含全局)。 */
int meow_font_config_multifont(int on,
const char *global_png, const char *global_idx,
const meow_font_class_cfg cfg[MEOW_FCLS_COUNT],
int *distinct, int *truncated);
/* ---- MeowPanel 预留:TF 卡字体库 .zip 导入/导出(TODO,未实现)----
* 当前仅声明占位,供 MeowPanel 后续接入;不实现、不接 zip 库。
* 调用方约定(待实现):导入=校验 .zip 内含 font.png+font.idx+font.json 三件套,
* 校验通过后解压到 TF 卡 MeowOS/fonts/<family>/(family 取自 font.json 的 family 或 zip 名,
* 冲突时覆盖需先确认);导出=把 MeowOS/fonts/<family>/ 三件套打包为 <out_zip>。
* 目录布局与 font.json schema 见 docs/MEOWOS_SPEC_REF.md §1.2/§5.2。
* 返回码:MEOW_OK=0 成功,负值错误码(沿用本模块失败负值约定)。 */
int meow_font_import_zip(const char *zip_path); /* TODO:MeowPanel */
int meow_font_export_zip(const char *family, const char *out_zip); /* TODO:MeowPanel */
#ifdef __cplusplus
}
#endif
Binary file not shown.
+596 -77
View File
@@ -1,12 +1,25 @@
/* meow_launcherd.c — R3 Pro II Plan B zero-patch app launcher daemon.
*
* M2.53 亮度接管修正(开机/切到 MeowOS 亮度被重置为 25/16):
* - 根因:HibyOS 播放器运行/恢复画面会把它自己的背光下限(16/25)写进 sysfs,
* MeowOS 主屏接管时又以 read_brightness() 读到的该值当「当前亮度」
* (saved_brightness / meow_saved_brightness),从不重应用用户 display.brightness,
* 故用户设的低亮度(<25,最低到 1)被覆盖。
* - 修法:run_menu 主屏接管(had_player==0,播放器已死=真接管)时调
* apply_user_brightness():读 settings display.brightness(经新导出的
* meow_settings_brightness_clamped(),clamp 下限 brightness_min 默认 1、上限 100)
* → write_brightness 强写回,覆盖播放器残留的 16/25。
* - 边界:播放器会话期间的覆盖菜单(had_player==1)不压回——播电影时亮度归播放器管;
* ensure_backlight/restore_backlight 仍用 saved_brightness(覆盖会话=播放器真实值),
* 不改为用户值,避免菜单关闭瞬间抢播放器背光。
*
* M2.46-2 状态栏 5 问题(2026-08-29):
* ① 亮度图标主题色(on_surface_variant 灰 → on_surface 主色)+ 拖动亮度滑条时状态栏百分比实时跟:
* 根因 s_commit brightness 分支只 write_brightness 未同步 g_sys.brightness/未重绘;补
* g_sys.brightness=v + paint_sb_right()(与随后 meow_sys_refresh 从 sysfs 回读一致不打架)。
* ② 右簇音/电顺序互换:最右由「音量」改「电量」(绘制顺序先电量后音量,右→左:电→音→蓝牙→WiFi→亮度)。
* ③ 图标视觉对齐:sb_blit_icon 按 ink 边界居中 + 主图标等比等大(SB_ICON_INK=24),音量不再「小一圈/
* 下边距」;ink 垂直中心对齐状态栏文字中心(g_sb_icon_cy=SB_ANCHOR_Y+字行高/2),图文同中线。
* ③ 图标视觉对齐:sb_blit_icon 按 ink 边界居中 + 主图标等比等大(SB_ICON_INK=16),音量不再「小一圈/
* 下边距」;ink 垂直中心对齐状态栏文字中心(g_sb_icon_cy=SB_ICON_CY=14,见 sb_text_y),图文同中线。
* ④ WiFi 图标随信号分档:新增 wifi_signal_level() 读 /proc/net/wireless wlan0 level(dBm),
* ≥-60 强 / [-60,-75) 中 / <-75 弱,选 SB_ICON_WIFI_STRONG/MEDIUM/WEAK(新位图追加进 sb_icons.h);
* 强弱档保留自然 ink(少一格即语义),满格/强档走等大;wifi_on 为假不画(保持现状)。
@@ -765,15 +778,25 @@ static void draw_text(int x, int y, const char *s, int scale, uint32_t color)
/* ================================================================== */
/* M2.6 meow_font 集成(agent-28 字体子系统,font/meow_font.a + libz.a)*/
/* 字库放 TF 卡 MeowAPPs/fonts/font.png+font.idx(或固件 /usr/share/ */
/* 字库放 TF 卡 MeowOS/fonts/default/font.png+font.idx(或固件 /usr/share/ */
/* meowos/font/);MeowOS 启动 ensure_card_mounted 后再 load,卡晚 */
/* 挂载时轮询重试。没加载成功时 ui_draw 回退 5x7 点阵(ASCII)。 */
/* ================================================================== */
#define UI_FONT_PNG_CARD MEOWAPPS "/fonts/font.png"
#define UI_FONT_IDX_CARD MEOWAPPS "/fonts/font.idx"
#define UI_FONT_PNG_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/font.png"
#define UI_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/font.idx"
#define UI_FONT_PNG_SYS "/usr/share/meowos/font/font.png"
#define UI_FONT_IDX_SYS "/usr/share/meowos/font/font.idx"
/* M2.5x:TF 卡多字体库 —— 字体目录 = <卡根>/MeowOS/fonts/<family>/(font.png+font.idx+font.json)。
* <卡根> 默认 /usr/data/mnt/sd_0(card_present() 判定;--fontscan 可指定宿主根以便单测)。 */
#define MEOW_FONTS_CFG "MeowOS/fonts"
#define FONT_PRESETS_MAX 17 /* 1 默认 + ≤16 卡内字体(防爆) */
static const char *font_presets[FONT_PRESETS_MAX]; /* SC_PRESET 值 = 卡根相对路径 */
static const char *font_preset_labels[FONT_PRESETS_MAX]; /* SC_PRESET 中文标签 */
static char font_preset_val_buf[FONT_PRESETS_MAX][320]; /* 值持久存储(指向上面指针数组) */
static char font_preset_label_buf[FONT_PRESETS_MAX][64]; /* 标签持久存储 */
static int font_presets_n = 0; /* 运行时动态计数(默认=1,仅"默认字体") */
static int mfont_ready = 0;
static int mfont_forced = 0; /* font.file 变更→强制真实(un)load,跳过 SDK 复用捷径 */
/* 24-bit RGB → RGB565(与 put_px/meow_font 一致) */
static uint16_t rgb565(uint32_t c)
@@ -787,23 +810,154 @@ static void meow_font_bind_fb(void)
meow_font_set_fb(fb_xres, fb_yres, fb_line);
}
/* 尝试加载字库(卡上 + 固件);成功返回 1。
/* 把 font.file 存的「卡根相对/绝对」路径解析为设备绝对路径。
* 相对路径以 /usr/data/mnt/sd_0/ 为卡根(与 MEOWAPPS 同源);绝对路径原样。
* 返回 0 成功,-1 参数无效。 */
static int meow_font_resolve_path(const char *ref, char *out, int olen)
{
if (!ref || !ref[0]) return -1;
if (ref[0] == '/') { snprintf(out, olen, "%s", ref); return 0; }
snprintf(out, olen, "/usr/data/mnt/sd_0/%s", ref);
return 0;
}
/* 解析 font.file 指向的 png + 派生同目录 idx(font.png → font.idx)。
* 仅当指向的 png 存在(access R_OK)返回 0;否则 -1(交由回退链)。
* 返回 0:out_png/out_idx 已填设备绝对路径。 */
static int meow_font_resolve_selected(char *out_png, int png_len, char *out_idx, int idx_len)
{
char ref[MEOW_SET_MAX_LEN];
if (meow_settings_get_str("font.file", ref, sizeof ref) != 0) return -1;
if (meow_font_resolve_path(ref, out_png, png_len) != 0) return -1;
if (access(out_png, R_OK) != 0) return -1; /* 指向不存在 → 回退卡默认/固件 */
snprintf(out_idx, idx_len, "%s", out_png); /* 同目录 font.idx(标准三件套布局) */
size_t n = strlen(out_idx);
if (n > 4 && !strcmp(out_idx + n - 4, ".png")) {
out_idx[n-4] = '.'; out_idx[n-3] = 'i'; out_idx[n-2] = 'd'; out_idx[n-1] = 'x'; out_idx[n] = 0;
}
return 0;
}
/* 尝试加载字库;成功返回 1。优先级(M2.5x):
* 1) font.file(设置键,真正接管加载;相对路径按卡根解析)→ 存在且能 load 即用;
* 2) 卡 MeowOS/fonts/default/font.png(默认字库);
* 3) 固件 /usr/share/meowos/font/font.png。
* A-1/A-2:SDK meow_ui_init() 已用同一 meow_font.o 加载过(可能成功也可能未成功)。
* 若 SDK 侧 g_mfont_ready==1(已加载),直接复用,避免重复 unload+reload;否则(卡晚挂载)
* 这里按原逻辑重试(launcher 的 mfont_ready 是独立标志,用于 CN/EN 文案选择与 ui_draw 判定)。 */
* 当 font.file 指向「默认卡字库」或未设置时复用 SDK 已加载状态,避免重复 unload+reload;
* 当 font.file 指向自定义字库时必须显式 load(SDK 只加载默认,不读 font.file)。
* mfont_forced=1(font.file 变更触发)时无条件真实 reload,跳过上述两条捷径。
* 全失败 → mfont_ready=0,ui_draw 回退 5x7 点阵(系统不死)。 */
static int meow_font_ensure(void)
{
if (mfont_ready) return 1;
if (meow_gfx_mfont_ready()) { mfont_ready = 1; return 1; } /* SDK 已加载,复用 */
if (mfont_ready && !mfont_forced) return 1;
mfont_forced = 0;
char fpng[320], fidx[320];
int have_sel = (meow_font_resolve_selected(fpng, sizeof fpng, fidx, sizeof fidx) == 0);
/* SDK 已加载且所选字库为「默认卡字库」(或未设置)→ 复用,避免重复解码。 */
if (meow_gfx_mfont_ready() && (!have_sel || !strcmp(fpng, UI_FONT_PNG_CARD))) {
mfont_ready = 1;
logmsg("meow_font: reuse sdk-loaded font");
return 1;
}
/* font.file 优先。 */
if (have_sel && meow_font_load(fpng, fidx) == 0) {
mfont_ready = 1;
logmsg("meow_font: loaded font.file %s (%d/%d sizes)",
fpng, meow_font_has_size(28), meow_font_has_size(20));
return 1;
}
if (have_sel)
logmsg("meow_font: font.file %s load failed, fallback", fpng);
/* 卡默认 → 固件。 */
if (meow_font_load(UI_FONT_PNG_CARD, UI_FONT_IDX_CARD) == 0 ||
meow_font_load(UI_FONT_PNG_SYS, UI_FONT_IDX_SYS) == 0) {
mfont_ready = 1;
logmsg("meow_font: loaded (%d/%d sizes)", meow_font_has_size(28), meow_font_has_size(20));
logmsg("meow_font: loaded fallback (%d/%d sizes)",
meow_font_has_size(28), meow_font_has_size(20));
return 1;
}
return 0;
}
/* 求「当前生效字体」的 png/idx 绝对路径 + family,供 launch_app 注入 MEOW_FONT_PNG/IDX/APP。
* 优先级(与 meow_font_ensure 一致):font.file(resolve_selected)→ 卡默认 MeowOS/fonts/default。
* family 取自 MeowOS/fonts/<family>/font.png 目录名;无法判定则 "default"。
* 返回 0 成功(out_png/out_idx/out_family 已填),-1 无卡/无可用字体(调用方跳过注入,
* SDK 自身会回退卡默认→固件→点阵)。 */
static int meow_font_effective_paths(char *out_png, int plen, char *out_idx, int ilen,
char *out_family, int flen)
{
char fpng[400], fidx[400];
if (meow_font_resolve_selected(fpng, sizeof fpng, fidx, sizeof fidx) == 0) {
snprintf(out_png, plen, "%s", fpng);
snprintf(out_idx, ilen, "%s", fidx);
const char *prefix = "/MeowOS/fonts/";
const char *p = strstr(fpng, prefix);
if (p) {
const char *q = p + strlen(prefix);
const char *end = strrchr(q, '/');
int rest = end ? (int)(end - q) : 0;
if (rest > 0 && rest < flen) { memcpy(out_family, q, rest); out_family[rest] = 0; }
else snprintf(out_family, flen, "default");
} else snprintf(out_family, flen, "default");
return 0;
}
/* 回退卡默认 MeowOS/fonts/default */
if (access(UI_FONT_PNG_CARD, R_OK) == 0) {
snprintf(out_png, plen, "%s", UI_FONT_PNG_CARD);
snprintf(out_idx, ilen, "%s", UI_FONT_IDX_CARD);
snprintf(out_family, flen, "default");
return 0;
}
return -1;
}
/* M2.5x 多字体:从 settings 读 display.multi_font + 6 类 font.class_*,把各自相对路径按卡根
* 解析为绝对路径,去重装载到 meow_font 槽位(同 family 去重 & ≤4 套上限由
* meow_font_config_multifont 统一处理)。仅当全局字库已装载(mfont_ready / slot0)时生效;
* 关或未装载 → 单字体(on=0),零额外开销。调用时机:启动 meow_font_ensure() 之后,以及
* 设置页变更 display.multi_font / font.class_* / font.file。 */
static void meow_font_reload_multifont(void)
{
static const char *cls_keys[MEOW_FCLS_COUNT] = {
"font.class_cjk", "font.class_cjk_sym", "font.class_num",
"font.class_latin", "font.class_latin_sym", "font.class_other" };
int on = 0;
meow_settings_get_bool("display.multi_font", &on);
char gpng[400], gidx[400], gfam[96];
if (meow_font_effective_paths(gpng, sizeof gpng, gidx, sizeof gidx, gfam, sizeof gfam) != 0)
return; /* 无全局字体:无法多字体 */
/* 各类候选(静态缓冲保活;config_multifont 同步读取,不持指针) */
static char cpng[MEOW_FCLS_COUNT][400], cidx[MEOW_FCLS_COUNT][400];
meow_font_class_cfg cfg[MEOW_FCLS_COUNT];
memset(cfg, 0, sizeof cfg);
for (int c = 0; c < MEOW_FCLS_COUNT; c++) {
char ref[MEOW_SET_MAX_LEN];
if (meow_settings_get_str(cls_keys[c], ref, sizeof ref) == 0 && ref[0]) {
char abs[400];
if (meow_font_resolve_path(ref, abs, sizeof abs) == 0 && access(abs, R_OK) == 0) {
snprintf(cpng[c], sizeof cpng[c], "%s", abs);
snprintf(cidx[c], sizeof cidx[c], "%s", abs);
size_t n = strlen(cidx[c]);
if (n > 4 && !strcmp(cidx[c] + n - 4, ".png")) {
cidx[c][n-4]='.'; cidx[c][n-3]='i'; cidx[c][n-2]='d'; cidx[c][n-1]='x'; cidx[c][n]=0;
}
cfg[c].png = cpng[c]; cfg[c].idx = cidx[c];
}
}
}
int distinct = 0, truncated = 0;
if (meow_font_config_multifont(on, gpng, gidx, cfg, &distinct, &truncated) != 0)
logmsg("multi_font: config failed -> single-font");
else if (truncated)
logmsg("multi_font: WARN >%d 套不同字体(会增内存),已按上限截断", distinct);
else if (on)
logmsg("multi_font: enabled, %d distinct fonts (global=%s)", distinct, gfam);
}
/* 统一文本绘制:字库就绪用 meow_font(真字体/中文),否则回退 5x7 点阵。
* size 为字库字号(28=状态栏/大标题,20=正文/按钮);回退时按 scale 近似。 */
static void ui_draw(int x, int y, const char *s, uint32_t color, int size)
@@ -822,6 +976,13 @@ static int ui_width(const char *s, int size)
return (int)strlen(s) * 6 * ((size >= 16) ? 2 : 1);
}
/* 文本高度(字库行高 or 回退 7×scale 估算),用于垂直居中 / 行高预算。 */
static int ui_text_height(int size)
{
if (mfont_ready) return meow_font_line_height(size);
return 7 * ((size >= 16) ? 2 : 1);
}
static int fb_open(void)
{
int fd = open(FB_DEV, O_RDWR);
@@ -1159,7 +1320,8 @@ struct meow_svc {
};
struct app {
char id[MEOW_APP_ID_MAX]; /* M2.52:app_id=卡上目录名 de->d_name(与注册表 g_accts 一致),供 MEOW_LOG_APP 注入 */
char id[MEOW_APP_ID_MAX]; /* M2.52:app_id=app.json 的 package(权威,缺省回退卡上目录名 de->d_name),供 MEOW_LOG_APP 注入 */
char package[MEOW_APP_ID_MAX]; /* 安卓化:app.json 的 package 字段(反域名权威标识;目录名=package),scan_apps 据此定 app_id */
char name[17]; /* ASCII display name */
char exec[129]; /* absolute path */
int ok; /* exec exists and X_OK */
@@ -1418,6 +1580,156 @@ static int ensure_card_mounted(void)
return 0;
}
/* ================================================================== */
/* M2.5x:TF 卡多字体库扫描 + preset 动态化 */
/* 字体目录 = <卡根>/MeowOS/fonts/<family>/,每套含三件套: */
/* font.png(预渲染灰度 PNG,bit depth 4|8)+ font.idx(MFNI v2 索引) */
/* + font.json(元数据 + 完整性校验)。缺任一件 → 该套不进列表 + 日志。 */
/* font.json schema(定版): */
/* { */
/* "family": "家族标识(同目录名)", // 信息用,不参与校验 */
/* "name": "下拉显示名", // 缺则回退目录名 */
/* "glyph_count": 10000, // 信息用 */
/* "size_list": [28,20], // 信息用 */
/* "bit_depth": 4, // 信息用(4|8) */
/* "sha256_png": "<64hex>", // font.png 校验 */
/* "sha256_idx": "<64hex>", // font.idx 校验 */
/* "source": "...", // 可选:来源 */
/* "license": "..." // 可选:许可 */
/* } */
/* 校验:sha256 不匹配仅 WARN 不硬失败(仍可选);缺三件套或 font.json */
/* 解析不出 name → 跳过 + 日志。仅启动/进设置页扫描一次(勿放热路径)。 */
/* ================================================================== */
/* 计算文件 sha256(走 busybox/宿主 sha256sum;输出小写 64hex)。返回 0 成功。 */
static int font_sha256_file(const char *path, char *out, int olen)
{
out[0] = 0;
/* 单引号转义防 shell 注入(路径含 ' 时安全;转义后超长则截断 → sha256sum 失败走 WARN) */
char esc[1024]; int j = 0;
for (const char *p = path; *p && j < (int)sizeof esc - 4; ) {
if (*p == '\'') { memcpy(esc + j, "'\\''", 4); j += 4; p++; }
else esc[j++] = *p++;
}
esc[j] = 0;
char cmd[1080];
snprintf(cmd, sizeof cmd, "sha256sum '%s' 2>/dev/null", esc);
FILE *f = popen(cmd, "r");
if (!f) return -1;
char line[160];
int ok = -1;
if (fgets(line, sizeof line, f)) {
int i = 0, k = 0;
while (line[i] == ' ' || line[i] == '\t') i++; /* 跳前导空白 */
while (line[i] && k < 64) {
char c = line[i++];
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
out[k++] = c;
else break;
}
out[k] = 0;
ok = (k == 64) ? 0 : -1;
}
pclose(f);
return ok;
}
/* 比较两个 sha256 十六进制串(大小写不敏感)。均为 64hex 才比对,否则视为不匹配。 */
static int font_sha_eq(const char *a, const char *b)
{
if (!a || !b || strlen(a) < 64 || strlen(b) < 64) return 0;
for (int i = 0; i < 64; i++) {
char ca = a[i], cb = b[i];
if (ca >= 'A' && ca <= 'F') ca += 32;
if (cb >= 'A' && cb <= 'F') cb += 32;
if (ca != cb) return 0;
}
return 1;
}
/* 解析 font.json:提取 name / sha256_png / sha256_idx(文件缺失或解析不出则留空)。 */
static void meow_font_json_probe(const char *json_path, char *name, int nlen,
char *sha_png, int slen, char *sha_idx, int xlen)
{
name[0] = 0; sha_png[0] = 0; sha_idx[0] = 0;
FILE *f = fopen(json_path, "r");
if (!f) return;
char buf[256];
while (fgets(buf, sizeof buf, f)) {
if (!name[0]) json_str(buf, "name", name, nlen);
if (!sha_png[0]) json_str(buf, "sha256_png", sha_png, slen);
if (!sha_idx[0]) json_str(buf, "sha256_idx", sha_idx, xlen);
}
fclose(f);
}
/* 探测一个 <dirpath>/<family> 套件是否合法:三件套齐全 → 解析 label + 校验 sha。
* 返回 1=进列表,0=跳过(缺组件/json 不可读)。sha 失败仅 WARN。 */
static int meow_font_probe_family(const char *dirpath, const char *family,
char *label, int llen)
{
char ppath[400], ipath[400], jpath[400];
snprintf(ppath, sizeof ppath, "%s/font.png", dirpath);
snprintf(ipath, sizeof ipath, "%s/font.idx", dirpath);
snprintf(jpath, sizeof jpath, "%s/font.json", dirpath);
if (access(ppath, R_OK) != 0 || access(ipath, R_OK) != 0 || access(jpath, R_OK) != 0) {
logmsg("fonts: skip '%s' (缺 font.png/font.idx/font.json)", family);
return 0;
}
char name[64] = "", jsha_png[80] = "", jsha_idx[80] = "";
meow_font_json_probe(jpath, name, sizeof name, jsha_png, sizeof jsha_png,
jsha_idx, sizeof jsha_idx);
snprintf(label, llen, "%s", name[0] ? name : family); /* 显示名:name 或回退目录名 */
/* 完整性校验:仅 WARN,不硬失败;该套仍标记可选。 */
char apng[80] = "", aidx[80] = "";
if (font_sha256_file(ppath, apng, sizeof apng) != 0)
logmsg("fonts: '%s' sha256(png) 不可用,跳过校验", family);
else if (jsha_png[0] && !font_sha_eq(apng, jsha_png))
logmsg("fonts: '%s' WARN sha256_png 不匹配(仍可选)", family);
if (font_sha256_file(ipath, aidx, sizeof aidx) != 0)
logmsg("fonts: '%s' sha256(idx) 不可用,跳过校验", family);
else if (jsha_idx[0] && !font_sha_eq(aidx, jsha_idx))
logmsg("fonts: '%s' WARN sha256_idx 不匹配(仍可选)", family);
logmsg("fonts: add '%s' (%s)", label, family);
return 1;
}
/* 扫描 <card_root>/MeowOS/fonts/ 下每个家族子目录,重建字体 preset 列表(默认 + 卡内合法套件)。
* 上限 FONT_PRESETS_MAX(1 默认 + ≤16 用户字体)。无卡/无目录时仅默认。 */
static void meow_font_scan_presets_root(const char *card_root)
{
font_presets_n = 0;
font_presets[0] = "MeowOS/fonts/default/font.png"; /* 默认字库 = MeowOS/fonts/default */
font_preset_labels[0] = "默认字体";
font_presets_n = 1;
char root[340];
snprintf(root, sizeof root, "%s/%s", card_root, MEOW_FONTS_CFG);
DIR *d = opendir(root);
if (!d) return; /* 无卡/无字库目录:仅默认 */
struct dirent *de;
while ((de = readdir(d)) && font_presets_n < FONT_PRESETS_MAX) {
if (de->d_name[0] == '.') continue;
char dirpath[400], label[64];
snprintf(dirpath, sizeof dirpath, "%s/%s", root, de->d_name);
if (meow_font_probe_family(dirpath, de->d_name, label, sizeof label) == 0)
continue; /* 缺组件/损坏 → 跳过该套 */
int i = font_presets_n;
snprintf(font_preset_val_buf[i], sizeof font_preset_val_buf[i],
"%s/%s/font.png", MEOW_FONTS_CFG, de->d_name);
snprintf(font_preset_label_buf[i], sizeof font_preset_label_buf[i], "%s", label);
font_presets[i] = font_preset_val_buf[i];
font_preset_labels[i] = font_preset_label_buf[i];
font_presets_n++;
}
closedir(d);
}
/* 设备默认卡根扫描(等价现状:无卡时 opendir 失败 → 仅默认字体)。 */
static void meow_font_scan_presets(void)
{
meow_font_scan_presets_root("/usr/data/mnt/sd_0");
}
/* wlan0 是否已联网(链路 up,与状态栏 WIFI 指示一致) */
static int wlan_connected(void)
{
@@ -1491,6 +1803,7 @@ static void meow_parse_app_json(FILE *f, struct app *a)
{
char buf[256];
char name[17] = "", exec[129] = "", icon[129] = "";
char package[MEOW_APP_ID_MAX] = "";
int fram = 0, immersive = 0;
struct app tmp;
memset(&tmp, 0, sizeof tmp);
@@ -1498,6 +1811,7 @@ static void meow_parse_app_json(FILE *f, struct app *a)
if (!name[0]) json_str(buf, "name", name, sizeof name);
if (!exec[0]) json_str(buf, "exec", exec, sizeof exec);
if (!icon[0]) json_str(buf, "icon", icon, sizeof icon);
if (!package[0]) json_str(buf, "package", package, sizeof package); /* 安卓化权威 app_id */
/* route.<id>=<chain>(activity 路由,M1) */
char *rp = strstr(buf, "route.");
if (rp && tmp.nroute < MEOW_MAX_ROUTES) {
@@ -1594,6 +1908,7 @@ static void meow_parse_app_json(FILE *f, struct app *a)
}
strncpy(a->name, name, 16);
strncpy(a->exec, exec, 128);
strncpy(a->package, package, sizeof a->package - 1);
a->framework = fram ? '1' : 0;
a->immersive = immersive ? '1' : 0;
strncpy(a->icon, icon, 128);
@@ -1627,10 +1942,14 @@ static int scan_apps(struct app apps[MAX_APPS])
memset(a, 0, sizeof *a);
memcpy(a, &a0, sizeof a0);
a->ok = (access(a->exec, X_OK) == 0);
/* M2.52:记 app_id=目录名(与注册表 g_accts 同步一致),launch_app 注入 MEOW_LOG_APP 用 */
strncpy(a->id, de->d_name, sizeof a->id - 1);
/* M1 路由注册表同步(app_id = 目录名 de->d_name) */
meow_acct_sync(de->d_name, a);
/* 安卓化:app_id=app.json 的 package(权威),缺省回退目录名 de->d_name;
* 两者都存在且不同则 WARN(正常情况二者相等)。launch_app 注入 MEOW_LOG_APP 用 */
const char *aid = a0.package[0] ? a0.package : de->d_name;
strncpy(a->id, aid, sizeof a->id - 1);
if (a0.package[0] && strcmp(a0.package, de->d_name) != 0)
logmsg("WARN: app '%s' package '%s' != dir '%s'", a0.name, a0.package, de->d_name);
/* M1 路由注册表同步(app_id = a->id,即 package 优先) */
meow_acct_sync(a->id, a);
n++;
}
closedir(d);
@@ -2269,18 +2588,17 @@ static void meow_sys_light(void)
* line 2: VOL nn% (left)
* M2.11:值全部取自 meow_sys_state 快照(g_sys),不再每帧读 amixer/sysfs;
* 音量 = settings audio.volume(非 CS43198 死镜像);蓝牙 = 实际 powered;
* WiFi = operstate 实际 up。纯黑背景 = menu_bg;分隔线 y=40。*/
* WiFi = operstate 实际 up。纯黑背景 = menu_bg;分隔线 y=28(栏高 40→28)。*/
/* ---- 状态栏固定完整格重绘(M2.17:修「整个状态栏部件脏区残留」)----
* 根因:M2.16 各部件/数字更新只把「当前内容包围盒」标为脏区并用它清底——旧内容超出新包围盒的
* 部分(时钟数字 '0' 底弧落于新 '1' 包围盒外、电量%/WiFi/BT 出现或消失、音量数字)未被重绘 →
* 残留;按音量键全屏刷新才清。修法:状态栏(0,0,480×40)用一条固定完整格:字段更新时整条清底 +
* 残留;按音量键全屏刷新才清。修法:状态栏(0,0,480×28)用一条固定完整格:字段更新时整条清底 +
* 重绘时钟/右侧 + 整条标脏(SDK back_buf 做最终态 → meow_ui_flush 原子 blit 整条,无中间态)。
* 任何旧内容残留被整条覆盖。480×40≈38KB 极小,每秒一 blit 无碍;分隔线 y=40 单独绘制不在此列。
* 任何旧内容残留被整条覆盖。480×28≈27KB 极小,每秒一 blit 无碍;分隔线 y=28 单独绘制不在此列。
* (逐字段固定格在 480px 宽 + 「蓝牙」CJK 全字段同现时与时钟格重叠,故取整条退化——§18.46。) */
#define SB_BAR_Y 0
#define SB_BAR_H 40 /* rows 0..39;分隔线在 y=40 不在此 */
#define SB_ANCHOR_Y 2 /* 字形升线锚点 y */
#define SB_FONT 28
#define SB_BAR_H 28 /* rows 0..27;分隔线在 y=28 不在此(40→28,用户定) */
#define SB_FONT 20 /* 状态栏字体 28→20(28px 栏放不下 28 号;垂直居中锚点见 sb_text_y) */
static int g_sb_clock_x, g_sb_clock_y, g_sb_clock_w, g_sb_clock_h;
static int g_sb_right_x, g_sb_right_y, g_sb_right_w, g_sb_right_h;
@@ -2363,13 +2681,21 @@ static const meow_ui_theme_t *s_ui_theme(void)
/* ---- 状态栏右簇图标化(MD3)---- 布局常量 */
#define SB_TGAP 6 /* 图标右缘 ↔ 文字间隙 */
#define SB_XGAP 10 /* 相邻两项(右→左)间隙 */
#define SB_ICON_INK 24 /* 主图标目标 ink 高(px):按 ink 边界等比缩放对齐,音量/亮度等图标视觉一致 */
#define SB_ICON_INK 16 /* 主图标目标 ink 高(px):24→16(28px 栏等比缩放,留上下边距) */
#define SB_CLK_MARGIN 14 /* 右簇左缘相对时钟右缘须留出的安全间距 */
/* 图标 ink 垂直中心线 = 状态栏文字垂直中心(机测 mfont28 数字在 SB_ANCHOR_Y=2 下中心 ≈ y=17)。
/* 图标 ink 垂直中心线 = 状态栏文字垂直中心(28px 栏文字按 (28-行高)/2 锚定后中心≈14,见 sb_text_y)。
* 图标 ink 对齐此线 → 与数字/文字同中线(修「图标比文字低」);如需微调只改这一个常量。 */
#define SB_ICON_CY 17
#define SB_ICON_CY 14
static int g_sb_icon_cy = SB_ICON_CY;
/* 状态栏文字垂直锚 y(行首升线顶):(栏高-行高)/2 把文字垂直居中于 28px 栏;
* 下限 1 防极端行高(>栏高)时算出负 y 溢出顶端(不越界优先)。 */
static int sb_text_y(void)
{
int y = (SB_BAR_H - ui_text_height(SB_FONT)) / 2;
return (y < 1) ? 1 : y;
}
/* 状态栏 MD3 图标 blit:把 sb_icons.h 的 32x32 单通道 alpha 位图画进状态栏(fb = SDK back_buf)。
* x = 图标槽左缘(槽宽 SB_ICON_SIZE);fg = 24-bit RGB 前景色(主题 on_surface / on_surface_variant)。
* M2.46-2 视觉对齐(音量图标小一圈/下边距、图标与文字垂直中心):
@@ -2436,7 +2762,7 @@ static int sb_r_place(int *xr, int icon, const char *txt, uint32_t col, int ink_
int tw = txt ? ui_width(txt, SB_FONT) : 0;
int x = *xr - (iconw + gap + tw);
if (icon >= 0) { sb_blit_icon(icon, x, col, ink_h); x += iconw + gap; }
if (txt) ui_draw(x, SB_ANCHOR_Y, txt, col, SB_FONT);
if (txt) ui_draw(x, sb_text_y(), txt, col, SB_FONT);
*xr -= (iconw + gap + tw) + SB_XGAP;
return *xr + SB_XGAP;
}
@@ -2489,8 +2815,8 @@ static void sb_redraw(void)
struct tm tm;
localtime_r(&now, &tm);
snprintf(timebuf, sizeof timebuf, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec);
ui_draw(8, SB_ANCHOR_Y, timebuf, sb_fg, SB_FONT);
/* 图标 ink 垂直中心 = 状态栏文字中心(SB_ICON_CY=机测 17):保证图标与数字/文字同中线。 */
ui_draw(8, sb_text_y(), timebuf, sb_fg, SB_FONT);
/* 图标 ink 垂直中心 = 状态栏文字中心(SB_ICON_CY=14,28px 栏文字中心):同中线。 */
g_sb_icon_cy = SB_ICON_CY;
/* 右簇 MD3 图标化(从右端 fb_xres-8 往左):[电量图标+%] [音量图标+%] [蓝牙图标] [WiFi图标] [亮度图标+%]。
* M2.46-2:音量/电量顺序互换(最右由音量改为电量);亮度由 on_surface_variant 改为主色 on_surface;
@@ -2538,10 +2864,10 @@ static void sb_redraw(void)
if (xr - bw > 8 + ui_width(timebuf, SB_FONT) + SB_CLK_MARGIN)
sb_r_place(&xr, SB_I_BRIGHT, bright, sb_fg, SB_ICON_INK); /* 亮度:图标+%(主色,与时钟不重叠才画) */
}
g_sb_clock_x = 4; g_sb_clock_y = SB_ANCHOR_Y;
g_sb_clock_x = 4; g_sb_clock_y = sb_text_y();
g_sb_clock_w = 12 + ui_width(timebuf, SB_FONT); g_sb_clock_h = SB_BAR_H;
g_sb_right_x = xr + SB_XGAP;
g_sb_right_y = SB_ANCHOR_Y; g_sb_right_w = (fb_xres - 8) - g_sb_right_x + 4;
g_sb_right_y = sb_text_y(); g_sb_right_w = (fb_xres - 8) - g_sb_right_x + 4;
g_sb_right_h = SB_BAR_H;
meow_dirty_add(0, SB_BAR_Y, fb_xres, SB_BAR_H); /* 整条标脏 */
}
@@ -2555,7 +2881,7 @@ static void draw_statusbar(void)
{
sb_redraw();
const meow_ui_theme_t *th = s_ui_theme(); /* M2.44 分隔线随主题 outline */
fill_rect(0, 40, fb_xres, 1, th->outline); /* separator(整条之外,单独绘制) */
fill_rect(0, SB_BAR_H, fb_xres, 1, th->outline); /* separator(整条之外,单独绘制;栏底=28) */
}
/* returns 0 on success (app ran and exited), -1 on launch failure */
@@ -2903,23 +3229,12 @@ static void sys_do_reboot(int cmd)
for (;;) usleep(1000); /* 防御:永不返回 */
}
/* 回HibyOS:写 /data/meow_boot_default(规格标记)+ meow.conf boot_default=1
* (bootmenu 读 meow.conf 定默认),再 reboot 回选单并默认 HibyOS。 */
/* 回HibyOS:写板载 /data/meow_boot_default=1(开机默认权威标记,O_TRUNC 单值、不写 TF 卡),
* 再 reboot 回选单并默认 HibyOS。meow.conf 不再写/承载 boot_default(bootmenu 以板载标记为准)。 */
static void set_boot_default_hiby(void)
{
char conf[256];
int fd;
fd = open("/data/meow_boot_default", O_WRONLY | O_CREAT | O_TRUNC, 0644);
int fd = open("/data/meow_boot_default", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd >= 0) { write(fd, "1\n", 2); close(fd); logmsg("pm: /data/meow_boot_default=1"); }
snprintf(conf, sizeof conf, "%s/meow.conf", MEOWAPPS);
fd = open(conf, O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd >= 0) {
if (write(fd, "boot_default=1\n", 15) < 0) logmsg("pm: write boot_default failed");
close(fd);
logmsg("pm: boot_default=1 appended to meow.conf");
} else {
logmsg("pm: meow.conf open failed: %s", strerror(errno));
}
}
/* 电源菜单(M2.6):2×2 网格,dirty 绘制消频闪;输入修复:
@@ -3265,6 +3580,20 @@ static volatile sig_atomic_t sigterm_flag = 0;
static long sim_exit_tap_ms = 0;
static int sim_exit_tap_done = 0;
/* M2.53:MeowOS 主屏接管时把亮度强制压回用户设置值(display.brightness,经
* meow_settings_brightness_clamped() —— 下限 brightness_min 默认 1、上限 100),
* 覆盖 HibyOS 播放器写入 sysfs 的背光下限(16/25)。仅应在「MeowOS 真正接管画面」
* (播放器已死、主屏常驻,即 run_menu 的 had_player==0)时调用;播放器会话期间的
* 覆盖菜单(had_player==1)不去抢亮度——尊重播放器背光(§红线)。 */
static void apply_user_brightness(void)
{
int v = meow_settings_brightness_clamped();
if (v <= 0) v = 60; /* 防御:clamp 后不可能 ≤0,仅兜底 */
write_brightness(v);
g_sys.brightness = v;
logmsg("apply user brightness (MeowOS takeover) -> %d", v);
}
/* real menu loop */
static void run_menu(struct app apps[MAX_APPS], int napps, int state,
long sim_tap_ms, long sim_exit_ms)
@@ -3306,8 +3635,9 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
fbfd = fb_open();
meow_font_bind_fb(); /* M2.6 字库绑定当前 fb 几何(宽/高/pitch) */
/* M2.6 加载字库(卡上 MeowAPPs/fonts/,卡片已挂载则该步成功;未挂载由下方轮询补载) */
/* M2.6 加载字库(卡上 MeowOS/fonts/default/,卡片已挂载则该步成功;未挂载由下方轮询补载) */
meow_font_ensure();
meow_font_reload_multifont(); /* M2.5x 多字体(读 settings display.multi_font + 6 类;关=单字体) */
/* M2.2:先判播放器是否存活。MeowOS 常驻主界面(播放器已死)→ 不 SIGSTOP、不备份
* 播放器帧,全程只自绘 fb0(buffer0 + pan0);仅当存在存活播放器(会话覆盖场景)才
* 冻结 + 备份,退出时恢复其帧。 */
@@ -3325,6 +3655,11 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
}
ensure_backlight(); /* only if the panel is dark, never yank brightness */
unblank_panel(); /* player frozen: it can no longer re-blank */
/* M2.53:MeowOS 主屏接管(播放器已死,had_player==0)时强制把亮度压回用户
* display.brightness 设定值。HibyOS 播放器运行/恢复画面会把它自己的背光下限
* (16/25)写进 sysfs;若以它为「当前亮度」,用户设的低亮度(<25)会被覆盖。
* 覆盖菜单(had_player==1,播放器会话中)不压回——播电影时亮度归播放器管。 */
if (!had_player) apply_user_brightness();
if (had_player) fb_backup_player(); /* only snapshot when a live player is overlaid */
draw_menu(apps, napps, state, 0, 0, ""); /* full menu into back_buf */
fb_norm_buf0(); /* ensure LCDC shows buffer0 (yoffset=0) */
@@ -3465,6 +3800,7 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
/* 卡在且已挂载(刚插入/重挂成功):补字库 + 重扫 · 仅真正变化才重绘 */
int was_nocard = (state == 1);
if (!mfont_ready && meow_font_ensure()) { dirty = 1; logmsg("hotplug: font loaded late"); }
if (mfont_ready) meow_font_reload_multifont(); /* M2.5x:卡挂载后重配多字体(此前无全局可配) */
if (was_nocard || state != 0 || napps == 0) {
struct app fresh[MAX_APPS];
int fn = scan_apps(fresh);
@@ -4008,9 +4344,9 @@ static const char *const hk_preset_labels[] = { "电源+音量减", "连按序
static const char *const seq_presets[] = { "volup,volup,volup", "voldown,voldown,voldown" };
static const char *const seq_preset_labels[] = { "音量上×3", "音量下×3" };
#define SEQ_PRESETS_N 2
static const char *const font_presets[] = { "MeowAPPs/fonts/font.png" };
static const char *const font_preset_labels[] = { "默认字体" };
#define FONT_PRESETS_N 1
/* font.file 行(字体文件)不使用编译期静态表:其 presets/preset_labels 指向字体区
* (font_presets/font_preset_labels, 见本文件字体区) 的运行时动态数组,计数由
* s_row_presets() 动态返回(默认 1,进设置页扫描 TF 卡后更新)。见 SMENU 行。 */
static const char *const out_presets[] = { "hw:0,0" };
static const char *const out_preset_labels[] = { "CS43198 (hw:0,0)" };
#define OUT_PRESETS_N 1
@@ -4074,7 +4410,16 @@ static const srow_t SMENU[] = {
{ "滑动方向", "input.swipe_invert", SC_SWITCH, 0,0,0, NULL,NULL,0 },
/* 字体主题 */
{ "字体主题", NULL, SC_HEADER, 0,0,0, NULL,NULL,0 },
{ "字体文件", "font.file", SC_PRESET, 0,0,0, font_presets, font_preset_labels, FONT_PRESETS_N },
{ "字体文件", "font.file", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 },
/* M2.5x 多字体:开关 + 6 个字符类别字体(preset=可用 MeowOS/fonts/<family>/ 列表;
* 值空 = 跟随全局 font.file)。仅开关开启时 6 行可见(s_row_visible)。 */
{ "多字体", "display.multi_font", SC_SWITCH, 0,0,0, NULL,NULL,0 },
{ "中文(CJK)", "font.class_cjk", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 },
{ "中文标点", "font.class_cjk_sym", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 },
{ "数字", "font.class_num", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 },
{ "拉丁字母", "font.class_latin", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 },
{ "半角标点", "font.class_latin_sym", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 },
{ "其他字符", "font.class_other", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 },
{ "标题字号", "font.size.title", SC_SLIDER, 16,40,1, NULL,NULL,0 },
{ "正文字号", "font.size.body", SC_SLIDER, 12,28,1, NULL,NULL,0 },
{ "UI风格", "ui.style", SC_ENUM, 0,0,0, NULL,NULL,0 },
@@ -4120,7 +4465,41 @@ static int s_sel_track_y = -1;
#define S_SLIDER_W (SET_CONTENT_W)
/* ---- 行高/位置/导航辅助 ---- */
static int s_setting_idx(int i) { return SMENU[i].ctrl != SC_HEADER; }
/* M2.5x 多字体:6 个「字体文件」行仅在 display.multi_font=1 时可见(开时显示)。
* 可见性与分组的标题行无关(标题恒显示)。 */
static int s_row_visible(int i)
{
const srow_t *r = &SMENU[i];
if (r->key && !strncmp(r->key, "font.class_", 11)) {
int on = 0; meow_settings_get_bool("display.multi_font", &on);
return on;
}
return 1;
}
/* 可见行数(= 压缩后占用的行数;标题行计入,隐藏的 class 行不计) */
static int s_vis_count(void)
{
int n = 0;
for (int i = 0; i < SMENU_N; i++) if (s_row_visible(i)) n++;
return n;
}
/* SMENU 下标 → 压缩位置(第几个可见行);不可见返回 -1 */
static int s_vis_of(int i)
{
if (i < 0 || i >= SMENU_N || !s_row_visible(i)) return -1;
int p = 0;
for (int k = 0; k < i; k++) if (s_row_visible(k)) p++;
return p;
}
/* 压缩位置 → SMENU 下标 */
static int s_vis_at(int p)
{
int c = 0;
for (int i = 0; i < SMENU_N; i++) if (s_row_visible(i)) { if (c == p) return i; c++; }
return SMENU_N - 1;
}
static int s_setting_idx(int i) { return i >= 0 && i < SMENU_N && SMENU[i].ctrl != SC_HEADER && s_row_visible(i); }
static int s_first_setting(void)
{
for (int i = 0; i < SMENU_N; i++) if (s_setting_idx(i)) return i;
@@ -4132,10 +4511,23 @@ static int s_next_setting(int i)
return s_first_setting(); /* 回绕 */
}
/* SC_PRESET 行取「选项表 + 数量」:字体行(font.file + font.class_*)用运行时动态数组
* (TF 卡 MeowOS/fonts/<family>/ 扫描结果,见字体区 font_presets/font_presets_n),
* 其余行用编译期静态表(r->presets / r->preset_n)。 */
static const char *const *s_row_presets(const srow_t *r, int *n)
{
if (r && r->key && (!strcmp(r->key, "font.file") || !strncmp(r->key, "font.class_", 11))) {
if (n) *n = font_presets_n;
return font_presets;
}
if (n) *n = r->preset_n;
return r->presets;
}
/* 滚动:纯范围钳制到 [0, 内容高度-视口高](列表拖动滚动用;不强制选中行可见) */
static int s_clamp_scroll_range(int scroll)
{
int total = SMENU_N * SROW_H;
int total = s_vis_count() * SROW_H;
if (total <= SET_CONTENT_H) return 0;
int max_scroll = total - SET_CONTENT_H;
if (scroll < 0) scroll = 0;
@@ -4147,20 +4539,22 @@ static int s_clamp_scroll_range(int scroll)
static int s_clamp_scroll(int sel, int scroll)
{
scroll = s_clamp_scroll_range(scroll);
int top = sel * SROW_H;
int pos = s_vis_of(sel);
if (pos < 0) pos = s_vis_of(s_first_setting());
int top = pos * SROW_H;
int bot = top + SROW_H;
if (top < scroll) scroll = top; /* 选中行滑出顶部 → 上滚 */
if (bot > scroll + SET_CONTENT_H) scroll = bot - SET_CONTENT_H; /* 滑出底部 → 下滚 */
return s_clamp_scroll_range(scroll);
}
/* 行 y 落在 content 坐标 cy 时对应的行号;越界返回 -1 */
/* 行 y 落在 content 坐标 cy 时对应的行号(压缩位置 → SMENU 下标);越界返回 -1 */
static int s_row_at_content_y(int cy)
{
if (cy < 0) return -1;
int idx = cy / SROW_H;
if (idx >= SMENU_N) return -1;
return idx;
int pos = cy / SROW_H;
if (pos >= s_vis_count()) return -1;
return s_vis_at(pos);
}
/* ---- 值格式化/展示 ---- */
@@ -4231,8 +4625,15 @@ static void s_display_value(const srow_t *r, char *buf, int len)
/* SC_PRESET */
char cur[MEOW_SET_MAX_LEN];
if (meow_settings_get_str(r->key, cur, sizeof cur) != 0) { snprintf(buf, len, "-"); return; }
for (int i = 0; i < r->preset_n; i++) {
if (!strcmp(r->presets[i], cur)) {
/* M2.5x 多字体:class 行空值 = 跟随全局 font.file */
if (r->key && !strncmp(r->key, "font.class_", 11) && !cur[0]) {
snprintf(buf, len, "跟随全局");
return;
}
int pn = 0;
const char *const *pps = s_row_presets(r, &pn);
for (int i = 0; i < pn; i++) {
if (!strcmp(pps[i], cur)) {
snprintf(buf, len, "%s", (r->preset_labels && r->preset_labels[i][0]) ? r->preset_labels[i] : cur);
return;
}
@@ -4947,6 +5348,24 @@ static void s_commit(const srow_t *r, int save_now)
!strcmp(r->key, "display.theme_dark_start") || !strcmp(r->key, "display.theme_dark_end")) {
paint_sb_right(); /* 状态栏文字/底色随主题刷新(整条标脏) */
}
/* M2.5x:字体文件(font.file)变更 → 立即 unload + reload(若已加载)。
* 置 mfont_forced 跳过 meow_font_ensure 的 SDK 复用捷径,强制走
* font.file → 卡默认 → 固件 链;全失败则回退 5x7 点阵(系统不死)。 */
else if (!strcmp(r->key, "font.file")) {
mfont_ready = 0;
mfont_forced = 1;
if (!meow_font_ensure())
logmsg("font.file: reload failed -> dotmatrix fallback");
else
logmsg("font.file: reloaded");
meow_font_reload_multifont(); /* M2.5x:全局字体变更 → 重配多字体(各类跟随全局者随之更新) */
meow_dirty_full(); /* 字体变化可能影响所有文本渲染,整帧重绘 */
}
/* M2.5x 多字体:开关或任一类别字体变更 → 重新去重装载类别字库并重绘(多字体可能影响文本渲染)。 */
else if (!strcmp(r->key, "display.multi_font") || !strncmp(r->key, "font.class_", 11)) {
meow_font_reload_multifont();
meow_dirty_full();
}
if (save_now) meow_settings_save();
}
static void s_set_int_value(const srow_t *r, int v, int save_now)
@@ -4993,13 +5412,15 @@ static void draw_settings_page(int sel, int scroll_off)
/* M2.38 Bug5:整屏背景替代带圆角的单张卡片(用户反馈圆角非全屏底很怪)。
* 内容区从 SET_CARD_Y 起整屏 fill surface_variant、去圆角、满宽直到屏幕底;
* 状态栏(0-40)与标题区(40-92)仍为 surface,标题/分隔线/分组 outline/选中高亮/滚动条布局不变。 */
* 状态栏(0-28)与标题区(28-92)仍为 surface,标题/分隔线/分组 outline/选中高亮/滚动条布局不变。 */
fill_rect(0, SET_CARD_Y, W, H - SET_CARD_Y, th->surface_variant);
int clip_top = SET_CONTENT_Y, clip_bot = SET_CONTENT_BOT;
s_sel_track_y = -1;
for (int i = 0; i < SMENU_N; i++) {
int sy = SET_CONTENT_Y + i * SROW_H - scroll_off;
int pos = s_vis_of(i);
if (pos < 0) continue; /* 隐藏行(多字体关闭时 6 个 class 行) */
int sy = SET_CONTENT_Y + pos * SROW_H - scroll_off;
if (sy + SROW_H < clip_top || sy >= clip_bot) continue; /* 视野外行跳过(配滚动) */
const srow_t *r = &SMENU[i];
int selected = (i == sel);
@@ -5055,8 +5476,9 @@ static void draw_settings_page(int sel, int scroll_off)
}
/* 滚动条(内容超出视口时显示 thumb;on_surface_variant 弱化) */
if (SMENU_N * SROW_H > SET_CONTENT_H) {
int total = SMENU_N * SROW_H;
int vis_total = s_vis_count() * SROW_H;
if (vis_total > SET_CONTENT_H) {
int total = vis_total;
int thumb_h = SET_CONTENT_H * SET_CONTENT_H / total;
if (thumb_h < 24) thumb_h = 24;
int th_y = SET_CONTENT_Y + (SET_CONTENT_H - thumb_h) * scroll_off / (total - SET_CONTENT_H);
@@ -5077,6 +5499,20 @@ static void draw_settings_page(int sel, int scroll_off)
}
}
/* M2.5x 多字体:class 行下拉的「跟随全局」选项(值=空串)在 write 路径经此映射回真实存储值。 */
static const char *s_preset_value(const srow_t *r, int pick)
{
int is_cls = r->key && !strncmp(r->key, "font.class_", 11);
if (is_cls) {
if (pick == 0) return ""; /* 跟随全局 font.file */
pick--;
}
int pn = 0;
const char *const *pps = s_row_presets(r, &pn);
if (pick < 0 || pick >= pn) return is_cls ? "" : (pps && pn > 0 ? pps[0] : "");
return pps[pick];
}
/* ---- 选择器子对话框(MD3 §3.3:居中圆角卡片,列选项;中文标签)----
* ENUM 用 meow_settings_enum_names/enum_label;PRESET 用本地预设表。
* 返回选中索引;取消返回 -1。阻塞(自开输入设备)。
@@ -5100,14 +5536,24 @@ static int s_dropdown(const srow_t *r)
} else {
char cstr[MEOW_SET_MAX_LEN];
meow_settings_get_str(r->key, cstr, sizeof cstr);
n = r->preset_n;
if (n > S_NET_MAX) n = S_NET_MAX;
for (int i = 0; i < n; i++) {
names[i] = r->presets[i];
snprintf(labels[i], sizeof labels[i], "%s",
(r->preset_labels && r->preset_labels[i][0]) ? r->preset_labels[i] : r->presets[i]);
item[i] = labels[i];
if (!strcmp(r->presets[i], cstr)) cur = i;
int pn = 0;
const char *const *pps = s_row_presets(r, &pn);
int is_cls = r->key && !strncmp(r->key, "font.class_", 11);
int base = is_cls ? 1 : 0; /* class 行 0 号留给「跟随全局」 */
int cnt = pn; if (cnt > S_NET_MAX - base) cnt = S_NET_MAX - base;
n = base + cnt;
if (is_cls) {
names[0] = "";
snprintf(labels[0], sizeof labels[0], "跟随全局字体");
item[0] = labels[0];
if (!cstr[0]) cur = 0;
}
for (int i = 0; i < cnt; i++) {
names[base + i] = pps[i];
snprintf(labels[base + i], sizeof labels[base + i], "%s",
(r->preset_labels && r->preset_labels[i][0]) ? r->preset_labels[i] : pps[i]);
item[base + i] = labels[base + i];
if (!strcmp(pps[i], cstr)) cur = base + i;
}
}
if (n <= 0) return -1;
@@ -5130,7 +5576,7 @@ static int s_dd_modal(const char *title, const char *item[S_NET_MAX], int n, int
int opt_row = 48, pad = 20, title_h = 40;
int d_h = title_h + n * opt_row + pad;
int d_w = 348, d_x = (W - d_w) / 2, d_y = (H - d_h) / 2;
if (d_y < 40) d_y = 40;
if (d_y < SB_BAR_H) d_y = SB_BAR_H; /* 下拉不压状态栏(栏高 40→28,随常量) */
if (H - d_y - d_h < 20) d_y = H - d_h - 20;
struct keypoll kp_power, kp_keys;
@@ -5260,6 +5706,10 @@ static void run_settings_page(void)
int scroll_off = 0;
int H = fb_yres;
/* M2.5x:进设置页扫描一次 TF 卡 MeowOS/fonts/<family>/,刷新「字体文件」下拉。
* 进设置时卡通常在位;此步只枚举/校验,不加载(加载由 meow_font_ensure 按 font.file 决定)。 */
meow_font_scan_presets();
struct keypoll kp_power, kp_keys;
kp_open(&kp_power, DEV_POWER); kp_open(&kp_keys, DEV_KEYS);
struct kd_state kd_vold, kd_volu, kd_play, kd_pwr;
@@ -5585,7 +6035,7 @@ static void run_settings_page(void)
int pick = s_dropdown(r);
if (pick >= 0) {
if (r->ctrl == SC_ENUM) meow_settings_set_enum(r->key, pick);
else meow_settings_set_str(r->key, r->presets[pick]);
else meow_settings_set_str(r->key, s_preset_value(r, pick));
s_commit(r, 1);
}
/* M2.38 Bug3:s_dropdown 返回后彻底排空本页 tfd +
@@ -5636,7 +6086,7 @@ static void run_settings_page(void)
if (sel_changed) { scroll_off = s_clamp_scroll(sel, scroll_off); sel_changed = 0; }
draw_settings_page(sel, scroll_off);
/* 局部标脏:标题+卡片+滚动条+底部(y=42 以下全部,覆盖主题/选中/值变化),
* 状态栏(0-40)由 draw_statusbar/draw_clock 各自维护,不在此列。
* 状态栏(0-28)由 draw_statusbar/draw_clock 各自维护,不在此列。
* 选择器关闭后(after_modal)其全屏 dim 把状态栏也压暗了,须整帧重绘还原。 */
if (after_modal) { meow_dirty_full(); after_modal = 0; }
else meow_dirty_add(0, 42, fb_xres, fb_yres - 42);
@@ -6280,14 +6730,17 @@ static int launch_app(struct app *a)
char *argv[] = { a->exec, NULL };
char env_path[320], env_log[320], env_data[320], env_cache[320];
char env_app[MEOW_APP_ID_MAX + 16], env_lvl[48];
char *envp[12];
char env_fpng[400], env_fidx[400], env_fapp[96];
char env_mf[24];
char env_cls[MEOW_FCLS_COUNT][2][400];
char *envp[32]; /* PATH,HOME,log_dir,log_app,log_lvl,font 全局(3),多字体(1),class(≤12),data,cache */
int ne = 0;
const char *cp = getenv("PATH");
snprintf(env_path, sizeof env_path, "PATH=%s", cp ? cp : "/usr/bin:/bin");
envp[ne++] = env_path;
envp[ne++] = "HOME=/root";
if (log_dir[0]) { snprintf(env_log, sizeof env_log, "MEOW_LOG_DIR=%s", log_dir); envp[ne++] = env_log; }
/* M2.52:MEOW_LOG_APP=app_id(目录名,与注册表一致;无 id 则跳过,SDK 兜底 "app") */
/* M2.52:MEOW_LOG_APP=app_id(=app.json.package,缺省回退目录名;与注册表一致;无 id 则跳过,SDK 兜底 "app") */
if (a->id[0]) { snprintf(env_app, sizeof env_app, "MEOW_LOG_APP=%s", a->id); envp[ne++] = env_app; }
/* M2.52:MEOW_LOG_LEVEL 读全局 log.level(error/warn/info/debug→索引0/1/2/3),
* SDK 只认大写字符串名(ERROR/WARN/INFO/DEBUG),故转大写注入;缺省兜底 info。 */
@@ -6299,6 +6752,55 @@ static int launch_app(struct app *a)
snprintf(env_lvl, sizeof env_lvl, "MEOW_LOG_LEVEL=%s", lvl_name[lv]);
envp[ne++] = env_lvl;
}
/* M2.x:注入「当前生效字体」= 系统 font.file(launcher 已 load 的那套),
* SDK meow_ui_gfx.load_font 优先读 MEOW_FONT_PNG/IDX,app 未自带字库(§5.7 load_handle
* 未实现)时跟随系统字体,不再硬编码 MeowAPPs/fonts。无卡/无可用字体则跳过注入,
* SDK 自行回退卡默认→固件→点阵。 */
{
static const char *cls_env_png[MEOW_FCLS_COUNT] = {
"MEOW_FONT_CLS_CJK_PNG", "MEOW_FONT_CLS_CJK_SYM_PNG", "MEOW_FONT_CLS_NUM_PNG",
"MEOW_FONT_CLS_LATIN_PNG", "MEOW_FONT_CLS_LATIN_SYM_PNG", "MEOW_FONT_CLS_OTHER_PNG" };
static const char *cls_env_idx[MEOW_FCLS_COUNT] = {
"MEOW_FONT_CLS_CJK_IDX", "MEOW_FONT_CLS_CJK_SYM_IDX", "MEOW_FONT_CLS_NUM_IDX",
"MEOW_FONT_CLS_LATIN_IDX", "MEOW_FONT_CLS_LATIN_SYM_IDX", "MEOW_FONT_CLS_OTHER_IDX" };
static const char *cls_keys[MEOW_FCLS_COUNT] = {
"font.class_cjk", "font.class_cjk_sym", "font.class_num",
"font.class_latin", "font.class_latin_sym", "font.class_other" };
char fpng[400], fidx[400], fam[96];
int have_global;
int mf_on = 0;
meow_settings_get_bool("display.multi_font", &mf_on);
snprintf(env_mf, sizeof env_mf, "MEOW_MULTI_FONT=%d", mf_on ? 1 : 0);
envp[ne++] = env_mf;
have_global = (meow_font_effective_paths(fpng, sizeof fpng, fidx, sizeof fidx,
fam, sizeof fam) == 0);
if (have_global) {
snprintf(env_fpng, sizeof env_fpng, "MEOW_FONT_PNG=%s", fpng);
snprintf(env_fidx, sizeof env_fidx, "MEOW_FONT_IDX=%s", fidx);
snprintf(env_fapp, sizeof env_fapp, "MEOW_FONT_APP=%s", fam);
envp[ne++] = env_fpng;
envp[ne++] = env_fidx;
envp[ne++] = env_fapp;
logmsg("launch: font env %s/%s (family=%s)", fpng, fidx, fam);
}
/* M2.5x 多字体:每类注入 png/idx(命中则覆盖全局;类值为空=跟随全局,不注入)。
* SDK 读这些 env 构建多字体映射;同 family 去重由 SDK 侧 meow_font_config_multifont 处理。 */
if (mf_on)
for (int c = 0; c < MEOW_FCLS_COUNT; c++) {
char ref[MEOW_SET_MAX_LEN], abs[400], idxabs[400];
if (meow_settings_get_str(cls_keys[c], ref, sizeof ref) != 0 || !ref[0]) continue;
if (meow_font_resolve_path(ref, abs, sizeof abs) != 0 || access(abs, R_OK) != 0) continue;
snprintf(idxabs, sizeof idxabs, "%s", abs);
size_t n4 = strlen(idxabs);
if (n4 > 4 && !strcmp(idxabs + n4 - 4, ".png")) {
idxabs[n4-4]='.'; idxabs[n4-3]='i'; idxabs[n4-2]='d'; idxabs[n4-1]='x'; idxabs[n4]=0;
}
snprintf(env_cls[c][0], sizeof env_cls[c][0], "%s=%s", cls_env_png[c], abs);
snprintf(env_cls[c][1], sizeof env_cls[c][1], "%s=%s", cls_env_idx[c], idxabs);
envp[ne++] = env_cls[c][0];
envp[ne++] = env_cls[c][1];
}
}
char pkg[64];
if (meow_pkg_name(a->exec, pkg, sizeof pkg) == 0) {
snprintf(env_data, sizeof env_data, "MEOW_DATA_DIR=/usr/data/mnt/sd_0/MeowData/%s/data", pkg);
@@ -6510,7 +7012,8 @@ static void usage(void)
" [--sim-power-pick N] [--sim-no-exec]\n"
" [--dumpkeys N] [--scan] [--sysdump]\n"
" [--pkg-install <pkg.mpk>] [--pkg-export <pkg> <out.mpk>]\n"
" [--ipc-only N] [--ipc-sock <path>] [--appinfo <app.json>]\n");
" [--ipc-only N] [--ipc-sock <path>] [--appinfo <app.json>]\n"
" [--fontscan <card_root>]\n");
}
int main(int argc, char **argv)
@@ -6519,6 +7022,7 @@ int main(int argc, char **argv)
int do_scan = 0, do_sysdump = 0, sim_vol = 0;
long ipc_only_ms = 0;
const char *appinfo_path = NULL;
const char *fontscan_root = NULL; /* --fontscan <root>:宿主/qemu 测试字形扫描 */
const char *pkg_install_mpk = NULL, *pkg_export_name = NULL, *pkg_export_out = NULL;
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--sim-hotkey") && i + 1 < argc) sim_hotkey = atol(argv[++i]);
@@ -6537,6 +7041,7 @@ int main(int argc, char **argv)
snprintf(ipc_sock_path, sizeof ipc_sock_path, "%s", argv[++i]);
}
else if (!strcmp(argv[i], "--appinfo") && i + 1 < argc) appinfo_path = argv[++i];
else if (!strcmp(argv[i], "--fontscan") && i + 1 < argc) fontscan_root = argv[++i];
else if (!strcmp(argv[i], "--pkg-install") && i + 1 < argc) pkg_install_mpk = argv[++i];
else if (!strcmp(argv[i], "--pkg-export") && i + 2 < argc) { pkg_export_name = argv[++i]; pkg_export_out = argv[++i]; }
else { usage(); return 1; }
@@ -6556,6 +7061,20 @@ int main(int argc, char **argv)
* menu open so edits apply without a daemon restart) */
settings_load(); /* M2.11:读系统设置(audio.volume 等),供音量/状态栏使用 */
/* M2.5x:启动扫描一次 TF 卡 MeowOS/fonts/<family>/,填充「字体文件」下拉 preset。
* 无卡/无字库目录 → 仅默认字体(等价现状)。进设置页时 run_settings_page 会再扫一次。 */
meow_font_scan_presets();
if (fontscan_root) {
/* M2.5x 测试钩子:扫描指定卡根下的 MeowOS/fonts/<family>/ 并打印 preset(qemu/宿主可测)。
* 用于验证 font.json 解析/三件套/缺件跳过/sha 校验 + preset 动态化逻辑。 */
meow_font_scan_presets_root(fontscan_root);
printf("fonts=%d\n", font_presets_n);
for (int i = 0; i < font_presets_n; i++)
printf(" %d %s -> %s\n", i, font_preset_labels[i], font_presets[i]);
return 0;
}
if (sim_vol > 0) {
/* M2.14 sim:验证线性渐变——step=1/2/5 下设定音量按 1% 逐级爬升/回落,
* 每 1% 经 meow_dac_vol_write 写一次真实 DAC,打点进 log(stderr)。 */
+24 -1
View File
@@ -77,6 +77,9 @@ static const meow_setting_def g_defs[] = {
{ "display.theme_dark_start", SET_INT, "0", 0, 1439, NULL, NULL },
{ "display.theme_dark_end", SET_INT, "0", 0, 1439, NULL, NULL },
{ "display.background", SET_STR, "black",0, 0, NULL, NULL },
/* M2.5x 多字体:1=按字符类别逐字符路由(配合下方 6 类 font.class_*);
* 0=单字体(只走 font.file,零额外内存/性能开销,等价现状)。 */
{ "display.multi_font", SET_INT, "0", 0, 1, NULL, NULL },
/* 电源 */
{ "power.longpress_menu_ms", SET_INT, "3000", 0, 0, NULL, NULL },
{ "power.forceoff_ms", SET_INT, "8000", 0, 0, NULL, NULL },
@@ -92,7 +95,15 @@ static const meow_setting_def g_defs[] = {
/* M2.38:设置页滑动方向切换(0=标准:手指上滑→列表往下翻;1=反向) */
{ "input.swipe_invert", SET_BOOL, "0", 0, 0, NULL, NULL },
/* 字体/主题 */
{ "font.file", SET_STR, "MeowAPPs/fonts/font.png", 0,0, NULL, NULL },
{ "font.file", SET_STR, "MeowOS/fonts/default/font.png", 0,0, NULL, NULL },
/* M2.5x 多字体:6 个字符类别各一字体(SET_STR,值=字体 family 相对路径,缺省空=跟随全局 font.file)。
* 仅 display.multi_font=1 时生效;同一 family 去重;上限 4 套不同字体(超出 WARN)。 */
{ "font.class_cjk", SET_STR, "", 0,0, NULL, NULL },
{ "font.class_cjk_sym", SET_STR, "", 0,0, NULL, NULL },
{ "font.class_num", SET_STR, "", 0,0, NULL, NULL },
{ "font.class_latin", SET_STR, "", 0,0, NULL, NULL },
{ "font.class_latin_sym", SET_STR, "", 0,0, NULL, NULL },
{ "font.class_other", SET_STR, "", 0,0, NULL, NULL },
{ "font.size.title", SET_INT, "28", 0, 0, NULL, NULL },
{ "font.size.body", SET_INT, "20", 0, 0, NULL, NULL },
{ "ui.style", SET_ENUM, "md3", 0, 0, "md3,miui", "Material You,MIUI" },
@@ -205,6 +216,18 @@ static int clamp_brightness(int v)
return v;
}
/* M2.53:返回当前用户亮度设定(display.brightness)经 clamp_brightness 后的值
* (下限 brightness_min 默认 1、上限 100)。MeowOS 主屏接管时用它压回用户亮度,
* 覆盖 HibyOS 播放器写入的背光下限 16/25。settings 未 init/键缺失回退默认 101 再 clamp
* (→100,不闪屏)。纯函数,宿主机可直接测(MEOW_SETTINGS_FILE 可编译期覆盖)。 */
int meow_settings_brightness_clamped(void)
{
meow_setting *b = find_set("display.brightness");
int v = 101; /* 默认表 display.brightness 默认 */
if (b && b->cur[0]) v = atoi(b->cur);
return clamp_brightness(v);
}
/* ---- 按类型把字符串值 apply 到 cur(读文件/导入时用;含 clamp/校验)---- */
static void apply_value(meow_setting *s, const char *v)
{
+6
View File
@@ -70,6 +70,12 @@ int meow_settings_get_str (const char *key, char *buf, int buf_len);
int meow_settings_get_enum(const char *key, int *out); /* 返回索引(名字->索引) */
int meow_settings_get_bool(const char *key, int *out); /* 0/1 */
/* M2.53:返回当前用户亮度设定(display.brightness)经 clamp_brightness 后的值
* (下限 display.brightness_min 默认 1=极暗但可见、上限 100=防闪屏)。
* MeowOS 主屏接管时用它把亮度压回用户值,覆盖 HibyOS 播放器写入的背光下限 16/25。
* settings 未初始化/键缺失时回退默认 101 再 clamp(→100);纯函数,宿主机可直接测。 */
int meow_settings_brightness_clamped(void);
int meow_settings_set_int (const char *key, int v); /* clamp 到取值域 */
int meow_settings_set_str (const char *key, const char *v);/* 长度截断 MEOW_SET_MAX_LEN */
int meow_settings_set_enum(const char *key, int idx); /* 校验枚举合法,越界拒绝 */