/* * meow_ui_theme.c — 主题/配置(B1.5 / M2.46)。 * * 内置日间/夜间两套色板;模式跟随系统设置键 display.theme_mode + display.theme * (/usr/data/meow/settings.conf,与 launcher meow_settings 同源)。 * 判定与 launcher 的 s_ui_theme() 完全对齐(避免两套逻辑漂移): * - display.theme_mode = manual:直读 display.theme(dark/light)决定深浅。 * - display.theme_mode = auto:按深色时段 display.theme_dark_start/end(分钟 0-1439), * 半开区间 [start,end);start>end 跨午夜;start==end 视为未配置 → 回退 manual(display.theme)。 * 读取缺失/非法一律回退深色(与 launcher 默认一致)。 * 切换即广播 listener。AUTO 解析只在查询时按当前系统时间计算(不缓存)。 */ #include "meow_ui.h" #include "meow_ui_internal.h" /* meow_theme_reload() 定义(供 launcher 逼重读 settings) */ #include #include #include #include #include #define MEOW_SETTINGS_FILE "/usr/data/meow/settings.conf" /* ---- 主题表(§5.8.4 + §8.1.3):[style][dark] ——— * 0=MEOW_STYLE_MD3, 1=MEOW_STYLE_MIUIX;dark 0=light, 1=dark。 * 旧字段 bg/fg/accent/dim = surface/onSurface/primary/onSurfaceVariant(映射见 meow_ui.h)。 * bg/surface 随后由「背景全局源」(display.background,§8.1.4)按 style×mode 覆写。 */ static meow_theme g_theme[2][2]; static meow_theme_mode g_mode = MEOW_THEME_DARK; /* 配置生效的判定模式(默认深色,与 launcher 默认一致) */ static meow_theme_mode g_manual = MEOW_THEME_DARK; /* display.theme 解析出的手动深浅;auto 未配置时段时回退用之 */ static int g_dark_start = 0; /* 深色时段开始(分钟 0-1439) */ static int g_dark_end = 0; /* 深色时段结束(分钟 0-1439) */ static int g_loaded = 0; static int g_style = MEOW_STYLE_MD3; /* 顶级主题风格族(§8.1.2):默认 MD3 */ static char g_background[64] = ""; /* display.background(black/blue/green/white/… 或图片路径) */ static meow_theme_listener g_listeners[8]; static int g_nlisteners = 0; /* ---- 读 /usr/data/meow/settings.conf 的 key=value(兼容 "key = value" 带空格形式)---- * 与 launcher meow_settings_import 同款解析:按 '=' 切分,key/value 各自去首尾空白, * 精确匹配 key(避免 display.theme 误匹配 display.theme_mode)。返回 0=命中,-1=未命中。 */ static int get_setting_str(const char *key, char *buf, int buflen) { FILE *f = fopen(MEOW_SETTINGS_FILE, "r"); if (!f || buflen <= 0) return -1; char line[160]; int kl = (int)strlen(key); int found = 0; while (fgets(line, sizeof line, f)) { char *eq = strchr(line, '='); if (!eq) continue; /* key = 行首到 '=',去尾空白 */ char *k = line; while (*k == ' ' || *k == '\t') k++; int n = (int)(eq - k); while (n > 0 && (k[n-1] == ' ' || k[n-1] == '\t')) n--; if (n != kl || strncmp(k, key, (size_t)kl) != 0) continue; /* value = '=' 后,去首尾空白 / 行尾 CRLF */ const char *v = eq + 1; while (*v == ' ' || *v == '\t') v++; int m = 0; while (*v && *v != '\n' && *v != '\r' && m < buflen - 1) buf[m++] = *v++; while (m > 0 && (buf[m-1] == ' ' || buf[m-1] == '\t')) m--; buf[m] = 0; found = 1; break; } fclose(f); return found ? 0 : -1; } static int get_setting_int(const char *key, int *out) { char buf[32]; if (get_setting_str(key, buf, sizeof buf) != 0) return -1; *out = atoi(buf); return 0; } /* 解析 display.theme 的值(dark/light,非索引)→ LIGHT/DARK;未知/缺失回退 DARK(launcher 默认)。 */ static meow_theme_mode parse_manual(const char *v) { while (*v == ' ' || *v == '\t') v++; if (!strncmp(v, "light", 5)) return MEOW_THEME_LIGHT; return MEOW_THEME_DARK; /* "dark" / 未知 → 深色 */ } /* ---- 填充 4 张主题表的内建色板(草案值,MD3 默认;MIUIx 用 MD3 基础做占位)---- * bg/surface 本函数填默认面;随后 apply_background() 按 display.background 覆写。 */ static void init_defaults(void) { meow_theme *t; /* MD3 light */ t = &g_theme[MEOW_STYLE_MD3][0]; t->bg = 0xE8EAF0; t->fg = 0x1A1A2A; t->accent = 0x2A5A8A; t->dim = 0x4A4F5A; t->font_title = 28; t->font_body = 20; t->primary = 0x2A5A8A; t->onPrimary = 0xFFFFFF; t->primaryContainer = 0xC9E2F5; t->onPrimaryContainer = 0x0A2B46; t->secondary = 0x53626E; t->onSecondary = 0xFFFFFF; t->secondaryContainer = 0xD6E5F2; t->onSecondaryContainer = 0x101E28; t->tertiary = 0x635B78; t->onTertiary = 0xFFFFFF; t->tertiaryContainer = 0xE9DEFB; t->onTertiaryContainer = 0x1E1734; t->surface = 0xE8EAF0; t->onSurface = 0x1A1A2A; t->surfaceVariant = 0xDDE1EA; t->onSurfaceVariant = 0x4A4F5A; t->surfaceContainerLowest = 0xFFFFFF; t->surfaceContainerLow = 0xF2F3F7; t->surfaceContainer = 0xECEEF3; t->surfaceContainerHigh = 0xE6E8EE; t->surfaceContainerHighest = 0xE1E3E9; t->surfaceDim = 0xD8DAE0; t->surfaceBright = 0xFFFFFF; t->surfaceTint = 0x2A5A8A; t->outline = 0x74777F; t->outlineVariant = 0xC4C6D0; t->scrim = 0x000000; t->error = 0xBA1A1A; t->onError = 0xFFFFFF; t->errorContainer = 0xFFDAD6; t->onErrorContainer = 0x410002; t->inverseSurface = 0x2F3036; t->inverseOnSurface = 0xF0F0F5; t->inversePrimary = 0xA6C8EE; t->shadow = 0x000000; t->radius = 12; t->radius_card = 16; t->spacing = 8; t->stroke_w = 0; /* MD3 dark */ t = &g_theme[MEOW_STYLE_MD3][1]; t->bg = 0x141824; t->fg = 0xFFFFFF; t->accent = 0x4A6A9A; t->dim = 0x9AA4C8; t->font_title = 28; t->font_body = 20; t->primary = 0x4A6A9A; t->onPrimary = 0xFFFFFF; t->primaryContainer = 0x2A3A5C; t->onPrimaryContainer = 0xD5E4F5; t->secondary = 0xB8C6D2; t->onSecondary = 0x27323B; t->secondaryContainer = 0x3A4650; t->onSecondaryContainer = 0xD6E5F2; t->tertiary = 0xCDC2E6; t->onTertiary = 0x372C52; t->tertiaryContainer = 0x4E4269; t->onTertiaryContainer = 0xE9DEFB; t->surface = 0x141824; t->onSurface = 0xFFFFFF; t->surfaceVariant = 0x2A2F3C; t->onSurfaceVariant = 0x9AA4C8; t->surfaceContainerLowest = 0x0F121C; t->surfaceContainerLow = 0x1C2030; t->surfaceContainer = 0x20253A; t->surfaceContainerHigh = 0x262C44; t->surfaceContainerHighest = 0x2C334E; t->surfaceDim = 0x12151E; t->surfaceBright = 0x3A3E50; t->surfaceTint = 0x4A6A9A; t->outline = 0x8B93A3; t->outlineVariant = 0x3E4450; t->scrim = 0x000000; t->error = 0xFFB4AB; t->onError = 0x690005; t->errorContainer = 0x93000A; t->onErrorContainer = 0xFFDAD6; t->inverseSurface = 0xF0F0F5; t->inverseOnSurface = 0x2F3036; t->inversePrimary = 0xA6C8EE; t->shadow = 0x000000; t->radius = 12; t->radius_card = 16; t->spacing = 8; t->stroke_w = 0; /* MIUIx = MD3 基础(占位),primary 更饱和、圆角/间距更大、细描边(§8.1.3 可感知差异)。 * 先整表复制 MD3,再覆写差异字段。 */ g_theme[MEOW_STYLE_MIUIX][0] = g_theme[MEOW_STYLE_MD3][0]; g_theme[MEOW_STYLE_MIUIX][0].primary = 0x2A5AC8; g_theme[MEOW_STYLE_MIUIX][0].accent = 0x2A5AC8; g_theme[MEOW_STYLE_MIUIX][0].primaryContainer = 0xD6E1FA; g_theme[MEOW_STYLE_MIUIX][0].surfaceTint = 0x2A5AC8; g_theme[MEOW_STYLE_MIUIX][0].radius = 18; g_theme[MEOW_STYLE_MIUIX][0].radius_card = 24; g_theme[MEOW_STYLE_MIUIX][0].spacing = 10; g_theme[MEOW_STYLE_MIUIX][0].stroke_w = 1; g_theme[MEOW_STYLE_MIUIX][1] = g_theme[MEOW_STYLE_MD3][1]; g_theme[MEOW_STYLE_MIUIX][1].primary = 0x4A7AE8; g_theme[MEOW_STYLE_MIUIX][1].accent = 0x4A7AE8; g_theme[MEOW_STYLE_MIUIX][1].primaryContainer = 0x2A3A6C; g_theme[MEOW_STYLE_MIUIX][1].surfaceTint = 0x4A7AE8; g_theme[MEOW_STYLE_MIUIX][1].radius = 18; g_theme[MEOW_STYLE_MIUIX][1].radius_card = 24; g_theme[MEOW_STYLE_MIUIX][1].spacing = 10; g_theme[MEOW_STYLE_MIUIX][1].stroke_w = 1; } /* ---- 背景全局源(§8.1.4):display.background 是「style×mode 下背景底选项集」的当前值, * 决定主题 surface/bg(深浅成对)。按名映射 (dark_surface, light_surface): * dark 默认/black → 0x141824 / light 默认/white → 0xE8EAF0(既有默认,保证 App 无回归); * dark blue → 0x0A1428 / light blue → 0xE5EEFB;green → 0x0A2814 / 0xE6F6E8。 * 图片路径(*.png/jpg/jpeg):背景图即观感 → 用默认 surface(本批草案)。 */ static int bg_is_image(const char *v) { while (*v == ' ' || *v == '\t') v++; const char *dot = strrchr(v, '.'); if (!dot || dot == v) return 0; return !strncasecmp(dot, ".png", 4) || !strncasecmp(dot, ".jpg", 4) || !strncasecmp(dot, ".jpeg", 5); } static void bg_surfaces(const char *v, uint32_t *dark, uint32_t *light) { *dark = 0x141824; *light = 0xE8EAF0; /* 默认(未知/图片/空)→ 既有默认面 */ while (*v == ' ' || *v == '\t') v++; if (bg_is_image(v)) return; /* 背景图:用默认 */ if (!strncmp(v, "blue", 4)) { *dark = 0x0A1428; *light = 0xE5EEFB; return; } if (!strncmp(v, "green", 5)) { *dark = 0x0A2814; *light = 0xE6F6E8; return; } if (!strncmp(v, "white", 5)) { *dark = 0x141824; *light = 0xFFFFFF; return; } if (!strncmp(v, "lightblue", 9)) { *dark = 0x0A1428; *light = 0xE5EEFB; return; } if (!strncmp(v, "lightgreen", 10)) { *dark = 0x0A2814; *light = 0xE6F6E8; return; } /* black / 其它未知 → 默认面 */ } static void apply_background(void) { uint32_t ds, ls; bg_surfaces(g_background, &ds, &ls); for (int s = 0; s < 2; s++) { g_theme[s][0].bg = g_theme[s][0].surface = ls; /* light 行 */ g_theme[s][1].bg = g_theme[s][1].surface = ds; /* dark 行 */ } } static void load_settings(void) { char buf[64]; g_manual = MEOW_THEME_DARK; g_mode = MEOW_THEME_DARK; g_dark_start = 0; g_dark_end = 0; /* display.theme:手动深浅(auto 未配置时段时也回退这里) */ if (get_setting_str("display.theme", buf, sizeof buf) == 0) g_manual = parse_manual(buf); /* display.theme_mode:manual=读 display.theme;auto=时段。缺失/非法 → manual */ if (get_setting_str("display.theme_mode", buf, sizeof buf) == 0) { if (!strcmp(buf, "auto")) g_mode = MEOW_THEME_AUTO; else g_mode = g_manual; /* manual(含 "light"/"dark"/未知)→ 读 display.theme */ } else { g_mode = g_manual; /* 无 theme_mode 键 → 默认 manual → display.theme */ } get_setting_int("display.theme_dark_start", &g_dark_start); get_setting_int("display.theme_dark_end", &g_dark_end); /* 顶级主题风格族(§8.1.2 minimal):ui.style 的 md3/miuix。读取缺失/未知 → 回退 MD3。 * 决定状态栏右侧图标集(§3.1 同一个状态栏);配色日/夜仍由 display.theme 现有解析管。 */ g_style = MEOW_STYLE_MD3; if (get_setting_str("ui.style", buf, sizeof buf) == 0) { const char *v = buf; while (*v == ' ' || *v == '\t') v++; if (!strncmp(v, "miuix", 5) || !strncmp(v, "miui", 4)) g_style = MEOW_STYLE_MIUIX; } /* 背景全局源(§8.1.4):display.background 为 style×mode 下背景底选项集的当前值。 * 读取缺失/未知 → ""(默认面)。 */ g_background[0] = 0; if (get_setting_str("display.background", g_background, sizeof g_background) != 0) g_background[0] = 0; /* 载入/覆写 4 张主题表(色板 + style×mode 背景 surface/bg) */ init_defaults(); apply_background(); } /* 当前本地时刻的「当天第几分钟」(tm_hour*60+tm_min);供时段判定用(与 launcher s_now_min_of_day 一致)。 */ static int 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; } /* 深色时段判定(与 launcher theme_should_dark 完全一致):now_min ∈ [start,end) → 深色。 * - start==end 视为未配置,返回 -1(由调用方决定回退策略)。 * - startend:跨午夜(如 1140→420 = 19:00→次日07:00)。 * - 边界: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; /* 跨午夜 */ } static meow_theme_mode resolve_mode(meow_theme_mode m) { if (m != MEOW_THEME_AUTO) return m; int d = theme_should_dark(now_min_of_day(), g_dark_start, g_dark_end); if (d < 0) return g_manual; /* 时段未配置:回退 display.theme 手动深浅 */ return d ? MEOW_THEME_DARK : MEOW_THEME_LIGHT; } static void broadcast(void) { for (int i = 0; i < g_nlisteners; i++) if (g_listeners[i]) g_listeners[i](g_mode); } /* 当前「style×mode」主题:style=0/1(MD3/MIUIX),dark=0(light)/1(dark)。 */ static const meow_theme *theme_current(int style, int dark) { if (style < MEOW_STYLE_MD3) style = MEOW_STYLE_MD3; if (style > MEOW_STYLE_MIUIX) style = MEOW_STYLE_MIUIX; return &g_theme[style][dark ? 1 : 0]; } const meow_theme *meow_theme_get(void) { if (!g_loaded) { load_settings(); g_loaded = 1; } return theme_current(g_style, resolve_mode(g_mode) == MEOW_THEME_DARK); } meow_theme_mode meow_theme_get_mode(void) { if (!g_loaded) { load_settings(); g_loaded = 1; } return g_mode; } void meow_theme_set_mode(meow_theme_mode m) { if (!g_loaded) { load_settings(); g_loaded = 1; } if (m < MEOW_THEME_LIGHT || m > MEOW_THEME_AUTO) return; if (m == g_mode) return; g_mode = m; broadcast(); } /* 重置缓存:下一次 meow_theme_get()/get_mode()/defaults() 重读 settings.conf。 * launcher 在 statusbar 刷新前调用,让 SDK 全局状态栏(其主题缓存于 g_loaded)跟手 * launcher 侧主题变更(settings.conf 被改后立即反映),避免「界面已换肤、状态栏滞留旧主题」。 */ void meow_theme_reload(void) { g_loaded = 0; } meow_theme meow_theme_defaults(meow_theme_mode m) { if (!g_loaded) { load_settings(); g_loaded = 1; } /* 脱机取内置板:返回 MD3(默认)style 下解析出的模式表。 */ return g_theme[MEOW_STYLE_MD3][resolve_mode(m) == MEOW_THEME_DARK ? 1 : 0]; } /* ---- §8.1.2 顶级主题风格族(minimal)---- * meow_ui_style() 返回当前 ui.style(读取走 load_settings,随 meow_theme_reload() 重读, * 故 launcher 改 settings.conf 后经 sb_sdk_refresh→theme_reload 即跟手)。 * meow_ui_set_style() 记录 style(越界 clamp;同值忽略)并触发 reload/broadcast——供需要 * 显式切换的调用方(后续 launcher 设置页切换可改走此路径);状态栏图标集据此换集。 */ int meow_ui_style(void) { if (!g_loaded) { load_settings(); g_loaded = 1; } return g_style; } void meow_ui_set_style(int style) { if (style < MEOW_STYLE_MD3) style = MEOW_STYLE_MD3; if (style > MEOW_STYLE_MIUIX) style = MEOW_STYLE_MIUIX; if (style == g_style) return; g_style = style; /* 触发下一帧按新 style 重绘(§8.1.2):g_theme[s][*] 是独立表(不随 g_style 重算), * g_style 记录后 meow_theme_get() 立即可取新 style×mode 表;只需广播主题 listener, * 让 App/状态栏/控件下一帧用新 style 重绘/换图标集。不清 settings 缓存(避免覆盖程序化 style)。 */ broadcast(); } void meow_theme_add_listener(meow_theme_listener cb) { if (!cb) return; if (g_nlisteners >= (int)(sizeof g_listeners / sizeof g_listeners[0])) return; for (int i = 0; i < g_nlisteners; i++) if (g_listeners[i] == cb) return; g_listeners[g_nlisteners++] = cb; } void meow_theme_remove_listener(meow_theme_listener cb) { for (int i = 0; i < g_nlisteners; i++) { if (g_listeners[i] == cb) { g_listeners[i] = g_listeners[g_nlisteners - 1]; g_nlisteners--; return; } } }