全屏滑条统一路径+对比色(112/115) + 字体索引+查列表+丢失降级(113) + 文档刷新(114) + svcd CLI文档+mips-repo拆bins/mpk(116)

This commit is contained in:
MeowOS Dev committed 2026-08-30 14:14:30 +08:00
1 parent b75ecce5de
commit 19a001f007
22 files changed
+627 -90

No files matched your search

+10 -8
View File
@@ -23,22 +23,24 @@
## 当前架构与组件(2026-08-30 快照)
- **SDK 统一日志 + 通知(M2.52/53 已落地)**:`meow_log()` 按 app 分目录、每次启动新文件 `MeowOS/logs/apps/<app_id>/logs/<ts>_<pid>.log`,单文件 2MB 轮转、每 App 总量 50MB 删最旧;通知 API `meow_notify_*`(4 类型×每类型 4、媒体单例);**#5 SDK↔IPC 桥**(`meow_notify_ipc.c/.h`:app→launcher 中央存储,send/cancel 接入、回填 launcher `nid`,无 socket 降级不崩)。通知中心 UI / D-Bus 桥 / TF 转储为后续任务。
- **TF 卡多字体库(M2.6 字重结构)**:`MeowOS/fonts/<family>/<weight>/`(`font.png`+`font.idx`+`font.json` 三件套),默认 `MeowOS/fonts/default/regular`;`font.file` 设置键选默认套(family→字重两级子菜单);SDK `load_font` **优先读 env `MEOW_FONT_PNG/IDX`**(launcher 每次 `launch_app` 注入),app 未自带字库时**跟随系统 `font.file`**;≤16 套卡内(防爆);§5.7 字体管理 + 应用字库(多句柄,**草案未实现**)。已部署 6 套(思源宋体 / MiSans / GoogleSans / Meslo / 思源黑体 / Ubuntu Mono,`<family>/regular`,位深 4)。
- **SDK 统一日志 + 通知(M2.52/53 已落地 + 模型补全)**:`meow_log()` 按 app 分目录、每次启动新文件 `MeowOS/logs/apps/<app_id>/logs/<ts>_<pid>.log`,单文件 2MB 轮转、每 App 总量 50MB 删最旧;通知 API `meow_notify_*`(4 类型×每类型 4、媒体单例);**#5 SDK↔IPC 桥**(`meow_notify_ipc.c/.h`:app→launcher 中央存储,send/cancel 接入、回填 launcher `nid`,无 socket 降级不崩)。**通知模型补全(agent-107)**:SDK 补 `icon`+`meow_notify_update_progress`,IPC `m_notify_t` 补 `urgency`(复用 pad)+`MSG_NOTIFY_PROGRESS`,存储补 urgency + icon 回退 + progress(内存实时/里程碑落盘),dump CSV 加 3 列;**#6 TF 转储**已落地(`MeowOS/notifications/` appended CSV + 10MB 滚动 + 开机清板载/内存表)。通知中心 UI / D-Bus 桥为后续任务。
- **TF 卡多字体库 + 字重结构(M2.5x/M2.58)**:`MeowOS/fonts/<family>/<weight>/`(`font.png`+`font.idx`+`font.json` 三件套),默认 `MeowOS/fonts/default/regular`;`font.file` 设置键选默认套(**family 点开→字重子菜单**,两级扫描 fam/wt/name 数组);`gen_font.py --weight` 写 `font.json.weight` + `--charset` 档位(common/gbk/full);SDK `load_font` **优先读 env `MEOW_FONT_PNG/IDX`**(launcher 每次 `launch_app` 注入),app 未自带字库时**跟随系统 `font.file`**;多字体 6 类(按字符类别路由) + `font.load_full` 大字库开关 + 档位按需加载;≤16 套卡内(防爆);§5.7 字体管理 + 应用字库(多句柄,**草案未实现/**字体索引 agent-113 进行中)。已迁移卡上 8 套到 `<family>/regular/`(default / google_sans / meslo / misans / source_han_sans / source_han_serif / ubuntu_mono / ztest,位深 4)。
- **全屏机制(M2.5x)**:`app.json fullscreen`(app 级)+`route.<id>.fullscreen`(activity 级)+env `MEOW_FULLSCREEN` 注入,launcher 启动 app/切 activity 前预设 SDK 全屏;进入/退出全屏=**状态栏上收出屏顶只剩提示线(4px)**+自然过渡;全屏下保留左缘返回手势+顶部伸缩滑条+顶部下滑开通知中心(热区缩小)。⚠️ 全屏滑条仍有已知问题在跟进(agent-112)。
- **包名/目录安卓化**:`app.json` `package`(反域名**权威 app_id**,==目录名,缺省回退目录名)→ 示例 `os.meow.hello`;目录 `MeowAPPs/os.meow.hello`;数据 `MeowData/<pkg>/{data,cache}`。
- **meow_svcd 服务管理器**(`patch_v2/meow_svcd/`,mipsel 静态 ~99KB,进固件不入卡):类 systemd/systemctl,由 supervisor(bootmenu/launcher)`fork/exec` 托管;读 TF `MeowOS/services.d/<name>.conf`(目录式 `key=value`,未知键忽略/前向兼容);服务=svcd **独立子进程**(崩溃隔离:服务挂不影响 svcd/supervisor);事件触发 `boot`/`card-mount`/`wifi-up`(1s 轮询);`-f` 前台便于 supervisor `waitpid`;状态机 DISABLED/STOPPED/RUNNING/STOPPING/FAILED + CLI。**接入 supervisor 启动链未做**。
- **mDNS 方案A(已拍板)**:完整 **`mdnsd`**(994KB,mipsel 静态)放 TF `MeowOS/opt/mdnsd/mdnsd`,由 meow_svcd 托管(需 root/setcap `cap_net_bind_service,cap_net_raw` 绑 UDP5353+组播 224.0.0.251);SAResponder(885KB)因"单服务注册型"无法按名访问设备(`ingenic.local`)被排除。⚠️ 手机热点组播常在客户端间丢失 → mDNS 有风险,主机 DNS 兜底。**`MeowOpt` 已废弃并入 `MeowOS/opt`**(二进制归 MeowOS/opt,拔卡降级不崩)。
- **meow_svcd 服务管理器**(`patch_v2/meow_svcd/`,mipsel 静态 ~99KB,进固件不入卡):类 systemd/systemctl;**已接入固件开机链**(`etc/init.d/S89meow_svcd` 开机自启,`-f` 前台 + 重挂循环,置于 S84ssh 后、S90 前;bootmenu/launcher `fork/exec`+`waitpid` 托管仍为备选);**双源 services.d**(板载 `/usr/data/meow/services.d/` + TF `MeowOS/services.d/<name>.conf`,同名板载优先+WARN;目录式 `key=value`,未知键忽略/前向兼容);服务=svcd **独立子进程**(崩溃隔离:服务挂不影响 svcd/supervisor);事件触发 `boot`/`card-mount`/`wifi-up`(1s 轮询);`-f` 前台便于 supervisor `waitpid`;状态机 DISABLED/STOPPED/RUNNING/STOPPING/FAILED + CLI。
- **mDNS(自研 `meow_mdns`,IPv4-only)+ `meow_svcd` 双源服务**:完整 mDNSResponder/SAResponder 均因内核无 IPv6(`socket(AF_INET6)` 失败)被否,改**自研 `meow_mdns`**(mipsel 静态 ~79KB)→ **进固件 `/usr/bin/meow_mdns`**(不入卡),由 `meow_svcd` 托管。`meow_svcd` **双源 services.d**(板载 `/usr/data/meow/services.d/` + TF `MeowOS/services.d/`,同名板载优先+WARN),`etc/init.d/S89meow_svcd` 开机自启并预置 `mdnsd.conf`(trigger=boot);hostname=**meowos**(`system.hostname`,S89 应用)。⚠️ **手机热点组播隔离**:设备→主机组播被热点丢弃 → 该热点下 `avahi-resolve <host>.local` 超时(网络层限制,非缺陷),主机需 DNS 兜底。**`MeowOpt` 已废弃并入 `MeowOS/opt`**。
- **boot 默认→板载**:**`/data/meow_boot_default`**(O_TRUNC 单值、不递增、权威);bootmenu 读它定默认;**meow.conf 不再承担 boot_default**(其 O_APPEND 累积 bug 已修,设备已清)。
- **状态栏 40→28px**:SDK `MUI_SBAR_H=28`(字号 20)+ launcher `SB_BAR_H=28`/`SB_FONT=20`;状态栏两条实现(SDK `meow_ui_sys_statusbar_render` / launcher `draw_statusbar`)**待 MD3 收敛为单一 SDK 全局状态栏**(launcher 退役 draw_statusbar 复用 SDK)。
- **系统修复(agent-104/106,M2.56)**:亮度开机被播放器覆盖→launcher 接管压回用户值;SDK 状态栏音量读取修复(空格匹配 + clamp `volume_max`);设置开关 `meow_settings_get_switch/set_switch` 修复(SET_INT{0,1});**「关于本机」**(设置最底,只读弹窗:hostname=meowos/版本/内存/存储/WiFi);bootmenu 版本显示读 `/etc/meowos_version`(替换硬编码 0.7.9);boot 默认板载 `/data/meow_boot_default`;SDK 拆分触发线(>1200 行才拆,现状 meow_ui_gfx.c=839 行保持扁平)。
- **键码/阈值修正**:`MEOW_KEY_PREVIOUS=165`(19 是另一码,[A-6] 定案);左缘返回阈值 **65px**([A-4] 90vs65 定案 65);亮度下限 1([A-3]);热键窗口下限 60ms([A-1]);日志路径统一 `MeowOS/logs`([A-7])。
- **TF 卡资产**:包管理 CLI `nyai`(部署 `MeowOS/opt/bin/nyai`,同 launcher 包语义);应用日志 `MeowOS/logs/apps/<id>/logs/<ts>_<pid>.log`;服务定义 `MeowOS/services.d/<name>.conf`。
- **mips-repo/**(宿主侧 mipsel 软件仓库):交叉编译产物 + `build.sh` + `manifest.md`,供后续固件/TF 复用(见「目录速览」)。
- **仓库公开净化**:本地 main 单提交,HEAD 敏感 0 命中;`devdocs/`(本地开发文档 + WORK_STATE/DEV_NOTES)、`scripts/build/`(固件打包脚本)**gitignore 不入库**;`mips-repo/`(mipsel 软件仓库)入库。
- **mips-repo/**(宿主侧 mipsel 软件仓库):`bins/`(二进制工具:交叉编译产物 + `build.sh` + `manifest.md`)+ `mpk/`(`.mpk` 软件包),供后续固件/TF 复用(见「目录速览」)。
- **仓库公开净化**:净化后基线单提交 `245bee2` + 本开发批次 8 提交(领先 origin/main 8),HEAD 敏感 0 命中;`devdocs/`(本地开发文档 + WORK_STATE/DEV_NOTES)、`scripts/build/`(固件打包脚本)**gitignore 不入库**;`mips-repo/`(mipsel 软件仓库)入库。
## 目录速览
- `patch_v2/` — meow_launcherd(MeowOS 核心)、bootmenu(引导选单)、watchdog、meow_svcd(服务管理器)、sdk(meow_ui SDK)、meowpkg(包管理);源码与构建脚本
- `mips-repo/` — mipsel 交叉编译软件仓库(mdnsd 等:`README.md`+`bin/`),供后续固件/TF 复用
- `mips-repo/` — mipsel 交叉编译软件仓库(`bins/`=二进制工具=`meow_mdns`、`mdnsd` 等;`mpk/`=`.mpk` 软件包),供后续固件/TF 复用
- `font/` — 预渲染字形系统(gen 生成器、meow_font 渲染库、atlas 产物)
- `fw_apps/` — 固件组装产物(`.upt` 不入库,由 `scripts/build/build_fw_apps*.sh` 重新打包)
- `bootlogos/` — 用户定制启动图/关机 logo
@@ -48,7 +50,7 @@
## 构建/刷机
- 固件打包:`scripts/build/build_fw_apps_m253.sh`(**现 M2.53 / 0.7.48**;需交叉工具链 `zig-mipsel-cc` + musl.cc,见 `devdocs/ANALYSIS.md` 第 10 节)
- 固件打包:`scripts/build/build_fw_apps_m258.sh`(**现 M2.58 / 0.7.53**;需交叉工具链 `zig-mipsel-cc` + musl.cc,见 `devdocs/ANALYSIS.md` 第 10 节)
- 升级锁根:SD 卡根 `r3proii.upt`(固件由 `build_fw_apps*.sh` 打包成 `.upt` 再刷)
- 刷机:failsafe(关机后按住 音量加+电源)从 SD 卡根 `r3proii.upt` 升级;勿用播放器 UI 卡死的升级按钮
- 组件归属:**launcher / bootmenu / watchdog 为固件组件**(`/usr/bin`,只读 squashfs);`/usr/data` 下的同名文件仅 dev 手动 exec 快速下发,开机不用。**真机验证字体/包名/通知须重打固件 + 刷机 + reboot**。
+24 -19
View File
@@ -9,7 +9,7 @@
### 1.1 板载(NAND rootfs / /data)
- 设置持久化:**板载** `/usr/data/meow/settings.conf`(UBIFS,小文件 <10KB,跨换卡保留)`[SYSTEM_SETTINGS §2][MEOWOS_API §3]`
- **板载服务目录**:`/usr/data/meow/services.d/<name>.conf`(M2.54,Data 区,恒定存在、权威优先;首启由 `S89meow_svcd` 预置 `mdnsd.conf`)。与 TF `MeowOS/services.d/` 双源,同名板载优先 + WARN;板载 `trigger=boot` 服务不依赖卡(拔卡保留)`[meow_svcd.md][meow_svcd.c]`
- **主机名**:设置键 `system.hostname`(SET_STR,默认 `meowos`;RFC1035 首标签),`S89meow_svcd` 开机读 settings `sethostname`;供自研 mDNS 应答器公布 `<host>.local` `[SYSTEM_SETTINGS §1][mips-repo/meow_mdns]`
- **主机名**:设置键 `system.hostname`(SET_STR,默认 `meowos`;RFC1035 首标签),`S89meow_svcd` 开机读 settings `sethostname`;供自研 mDNS 应答器公布 `<host>.local` `[SYSTEM_SETTINGS §1][mips-repo/bins/meow_mdns]`
- `/etc/meowos_version`:版本唯一来源(launcher 运行时 `load_meowos_version` 读;`MEOWOS_VERSION` 宏仅作 dev 占位兜底)`[DEV_NOTES #4]`
- `/tmp`:tmpfs,开机清空;开机模式标记 `/tmp/meow_boot_mode`、电源菜单 `/tmp/meow_power_menu{,_active}`、app 沉浸 `/tmp/meowapp_state` `[BOOT_SELECTOR §1][KEY_CONTROL §9][APPS §3.5]`
- 日志启动缓冲 `/tmp/meow_boot.log`(256KB 滚动,卡出现后自动迁卡)`[APPS §4.5]`
@@ -22,17 +22,17 @@
├── Music/ # 播放器标准索引目录
├── MeowData/<pkg>/{data,cache} # 应用专属数据(exFAT 无软链,用 env 注入)
├── MeowAPPs/ # 应用(<package>=app.json.package=目录名;app.json+二进制+资源, fonts/, backgrounds/;日志已入 MeowOS/logs[源:审计 2026-08-30])
├── MeowOpt/ # 板载外部工具/二进制(如 mdnsd/,卡上加载,拔卡降级不崩)`[DEV_NOTES][meow_svcd.md]`
└── MeowOS/ # MeowOS 专属(一切非必要文件)
└── MeowOS/ # MeowOS 专属(一切非必要文件;**MeowOpt 已废弃并入此处的 opt/**)
├── backups/settings/setting.bak # 设置导出默认路径
├── services.d/ # 服务配置(meow_svcd 的 <name>.conf;TF 源,与板载 /usr/data/meow/services.d 双源,同名板载优先)`[meow_svcd.md]`
├── fonts/<family>/<weight>/ # 多字体库(每 <family>/<weight> 一套三件套;见 §5.2)
│ ├── font.png # 预渲染灰度 PNG(bit depth 4|8,gen/gen_font.py 产物)
│ ├── font.idx # 索引(MFNI v2,gen/gen_font.py 产物)
│ └── font.json # 元数据 + sha256 完整性校验(schema 见 §5.2)
│ └── font.json # 元数据 + sha256 完整性校验(schema 见 §5.2;含 weight+charset+glyph_count)
├── config/fonts/index # 字体列表索引(M2.58:CSV family,weight,path,label,tier,glyph_count;见 §5.7)
├── logs/ # 系统日志 meow_<bootts>.log + apps/...(见 §3 日志)
├── notifications/(转储区,保留最新 10MB,不被电源事件清)
├── opt/{bin,nyai,sources.list}, lib/, tools/
├── notifications/(转储区,保留最新 10MB,不被电源事件清;文件 = notifications.log,appended CSV 行)
├── opt/{bin,nyai,sources.list}, lib/, tools/ # 外围工具/二进制(MeowOpt 已废弃并入;mDNS 自研 meow_mdns 在固件,不入此)
└── themes/charging/<id>/ # 充电屏主题(theme.json manifest)
```
`[TF_CARD_LAYOUT §1][APPS 附录][NOTIFICATION_SYSTEM §5]`
@@ -129,10 +129,11 @@
- 类 systemd/systemctl。**已接入系统启动链(M2.54)**:固件 `etc/init.d/S89meow_svcd` 开机自启(`-f` 前台 + 重挂循环,失败不死;置于 S84ssh 之后、S90meow_bootmenu 之前——因 S90 exec 引导选单阻塞 rcS)。由 bootmenu/launcher `fork/exec` + `waitpid` 托管仍为备选。
- **双源 services.d**(板载优先):板载 `/usr/data/meow/services.d/<name>.conf` + TF **`MeowOS/services.d/<name>.conf`**,两处都读;同名服务板载优先(TF 同名跳过 + WARN「<name> 板载与 TF 都存在,采用板载」)。目录式 `key=value`,未知键忽略/前向兼容:`exec`(必填)/`args`/`env`/`enabled`(0=永不自动启动,默认0)/`restart`(`always`/`on-fail`/`never`,默认on-fail)/`backoff_ms`(默认1000)/`max_restart`(默认5)/`cap`(本阶段仅解析不实装)/`trigger`(`boot`/`card-mount`/`wifi-up`,默认card-mount)。
- **板载-trigger=boot 语义**:板载服务恒定存在,boot 触发不依赖卡(拔卡保留);TF 服务 boot 仍需卡。
- **服务目录规则**:**固件工具**→板载 `/usr/data/meow/services.d/<name>.conf`(Data 区,拔卡保留);**TF 工具/第三方**→TF `MeowOS/services.d/<name>.conf`(随卡存在/消失);**双源板载优先**(TF 同名被板载屏蔽 + WARN「<name> 板载与 TF 都存在,采用板载」)。
- 崩溃隔离:服务=svcd **独立子进程**(`fork+execv`),svcd 只 `waitpid`/`SIGCHLD` 感知;服务死不影响 svcd/supervisor;重启受 `backoff_ms` 退避防热循环。
- 状态机:`DISABLED/STOPPED/RUNNING/STOPPING/FAILED`;稳定运行 30s 清零 restart_count。CLI:`status/reload/start|stop|restart <name|all>/pid`(AF_UNIX 单行文本 `/tmp/meow_svcd.sock`)。
- **卡上外部工具 = `MeowOpt/`**(TF 根下平级目录,如 `MeowOpt/mdnsd/mdnsd`):MeowOS 启动时从卡加载,拔卡降级不崩 `[DEV_NOTES][meow_svcd.md]`。
- **mDNS 应答器 = `meow_mdns`(自研,已拍板替代完整 mdnsd)**:`mips-repo/meow_mdns/`,**IPv4-only**(内核无 IPv6,完整 mDNSResponder/SAResponder 均 `socket(AF_INET6)` 失败),mipsel 静态 ~79KB(md5 b23de7ce)。**进固件 `/usr/bin/meow_mdns`**(不入卡),由 meow_svcd 托管(板载 `/usr/data/meow/services.d/mdnsd.conf`,`trigger=boot`、root 即可绑 5353/组播,`cap=` 空)。功能:join `224.0.0.251:5353`,应答 `<host>.local` A(wlan0 IPv4)/`_ssh._tcp` PTR/SRV/TXT/`_services._dns-sd` PTR;支持 `MEOW_MDNS_HOST`/`_ADDR`/`_PORT`/`_TTL`/`_CF`/`_QD`/`_MCAST`/`_MIF`/`_DBG`。⚠️ **手机热点隔离**:设备→主机组播被热点丢弃,avahi 只收组播应答 → 该热点下 `avahi-resolve <host>.local` 超时(网络层限制,非应答器缺陷),主机需 DNS 兜底 `[mips-repo/meow_mdns/manifest.md][meow_svcd.md]`。
- 状态机:`DISABLED/STOPPED/RUNNING/STOPPING/FAILED`;稳定运行 30s 清零 restart_count。CLI(systemctl 式,SSH 直呼):`status/reload/start|stop|restart <name|all>/pid`,示例 `meow_svcd status`/`meow_svcd restart mdnsd`/`meow_svcd stop all`/`meow_svcd reload`;控制走 AF_UNIX 单行文本 `/tmp/meow_svcd.sock`;守护默认后台,`-f/--foreground` 前台供 supervisor 托管。
- **卡上外部工具 = `MeowOS/opt/`**(`{bin,nyai,sources.list}, lib/, tools/`):MeowOS 启动时从卡加载,拔卡降级不崩。**`MeowOpt` 已废弃并入此处**;mDNS 已改为自研 `meow_mdns`(固件 `/usr/bin`),不再放卡。`[DEV_NOTES][meow_svcd.md]`。
- **mDNS 应答器 = `meow_mdns`(自研,已拍板替代完整 mdnsd)**:`mips-repo/bins/meow_mdns/`,**IPv4-only**(内核无 IPv6,完整 mDNSResponder/SAResponder 均 `socket(AF_INET6)` 失败),mipsel 静态 ~79KB(md5 b23de7ce)。**进固件 `/usr/bin/meow_mdns`**(不入卡),由 meow_svcd 托管(板载 `/usr/data/meow/services.d/mdnsd.conf`,`trigger=boot`、root 即可绑 5353/组播,`cap=` 空)。功能:join `224.0.0.251:5353`,应答 `<host>.local` A(wlan0 IPv4)/`_ssh._tcp` PTR/SRV/TXT/`_services._dns-sd` PTR;支持 `MEOW_MDNS_HOST`/`_ADDR`/`_PORT`/`_TTL`/`_CF`/`_QD`/`_MCAST`/`_MIF`/`_DBG`。⚠️ **手机热点隔离**:设备→主机组播被热点丢弃,avahi 只收组播应答 → 该热点下 `avahi-resolve <host>.local` 超时(网络层限制,非应答器缺陷),主机需 DNS 兜底 `[mips-repo/bins/meow_mdns/manifest.md][meow_svcd.md]`。
- `meow_settings_*`(设置后端,已定)/`meowcli`/`meow_sys_devattr`/`meow_devattr_set` `[MEOWOS_API]`
- 后台权限:`MSG_HELLO/MSG_REGISTER.flags` 扩展 `MF_BACKGROUND`/`MF_PLAYBACK`;`PERF_BACKGROUND`/`PERF_AUDIO_BACKGROUND`(规划)`[FRAMEWORK_DESIGN 附录 A]`
@@ -184,7 +185,7 @@
- 刷新率:`display.refresh_rate` enum 60/30/15(默认 60);`display.refresh_always_max` bool false(关=允许 60→30→15 降档)。governor:连续 12 帧超预算降一档,连续 4 批≈48 帧远低预算升一档 `[SYSTEM_SETTINGS §1][PERF_SCHEDULER][MIGRATION_AC §12.1]`
- 息屏:`display.screen_off_timeout` int 秒 默认 **30**(0=常亮);`display.screen_off_mode` enum `panel`(面板下电+背光0+FBIOPBLANK)/`black`(背光0全黑+双击亮屏) `[SYSTEM_SETTINGS §1]`
- 亮度 clamp:`display.brightness` 0-255 默认 101(clamp ≥ `display.brightness_min`);`display.brightness_min` 默认 **1**(M2.39 由 25 改 1)——**与旧文档 25 冲突见 §A** `[SYSTEM_SETTINGS §1]`
- **主机名(M2.54)**:`system.hostname`(SET_STR 默认 `meowos`,RFC1035 首标签);`S89meow_svcd` 开机读板载 `settings.conf` 应用 `sethostname`,缺省 `meowos`/系统名;供 `meow_mdns` 公布 `<host>.local`。settings 无该键 → 默认 `meowos`。`[SYSTEM_SETTINGS §1][mips-repo/meow_mdns/manifest.md]`
- **主机名(M2.54)**:`system.hostname`(SET_STR 默认 `meowos`,RFC1035 首标签);`S89meow_svcd` 开机读板载 `settings.conf` 应用 `sethostname`,缺省 `meowos`/系统名;供 `meow_mdns` 公布 `<host>.local`。settings 无该键 → 默认 `meowos`。`[SYSTEM_SETTINGS §1][mips-repo/bins/meow_mdns/manifest.md]`
### 5.5 MeowAPPs 包结构 / .mpk / 数据
- `<pkg>/` = 程序主体+app.json+资源;`MeowData/<pkg>/{data,cache}` = 数据;首次启动自动建 data/cache 并经 env 注入,exFAT 无软链 `[APPS 附录 §1/§2]`
@@ -199,13 +200,14 @@
### 5.7 字体管理 + 应用字库接口(扩展 → SDK 统一接口 / 应用自带字库 / 多句柄)
> 在 §5.2「TF 卡多字体库(M2.5x)」基础上**扩展**:把 MeowOS 字体管理器定义为可用字库的**唯一权威来源**,向 SDK 提供统一查询/选用接口,并支持**应用自带字库**与**多句柄(多字体)**。
> ⚠️ **凡 `meow_font_*_h` / `meow_ui_*_h` / 列表枚举 / 句柄加载类接口均为「草案/待定案」**:当前 SDK 只有单字体 `meow_font_*` 与 `meow_ui_text*`(用当前默认字体),本小节为新功能、**未实现**,勿当作已落地。
> ⚠️ **状态分级(2026-08-30)**:launcher 侧字体管理器(§5.7.1)与 **两级扫描/字重子菜单(agent-111,M2.58)**、`meow_font_has_glyph` 已实现;**列表枚举(§5.7.1/§5.7.2)** 落地(**agent-113,M2.58**):「扫描结果落盘 `MeowOS/config/fonts/index`(family/weight/path/label/tier/glyph_count),SDK `meow_font_get_available_count/enumerate` 从该文件读、不依赖 launcher 内存数组;字库丢失→降级默认+日志;render 走 env `MEOW_FONT_PNG/IDX`,list 枚举走 index——两路明确」。**应用自带字库(§5.7.3)/ 多句柄(§5.7.4)/ 凡 `_h` 接口仍为「草案/未实现」**,勿当作已落地;当前 SDK 只有单字体 `meow_font_*` 与 `meow_ui_text*`(用当前默认字体)。
> `[源:meow_font.h][源:agent-79 实现][源:meow_ui.h]`
> **app 未自带字库 → 跟随系统 `font.file`(M2.x 已落地)**:launcher 每次 `launch_app` 前把「当前生效字体」注入为环境变量 `MEOW_FONT_PNG`/`MEOW_FONT_IDX`(绝对路径,可选 `MEOW_FONT_APP=<family>`);SDK `meow_ui_gfx.c load_font()` **优先读 env**,无 env 则回退卡默认 `MeowOS/fonts/default` → 固件。§5.7.3 的 `load_handle`(app 自带字库)**未实现**,故当前 app 只能跟随系统字体;一旦实现自带字库,其加载优先级应高于 env。
- **5.7.1 字体管理器(launcher 侧)= 可用字库唯一权威来源**
- 唯一维护并扫描 `MeowOS/fonts/<family>/<weight>/`(+默认),产出「字体列表」(family slug / weight slug / 显示名 / glyph_count / size_list / bit_depth)。**SDK 不自行发现字库**,一律经本接口取 launcher 扫描结果。
- 唯一维护并扫描 `MeowOS/fonts/<family>/<weight>/`(+默认),产出「字体列表」(family slug / weight slug / 显示名 / glyph_count / size_list / bit_depth)。**SDK 不自行发现字库**,一律经本接口取 launcher 扫描结果。**扫描结果落盘 `MeowOS/config/fonts/index`**(family/weight/path/label/tier/glyph_count);SDK 读该文件(不依赖 launcher 内存数组)。
- **释放语义(agent-113 落地结论)**:持久来源 = index 文件;`font_presets*`/`*_buf` 为「静态、有界(`FONT_PRESETS_MAX=17`)、非堆」数组 → 无内存泄漏、无运行期 free;「释放」= 两个扫描点(启动/进设置页)重建 + 固定上限防膨胀,离开字体列表视图无需显式 free(下次进设置/重扫自动重建)。⚠️ **未做「启动后即清空数组」**:`meow_font_effective_paths_tiered`(launch_app 大字库档位选库)与 `meow_font_reload_multifont` 运行期持续读 `font_presets_n`,清空会造成大字库档位退化(red line「保持档位/多字体行为」);如需「零驻留」,须把这几个核心路径改为直接读 index(较大重构,留待后续)。
- 扫描与校验规则沿用 §5.2:三件套 `font.png/font.idx/font.json` **缺任一件 → 该套跳过 + 日志**;`font.json` 解析 `name`(缺回退目录名)/`weight`(缺省 `regular`)/`glyph_count`/`size_list`/`bit_depth`;`sha256_png/sha256_idx` 仅 **WARN** 不硬失败(该套仍可选)。
- 列表上限沿用 `FONT_PRESETS_MAX = 17`(1 默认 + ≤16 卡内套装,防爆);顺序 = 默认(0) + 扫描顺序。扫描时机不变(启动 / 进设置页),**勿放热路径** `[源:agent-79 实现]`。
- 权威列表数据模型(SDK 侧快照,枚举时填充):
@@ -221,17 +223,20 @@
char path_png[320]; /* 系统库:相对卡根 MeowOS/fonts/<family>/<weight>/font.png */
} meow_font_info;
```
- **5.7.2 应用可读 TF 卡字体列表(统一规范接口,app 面向)** `[源:agent-79 实现][源:meow_font.h]`
- **5.7.2 应用可读 TF 卡字体列表(统一规范接口,app 面向;已落地,M2.58)** `[源:agent-79 实现][源:agent-113 实现][源:meow_font.h]`
- **读取来源 = `MeowOS/config/fonts/index`**(launcher 扫 `MeowOS/fonts/<family>/<weight>/` 后落盘;CSV 每套一条 `"family","weight","path","label","tier",glyph_count`,path=卡根相对 `font.png`,label=显示名+字重+尺寸标记,tier=common|gbk|full;默认行恒为 index0)。`meow_font_get_available_count/enumerate` **只读该文件**,不再依赖 launcher 内存数组;无卡/无 index → 仅板载默认项(family=default,count=1)。index 解析在 `font/meow_font.c`(`meow_font_index_load`),SDK 经 `meow_ui.h §5.7` 暴露。
```c
int meow_font_get_available_count(void); /* 可用字库数(含默认) */
int meow_font_get_available_count(void); /* 可用字库数(含默认;无卡→1) */
int meow_font_enumerate(int idx, meow_font_info *out); /* 按序号取,越界返回 0 */
typedef int (*meow_font_enum_cb)(const meow_font_info *info, void *ctx);
int meow_font_enumerate_cb(meow_font_enum_cb cb, void *ctx); /* 回调式,返回套数 */
int meow_font_select_by_id(int id); /* 选该套为 app 会话默认字体 */
```
- 语义:`id` = 列表序号(§5.7.1 快照);`0` = **跟随系统默认**(用户 `font.file` 所选,app 会话不覆盖);`>0` = **app 会话内固定用该套**(覆盖 `font.file` 至 app 结束)。成功 0,越界/加载失败返回负值。
- 语义:`id` = 列表序号(index 行号);`0` = **跟随系统默认**(用户 `font.file` 所选,经 env `MEOW_FONT_PNG/IDX`,app 会话不覆盖);`>0` = **app 会话内固定用该套**(覆盖 `font.file` 至 app 结束)。成功 0,越界返回负值。
- **字库丢失降级默认(不丢请求)+ 日志**:`select_by_id(id>0)` 加载失败(png 缺失/坏)→ **降级**到系统默认(env→卡默认→固件),并 `meow_log(MEOW_LOG_WARN, "font <path> missing, fallback default")`(落 app 日志 = SDK meow_log);请求不丢(仍按"选过"处理)。渲染仍走 env `MEOW_FONT_PNG/IDX`(选中的那一套),**列表枚举走 index 文件**——两路明确匹配。
- **列表快照是临时的**:launcher 仅启动/进设置页重扫,期间拔卡或改卡内字库不刷新;app 应**每次会话重新枚举**、按 `family` 持久化,勿缓存数值 id。
- 覆盖范围:系统库 `MeowOS/fonts/<family>/<weight>/` 与默认(含固件 `/usr/share/meowos/font` 回退)都反映在列表里;app 侧「选用已有字库」**只用本接口,不直接拼路径**。
- **launcher 侧系统设置同样降级 + 系统日志**:`font.file` 选中字体缺失 → `meow_font_ensure`/`meow_font_reload_multifont` 兜底链(font.file→卡默认→固件)回退默认,并 `logmsg("meow_font: selected font %s missing, fallback default")`(系统日志 = launcher logmsg;多字体类别缺失 → `multi_font: class %s font %s missing, fallback global`)。app 日志(SDK meow_log)与系统日志(launcher logmsg)分流明确。
- **5.7.3 应用自带字库(按 handle 加载)** `[源:meow_font.h]`
- 目录:`MeowAPPs/<pkg>/fonts/<handle>/{font.png,font.idx,font.json}`;`<pkg>` = `app.json.package`(反域名 app_id,见 §5.5),`<handle>` = 应用自设名字(如 `lyrics`/`reader`)。
- `app.json` 声明(**可选**):`"fonts": ["lyrics","reader"]`。作用 = MeowPanel 打包校验 / launcher 装包校验 / SDK 提供取证;**不声明也可运行时按 handle 加载**。
@@ -329,10 +334,10 @@
- **A-6 KEY_PREVIOUS(code)**:KEY_CONTROL §3 明注「KEY_PREVIOUSSONG 是 **165**;19 是另一码」;meow_ui.h `#define MEOW_KEY_PREVIOUS 19 /* KEY_PREVIOUSSONG */` 注释标错。→ **19 的标签应改**(值 19 非 prevsong;165 才是),属 meow_ui.h 注释误导。
- **A-7 app 日志路径**:APP_LOG_MANAGER §4 写 `apps/<app_id>.log`(单文件);meow_ui.h §9 / DEV_NOTES #9 为 **`apps/<包名>/logs/<启动时间>_<pid>.log`**(每次启动新文件)——用户已纠正,勿用单文件。→ 以 meow_ui.h/DEV_NOTES(M2.51)为准,APP_LOG_MANAGER 目录小节过时。
- **A-8 状态栏高**:MEOW_APP_SDK §3.1「slim ~20-40px」;实际定案 **40px**([D6] MIGRATION_AC §11.1),后被用户改为 **28px**(MUI_SBAR_H=28,字号 20,图标 ink 16/中线 14,见 M2.x 状态栏压缩)。→ 现取 28。
- **A-9 SDK 通知转储**:NOTIFICATION_SYSTEM §8「创建 10s 后自动转储到 TF」;meow_ui.h 现仅「活动表+meow_log 打日志,真实投递 UI/D-Bus 为后续」。→ 10s 转储/TF 尚未实现,属"已定未做"。
- **A-9 SDK 通知转储**:NOTIFICATION_SYSTEM §8「创建 10s 后自动转储到 TF」;meow_ui.h 现仅「活动表+meow_log 打日志,真实投递 UI/D-Bus 为后续」。→ **10s 转储/TF 已实现(M2.5x,#6)**:launcher 侧带 TTL 10s 转储钩子,dump 到 `MeowOS/notifications/notifications.log`(appended CSV + 10MB 滚动 + 开机清板载/内存表);meow_ui.h 注释「真实投递 UI/D-Bus 为后续」仍指**通知中心 UI / D-Bus 桥**未做。
- **A-10 版本来源细解**:DEV_NOTES #4「/etc/meowos_version 唯一来源;MEOWOS_VERSION 宏仅 dev 占位永不动」;APPS.md §4.6「MEOWOS_VERSION 宏=单一来源,build 时 sed 写入 etc/meowos_version」。→ 两者都强调"写入同源",但"谁是源头"措辞矛盾;实践上 build 脚本 echo 生成 /etc/meowos_version,宏作屏显 dev 占位。
- **A-11 `MeowOS/opt/` vs `MeowOpt/`**:§1.2 树中 `MeowOS/opt/{bin,nyai,sources.list},lib/,tools/`(nyai 包管 + 工具)与新增顶层 `MeowOpt/`(板载外部工具/二进制,如 mdnsd)并存,命名仅大小写之差、语义可能重叠。→ **两者是否为同一用途/是否需归并待确认**;当前按各自文档记录,勿混用。
- **A-12 `fullscreen` 字段(已定 → 本次实现)**:§5.5 与 MEOW_APP_SDK §9 将 `fullscreen` 列为 app.json 字段,但 `meow_parse_app_json()` **此前并未读取该键**(实现只解析 name/exec/icon/package/framework/immersive/route./awake./service.),属"已定未做"。→ **本次实现**:launcher 启动 app/切 activity 据 `app.json fullscreen`/`route.<id>.fullscreen` 预设 SDK 全屏(env `MEOW_FULLSCREEN`),见 §5.1/§3.1。注意全屏语义由「隐藏状态栏+隐藏返回键」细化为「状态栏收合至提示线 + 保留左缘返回+顶部提示线+下滑通知中心(热区缩小)」(见 A-13)。
- **A-13 全屏语义细化(状态栏「隐藏」→「收合至提示线」)**:既有 §3.1/§5.1 描述全屏=隐藏状态栏(`meow_ui_statusbar_height()`=0、隐藏返回键=沉浸);本次定案细化为**进入/退出全屏:状态栏向上收出屏顶、只留一条提示线高度**,**全屏下保留**左缘返回手势 + 顶部动态伸缩提示线,且**顶部下滑仍可开通知中心(顶部热区缩小)**。→ 需在 SDK 端明确「收合(collapsed)」状态(新增 `meow_ui_statusbar_collapsed()` 等)与「隐藏」的差异,UI 依此绘制提示线。
- **A-11 `MeowOS/opt/` vs `MeowOpt/`(已归并)**:曾有顶层 `MeowOpt/`(板载外部工具/二进制,如 mdnsd)与 `MeowOS/opt/{bin,nyai,sources.list},lib/,tools/`(nyai 包管 + 工具)并存、命名仅大小写之差。→ **已归并**:`MeowOpt` 废弃并入 `MeowOS/opt/`;mDNS 由完整 mdnsd(TF MeowOS/opt/mdnsd)改为**自研 `meow_mdns`(固件 `/usr/bin`,IPv4-only)**,不再放卡。全仓库统一用 `MeowOS/opt/`。
- **A-12 `fullscreen` 字段(已定 → 已实现,M2.5x)**:§5.5 与 MEOW_APP_SDK §9 将 `fullscreen` 列为 app.json 字段,但 `meow_parse_app_json()` **此前并未读取该键**(实现只解析 name/exec/icon/package/framework/immersive/route./awake./service.),属"已定未做"。→ **已实现**:launcher 启动 app/切 activity 据 `app.json fullscreen`/`route.<id>.fullscreen` 预设 SDK 全屏(env `MEOW_FULLSCREEN`),见 §5.1/§3.1。全屏语义由「隐藏状态栏+隐藏返回键」细化为「状态栏收合至提示线 + 保留左缘返回+顶部提示线+下滑通知中心(热区缩小)」(见 A-13)。
- **A-13 全屏语义细化(状态栏「隐藏」→「收合至提示线」)**:既有 §3.1/§5.1 描述全屏=隐藏状态栏(`meow_ui_statusbar_height()`=0、隐藏返回键=沉浸);本次定案细化为**进入/退出全屏:状态栏向上收出屏顶、只留一条提示线高度**,**全屏下保留**左缘返回手势 + 顶部动态伸缩提示线,且**顶部下滑仍可开通知中心(顶部热区缩小)**。→ **已实现(M2.5x)**:SDK 端明确「收合(collapsed)=状态栏缩为提示线(4px)」vs「隐藏=高度0」;全屏下保留左缘返回+顶部伸缩滑条+顶部下滑通知中心(热区缩小)。⚠️ **全屏顶部动态伸缩滑条仍有已知问题在跟进(agent-112)**。
- **A-14 `fonts` 键二义**:§5.7.3 `"fonts":[<handle>…]`=应用自带字库句柄数组;§5.2/§5.7.8 `"fonts":"full"|"gbk"|"common"`=字库档位字符串。→ 同名键两种语义,解析器需区分(字符串→档位 / 数组→句柄);若避免歧义可改用专键如 `"font_tier"`(**未拍板**)。
`[各源标注见上]`
+194
View File
@@ -22,6 +22,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> /* access():字库缺失判定用 */
/* ---------------- 最小 zlib ABI 声明(与 libz.a 匹配,mipsel 32-bit unsigned long=4B) ------- */
typedef unsigned char mzf_Bytef;
@@ -721,3 +722,196 @@ int meow_font_config_multifont(int on,
if (distinct) *distinct = d_n;
return 0;
}
/* ================================================================== */
/* M2.58 字体索引 + 应用字库查询(规格 §5.7:索引文件 / 查列表 / 丢失降级默认) */
/* ================================================================== */
/* SDK 可选日志:SDK 链 meow_log 强符号(→app 日志);launcher 无 meow_log(弱符号 → NULL,
* 静默跳过)——避免 meow_font.o 依赖 SDK,launcher 链接不至于为未定义 meow_log 而失败。 */
extern void meow_log(int lvl, const char *fmt, ...) __attribute__((weak));
#define MEOW_FONT_INDEX_MAX 32 /* index 条数上限(实际 ≤17,留余量) */
#define MEOW_FONT_DEF_PNG "/usr/data/mnt/sd_0/MeowOS/fonts/default/regular/font.png"
#define MEOW_FONT_DEF_IDX "/usr/data/mnt/sd_0/MeowOS/fonts/default/regular/font.idx"
#define MEOW_FONT_SYS_PNG "/usr/share/meowos/font/font.png"
#define MEOW_FONT_SYS_IDX "/usr/share/meowos/font/font.idx"
const char *meow_font_card_root(void)
{
const char *e = getenv("MEOW_CARD_ROOT");
return (e && e[0]) ? e : "/usr/data/mnt/sd_0";
}
int meow_font_index_path(char *out, int olen)
{
if (!out || olen <= 0) return -1;
snprintf(out, (size_t)olen, "%s/%s", meow_font_card_root(), MEOW_FONT_INDEX_REL);
return 0;
}
/* 合成「板载默认」条目(index0;无卡/无 index 时唯一项)。 */
static void meow_font_default_entry(meow_font_info *out)
{
if (!out) return;
memset(out, 0, sizeof *out);
snprintf(out->family, sizeof out->family, "default");
snprintf(out->name, sizeof out->name, "默认字体");
snprintf(out->weight, sizeof out->weight, "regular");
snprintf(out->tier, sizeof out->tier, "common");
out->glyph_count = 0;
snprintf(out->label, sizeof out->label, "默认字体 Regular(common)");
snprintf(out->path_png, sizeof out->path_png, "MeowOS/fonts/default/regular/font.png");
}
/* CSV 单字段读取(支持双引号包裹 + 内嵌双引号转义)。p 走到下一字段/行尾。 */
static int csv_field(const char **pp, char *out, int olen)
{
const char *p = *pp;
if (!out || olen <= 0) return 0;
int o = 0;
if (*p == '"') {
p++;
while (*p && o < olen - 1) {
if (*p == '"') {
if (p[1] == '"') { out[o++] = '"'; p += 2; continue; }
p++; /* 结束引号 */
break;
}
out[o++] = *p++;
}
if (*p == ',') p++;
else if (*p == '\r' || *p == '\n') { /* 行尾,由外层清理 */ }
} else {
while (*p && *p != ',' && *p != '\r' && *p != '\n' && o < olen - 1)
out[o++] = *p++;
if (*p == ',') p++;
}
out[o] = 0;
*pp = p;
return 1;
}
/* 有界复制(字符串入 dst≤dlen 且保证 NUL;避免 -Wformat-truncation 之类告警)。 */
static void fcpy(char *dst, int dlen, const char *src)
{
if (dlen <= 0) return;
size_t n = src ? strlen(src) : 0;
if (n >= (size_t)dlen) n = (size_t)dlen - 1;
memcpy(dst, src, n);
dst[n] = 0;
}
/* 解析一行 index(CSV 6 字段)。成功填 info 并返回 1;path 为空 → 0(跳过)。 */
static int meow_font_parse_index_line(char *line, meow_font_info *info)
{
if (!line || !info) return 0;
const char *p = line;
char f[6][360];
for (int i = 0; i < 6; i++) {
if (!csv_field(&p, f[i], (int)sizeof f[i])) return 0;
if (!f[i][0]) return 0;
}
memset(info, 0, sizeof *info);
fcpy(info->family, sizeof info->family, f[0]);
fcpy(info->name, sizeof info->name, f[0]); /* index 无单列 name → 回退 family */
fcpy(info->weight, sizeof info->weight, f[1]);
fcpy(info->path_png, sizeof info->path_png, f[2]);
fcpy(info->label, sizeof info->label, f[3]);
fcpy(info->tier, sizeof info->tier, f[4]);
info->glyph_count = atoi(f[5]);
return info->path_png[0] ? 1 : 0;
}
int meow_font_index_load(meow_font_info *out, int max)
{
if (!out || max <= 0) return 0;
char path[450];
if (meow_font_index_path(path, sizeof path) != 0) return 0;
FILE *f = fopen(path, "r");
if (!f) { meow_font_default_entry(&out[0]); return 1; } /* 无卡/无 index → 仅板载默认 */
int n = 0, total = 0;
char line[1024];
while (fgets(line, sizeof line, f)) {
char *s = line;
while (*s == ' ' || *s == '\t') s++;
if (!*s || *s == '\n' || *s == '\r' || *s == '#') continue; /* 忽略空行/注释 */
meow_font_info tmp;
if (!meow_font_parse_index_line(s, &tmp)) continue;
total++;
if (n < max) { out[n] = tmp; n++; }
}
fclose(f);
if (total == 0) { meow_font_default_entry(&out[0]); return 1; }
return total;
}
int meow_font_get_available_count(void)
{
meow_font_info buf[1];
return meow_font_index_load(buf, 1); /* max=1:只统计总数,不要求全填 */
}
int meow_font_enumerate(int idx, meow_font_info *out)
{
if (!out || idx < 0) return 0;
meow_font_info buf[MEOW_FONT_INDEX_MAX];
int n = meow_font_index_load(buf, MEOW_FONT_INDEX_MAX);
if (idx >= n) return 0;
memcpy(out, &buf[idx], sizeof *out);
return 1;
}
/* 相对路径按卡根解析为绝对路径(绝对路径原样返回)。 */
static void font_abs_path(const char *rel, char *out, int olen)
{
if (!rel || !rel[0]) { if (olen > 0) out[0] = 0; return; }
if (rel[0] == '/') { snprintf(out, (size_t)olen, "%s", rel); return; }
snprintf(out, (size_t)olen, "%s/%s", meow_font_card_root(), rel);
}
/* 由 png 绝对路径派生同目录 font.idx(.png → .idx)。0=成功。 */
static int font_idx_from_png(const char *png, char *out, int olen)
{
if (!png || olen <= 0) return -1;
snprintf(out, (size_t)olen, "%s", png);
size_t n = strlen(out);
if (n > 4 && !strcmp(out + n - 4, ".png")) {
out[n-4]='.'; out[n-3]='i'; out[n-2]='d'; out[n-1]='x'; out[n]=0;
return 0;
}
return -1;
}
/* 装载系统默认字体(跟随 font.file,经 env MEOW_FONT_PNG/IDX → 卡默认 → 固件)。
* 与 SDK meow_ui_gfx load_font 的优先级一致;app 经 select_by_id(0) 或任何降级回退时走此路径。 */
static int meow_font_load_system_default(void)
{
const char *ep = getenv("MEOW_FONT_PNG");
const char *ei = getenv("MEOW_FONT_IDX");
char idxbuf[400];
if (ep && ep[0]) {
if (!ei || !ei[0]) {
if (font_idx_from_png(ep, idxbuf, sizeof idxbuf) != 0) ei = NULL;
else ei = idxbuf;
}
if (ei && meow_font_load(ep, ei) == 0) return 0;
}
if (meow_font_load(MEOW_FONT_DEF_PNG, MEOW_FONT_DEF_IDX) == 0) return 0;
if (meow_font_load(MEOW_FONT_SYS_PNG, MEOW_FONT_SYS_IDX) == 0) return 0;
return -1;
}
int meow_font_select_by_id(int id)
{
if (id == 0) return meow_font_load_system_default(); /* 跟随系统默认(font.file) */
meow_font_info info;
if (!meow_font_enumerate(id, &info)) return -1; /* 越界 → 负值 */
if (!info.path_png[0]) return -1;
char png[400], idx[400];
font_abs_path(info.path_png, png, sizeof png);
if (font_idx_from_png(png, idx, sizeof idx) != 0) return -1;
if (access(png, R_OK) == 0 && meow_font_load(png, idx) == 0) return 0;
/* 字库缺失 → 降级默认(不丢请求)+ WARN(app 日志;meow_log 为 SDK 统一日志) */
if (meow_log) meow_log(2 /* MEOW_LOG_WARN */, "font %s missing, fallback default", png);
return meow_font_load_system_default();
}
+48
View File
@@ -117,6 +117,54 @@ int meow_font_config_multifont(int on,
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 */
/* ================================================================== */
/* M2.58 字体索引 + 应用字库查询(规格 §5.7:索引文件 / 查列表 / 丢失降级默认) */
/* ================================================================== */
/* 系统字体列表的持久化快照 = TF 卡 <card_root>/MeowOS/config/fonts/index(CSV,每套一条:
* "family","weight","path","label","tier",glyph_count
* path = 卡根相对 font.png(如 MeowOS/fonts/misans/regular/font.png)
* label = 显示名(含字重 + 尺寸标记,如 `MiSans Regular(common≈4k)`)
* tier = common|gbk|full(档位,信息用 + 驱动按需加载)
* launcher 扫描 MeowOS/fonts/<family>/<weight>/ 后写该文件(覆盖,含默认行 index0);
* SDK 侧 meow_font_get_available_count()/enumerate() 只从这个文件读,不依赖 launcher 内存数组;
* 无卡/无 index → 仅 1 项板载默认(family=default)。 */
#define MEOW_FONT_INDEX_REL "MeowOS/config/fonts/index"
/* 权威列表数据模型(SDK 侧快照,枚举时填充;规格 §5.7.1)。 */
#ifndef MEOW_FONT_INFO_DEFINED
#define MEOW_FONT_INFO_DEFINED
typedef struct meow_font_info {
char family[64]; /* family slug = MeowOS/fonts/<family>/ 一级目录名 */
char name[64]; /* 显示名(index 无单列 → 回退 family) */
char weight[32]; /* weight slug(regular|bold|…,缺省 regular) */
char tier[8]; /* 档位 common|gbk|full(info 用) */
int glyph_count; /* font.json glyph_count(info 用) */
char label[128]; /* index 显示名(含字重 + 尺寸标记) */
int size_list[16]; /* 原生字号表(index 无 → 空) */
int size_count; /* 有效字号数 */
int bit_depth; /* 4|8(index 无 → 0) */
char path_png[320]; /* 卡根相对 MeowOS/fonts/<family>/<weight>/font.png */
} meow_font_info;
#endif
/* 卡根:env MEOW_CARD_ROOT 优先,否则默认 /usr/data/mnt/sd_0。 */
const char *meow_font_card_root(void);
/* <card_root>/MeowOS/config/fonts/index 绝对路径写入 out;0=成功。 */
int meow_font_index_path(char *out, int olen);
/* 读 index → 填充 out 数组(最多 max 条),返回「总条数」(无 index/不可读/空 → 1=合成默认行)。
* 注意 out[i].path_png 为「卡根相对」路径,需经卡根解析为绝对路径再使用。 */
int meow_font_index_load(meow_font_info *out, int max);
/* 应用可读 TF 卡字体列表(规格 §5.7.2):
* - get_available_count():可用字库数(含默认;无卡 → 1)。
* - enumerate(idx, out):按序号取,越界返回 0;idx=0=默认行(family=default)。
* - select_by_id(id):id=0 跟随系统默认(font.file,经 env MEOW_FONT_PNG/IDX);
* id>0 会话内固定用该套;该套字库缺失 → 降级默认(不丢请求)+ WARN 日志
* (meow_log→app 日志)。成功 0,越界负值。 */
int meow_font_get_available_count(void);
int meow_font_enumerate(int idx, meow_font_info *out);
int meow_font_select_by_id(int id);
#ifdef __cplusplus
}
#endif
+72 -13
View File
@@ -2,24 +2,42 @@
目标设备:R3 Pro II / MeowOS 固件(**mipsel-linux-muslsf**,静态,无 glibc 依赖)。
用途:存放针对该设备的交叉编译二进制与构建信息,便于**复用**到后续固件 / TF 卡
(MeowOpt 目录;`MeowLib` 未定义/未用——`MeowOpt` 与 `MeowOS/opt` 命名需归一:代码用 `MeowOS/opt`,文档 `MeowOpt` 指卡上外部工具目录。[源:审计 2026-08-30])。
(`MeowOS/opt` 目录;`MeowLib` 未定义/未用——`MeowOpt` 与 `MeowOS/opt` 命名需归一:代码用 `MeowOS/opt`,文档 `MeowOpt` 指卡上外部工具目录。[源:审计 2026-08-30])。
## 仓库结构(**两半**:`bins/` 普通二进制工具 + `mpk/` `.mpk` 软件包)
## 仓库结构(每工具一个目录)
```
mips-repo/
README.md # 本文件
bins/ # 「普通二进制/工具」仓库:交叉编译产物 + 构建/元信息
<tool>/
build.sh # 交叉编译脚本(可重复执行)
build.sh # 交叉编译脚本(可重复执行;可搬,路径相对本目录)
manifest.md # 元信息:来源/版本/arch/校验/产物路径/安装目标
bin/<tool> # 交叉编译产物(静态 mipsel,如存在)
(<tool>.c 源码如自研也放此)
mpk/ # 「.mpk 文件」仓库:MeowAPPs 软件包(.mpk = tar.gz)
README.md # .mpk 规范说明(格式/字段/安装)
<pkg>.mpk # 成品包(app.json + meow 二进制 + 资源;不含 data/cache)
```
> 两者定位不同:`bins/` 是**宿主侧工具**(服务/二进制,如 mDNS 应答器、netd),由
> **meow_svcd** 托管、打进固件或按需从卡 `MeowOS/opt/<tool>/` 部署;`mpk/` 是**应用层
> 软件包**(MeowAPP 分发单元),用 **`nyai`** 安装到 `MeowAPPs/<包名>/`。
## 当前已入库工具
- **mdnsd/**:Apple mDNSResponder-2881.0.25 交叉编译产物(`mipsel-linux-muslsf` 静态)。两种变体同在 `bin/`:
### bins/(普通二进制 / 工具仓库)
- **bins/mdnsd/**:Apple mDNSResponder-2881.0.25 交叉编译产物(`mipsel-linux-muslsf` 静态)。两种变体同在 `bin/`:
- `bin/mdnsd` = **SAResponder**(mDNSResponderPosix,单服务注册型,`-n/-f` 只发布/回指定服务)strip 后 **885.6 KB**;
- `bin/mdnsd.bin` = **完整 mdnsd** 守护进程(通用宿主响应器,回任意 `.local` 查询)strip 后 **994 KB**。
⚠️ 实测(内核无 IPv6)**两者均失败**:`socket AF_INET6: Address family not supported by protocol`;且即便 `make HAVE_IPV6=0` 编译通过、daemon 能跑,**也不发布主机名 A 记录**(IPC 服务型守护进程,等客户端注册)。故 mDNS 方案A 判为**不达标**,由 `meow_mdns/` 替代(见下)。
- **meow_mdns/**:自研极简 IPv4-only mDNS 应答器(~77.6 KB,`mipsel-linux-muslsf` 静态)。join `224.0.0.251:5353`,应答 `<host>.local` A + `_ssh._tcp.local` PTR/SRV/TXT;零 IPv6 依赖。部署到 TF `MeowOS/opt/mdnsd/mdnsd`(95 KB < 阈值,可考虑进固件)。详见 `meow_mdns/manifest.md`。⚠️ 手机热点**丢弃客户端间组播**→ 设备能应答、主机收不到(avahi 只认组播应答),`avahi-resolve *.local` 仍超时(网络层热点隔离,非应答器缺陷),需主机 DNS 兜底。
⚠️ 实测(内核无 IPv6)**两者均失败**:`socket AF_INET6: Address family not supported by protocol`;且即便 `make HAVE_IPV6=0` 编译通过、daemon 能跑,**也不发布主机名 A 记录**(IPC 服务型守护进程,等客户端注册)。故 mDNS 方案A 判为**不达标**,由 `bins/meow_mdns/` 替代(见下)。
- **bins/meow_mdns/**:自研极简 IPv4-only mDNS 应答器(~77.6 KB,`mipsel-linux-muslsf` 静态)。join `224.0.0.251:5353`,应答 `<host>.local` A + `_ssh._tcp.local` PTR/SRV/TXT;零 IPv6 依赖。部署到 TF `MeowOS/opt/mdnsd/mdnsd`(95 KB < 阈值,可考虑进固件,实际已改板载 `/usr/bin/meow_mdns`,见下)。详见 `bins/meow_mdns/manifest.md`。⚠️ 手机热点**丢弃客户端间组播**→ 设备能应答、主机收不到(avahi 只认组播应答),`avahi-resolve *.local` 仍超时(网络层热点隔离,非应答器缺陷),需主机 DNS 兜底。
### mpk/(`.mpk` 软件包仓库)
- 目前**为空**(仅 `README.md` 说明规范)。MeowAPPs 成品包 `.mpk` 集中放此,供 `nyai` 经
`sources.list`(本地源 = `MeowOS/opt/repo`)或 `nyai install <x.mpk>` 直接安装。
## 目标架构与工具链
- 架构:`mipsel`(MIPS32r2, little-endian)
@@ -27,13 +45,54 @@ mips-repo/
- 交叉编译器:`~/.tools/zig-mipsel-cc`(zig 目标 mips-linux-musl)
- 目标静态库 sysroot:`~/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf`
## 新增一个工具的流程
1. `mkdir -p <tool>/bin`,写 `<tool>/build.sh`(用 zig-mipsel-cc,`-Os -static`)。
2. 跑 `build.sh`,产出放 `<tool>/bin/<tool>`。
3. 写 `<tool>/manifest.md`:来源(如 Apple-OSS/mDNSResponder 版本)、ARCH、`file`
## 服务管理:meow_svcd CLI(托管 `bins/` 下的服务)
`bins/` 里的常驻服务(如 `meow_mdns`)由 **meow_svcd**(类 systemd/systemctl,固件
`/usr/bin/meow_svcd`,板载 `etc/init.d/S89meow_svcd` 开机自启)托管,SSH 到设备后可直接用:
```
meow_svcd status # 列出所有服务与状态(card_present/wifi_up/services=N)
meow_svcd restart meow_mdns # 重启单服务(如板载 mDNS 应答器)
meow_svcd stop all # 停止全部服务
meow_svcd reload # 重读板载+TF 双源 services.d
meow_svcd start meow_mdns # 启动单服务
meow_svcd pid # 打印守护进程 pid
```
- 控制走 AF_UNIX 单行文本,默认 socket `/tmp/meow_svcd.sock`;应答 `OK` / `ERR no-such-service` /
多行 `svc=<name> state=… pid=… restart=… exit=…`。守护默认**后台**;`-f/--foreground` 前台(供
supervisor `fork/exec`+`waitpid` 托管)。
- **服务目录规则(双源板载优先)**:固件工具 → 板载 `/usr/data/meow/services.d/<name>.conf`(Data 区,
拔卡保留);TF 工具/第三方 → TF `MeowOS/services.d/<name>.conf`(随卡存在/消失);同名板载优先(TF 被屏蔽 + WARN)。
- 详见 `patch_v2/meow_svcd/meow_svcd.md` / `docs/MEOWOS_SPEC_REF.md §4`。
## 应用包:`.mpk` 规范 & `nyai`
`.mpk` 是 MeowOS 的应用分发单元(`MeowAPPs`),规范与 `nyai` 用法见 `APPS.md 附录 §3(.mpk) / §5(nyai)`。
要点:
- **包名 = 文件名茎 = `app.json` 的 `package`**(如 `os.meow.hello` → `os.meow.hello.mpk`)。
- **格式**:`.mpk` = tar.gz,含 `app.json` + `meow` 二进制 + 资源,**不含 data/cache**(运行时按设备生成)。
- **安装**:`nyai install <pkg或文件.mpk>`;`nyai index <dir>` 为目录生成 `Packages` 索引
(Package/Version/Filename/Size/SHA256/Depends/Description);`nyai export <pkg>` 打包导出。
- **部署 `nyai`**:`MeowOS/opt/bin/nyai`(卡,随 TF_CARD_LAYOUT 外围工具目录)。M2.12+ 固件
`meow_card_path.sh` 已前插 `MeowOS/opt/bin` 可直呼;更早固件上 SSH 需绝对路径
`/usr/data/mnt/sd_0/MeowOS/opt/bin/nyai` 或 `export PATH=/usr/data/mnt/sd_0/MeowOS/opt/bin:$PATH`。
## 新增一个工具/包的流程
- **工具(进 `bins/`)**:
1. `mkdir -p bins/<tool>/bin`,写 `bins/<tool>/build.sh`(用 zig-mipsel-cc,`-Os -static`)。
2. 跑 `build.sh`,产出放 `bins/<tool>/bin/<tool>`。
3. 写 `bins/<tool>/manifest.md`:来源(如 Apple-OSS/mDNSResponder 版本)、ARCH、`file`
输出摘要、`ls -l` 大小、构建命令、安装路径(固件 or TF MeowOpt)。
- **应用包(进 `mpk/`)**:
1. 打包不含 data/cache 的 `MeowAPPs/<包名>/` → `mpk/<包名>.mpk`(或 `nyai export`)。
2. `nyai index mpk/` 生成/刷新 `Packages` 索引(供经源安装)。
3. `.mpk` 字段与安装语义见 `mpk/README.md` / `APPS.md 附录 §3`。
## 部署到哪里
- 体积小(约 <200KB)→ 可直接打进固件(/usr/bin 或由 supervisor 启动)。
- 体积大 → 放 TF 卡 `MeowOpt/<tool>/`,由 MeowOS 启动时从卡加载(拔卡降级不崩)。
- **按需取用**:从 `MeowOpt/<tool>/` 按需部署,不一定要全部入库;若某工具体积仍偏重(如 mdnsd 的 885~994KB),可评估更小方案(OpenWrt linkd / libmdns,或自研极简响应器——仅回 A 记录 + `_ssh` 服务)以塞进固件 <200KB。
- 体积小(约 <200KB)→ 可直接打进固件(/usr/bin 或由 supervisor/meow_svcd 启动)。
- 体积大 → 放 TF 卡 `MeowOS/opt/<tool>/`,由 MeowOS 启动时从卡加载(拔卡降级不崩)。
- **按需取用**:从 `MeowOS/opt/<tool>/` 按需部署,不一定要全部入库;若某工具体积仍偏重(如 mdnsd 的 885~994KB),可评估更小方案(OpenWrt linkd / libmdns,或自研极简响应器——仅回 A 记录 + `_ssh` 服务)以塞进固件 <200KB。
- `.mpk` → 经 `nyai` 装到 `MeowAPPs/<包名>/`(数据建 `MeowData/<包名>/{data,cache}`)。
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+49
View File
@@ -0,0 +1,49 @@
# mips-repo/mpk/ — MeowOS `.mpk` 软件包仓库
本目录存放 MeowAPPs 的成品**`.mpk`** 软件包(应用分发单元),供 `nyai` 安装。规范见
`APPS.md 附录 §3(.mpk) / §5(nyai)`,此处为速览与示例。
目前仓库为**空**(仅说明);首个包入仓后形如:
```
mpk/
README.md # 本文件
<pkg>.mpk # 成品包(tar.gz)
Packages # 可选:nyai index mpk/ 生成的索引
```
## `.mpk` 规范
- **包名 `<pkg>` = 文件名茎 = `app.json` 的 `package`**(如 `os.meow.hello` → `os.meow.hello.mpk`)。
- **格式**:`.mpk` = **tar.gz**,含 `app.json` + `meow` 二进制 + 资源,**不含 `data`/`cache`**
(运行时按设备生成)。打包目录为 `MeowAPPs/<包名>/`(剔除 data/cache 后打包)。
- **关键字段**(在 `app.json` 内,`package` 必填):`package`/`name`/`version`/`exec`/
`routes`/`services`/`depends`/`description` 等;`size`/`sha256` 由索引生成。
- **安装**:`nyai install <包或文件.mpk>`(读 `.mpk` 内 `app.json`,校验包名/哈希,解压到
`MeowAPPs/<包名>/`,同名先备份 `.bak_<pid>`,触发数据 init 建 `MeowData/<包名>/{data,cache}`)。
- **导出**:`nyai export <pkg> [out.mpk]`(打包 `MeowAPPs/<pkg>/`,不含 data/cache)。
- **索引**:`nyai index <dir>` 为目录生成 `Packages`(每行 `Package/Version/Filename/Size/SHA256/
Depends/Description`,空行分隔记录),供经源(`sources.list`)安装解析依赖。
## 示例(结构示意)
以 `os.meow.hello` 为例,打包自 `MeowAPPs/os.meow.hello/`:
```bash
# 打包导出
nyai export os.meow.hello # → os.meow.hello.mpk
# 或手动:剔除 data/cache 后 tar.gz(与 launcher 同 gzip -dc | tar -x 管道语义)
cd MeowAPPs/os.meow.hello && tar czf os.meow.hello.mpk app.json meow assets/
# 本地源目录 + 索引生成
mkdir -p /usr/data/mnt/sd_0/MeowOS/opt/repo
cp os.meow.hello.mpk /usr/data/mnt/sd_0/MeowOS/opt/repo/
nyai index /usr/data/mnt/sd_0/MeowOS/opt/repo # 生成 Packages
# 在设备上安装(M2.12+ 可直呼 nyai;更早固件用绝对路径)
nyai install os.meow.hello # 或 nyai install os.meow.hello.mpk
```
> 注:`nyai` 部署于卡 `MeowOS/opt/bin/nyai`(M2.12+ 由 `meow_card_path.sh` 前插 PATH 可直呼)。
> 卡上源配置 `MeowOS/opt/nyai/sources.list`(无此文件默认本地源 `MeowOS/opt/repo`)。
Binary file not shown.
+48
View File
@@ -117,6 +117,54 @@ int meow_font_config_multifont(int on,
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 */
/* ================================================================== */
/* M2.58 字体索引 + 应用字库查询(规格 §5.7:索引文件 / 查列表 / 丢失降级默认) */
/* ================================================================== */
/* 系统字体列表的持久化快照 = TF 卡 <card_root>/MeowOS/config/fonts/index(CSV,每套一条:
* "family","weight","path","label","tier",glyph_count
* path = 卡根相对 font.png(如 MeowOS/fonts/misans/regular/font.png)
* label = 显示名(含字重 + 尺寸标记,如 `MiSans Regular(common≈4k)`)
* tier = common|gbk|full(档位,信息用 + 驱动按需加载)
* launcher 扫描 MeowOS/fonts/<family>/<weight>/ 后写该文件(覆盖,含默认行 index0);
* SDK 侧 meow_font_get_available_count()/enumerate() 只从这个文件读,不依赖 launcher 内存数组;
* 无卡/无 index → 仅 1 项板载默认(family=default)。 */
#define MEOW_FONT_INDEX_REL "MeowOS/config/fonts/index"
/* 权威列表数据模型(SDK 侧快照,枚举时填充;规格 §5.7.1)。 */
#ifndef MEOW_FONT_INFO_DEFINED
#define MEOW_FONT_INFO_DEFINED
typedef struct meow_font_info {
char family[64]; /* family slug = MeowOS/fonts/<family>/ 一级目录名 */
char name[64]; /* 显示名(index 无单列 → 回退 family) */
char weight[32]; /* weight slug(regular|bold|…,缺省 regular) */
char tier[8]; /* 档位 common|gbk|full(info 用) */
int glyph_count; /* font.json glyph_count(info 用) */
char label[128]; /* index 显示名(含字重 + 尺寸标记) */
int size_list[16]; /* 原生字号表(index 无 → 空) */
int size_count; /* 有效字号数 */
int bit_depth; /* 4|8(index 无 → 0) */
char path_png[320]; /* 卡根相对 MeowOS/fonts/<family>/<weight>/font.png */
} meow_font_info;
#endif
/* 卡根:env MEOW_CARD_ROOT 优先,否则默认 /usr/data/mnt/sd_0。 */
const char *meow_font_card_root(void);
/* <card_root>/MeowOS/config/fonts/index 绝对路径写入 out;0=成功。 */
int meow_font_index_path(char *out, int olen);
/* 读 index → 填充 out 数组(最多 max 条),返回「总条数」(无 index/不可读/空 → 1=合成默认行)。
* 注意 out[i].path_png 为「卡根相对」路径,需经卡根解析为绝对路径再使用。 */
int meow_font_index_load(meow_font_info *out, int max);
/* 应用可读 TF 卡字体列表(规格 §5.7.2):
* - get_available_count():可用字库数(含默认;无卡 → 1)。
* - enumerate(idx, out):按序号取,越界返回 0;idx=0=默认行(family=default)。
* - select_by_id(id):id=0 跟随系统默认(font.file,经 env MEOW_FONT_PNG/IDX);
* id>0 会话内固定用该套;该套字库缺失 → 降级默认(不丢请求)+ WARN 日志
* (meow_log→app 日志)。成功 0,越界负值。 */
int meow_font_get_available_count(void);
int meow_font_enumerate(int idx, meow_font_info *out);
int meow_font_select_by_id(int id);
#ifdef __cplusplus
}
#endif
Binary file not shown.
+86 -3
View File
@@ -897,7 +897,7 @@ static int meow_font_ensure(void)
return 1;
}
if (have_sel)
logmsg("meow_font: %s load failed, fallback", fpng);
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) {
@@ -906,6 +906,7 @@ static int meow_font_ensure(void)
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;
}
@@ -1082,6 +1083,9 @@ static void meow_font_reload_multifont(void)
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);
}
}
}
@@ -1900,10 +1904,84 @@ static int meow_font_probe_weight(const char *dirpath, const char *family, const
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);
}
/* 扫描 <card_root>/MeowOS/fonts/ 下两级 <family>/<weight>/,重建字体 preset 列表。
* 每个 (family,weight) 组合一套(如 misans/regular、misans/bold);family 级 'default' 由
* 索引 0(默认字体)单独处理,不进循环,避免重复。上限 FONT_PRESETS_MAX
* (1 默认 + ≤16 用户字体)。无卡/无目录时仅默认。 */
* (1 默认 + ≤16 用户字体)。无卡/无目录时仅默认。
*
* M2.58 释放语义(规格 §5.7 需求 4「扫→写 index→释放(或按需读 index)」结论):
* - 持久来源 = <card_root>/MeowOS/config/fonts/index(扫描后由 meow_font_write_index 落盘)。
* SDK 只读该文件,从不依赖本内存数组。
* - font_presets* / *_buf 均为「静态、有界(FONT_PRESETS_MAX=17)、非堆」数组:无内存泄漏,
* 故无运行期 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(较大重构,超出本批边界,留待后续)。 */
static void meow_font_scan_presets_root(const char *card_root)
{
font_presets_n = 0;
@@ -1933,7 +2011,7 @@ static void meow_font_scan_presets_root(const char *card_root)
char root[340];
snprintf(root, sizeof root, "%s/%s", card_root, MEOW_FONTS_CFG);
DIR *d = opendir(root);
if (!d) return; /* 无卡/无字库目录:仅默认 */
if (d) { /* 无卡/无字库目录 → 跳过扫描,仅默认 */
struct dirent *de;
while ((de = readdir(d)) && font_presets_n < FONT_PRESETS_MAX) {
if (de->d_name[0] == '.') continue;
@@ -1970,6 +2048,11 @@ static void meow_font_scan_presets_root(const char *card_root)
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 静默放弃(此路径不刷屏)。 */
meow_font_write_index(card_root);
}
/* 设备默认卡根扫描(等价现状:无卡时 opendir 失败 → 仅默认字体)。 */
+26
View File
@@ -158,6 +158,32 @@ meow_svcd pid
应答:`OK` / `ERR no-such-service` / 多行 `svc=... state=... pid=... restart=... exit=...`。
### 6.1 `systemctl` 式 CLI 用法(SSH 终端示例)
守护已由固件 `etc/init.d/S89meow_svcd` 开机自启,SSH 登录设备后**直接敲**命令即可,无需先知道 socket:
```bash
meow_svcd status # 列出所有服务 + 全局状态
# # meow_svcd status
# card_present=1 wifi_up=0 services=3
# svc=mdnsd state=running pid=1234 restart=0 exit=0 trigger=boot
# svc=meow_netd state=stopped pid=0 restart=0 exit=0 trigger=card-mount
# svc=ntpc state=stopped pid=0 restart=0 exit=0 trigger=card-mount
meow_svcd restart mdnsd # 重启单个服务(如板载 mDNS 应答器)
meow_svcd start meow_netd # 启动单个服务
meow_svcd stop all # 停止全部服务
meow_svcd reload # 重读板载 + TF 双源 services.d 配置
meow_svcd pid # 打印守护进程 pid
```
- **socket**:控制走 AF_UNIX 单行文本,默认 `/tmp/meow_svcd.sock`(`--sock`/`MEOW_SVCD_SOCK` 可改)。
守护**未运行**时命令回退打印状态文件 `/tmp/meow_svcd.status`(或 `card_present=0 services=0`)并以退出码 1 返回。
- **守护前台**:`-f`/`--foreground` —— 守护自身跑前台,供 supervisor `fork/exec`+`waitpid` 托管;
默认(无子命令)则为后台常驻。
- 应答:成功 `OK`(`pid`→pid 数字);服务不存在 `ERR no-such-service`;`svc=... state=...` 行
含 `pid/restart(重启计数)/exit(上次退出码)/trigger`。
## 7. 编译
```bash
+35
View File
@@ -333,6 +333,41 @@ int meow_notify_has_active(void); /* 1=尚有活动通知,0=无 *
const char *meow_notify_urgency_str(meow_notify_urgency u);
const char *meow_notify_category_str(meow_notify_category cat);
/* ============================================================ */
/* §5.7 应用可读 TF 卡字体列表(M2.58:字体索引 / 查列表 / 丢失降级默认) */
/* ============================================================ */
/* 系统字体管理器的面向应用接口(规格 §5.7.1/§5.7.2):
* - 权威列表 = launcher 扫描 MeowOS/fonts/<family>/<weight>/ 后落盘的持久文件
* MeowOS/config/fonts/index(CSV:family,weight,path,label,tier,glyph_count)。
* app 只经本接口读该列表(不直接拼路径、不依赖 launcher 内存数组)。
* - 渲染仍走 env MEOW_FONT_PNG/MEOW_FONT_IDX(launcher 注入的「当前生效字体」,
* 选中字体那一套);本节的 get_available_count/enumerate 只负责「列有哪些库」,
* select_by_id 负责「app 会话内固定用哪套」。两路明确。
* - 无卡 / 无 index → 仅 1 项板载默认(family=default)。 */
#ifndef MEOW_FONT_INFO_DEFINED
#define MEOW_FONT_INFO_DEFINED
typedef struct meow_font_info {
char family[64]; /* family slug = MeowOS/fonts/<family>/ 一级目录名 */
char name[64]; /* 显示名(index 无单列 → 回退 family) */
char weight[32]; /* weight slug(regular|bold|…,缺省 regular) */
char tier[8]; /* 档位 common|gbk|full(info 用) */
int glyph_count; /* font.json glyph_count(info 用) */
char label[128]; /* index 显示名(含字重 + 尺寸标记) */
int size_list[16]; /* 原生字号表(index 无 → 空) */
int size_count; /* 有效字号数 */
int bit_depth; /* 4|8(index 无 → 0) */
char path_png[320]; /* 卡根相对 MeowOS/fonts/<family>/<weight>/font.png */
} meow_font_info;
#endif
/* 可用字库数(含默认;无卡 → 1)。 */
int meow_font_get_available_count(void);
/* 按序号取:idx=0=默认行(family=default);越界返回 0。 */
int meow_font_enumerate(int idx, meow_font_info *out);
/* 选该套为 app 会话默认字体:id=0 跟随系统默认(font.file,经 env);id>0 固定用该套。
* 该套字库缺失 → 降级默认(不丢请求)+ WARN(meow_log→app 日志)。成功 0,越界负值。 */
int meow_font_select_by_id(int id);
/* ============================================================ */
/* §10 字体分层 / 大字库(M2.5x:基座声明 + font 域已实现按档装载) */
/* ============================================================ */
+14 -11
View File
@@ -361,19 +361,22 @@ void meow_ui_swipe_hint(int x)
* 有状态栏(非全屏)→ y = 状态栏高度;收合(collapsed)→ y=提示线下缘
* (MUI_SBAR_COLLAPSE_H,与提示线不重叠);无状态栏(全屏/隐藏)→ y=0。 */
int hy = meow_ui_statusbar_height();
/* x<0 视为清空(条长 0);条长上限=屏宽。 */
if (x < 0) x = 0;
/* 单一实现(与非全屏 Hello 完全一致,全屏仅 hy 不同):grow 画 0..x、shrunk 先清旧长
* 再画 0..x、x<=0 清空。x<0 视为清空;条长上限=屏宽。 */
if (x > 0) {
if (x > g_xres) x = g_xres;
/* 只按「新旧长度的差」增量绘制,避免整段重涂背景色:
* - 伸长:仅补画新增段(旧段已是 accent,不再用 bg 重涂——否则 app 底色≠theme bg
* 时会在旧段留下 bg 色块,就是残留残条);
* - 回缩/清空:仅把「超出新长度」的尾部填回背景色(实时回缩)。 */
if (x > g_hint_last) {
meow_ui_fill_rect(g_hint_last, hy, x - g_hint_last, MUI_HINT_H, th->accent);
} else if (x < g_hint_last) {
meow_ui_fill_rect(x, hy, g_hint_last - x, MUI_HINT_H, th->bg);
}
/* 往回滑(新长度 < 上次)时,先清掉「上次画到旧长度、但本次超出的部分」,
* 否则条只增不减(右滑变长、左滑回缩实时可见)。 */
if (g_hint_last > x) meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg);
/* 条色取主题前景(fg):fg 定义为与背景对比,能同时覆盖「非全屏(底=主题bg)」与
* 「全屏 Hello 类(底=主题accent)」两种情况——accent 在 Hello 全屏 accent 底上同色不可见。 */
meow_ui_fill_rect(0, hy, x, MUI_HINT_H, th->fg);
g_hint_last = x;
} else if (g_hint_last > 0) {
/* 手指回原点/抬手未达标/抬手达标:清掉此前画的条(填回背景色) */
meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg);
g_hint_last = 0;
}
/* 不发 flush:由调用方 meow_ui_flush_region(0,0,宽,6) 只刷顶部条 */
}
+21 -36
View File
@@ -125,7 +125,6 @@ static int t_press_valid = 0; /* BTN_TOUCH=1 已见(本次按下的 gate
static int t_dx = 0, t_dy = 0; /* 按下原点(BTN_TOUCH 后首个有效 ABS 锁定) */
static int t_cx = 0, t_cy = 0; /* 当前坐标(-1=本次尚未见到有效坐标) */
static int t_origin_valid = 0; /* 原点是否已锁定 */
static int t_gmax_dx = 0; /* 本手势「最大右移位移」(t_cx-t_dx 的最大值,条长上限) */
/* 按下边沿:只标记按下,绝不用传入坐标立即锁原点(避免用上一手势残留坐标当原点)。
* t_cx/t_cy 归 -1 —— 本次手势以「无坐标」起步,只接受随后到来的有效 ABS。 */
@@ -137,7 +136,6 @@ static void touch_press(int x, int y)
t_origin_valid = 0;
t_cx = -1; t_cy = -1;
t_dx = 0; t_dy = 0;
t_gmax_dx = 0; /* 新手势:最大右移位移清零(条长从 0 计) */
}
/* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。 */
static void touch_update(int x, int y)
@@ -147,17 +145,13 @@ static void touch_update(int x, int y)
}
if (x >= 0) t_cx = x;
if (y >= 0) t_cy = y;
if (t_down && t_origin_valid) {
int dx = t_cx - t_dx;
if (dx > t_gmax_dx) t_gmax_dx = dx; /* 记录本手势已达的最大右移位移(条长上限) */
}
}
static void touch_release(void)
{
if (!t_down) return;
t_down = 0; t_press_valid = 0;
/* 本次按下从未锁到有效坐标(幽灵触摸/坐标未就绪):丢弃,且清空全部轨迹。 */
if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; t_gmax_dx = 0; return; }
if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; return; }
t_origin_valid = 0;
int mdx = t_cx - t_dx, mdy = t_cy - t_dy;
int adx = mdx < 0 ? -mdx : mdx, ady = mdy < 0 ? -mdy : mdy;
@@ -169,8 +163,7 @@ static void touch_release(void)
* 被手势级清空,绝不继承上一手势坐标,确保:
* ① 中部右滑不会误判成左缘返回(t_dx>=EDGE_W);
* ② 先点中部再侧滑不返回(原点锁定在中部,>=EDGE_W),符合「必须从左缘起划」;
* ③ 返回判定要求右移严格正向(t_cx-t_dx>=0 且 ≥SWIPE_THRESH),杜绝「向左划也误返回」;
* 左划过程仅让进度条实时回缩,不额外生长(过程见上方 MEOW_EV_SWIPE_PROGRESS)。
* ③ 右移严格正向(t_cx-t_dx>=0),杜绝「向左划也出条/误返回」。
* SDK 统一上报为 MEOW_SWIPE_LEFT(设计"左滑=返回"契约,hello 用 on_swipe(LEFT) 退出)。
*
* 屏幕中部滑动不是返回手势:中部右滑→RIGHT(内容导航),中部左滑→非返回(落 TAP,
@@ -199,7 +192,7 @@ static void touch_release(void)
push_ev(MEOW_EV_TAP, t_cx, t_cy);
}
/* 手势级干净起点:清空全部轨迹,下一手势从零开始,杜绝污染无自愈。 */
t_cx = t_cy = -1; t_dx = t_dy = 0; t_gmax_dx = 0;
t_cx = t_cy = -1; t_dx = t_dy = 0;
}
/* ---- 输入线程:poll 所有 event 设备 + 解析 ---- */
@@ -231,22 +224,14 @@ static void *input_thread(void *arg)
if (code == ABS_X || code == ABS_MT_POSITION_X) {
if (t_down) touch_update(val, t_cy); /* 锁原点+更新当前(t_down 时才驱动) */
else t_cx = val;
/* 左滑进度实时上报(视觉条)。抬手触发仍由 touch_release 判定;这里只报过程。
* 修复要点(左缘返回滑条 bug):
* ① 双向上报(不再要求 t_cx>t_dx):向右→条伸长、向左→条实时回缩,
* 摒弃「只在返回瞬间闪末帧 / 反向才出条」;
* ② 仅左缘起手(t_dx<EDGE_W)才上报并被绘制——屏幕中部起手绝不触发条/返回;
* ③ 条长 = 本手势右移位移 clamped 到「本次手势最大右移位移」(t_gmax_dx),
* 条的最大 x 恒等于本次手势达过的最大右移距离,反复反向滑不会放大它。
* 负数(左滑越过原点)→ 条长 0(清空)。
* 事件载荷:a=按下X(t_dx),b=条长 len(clamp 后,≥0)。 */
if (t_down && t_origin_valid && t_dx >= 0 && t_dx < EDGE_W) {
int dx = t_cx - t_dx;
if (dx > t_gmax_dx) t_gmax_dx = dx;
int len = dx < 0 ? 0 : dx;
if (len > t_gmax_dx) len = t_gmax_dx;
push_ev(MEOW_EV_SWIPE_PROGRESS, t_dx, len);
}
/* 左滑进度实时上报(视觉条):原点确实在左缘(t_dx<EDGE_W) 且 向右移动
* (t_cx>t_dx)——grow/回缩全程由「非全屏已验证的同一条路径」驱动,全屏仅
* 垂直位置 hy 不同(hy=meow_ui_statusbar_height()),别无全屏特判。
* 抬手触发仍由 touch_release 判定;这里只报过程(事件载荷 b=当前X)。
* t_dx 是本次按下后首个有效 ABS 锁定的真实按下点(非残留),且每次抬手已
* 清空,杜绝「中部点一下出条 / 向左划也出条」。 */
if (t_down && t_origin_valid && t_dx >= 0 && t_dx < EDGE_W && t_cx > t_dx)
push_ev(MEOW_EV_SWIPE_PROGRESS, t_dx, t_cx);
}
else if (code == ABS_Y || code == ABS_MT_POSITION_Y) {
if (t_down) touch_update(t_cx, val); else t_cy = val;
@@ -335,17 +320,17 @@ void meow_ui_run(const meow_ui_events *ev)
meow_ui_sys_notif_pull();
} else if (e.type == MEOW_EV_SWIPE_PROGRESS) {
if (in_startup) continue; /* 启动窗口进度条静音 */
/* e.a=按下X(t_dx),e.b=条长 len(输入层已 clamp 到「本手势最大右移位移」且≥0)。
* SDK 自动画顶部条:从 x=0 画到 len(右移位移),向右伸长、向左实时回缩。
* M2.5x/A-13:全屏/收合下「左缘返回手势保留」,顶部动态提示条仍需绘制——不再以
* meow_ui_sys_statusbar_active()(全屏/隐藏=0)开关,否则全屏时滑条完全不显示。
* 条位 y=meow_ui_statusbar_height():非全屏=状态栏下沿、全屏=0、收合=提示线下缘
* (MUI_SBAR_COLLAPSE_H),与提示线不重叠加。APP 若注册 on_swipe_progress 仍可自绘
* (系统条照常画);递交给 app 的是 (len-x_start) 使 x_start+dx==len,与系统条同长。 */
int len = e.b;
meow_ui_swipe_hint(len);
int x0 = e.a, xc = e.b, dx = xc - x0; /* dx=当前X-按下X */
/* SDK 自动画顶部条:长度=手指当前 X(=距左缘距离,向内划长度随位移增长)。
* 全屏/收合下「左缘返回手势保留」,顶部动态提示条(meow_ui_swipe_hint)照常绘制
* (与 agent-108:不再以 meow_ui_sys_statusbar_active()(全屏/隐藏=0)开关,否则
* 全屏时滑条完全不显示)。条位 y=meow_ui_statusbar_height():非全屏=状态栏下沿
* (28)、全屏=顶部、收合=提示线下缘(MUI_SBAR_COLLAPSE_H),与提示线不重叠加——
* 全屏与非全屏共用同一条绘制/进度路径,只 hy 不同,无全屏特有的坐标/方向/最大值逻辑。
* APP 若注册 on_swipe_progress 仍可自绘(系统条照常画;x_start+dx==当前X,幂等)。 */
meow_ui_swipe_hint(xc);
if (g_events_set && g_events.on_swipe_progress)
g_events.on_swipe_progress(e.a, len - e.a);
g_events.on_swipe_progress(x0, dx);
meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6);
}
}