# MeowOS 系统设置子系统设计(阶段 B2a 后端 + B2b UI) > 状态:设计稿(2026-08-28)。**后端 B2a 前置到阶段 B 之前**(B 的通告栏快捷按钮/亮度滑条/主题开关全是设置项,必须先有统一后端,B 直接消费、零返工);B2b 实体设置页 UI 在 B 之后;阶段 C(MeowPanel)调同一后端。 > 原则:**一套设置后端,多端(实体 UI / Web 面板 / CLI)共用同一 API**,单一数据源,不留旁路。 --- ## 0. 目标与一句话 把 MeowOS 全部系统设置抽象成**统一设置子系统**(结构化存储 + C API + 变更通知);阶段 **B2a** 落地这套后端(`meow_settings_*` + `settings.conf` + listener,**先于阶段 B**);阶段 **B** 的通告栏控件直接消费它;阶段 **B2b** 用同一 API 建**实体设置 UI**;阶段 C 的 MeowPanel 设置页**直接调同一后端**(HTTP 转发 C API),实体与 web 镜像、改哪边都同步。 ## 1. 设置项清单(覆盖 MeowOS 全部可配置项) | 分组 | 设置键 | 类型 | 默认 | 说明 | |---|---|---|---|---| | **显示** | `display.brightness` | int 0-255 | 101 | 当前亮度(clamp ≥`display.brightness_min`,防黑屏;设置页滑条 clamp 到 1-100,101 会闪屏) | | | `display.brightness_min` | int | **1** | 亮度安全下限(用户可调;M2.39 由 25 改 1,旧持久 25 视为残留默认自动迁移到 1,使亮度滑条可调到「极暗但可见」) | | | `display.screen_off_mode` | enum | `panel` | `panel`=面板下电+背光0+FBIOPBLANK / `black`=背光0全黑+双击亮屏 | | | `display.screen_off_timeout` | int 秒 | 30 | 息屏时长(0=常亮) | | | `display.refresh_rate` | enum | `60` | 刷新率上限三档 `60`/`30`/`15`(默认 60,不逼 62Hz 瓶颈) | | | `display.refresh_always_max` | bool | false | 始终最大刷新率(=60 且不自动降档);关=允许自适应降档(60→30→15) | | | `display.theme` | enum | `dark` | `dark`/`light`(MD3 深浅;manual 模式下 s_ui_theme 直读它) | | | `display.theme_mode` | enum | `manual` | 主题判定方式:`manual`(=读 `display.theme`)/`auto`(按深色时段)。**B2b 新增** | | | `display.theme_dark_start` | int 分钟 0-1439 | `0` | 深色时段开始(如 19:00=1140);`start==end` 视为未配置 → auto 回退 manual。**B2b 新增** | | | `display.theme_dark_end` | int 分钟 0-1439 | `0` | 深色时段结束(如 07:00=420);`start>end`=跨午夜。**B2b 新增** | | | `display.background` | path/color | `black` | `black` 或图片路径(`MeowAPPs/backgrounds/xx.png`) | | **电源** | `power.longpress_menu_ms` | int | 3000 | 长按电源弹电源菜单阈值 | | | `power.forceoff_ms` | int | 8000 | 长按电源强关阈值 | | | `power.defeat_hotkey` | string | `power_voldown` | 切系统/切 MeowOS 热键组合 | | **开机** | `boot.default` | enum | `meow` | 开机默认进入 | | | `boot.menu_enabled` | bool | true | 是否显示引导选单 | | | `boot.timeout_ms` | int | 5000 | 选单超时(有输入暂停重计) | | **输入** | `input.hotkey_seq` | string | `volup,volup,volup` | 应用列表热键序列 | | | `input.hotkey_window_ms` | int | 2500 | 组合/序列窗口 | | | `input.touch_arm_delay_ms` | int | 700 | 触摸布防延迟(防幽灵) | | | `input.swipe_invert` | bool | false | 设置页滑动方向(0=手指上滑→往下翻;1=反向) | | **字体/主题** | `font.file` | path | `MeowOS/fonts/default/regular/font.png` | 字体图集(M2.6 指 `//font.png`,默认 `default/regular`;设置页「字体文件」行为 family→字重两级子菜单) | | | `display.multi_font` | int 0/1 | 0 | **多字体**:1=按字符类别逐字符路由;0=单字体(只走 `font.file`,零额外内存/性能) | | | `font.class_cjk` | path | *(空=跟全局)* | 多字体:CJK 表意+扩展 所用字体(family 相对路径) | | | `font.class_cjk_sym` | path | *(空=跟全局)* | 多字体:全角/中文标点 所用字体 | | | `font.class_num` | path | *(空=跟全局)* | 多字体:0-9 所用字体 | | | `font.class_latin` | path | *(空=跟全局)* | 多字体:A-Za-z 所用字体 | | | `font.class_latin_sym` | path | *(空=跟全局)* | 多字体:半角标点 所用字体 | | | `font.class_other` | path | *(空=跟全局)* | 多字体:其他字符 所用字体 | | | `font.load_full` | int 0/1 | 0 | **加载大字库(M2.5x)**:1=**强制全系统用大字库(`gbk`≈20k 档)**,覆盖 app 未声明/覆盖默认 `font.file`;0=默认小字库(`common`≈4k),仅 app 声明 `fonts:"full"` 才大字库 [源:用户 2026-08-30] | | | `font.size.title` / `font.size.body` | int | 28/20 | 标题/正文字号 | | | `ui.style` | enum | `md3` | `md3`(Material You)/`miui` | | **通知** | `notify.center_enabled` | bool | true | 下拉通知中心开关 | | | `notify.app_quota` | int | 16 | 每应用通知配额 | | | `notify.categories` | int | 4 | 通知类型数上限(每 App,M2.53)[源:审计 2026-08-30]| | | `notify.category_quota` | int | 4 | 每类型活动通知上限(4×4=16/App)| | | `notify.media_quota` | int | 1 | 媒体通知单例(每 App 仅 1 条)| | **日志** | `log.level` | enum | `info` | `error/warn/info/debug` | | | `log.to_card` | bool | true | 写 TF 卡(无卡回退内存环) | | **音频** | `audio.volume` | int 0-100 | 60 | 系统音量(MeowOS 直写 CS43198 DAC;读回为追踪值) | | | `audio.volume_max` | int 0-100 | 100 | MeowOS 内部音量上限(clamp;**不与 HibyOS user.ini 上限同步**——其带 CRC 有风险,已定案不做;用户可自设 MeowOS 上限) | | | `audio.volume_step` | int 1-5 | **1** | 音量键跳步(默认 1%,可配 1-5,最大 5) | | | `audio.output` | string | `hw:0,0` | MeowAPPs 音频输出设备 | | **网络** | `network.wifi_enabled` | bool | true | WiFi 开关(写 wlan0;设置页开关经 `wifi_up.sh`/`ip link set wlan0`,`ensure_wifi()` 尊重此值——关=0 则不再自动连网) | | | `network.bluetooth_enabled` | bool | false | 蓝牙开关(写 `hciconfig hci0 up/down`;默认关=少耗电) | | **系统** | `system.hostname` | string | `meowos` | 主机名(RFC1035 首标签;bootmenu 的 S89meow_svcd 开机读它 `sethostname`,缺省 `meowos`;供自研 mDNS 应答器 `meow_mdns` 公布 `.local`)[源:M2.54] | > **字体尺寸标记(M2.5x,随「加载大字库」一起落地)**:字体/主题选择器**每个字体选项标注库大小标记**,如 `MiSans Regular(common≈4k)` / `gbk≈20k` / `full`,直观标注码位/体积量级,让用户知道所选字库大小(M2.6 含字重:` ()`)。对应 `MeowOS/fonts///font.json` 的 `charset`(`common|gbk|full`)+ `glyph_count`;档位枚举 `MEOW_FONT_TIER_COMMON`(默认/≈4k)/`GBK`(大/≈20k)/`FULL`(全/可选);`gen_font.py --charset common|gbk|full [--weight ]` 按档生成。定案见 MEOWOS_SPEC_REF §5.2「字体档位/大字库」与 §5.7.8 [源:用户 2026-08-30]。 > 新增设置项时:在清单加一行 + 存储 schema + API 常量/枚举 + 实体 UI 控件 + web 面板控件(四端同步)。 ## 2. 统一存储(单一数据源) - **主持久化位置:板载 `/usr/data/meow/settings.conf`**(UBIFS)。settings.conf 是**小文件**(几十行、<10KB),持久在板载可跨换卡/掉卡保留配置,避免换音乐卡丢失设置。板载仅此小设置文件(日志/字库/媒体等大数据仍留 TF 卡,符合"板载不写大文件"纪律;KB 级设置除外)。 - **导入/导出(备份):** - **导出默认路径**:TF 卡 `MeowOS/backups/settings/setting.bak`(自动建目录)。用户可在导出时改路径。 - 本地 TF 卡:导入 = 从该 .bak 文件读入覆盖(选择/确认 + 校验)。 - Web 面板(阶段 C):`GET /api/settings/export`(下载 .conf)/ `POST /api/settings/import`(上传覆盖)。 - 二者都调用同一 `meow_settings_export(path)` / `import(path)`。 - **无卡/无板载时**:内存缓存,启动加载;板载在则自动落盘。 - **格式**:`key = value` 扁平文本(收敛旧 meow.conf),支持注释 `#`。 - 处理:`settings_load()`(启动读 + 变更即 `settings_save()` 落到 /data)、`settings_save()`(有变更/定时/掉电前)。避免频繁写(UBIFS 有磨损但 KB 级低频无碍)。 - 版本:文件头 `# meow-settings 1`。 ## 3. C API 契约(后端,`meow_settings.h`) ```c /* 类型 schema:每个设置键注册“名称/类型/默认/取值域”,存取统一走表 */ typedef enum { SET_INT, SET_STR, SET_ENUM, SET_BOOL } meow_set_type; int meow_settings_init(void); /* 加载存储,注册默认 */ int meow_settings_get_int(const char *key, int *out); int meow_settings_get_str(const char *key, char *buf, int len); int meow_settings_get_enum(const char *key, int *out); /* 按枚举名/索引 */ int meow_settings_get_bool(const char *key, int *out); int meow_settings_set_int(const char *key, int v); /* clamp 到取值域 */ int meow_settings_set_str(const char *key, const char *v); int meow_settings_set_enum(const char *key, int v); int meow_settings_set_bool(const char *key, int v); /* 变更通知:注册回调(实体 UI 重绘 / web 推送) */ typedef void (*meow_settings_listener)(const char *key); int meow_settings_add_listener(meow_settings_listener cb); int meow_settings_remove_listener(...); /* 枚举辅助:名字<->索引,供 UI/web 显示中文标签 */ int meow_settings_enum_label(const char *key, int idx, char *buf, int len); int meow_settings_enum_count(const char *key); /* 列表:枚举当前全部键,供 web 面板 / 设置页生成控件 */ int meow_settings_list(int (*cb)(const char *key, meow_set_type t, void*), void *ctx); /* 导入/导出(备份) */ int meow_settings_export(const char *path); /* 写一份 .conf 到 path */ int meow_settings_import(const char *path); /* 从 .conf 读入覆盖,校验 + 通知 */ ``` - **clamp/校验集中在 set**:如 `brightness` 自动 clamp ≥ `brightness_min`(默认 1;M2.39 由 25 改 1);`screen_off_timeout >= 0`;枚举越界拒绝。 - **变更通知**在 set 成功后对注册 listener 广播(key 字符串),实体 UI 据此重绘;web 端经 MeowPanel 长连接推送。 ## 4. 阶段 B2b:实体系统设置页(Launcher 内,用 B2a 已就位后端) - 入口:应用列表加一个**系统设置**卡片/图标(或列表项),点进设置页。 - UI:列表式(与应用列表同款渲染),**分组**:显示 / 电源 / 开机 / 输入 / 字体主题 / 通知 / 日志 / 音频。每项 = 标题 + 控件: - 开关(bool):`display.theme` 深浅、`notify.center_enabled`… - 滑条(int):亮度(**clamp ≥25**)、音量、息屏时长时间 - 选择器(enum):息屏模式、开机默认、主题、日志级别——选择器用子菜单/对话框,选项中文标签来自 `enum_label` - 路径(str):字体、背景——文件浏览器选择(可先用内置几个预设,自定义文件选择后期);**字体(M2.6 字重结构)用 family→字重两级子菜单**:点「字体文件」/多字体类别行 → 一级列扫描到的 family(多字体行首项「跟随全局」)→ 二级列该 family 的可用字重(regular/bold/italic…),点字重即选中(写 `font.file`/`font.class_*`= `//font.png` 卡根相对路径) - **所有控件只调 `meow_settings_set_*` / `get_*`**,绝不直接改文件;改完 `settings_save()`,UI 按 listener 重绘。保证与 web/CLI 一致。 - 权限/安全:本机信任,设置页不做鉴权。 ## 5. 阶段 C 复用(MeowPanel 设置页) - MeowPanel 后端新增 `/api/settings`: - `GET /api/settings` → 返回全部键值(调 `meow_settings_list` + get_*) - `POST /api/settings` (body: key/value)→ 调 `meow_settings_set_*` + save - `POST /api/settings/restore` → 恢复默认 - web 设置页**与实体设置页同一套 key/控件定义**(前端每 key 渲染对应控件),共用 `meow_settings` 后端。改 web → 实体立即反映(listener/轮询);改实体 → web 下拉刷新。 - 设置 API 也暴露给 **CLI/MCP**(`meowcli settings get/set`)供调试/自动化。 ## 6. 与阶段 B / D 的衔接 - **阶段 B**:搭 `meow_settings` 后端 + 状态栏/通知中心期间,逐步把**现有散落的配置**(meow.conf 的 boot_default/hotkey/bg_color/字体路径)**迁移**进 `settings.conf` 卸载旧 meow.conf;状态栏/通知中心读设置(如通知中心开关、字体字号)走 API。 - **阶段 D(Activity/通知框架)**:通知配额/行为等设置进 settings;应用可通过 IPC 读改名(可选暴露只读 `meow_settings_get_*`)。 ## 7. 实现里程碑 M1 存储+核心 API(get/set/save/load/listen/list/枚举)→ M2 实体设置页(分组列表+各类控件)→ M3 迁移旧 meow.conf + 状态栏/通知中心走 API → M4 web 设置页对接 → M5 加固(clamp/校验/掉电保存/测试)。 ## 8. 风险/注意 - 板载存储不写:settings.conf 只落 TF 卡,未挂载用内存,挂载后落盘;掉电前 save。 - clamp 必须集中在 set(防亮度 0 / 非法枚举)。 - 迁移 meow.conf 时保持向后兼容(旧文件读入,写回新格式),避免把现有用户配置弄丢。 - 字体/背景路径若失效回退默认(不崩)。 ## 附:系统状态重抓清单与策略(进 MeowOS 全量快照) **状态项**(进 MeowOS 时全量抓,HibyOS 改过也同步): - 电量(百分比/充电状态/电压/温度)、WiFi(开关/SSID/IP/信号/链路)、蓝牙(开关/连设备数)、音量(settings)、亮度(sysfs)、存储(/data 剩余/TF卡挂载容量)、性能(loadavg/meminfo)、温度(thermal_zone)、系统(模式/应用数/版本)、音频输出(后续) **性能策略**: - 进 MeowOS 一次性全量 `meow_sys_snapshot()`(阻塞可接受)。 - 主循环 10ms 热键轮询**绝不读系统状态**;单独 1s 轻刷仅时钟/电量/WiFi 信号。 - 性能/温度类用后台定时低频率(1-2s)读,仅 MeowPanel 用,别占 UI。 - 统一封装 `meow_sys_state` + `meow_sys_refresh()`,状态栏/设置/面板共用。 ## 附 B:设备属性域(device attributes)——meow_sys 只读+可控子域 > 承接 §18.44 设备全属性只读枚举(任务要求「覆盖设备所有属性:4.4 平衡口插入、3.5 单端口插入、低增益/高增益输出、及所有自带属性」)。在 `meow_sys_state`(`g_sys`)上扩展 `struct meow_sys_devattr`,状态栏/MeowPanel/设置页共用,只读属性与可控(kcontrol)分开标注。**只设计接口,不改 launcher 实现**(落地留框架批次)。 **检测源(只读居多,可控项白名单):** | 域字段 | 类型 | 读取源 | 可写 | 说明/单位 | |---|---|---|---|---| | `out.port` | int 0-7 | `amixer cget numid=9` | **写=切输出**(白名单1-4) | 现读=0(空闲);1=lineout/2=headset(3.5)/3=balance(4.4)/4=Type-C SPDIF;**0/5 危险勿写**。**line-out(1)=固定满幅电平,保守默认不响应音量键、不受 lock_vol 限制**(无实机接功放可测,按最保守处理,见 §附B line-out) | | `out.headset_present` | bool | `/sys/class/switch/headset/state` | 只读 | 3.5 插入(=1) | | `out.balance_present` | bool | `/sys/class/switch/balance/state` | 只读 | 4.4 插入(=1) | | `out.earpods_adc_sw` | bool | `/sys/devices/platform/earpods_adc/earpods_adc/earpods_adc_sw` | 只读 | 耳塞型判定 | | `out.balance_lineout_en` | bool | `amixer cget numid=8` | 写 | 平衡 Lineout 使能 | | `vol.left`/`vol.right` | int 0-255 | `amixer cget numid=1/2`(读恒 0,dummy get) | **写(真写 DAC)** | meow_vol_set 已用;get 仅跟踪值 | | `out.vol_max_headset`/`out.vol_max_balance` | int % | `config.json` volume.`lock_vol` | 只读(玩家态) | 3.5=55 / 4.4=43 ——**按输出口分级音量上限(实际「增益/响度等级」代理)** | | `out.vol_default_headset`/`out.vol_default_balance` | int % | `volume.default_vol` | 只读 | 20 / 15 | | `out.vol_warn_headset`/`out.vol_warn_balance` | int % | `volume.warn_vol` | 只读 | 34 / 28 | | `codec.soft_mute`/`mute_out`/`nos_en`/`dre_en`/`dop_en` | bool | numid=5/3/6/7/10 | 写 | 软静音/硬静音/关过采样/动态范围增强/DoP 编码(numid 8/10 put=存驱动标志,get=dummy) | | `codec.digital_filter` | int 0-4 | numid=4 | 写 | 滤波模式 | | `dac.sample_rate` / `dac.is_dsd` | int/bool | **无任何只读路径**:`/proc/asound` 不存在、codec `reg_val/write_reg_val` **只写**(`--w-------`,回读 EACCES)、`current_samplerate` 未导出、dmesg 已滚出 | 只读 | **Unsupported**(标记 `MEOW_ERR_NOENT`);需玩家态或改驱动导出 | | `usb_dac.mode` | bool | `/sys/class/udc/13500000.otg_new/is_a_peripheral`+`state` + `configfs usb_gadget/*/UDC` | 只读 | 当前 `is_a_peripheral=0`,state=not attached → 非 DAC 模式;=1 当 `is_a_peripheral=1`+gadget pid=0x0004 | | `usb_dac.vid`/`pid`/`dac_pid` | int | `/usr/resource/config.json` | 只读 | 0x32BB / 0x0101 / 0x0004 | | `bt.powered` | bool | `bluetoothctl show` `Powered`(或 `hciconfig` UP/DOWN);**勿用** `rfkill/*/soft` | 写(可控开/关) | 当前 no(rfkill soft=0/hard=0 但 HCI 未上电) | | `bt.mac`/`bt.name`/`bt.codec` | str | `/sys/class/bluetooth/hci0/{address,name}` + 连接后 bluealsa | 只读 | codec 无连接不可读 | | `batt.capacity`/`voltage`/`status` | int%/µV/enum | `power_supply/battery/*` | 只读 | 100% / 4342000µV / Discharging | | `chg.online`/`current`/`temp` | bool/µA | `fusb302-typec-source/online` + `mp2731-charger/*` | 只读 | 0 / 0µA / 30.0℃(当前未充) | | `ac.present` | bool | `power_supply/ac/present` | 只读 | 0 | | `sys.temperature` | int | 无 thermal_zone | 只读 | **不可读**(x1600 未暴露) | **关键结论**:① 3.5/4.4 是独立输出口,由 `Output Port Switch`(numid=9) 路由、由 `/sys/class/switch/headset|balance` 判别插入(headset 走 ADC=1 通道、balance 走 GPIO=PC24;`balancelo`/`lineout` 开关**未启用**),**有独立端口检测、无独立"平衡/单端"切换控件**;② **硬件/驱动层无高/低增益接口**——`codec_set_gain` 反汇编=**strcmp**、`codec_set_pregain/lrbalance/dsd_compensate`=**`jr ra` 空操作**、无 gain kcontrol、codec `reg_val` **只写不可回读**;幅度即 Playback Volume(0-255)、动态仅 DRE_EN;**真实的按输出口响度分级在玩家态 `config.json`(lock_vol 55/43、default_vol 20/15、warn_vol 34/28),可读**;③ USB DAC 模式 = `is_a_peripheral=1` + gadget(pid=0x0004)(当前 `is_a_peripheral=0`,state=not attached → 非 DAC 模式);④ CPU 温度、运行时采样率/DSD **无干净只读接口**(采样率/DSD 因 `/proc/asound` 缺失+codec reg 只写+`current_samplerate` 未导出而 **Unsupported**;温度因无 thermal_zone 而不可读),均标记 MEOW_ERR_NOENT。 **API / 暴露**(`meow_sys.h` + 三端复用,同 meow_settings):`meow_sys_devattr_refresh(&out)`、`meow_devattr_set(field,value)`(白名单)、`meow_devattr_enum_label(field,idx,buf,len)`;MeowPanel `GET/POST /api/device`;CLI `meowcli device [set]`。 **安全**:白名单写(out.port 1-4、vol、filter/nos/dre/dop/mute、bt.powered);禁写 0/5 端口;禁写 codec `write_reg_val`(保留为调试钩子);volume 读用跟踪值。 **附 B:line-out 与 gain(2026-08-29 实机更新,无实测保守默认)** - **`out.gain`(高/低)= 播放器应用层数字增益**,选 `ot_devices.json``VOLUMES.HW.Gains` 的 HDB/LDB(实机仅暴露高/低两档,MDB 未暴露;框架与曲线本有 HDB/MDB/LDB 三档)。选择值存 `user.ini`(位置式 struct + 自定义完整性字 `0x0134fe6e`,读不干净、写有完整性风险)→ **MeowOS 只读标记 `Unsupported`(MEOW_ERR_NOENT)**,不设可写;`ot_devices.json` 增益曲线作只读信息展示(标注"工厂校准,非当前选择态")。**增益(输出电平曲线) ≠ 音量上限(lock_vol)**,二者独立相乘叠加。 - **`out.port=1`(line-out,3.5mm 同口)**:给功放/台式机的满幅固定电平。**保守默认**:MeowOS 切到 line-out 后**不响应音量键**、**不套用 lock_vol 上限**(`out.vol_max_headset` 不作用于 line-out),UI 标注"固定电平输出";仅当用户在设置里显式开启"line-out 可调"时才放开。**该行为未实测**(无功放设备),待补:切 line-out 后音量键是否仍生效、音量滑杆是否影响输出、lock_vol 是否绕过——测后再松开约束。 - **刷新触发**:用户可能经 HibyOS 切增益档 / 切输出口,MeowOS 需在这两个事件后全量重抓 `meow_sys_refresh()`(与"每次进 MeowOS 抓一遍"同逻辑),勿只靠 1s 轻刷。