Files
AskaEth d8241cd2d6 launcher(M7.0b): 电源菜单/Now Playing 控件化 + SDK 主题薄壳 + i18n 抽取(试点迁移)
M7 整体 MD3 重写试点(M7.0 定两隔离页,只动 patch_v2/meow_launcherd/):
- 电源菜单 power_menu.c:圆角卡→meow_card;6 圆钮→meow_iconbtn(回HiBy=ARROW_BACK、
  取消=CLOSE;sb_icons 表在位图)+ meow_widget_custom_create(关机/重启/升级/锁定=pm_icons
  专有,圆形命中自定义);命中/选中/按压态全走 SDK(meow_widget_hit/set_state/draw),
  触摸按下置 PRESSED、抬起清(pm_blend 按主题混合状态层);py 手动驱动控件(哨兵 activity
  0x50 push/pop,owner 闸门使命中生效)。
- Now Playing now_playing.c:进度条→meow_progress;播放控制三钮→meow_iconbtn(SKIP_PREV /
  PLAY_ARROW|PAUSE同位双钮按状态只画其一/ SKIP_NEXT);命中走 meow_widget_hit;封面
  (meow_img contain) 与歌名/艺术家 (meow_font) 保留;哨兵 activity 0x51 push/pop。
- 主题薄壳 font_theme.c:s_ui_theme() 改读 SDK meow_theme_export()(单一权威,9 字段映射
  §8.1.5),退役 launcher 自持 4 套静态色板(#if 0 回退位)。
- i18n 抽取:两页硬编码中文案改 meow_str(新增 meow_str_zh 辅助:SDK 内嵌表无 launcher 域串
  时回退中文默认,TF 覆盖表/后续并入 SDK 内嵌后自动换语)。
- 会话骨架不动:物理键语义(单击息屏/3s 菜单/8s 强关/左缘返回/横划 NP)与两页进入退出/事件流
  未改,只替换「怎么画/怎么命中」;旧绘制函数 #if 0 留回退位。
- 验证:新增 test_power_menu_widgets / test_now_playing_widgets 宿主测试(圆形命中/owner+enabled
  闸门/选中按压态像素/主题四态 MD3+MIUIx×明暗 渲染 + meow_theme_export 九字段)全 PASS;
  launcher 全量宿主回归(lock_screen/overlay_gate/utf8_name/media/depends/ams_start/bgplay/
  settings_brightness/netmgr_ipc/vol_ramp_jack/notif_panel/notif_tf/lock_render + SDK theme_export/
  iconbtn)全 PASS;交叉 -Wall -Wextra 0 warning。新 meow_launcherd md5 94a33b7b。
2026-09-02 17:25:21 +08:00

849 lines
45 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* font_theme.c — MeowOS launcher 拆分模块(LAUNCHER_SPLIT_PLAN 拆分)。*/
/* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/
#include "meow_launcher_internal.h"
/* M2.6 字重结构 + M2.8 三级定案(FONT_SYSTEM):卡内字体 =
* <卡根>/MeowOS/fonts/<family>/<weight>/(两级,向后兼容)或
* <卡根>/MeowOS/fonts/<family>/<weight>/<tier>/(三级,tier=common/gbk/full)。
* 每个 preset = 一套 <family>/<weight>[/<tier>](如 misans/regular 或 source_han_sans_cn/regular/gbk)。
* 值 = 卡根相对路径 MeowOS/fonts/<family>/<weight>[/<tier>]/font.png;family/weight 单列供设置页
* 「family→字重」子菜单分组与 meow_font_effective_paths_tiered 按 family+weight 选库。 */
const char *font_presets[FONT_PRESETS_MAX]; /* SC_PRESET 值 = 卡根相对路径 */
const char *font_preset_labels[FONT_PRESETS_MAX]; /* SC_PRESET 中文标签(含字重+档位大小标记) */
/* M2.5x/M2.8 字体档位:每套的档("common"/"gbk"/"full";两级取 font.json charset,缺省
* "common";三级取 tier 目录名,权威)与 glyph_count(信息用)。供档位分层装载
* (meow_font_effective_paths_tiered)与设置页标注/选档。 */
const char *font_preset_tiers[FONT_PRESETS_MAX];
static int font_preset_glyphs[FONT_PRESETS_MAX];
const char *font_preset_fams[FONT_PRESETS_MAX]; /* family slug(目录一级) */
const char *font_preset_wts[FONT_PRESETS_MAX]; /* weight slug(目录二级) */
const char *font_preset_names[FONT_PRESETS_MAX]; /* font.json name(family 显示名) */
static char font_preset_val_buf[FONT_PRESETS_MAX][320]; /* 值持久存储(指向上面指针数组) */
static char font_preset_label_buf[FONT_PRESETS_MAX][96]; /* 标签持久存储(含字重+档位标记) */
static char font_preset_tier_buf[FONT_PRESETS_MAX][8]; /* charset 持久存储 */
static char font_preset_fam_buf[FONT_PRESETS_MAX][64]; /* family 持久存储 */
static char font_preset_wt_buf[FONT_PRESETS_MAX][32]; /* weight 持久存储 */
static char font_preset_name_buf[FONT_PRESETS_MAX][64]; /* name 持久存储 */
int font_presets_n = 0; /* 运行时动态计数(默认=1,仅"默认字体") */
int mfont_ready = 0;
int mfont_forced = 0; /* font.file 变更→强制真实(un)load,跳过 SDK 复用捷径 */
/* fb_open 后调用:设置字库绘制目标为当前双缓冲(buffer0 起点 = fb 偏移 0) */
void meow_font_bind_fb(void)
{
meow_font_set_fb(fb_xres, fb_yres, fb_line);
}
/* 把 font.file 存的「卡根相对/绝对」路径解析为设备绝对路径。
* 相对路径以 /usr/data/mnt/sd_0/ 为卡根(与 MEOWAPPS 同源);绝对路径原样。
* 返回 0 成功,-1 参数无效。 */
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,font.load_full 已接入):
* 0) 生效档 = font.load_full ? gbk : common(launcher 自身 UI 跟随系统大字库开关);
* 1) 生效字体(tier 解析,tier0 即 font.file 所选;相对路径按卡根解析);
* 2) 卡 MeowOS/fonts/default/regular/font.png(默认字库);
* 3) 固件 /usr/share/meowos/font/font.png。
* A-1/A-2:SDK meow_ui_init() 已用同一 meow_font.o 加载过(可能成功也可能未成功)。
* 当字体指向「默认卡字库」或未设置时复用 SDK 已加载状态,避免重复 unload+reload;
* 当字体指向自定义字库时必须显式 load(SDK 只加载默认,不读 font.file)。
* mfont_forced=1(font.file/font.load_full 变更触发)时无条件真实 reload,跳过上述捷径。
* 全失败 → mfont_ready=0,ui_draw 回退 5x7 点阵(系统不死)。 */
int meow_font_ensure(void)
{
if (mfont_ready && !mfont_forced) return 1;
mfont_forced = 0;
char fpng[400], fidx[400], fam[96];
int tier = meow_font_effective_tier(MEOW_FONT_TIER_COMMON); /* launcher 自身 UI 跟随 load_full */
int have_sel = (meow_font_effective_paths_tiered(tier, fpng, sizeof fpng,
fidx, sizeof fidx, fam, sizeof fam) == 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;
}
/* 生效字体优先(tier=common 时即 font.file;load_full → 大字库 gbk 档)。 */
if (have_sel && meow_font_load(fpng, fidx) == 0) {
mfont_ready = 1;
logmsg("meow_font: loaded %s (%d/%d sizes), tier=%d", fpng,
meow_font_has_size(28), meow_font_has_size(20), tier);
return 1;
}
if (have_sel)
logmsg("meow_font: selected font %s missing, fallback default", 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 fallback (%d/%d sizes)",
meow_font_has_size(28), meow_font_has_size(20));
return 1;
}
logmsg("meow_font: all font sources missing, using 5x7 bitmap"); /* WARN:字库不可用 */
return 0;
}
/* 从 MeowOS/fonts/<family>/<weight>/font.png 绝对路径提 family(第一级目录名)。
* 如 .../misans/regular/font.png → "misans"、.../default/regular/font.png → "default";
* 结构异常/找不到前缀 → 回退 "default"。 */
static void font_family_from_png(const char *png, char *out, int olen)
{
const char *prefix = "/MeowOS/fonts/";
const char *p = png ? strstr(png, prefix) : NULL;
if (!p) { snprintf(out, olen, "default"); return; }
const char *q = p + strlen(prefix);
const char *slash = strchr(q, '/');
int n = slash ? (int)(slash - q) : (int)strlen(q);
if (n <= 0 || n >= olen) { snprintf(out, olen, "default"); return; }
memcpy(out, q, (size_t)n); out[n] = 0;
}
/* 同路径同时提取 family + weight(两级目录名)。weight 缺省 "regular"。 */
void font_family_weight_from_png(const char *png, char *fam, int flen, char *wt, int wlen)
{
const char *prefix = "/MeowOS/fonts/";
const char *p = png ? strstr(png, prefix) : NULL;
if (!p) { snprintf(fam, flen, "default"); snprintf(wt, wlen, "regular"); return; }
const char *q = p + strlen(prefix);
const char *s1 = strchr(q, '/');
if (!s1) { snprintf(fam, flen, "%s", q); snprintf(wt, wlen, "regular"); return; }
int nf = (int)(s1 - q);
if (nf > 0 && nf < flen) { memcpy(fam, q, (size_t)nf); fam[nf] = 0; }
else snprintf(fam, flen, "default");
const char *s2 = strchr(s1 + 1, '/');
int nw = s2 ? (int)(s2 - (s1 + 1)) : (int)strlen(s1 + 1);
if (nw > 0 && nw < wlen) { memcpy(wt, s1 + 1, (size_t)nw); wt[nw] = 0; }
else snprintf(wt, wlen, "regular");
}
/* 求「当前生效字体」的 png/idx 绝对路径 + family,供 launch_app 注入 MEOW_FONT_PNG/IDX/APP。
* 优先级(与 meow_font_ensure 一致):font.file(resolve_selected)→ 卡默认 MeowOS/fonts/default。
* family 取自 MeowOS/fonts/<family>/<weight>/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);
font_family_from_png(fpng, out_family, flen);
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;
}
/* 档位 → font.json charset 字符串名(common/gbk/full)。 */
static const char *font_tier_charset(int tier)
{
if (tier >= MEOW_FONT_TIER_FULL) return "full";
if (tier >= MEOW_FONT_TIER_GBK) return "gbk";
return "common";
}
/* 生效档:app 声明档(0/1/2,越界 clamp)合并「加载大字库」开关(font.load_full)。
* 开 → 强制 ≥GBK(gbk≈20k 档大字库);关(默认)→ 保持 app 声明(缺省 common≈4k)。
* 断言:0<=返回<=2。 */
int meow_font_effective_tier(int app_tier)
{
if (app_tier < MEOW_FONT_TIER_COMMON) app_tier = MEOW_FONT_TIER_COMMON;
if (app_tier > MEOW_FONT_TIER_FULL) app_tier = MEOW_FONT_TIER_FULL;
int lf = 0;
if (meow_settings_get_switch("font.load_full", &lf) == 0 && lf)
if (app_tier < MEOW_FONT_TIER_GBK) app_tier = MEOW_FONT_TIER_GBK;
return app_tier;
}
/* 【tier 选择入口,M2.5x 已实现】按字体档位取「当前生效字体」png/idx,供 launch_app 注入
* MEOW_FONT_PNG/IDX/APP(配合 MEOW_FONT_TIER)。口径(MEOWOS_SPEC_REF §5.2/§5.7.8):
* - 生效档 = max(app 声明档, font.load_full ? GBK : COMMON):系统「加载大字库」开 →
* 强制 ≥gbk;
* - tier<=common → 走既有 meow_font_effective_paths(font.file/卡默认/固件);
* - tier>0 → 在已扫描的 preset(两级或三级,值路径 = MeowOS/fonts/<family>/<weight>[/<tier>]/font.png)
* 里找档位匹配(gbk 请求接受 gbk/full;full 请求接受 full)的那套,以其 font.png/idx 绝对路径返回
* (family = 一级目录名)。
* - M2.6 字重:优先选「与用户当前 font.file 同 family(其次同 weight)」的大字库,避免
* 大字库跳到另一家字体(三级同 family 下按 tier 找 gbk/full,见下);找不到匹配 →
* 回退 meow_font_effective_paths(避免点到固件小库)。
* 返回 0 成功(out_png/out_idx/out_family 已填),-1 无可用。 */
int meow_font_effective_paths_tiered(int tier, char *out_png, int plen,
char *out_idx, int ilen, char *out_family, int flen)
{
int eff = meow_font_effective_tier(tier); /* 合并 font.load_full:开 → 强制 ≥GBK */
if (eff <= MEOW_FONT_TIER_COMMON)
return meow_font_effective_paths(out_png, plen, out_idx, ilen, out_family, flen);
/* 大字库:从扫描结果选 charset 匹配的一套。
* 打分(档位主导 ×100):gbk 请求 → gbk=200 > full=100;full 请求 → full=200 > gbk=100;
* + 同 family +30(其次同 weight +10,M2.6)仅作同档位内 tie-break:大字库沿用用户所选
* family,但绝不因「同 family」而把 common 小库越档选成大字库。 */
const char *want = font_tier_charset(eff);
char curfam[64] = "", curwt[32] = "";
{ /* 用户当前 font.file 所选 family/weight(无法判定 → 空,不参与加成) */
char fpng[400], fidx[400];
if (meow_font_resolve_selected(fpng, sizeof fpng, fidx, sizeof fidx) == 0)
font_family_weight_from_png(fpng, curfam, sizeof curfam, curwt, sizeof curwt);
}
char ref[320];
int found = -1, best_score = 0;
for (int i = 0; i < font_presets_n; i++) {
const char *cs = font_preset_tiers[i] ? font_preset_tiers[i] : "common";
/* 档位优先(×100 主导):gbk 请求 → gbk=200 > full=100;full 请求 → full=200 > gbk=100。
* M2.6:同 family +30、同 weight +10 仅作同档位内 tie-break(不越档选小库)。 */
int score = 0;
if (!strcmp(cs, want)) score = 200;
else if (!strcmp(cs, "full") || !strcmp(cs, "gbk")) score = 100; /* 降级 */
if (curfam[0] && font_preset_fams[i] && !strcmp(font_preset_fams[i], curfam)) score += 30;
if (curfam[0] && curwt[0] && font_preset_wts[i] && !strcmp(font_preset_wts[i], curwt)) score += 10;
if (score > best_score) { best_score = score; found = i; }
}
if (found >= 0)
snprintf(ref, sizeof ref, "%s", font_presets[found]);
if (found >= 0) {
/* 相对路径按卡根解析为设备绝对路径(与 meow_font_effective_paths 同源)。 */
if (meow_font_resolve_path(ref, out_png, plen) != 0) return -1;
if (access(out_png, R_OK) != 0) goto fallback;
snprintf(out_idx, ilen, "%s", out_png);
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;
}
font_family_from_png(out_png, out_family, flen);
return 0;
}
fallback:
/* 无匹配大字库/不可读 → 回退全局(font.file 所选;其可能是大字库或默认)。 */
return meow_font_effective_paths(out_png, plen, out_idx, ilen, out_family, flen);
}
/* 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。 */
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_switch("display.multi_font", &on);
char gpng[400], gidx[400], gfam[96];
/* 全局跟生效档(合并 font.load_full → 大字库),与 meow_font_ensure 同源,避免槽0实际装载与
* 此处理解不一致(多字体去重以全局 png 为基准)。 */
if (meow_font_effective_paths_tiered(meow_font_effective_tier(MEOW_FONT_TIER_COMMON),
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];
} else {
/* M2.58:类别字体配置了但缺失 → 该类别回退全局,并记系统 WARN。 */
logmsg("multi_font: class %s font %s missing, fallback global", cls_keys[c], ref);
}
}
}
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);
}
/* ================================================================== */
/* M2.5x 多字体库扫描 + M2.6 字重结构 / preset 动态化 */
/* 字体目录 = <卡根>/MeowOS/fonts/<family>/<weight>/,每套含三件套: */
/* font.png(预渲染灰度 PNG,bit depth 4|8)+ font.idx(MFNI v2 索引) */
/* + font.json(元数据 + 完整性校验)。缺任一件 → 该套不进列表 + 日志。 */
/* font.json schema(定版): */
/* { */
/* "family": "家族标识(同目录名/family)", // 信息用,不参与校验 */
/* "name": "下拉显示名", // 缺则回退目录名 */
/* "weight": "regular", // 字重 slug(缺省 regular) */
/* "glyph_count": 10000, // 信息用 */
/* "charset": "common|gbk|full", // 档位(M2.5x,驱动按需加载) */
/* "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)
{
if (!out || olen <= 0) return -1;
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);
/* M2.84-bugfix:禁止裸 popen+fgets——sha256sum 在卡慢/挂起时可能长时间不返回,
* fgets 会阻塞在 pipe_wait(主循环卡死、电源键无响应)。改用有界 popen_read_tmo
* (O_NONBLOCK + poll + 超时,超时 SIGKILL 子进程),绝不阻塞主循环。 */
char *o = popen_read_tmo(1500, cmd);
if (!o) return -1;
int i = 0, k = 0;
while (o[i] == ' ' || o[i] == '\t') i++; /* 跳前导空白 */
while (o[i] && k < 64 && k + 1 < olen) {
char c = o[i++];
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
out[k++] = c;
else break;
}
out[k] = 0;
free(o);
return (k == 64) ? 0 : -1;
}
/* 比较两个 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 / weight(M2.6,缺省 "regular")。
* 文件缺失或解析不出则留空(name 空 → 调用方回退目录名;weight 空 → 回退 "regular")。 */
static void meow_font_json_probe(const char *json_path, char *name, int nlen,
char *sha_png, int slen, char *sha_idx, int xlen,
char *charset, int clen, int *glyph_count,
char *weight, int wlen)
{
name[0] = 0; sha_png[0] = 0; sha_idx[0] = 0;
if (charset) charset[0] = 0;
if (glyph_count) *glyph_count = 0;
if (weight) weight[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);
/* M2.5x 字体档位:charset(common|gbk|full)+ glyph_count(信息用,驱动档位分层/标注)。 */
if (charset && !charset[0]) {
char v[8] = "";
if (json_str(buf, "charset", v, sizeof v) > 0 && v[0])
snprintf(charset, clen, "%s", v);
}
if (glyph_count && *glyph_count == 0) {
char v[16] = "";
if (json_str(buf, "glyph_count", v, sizeof v) > 0 && v[0])
*glyph_count = atoi(v);
}
/* M2.6 字重:weight(regular|bold|italic|…),缺省 "regular"。 */
if (weight && !weight[0]) {
char v[32] = "";
if (json_str(buf, "weight", v, sizeof v) > 0 && v[0])
snprintf(weight, wlen, "%s", v);
}
}
fclose(f);
}
/* 字重 slug → 显示名(首字母大写、连字符/下划线转空格:regular→Regular、bold→Bold、italic→Italic)。 */
static void font_weight_display(const char *weight, char *out, int olen)
{
const char *w = (weight && weight[0]) ? weight : "regular";
out[0] = 0;
int o = 0, cap = 1;
for (const char *p = w; *p && o < olen - 1; p++) {
char c = *p;
if (c == '_' || c == '-') { if (o < olen - 1) out[o++] = ' '; cap = 1; continue; }
out[o++] = cap ? (char)((c >= 'a' && c <= 'z') ? c - 32 : c) : c;
cap = 0;
}
out[o] = 0;
}
/* 档位大小标记(设置页字体选项标注):common≈4k / gbk≈20k / full。
* 由 font.json 的 charset + glyph_count 派生;缺 charset 回退 common。 */
static void font_tier_marker(const char *charset, int glyph_count, char *out, int olen)
{
const char *tier = (charset && charset[0]) ? charset : "common";
if (!strcmp(tier, "full")) { snprintf(out, olen, "full"); return; }
if (glyph_count > 0)
snprintf(out, olen, "%s≈%dk", tier, (glyph_count + 500) / 1000);
else
snprintf(out, olen, "%s", tier);
}
/* 探测一套件是否合法:三件套齐全 → 解析 label+档位+字重。
* dirpath = <卡根>/MeowOS/fonts/<family>/<weight>(两级)或 .../<family>/<weight>/<tier>(三级)。
* tier_dir:三级时传该 tier 目录名(common/gbk/full,权威档位);两级传 NULL(档位读
* font.json charset,缺省 common)。
* 返回 1=进列表,0=跳过(缺组件/json 不可读)。sha 失败仅 WARN。
* 输出:label=显示名 `<name> <Weight>(common≈4k)`(如 `MiSans Regular(common≈4k)`);
* charset/glyph_count=档位信息;wtout=weight slug(缺省 "regular")。 */
static int meow_font_probe_weight(const char *dirpath, const char *family, const char *weight,
const char *tier_dir, int verify_sha,
char *label, int llen,
char *charset, int clen, int *glyph_count,
char *wtout, int wtlen, char *nameout, int namelen)
{
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);
const int is3 = (tier_dir != NULL && tier_dir[0] != 0);
const char *tsep = is3 ? "/" : "";
const char *tiernm = is3 ? tier_dir : "";
if (access(ppath, R_OK) != 0 || access(ipath, R_OK) != 0 || access(jpath, R_OK) != 0) {
logmsg("fonts: skip '%s/%s%s%s' (缺 font.png/font.idx/font.json)", family, weight, tsep, tiernm);
return 0;
}
char name[64] = "", jsha_png[80] = "", jsha_idx[80] = "";
char cs[8] = ""; int gc = 0; char wts[32] = "";
meow_font_json_probe(jpath, name, sizeof name, jsha_png, sizeof jsha_png,
jsha_idx, sizeof jsha_idx, cs, sizeof cs, &gc, wts, sizeof wts);
const char *wt = wts[0] ? wts : (weight ? weight : "regular");
/* 档位:三级定案 = tier 目录名(权威);两级(向后兼容)= font.json charset(缺省 common)。 */
const char *tier = is3 ? tier_dir : (cs[0] ? cs : "common");
if (wtout) snprintf(wtout, wtlen, "%s", wt);
if (nameout) snprintf(nameout, namelen, "%s", name[0] ? name : family);
/* 显示名 = `<name> <Weight>(common≈4k)`(字重缺省 Regular) */
char wd[24]; font_weight_display(wt, wd, sizeof wd);
char marker[24];
font_tier_marker(tier, gc, marker, sizeof marker);
snprintf(label, llen, "%s %s(%s)", name[0] ? name : family, wd, marker);
if (charset) snprintf(charset, clen, "%s", tier);
if (glyph_count) *glyph_count = gc;
/* 完整性校验:仅 WARN,不硬失败;该套仍标记可选。
* M3:verify_sha=0(进设置页重扫)跳过逐套 sha256sum(gbk/full 套数 MB,逐套双 sha256
* 真机耗数秒,入口卡顿主因之一);完整性校验保留给启动全量扫描(verify_sha=1)。 */
if (verify_sha) {
char apng[80] = "", aidx[80] = "";
if (font_sha256_file(ppath, apng, sizeof apng) != 0)
logmsg("fonts: '%s/%s%s%s' sha256(png) 不可用,跳过校验", family, wt, tsep, tiernm);
else if (jsha_png[0] && !font_sha_eq(apng, jsha_png))
logmsg("fonts: '%s/%s%s%s' WARN sha256_png 不匹配(仍可选)", family, wt, tsep, tiernm);
if (font_sha256_file(ipath, aidx, sizeof aidx) != 0)
logmsg("fonts: '%s/%s%s%s' sha256(idx) 不可用,跳过校验", family, wt, tsep, tiernm);
else if (jsha_idx[0] && !font_sha_eq(aidx, jsha_idx))
logmsg("fonts: '%s/%s%s%s' WARN sha256_idx 不匹配(仍可选)", family, wt, tsep, tiernm);
}
logmsg("fonts: add '%s/%s%s%s' (%s) tier=%s glyph_count=%d", family, wt, tsep, tiernm, label, tier, gc);
return 1;
}
/* M2.58 字体索引:CSV 字段写入(始终双引号包裹,内嵌双引号加倍)。返回写入长度。 */
static int font_index_csvq(char *dst, int olen, const char *s)
{
int o = 0;
if (olen <= 0) return 0;
if (o < olen) dst[o++] = '"';
for (; *s && o < olen - 3; s++) {
if (*s == '"') dst[o++] = '"';
if (o < olen - 2) dst[o++] = *s;
}
if (o < olen) dst[o++] = '"';
dst[o] = 0;
return o;
}
/* 把当前 font_presets* 扫描结果落盘为 <card_root>/MeowOS/config/fonts/index(覆盖)。
* 每套一条 `"family","weight","path","label","tier",glyph_count`;path=卡根相对 font.png。
* 目录不存在则 mkdir -p;无卡(mkdir/fopen 失败)→ 静默放弃(仅内存默认项)。
* 该文件是 SDK meow_font_get_available_count()/enumerate() 的持久数据源(规格 §5.7)。 */
static void meow_font_write_index(const char *card_root)
{
if (!card_root || !card_root[0]) return;
char t1[360], t2[380], base[400], path[420];
snprintf(t1, sizeof t1, "%s/MeowOS", card_root);
snprintf(t2, sizeof t2, "%s/MeowOS/config", card_root);
snprintf(base, sizeof base, "%s/MeowOS/config/fonts", card_root);
/* 无卡(card_root 父目录不存在 → ENOENT)→ 静默放弃:仅内存默认项,不刷系统日志。 */
if (mkdir(t1, 0755) != 0 && errno != EEXIST) {
if (errno != ENOENT) logmsg("fonts: index mkdir fail %s", t1);
return;
}
if (mkdir(t2, 0755) != 0 && errno != EEXIST) { logmsg("fonts: index mkdir fail %s", t2); return; }
if (mkdir(base, 0755) != 0 && errno != EEXIST) { logmsg("fonts: index mkdir fail %s", base); return; }
snprintf(path, sizeof path, "%s/index", base);
FILE *f = fopen(path, "w");
if (!f) { logmsg("fonts: index fopen fail %s", path); return; }
char line[1024];
for (int i = 0; i < font_presets_n; i++) {
const char *fam = font_preset_fams[i] ? font_preset_fams[i] : "default";
const char *wt = font_preset_wts[i] ? font_preset_wts[i] : "regular";
const char *val = font_presets[i] ? font_presets[i] : "MeowOS/fonts/default/regular/font.png";
const char *lbl = font_preset_labels[i] ? font_preset_labels[i] : fam;
const char *tier = font_preset_tiers[i] ? font_preset_tiers[i] : "common";
int o = 0;
o = font_index_csvq(line, sizeof line, fam);
line[o++] = ','; line[o] = 0;
o += font_index_csvq(line + o, sizeof line - (size_t)o, wt);
line[o++] = ','; line[o] = 0;
o += font_index_csvq(line + o, sizeof line - (size_t)o, val);
line[o++] = ','; line[o] = 0;
o += font_index_csvq(line + o, sizeof line - (size_t)o, lbl);
line[o++] = ','; line[o] = 0;
o += font_index_csvq(line + o, sizeof line - (size_t)o, tier);
line[o++] = ','; line[o] = 0;
snprintf(line + o, sizeof line - (size_t)o, "%d\n", font_preset_glyphs[i]);
fputs(line, f);
}
fclose(f);
logmsg("fonts: index written (%d entries) -> %s", font_presets_n, path);
}
/* 把一个探测成功的套装登记为 preset i(值路径 = MeowOS/fonts/<family>/<weight>[/<tier>]/font.png)。
* tier 非空 → 三级路径;否则 → 两级路径(旧结构向后兼容)。返回 0 成功,-1 已满。 */
static int font_presets_add(const char *family, const char *weight, const char *tier,
const char *label, const char *cs, int gc,
const char *wt, const char *nm)
{
if (font_presets_n >= FONT_PRESETS_MAX) return -1;
int i = font_presets_n;
if (tier && tier[0])
snprintf(font_preset_val_buf[i], sizeof font_preset_val_buf[i],
"%s/%s/%s/%s/font.png", MEOW_FONTS_CFG, family, weight, tier);
else
snprintf(font_preset_val_buf[i], sizeof font_preset_val_buf[i],
"%s/%s/%s/font.png", MEOW_FONTS_CFG, family, weight);
snprintf(font_preset_label_buf[i], sizeof font_preset_label_buf[i], "%s", label);
snprintf(font_preset_tier_buf[i], sizeof font_preset_tier_buf[i], "%s", cs && cs[0] ? cs : "common");
snprintf(font_preset_fam_buf[i], sizeof font_preset_fam_buf[i], "%s", family);
snprintf(font_preset_wt_buf[i], sizeof font_preset_wt_buf[i], "%s", wt && wt[0] ? wt : "regular");
snprintf(font_preset_name_buf[i], sizeof font_preset_name_buf[i], "%s", nm ? nm : family);
font_presets[i] = font_preset_val_buf[i];
font_preset_labels[i] = font_preset_label_buf[i];
font_preset_tiers[i] = font_preset_tier_buf[i];
font_preset_fams[i] = font_preset_fam_buf[i];
font_preset_wts[i] = font_preset_wt_buf[i];
font_preset_names[i] = font_preset_name_buf[i];
font_preset_glyphs[i] = gc;
font_presets_n++;
return 0;
}
/* 扫描 <card_root>/MeowOS/fonts/ 下字体,重建字体 preset 列表。
* 布局(FONT_SYSTEM 三级定案):
* - 两级(向后兼容):<family>/<weight>/font.png → 每 (family,weight) 一套,tier 读 font.json
* charset(缺省 common)。
* - 三级(定案):<family>/<weight>/<tier>/(tier=common/gbk/full,每 tier 一套三件套)→
* 每 (family,weight,tier) 一套,tier=目录名(权威)。
* - 同一 (family,weight) 下:若 weight 根有 font.png(两级)则按两级;否则扫其 <tier>/ 子目录。
* family 级 'default' 由索引 0(默认字体)单独处理,不进循环,避免重复。上限 FONT_PRESETS_MAX
* (1 默认 + 卡内各套,按 (family,weight,tier) 一套一 preset 计数)。无卡/无目录时仅默认。
*
* M2.58 释放语义(规格 §5.7 需求 4「扫→写 index→释放(或按需读 index)」结论):
* - 持久来源 = <card_root>/MeowOS/config/fonts/index(扫描后由 meow_font_write_index 落盘)。
* SDK 只读该文件,从不依赖本内存数组。
* - font_presets* / *_buf 均为「静态、有界(FONT_PRESETS_MAX=64)、非堆」数组:无内存泄漏,
* 故无运行期 free;「释放」含义 = 两个扫描点(启动 / 进设置页)重建 + 每套固定上限防膨胀。
* - 设置字体列表(font.file / font.class_* 下拉、family→字重子菜单)在本视图内显示时用
* 该数组;离开字体列表视图(run_settings_page 退出)无需显式 free(静态数组),下次
* 进设置/重扫自动重建。
* - ⚠️ 未做「启动后即清空数组」:meow_font_effective_paths_tiered(launch_app 大字库档位
* 选库)与 meow_font_reload_multifont 在运行期持续读 font_presets_n,清空会造成大字库
* 档位退化(red line「保持档位/多字体行为」)。如需「零驻留」,须把这几个核心路径改为
* 直接读 index(较大重构,超出本批边界,留待后续)。 */
void meow_font_scan_presets_root(const char *card_root, int verify_sha)
{
font_presets_n = 0;
font_presets[0] = "MeowOS/fonts/default/regular/font.png"; /* 默认字库 = MeowOS/fonts/default/regular */
font_preset_labels[0] = font_preset_label_buf[0]; /* 指向缓冲(运行时派生含字重+大小标记) */
font_preset_fams[0] = font_preset_fam_buf[0];
font_preset_wts[0] = font_preset_wt_buf[0];
font_preset_names[0] = font_preset_name_buf[0];
/* 默认套也读 font.json 档位+字重(存在则跟真实档,缺则 common/regular);供分层装载/标注。 */
{
char jp[400], cs[8] = ""; int gc = 0;
char nm0[64] = "", sp0[80] = "", si0[80] = "", wt0[32] = "";
snprintf(jp, sizeof jp, "%s/%s/default/regular/font.json", card_root, MEOW_FONTS_CFG);
meow_font_json_probe(jp, nm0, sizeof nm0, sp0, sizeof sp0, si0, sizeof si0,
cs, sizeof cs, &gc, wt0, sizeof wt0);
char mk[24]; font_tier_marker(cs, gc, mk, sizeof mk);
char wd[24]; font_weight_display(wt0[0] ? wt0 : "regular", wd, sizeof wd);
snprintf(font_preset_label_buf[0], sizeof font_preset_label_buf[0], "默认字体 %s(%s)", wd, mk);
snprintf(font_preset_tier_buf[0], sizeof font_preset_tier_buf[0], "%s", cs[0]?cs:"common");
snprintf(font_preset_fam_buf[0], sizeof font_preset_fam_buf[0], "default");
snprintf(font_preset_wt_buf[0], sizeof font_preset_wt_buf[0], "%s", wt0[0]?wt0:"regular");
snprintf(font_preset_name_buf[0], sizeof font_preset_name_buf[0], "默认字体");
font_preset_tiers[0] = font_preset_tier_buf[0];
font_preset_glyphs[0] = gc;
}
font_presets_n = 1;
char root[340];
snprintf(root, sizeof root, "%s/%s", card_root, MEOW_FONTS_CFG);
DIR *d = opendir(root);
if (d) { /* 无卡/无字库目录 → 跳过扫描,仅默认 */
struct dirent *de;
while ((de = readdir(d)) && font_presets_n < FONT_PRESETS_MAX) {
if (de->d_name[0] == '.') continue;
if (!strcmp(de->d_name, "default")) continue; /* 默认字体由索引 0 单独处理 */
char fampath[400];
snprintf(fampath, sizeof fampath, "%s/%s", root, de->d_name);
DIR *w = opendir(fampath); /* 一级:<family>/ */
if (!w) continue;
struct dirent *we;
while ((we = readdir(w)) && font_presets_n < FONT_PRESETS_MAX) {
if (we->d_name[0] == '.') continue;
char dirpath[400], pngcheck[420];
snprintf(dirpath, sizeof dirpath, "%s/%s", fampath, we->d_name);
snprintf(pngcheck, sizeof pngcheck, "%s/font.png", dirpath);
char label[96] = "", cs[8] = ""; int gc = 0; char wt[32] = "", nm[64] = "";
if (access(pngcheck, R_OK) == 0) {
/* 两级(旧结构向后兼容):<family>/<weight>/font.png,tier 读 font.json charset。 */
if (meow_font_probe_weight(dirpath, de->d_name, we->d_name, NULL, verify_sha,
label, sizeof label, cs, sizeof cs, &gc,
wt, sizeof wt, nm, sizeof nm) == 0)
continue; /* 缺组件/损坏 → 跳过该套 */
font_presets_add(de->d_name, we->d_name, NULL, label, cs, gc, wt, nm);
continue;
}
/* 三级定案:进入 <weight>/<tier>/ 逐套探测,tier=目录名(权威档位)。 */
DIR *t = opendir(dirpath);
if (!t) continue;
struct dirent *te;
while ((te = readdir(t)) && font_presets_n < FONT_PRESETS_MAX) {
if (te->d_name[0] == '.') continue;
/* tier 仅接受 common/gbk/full(FONT_SYSTEM §1);杂散子目录不进列表。 */
if (strcmp(te->d_name, "common") && strcmp(te->d_name, "gbk") &&
strcmp(te->d_name, "full")) continue;
char tpath[420];
snprintf(tpath, sizeof tpath, "%s/%s", dirpath, te->d_name);
if (meow_font_probe_weight(tpath, de->d_name, we->d_name, te->d_name, verify_sha,
label, sizeof label, cs, sizeof cs, &gc,
wt, sizeof wt, nm, sizeof nm) == 0)
continue; /* 缺组件/损坏 → 跳过该 tier 套 */
font_presets_add(de->d_name, we->d_name, te->d_name, label, cs, gc, wt, nm);
}
closedir(t);
}
closedir(w);
}
closedir(d);
}
/* M2.58:扫描结果落盘为 <card_root>/MeowOS/config/fonts/index(含默认行 index0),
* 作为 SDK meow_font_get_available_count()/enumerate() 的持久数据源;无卡时仅默认 1 行,
* 由 meow_font_write_index 内部 mkdir-fail 静默放弃(此路径不刷屏)。
* M2.82+ 修补:挂载点目录在无卡时也存在(mkdir 骗过 ENOENT 检查)→ 开机早扫曾把
* 卡上 18 条 index 覆盖成 1 条默认。故:fonts 根目录根本没打开(opendir 失败)且
* 只剩默认项时,判定为「卡在但字库目录不可读/未就绪」→ 不写 index,保留旧索引。 */
if (!d && font_presets_n <= 1) {
logmsg("fonts: scan root unavailable, keep existing index (%d presets in mem)", font_presets_n);
return;
}
meow_font_write_index(card_root);
}
/* 设备默认卡根扫描(等价现状:无卡时 opendir 失败 → 仅默认字体)。
* verify_sha=1:启动/热插拔全量扫描(含逐套 sha256 完整性校验)。
* verify_sha=0:进设置页重扫(只枚举+读 font.json+落 index,跳过 sha256 —— 入口不许卡数秒)。 */
void meow_font_scan_presets(int verify_sha)
{
meow_font_scan_presets_root("/usr/data/mnt/sd_0", verify_sha);
}
#if 0 /* ================================================================
* M7.0b 主题退役:launcher 不再自持 4 套静态色板 —— s_ui_theme() 改读 SDK
* meow_theme_export()(单一权威:MD3/MIUIx × 明暗 由 SDK meow_theme 解析)。
* 本块保留作回退位(删除 #if 0 行即可恢复旧手绘多色板路径)。
* ================================================================ */
/* MD3 tonal palette(565 参考值见 MD3_UI_SPEC §1;此处存 24-bit RGB,#hex) */
static const meow_ui_theme_t meow_ui_theme_dark = {
0x141824, 0x1E2436, 0x263038, 0x4A6A9A, 0xFFFFFF,
0x9AA4C8, 0x2A2E38, 0xE57373, 0x4A6A9A
};
static const meow_ui_theme_t meow_ui_theme_light = {
0xE8EAF0, 0xD5D9E4, 0xCFD4E0, 0x2A5A8A, 0x1A1A2A,
0x4A4F5A, 0x9AA4C8, 0xB3261E, 0x2A5A8A
};
/* MIUIx 族(§8.1.3 定案):暖色基面 + 小米橙 primary,供 launcher 主界面/设置页/通知中心/
* 电源菜单(s_ui_theme())在 ui.style=miuix 时使用(与 SDK meow_theme MIUIx 同色系)。 */
static const meow_ui_theme_t meow_ui_theme_miui_dark = {
0x181310, 0x2A231C, 0x2E2620, 0xFF8C4A, 0xEDE5DC,
0xAB9884, 0x4A3F35, 0xFFB4AB, 0xFF8C4A
};
static const meow_ui_theme_t meow_ui_theme_miui_light = {
0xF7F3EE, 0xE9DFD0, 0xF4EDE3, 0xF57200, 0x251A10,
0x7D6A5A, 0xCBBFB2, 0xB3261E, 0xF57200
};
/* 获取当前本地时刻的「当天第几分钟」(tm_hour*60+tm_min);供时段判定用 */
static int s_now_min_of_day(void)
{
time_t t = time(NULL);
struct tm tm;
localtime_r(&t, &tm);
return tm.tm_hour * 60 + tm.tm_min;
}
/* 深色时段判定(纯逻辑,便于单测):now_min ∈ [start, end) → 深色。
* - start==end 视为未配置,返回 -1(由调用方决定回退策略)。
* - start<end:非跨天,如 720→1080 = 12:00→18:00,now ∈ [720,1080) 为深色。
* - start>end:跨午夜,如 1140→420 = 19:00→次日07:00,now∈[1140,1440)∪[0,420) 为深色。
* - 边界:end 半开(恰好 end 分回到浅色),start 闭(恰好 start 分进入深色)。 */
static int theme_should_dark(int now_min, int start, int end)
{
if (start == end) return -1; /* 未配置 */
if (start < end)
return (now_min >= start && now_min < end) ? 1 : 0;
return (now_min >= start || now_min < end) ? 1 : 0; /* 跨午夜 */
}
/* 当前主题(style×明暗 两套;接 ui.style + display.theme_mode + display.theme)。
* style(§8.1.1):ui.style 的 md3/miuix → 返回对应体系(MD3 tonal / MIUIx 暖色+橙)。
* manual(mode==THEME_MODE_MANUAL):直读 display.theme(dark/light),保持原有行为。
* auto(mode==THEME_MODE_AUTO):读深色时段两键 + 当前本地分钟;start==end 未配置 →
* 回退 manual(读 display.theme,避免误判全天深色)。
* 读取失败一律回退 dark。 */
const meow_ui_theme_t *s_ui_theme(void)
{
int style = 0; /* ui.style:0=md3 / 1=miuix */
if (meow_settings_get_enum("ui.style", &style) != 0) style = 0;
const meow_ui_theme_t *light = (style == 1) ? &meow_ui_theme_miui_light : &meow_ui_theme_light;
const meow_ui_theme_t *dark = (style == 1) ? &meow_ui_theme_miui_dark : &meow_ui_theme_dark;
int mode = THEME_MODE_MANUAL;
if (meow_settings_get_enum("display.theme_mode", &mode) != 0) mode = THEME_MODE_MANUAL;
int v = 0; /* display.theme(manual)当前索引 */
int have_manual = (meow_settings_get_enum("display.theme", &v) == 0);
if (mode == THEME_MODE_AUTO) {
int start = 0, end = 0;
meow_settings_get_int("display.theme_dark_start", &start);
meow_settings_get_int("display.theme_dark_end", &end);
int d = theme_should_dark(s_now_min_of_day(), start, end);
if (d < 0) { /* 未配置:回退 manual(display.theme) */
if (have_manual && v == 1) return light;
return dark;
}
return d ? dark : light;
}
/* manual:照旧读 display.theme */
if (have_manual && v == 1) return light;
return dark;
}
#endif /* M7.0b 退役:launcher 4 套静态色板 → SDK meow_theme 单一权威 */
/* M7.0b 主题薄壳:launcher 主题 = SDK meow_theme_export()(MD3/MIUIx × 明暗 由 SDK 解析,
* launcher 侧不再读 ui.style/display.theme 自行选板)。只做一次拷贝 + 9 字段映射到 launcher
* 绘制便利结构 meow_ui_theme_t(§8.1.5)。返回静态缓存(调用方立即使用,不长期持有)。
* 注:meow_theme_export() 在 g_loaded==0 时先读 settings.conf,宿主/脱机也可靠。 */
const meow_ui_theme_t *s_ui_theme(void)
{
meow_theme th;
static meow_ui_theme_t s;
meow_theme_export(&th);
/* 映射:SDK meow_theme → launcher meow_ui_theme_t(9 字段,§8.1.5) */
s.surface = th.surface;
s.surface_variant = th.surfaceVariant;
s.surface_container = th.surfaceContainer;
s.primary = th.primary;
s.on_surface = th.onSurface;
s.on_surface_variant = th.onSurfaceVariant;
s.outline = th.outline;
s.error = th.error;
s.surface_tint = th.surfaceTint;
return &s;
}