Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/meow_settings.c
T

800 lines
32 KiB
C
Raw 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.
/* meow_settings.c — MeowOS 系统设置后端(阶段 B2a)
*
* 实现与设计:SYSTEM_SETTINGS.md §1/§2/§3 + MEOWOS_API.md §2。
* - 统一存储:板载 /usr/data/meow/settings.conf(UBIFS,KB 级小文件,扁平 key=value)。
* 导入/导出(备份)走同一后端;默认备份路径 TF 卡 MeowOS/backups/settings/setting.bak。
* - 单点数据源:全部设置键存于本模块内存表(名称/类型/默认/取值域/当前值),
* 实体 UI / Web 面板 / CLI 全走 meow_settings_* API,不留旁路。
* - clamp/校验集中 set(亮度 auto clamp >= display.brightness_min;枚举越界拒绝;
* str 截断 MEOW_SET_MAX_LEN);set 成功后标记脏 + 向 listener 广播(key 字符串)。
* - 落盘:meow_settings_save()(写板载 settings.conf,保留 "# meow-settings 1" 头)。
* 为避免 UBIFS 高频写,调用方在低频点(如音量渐变结束)才落盘一次。
*
* 空实现风格:本文件自包含(不依赖 launcher 主模块的 logmsg —— 现归 util.c),文件 I/O 失败仅打点,
* 绝不崩(内存继续用,符合 SYSTEM_SETTINGS.md §8「无卡/无板载用内存缓存」)。
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h> /* strcasecmp */
#include <stdarg.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include "meow_settings.h"
/* 存储路径(板载 UBIFS 小文件)/ 默认备份路径(TF 卡)。
* 允许编译期覆盖(供宿主机单元测试指向临时文件;设备上默认不变)。 */
#ifndef MEOW_SETTINGS_FILE
#define MEOW_SETTINGS_FILE "/usr/data/meow/settings.conf"
#endif
#ifndef MEOW_SETTINGS_BAK
#define MEOW_SETTINGS_BAK "/usr/data/mnt/sd_0/MeowOS/backups/settings/setting.bak"
#endif
/* 表/监听器/临时缓冲上限 */
#define MEOW_SET_MAX_KEYS 64
#define MEOW_SET_MAX_LISTENERS 16
#define MEOW_SET_NAME_LEN 48
#define MEOW_SET_ENUM_LEN 96
#define MEOW_SET_LABELS_LEN 160
#define MEOW_SET_ENUM_MAX_OPT 8
/* 一个设置项:名称/类型/默认/当前值(统一字符串)+ clamp 域 + 枚举名/标签 */
typedef struct meow_setting {
char key[MEOW_SET_NAME_LEN];
meow_set_type type;
char def[MEOW_SET_MAX_LEN];
char cur[MEOW_SET_MAX_LEN];
int min, max; /* SET_INT 取值域(max<=0 表示仅保底、无上界) */
char enum_names[MEOW_SET_ENUM_LEN];
char enum_labels[MEOW_SET_LABELS_LEN];
int dirty;
} meow_setting;
static meow_setting g_sets[MEOW_SET_MAX_KEYS];
static int g_nsets = 0;
static meow_settings_listener g_listeners[MEOW_SET_MAX_LISTENERS];
static int g_nlisteners = 0;
static int g_inited = 0;
static int g_broadcasting = 0; /* 防 listener 递归 set */
/* ---- 默认值表:与 SYSTEM_SETTINGS.md §1 逐键一致(新增设置照附录扩展)---- */
static const meow_setting_def g_defs[] = {
/* 显示 */
{ "display.brightness", SET_INT, "101", 0, 255, NULL, NULL },
{ "display.brightness_min", SET_INT, "1", 0, 255, NULL, NULL },
{ "display.screen_off_mode", SET_ENUM, "panel",0, 0, "panel,black", "面板下电,背光全黑" },
{ "display.screen_off_timeout", SET_INT, "30", 0, 86400, NULL, NULL },
{ "display.refresh_rate", SET_ENUM, "60", 0, 0, "60,30,15", "60Hz,30Hz,15Hz" },
{ "display.refresh_always_max", SET_BOOL, "0", 0, 0, NULL, NULL },
{ "display.theme", SET_ENUM, "dark", 0, 0, "dark,light", "深色,浅色" },
/* B2b 主题三方式:display.theme_mode 决定判定方式(manual=读 display.theme;
* auto=按深色时段)。时段两键为分钟数 0-1439,start==end 视为未配置(回退 manual)。
* 注释:SYSTEM_SETTINGS.md §1 同步新增三行。 */
{ "display.theme_mode", SET_ENUM, "manual", 0, 0, "manual,auto", "手动,时段自动" },
{ "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.79 主界面背景图片叠加层强度(int 0-255,缺省 128):图片背景上叠 黑(深色)/白(浅色)
* 半透明遮罩,保证控件/文字可读。仅图片背景(display.background=路径)时生效。 */
{ "display.bg_overlay_alpha", SET_INT, "128", 0, 255, NULL, NULL },
/* M2.79 主界面布局卡片宫格/列表(card=0 默认 / list=1),设置页切换写 settings.conf。 */
{ "ui.home_layout", SET_ENUM, "card", 0, 0, "card,list", "卡片宫格,列表" },
/* 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 },
{ "power.defeat_hotkey", SET_STR, "power_voldown", 0,0, NULL, NULL },
/* 开机 */
{ "boot.default", SET_ENUM, "meow", 0, 0, "meow,hiby", "MeowOS,HiByOS" },
{ "boot.menu_enabled", SET_BOOL, "1", 0, 0, NULL, NULL },
{ "boot.timeout_ms", SET_INT, "5000", 0, 0, NULL, NULL },
/* 输入 */
{ "input.hotkey_seq", SET_STR, "volup,volup,volup", 0,0, NULL, NULL },
{ "input.hotkey_window_ms", SET_INT, "2500", 0, 0, NULL, NULL },
{ "input.touch_arm_delay_ms", SET_INT, "700", 0, 0, NULL, NULL },
/* M2.38:设置页滑动方向切换(0=标准:手指上滑→列表往下翻;1=反向) */
{ "input.swipe_invert", SET_BOOL, "0", 0, 0, NULL, NULL },
/* 字体/主题 */
{ "font.file", SET_STR, "MeowOS/fonts/default/regular/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 },
/* M2.5x 字体档位分层 / 按需大字库:font.load_full(int 0/1 默认 0)=「加载大字库」开关,
* 1=强制全系统用大字库 gbk≈20k 档(覆盖 app 未声明/覆盖默认 font.file);
* 0=默认小字库 common≈4k,仅 app 声明 fonts:"full" 才大字库。见 §5.2/§5.7.8。 */
{ "font.load_full", SET_INT, "0", 0, 1, 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" },
/* 通知 */
{ "notify.center_enabled", SET_BOOL, "1", 0, 0, NULL, NULL },
{ "notify.app_quota", SET_INT, "16", 0, 0, NULL, NULL },
/* M2a:通知配额(NOTIFICATION_SYSTEM.md §7,用户拍板 2026-08-29)。
* notify.categories = 每 App 最多申请的通知类型数(≤4)
* notify.category_quota = 每类型活跃(未过期)通知上限(默认 4)
* notify.media_quota = 每 App 媒体通知上限(默认 1;允许多媒体 App 同时) */
{ "notify.categories", SET_INT, "4", 1, 4, NULL, NULL },
{ "notify.category_quota", SET_INT, "4", 1, 16, NULL, NULL },
{ "notify.media_quota", SET_INT, "1", 1, 1, NULL, NULL },
/* 通知中心「勿扰」面板内开关(SET_BOOL,默认 0=关):开=只显示 urgency>=2(HIGH/CRITICAL)。
* 面板内 toggle 读写均走 meow_settings_set_bool/get_bool,零旁路(通知中心 UI 方案 §12.2)。 */
{ "notify.dnd", SET_BOOL, "0", 0, 0, NULL, NULL },
/* 日志 */
{ "log.level", SET_ENUM, "info", 0, 0, "error,warn,info,debug", "错误,警告,信息,调试" },
{ "log.to_card", SET_BOOL, "1", 0, 0, NULL, NULL },
/* 音频 */
{ "audio.volume", SET_INT, "60", 0, 100, NULL, NULL },
{ "audio.volume_max", SET_INT, "100", 0, 100, NULL, NULL },
{ "audio.volume_step", SET_INT, "1", 1, 5, NULL, NULL },
{ "audio.output", SET_STR, "hw:0,0", 0,0, NULL, NULL },
/* 网络(M2.39 设置页 WiFi/蓝牙开关;wifi 默认开=允许 ensure_wifi 自动连网,
* 用户关=0 则 ensure_wifi 不再自动拉起 / 蓝牙默认关=少耗电) */
{ "network.wifi_enabled", SET_BOOL, "1", 0, 0, NULL, NULL },
{ "network.bluetooth_enabled", SET_BOOL, "0", 0, 0, NULL, NULL },
/* 系统(M2.54:主机名,bootmenu S89meow_svcd 开机读 system.hostname sethostname;
* 缺省 meowos;RFC1035 首标签,供 meow_mdns 公布 <host>.local) */
{ "system.hostname", SET_STR, "meowos", 0, 0, NULL, NULL },
};
/* ---- 日志(自包含,不依赖 launcher 的 logmsg;前缀 "settings:")---- */
static void sset_log(const char *fmt, ...)
{
char msg[256];
va_list ap;
va_start(ap, fmt);
vsnprintf(msg, sizeof msg, fmt, ap);
va_end(ap);
fprintf(stderr, "settings: %s\n", msg);
}
/* ---- 内部查找 ---- */
static meow_setting *find_set(const char *key)
{
if (!key) return NULL;
for (int i = 0; i < g_nsets; i++)
if (!strcmp(g_sets[i].key, key)) return &g_sets[i];
return NULL;
}
/* ---- 枚举名/标签的逗号串切分辅助 ---- */
static int enum_count_str(const char *names)
{
if (!names || !names[0]) return 0;
int n = 1;
for (const char *p = names; *p; p++)
if (*p == ',') n++;
return n;
}
/* 取第 idx 个名字(返回指向 names 内部,静态覆盖;用完即弃) */
static const char *enum_at_str(const char *names, int idx)
{
static char buf[MEOW_SET_ENUM_LEN * 2];
if (!names || idx < 0) return NULL;
strncpy(buf, names, sizeof buf - 1);
buf[sizeof buf - 1] = 0;
char *p = buf;
for (int i = 0; i < idx; i++) {
while (*p && *p != ',') p++;
if (*p == ',') p++; else return NULL;
}
while (*p == ' ' || *p == '\t') p++;
/* 截到逗号/结尾 */
char *e = p;
while (*e && *e != ',') e++;
*e = 0;
return p;
}
static int enum_index_of_name_str(const char *names, const char *name)
{
if (!names || !name) return -1;
int n = enum_count_str(names);
for (int i = 0; i < n; i++) {
const char *s = enum_at_str(names, i);
if (s && !strcmp(s, name)) return i;
}
return -1;
}
/* ---- clamp/校验集中:int 取值域(max<=0 → 仅保底)---- */
static int clamp_int(meow_setting *s, int v)
{
if (s->max > 0) {
if (v < s->min) v = s->min;
if (v > s->max) v = s->max;
} else {
if (v < s->min) v = s->min;
}
return v;
}
/* display.brightness 额外 clamp >= display.brightness_min(防黑屏,SYSTEM_SETTINGS §3)
* 且 M2.39 上限 100(max_brightness=101,但 101 会闪屏,用户确认上限 100)。 */
static int clamp_brightness(int v)
{
int mn = 1; /* def 默认(brightness_min 未 init/空时用默认 1=极暗但非全黑,屏仍可见) */
meow_setting *bm = find_set("display.brightness_min");
if (bm && bm->cur[0]) mn = atoi(bm->cur);
if (v < mn) v = mn;
if (v > 100) v = 100;
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)
{
if (!s || !v) return;
switch (s->type) {
case SET_INT: {
int iv = atoi(v);
iv = clamp_int(s, iv);
if (!strcmp(s->key, "display.brightness")) iv = clamp_brightness(iv);
snprintf(s->cur, sizeof s->cur, "%d", iv);
break;
}
case SET_BOOL: {
int b = 0;
char p[16], *q = p;
strncpy(p, v, sizeof p - 1); p[sizeof p - 1] = 0;
while (*q == ' ' || *q == '\t') q++;
long n = strtol(q, NULL, 10);
if (!strcasecmp(q, "true") || !strcasecmp(q, "yes") || !strcasecmp(q, "on")) b = 1;
else if (q[0] && n != 0) b = 1; /* 数值非 0(含旧风格 "2")视为 true;向后兼容 */
snprintf(s->cur, sizeof s->cur, "%d", b);
break;
}
case SET_ENUM: {
int idx = enum_index_of_name_str(s->enum_names, v);
if (idx < 0) idx = atoi(v); /* 向后兼容:数值索引 */
if (idx >= 0 && idx < enum_count_str(s->enum_names)) {
const char *name = enum_at_str(s->enum_names, idx);
if (name) strncpy(s->cur, name, sizeof s->cur - 1);
}
/* 未知值忽略(保留默认) */
break;
}
case SET_STR:
strncpy(s->cur, v, sizeof s->cur - 1);
s->cur[sizeof s->cur - 1] = 0;
break;
}
s->dirty = 0;
}
/* ---- 广播(防 listener 递归 set)---- */
static void notify_key(const char *key)
{
if (g_broadcasting) return;
g_broadcasting = 1;
for (int i = 0; i < g_nlisteners; i++)
if (g_listeners[i]) g_listeners[i](key);
g_broadcasting = 0;
}
/* import 内部:apply + 脏 + 广播(定义在下方 import 之前声明) */
static void apply_value_nosave(meow_setting *s, const char *v, int *applied);
/* ---- 注册 ---- */
int meow_settings_register(const meow_setting_def *def)
{
if (!def || !def->key) return MEOW_ERR_INVAL;
if (g_nsets >= MEOW_SET_MAX_KEYS) return MEOW_ERR_NOMEM;
if (find_set(def->key)) return MEOW_ERR_EXISTS;
meow_setting *s = &g_sets[g_nsets++];
memset(s, 0, sizeof *s);
strncpy(s->key, def->key, sizeof s->key - 1);
s->type = def->type;
strncpy(s->def, def->def ? def->def : "", sizeof s->def - 1);
strncpy(s->cur, s->def, sizeof s->cur - 1);
s->min = def->min;
s->max = def->max;
if (def->enum_names) strncpy(s->enum_names, def->enum_names, sizeof s->enum_names - 1);
if (def->enum_labels) strncpy(s->enum_labels, def->enum_labels, sizeof s->enum_labels - 1);
s->dirty = 0;
return MEOW_OK;
}
/* ---- 初始化(幂等;重载=重置默认 + 重读 settings.conf)---- */
int meow_settings_init(void)
{
if (g_nsets == 0) {
for (unsigned i = 0; i < sizeof g_defs / sizeof g_defs[0]; i++) {
int rc = meow_settings_register(&g_defs[i]);
/* 表满/重复为内部异常;防御性继续(注册失败仅该键缺失) */
(void)rc;
}
}
/* 重置全部到默认,再读文件(与旧 settings_load 语义一致:默认 + 覆盖) */
for (int i = 0; i < g_nsets; i++) {
strncpy(g_sets[i].cur, g_sets[i].def, sizeof g_sets[i].cur - 1);
g_sets[i].dirty = 0;
}
FILE *f = fopen(MEOW_SETTINGS_FILE, "r");
if (!f) {
sset_log("no %s, defaults active", MEOW_SETTINGS_FILE);
g_inited = 1;
return MEOW_OK;
}
char buf[768];
while (fgets(buf, sizeof buf, f)) {
char *eq = strchr(buf, '=');
if (!eq) continue;
*eq = 0;
/* 左去空 */
char *k = buf;
while (*k == ' ' || *k == '\t') k++;
int kl = (int)strlen(k);
while (kl > 0 && (k[kl-1] == ' ' || k[kl-1] == '\t')) k[--kl] = 0;
if (!k[0] || k[0] == '#') continue; /* 空行/注释 */
/* 右值去空 */
char *v = eq + 1;
while (*v == ' ' || *v == '\t') v++;
int vl = (int)strlen(v);
while (vl > 0 && (v[vl-1] == '\n' || v[vl-1] == '\r')) v[--vl] = 0;
while (vl > 0 && (v[vl-1] == ' ' || v[vl-1] == '\t')) v[--vl] = 0;
meow_setting *s = find_set(k);
if (s) apply_value(s, v);
/* 未知键忽略(SYSTEM_SETTINGS §3 存储格式) */
}
fclose(f);
/* 读取后统一补一次亮度下限 clamp(brightness_min 可能在 brightness 之后读入) */
meow_setting *b = find_set("display.brightness");
if (b) {
int iv = atoi(b->cur);
int cv = clamp_brightness(iv);
snprintf(b->cur, sizeof b->cur, "%d", cv);
}
g_inited = 1;
/* M2.39:迁移旧持久亮度下限 25 → 1(SYSTEM_SETTINGS §1 下限默认已改 1,但设备
* settings.conf 里还残留旧版写入的 display.brightness_min=25,覆盖默认表,导致亮度
* 滑条被 clamp 在 25 调不下去;且 SMENU 设置页并不暴露 brightness_min 这一项,故 25
* 只能是旧默认残留→强制视为待迁移,改写为 1 并落盘,让用户能调到「极暗但可见」的 1)。
* 此处只针对恰为旧默认 25 的值;用户其它自定义值(非 25)不动。 */
meow_setting *bmin = find_set("display.brightness_min");
if (bmin && !strcmp(bmin->cur, "25")) {
snprintf(bmin->cur, sizeof bmin->cur, "%d", 1);
bmin->dirty = 1;
sset_log("brightness_min stale default 25 -> 1 (migrated)");
meow_settings_save();
}
return MEOW_OK;
}
/* ---- 生命周期 ---- */
int meow_settings_shutdown(void)
{
if (g_inited) meow_settings_save();
return MEOW_OK;
}
void meow_settings_dump(const char *log_prefix)
{
const char *lp = log_prefix ? log_prefix : "";
for (int i = 0; i < g_nsets; i++)
sset_log("%s%s = %s", lp, g_sets[i].key, g_sets[i].cur);
}
/* ---- get ---- */
int meow_settings_get_int(const char *key, int *out)
{
meow_setting *s = find_set(key);
if (!s) return MEOW_ERR_NOENT;
if (!g_inited) return MEOW_ERR_NOTINIT;
if (s->type != SET_INT) return MEOW_ERR_INVAL;
if (!out) return MEOW_ERR_INVAL;
*out = atoi(s->cur);
return MEOW_OK;
}
int meow_settings_get_str(const char *key, char *buf, int buf_len)
{
meow_setting *s = find_set(key);
if (!s) return MEOW_ERR_NOENT;
if (!g_inited) return MEOW_ERR_NOTINIT;
if (s->type != SET_STR) return MEOW_ERR_INVAL;
if (!buf || buf_len <= 0) return MEOW_ERR_INVAL;
strncpy(buf, s->cur, buf_len);
buf[buf_len - 1] = 0;
return MEOW_OK;
}
int meow_settings_get_enum(const char *key, int *out)
{
meow_setting *s = find_set(key);
if (!s) return MEOW_ERR_NOENT;
if (!g_inited) return MEOW_ERR_NOTINIT;
if (s->type != SET_ENUM) return MEOW_ERR_INVAL;
if (!out) return MEOW_ERR_INVAL;
*out = enum_index_of_name_str(s->enum_names, s->cur);
if (*out < 0) *out = 0; /* 非法当前值兜底:首项 */
return MEOW_OK;
}
int meow_settings_get_bool(const char *key, int *out)
{
meow_setting *s = find_set(key);
if (!s) return MEOW_ERR_NOENT;
if (!g_inited) return MEOW_ERR_NOTINIT;
if (s->type != SET_BOOL) return MEOW_ERR_INVAL;
if (!out) return MEOW_ERR_INVAL;
*out = (s->cur[0] == '1');
return MEOW_OK;
}
/* ---- 标注脏 + 广播的公共尾部 ---- */
static void mark_changed(meow_setting *s, const char *newval)
{
if (strcmp(newval, s->cur) == 0) return; /* 未变:不广播 */
strncpy(s->cur, newval, sizeof s->cur - 1);
s->cur[sizeof s->cur - 1] = 0;
s->dirty = 1;
notify_key(s->key);
}
/* ---- set ---- */
int meow_settings_set_int(const char *key, int v)
{
meow_setting *s = find_set(key);
if (!s) return MEOW_ERR_NOENT;
if (!g_inited) return MEOW_ERR_NOTINIT;
if (s->type != SET_INT) return MEOW_ERR_INVAL;
v = clamp_int(s, v);
if (!strcmp(s->key, "display.brightness")) v = clamp_brightness(v);
char tmp[24];
snprintf(tmp, sizeof tmp, "%d", v);
mark_changed(s, tmp);
return MEOW_OK;
}
int meow_settings_set_str(const char *key, const char *v)
{
meow_setting *s = find_set(key);
if (!s) return MEOW_ERR_NOENT;
if (!g_inited) return MEOW_ERR_NOTINIT;
if (s->type != SET_STR) return MEOW_ERR_INVAL;
char tmp[MEOW_SET_MAX_LEN];
strncpy(tmp, v ? v : "", sizeof tmp - 1);
tmp[sizeof tmp - 1] = 0;
mark_changed(s, tmp);
return MEOW_OK;
}
int meow_settings_set_enum(const char *key, int idx)
{
meow_setting *s = find_set(key);
if (!s) return MEOW_ERR_NOENT;
if (!g_inited) return MEOW_ERR_NOTINIT;
if (s->type != SET_ENUM) return MEOW_ERR_INVAL;
int n = enum_count_str(s->enum_names);
if (idx < 0 || idx >= n) return MEOW_ERR_INVAL; /* 越界拒绝 */
const char *name = enum_at_str(s->enum_names, idx);
if (!name) return MEOW_ERR_INVAL;
mark_changed(s, name);
return MEOW_OK;
}
int meow_settings_set_bool(const char *key, int v)
{
meow_setting *s = find_set(key);
if (!s) return MEOW_ERR_NOENT;
if (!g_inited) return MEOW_ERR_NOTINIT;
if (s->type != SET_BOOL) return MEOW_ERR_INVAL;
char tmp[8];
snprintf(tmp, sizeof tmp, "%d", v ? 1 : 0);
mark_changed(s, tmp);
return MEOW_OK;
}
/* ---- 开关型(M2.5x)----
* 开关 = SET_BOOL,或 SET_INT 且取值域恰为 {0,1}(如 display.multi_font / font.load_full,
* 见 SYSTEM_SETTINGS §1「int 0/1」)。实体设置页 SC_SWITCH 与字体逻辑用通用读写,
* 避免对 SET_INT 开关误用 get_bool/set_bool(类型不符必失败 → 点不动/恒 OFF/6 类行恒隐藏)。 */
static int is_switch_like(meow_setting *s)
{
if (!s) return 0;
if (s->type == SET_BOOL) return 1;
if (s->type == SET_INT && s->min == 0 && s->max == 1) return 1;
return 0;
}
int meow_settings_get_switch(const char *key, int *out)
{
meow_setting *s = find_set(key);
if (!s) return MEOW_ERR_NOENT;
if (!g_inited) return MEOW_ERR_NOTINIT;
if (!out) return MEOW_ERR_INVAL;
if (!is_switch_like(s)) return MEOW_ERR_INVAL;
if (s->type == SET_BOOL) *out = (s->cur[0] == '1');
else *out = (atoi(s->cur) != 0);
return MEOW_OK;
}
int meow_settings_set_switch(const char *key, int v)
{
meow_setting *s = find_set(key);
if (!s) return MEOW_ERR_NOENT;
if (!g_inited) return MEOW_ERR_NOTINIT;
if (!is_switch_like(s)) return MEOW_ERR_INVAL;
if (s->type == SET_BOOL) return meow_settings_set_bool(key, v);
return meow_settings_set_int(key, v ? 1 : 0); /* SET_INT 0/1:写 0/1(clamp 到 {0,1}) */
}
/* ---- listener ---- */
int meow_settings_add_listener(meow_settings_listener cb)
{
if (!cb) return MEOW_ERR_INVAL;
for (int i = 0; i < g_nlisteners; i++)
if (g_listeners[i] == cb) return MEOW_ERR_EXISTS;
if (g_nlisteners >= MEOW_SET_MAX_LISTENERS) return MEOW_ERR_NOMEM;
g_listeners[g_nlisteners++] = cb;
return MEOW_OK;
}
int meow_settings_remove_listener(meow_settings_listener cb)
{
for (int i = 0; i < g_nlisteners; i++) {
if (g_listeners[i] == cb) {
memmove(&g_listeners[i], &g_listeners[i+1],
(g_nlisteners - i - 1) * sizeof *g_listeners);
g_nlisteners--;
return MEOW_OK;
}
}
return MEOW_ERR_NOENT;
}
/* ---- enum 辅助 ---- */
int meow_settings_enum_label(const char *key, int idx, char *buf, int buf_len)
{
meow_setting *s = find_set(key);
if (!s) return MEOW_ERR_NOENT;
if (s->type != SET_ENUM) return MEOW_ERR_INVAL;
if (!buf || buf_len <= 0) return MEOW_ERR_INVAL;
if (idx < 0 || idx >= enum_count_str(s->enum_names)) return MEOW_ERR_INVAL;
const char *lab = enum_at_str(s->enum_labels, idx);
if (!lab || !lab[0]) lab = enum_at_str(s->enum_names, idx); /* 无标签回退名字 */
if (!lab) return MEOW_ERR_INVAL;
strncpy(buf, lab, buf_len);
buf[buf_len - 1] = 0;
return MEOW_OK;
}
int meow_settings_enum_count(const char *key)
{
meow_setting *s = find_set(key);
if (!s) return -MEOW_ERR_NOENT;
if (s->type != SET_ENUM) return -MEOW_ERR_INVAL;
return enum_count_str(s->enum_names);
}
int meow_settings_enum_index_of_name(const char *key, const char *name)
{
meow_setting *s = find_set(key);
if (!s) return MEOW_ERR_NOENT;
if (s->type != SET_ENUM) return MEOW_ERR_INVAL;
return enum_index_of_name_str(s->enum_names, name);
}
void meow_settings_enum_names(const char *key, const char **out_names, int *n)
{
static char scratch[MEOW_SET_ENUM_LEN];
static const char *ptrs[MEOW_SET_ENUM_MAX_OPT];
if (n) *n = 0;
meow_setting *s = find_set(key);
if (!s || s->type != SET_ENUM || !out_names) return;
strncpy(scratch, s->enum_names, sizeof scratch - 1);
scratch[sizeof scratch - 1] = 0;
char *p = scratch;
int k = 0;
while (*p && k < MEOW_SET_ENUM_MAX_OPT) {
while (*p == ' ' || *p == '\t') p++;
if (!*p) break;
ptrs[k++] = p;
while (*p && *p != ',') p++;
if (*p == ',') { *p = 0; p++; }
}
for (int i = 0; i < k; i++) out_names[i] = ptrs[i];
if (n) *n = k;
}
/* ---- list ---- */
int meow_settings_list(int (*cb)(const char *key, meow_set_type t, void *ctx), void *ctx)
{
if (!cb) return MEOW_ERR_INVAL;
if (!g_inited) return MEOW_ERR_NOTINIT;
for (int i = 0; i < g_nsets; i++)
cb(g_sets[i].key, g_sets[i].type, ctx);
return MEOW_OK;
}
/* ---- 默认值 / 复位 ---- */
int meow_settings_get_default(const char *key, char *buf, int len)
{
meow_setting *s = find_set(key);
if (!s) return MEOW_ERR_NOENT;
if (!buf || len <= 0) return MEOW_ERR_INVAL;
strncpy(buf, s->def, len);
buf[len - 1] = 0;
return MEOW_OK;
}
int meow_settings_reset(const char *key)
{
meow_setting *s = find_set(key);
if (!s) return MEOW_ERR_NOENT;
if (strcmp(s->cur, s->def) != 0) {
strncpy(s->cur, s->def, sizeof s->cur - 1);
s->cur[sizeof s->cur - 1] = 0;
s->dirty = 1;
notify_key(s->key);
}
return MEOW_OK;
}
int meow_settings_reset_all(void)
{
for (int i = 0; i < g_nsets; i++) {
meow_setting *s = &g_sets[i];
if (strcmp(s->cur, s->def) != 0) {
strncpy(s->cur, s->def, sizeof s->cur - 1);
s->cur[sizeof s->cur - 1] = 0;
s->dirty = 1;
notify_key(s->key);
}
}
return MEOW_OK;
}
/* ---- 落盘(写板载 settings.conf;保留 "# meow-settings 1" 头)---- */
static void write_full_conf(int fd)
{
char buf[576];
int l = snprintf(buf, sizeof buf, "# meow-settings 1\n");
if (l > 0) write(fd, buf, l);
for (int i = 0; i < g_nsets; i++) {
meow_setting *s = &g_sets[i];
const char *val = s->cur;
char num[24];
/* 非字符串类型把 cur 规范为可读形式(enum=名字,bool/int=数字) */
if (s->type == SET_INT) { snprintf(num, sizeof num, "%d", atoi(s->cur)); val = num; }
else if (s->type == SET_BOOL) { snprintf(num, sizeof num, "%d", s->cur[0] == '1'); val = num; }
else if (s->type == SET_ENUM) { val = s->cur; }
l = snprintf(buf, sizeof buf, "%s = %s\n", s->key, val);
if (l > 0) write(fd, buf, l);
}
}
int meow_settings_save(void)
{
if (!g_inited) return MEOW_ERR_NOTINIT;
if (system("mkdir -p /usr/data/meow 2>/dev/null") != 0) {
/* 目录已存在等返回非 0 也属正常,不视为失败 */
}
int fd = open(MEOW_SETTINGS_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
sset_log("save: open %s fail: %s", MEOW_SETTINGS_FILE, strerror(errno));
return MEOW_ERR_IO;
}
write_full_conf(fd);
close(fd);
/* boot.default:除写 settings.conf 外,还要同步到启动菜单的权威文件 /data/meow_boot_default
* (bootmenu 开机读它定默认项;与 bootmenu set_boot_default_meow() 写 "0" / launcher
* set_boot_default_hiby() 写 "1" 同文件、同语义:meow=0(默认 MeowOS), hiby=1(默认 HiByOS))。
* 幂等:值未变也 O_TRUNC 重写,无害。init 读入不触发 save,故不会反向覆盖权威文件。 */
meow_setting *bdef = find_set("boot.default");
if (bdef) {
int v = (!strcmp(bdef->cur, "hiby")) ? 1 : 0; /* meow(默认) -> 0, hiby -> 1 */
int bf = open("/data/meow_boot_default", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (bf >= 0) {
char bb[8];
int n = snprintf(bb, sizeof bb, "%d\n", v);
write(bf, bb, n);
close(bf);
sset_log("boot.default=%s -> /data/meow_boot_default=%d", bdef->cur, v);
} else {
sset_log("boot.default sync: open /data/meow_boot_default fail: %s", strerror(errno));
}
}
for (int i = 0; i < g_nsets; i++) g_sets[i].dirty = 0;
return MEOW_OK;
}
/* ---- export:写一份 .conf 到 path(默认 TF 卡备份)---- */
int meow_settings_export(const char *path)
{
const char *p = path ? path : MEOW_SETTINGS_BAK;
/* 自动建目录(取 p 的 dirname),失败静默(导出尽力而为) */
{
char dir[320];
char cmd[344];
snprintf(dir, sizeof dir, "%s", p);
char *slash = strrchr(dir, '/');
if (slash) {
if (slash == dir) slash[1] = 0; else *slash = 0;
snprintf(cmd, sizeof cmd, "mkdir -p '%s' 2>/dev/null", dir);
system(cmd);
}
}
int fd = open(p, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
sset_log("export: open %s fail: %s", p, strerror(errno));
return MEOW_ERR_IO;
}
write_full_conf(fd);
close(fd);
sset_log("exported -> %s", p);
return MEOW_OK;
}
/* ---- import:从 .conf 读入覆盖;逐键校验,损坏键不中断 ---- */
int meow_settings_import(const char *path)
{
if (!g_inited) return MEOW_ERR_NOTINIT;
const char *p = path ? path : MEOW_SETTINGS_BAK;
FILE *f = fopen(p, "r");
if (!f) { sset_log("import: open %s fail: %s", p, strerror(errno)); return MEOW_ERR_NOENT; }
int bad = 0, applied = 0;
char buf[768];
while (fgets(buf, sizeof buf, f)) {
char *eq = strchr(buf, '=');
if (!eq) continue;
*eq = 0;
char *k = buf;
while (*k == ' ' || *k == '\t') k++;
int kl = (int)strlen(k);
while (kl > 0 && (k[kl-1] == ' ' || k[kl-1] == '\t')) k[--kl] = 0;
if (!k[0] || k[0] == '#') continue;
char *v = eq + 1;
while (*v == ' ' || *v == '\t') v++;
int vl = (int)strlen(v);
while (vl > 0 && (v[vl-1] == '\n' || v[vl-1] == '\r')) v[--vl] = 0;
while (vl > 0 && (v[vl-1] == ' ' || v[vl-1] == '\t')) v[--vl] = 0;
meow_setting *s = find_set(k);
if (!s) { bad++; continue; } /* 未知键:计损坏,不中断 */
/* 逐键校验:走 set 的 clamp/校验(apply_value 已 clamp);值合法才算成功 */
apply_value_nosave(s, v, &applied);
}
fclose(f);
meow_settings_save(); /* 导入后落盘(应用覆盖) */
sset_log("imported %s: applied=%d invalid=%d", p, applied, bad);
return applied > 0 ? MEOW_OK : MEOW_ERR_INVAL;
}
/* ---- import 内部:apply + 脏 + 广播(不重置整表)---- */
static void apply_value_nosave(meow_setting *s, const char *v, int *applied)
{
if (!s || !v) return;
char before[MEOW_SET_MAX_LEN];
strncpy(before, s->cur, sizeof before - 1); before[sizeof before - 1] = 0;
apply_value(s, v);
if (strcmp(before, s->cur) != 0) {
s->dirty = 1;
notify_key(s->key);
if (applied) (*applied)++;
}
}