From 064635b3c4e2927a97b92cd87d777d02a4ad23d4 Mon Sep 17 00:00:00 2001 From: MeowOS Dev Date: Sun, 30 Aug 2026 03:27:24 +0800 Subject: [PATCH] =?UTF-8?q?WIP=20checkpoint=20(M2.53=E2=86=92M2.54=20?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E5=8C=BA):=20=E5=AD=97=E4=BD=93=E5=BD=92Meow?= =?UTF-8?q?OS/fonts+SDK=E8=B7=9F=E9=9A=8F+=E5=A4=9A=E5=AD=97=E4=BD=93(?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E4=B8=AD)+boot=E6=9D=BF=E8=BD=BD+=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=A0=8F28+=E4=BA=AE=E5=BA=A6/=E9=9F=B3=E9=87=8F?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D+=E6=96=87=E6=A1=A3=E5=88=B7=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + APPS.md | 40 +- DEVELOPMENT_PLAN.md | 4 +- GESTURE_BACK_DESIGN.md | 2 +- MEOWOS_API.md | 4 +- MEOW_APP_SDK.md | 20 +- README.md | 39 +- SYSTEM_SETTINGS.md | 14 +- TF_CARD_LAYOUT.md | 11 +- docs/MEOWOS_SPEC_REF.md | 135 +++- font/gen/gen_font.py | 1 + font/meow_font.c | 301 ++++++-- font/meow_font.h | 56 ++ font/test_multi_font.c | 271 +++++++ mips-repo/README.md | 2 +- patch_v2/apps_assets/apps/hello/app.json | 1 - .../apps_assets/apps/os.meow.hello/app.json | 1 + .../apps/{hello => os.meow.hello}/hello | Bin patch_v2/bootmenu/meow_bootmenu | Bin 149228 -> 149132 bytes patch_v2/bootmenu/meow_bootmenu.c | 96 ++- patch_v2/deploy_device.sh | 14 +- patch_v2/meow_launcherd/meow_font.h | 56 ++ patch_v2/meow_launcherd/meow_launcherd | Bin 346364 -> 370396 bytes patch_v2/meow_launcherd/meow_launcherd.c | 673 ++++++++++++++++-- patch_v2/meow_launcherd/meow_settings.c | 25 +- patch_v2/meow_launcherd/meow_settings.h | 6 + patch_v2/onboard.sh | 4 +- patch_v2/sdk/build_sdk.sh | 6 +- patch_v2/sdk/meow_notify_ipc.c | 243 +++++++ patch_v2/sdk/meow_notify_ipc.h | 44 ++ patch_v2/sdk/meow_ui.h | 2 +- patch_v2/sdk/meow_ui_gfx.c | 51 +- patch_v2/sdk/meow_ui_input.c | 4 +- patch_v2/sdk/meow_ui_notify.c | 78 +- patch_v2/sdk/meow_ui_sys.c | 33 +- patch_v2/sdk/meow_ui_sys.h | 12 +- patch_v2/sdk/meow_ui_vol.c | 52 ++ patch_v2/sdk/meow_ui_vol.h | 32 + patch_v2/sdk/test_meow_ui_vol.c | 145 ++++ patch_v2/sdk/test_notify_integration.c | 202 ++++++ patch_v2/sdk/test_notify_ipc.c | 281 ++++++++ patch_v2/sdk/test_notify_post.c | 150 ++++ patch_v2/verify_apps.sh | 8 +- patch_v2/verify_chain.sh | 22 +- 44 files changed, 2834 insertions(+), 308 deletions(-) create mode 100644 font/test_multi_font.c delete mode 100644 patch_v2/apps_assets/apps/hello/app.json create mode 100644 patch_v2/apps_assets/apps/os.meow.hello/app.json rename patch_v2/apps_assets/apps/{hello => os.meow.hello}/hello (100%) create mode 100644 patch_v2/sdk/meow_notify_ipc.c create mode 100644 patch_v2/sdk/meow_notify_ipc.h create mode 100644 patch_v2/sdk/meow_ui_vol.c create mode 100644 patch_v2/sdk/meow_ui_vol.h create mode 100644 patch_v2/sdk/test_meow_ui_vol.c create mode 100644 patch_v2/sdk/test_notify_integration.c create mode 100644 patch_v2/sdk/test_notify_ipc.c create mode 100644 patch_v2/sdk/test_notify_post.c diff --git a/.gitignore b/.gitignore index c055e55..1230bf2 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,4 @@ ssh_keys/ # 本地开发文档(Analysis/Notes/Test/device_test/docs-plan,仅本地保留,不入库) devdocs/ scripts/build/ +fonts_lib/ diff --git a/APPS.md b/APPS.md index f827e80..ea8b881 100644 --- a/APPS.md +++ b/APPS.md @@ -42,19 +42,21 @@ ``` /usr/data/mnt/sd_0/ # TF 卡挂载点 └── MeowAPPs/ - ├── / - │ ├── app.json # 应用描述(必填) + ├── / # 目录名 = app.json 的 package(权威 app_id) + │ ├── app.json # 应用描述(必填;package 字段为权威 app_id) │ ├── # 静态 MIPS32r2 ELF(exec 指向) │ └── icon.png # 预留:列表图标(v2 未用) └── ... ``` +`` 即 `app.json` 的 `package` 字段(反域名权威标识,如 `os.meow.hello`);**目录名必须等于 package**,launcher 以 package 为 app_id,缺失时回退目录名。 ### 2.1 app.json 字段 ```json { "name": "Hello App", - "exec": "/usr/data/mnt/sd_0/MeowAPPs/hello/hello", + "exec": "/usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/hello", + "package": "os.meow.hello", "version": "0.1", "author": "whoever" } @@ -64,11 +66,12 @@ |---|---|---| | `name` | ✓ | 列表显示名。**v2 仅支持 ASCII**(UTF-8 中文留待 v3,需完整 UTF-8→UTF-16 转换) | | `exec` | ✓ | 应用可执行文件绝对路径(必须是**静态** MIPS32r2 ELF,soft/hard-float 均可) | +| `package` | — | 反域名权威标识(如 `os.meow.hello`),launcher 取之为 app_id;**目录名=package**。缺省回退目录名 `de->d_name`;两者存在且不同 → WARN | | `version` / `author` | — | 保留字段,v2 不解析 | - 目录扫描深度 1 级:只枚举 `MeowAPPs/` 下的直接子目录 - 应用计数上限 **8**(超出忽略;单屏 8 行) -- app.json 解析器极简(launcher 内实现):仅匹配 `"name":` 与 `"exec":` 后的引号字符串,不做 JSON 校验(多行/单行均可) +- app.json 解析器极简(launcher 内实现):仅匹配 `"name":`、`"exec":`、`"package":` 后的引号字符串(及扩展 icon/framework/immersive/route./`awake.`(keep_awake)/service.),不做 JSON 校验(多行/单行均可)[源:审计 2026-08-30] - 列表刷新时机:每次菜单打开时重新扫描(TF 卡热插拔后立即可见) ### 2.2 边界情况行为(launcher 实现,全部真机验证) @@ -92,7 +95,7 @@ ### 3.1 启动流程(meow_launcherd 侧) ``` -热键(按住电源 + 单击音量减;电源按下后 300ms~2.5s 内收到音量减按下边沿触发) +热键(按住电源 + 单击音量减;电源按下后 60ms~2.5s 内收到音量减按下边沿触发;v6.7 起窗口下限由 300ms 改为 60ms,窗口 60–2500ms) → meow_launcherd 检测(EVIOCGKEY 状态轮询,10ms;电源按下即 arm,窗口外或松开即 disarm) → SIGSTOP hiby_player(/proc/N/exe == /usr/bin/hiby_player;等待状态 T) → 背光仅在面板灭时拉回(brightness==0 才写 saved/60,绝不无脑拉亮度)+ unblank 面板(先 SIGSTOP 再 unblank)→ fb0 直绘应用列表 @@ -188,7 +191,7 @@ - **⚠️ 应用启动后须防 goodix 幽灵触摸**(v6.8,Bug 2 根因):goodix 在启动触摸的尾部发出一段幽灵触摸(实测启动后 1.2-2.25s,§18.16),无防护时应用会**误把幽灵当下按而立即退出画 BYE**,且 launcher 菜单会把同一幽灵重放成行点击而**重开应用**(用户看到「bye 后又回 hello 主界面、需再点一次」)。hello v6.8 增加 `COOLDOWN_MS=2500`:启动后前 2.5s 忽略一切按下边沿,只有用户**真正**的下按才触发退出。launcher 侧同步在应用退出后 drain + 冷却(见 §4.7)。 - 沉浸应用写 `/tmp/meowapp_state`,处理 `SIGUSR1` 作为「返回事件」 - 应用异常卡死 → 虚拟返回键(若可见)→ SIGKILL;沉浸态卡死 → 音量减连按 3 次(强杀序列) -- **应用日志**:launcher 通过环境变量 `MEOW_LOG_DIR` 告知 TF 卡日志目录(无卡时不设),应用自行决定文件名(示例 hello 写 `hello.log`);启动时先 drain 输入队列、只响应"按下边沿"退出(释放事件/轨迹事件忽略) +- **应用日志**:launcher 通过环境变量 `MEOW_LOG_DIR` 告知 TF 卡日志目录(无卡时不设),应用自行决定文件名(示例 hello 写 `apps//logs/_.log`;[源:审计 2026-08-30]);启动时先 drain 输入队列、只响应"按下边沿"退出(释放事件/轨迹事件忽略) ### 3.6 应用运行期卡移除(M2.11 最佳努力) @@ -232,11 +235,11 @@ | 下一曲 | event2 | 163 | (未用) | | 上一曲 | event0 | 165 | (弃用——GPIO PC28 硬件链断,§18.12) | -**热键判定(v6.5 定案)**:**按住电源 → 电源按下后 300ms~2.5s 内单击音量减**(在窗口内收到音量减的**按下边沿**且电源仍被按住)⇒ 开菜单。电源按下会触发播放器息屏流程(**已接受的副作用**,用 v6.2/v6.3 已验证时序保证菜单可见:SIGSTOP 播放器 → unblank 面板 → 仅灭屏时拉背光);音量减单击副作用 = **音量 -1**(可接受)。电源松开或窗口超时(>2.5s)即 disarm;音量减在窗口外(<300ms 或 >2.5s)按下视为普通音量键,不触发。**纯单键序列**(v6 volup×3)与组合键(上一曲+音量加,GPIO 断,§18.12)均已退役,仅保留在 `meow.conf hotkey=seq`(历史备选,不推荐)。 +**热键判定(v6.5 定案;v6.7 起窗口下限 300ms→60ms)**:**按住电源 → 电源按下后 60ms~2.5s 内单击音量减**(在窗口内收到音量减的**按下边沿**且电源仍被按住)⇒ 开菜单。电源按下会触发播放器息屏流程(**已接受的副作用**,用 v6.2/v6.3 已验证时序保证菜单可见:SIGSTOP 播放器 → unblank 面板 → 仅灭屏时拉背光);音量减单击副作用 = **音量 -1**(可接受)。电源松开或窗口超时(>2.5s)即 disarm;音量减在窗口外(<60ms 或 >2.5s)按下视为普通音量键,不触发。**纯单键序列**(v6 volup×3)与组合键(上一曲+音量加,GPIO 断,§18.12)均已退役,仅保留在 `meow.conf hotkey=seq`(历史备选,不推荐)。 热键 `meow.conf` 配置: ``` -hotkey=power_voldown # 默认:电源按住 + 单击音量减(300-2500ms 窗口) +hotkey=power_voldown # 默认:电源按住 + 单击音量减(60-2500ms 窗口;v6.7 起下限 300ms→60ms) hotkey=seq # 历史序列热键(单键连按 N 次,不推荐) ``` (`hotkey_seq=`/`hotkey_seq_gap=` 仅在 `hotkey=seq` 时生效。) @@ -269,12 +272,12 @@ meow_launcherd --sim-exit N # 菜单打开 N 毫秒后模拟退出 ### 4.5 统一日志系统(MeowAPPs 组件共用) -- **默认位置**:TF 卡 `/usr/data/mnt/sd_0/MeowAPPs/logs/meow_.log`(如 `meow_20260828_020123.log`;boot 时间戳 = 当前墙钟 − /proc/uptime,同一 boot 会话内多次启动共用同一文件) +- **默认位置**:TF 卡 `/usr/data/mnt/sd_0/MeowOS/logs/meow_.log`(如 `meow_20260828_020123.log`;`bootts` = 当前墙钟 − /proc/uptime,同一 boot 会话内多次启动共用同一文件)。**系统日志**(launcher/watchdog 等 MeowOS 组件)写此文件;**应用日志**另按 `apps//logs/_.log` 分文件(见本 §4.5「组件环境变量」)。⚠️ `meow_bootmenu` 仍用 `MeowAPPs/logs`(代码侧不一致;bootmenu 与 launcher 日志目录**待统一为 `MeowOS/logs`**,[源:审计 2026-08-30]) - **无 TF 卡 / 卡晚挂载(S90 早于卡挂载)**:**开不了卡日志**——日志落 **/tmp 启动缓冲**(`/tmp/meow_boot.log`,上限 256KB 滚动),**绝不写板载存储**(/usr/data 只剩 ~10MB);**卡出现后自动迁移**(主循环 ~1s 周期重试 + 输入设备重试循环内也检查;无需开菜单)——`log_poll_card()` 打开卡日志并把 /tmp 缓冲整段搬入后删除 /tmp 文件 - **容量上限**:logs/ 目录总量 1GB,开机时超限按 mtime 删最旧 - **组件环境变量**: - `MEOW_LOG_FILE` → meow_watchdog:追加写同一 boot 日志文件(无该变量则走 stderr) - - `MEOW_LOG_DIR` → 应用:日志目录(示例 hello 写 `/hello.log`) + - `MEOW_LOG_DIR` → 应用:日志目录(每 App `MeowOS/logs/apps//logs/`,应用写 `_.log`;[源:审计 2026-08-30]) - 内容:时间戳 + 全部按键边沿/菜单/应用事件(真机按键证据) - PID 文件:`/usr/data/meow_launcherd.pid`;播放器发现:扫描 `/proc/N/exe` == `/usr/bin/hiby_player` @@ -319,7 +322,7 @@ meow_launcherd --sim-exit N # 菜单打开 N 毫秒后模拟退出 > 所有设备操作遵守纪律:动播放器前数实例;重启前 `rm -f /data/recovery_all`;息屏测试后恢复背光;结束恢复设备干净。 0. **轮前检查**:`ls /data/recovery_all`(存在即删+告警);`ps | grep hiby_player | wc -l` 应=1。 -1. **推送**:`meow_launcherd/meow_launcherd` → `/data/meow_launcherd`(测试期直接跑 `/data/...`);`meow_watchdog` → `/usr/data/meow_watchdog`;`hello` → `/usr/data/mnt/sd_0/MeowAPPs/hello/hello`。 +1. **推送**:`meow_launcherd/meow_launcherd` → `/data/meow_launcherd`(测试期直接跑 `/data/...`);`meow_watchdog` → `/usr/data/meow_watchdog`;`hello` → `/usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/hello`。 2. **验证输入路径**(无物理按键时的替代证据): - `meow_launcherd --scan` → 列出应用 - `meow_launcherd --dumpkeys 6000` + 物理按键 → 日志出现 `key VOL_DOWN/VOL_UP DOWN/UP` @@ -350,20 +353,22 @@ meow_launcherd --sim-exit N # 菜单打开 N 毫秒后模拟退出 ### 1. 应用目录结构 ``` -MeowAPPs/<包名>/ -├── app.json # 应用配置(名称/exec/路由/图标/框架声明等) +MeowAPPs// # 目录名 = app.json 的 package(权威 app_id) +├── app.json # 应用配置(name/exec/package/路由/图标/框架声明等) ├── meow # 程序主体(静态 MIPS 二进制;也可多文件/脚本) └── <资源文件/图标…> # 只读程序资源 -MeowData/<包名>/ +MeowData// ├── data/ # 持久应用数据(配置、播放列表、登录态…) └── cache/ # 缓存(可清) ``` +> `` = `app.json` 的 `package`(反域名,如 `os.meow.hello`),与目录名一致;launcher/app 数据/日志/`.mpk` 均以它为包名。 > 注:TF 卡为 exFAT,**不支持符号链接**,故数据目录不用链接,改由 MeowOS 通过**环境变量**注入(见 §2)。 ### 2. 数据目录(MeowOS 负责,用环境变量注入) +- 本文档 `<包名>` = `app.json` 的 `package`(反域名权威标识,如 `os.meow.hello`),与目录名一致。 - **程序主体 + 必要配置文件** 放 `MeowAPPs/<包名>/`(可替换/可升级)。 - **软件数据** 放 `MeowData/<包名>/{data,cache}`(独立于程序,重装/升级保留数据)。 - **MeowOS 每次启动应用前**:验证 `MeowData/<包名>/{data,cache}` 存在(否则建)。 @@ -376,6 +381,7 @@ MeowData/<包名>/ ### 3. 软件包 `.mpk` +- **包名 = 文件名茎 = `app.json` 的 `package`**(如 `os.meow.hello` → `os.meow.hello.mpk`)。 - **格式**:`.mpk` = tar.gz(含 `app.json` + `meow` 二进制 + 资源),**不含 data/cache**(运行时按设备生成)。 - **导出**:打包 `MeowAPPs/<包名>/`(**不含 data/cache 目录**)→ `<包名>.mpk`。 - **导入/安装**(MeowOS 软件包安装程序 `meow_pkg_install()`): @@ -392,8 +398,8 @@ MeowData/<包名>/ ### 5. 软件包管理器 CLI `nyai`(静态 MIPS,apt/yum 风格,含镜像源) -`nyai` 是 MeowOS 的包管理 CLI(静态 MIPS32r2/musl,无外部依赖;tar.gz 用 `gzip -dc | tar -x` 管道,与 launcher 一致)。**部署**:`MeowOS/opt/bin/nyai`(卡,随 TF_CARD_LAYOUT 外围工具目录)。 -**调用**:当前固件(M2.11,无 `/etc/profile.d/meow_card_path.sh`)SSH 需绝对路径 +`nyai` 是 MeowOS 的包管理 CLI(静态 MIPS32r2/musl,无外部依赖;tar.gz 用 `gzip -dc | tar -x` 管道,与 launcher 一致)。**包名 `` = `app.json` 的 `package`(= `.mpk` 文件名茎 = 目录名)**。**部署**:`MeowOS/opt/bin/nyai`(卡,随 TF_CARD_LAYOUT 外围工具目录)。 +**调用**:M2.12+ 固件 `meow_card_path.sh` 已将 `MeowOS/opt/bin` 前插 PATH,`nyai` 可直接调用;更早固件(M2.11 及以前,无 `/etc/profile.d/meow_card_path.sh`)SSH 需绝对路径[源:审计 2026-08-30] `/usr/data/mnt/sd_0/MeowOS/opt/bin/nyai` 或 `export PATH=/usr/data/mnt/sd_0/MeowOS/opt/bin:$PATH`; M2.12+ 固件该目录已由 `meow_card_path.sh` 前插,`nyai` 可直接调用。 @@ -426,7 +432,7 @@ file:///usr/data/mnt/sd_0/MeowOS/opt/repo # file:// 等价本 **索引 `Packages`**(`nyai index ` 生成,行式、空行分隔记录): ``` -Package: simtest # 包名 = .mpk 文件名茎 +Package: simtest # 包名 = .mpk 文件名茎 = app.json.package Version: 1.2.3 # 来自该 .mpk 内 app.json Filename: simtest.mpk Size: 388 # 字节 diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md index 41c35e5..0ee5456 100644 --- a/DEVELOPMENT_PLAN.md +++ b/DEVELOPMENT_PLAN.md @@ -131,7 +131,7 @@ - **返回语义**:默认回应用列表;接阶段 D 后按导航栈回上一层 - **虚拟返回键**:启动器看门狗在左上角绘制 48×48;**沉浸模式**(应用声明)隐藏之,改左缘滑动返回(SIGUSR1);物理强杀组合保留 - **亮度安全**:任何亮度写入 clamp ≥25;不干扰播放器自动息屏 -- **日志**:TF 卡 `MeowAPPs/logs/`,开机新文件,1GB 滚动;无卡不写板载 +- **日志**:TF 卡 `MeowOS/logs/`(系统日志 `meow_.log` + 应用日志 `apps//logs/_.log`),开机新文件,1GB 滚动;无卡不写板载([源:审计 2026-08-30]) - **应用目录**:TF 卡 `MeowApps//{app.json, 二进制}`;app.json 含名称/图标/执行路径/路由表(阶段 D) --- @@ -141,7 +141,7 @@ - 迭代回路:改代码 → 交叉编译(zig + musl.cc)→ SSH 推 `/data` 或 bind-mount 部署 → 真机验证(注入 + 用户物理确认)→ repack 固件刷入 - 注入仅能测逻辑路径(触摸注入不可投递)→ **物理项一律需用户确认** - 每次部署前后核对:播放器实例数、背光、bind 列表、recovery_all 标志 -- 崩溃/异常优先看 `MeowAPPs/logs/` 与 `/proc/interrupts`(区分软件/硬件) +- 崩溃/异常优先看 `MeowOS/logs/` 与 `/proc/interrupts`(区分软件/硬件)[源:审计 2026-08-30] - 刷机走官方 SD 卡升级通道;变砖用官方固件刷回 + TF 卡全量备份兜底 --- diff --git a/GESTURE_BACK_DESIGN.md b/GESTURE_BACK_DESIGN.md index c0072ec..1d2dec1 100644 --- a/GESTURE_BACK_DESIGN.md +++ b/GESTURE_BACK_DESIGN.md @@ -111,5 +111,5 @@ | `patch_v2/meow_watchdog/meow_watchdog.c` | 删除 `paint_button()` 返回键绘制/按钮命中;保留兜底 | | `patch_v2/meow_launcherd/meow_launcherd.c` | 解析 app.json `immersive` → 写 `/tmp/meowapp_state` | | `patch_v2/hello_app/hello.c` | 删 `on_key(KEY_POWER)→exit`;声明 immersive;处理 `on_swipe_progress` 画条 | -| `patch_v2/apps_assets/apps/hello/app.json` | 加 `"immersive":1` | +| `patch_v2/apps_assets/apps/os.meow.hello/app.json` | 加 `"immersive":1` | | `APPS.md` / `MEOW_APP_SDK.md` | 已写 §3.4.1 规范;SDK 文档补 `on_swipe_progress`/`meow_ui_swipe_hint` | diff --git a/MEOWOS_API.md b/MEOWOS_API.md index 79a493a..6146f67 100644 --- a/MEOWOS_API.md +++ b/MEOWOS_API.md @@ -64,7 +64,7 @@ int meow_settings_set_str (const char *key, const char *v);/* 长度截断 MEOW int meow_settings_set_enum(const char *key, int v); /* 校验枚举合法 */ int meow_settings_set_bool(const char *key, bool v); ``` -- **所有 clamp/校验集中在 set**(亮度 clamp ≥ `display.brightness_min` 默认 25、枚举越界拒绝、str 截断)。 +- **所有 clamp/校验集中在 set**(亮度 clamp ≥ `display.brightness_min` 默认 **1**(M2.39 由 25 改 1,旧持久 25 自动迁移;[源:审计 2026-08-30])、枚举越界拒绝、str 截断)。 - set 成功后**自动 `settings_save()` 落盘**(板载 /usr/data/meow/settings.conf)并**通知 listener**。 ### 2.3 枚举辅助(供 UI/web 显示中文标签) @@ -122,7 +122,7 @@ int meow_settings_register(const meow_setting_def *def); /* 注册一个设置 ``` # meow-settings 1 display.brightness = 101 -display.brightness_min = 25 +display.brightness_min = 1 display.screen_off_mode = panel boot.default = meow input.hotkey_seq = volup,volup,volup diff --git a/MEOW_APP_SDK.md b/MEOW_APP_SDK.md index 3202226..b78ca6c 100644 --- a/MEOW_APP_SDK.md +++ b/MEOW_APP_SDK.md @@ -84,14 +84,14 @@ void meow_ui_fill_rect_alpha(int x, int y, int w, int h, uint32_t color, int alp ### 3.1 全局状态栏(系统级组件,A+C 方案并入) -**定位**:状态栏是**系统级全局组件**,由 SDK 统一绘制——所有界面(launcher 菜单 + MeowAPP 画面)默认都显示,**仅当 APP 声明全屏(fullscreen)才隐藏**。数据由 SDK 内置状态读取器抓系统快照(与 launcher 同源单点一致),非各画各的。 +**定位**:状态栏是**系统级全局组件**,由 SDK 统一绘制——所有界面(launcher 菜单 + MeowAPP 画面)默认都显示,**仅由 SDK 运行时 `meow_ui_set_fullscreen(1)` 控制隐藏**(`fullscreen` 字段**未在 app.json 解析**,已定未做,见 §3.1 注 [源:审计 2026-08-30])。数据由 SDK 内置状态读取器抓系统快照(与 launcher 同源单点一致),非各画各的。 ```c /* 状态栏显隐(默认显示;APP 声明全屏则隐藏——沉浸) */ void meow_ui_set_statusbar(int visible); /* 1=显示(默认) 0=隐藏(全屏) */ int meow_ui_statusbar_visible(void); -/* 全屏声明:等价 app.json `fullscreen:1`(外观上隐藏状态栏 + 隐藏返回键)。 */ +/* 全屏:由 SDK 运行时 meow_ui_set_fullscreen(1) 控制(app.json `fullscreen` 未解析,已定未做;外观=隐藏状态栏+隐藏返回键)。[源:审计 2026-08-30] */ void meow_ui_set_fullscreen(int on); /* 状态栏高度(px,供 APP 布局上移内容,避免被盖——status bar inset) */ @@ -99,10 +99,10 @@ int meow_ui_statusbar_height(void); ``` **绘制规则**: -- `meow_ui_flush()` 上屏前,SDK 自动在顶部画状态栏(slim ~20-40px),内容:时钟 / 电量 / WiFi / 蓝牙 / 音量(与 launcher 一致)。 +- `meow_ui_flush()` 上屏前,SDK 自动在顶部画状态栏(**28px**,`meow_ui_statusbar_height()` 即此值;用户定 40→28,栏内字号 20),内容:时钟 / 电量 / WiFi / 蓝牙 / 音量(与 launcher 一致)。 - 状态栏数据由 SDK 内置读取器周期刷新(~1s 轻刷,读 `/sys/class/power_supply/*`、WiFi 状态、时钟、音量寄存器——与 launcher 同一批 source)。 - **z 顺序**:状态栏是最顶层。APP 内容需 `meow_ui_statusbar_height()` 下移(inset),否则被状态栏盖住(有状态栏 OS 的通用做法)。 -- **全屏(fullscreen)**:`meow_ui_set_fullscreen(1)` 或 app.json `fullscreen:1` → 隐藏状态栏 + 隐藏返回键(沉浸);退出全屏恢复。 +- **全屏**:运行时 `meow_ui_set_fullscreen(1)` 控制 → 隐藏状态栏 + 隐藏返回键(沉浸);退出恢复。**注**:app.json `fullscreen` 字段未在解析器读取(已定未做 [源:审计 2026-08-30]),故不能经 app.json 声明全屏。 **与 launcher 统一(A+C)**:launcher 迁移到 SDK 后,其状态栏(现私有 `draw_statusbar()`)改走 SDK 这一套,全局统一、单点一致。 @@ -307,17 +307,25 @@ patch_v2/meow_launcherd/meow_font.a # 复用(静态链接) ``` 构建出 `patch_v2/sdk/meow_ui.a`;应用编译链(zig-mipsel-cc)额外链 `meow_ui.a meow_font.a libz.a -lpthread`。 +> **构建耦合(已知设计,非缺陷)**:`build_sdk.sh` 用 `-I../meow_launcherd` 引 `meow_ipc.h`(`meow_notify_ipc.c` 复用共享协议头,同一份、杜绝两端漂移),故 SDK 编译期依赖 launcher 头、**非独立库**;**运行时无碍** —— App 只链 `meow_ui.a`(含 meow_font.o),不链入 launcher 符号,故 app 侧无 launcher 依赖。 + **app.json 扩展字段**(向后兼容,缺省即旧行为): ```json { "name": "Hello App", - "exec": "...", + "exec": "/usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/hello", + "package": "os.meow.hello", // 反域名权威 app_id;目录名=package,launcher 取之为 app_id "version": "0.1", "framework": 1, // 1=走 meow_ui(框架承载);缺省/0=裸自绘(旧) "immersive": 0, // 1=沉浸模式(隐藏虚拟返回键,左缘滑动返回) - "icon": "apps/hello/icon.png" // 可选:列表图标(供图片层) + "icon": "apps/os.meow.hello/icon.png" // 可选:列表图标(供图片层) } ``` +上例 ``=`os.meow.hello`(= `apps//` 目录名 = `package` 字段值);exec 即 `<挂载点>/MeowAPPs//`。 + +> **app 字体来源(M2.x 已落地)**:app 若**自带字库**(`load_handle`,见 §5.7.3,**未实现**)则用它;否则 SDK `meow_ui_gfx.c load_font()` **优先读 launcher 注入的环境变量** `MEOW_FONT_PNG`/`MEOW_FONT_IDX`(=系统 `font.file` 当前生效字体,绝对路径)/`MEOW_FONT_APP`(family),无 env 则回退卡默认 `MeowOS/fonts/default/font.png` → 固件。因此**app 未自带字库时跟随系统 `font.file`**,不再硬编码 `MeowAPPs/fonts`。 +> +> **app 多字体(M2.5x,`display.multi_font`)**:launcher 注入 `MEOW_MULTI_FONT=0/1` 与 6 类 `MEOW_FONT_CLS_<类>_PNG/IDX`(命中则覆盖全局;类值为空=跟随全局不注入)。SDK `meow_ui_gfx` 的 `meow_gfx_load_multifont()` 读 env → 调 `meow_font_config_multifont()`(同 png 去重、≤4 套上限、缺类回退全局)构建**逐字符分类路由**:`meow_font_drawtext/width_text/line_height` 按 `meow_font_class_of(cp)`(`MEOW_FCLS_CJK/_CJK_SYM/_NUM/_LATIN/_LATIN_SYM/_OTHER`)用对应字库取字形与度量;`meow_font_has_glyph(cp)` 可预判缺字。`display.multi_font=0`/无 env → 单字体(现状,零额外开销)。底层接口见 meow_font.h:`meow_font_load_slot`/`meow_font_unload_slot`/`meow_font_enable_multifont`/`meow_font_set_class_slot`/`meow_font_config_multifont`;`MEOW_MAX_SLOTS=5`。 --- diff --git a/README.md b/README.md index d2932a0..c87c751 100644 --- a/README.md +++ b/README.md @@ -9,40 +9,49 @@ | 文档 | 内容 | |---|---| | `docs/MEOWOS_SPEC_REF.md` | 权威速查总索引(首选入口;常量/路径照抄原文 + 出处 + 矛盾清单) | -| `ANALYSIS.md` | 主技术档案:固件逆向、启动链、fb 双缓冲、HGL、一切踩坑与纪律(§ 章节持续累积) | -| `DEVELOPMENT_PLAN.md` | 路线图与分阶段计划(阶段 0 双系统 / A 稳定化 / B 状态栏通知中心 / B2 系统设置 / C MeowPanel / D Activity 框架 / E 网易云) | +| `devdocs/ANALYSIS.md` | 主技术档案(**本地开发档,gitignore 不入库**):固件逆向、启动链、fb 双缓冲、HGL、一切踩坑与纪律(§ 章节持续累积) | +| `DEVELOPMENT_PLAN.md` | 路线图与分阶段计划(**自身已过时,以实际进度为准**;阶段 0 双系统 / A 稳定化 / B 状态栏通知中心 / B2 系统设置 / C MeowPanel / D Activity 框架 / E 网易云) | | `FRAMEWORK_DESIGN.md` | Activity / 通知 IPC 框架协议(IPC、路由表、导航栈、后台权限、媒体会话) | | `SYSTEM_SETTINGS.md` | 系统设置子系统设计(统一存储 + C API + 实体 UI + web 复用 + 导入导出) | | `MEOWOS_API.md` | 统一 API 蓝本(命名规范、返回码、设置 API、HTTP 映射、扩展指南) | | `APPS.md` | MeowAPPs 应用规范(目录、app.json、生命周期、边界、日志、返回键) | | `GESTURE_BACK_DESIGN.md` / `KEY_CONTROL_DESIGN.md` / `PERF_SCHEDULER.md` | 左缘返回 / 按键契约与信号 / 帧率调度 | -> 一次性/阶段性设计文档(已过时或未实施)移至 `docs/plan/` 归档(移动不删、保留历史):`BOOT_SELECTOR_DESIGN`、`CHARGING_SCREEN`、`MAIN_MD3_PLAN`、`MD3_UI_SPEC`、`MEOW_TOUCH_DESIGN`、`MIGRATION_AC`、`BLUETOOTH_MANAGER_PLAN`、`APP_LOG_MANAGER`。 +> 一次性/阶段性设计文档(已过时或未实施)移至 `devdocs/plan/` 归档(移动不删、保留历史;`devdocs/` 为本地开发档,不入库):`BOOT_SELECTOR_DESIGN`、`CHARGING_SCREEN`、`MAIN_MD3_PLAN`、`MD3_UI_SPEC`、`MEOW_TOUCH_DESIGN`、`MIGRATION_AC`、`BLUETOOTH_MANAGER_PLAN`、`APP_LOG_MANAGER`。 -**文档收敛规则**:不再为每个功能新增设计 md;新功能在对应权威文档(`MEOW_APP_SDK.md`/`APPS.md`/`SYSTEM_SETTINGS.md` 等)补小节;新增决策/检查点写 `DEV_NOTES.md`;过时一次性设计进 `docs/plan/`。 +**文档收敛规则**:不再为每个功能新增设计 md;新功能在对应权威文档(`MEOW_APP_SDK.md`/`APPS.md`/`SYSTEM_SETTINGS.md` 等)补小节;新增决策/检查点写 `devdocs/DEV_NOTES.md`;过时一次性设计进 `devdocs/plan/`。 -## 当前架构与组件(2026-08 快照) +## 当前架构与组件(2026-08-30 快照) -- **meow_svcd 服务管理器**(`patch_v2/meow_svcd/`):类 systemd/systemctl 的独立守护进程,由 supervisor(meow_launcherd/bootmenu)`fork/exec` 托管。读 TF 卡 `MeowOS/services.d/.conf`(目录式 `key=value`:`exec/args/env/enabled/restart/backoff_ms/max_restart/trigger`,未知键忽略、前向兼容);每个服务是 svcd 的**独立子进程**(崩溃隔离:服务挂不影响 svcd/supervisor);事件触发(`boot`/`card-mount`/`wifi-up`,1s 轮询);`-f` 前台运行便于 supervisor `waitpid`。mipsel 静态 ~99KB,进固件不入卡(拔卡 svcd 不消失)。 -- **MeowOpt/(TF 卡)**:板载外部工具/二进制(如 `MeowOpt/mdnsd/mdnsd`),由 MeowOS 启动时从卡加载,拔卡降级不崩。 -- **mDNS 方案A(已拍板)**:用**完整 `mdnsd`**(994KB,mipsel 静态)放 TF `MeowOpt/mdnsd/mdnsd`,由 meow_svcd 托管;SAResponder(885KB)因"单服务注册型"无法按名访问设备(`ingenic.local`)被排除。⚠️ 手机热点组播常在客户端间丢失 → mDNS 有风险,主机 DNS 兜底。 -- **SDK 已落地**:统一日志 `meow_log()`(按 app 分目录、每次启动新文件、单文件 2MB 轮转、每 App 总量 50MB 删最旧,M2.52)+ 通知 API `meow_notify_*`(4 类型×每类型 4、媒体单例,M2.53)。 +- **SDK 统一日志 + 通知(M2.52/53 已落地)**:`meow_log()` 按 app 分目录、每次启动新文件 `MeowOS/logs/apps//logs/_.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 卡多字体库**:`MeowOS/fonts//`(`font.png`+`font.idx`+`font.json` 三件套),默认 `MeowOS/fonts/default`;`font.file` 设置键选默认套;SDK `load_font` **优先读 env `MEOW_FONT_PNG/IDX`**(launcher 每次 `launch_app` 注入),app 未自带字库时**跟随系统 `font.file`**;≤16 套卡内(防爆);§5.7 字体管理 + 应用字库(多句柄,**草案未实现**)。已部署 4 套(思源宋体 / MiSans / GoogleSans / Meslo,位深 4)。 +- **包名/目录安卓化**:`app.json` `package`(反域名**权威 app_id**,==目录名,缺省回退目录名)→ 示例 `os.meow.hello`;目录 `MeowAPPs/os.meow.hello`;数据 `MeowData//{data,cache}`。 +- **meow_svcd 服务管理器**(`patch_v2/meow_svcd/`,mipsel 静态 ~99KB,进固件不入卡):类 systemd/systemctl,由 supervisor(bootmenu/launcher)`fork/exec` 托管;读 TF `MeowOS/services.d/.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,拔卡降级不崩)。 +- **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)。 +- **键码/阈值修正**:`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//logs/_.log`;服务定义 `MeowOS/services.d/.conf`。 - **mips-repo/**(宿主侧 mipsel 软件仓库):交叉编译产物 + `build.sh` + `manifest.md`,供后续固件/TF 复用(见「目录速览」)。 +- **仓库公开净化**:本地 main 单提交,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 源码/产物);源码与构建脚本 -- `mips-repo/` — mipsel 交叉编译软件仓库(mdnsd 等:`build.sh`+`manifest.md`+`bin/`),供后续固件/TF 复用 +- `patch_v2/` — meow_launcherd(MeowOS 核心)、bootmenu(引导选单)、watchdog、meow_svcd(服务管理器)、sdk(meow_ui SDK)、meowpkg(包管理);源码与构建脚本 +- `mips-repo/` — mipsel 交叉编译软件仓库(mdnsd 等:`README.md`+`bin/`),供后续固件/TF 复用 - `font/` — 预渲染字形系统(gen 生成器、meow_font 渲染库、atlas 产物) -- `fw_apps/` — 固件组装产物(`.upt` 不入库,由 build_fw_apps*.sh 重新打包) +- `fw_apps/` — 固件组装产物(`.upt` 不入库,由 `scripts/build/build_fw_apps*.sh` 重新打包) - `bootlogos/` — 用户定制启动图/关机 logo -- `device_test/` — 真机验证截图/证据(`.raw` 不入库) +- `scripts/build/` — 固件打包脚本 `build_fw_apps_m*.sh`(**本地 gitignore 不入库**) +- `devdocs/` — 本地开发文档(ANALYSIS / WORK_STATE / DEV_NOTES / OPEN_QUESTIONS / FLASH_SSH / TEST_ON_DEVICE / device_test / plan 归档;**gitignore 不入库**) - `ssh_keys/`, `.tools/` — SSH 密钥、交叉工具链(不入库,按需) ## 构建/刷机 -- 固件打包:`build_fw_apps*.sh`(需交叉工具链 `zig-mipsel-cc` + musl.cc,见 ANALYSIS 第 10 节) +- 固件打包:`scripts/build/build_fw_apps_m253.sh`(**现 M2.53 / 0.7.48**;需交叉工具链 `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**。 - 回退:官方固件 / `backups/`(NAND 全量备份)重刷 ## 关键纪律(红线) @@ -53,4 +62,4 @@ - 日志/字库/媒体等大数据只写 TF 卡;板载只写 KB 级 `meow/settings.conf` - 绝对不对挂载点 `rm -rf` -详见 `ANALYSIS.md` 纪律章节。 +详见 `devdocs/ANALYSIS.md` 纪律章节。 diff --git a/SYSTEM_SETTINGS.md b/SYSTEM_SETTINGS.md index 0b5d8e1..8cfa0ea 100644 --- a/SYSTEM_SETTINGS.md +++ b/SYSTEM_SETTINGS.md @@ -34,11 +34,21 @@ | | `input.hotkey_window_ms` | int | 2500 | 组合/序列窗口 | | | `input.touch_arm_delay_ms` | int | 700 | 触摸布防延迟(防幽灵) | | | `input.swipe_invert` | bool | false | 设置页滑动方向(0=手指上滑→往下翻;1=反向) | -| **字体/主题** | `font.file` | path | `MeowAPPs/fonts/font.png` | 字体图集 | +| **字体/主题** | `font.file` | path | `MeowOS/fonts/default/font.png` | 字体图集 | +| | `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.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;读回为追踪值) | @@ -92,7 +102,7 @@ int meow_settings_export(const char *path); /* 写一份 .conf 到 path */ int meow_settings_import(const char *path); /* 从 .conf 读入覆盖,校验 + 通知 */ ``` -- **clamp/校验集中在 set**:如 `brightness` 自动 clamp ≥ `brightness_min`(默认 25);`screen_off_timeout >= 0`;枚举越界拒绝。 +- **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 已就位后端) diff --git a/TF_CARD_LAYOUT.md b/TF_CARD_LAYOUT.md index 6ad794b..9a1d394 100644 --- a/TF_CARD_LAYOUT.md +++ b/TF_CARD_LAYOUT.md @@ -14,13 +14,16 @@ ├── MeowData/ # 用户数据(歌曲、封面缓存、下载等) ├── MeowAPPs/ # 应用 │ ├── / # app.json + 二进制 + 资源 -│ ├── fonts/ # 预渲染字库 font.png + font.idx(可由 MeowOS 加载) -│ ├── logs/ # 统一日志(meow_.log;无卡回退内存环) +│ ├── fonts/ # 默认字库 font.png + font.idx(font.file 缺省指此处;可由 MeowOS 加载) │ └── backgrounds/ # 可选背景图 +├── MeowOpt/ # 板载外部工具/二进制(如 mdnsd/;卡上加载,拔卡降级不崩。与 MeowOS/opt 命名待归一,见 SPEC_REF 矛盾 §A-11[源:审计 2026-08-30]) └── MeowOS/ # MeowOS 专属(一切非必要文件) ├── backups/ │ └── settings/ │ └── setting.bak # 设置导出默认路径(SYSTEM_SETTINGS 一致) + ├── fonts// # 多字体库(每套一个子目录;font.file 选默认套) + ├── logs/ # 统一日志:系统 meow_.log + 应用 apps//logs/_.log + ├── services.d/ # 服务配置(meow_svcd 的 .conf) ├── opt/ # 外围可选工具(curl/socat/tmux/jq/rsync/nano/htop/ncdu/file/xxd…) ├── lib/ # 可选共享库(opt 工具的依赖 .so) ├── tools/ # 调试/运维(可不设,并入 opt) @@ -48,8 +51,8 @@ - **移工具**:从 rootfs 移除 `MeowOS/opt/` 对应二进制;MeowOS 启动时若卡在则 PATH 前插 `/usr/data/mnt/sd_0/MeowOS/opt/bin:$PATH`(卡不在跳过)。用 `meowtool` 包装提供绝对路径引用。 - **框架零卡依赖审查**:确认 launcher/bootmenu 没有任何"卡必须在"的硬依赖(settings 在 /data、字体/应用/日志对卡可缺省/回退)。 -- **字库**:卡 `MeowAPPs/fonts/`(无卡回退点阵,已有)。 -- **日志**:卡 `MeowAPPs/logs/`(无卡内存环,已有)。 +- **字库**:多字体库 `MeowOS/fonts//`(每套 `font.png/font.idx/font.json`,`font.file` 选默认套,缺省 `MeowOS/fonts/default/font.png`;无卡回退点阵,已有。[源:审计 2026-08-30])。 +- **日志**:卡 `MeowOS/logs/`(系统日志 `meow_.log` + 应用日志 `apps//logs/_.log`;无卡内存环,已有。[源:审计 2026-08-30])。 - **设置**:板载 `/data/meow/settings.conf`(小文件跨卡保留),导出默认 `MeowOS/backups/settings/setting.bak`。 ## 5. 体积收益 diff --git a/docs/MEOWOS_SPEC_REF.md b/docs/MEOWOS_SPEC_REF.md index 7947062..7879ca4 100644 --- a/docs/MEOWOS_SPEC_REF.md +++ b/docs/MEOWOS_SPEC_REF.md @@ -19,17 +19,21 @@ /usr/data/mnt/sd_0/ ├── Music/ # 播放器标准索引目录 ├── MeowData//{data,cache} # 应用专属数据(exFAT 无软链,用 env 注入) -├── MeowAPPs/ # 应用(/app.json+二进制+资源, fonts/, logs/, backgrounds/) +├── MeowAPPs/ # 应用(=app.json.package=目录名;app.json+二进制+资源, fonts/, backgrounds/;日志已入 MeowOS/logs[源:审计 2026-08-30]) ├── MeowOpt/ # 板载外部工具/二进制(如 mdnsd/,卡上加载,拔卡降级不崩)`[DEV_NOTES][meow_svcd.md]` └── MeowOS/ # MeowOS 专属(一切非必要文件) ├── backups/settings/setting.bak # 设置导出默认路径 + ├── fonts// # 多字体库(每套一个子目录,见 §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) ├── logs/ # 系统日志 meow_.log + apps/...(见 §3 日志) ├── notifications/(转储区,保留最新 10MB,不被电源事件清) ├── opt/{bin,nyai,sources.list}, lib/, tools/ └── themes/charging// # 充电屏主题(theme.json manifest) ``` `[TF_CARD_LAYOUT §1][APPS 附录][NOTIFICATION_SYSTEM §5]` -- 环境注入(launcher 每次启动 app 前):`MEOW_DATA_DIR`/`MEOW_CACHE_DIR`/`MEOW_LOG_DIR`/`MEOW_LOG_APP`/`MEOW_LOG_LEVEL`/`MEOW_ACTIVITY`/`MEOW_CHAIN`;XDG:`XDG_DATA_HOME`/`XDG_CACHE_HOME`/`XDG_CONFIG_HOME`(=MeowData//{data,cache,config})`[APPS 附录 §2][FRAMEWORK_DESIGN 附录 D2]` +- 环境注入(launcher 每次启动 app 前):`MEOW_DATA_DIR`/`MEOW_CACHE_DIR`/`MEOW_LOG_DIR`/`MEOW_LOG_APP`(app_id=package)/`MEOW_LOG_LEVEL`/`MEOW_ACTIVITY`/`MEOW_CHAIN`;XDG:`XDG_DATA_HOME`/`XDG_CACHE_HOME`/`XDG_CONFIG_HOME`(=MeowData//{data,cache,config})`[APPS 附录 §2][FRAMEWORK_DESIGN 附录 D2]` ### 1.3 板载 vs TF 划分原则(拔卡降级) - 板载只放**稳定框架+KB 级设置**;日志/字库/媒体等大件一律 TF 卡 `[TF_CARD_LAYOUT §0][SYSTEM_SETTINGS §2]` @@ -42,7 +46,7 @@ ## 2. IPC / 接口 ### 2.1 launcher↔app env 注入 -`MEOW_LOG_DIR`(卡日志目录,无卡不设)/`MEOW_LOG_APP`(app_id)/`MEOW_LOG_LEVEL`(本 app 级别,系统下发)/`MEOW_ACTIVITY`/`MEOW_CHAIN`;;...; `[APPS §3.5][APP_LOG_MANAGER §3][FRAMEWORK_DESIGN §3]` +`MEOW_LOG_DIR`(卡日志目录,无卡不设)/`MEOW_LOG_APP`(app_id=app.json.package,缺省回退目录名)/`MEOW_LOG_LEVEL`(本 app 级别,系统下发)/`MEOW_ACTIVITY`/`MEOW_CHAIN`;;...; `[APPS §3.5][APP_LOG_MANAGER §3][FRAMEWORK_DESIGN §3]` ### 2.2 meow_ipc 消息协议(AF_UNIX,全小端 LE) - 帧头 16B:`magic=0x4D4F5701`('MEOW'|1)/`version=1`/`type`/`req_id`/`len`;`len` 上限 **MEOW_IPC_MAX=4096**;列表编码 `[u16 count]`+NUL 串 `[FRAMEWORK_DESIGN §1]` @@ -87,7 +91,7 @@ - 主题:`MEOW_THEME_LIGHT/DARK/AUTO`;`meow_theme{bg,fg,accent,dim,font_title=28,font_body=20}` `[meow_ui.h]` - 控件:`meow_list_source = int (*)(int idx,char *label,int len,void *ctx)`([D2] 定案)`[meow_ui.h]` - 刷新率:`meow_ui_set_refresh_rate(60/30/15)`/`meow_ui_set_refresh_always_max(1)`/`meow_ui_effective_rate()`/`meow_ui_frames_presented()` `[meow_ui.h §7.1]` -- 状态栏:`meow_ui_set_statusbar(1/0)`/`meow_ui_statusbar_height()`(非全屏=40,全屏=0)/`meow_ui_set_fullscreen(on)`;状态栏高 **MUI_SBAR_H=40** `[MIGRATION_AC §11.1][meow_ui.h]` +- 状态栏:`meow_ui_set_statusbar(1/0)`/`meow_ui_statusbar_height()`(非全屏=28,全屏=0)/`meow_ui_set_fullscreen(on)`;状态栏高 **MUI_SBAR_H=28**(用户定 40→28,字号随之为 20)`[MIGRATION_AC §11.1][meow_ui.h]` - 日志:`MEOW_LOG_DEBUG=0/INFO/WARN/ERROR`;`meow_log(lvl,fmt,...)`/`meow_log_set_level`/`meow_log_get_level` `[meow_ui.h §9]` - 通知:见 §4。 @@ -101,7 +105,8 @@ - 生命周期:`meow_ui_run(&ev)`(阻塞)/`meow_ui_request_exit()`;`meow_ui_time_ms()`(单调 ms) ### 3.3 实现定案(相对设计稿)`[MEOW_APP_SDK §12][meow_ui.h]` -- [D1] `meow_ui_opts` 补 width/height;[D2] `meow_list_source` 定案签名;[D3] 内部 `meow_gfx_fill_round/blit_rgba/mark_dirty`(非公共);[D4] 新增 `meow_theme_defaults(mode)`/`meow_theme_current()`;[D5] `on_swipe_progress`+`meow_ui_swipe_hint(x)`+内部 `MEOW_EV_SWIPE_PROGRESS`/`meow_input_inject`;[D6] 状态栏高=40;[D7] 音量源=settings.conf `audio.volume`;[D8] 蓝牙读 `hciconfig` 30s 缓存;[D9] 状态读取器跑在 `meow_ui_run` 1s 节流(无独立线程);[D10] 全局返回默认语义="APP 未注册 on_swipe 时 SDK 默认退出;已注册交 APP" +- [D1] `meow_ui_opts` 补 width/height;[D2] `meow_list_source` 定案签名;[D3] 内部 `meow_gfx_fill_round/blit_rgba/mark_dirty`(非公共);[D4] 新增 `meow_theme_defaults(mode)`/`meow_theme_current()`;[D5] `on_swipe_progress`+`meow_ui_swipe_hint(x)`+内部 `MEOW_EV_SWIPE_PROGRESS`/`meow_input_inject`;[D6] 状态栏高=40(后续用户定 40→28);[D7] 音量源=settings.conf `audio.volume`;[D8] 蓝牙读 `hciconfig` 30s 缓存;[D9] 状态读取器跑在 `meow_ui_run` 1s 节流(无独立线程);[D10] 全局返回默认语义="APP 未注册 on_swipe 时 SDK 默认退出;已注册交 APP" +- **构建取向(已知设计,非缺陷)**:`build_sdk.sh` 用 `-I../meow_launcherd` 引 `meow_ipc.h`(`meow_notify_ipc.c` 复用共享协议头),故 SDK 编译期依赖 launcher 头、**非独立库**;**运行时无碍** —— App 只链 `meow_ui.a`(含 meow_font.o),不会链入 launcher 符号。SDK 编译依赖 launcher 头属已知设计,勿当作耦合错误。 --- @@ -140,6 +145,15 @@ - **日/夜/自动**:SDK `meow_theme` 内置 `meow_theme_light/dark`;模式键 `display.theme_mode`(SYSTEM_SETTINGS 记为 `manual`/`auto`——**与 SDK 枚举 light/dark/auto 冲突**,见 §A);AUTO 判时段 `[SYSTEM_SETTINGS §1][MEOW_APP_SDK §8]` - 背景图系统:`display.bg_mode`(color/image)/`display.bg_dark_color`/`display.bg_light_color`/`display.bg_image`/`display.bg_image_dark`/`bg_image_light`/`display.bg_overlay_alpha`(0-255 默认 ~120);图片目录 **`MeowData/backgrounds/`**(480×720 约束);叠加层颜色随主题(深=黑叠/白=白叠)`[MAIN_MD3_PLAN §6][DEV_NOTES #12/#13]` - 主界面背景:深色用 background(可选色)、浅色用 surface(白) `[DEV_NOTES #13]` +- **TF 卡多字体库(M2.5x)**: + - 目录 **`<卡根>/MeowOS/fonts//`**(每套一个子目录),内含三件套:`font.png`(预渲染灰度 PNG,bit depth 4|8)+ `font.idx`(MFNI v2 索引),二者均由 `font/gen/gen_font.py` 生成;`font.json` 为元数据+完整性校验。**缺任一件 → 该套不进列表 + 日志**。 + - **font.json schema(定版)**:`family`(同目录名,信息用)/`name`(下拉显示名,缺则回退目录名)/`glyph_count`(信息用)/`size_list`(信息用)/`bit_depth`(4|8,信息用)/`sha256_png`(font.png 的 sha256 低 64hex)/`sha256_idx`(font.idx 的 sha256)/`source`(可选)/`license`(可选)。校验失败**仅 WARN 不硬失败**(该套仍可选);缺 font.json 或解析不出 `name` → 跳过。 + - **加载优先级**:`font.file`(设置键,默认 `MeowOS/fonts/default/font.png`)指向的字体**存在且能 load → 用它**;否则回退卡默认 `MeowOS/fonts/default/font.png` → 固件 `/usr/share/meowos/font/font.png`。相对路径按卡根 `/usr/data/mnt/sd_0/` 解析。全失败 → 字库不可用,`ui_draw` 回退 5x7 点阵(系统不死)。 + - **默认字库目录**:默认字体统一放 **`<卡根>/MeowOS/fonts/default/`**(`font.png`+`font.idx`,可选 `font.json`),不再用 `MeowAPPs/fonts/font.png`;设备旧 `MeowAPPs/fonts/` **保留**(勿删,避免破坏老约定/已有文件),仅新引用一律指向 `MeowOS/fonts/default`。 + - **`font.file` 变更即 unload+reload 生效**(设置页回调);拔卡 → 回退固件内置。 + - **preset 动态化**:启动/进设置页扫描一次 `MeowOS/fonts/*/`,重建「字体文件」下拉(默认字体 + ≤16 套卡内字体;上限防爆)。扫描仅此二时机,勿放热路径。 + - **MeowPanel 预留**:`meow_font_import_zip()`/`meow_font_export_zip()` 为 TODO 声明(不实现),供 MeowPanel 后续接入(`.zip` 导入校验→解压到 `MeowOS/fonts//`、导出打包)。 + - **多字体(M2.5x,`display.multi_font`)**:按字符类别(CJK/CJK_SYM/NUM/LATIN/LATIN_SYM/OTHER)逐字符路由,每类可各选一套字库(`font.class_*`,默认空=跟随全局 `font.file`);同 family 去重、同时装载不同字体 ≤4 套(超出 WARN「过多不同字体会增内存」并截断);开关关 = 单字体(现状)。详见 §5.7.7「多字体」。 ### 5.3 通知配额/转储 - **每 App 最多 4 类型;每类型活动 ≤4;媒体单例(每 App 仅 1 条,可多 App)**;系统事件抽象为 App(`app_id=system`) 走同一配额 `[NOTIFICATION_SYSTEM §7][meow_ui.h][DEV_NOTES #11]` @@ -155,13 +169,119 @@ - `/` = 程序主体+app.json+资源;`MeowData//{data,cache}` = 数据;首次启动自动建 data/cache 并经 env 注入,exFAT 无软链 `[APPS 附录 §1/§2]` - `.mpk` = tar.gz(app.json + meow 二进制 + 资源),**不含 data/cache**;导入校验 app.json/哈希→解压→数据 init→登入列表 `[APPS 附录 §3]` - 包管理 **CLI `nyai`**(静态 MIPS,部署 `MeowOS/opt/bin/nyai`):install/remove/list/info/search/index/export/upgrade/scan;源 `MeowOS/opt/nyai/sources.list`;索引 `Packages`(Package/Version/Filename/Size/SHA256/Depends/Description)`[APPS 附录 §5]` -- 应用数上限 **8**(单屏 8 行);扫描深度 1 级;`app.json` 字段:name(仅 ASCII)/exec(静态 MIPS32r2 ELF)/version/author + 扩展 `framework`/`immersive`/`icon`/`route.`/`service.`/`fullscreen` `[APPS §2][MEOW_APP_SDK §9][FRAMEWORK_DESIGN §2]` +- 应用数上限 **8**(单屏 8 行);扫描深度 1 级;`app.json` 字段:name(仅 ASCII)/exec(静态 MIPS32r2 ELF)/version/author + **`package`(反域名权威 app_id;==目录名;缺省回退目录名)** + 扩展 `framework`/`immersive`/`icon`/`route.`/`awake.`(keep_awake)/`service.`/`fullscreen`(**未解析**,见矛盾 §A-12) `[APPS §2][MEOW_APP_SDK §9][FRAMEWORK_DESIGN §2][源:审计 2026-08-30]` ### 5.6 冷启动充电屏 - 触发(**三者同时**):冷启动(`poweroff` 写 `/data/meow_cold_boot=1`;`reboot` 写 0)+ 在充电(AC online=1 或 battery status=Charging)+(开机瞬间一次性快照,判定后不回头)`[CHARGING_SCREEN §0]` - 热启动(reboot、含充电态 reboot)一律**不进**充电屏,走引导选单 `[CHARGING_SCREEN §0]` - 主题:`theme.charging=`(默认 default);`MeowOS/themes/charging//theme.json`;加载失败回退内置纯代码默认主题 `[CHARGING_SCREEN §1/§2]` +### 5.7 字体管理 + 应用字库接口(扩展 → SDK 统一接口 / 应用自带字库 / 多句柄) +> 在 §5.2「TF 卡多字体库(M2.5x)」基础上**扩展**:把 MeowOS 字体管理器定义为可用字库的**唯一权威来源**,向 SDK 提供统一查询/选用接口,并支持**应用自带字库**与**多句柄(多字体)**。 +> ⚠️ **凡 `meow_font_*_h` / `meow_ui_*_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=`);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 slug / 显示名 / glyph_count / size_list / bit_depth)。**SDK 不自行发现字库**,一律经本接口取 launcher 扫描结果。 + - 扫描与校验规则沿用 §5.2:三件套 `font.png/font.idx/font.json` **缺任一件 → 该套跳过 + 日志**;`font.json` 解析 `name`(缺回退目录名)/`glyph_count`/`size_list`/`bit_depth`;`sha256_png/sha256_idx` 仅 **WARN** 不硬失败(该套仍可选)。 + - 列表上限沿用 `FONT_PRESETS_MAX = 17`(1 默认 + ≤16 卡内套装,防爆);顺序 = 默认(0) + 扫描顺序。扫描时机不变(启动 / 进设置页),**勿放热路径** `[源:agent-79 实现]`。 + - 权威列表数据模型(SDK 侧快照,枚举时填充): + ```c + typedef struct meow_font_info { + char family[64]; /* family slug = MeowOS/fonts// 目录名 */ + char name[64]; /* font.json "name",缺则回退 family */ + int glyph_count; /* font.json "glyph_count",信息用 */ + int size_list[16]; /* font.json "size_list"(原生面字号,0 结束) */ + int size_count; /* 有效字号数 */ + int bit_depth; /* 4 | 8 */ + char path_png[320]; /* 系统库:相对卡根 MeowOS/fonts//font.png */ + } meow_font_info; + ``` +- **5.7.2 应用可读 TF 卡字体列表(统一规范接口,app 面向)** `[源:agent-79 实现][源:meow_font.h]` + ```c + int meow_font_get_available_count(void); /* 可用字库数(含默认) */ + 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,越界/加载失败返回负值。 + - **列表快照是临时的**:launcher 仅启动/进设置页重扫,期间拔卡或改卡内字库不刷新;app 应**每次会话重新枚举**、按 `family` 持久化,勿缓存数值 id。 + - 覆盖范围:系统库 `MeowOS/fonts//` 与默认(含固件 `/usr/share/meowos/font` 回退)都反映在列表里;app 侧「选用已有字库」**只用本接口,不直接拼路径**。 +- **5.7.3 应用自带字库(按 handle 加载)** `[源:meow_font.h]` + - 目录:`MeowAPPs//fonts//{font.png,font.idx,font.json}`;`` = `app.json.package`(反域名 app_id,见 §5.5),`` = 应用自设名字(如 `lyrics`/`reader`)。 + - `app.json` 声明(**可选**):`"fonts": ["lyrics","reader"]`。作用 = MeowPanel 打包校验 / launcher 装包校验 / SDK 提供取证;**不声明也可运行时按 handle 加载**。 + - SDK 接口(草案): + ```c + int meow_font_load_handle(const char *pkg, const char *handle); /* 返回字体句柄(>0),失败负值;pkg=NULL=当前 app */ + void meow_font_unload_handle(int h); + int meow_font_handle_info(int h, meow_font_info *out); /* 查句柄对应信息 */ + ``` + - **应用自带字库校验更严**:三件套缺任一或 `font.json` 解析不出 → 加载**失败**(app 自声明字库,坏库应显式失败,而非静默回退点阵/默认)。 + - 与系统库**并存可混用**:系统套件经 §5.7.2 `meow_font_select_by_id` 激活即成合法 handle;应用自带经 `load_handle` 取得 handle;两者在同一 handle 空间。 +- **5.7.4 多句柄 / 多字体(不同控件用不同字体)** `[源:meow_font.h][源:meow_ui.h]` + - 低层 `meow_font_*` 新增 `_h` 句柄版本(显式传 handle): + ```c + void meow_font_drawtext_h(uint16_t *fb,int x,int y,const char *utf8,uint16_t color,int size,int handle); + void meow_font_drawtext_outline_h(uint16_t *fb,int x,int y,const char *utf8, + uint16_t color,uint16_t outline_color,int ol,int size,int handle); + int meow_font_width_text_h(const char *utf8,int size,int handle); + int meow_font_line_height_h(int size,int handle); + int meow_font_ascent_h(int size,int handle); + int meow_font_has_size_h(int size,int handle); + ``` + - **兼容关键**:现有无 `_h` 的 `meow_font_*`(drawtext/width_text/line_height/...)语义变更为「作用于**当前句柄**」(默认 = 系统 `font.file` 那套);`meow_font_load(png,idx)` 仍是装载系统默认的入口;新增 `meow_font_use(handle)` 设定当前句柄。**不改任何现有签名 → 纯扩展、向后兼容。** + ```c + void meow_font_use(int handle); /* 设定后续无 _h meow_font_* 所用字体 */ + int meow_font_get_current(void); + ``` + - SDK `meow_ui_*` 高层文本同理: + ```c + void meow_ui_use_font(int handle); /* 设定后续 meow_ui_text* 字体 */ + int meow_ui_font_get_current(void); /* 取当前 meow_ui_text* 所用句柄 */ + void meow_ui_text_h(int x,int y,const char *utf8,int size,uint32_t color,int handle); + void meow_ui_text_right_h(int right_x,int y,const char *utf8,int size,uint32_t color,int handle); + void meow_ui_text_center_h(int cx,int y,const char *utf8,int size,uint32_t color,int handle); + int meow_ui_text_width_h(const char *utf8,int size,int handle); + int meow_ui_text_height_h(int size,int handle); /* = meow_font_line_height_h */ + ``` + 现有 `meow_ui_text/text_right/text_center`(无 handle)= 用当前字体(`meow_ui_use_font` 设;默认系统默认)。**向后兼容。** 建议 app 切库前用 `meow_font_get_current()` / `meow_ui_font_get_current()` 求值保存,画完再 `meow_ui_use_font(旧)` 显式恢复,避免句柄状态泄漏影响后续绘制。 +- **5.7.5 多句柄用法示例(歌词页用 A 库、正文用 B 库)** + ```c + /* 应用自带 lyrics(歌词页) + 系统字库(正文)混用 */ + int hLyrics = meow_font_load_handle(NULL, "lyrics"); /* MeowAPPs//fonts/lyrics/ */ + meow_ui_use_font(hLyrics); + meow_ui_text_center(240, 300, "歌词行…", 20, 0xFFFFFF); /* 用 lyrics 库 */ + + int hBody = meow_font_select_by_id(2); /* 系统字库第 2 套(正文用) */ + meow_ui_use_font(hBody); + meow_ui_text(20, 60, "正文…", 20, 0xFFFFFF); /* 用系统库 */ + + /* 量宽/行高须与最终实际字形一致:用同 handle 的 _h 版 */ + int w = meow_ui_text_width_h("正文…", 20, hBody); + int lh = meow_ui_text_height_h(20, hBody); + ``` + - 说明:句柄状态为 app 进程内**全局**;同一屏需混排时,推荐每段绘制前显式 `meow_ui_use_font`,或全部走 `*_h` 显式传 handle,避免依赖上次绘制遗留的句柄状态。 +- **5.7.6 MeowPanel 上传 / 导出(.zip,预留)** `[源:meow_font.h]` + - 复用已声明的 `meow_font_import_zip(zip_path)` / `meow_font_export_zip(family,out_zip)`(§5.2 已列为 TODO,未实现)。 + - **系统字库导入**:`.zip` 内含 `font.png+font.idx+font.json` 三件套;校验三件套就绪 + `font.json` 可解析;sha256 沿用 `font.json` 约定(建议与运行时同口径 = **WARN**,仅缺件/不可解析硬失败);`family` 取自 `font.json.family` 或 zip 名;解压到 `MeowOS/fonts//`;**覆盖冲突需先确认**;导入后应触发 launcher 重扫(刷新 §5.7.1 列表)。 + - **应用字库导入**:目标 `MeowAPPs//fonts//`,与 `app.json "fonts"` 声明对齐,校验同上。 + - **导出**:系统库把 `MeowOS/fonts//` 三件套打包;应用库把 `MeowAPPs//fonts//` 打包。 + - 约定:`.zip` 布局与校验沿用 `font.json` schema(§5.2);导入/导出后的一致性(列表刷新、应用声明同步)归 MeowPanel + launcher 协作,本小节仅列接口语义。 +- **5.7.7 多字体(按字符类别逐字符路由,`display.multi_font`,M2.5x 新增)** `[源:meow_font.h][源:MEOWOS_SPEC_REF §5.2/§5.5]` + - **背景**:一款字体难兼顾中文(大图集、字形全)与拉丁/数字(干净、省内存)。多字体让**不同字符类别各用一套字库**,按 codepoint 逐字符路由取字形。 + - **设置键**(launcher 读,SDK 经 env 读同一映射): + - `display.multi_font`(int **0/1,默认 0=单字体**):1=按字符类别逐字符路由;0=只走 `font.file`(现状,零额外内存/性能开销)。 + - 6 个类别字体(SET_STR,值 = 字体 family 相对路径,**默认空 = 跟随全局 `font.file`**):`font.class_cjk`(CJK 表意+扩展)、`font.class_cjk_sym`(全角/中文标点)、`font.class_num`(0-9)、`font.class_latin`(A-Za-z)、`font.class_latin_sym`(半角标点)、`font.class_other`(其他)。 + - **字符分类器**(`meow_font_class_of(cp)`,按 codepoint):`MEOW_FCLS_CJK`/`_CJK_SYM`/`_NUM`/`_LATIN`/`_LATIN_SYM`/`_OTHER`。规则(ASCII 优先 + Unicode 区间):0x30-39→NUM、0x41-5A/61-7A→LATIN、<0x80(其余)→LATIN_SYM;0x3000-303F/0xFF00-FFEF/0x2014/0x2018-201D/0x2026/0x00B7→CJK_SYM;CJK 表意+扩展+兼容表意区(0x3400-4DBF/4E00-9FFF/20000-2FA1F…)→CJK;其余(假名/韩文/其他文字/扩展区)→OTHER。 + - **逐字符路由**:`meow_font_drawtext`/`_outline`/`width_text`/`line_height`/`ascent`/`has_size` 在 multi_font 开启时**逐字符分类 → 用类别对应槽的字库取字形与度量**(宽度/行高按该字体);缺某类字体/槽未载 → **回退全局**(slot0);该字符其字体无字形 → **豆腐缺字**(可经 `meow_font_has_glyph(cp)` 预判)。开关关 → 一律 slot0,与单字体一致。 + - **SDK 接口**:`meow_font_load_slot(slot,png,idx)`/`meow_font_unload_slot(slot)`/`meow_font_enable_multifont(on)`/`meow_font_set_class_slot(cls,slot)`/`meow_font_config_multifont(on,global_png,global_idx,cfg[],&distinct,&truncated)`;`MEOW_MAX_SLOTS=5`(slot0=全局 + ≤4 类别)。`meow_font_config_multifont` 统一做**同 png 去重**(含与全局相同 → follow slot0)与**≤4 套上限**(超出 → 置 `*truncated=1`,调用方 WARN「过多不同字体会增内存」,已装载者保留、新类别回退全局)。 + - **launcher env 注入**(launch_app,app 跟随系统字体):`MEOW_MULTI_FONT=0/1`;`MEOW_FONT_PNG/IDX`(全局,保留);6 类 `MEOW_FONT_CLS_<类>_PNG/IDX`(命中则覆盖全局;类值为空=跟随全局不注入)。SDK `meow_ui_gfx` 读 env 构建同一映射;无 env/关 → 单字体。 + - **持久化**:类别字体设置在 settings.conf(板载,无卡也可存),经 `meow_settings_*` 读写,随设置页变更即重配。 + - **内存**(推断解码 atlas = `plane_w×plane_h×(depth 4→0.5 / 8→1.0)` 字节;每套另加 glyph 数组 ≈ glyph_count×16B、idx 头保留约 128KB,idx 原始缓冲载毕即释)。实测仓库字库(depth 4):misans(CJK)≈2.44MiB、source_han_sans(CJK)≈2.53MiB、meslo(Latin)≈1.68MiB、google_sans(Latin)≈1.12MiB;按用户估算口径(**depth 8**):CJK ≈ 5-6MB、Latin ≈ 0.3-0.5MB(后者为窄覆盖 Latin 专库)。**典型「1CJK+1Latin」增量**:全局=CJK,另加一套 Latin → 增加 ≈0.3-1.7MB(depth 8 约 0.5MB;实测 depth4 latin 1.1-1.7MB);全局=Latin 另加一套 CJK → 增加 ≈2.4-2.5MB(depth 8 约 5-6MB)。`display.multi_font=0` 时仅全局一套,无任何额外装载。 + --- ## 6. 标志性 TODO / 未决(文档未定,勿凭猜实现) @@ -180,8 +300,9 @@ - **A-5 通知 app_quota**:NOTIFICATION_SYSTEM §7/meow_ui.h = 4 类型×每类型 4=16 条/App;SYSTEM_SETTINGS §1 `notify.app_quota`=16。FRAMEWORK_DESIGN §4「配额 ≤16/应用」。→ **口径可归并(4×4=16)**,但 SYSTEM_SETTINGS 未列 4/4 细分,建议实现以 meow_ui.h 的 `MEOW_NOTIFY_MAX_TYPES/MAX_PER_TYPE` 为准。 - **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/.log`(单文件);meow_ui.h §9 / DEV_NOTES #9 为 **`apps/<包名>/logs/<启动时间>_.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)。→ 取 40。 +- **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-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.)。→ 文档与实现不一致,属"已定未做";若要用需在解析器补读并处理。 `[各源标注见上]` diff --git a/font/gen/gen_font.py b/font/gen/gen_font.py index 7058597..91f1139 100644 --- a/font/gen/gen_font.py +++ b/font/gen/gen_font.py @@ -211,6 +211,7 @@ def write_idx(path, bit_depth, faces, glyph_tabs): tab_sorted = sorted(tab, key=lambda g: g[0]) buf = bytearray() for cp, x, y, w, h, adv, dx, dy in tab_sorted: + dy = max(dy, 0) # 少数全角/符号字形墨迹略高于 la 锚点,得到负 dy;u8 仅能表达非负,钳到 0(整体下移 1-3px,肉眼不可见) buf += struct.pack(" 32) return -1; - if (depth != g.depth) return -2; /* 位深须与 PNG 一致 */ + if (depth != st->depth) return -2; /* 位深须与 PNG 一致 */ size_t face_off = 16; meow_face *faces = (meow_face *)calloc((size_t)nfaces, sizeof(meow_face)); @@ -271,20 +283,21 @@ static int load_idx(const unsigned char *buf, size_t len) { return -5; } } - g.faces = faces; - g.face_count = nfaces; + st->faces = faces; + st->face_count = nfaces; return 0; } -/* ---------------- 面/字形查找 ---------------- */ -static meow_face *face_for_size(int size) { - if (g.face_count <= 0) return NULL; +/* ---------------- 面/字形查找(槽感知) ---------------- */ +static meow_face *face_for_size(int slot, int size) { + meow_font_slot *st = &g_slots[slot]; + if (st->face_count <= 0) return NULL; meow_face *best = NULL; int best_delta = -1; - for (int i = 0; i < g.face_count; i++) { - int d = g.faces[i].px - size; + for (int i = 0; i < st->face_count; i++) { + int d = st->faces[i].px - size; if (d < 0) d = -d; - if (best_delta < 0 || d < best_delta) { best_delta = d; best = &g.faces[i]; } + if (best_delta < 0 || d < best_delta) { best_delta = d; best = &st->faces[i]; } } return best; } @@ -300,11 +313,12 @@ static meow_glyph *find_glyph(meow_face *f, uint32_t cp) { return NULL; } -/* ---------------- 像素采样(depth 感知) ---------------- */ -static inline unsigned sample_px(unsigned x, unsigned y) { - if (x >= g.plane_w || y >= g.plane_h) return 0; - if (g.depth == 8) return g.plane[(size_t)y * g.plane_w + x]; - unsigned char b = g.plane[((size_t)y * g.plane_w + x) >> 1]; +/* ---------------- 像素采样(depth 感知,槽感知) ---------------- */ +static inline unsigned sample_px(int slot, unsigned x, unsigned y) { + meow_font_slot *st = &g_slots[slot]; + if (x >= st->plane_w || y >= st->plane_h) return 0; + if (st->depth == 8) return st->plane[(size_t)y * st->plane_w + x]; + unsigned char b = st->plane[((size_t)y * st->plane_w + x) >> 1]; unsigned nh = (x & 1) ? (b & 0x0F) : (b >> 4); return nh * 17; } @@ -320,10 +334,10 @@ static inline uint16_t blend565(uint16_t bg, uint16_t fg, unsigned a) { return (uint16_t)((r << 11) | (gc << 5) | b); } -/* ---------------- 字形 blit(含缩放、裁剪、alpha 混合) ---------------- */ -static void blit_glyph(uint16_t *fb, meow_face *face, const meow_glyph *gr, +/* ---------------- 字形 blit(含缩放、裁剪、alpha 混合;槽感知) ---------------- */ +static void blit_glyph(uint16_t *fb, int slot, meow_face *face, const meow_glyph *gr, int pen_x, int y, uint16_t color, unsigned scaleq) { - int stride = (int)(g.fb_pitch / 2); /* uint16 元素每行数 */ + int stride = (int)(g_fb_pitch / 2); /* uint16 元素每行数 */ /* 目标尺寸 = 源尺寸 * scaleq / 4096 */ unsigned gw, gh; int dx, dy; @@ -343,15 +357,15 @@ static void blit_glyph(uint16_t *fb, meow_face *face, const meow_glyph *gr, for (unsigned oy = 0; oy < gh; oy++) { int ay = py + (int)oy; if (ay < 0) continue; - if (ay >= (int)g.fb_h) break; + if (ay >= (int)g_fb_h) break; int sy = (int)((uint64_t)oy * sh / gh); int base = face->plane_x + gr->x; for (unsigned ox = 0; ox < gw; ox++) { int ax = px + (int)ox; if (ax < 0) continue; - if (ax >= (int)g.fb_w) break; + if (ax >= (int)g_fb_w) break; int sx = (int)((uint64_t)ox * sw / gw); - unsigned a = sample_px((unsigned)(base + sx), + unsigned a = sample_px(slot, (unsigned)(base + sx), (unsigned)(face->plane_y + gr->y + sy)); if (!a) continue; size_t idx = (size_t)ay * (size_t)stride + (size_t)ax; @@ -386,13 +400,13 @@ static uint32_t advance_for_missing(meow_face *f) { } static void draw_tofu(uint16_t *fb, int x, int y, int w, int h, uint16_t color) { - int stride = (int)(g.fb_pitch / 2); + int stride = (int)(g_fb_pitch / 2); for (int oy = 0; oy < h; oy++) { int ay = y + oy; - if (ay < 0 || ay >= (int)g.fb_h) break; + if (ay < 0 || ay >= (int)g_fb_h) break; for (int ox = 0; ox < w; ox++) { int ax = x + ox; - if (ax < 0 || ax >= (int)g.fb_w) break; + if (ax < 0 || ax >= (int)g_fb_w) break; if (oy == 0 || oy == h - 1 || ox == 0 || ox == w - 1) { size_t idx = (size_t)ay * (size_t)stride + (size_t)ax; fb[idx] = blend565(fb[idx], color, 255); @@ -401,23 +415,86 @@ static void draw_tofu(uint16_t *fb, int x, int y, int w, int h, uint16_t color) } } +/* ---------------- 字符分类器(multi_font 路由依据) ---------------- */ +int meow_font_class_of(uint32_t cp) { + /* 数字 */ + if (cp >= '0' && cp <= '9') return MEOW_FCLS_NUM; + /* 拉丁字母(基本拉丁) */ + if ((cp >= 'A' && cp <= 'Z') || (cp >= 'a' && cp <= 'z')) return MEOW_FCLS_LATIN; + /* 半角标点/空格/控制符(ASCII 非字母数字) */ + if (cp < 0x80) return MEOW_FCLS_LATIN_SYM; + /* 全角 / 中文标点 */ + if ((cp >= 0x3000 && cp <= 0x303F) || /* CJK 符号与标点(含全角空格 U+3000) */ + (cp >= 0xFF00 && cp <= 0xFFEF) || /* 半/全角形式(全角数字字母符号) */ + cp == 0x2014 || cp == 0x2018 || cp == 0x2019 || /* — ‘’ */ + cp == 0x201C || cp == 0x201D || cp == 0x2026 || /* “” … */ + cp == 0x00B7) /* · */ + return MEOW_FCLS_CJK_SYM; + /* CJK 表意文字 + 扩展 + 兼容表意 */ + if ((cp >= 0x4E00 && cp <= 0x9FFF) || + (cp >= 0x3400 && cp <= 0x4DBF) || + (cp >= 0x20000 && cp <= 0x2A6DF) || + (cp >= 0x2A700 && cp <= 0x2B73F) || + (cp >= 0x2B740 && cp <= 0x2B81F) || + (cp >= 0x2B820 && cp <= 0x2CEAF) || + (cp >= 0x2CEB0 && cp <= 0x2EBEF) || + (cp >= 0x30000 && cp <= 0x3134F) || + (cp >= 0xF900 && cp <= 0xFAFF) || + (cp >= 0x2F800 && cp <= 0x2FA1F)) + return MEOW_FCLS_CJK; + return MEOW_FCLS_OTHER; +} + +/* ---------------- 路由:一个 codepoint 应使用的槽(multi off → slot0) ---------------- */ +static int route_slot(uint32_t cp) { + if (!g_multifont) return 0; + int cls = meow_font_class_of(cp); + if (cls < 0 || cls >= MEOW_FCLS_COUNT) return 0; + int s = g_class_slot[cls]; + if (s >= 0 && s < MEOW_MAX_SLOTS && g_slots[s].loaded) return s; + return 0; /* 缺该类别字库/未装载 → 回退全局 slot0 */ +} + +/* 取某码位在路由落点槽内、对应最近字号的面 + scaleq;无可用字库返回 NULL。 + * 顺带带回落点槽(供 blit 采样;不需要者传 NULL)。 */ +static meow_face *routed_face(uint32_t cp, int size, unsigned *scaleq, int *slot_out) { + int slot = route_slot(cp); + if (slot_out) *slot_out = slot; + meow_font_slot *st = &g_slots[slot]; + if (!st->loaded) return NULL; + meow_face *f = face_for_size(slot, size); + if (!f) return NULL; + if (scaleq) *scaleq = (size == f->px) ? 4096u : (unsigned)(((uint64_t)size << 12) / (uint64_t)f->px); + return f; +} + /* ---------------- 公共 API ---------------- */ void meow_font_set_fb(unsigned fb_w, unsigned fb_h, unsigned pitch_bytes) { - g.fb_w = fb_w; g.fb_h = fb_h; g.fb_pitch = pitch_bytes; + g_fb_w = fb_w; g_fb_h = fb_h; g_fb_pitch = pitch_bytes; +} + +void meow_font_unload_slot(int slot) { + if (slot < 0 || slot >= MEOW_MAX_SLOTS) return; + meow_font_slot *st = &g_slots[slot]; + if (st->faces) { + for (int i = 0; i < st->face_count; i++) free(st->faces[i].glyphs); + free(st->faces); + } + free(st->plane); + st->faces = NULL; st->plane = NULL; st->face_count = 0; + st->plane_w = st->plane_h = 0; st->plane_rowbytes = 0; st->depth = 0; + st->loaded = 0; } void meow_font_unload(void) { - if (g.faces) { - for (int i = 0; i < g.face_count; i++) free(g.faces[i].glyphs); - free(g.faces); - } - free(g.plane); - g.faces = NULL; g.plane = NULL; g.face_count = 0; - g.plane_w = g.plane_h = 0; + for (int i = 0; i < MEOW_MAX_SLOTS; i++) meow_font_unload_slot(i); + g_multifont = 0; + for (int i = 0; i < MEOW_FCLS_COUNT; i++) g_class_slot[i] = 0; } -int meow_font_load(const char *png_path, const char *idx_path) { - meow_font_unload(); +int meow_font_load_slot(int slot, const char *png_path, const char *idx_path) { + if (slot < 0 || slot >= MEOW_MAX_SLOTS) return -9; + meow_font_unload_slot(slot); size_t plen = 0, ilen = 0; unsigned char *png = read_file(png_path, &plen); if (!png) return -1; @@ -430,46 +507,91 @@ int meow_font_load(const char *png_path, const char *idx_path) { free(png); if (!plane) { free(idx); return -2; } - g.plane = plane; - g.depth = depth; - g.plane_w = w; - g.plane_h = h; - g.plane_rowbytes = rowbytes; + meow_font_slot *st = &g_slots[slot]; + st->plane = plane; + st->depth = depth; + st->plane_w = w; + st->plane_h = h; + st->plane_rowbytes = rowbytes; - if (load_idx(idx, ilen) != 0) { + if (load_idx(idx, ilen, slot) != 0) { free(idx); - meow_font_unload(); + meow_font_unload_slot(slot); return -3; } free(idx); + st->loaded = 1; return 0; } +int meow_font_load(const char *png_path, const char *idx_path) { + meow_font_unload(); /* 全局字库(重)装:清全部槽 + 复位多字体配置 */ + return meow_font_load_slot(0, png_path, idx_path); +} + +void meow_font_enable_multifont(int on) { g_multifont = on ? 1 : 0; } + +void meow_font_set_class_slot(meow_fclass cls, int slot) { + if (cls < 0 || cls >= MEOW_FCLS_COUNT) return; + g_class_slot[cls] = (slot >= 0 && slot < MEOW_MAX_SLOTS && g_slots[slot].loaded) ? slot : 0; +} + int meow_font_has_size(int size) { - for (int i = 0; i < g.face_count; i++) - if (g.faces[i].px == size) return 1; + int lo = g_multifont ? 0 : 0; + int hi = g_multifont ? MEOW_MAX_SLOTS : 1; + for (int s = lo; s < hi; s++) { + meow_font_slot *st = &g_slots[s]; + if (!st->loaded) continue; + for (int i = 0; i < st->face_count; i++) + if (st->faces[i].px == size) return 1; + } + return 0; +} + +int meow_font_has_glyph(uint32_t cp) { + int slot = route_slot(cp); + meow_font_slot *st = &g_slots[slot]; + if (!st->loaded) return 0; + for (int i = 0; i < st->face_count; i++) + if (find_glyph(&st->faces[i], cp)) return 1; return 0; } int meow_font_ascent(int size) { - meow_face *f = face_for_size(size); + if (g_multifont) { + int best = 0; + for (int s = 0; s < MEOW_MAX_SLOTS; s++) { + meow_face *f = face_for_size(s, size); + if (f && f->ascent > best) best = f->ascent; + } + return best; + } + meow_face *f = face_for_size(0, size); return f ? f->ascent : 0; } int meow_font_line_height(int size) { - meow_face *f = face_for_size(size); + if (g_multifont) { + int best = 0; + for (int s = 0; s < MEOW_MAX_SLOTS; s++) { + meow_face *f = face_for_size(s, size); + if (f) { int h = f->ascent + f->descent; if (h > best) best = h; } + } + return best; + } + meow_face *f = face_for_size(0, size); return f ? (f->ascent + f->descent) : 0; } int meow_font_width_text(const char *utf8, int size) { - meow_face *f = face_for_size(size); - if (!f) return 0; - unsigned scaleq = (size == f->px) ? 4096u : (unsigned)(((uint64_t)size << 12) / (uint64_t)f->px); const unsigned char *p = (const unsigned char *)utf8; int total = 0; while (*p) { int cp = utf8_next(&p); if (cp < 0) continue; + unsigned scaleq; + meow_face *f = routed_face((uint32_t)cp, size, &scaleq, NULL); + if (!f) continue; meow_glyph *gr = find_glyph(f, (uint32_t)cp); unsigned adv = gr ? gr->advance : advance_for_missing(f); if (scaleq == 4096) total += (int)adv; @@ -480,17 +602,18 @@ int meow_font_width_text(const char *utf8, int size) { void meow_font_drawtext(uint16_t *fb, int x, int y, const char *utf8, uint16_t color, int size) { - meow_face *f = face_for_size(size); - if (!f || !g.plane) return; - unsigned scaleq = (size == f->px) ? 4096u : (unsigned)(((uint64_t)size << 12) / (uint64_t)f->px); const unsigned char *p = (const unsigned char *)utf8; int pen = 0; while (*p) { int cp = utf8_next(&p); if (cp < 0) continue; + unsigned scaleq; + int slot; + meow_face *f = routed_face((uint32_t)cp, size, &scaleq, &slot); + if (!f) continue; meow_glyph *gr = find_glyph(f, (uint32_t)cp); if (gr && gr->w > 0 && gr->h > 0) { - blit_glyph(fb, f, gr, x + pen, y, color, scaleq); + blit_glyph(fb, slot, f, gr, x + pen, y, color, scaleq); } else if (!gr) { /* 缺字:画空心方块 */ unsigned adv = advance_for_missing(f); @@ -511,22 +634,74 @@ void meow_font_drawtext_outline(uint16_t *fb, int x, int y, const char *utf8, if (ol <= 0) { meow_font_drawtext(fb, x, y, utf8, color, size); return; } static const int odx[8] = {-1,1,-1,1,0,0,-1,1}; static const int ody[8] = {-1,-1,1,1,-1,1,0,0}; - /* 先将每个字形画 8 方向偏移描边,再画原色:逐字符处理以保证 advance 一致性。 */ - meow_face *f = face_for_size(size); - if (!f || !g.plane) return; - unsigned scaleq = (size == f->px) ? 4096u : (unsigned)(((uint64_t)size << 12) / (uint64_t)f->px); + /* 先将每个字形画 8 方向偏移描边,再画原色:逐字符处理以保证 advance 一致。 */ const unsigned char *p = (const unsigned char *)utf8; int pen = 0; while (*p) { int cp = utf8_next(&p); if (cp < 0) continue; + unsigned scaleq; + int slot; + meow_face *f = routed_face((uint32_t)cp, size, &scaleq, &slot); + if (!f) continue; meow_glyph *gr = find_glyph(f, (uint32_t)cp); if (gr && gr->w > 0 && gr->h > 0) { for (int k = 0; k < 8; k++) - blit_glyph(fb, f, gr, x + pen + odx[k] * ol, y + ody[k] * ol, outline_color, scaleq); + blit_glyph(fb, slot, f, gr, x + pen + odx[k] * ol, y + ody[k] * ol, outline_color, scaleq); } - int adv = gr ? gr->advance : (int)advance_for_missing(f); - pen += scaleq == 4096 ? adv : (int)(((uint64_t)adv * scaleq) >> 12); + unsigned adv = gr ? gr->advance : advance_for_missing(f); + pen += scaleq == 4096 ? (int)adv : (int)(((uint64_t)adv * scaleq) >> 12); } meow_font_drawtext(fb, x, y, utf8, color, size); } + +/* ---------------- 多字体配置(去重 + ≤4 套上限,launcher 与 SDK 共用) ---------------- */ +int meow_font_config_multifont(int on, + const char *global_png, const char *global_idx, + const meow_font_class_cfg cfg[MEOW_FCLS_COUNT], + int *distinct, int *truncated) { + if (distinct) *distinct = 0; + if (truncated) *truncated = 0; + (void)global_idx; /* 全局字库已由 meow_font_load(slot0) 装载,idx 此处不重读 */ + /* 复位映射(保留已装载槽;未开则纯单字体) */ + g_multifont = 0; + for (int i = 0; i < MEOW_FCLS_COUNT; i++) g_class_slot[i] = 0; + int g0 = g_slots[0].loaded; + if (!on) { if (distinct) *distinct = g0 ? 1 : 0; return 0; } + + /* 已装载的不同字库(png 路径去重)。首项 = 全局(slot0)。 */ + const char *d_png[MEOW_MAX_SLOTS]; + int d_slot[MEOW_MAX_SLOTS]; + int d_n = 0; + d_png[0] = global_png ? global_png : ""; + d_slot[0] = 0; + d_n = 1; + g_multifont = 1; + + for (int c = 0; c < MEOW_FCLS_COUNT; c++) { + const char *png = cfg[c].png; + if (!png || !png[0]) continue; /* 空 = 跟随全局 */ + int found = -1; + if (global_png && !strcmp(png, global_png)) found = 0; + for (int k = 0; k < d_n && found < 0; k++) + if (!strcmp(png, d_png[k])) found = d_slot[k]; /* 同 family 去重 */ + if (found >= 0) { g_class_slot[c] = found; continue; } + + /* 新的一套不同字体:上限 MEOW_MAX_SLOTS(含全局)→ 截断 + WARN */ + if (d_n >= MEOW_MAX_SLOTS) { + if (truncated) *truncated = 1; + g_class_slot[c] = 0; /* 该类别回退全局 */ + continue; + } + int slot = d_n; /* slot0 已被全局占用 */ + const char *idx = cfg[c].idx ? cfg[c].idx : ""; + if (meow_font_load_slot(slot, png, idx) != 0) { /* 装载失败 → 回退全局 */ + g_class_slot[c] = 0; + continue; + } + d_png[d_n] = png; d_slot[d_n] = slot; d_n++; + g_class_slot[c] = slot; + } + if (distinct) *distinct = d_n; + return 0; +} diff --git a/font/meow_font.h b/font/meow_font.h index bb0f9c0..9ceb4bd 100644 --- a/font/meow_font.h +++ b/font/meow_font.h @@ -51,6 +51,62 @@ void meow_font_drawtext(uint16_t *fb, int x, int y, const char *utf8, void meow_font_drawtext_outline(uint16_t *fb, int x, int y, const char *utf8, uint16_t color, uint16_t outline_color, int ol, int size); +/* ---- 字符分类器(multi_font 逐字符路由的依据;按 codepoint 归类)---- */ +typedef enum { + MEOW_FCLS_CJK = 0, /* CJK 表意文字 + 扩展 / 兼容表意(U+3400-4DBF/4E00-9FFF/20000-2FA1F/…) */ + MEOW_FCLS_CJK_SYM = 1, /* 全角/中文标点(U+3000-303F、U+FF00-FFEF、U+2014/2018-201D/2026、U+00B7) */ + MEOW_FCLS_NUM = 2, /* 0-9 */ + MEOW_FCLS_LATIN = 3, /* A-Za-z(基本拉丁字母) */ + MEOW_FCLS_LATIN_SYM = 4, /* 半角标点(ASCII 非字母数字:空格/括号/引号/运算符/控制符) */ + MEOW_FCLS_OTHER = 5, /* 其他(假名/韩文/他文字/扩展区等) */ + MEOW_FCLS_COUNT = 6 +} meow_fclass; + +/* 把一个 codepoint 归类为上述之一。 */ +int meow_font_class_of(uint32_t cp); + +/* 校验码位是否在「将用于渲染它的字库」中存在字形(1=有,0=缺字/字库未加载→渲染为豆腐)。 + * multi_font 开启时按分类路由到对应字库,关闭时仅查全局字库(slot 0)。 */ +int meow_font_has_glyph(uint32_t cp); + +/* ---- 多字体(MEOW_FCLS* 逐字符路由)---- + * 槽位模型:slot 0 = meow_font_load() 装载的全局/默认字库(font.file);额外字库经 + * meow_font_load_slot() 装载后,用 meow_font_set_class_slot() 把某字符类指到某一槽。 + * meow_font_drawtext/width_text/line_height/... 在 meow_font_enable_multifont(1) 开启时 + * 逐字符分类 → 用该类别对应槽的字库取字形与度量;缺该类别字库/未加载 → 回退 slot 0。 + * 关闭(默认)时一律走 slot 0,行为与单字体完全一致(零额外开销)。 + * 同 family 去重、≤4 套上限由 meow_font_config_multifont() 统一处理(launcher 与 SDK 共用)。 */ +#define MEOW_MAX_SLOTS 5 /* slot0(全局) + ≤4 套类别字库;超出则截断并置 *truncated=1(调用方 WARN) */ + +int meow_font_load_slot(int slot, const char *png_path, const char *idx_path); /* 0=成功,负值失败 */ +void meow_font_unload_slot(int slot); +void meow_font_enable_multifont(int on); /* 0=单字体(默认);1=逐字符分类路由 */ +void meow_font_set_class_slot(meow_fclass cls, int slot); /* slot 越界/载入失败 → 该类别回退 slot 0 */ + +/* 一个字符类的候选字库路径(绝对路径,已由调用方按卡根解析;png=NULL/空 = 跟随全局)。 */ +typedef struct { + const char *png; + const char *idx; +} meow_font_class_cfg; + +/* 依据开关 + 各类候选配置多字体:同 png 路径去重(含与全局相同 → 直接用 slot 0), + * 独立字库数(含全局)> MEOW_MAX_SLOTS 时截断并置 *truncated=1(此前已装载的保留)。 + * 返回 0 成功,负值失败;*distinct = 实际装载的不同字库数(含全局)。 */ +int meow_font_config_multifont(int on, + const char *global_png, const char *global_idx, + const meow_font_class_cfg cfg[MEOW_FCLS_COUNT], + int *distinct, int *truncated); + +/* ---- MeowPanel 预留:TF 卡字体库 .zip 导入/导出(TODO,未实现)---- + * 当前仅声明占位,供 MeowPanel 后续接入;不实现、不接 zip 库。 + * 调用方约定(待实现):导入=校验 .zip 内含 font.png+font.idx+font.json 三件套, + * 校验通过后解压到 TF 卡 MeowOS/fonts//(family 取自 font.json 的 family 或 zip 名, + * 冲突时覆盖需先确认);导出=把 MeowOS/fonts// 三件套打包为 。 + * 目录布局与 font.json schema 见 docs/MEOWOS_SPEC_REF.md §1.2/§5.2。 + * 返回码:MEOW_OK=0 成功,负值错误码(沿用本模块失败负值约定)。 */ +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 */ + #ifdef __cplusplus } #endif diff --git a/font/test_multi_font.c b/font/test_multi_font.c new file mode 100644 index 0000000..dd48e56 --- /dev/null +++ b/font/test_multi_font.c @@ -0,0 +1,271 @@ +/* + * test_multi_font.c — MeowOS 多字体功能宿主导出单测。 + * + * 验证两块(MEOWOS_SPEC_REF §5.7 多字体 / §5.2 分类路由): + * (1) meow_font_class_of() 字符分类器:各代表 codepoint 归类正确。 + * (2) 逐字符路由:meow_font_drawtext/width_text/line_height/has_glyph 在 multi_font + * 开启时按类别用对应槽字库;关闭(默认)时与单字体一致;family 去重;≤4 套上限截断。 + * + * 为避免依赖仓库真实字体(不同机器/来源不确定),此处用「假 atlas/stub」:离线合成两张 + * 微型位图字体(合法 PNG 灰度8bit + MFNI v2 idx),各含 1-3 个字形、不同 advance,故 + * 路由结果可精确断言。 + * - lat(slot0/全局):'A' adv=10、'0' adv=10、' '(0x20) adv=10 —— 只有拉丁/数字/空格。 + * - cjk(slot1):'中'(0x4E2D) adv=30 —— 只有 CJK。 + * + * 用法:宿主 gcc -I. test_multi_font.c meow_font.c -lz -o test_multi_font && ./test_multi_font + * 退出码 0=全过;非 0=某断言失败(打印 FAIL)。 + */ +#include "meow_font.h" +#include +#include +#include +#include +#include + +/* ---------------- 微型合成字库(假 atlas/stub) ---------------- */ +static void wr_be32(unsigned char **p, uint32_t v) { (*p)[0]=v>>24;(*p)[1]=v>>16;(*p)[2]=v>>8;(*p)[3]=v; *p+=4; } +static void wr_le16(unsigned char **p, uint16_t v) { (*p)[0]=v&0xFF;(*p)[1]=v>>8; *p+=2; } +static void wr_le32(unsigned char **p, uint32_t v) { (*p)[0]=v&0xFF;(*p)[1]=(v>>8)&0xFF;(*p)[2]=(v>>16)&0xFF;(*p)[3]=(v>>24)&0xFF; *p+=4; } + +static void png_chunk2(unsigned char *dst, size_t *off, const char *type, + const unsigned char *data, size_t dlen) { + unsigned char *o = dst + *off; + wr_be32(&o, (uint32_t)dlen); + memcpy(o, type, 4); + o += 4; + if (dlen) { memcpy(o, data, dlen); o += dlen; } + /* CRC = crc32(type) 续 crc32(data) */ + uint32_t c = crc32(0L, (const Bytef *)type, 4); + if (dlen) c = crc32(c, (const Bytef *)data, (uInt)dlen); + wr_be32(&o, c); + *off += 12 + dlen; +} + +/* 生成一张合法 PNG:w x h、灰度8bit、非隔行、filter=0 全零像素。返回 malloc 缓冲。 */ +static unsigned char *make_png(int w, int h, size_t *outlen) { + const unsigned char sig[8] = {0x89,'P','N','G','\r','\n',0x1a,'\n'}; + /* IHDR data (13B) */ + unsigned char ihdr[13]; + ihdr[0]=w>>24; ihdr[1]=w>>16; ihdr[2]=w>>8; ihdr[3]=w; + ihdr[4]=h>>24; ihdr[5]=h>>16; ihdr[6]=h>>8; ihdr[7]=h; + ihdr[8]=8; /* bit depth 8 */ + ihdr[9]=0; /* color type 0 = gray */ + ihdr[10]=0; ihdr[11]=0; ihdr[12]=0; /* compression/filter/interlace */ + + int rowbytes = w; /* depth8 gray */ + size_t rawlen = (size_t)(rowbytes + 1) * h; + unsigned char *raw = (unsigned char *)calloc(1, rawlen); /* 每行 filter 字节=0 + 像素=0 */ + if (!raw) return NULL; + for (int y = 0; y < h; y++) raw[y * (rowbytes + 1)] = 0; + + uLongf destlen = compressBound((uLong)rawlen); + unsigned char *idat = (unsigned char *)malloc(destlen); + if (compress2(idat, &destlen, raw, (uLong)rawlen, Z_DEFAULT_COMPRESSION) != Z_OK) { + free(raw); free(idat); return NULL; + } + free(raw); + size_t total = 8 + 12 + 13 + 12 + destlen + 12; + unsigned char *out = (unsigned char *)malloc(total); + if (!out) { free(idat); return NULL; } + size_t off = 0; + memcpy(out + off, sig, 8); off += 8; + png_chunk2(out, &off, "IHDR", ihdr, 13); + png_chunk2(out, &off, "IDAT", idat, destlen); + png_chunk2(out, &off, "IEND", NULL, 0); + free(idat); + *outlen = off; + return out; +} + +/* 生成 MFNI v2 idx:1 个面(px 字号),planes 覆盖全 PNG,含 nglyphs 个字(cp/adv 升序)。 */ +static unsigned char *make_idx(int px, int ascent, int descent, int plane_w, int plane_h, + const uint32_t *cps, const int *advs, int nglyphs, + size_t *outlen) { + size_t len = 16 + 20 + (size_t)nglyphs * 16; + unsigned char *out = (unsigned char *)calloc(1, len); + unsigned char *o = out; + memcpy(o, "MFNI", 4); o += 4; + wr_le16(&o, 2); /* version=2 */ + *o++ = 8; /* depth */ + *o++ = 1; /* nfaces */ + wr_le32(&o, (uint32_t)plane_w); wr_le32(&o, (uint32_t)plane_h); + /* face */ + wr_le16(&o, (uint16_t)px); /* px */ + wr_le16(&o, 0); /* plane_x */ + wr_le16(&o, 0); /* plane_y */ + wr_le16(&o, (uint16_t)plane_w); /* plane_w */ + wr_le16(&o, (uint16_t)plane_h); /* plane_h */ + wr_le16(&o, (uint16_t)ascent); /* ascent (s16) */ + wr_le16(&o, (uint16_t)descent); /* descent (s16) */ + wr_le16(&o, (uint16_t)nglyphs); /* glyph_count */ + wr_le32(&o, 16 + 20); /* goff */ + /* glyphs 升序 */ + for (int i = 0; i < nglyphs; i++) { + wr_le32(&o, cps[i]); /* cp */ + wr_le16(&o, 0); /* x */ + wr_le16(&o, 0); /* y */ + wr_le16(&o, (uint16_t)px); /* w */ + wr_le16(&o, (uint16_t)px); /* h */ + wr_le16(&o, (uint16_t)advs[i]); /* advance */ + *o++ = 0; /* dx */ + *o++ = 0; /* dy */ + } + *outlen = len; + return out; +} + +/* 写一套字库 .png/.idx,返回 0 成功。 */ +static int write_font(const char *base, int px, int ascent, int descent, + const uint32_t *cps, const int *advs, int nglyphs) { + char pngp[512], idxp[512]; + snprintf(pngp, sizeof pngp, "%s.png", base); + snprintf(idxp, sizeof idxp, "%s.idx", base); + size_t plen = 0, ilen = 0; + unsigned char *png = make_png(64, 64, &plen); + if (!png) { fprintf(stderr, "make_png failed\n"); return -1; } + unsigned char *idx = make_idx(px, ascent, descent, 64, 64, cps, advs, nglyphs, &ilen); + if (!idx) { free(png); return -1; } + FILE *f = fopen(pngp, "wb"); + if (!f) { free(png); free(idx); return -1; } + fwrite(png, 1, plen, f); fclose(f); + f = fopen(idxp, "wb"); + if (!f) { free(png); free(idx); return -1; } + fwrite(idx, 1, ilen, f); fclose(f); + free(png); free(idx); + return 0; +} + +/* ---------------- 断言 ---------------- */ +static int g_fail = 0; +#define CHECK(cond, msg) do { \ + if (!(cond)) { g_fail++; fprintf(stderr, "FAIL: %s (line %d)\n", msg, __LINE__); } \ + else printf("ok : %s\n", msg); \ +} while (0) + +/* ---------------- 主测 ---------------- */ +int main(void) { + /* 造两套 stub 字库 */ + const uint32_t lat_cps[] = {0x20, 0x30, 0x41}; /* 空格 '0' 'A' */ + const int lat_adv[] = {10, 10, 10}; + const uint32_t cjk_cps[] = {0x4E2D}; /* '中' */ + const int cjk_adv[] = {30}; + if (write_font("/tmp/mf_lat", 20, 20, 6, lat_cps, lat_adv, 3) != 0) return 2; + if (write_font("/tmp/mf_cjk", 20, 25, 8, cjk_cps, cjk_adv, 1) != 0) return 2; + + /* ---- (1) 字符分类器 ---- */ + printf("\n== 分类器 meow_font_class_of ==\n"); + CHECK(meow_font_class_of('A') == MEOW_FCLS_LATIN, "A -> LATIN"); + CHECK(meow_font_class_of('z') == MEOW_FCLS_LATIN, "z -> LATIN"); + CHECK(meow_font_class_of('5') == MEOW_FCLS_NUM, "5 -> NUM"); + CHECK(meow_font_class_of(' ') == MEOW_FCLS_LATIN_SYM, "spc -> LATIN_SYM"); + CHECK(meow_font_class_of(',') == MEOW_FCLS_LATIN_SYM, ", -> LATIN_SYM"); + CHECK(meow_font_class_of('(') == MEOW_FCLS_LATIN_SYM, "( -> LATIN_SYM"); + CHECK(meow_font_class_of('.') == MEOW_FCLS_LATIN_SYM, ". -> LATIN_SYM"); + CHECK(meow_font_class_of(0x4E2D) == MEOW_FCLS_CJK, "U+4E2D -> CJK"); + CHECK(meow_font_class_of(0x9FFF) == MEOW_FCLS_CJK, "U+9FFF -> CJK"); + CHECK(meow_font_class_of(0x3400) == MEOW_FCLS_CJK, "U+3400(ExtA) -> CJK"); + CHECK(meow_font_class_of(0x20000) == MEOW_FCLS_CJK, "U+20000(ExtB) -> CJK"); + CHECK(meow_font_class_of(0x3001) == MEOW_FCLS_CJK_SYM, "U+3001(、) -> CJK_SYM"); + CHECK(meow_font_class_of(0x3000) == MEOW_FCLS_CJK_SYM, "U+3000(全角空格) -> CJK_SYM"); + CHECK(meow_font_class_of(0xFF0C) == MEOW_FCLS_CJK_SYM, "U+FF0C(,) -> CJK_SYM"); + CHECK(meow_font_class_of(0xFF10) == MEOW_FCLS_CJK_SYM, "U+FF10(0) -> CJK_SYM"); + CHECK(meow_font_class_of(0x2026) == MEOW_FCLS_CJK_SYM, "U+2026(…) -> CJK_SYM"); + CHECK(meow_font_class_of(0x00B7) == MEOW_FCLS_CJK_SYM, "U+00B7(·) -> CJK_SYM"); + CHECK(meow_font_class_of(0x3042) == MEOW_FCLS_OTHER, "U+3042(あ) -> OTHER"); + CHECK(meow_font_class_of(0xAC00) == MEOW_FCLS_OTHER, "U+AC00(가) -> OTHER"); + CHECK(meow_font_class_of(0xE9) == MEOW_FCLS_OTHER, "U+00E9(é) -> OTHER"); + + /* ---- (2) 多字体路由 ---- */ + printf("\n== 单字体基线(multi off,全局=lat)==\n"); + int r = meow_font_load("/tmp/mf_lat.png", "/tmp/mf_lat.idx"); + CHECK(r == 0, "load global lat"); + CHECK(meow_font_has_size(20), "lat has size 20"); + CHECK(meow_font_width_text("A", 20) == 10, "w(A)=10 (lat)"); + CHECK(meow_font_width_text("0", 20) == 10, "w(0)=10 (lat)"); + /* lat 无 '中' → 缺字回退空格 adv=10 */ + CHECK(meow_font_width_text("中", 20) == 10, "w(中)=10 fallback (lat no cjk)"); + CHECK(meow_font_width_text("A中", 20) == 20, "w(A中)=20 (single font)"); + CHECK(meow_font_has_glyph(0x4E2D) == 0, "has_glyph(中)=0 (lat lacks)"); + CHECK(meow_font_has_glyph('A') == 1, "has_glyph(A)=1 (lat has)"); + CHECK(meow_font_line_height(20) == 26, "lh(20)=26 (lat ascent20+desc6)"); + + printf("\n== 开启 multi_font(class_cjk -> cjk 槽)==\n"); + meow_font_class_cfg cfg[MEOW_FCLS_COUNT]; + memset(cfg, 0, sizeof cfg); + cfg[MEOW_FCLS_CJK].png = "/tmp/mf_cjk.png"; + cfg[MEOW_FCLS_CJK].idx = "/tmp/mf_cjk.idx"; + char gpng[64], gidx[64]; + snprintf(gpng, sizeof gpng, "/tmp/mf_lat.png"); + snprintf(gidx, sizeof gidx, "/tmp/mf_lat.idx"); + int distinct = 0, truncd = 0; + r = meow_font_config_multifont(1, gpng, gidx, cfg, &distinct, &truncd); + CHECK(r == 0, "config_multifont ok"); + CHECK(distinct == 2, "distinct=2 (lat + cjk)"); + CHECK(truncd == 0, "not truncated"); + CHECK(meow_font_width_text("A", 20) == 10, "w(A)=10 (routed -> global/lat, latin class)"); + CHECK(meow_font_width_text("0", 20) == 10, "w(0)=10 (num class -> global)"); + CHECK(meow_font_width_text("中", 20) == 30, "w(中)=30 (routed -> cjk slot)"); + CHECK(meow_font_width_text("A中", 20) == 40, "w(A中)=40 (per-char routing)"); + CHECK(meow_font_has_glyph(0x4E2D) == 1, "has_glyph(中)=1 (routed -> cjk)"); + CHECK(meow_font_line_height(20) == 33, "lh(20)=33 (max(lat26, cjk33))"); + /* 关闭 → 立即回到单字体 */ + meow_font_config_multifont(0, gpng, gidx, cfg, &distinct, &truncd); + CHECK(meow_font_width_text("A中", 20) == 20, "off -> w(A中)=20 again"); + CHECK(meow_font_has_glyph(0x4E2D) == 0, "off -> has_glyph(中)=0 again"); + + /* drawtext smoke:吃一个混合串不崩(当前 cfg 仍为合法 cjk 配置) */ + printf("\n== drawtext smoke(不影响断言,仅验证不崩)==\n"); + meow_font_set_fb(64, 64, 128); + static uint16_t buf[64 * 64]; + memset(buf, 0, sizeof buf); + meow_font_config_multifont(1, gpng, gidx, cfg, &distinct, &truncd); + meow_font_drawtext(buf, 0, 0, "A中0,", 0xFFFF, 20); + printf("drew 'A中0,' ok\n"); + + printf("\n== family 去重 / 上限 ==\n"); + /* latin 类别也指到 cjk → 应去重,distinct 仍为 2 */ + memset(cfg, 0, sizeof cfg); + cfg[MEOW_FCLS_CJK].png = "/tmp/mf_cjk.png"; cfg[MEOW_FCLS_CJK].idx = "/tmp/mf_cjk.idx"; + cfg[MEOW_FCLS_LATIN].png = "/tmp/mf_cjk.png"; cfg[MEOW_FCLS_LATIN].idx = "/tmp/mf_cjk.idx"; + meow_font_config_multifont(1, gpng, gidx, cfg, &distinct, &truncd); + CHECK(distinct == 2, "dedupe: latin==cjk path -> distinct=2"); + CHECK(truncd == 0, "dedupe: not truncated"); + /* 超上限:6 类各指向一个不同、可加载的字体(除全局)→ 第 5 个起截断。 + * MEOW_MAX_SLOTS=5(global + ≤4 类别),故最多 5 套不同字库。 */ + { + /* 生成 5 个额外不同字库(不同 advance,保证去重键不同) */ + const uint32_t fcps[] = {0x4E2D}; + int fav = 100; + char paths[8][64], idxs[8][64]; + for (int i = 2; i < 7; i++) { /* 生成 /tmp/mf_trunc2..6 */ + snprintf(paths[i], sizeof paths[i], "/tmp/mf_trunc%d", i); + if (write_font(paths[i], 20, 25, 8, fcps, &fav, 1) != 0) return 2; + snprintf(paths[i], sizeof paths[i], "/tmp/mf_trunc%d.png", i); + snprintf(idxs[i], sizeof idxs[i], "/tmp/mf_trunc%d.idx", i); + fav += 50; + } + char pp[6][64]; + /* 6 类全部指向不同非全局字库 */ + snprintf(pp[MEOW_FCLS_CJK], sizeof pp[0], "/tmp/mf_trunc2.png"); + snprintf(pp[MEOW_FCLS_CJK_SYM], sizeof pp[0], "/tmp/mf_trunc3.png"); + snprintf(pp[MEOW_FCLS_NUM], sizeof pp[0], "/tmp/mf_trunc4.png"); + snprintf(pp[MEOW_FCLS_LATIN], sizeof pp[0], "/tmp/mf_trunc5.png"); + snprintf(pp[MEOW_FCLS_LATIN_SYM], sizeof pp[0], "/tmp/mf_trunc6.png"); + snprintf(pp[MEOW_FCLS_OTHER], sizeof pp[0], "/tmp/mf_trunc2.png"); /* 与 CJK 同 → 去重,不新增 */ + for (int i = 0; i < 6; i++) { cfg[i].png = pp[i]; cfg[i].idx = NULL; } + /* 修 idx:write_font 已生对应 .idx,需给出 */ + char ppi[6][64]; + const char *maps[6] = {"/tmp/mf_trunc2", "/tmp/mf_trunc3", "/tmp/mf_trunc4", + "/tmp/mf_trunc5", "/tmp/mf_trunc6", "/tmp/mf_trunc2"}; + for (int i = 0; i < 6; i++) { + snprintf(ppi[i], sizeof ppi[i], "%s.idx", maps[i]); + cfg[i].idx = ppi[i]; + } + meow_font_config_multifont(1, gpng, gidx, cfg, &distinct, &truncd); + CHECK(truncd == 1, "6 distinct -> truncated=1"); + CHECK(distinct == MEOW_MAX_SLOTS, "distinct capped at MEOW_MAX_SLOTS"); + } + + printf("\n%s (%d failures)\n", g_fail ? "RESULT: FAIL" : "RESULT: PASS", g_fail); + return g_fail ? 1 : 0; +} diff --git a/mips-repo/README.md b/mips-repo/README.md index f2d570d..ae054c1 100644 --- a/mips-repo/README.md +++ b/mips-repo/README.md @@ -2,7 +2,7 @@ 目标设备:R3 Pro II / MeowOS 固件(**mipsel-linux-muslsf**,静态,无 glibc 依赖)。 用途:存放针对该设备的交叉编译二进制与构建信息,便于**复用**到后续固件 / TF 卡 -(MeowLib / MeowOpt 目录)。 +(MeowOpt 目录;`MeowLib` 未定义/未用——`MeowOpt` 与 `MeowOS/opt` 命名需归一:代码用 `MeowOS/opt`,文档 `MeowOpt` 指卡上外部工具目录。[源:审计 2026-08-30])。 ## 仓库结构(每工具一个目录) ``` diff --git a/patch_v2/apps_assets/apps/hello/app.json b/patch_v2/apps_assets/apps/hello/app.json deleted file mode 100644 index dbb7c3e..0000000 --- a/patch_v2/apps_assets/apps/hello/app.json +++ /dev/null @@ -1 +0,0 @@ -{"name":"Hello App","exec":"/usr/data/mnt/sd_0/MeowAPPs/hello/hello","version":"0.1","immersive":1} \ No newline at end of file diff --git a/patch_v2/apps_assets/apps/os.meow.hello/app.json b/patch_v2/apps_assets/apps/os.meow.hello/app.json new file mode 100644 index 0000000..81bb07b --- /dev/null +++ b/patch_v2/apps_assets/apps/os.meow.hello/app.json @@ -0,0 +1 @@ +{"name":"Hello App","exec":"/usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/hello","package":"os.meow.hello","version":"0.1","framework":1,"immersive":1} \ No newline at end of file diff --git a/patch_v2/apps_assets/apps/hello/hello b/patch_v2/apps_assets/apps/os.meow.hello/hello similarity index 100% rename from patch_v2/apps_assets/apps/hello/hello rename to patch_v2/apps_assets/apps/os.meow.hello/hello diff --git a/patch_v2/bootmenu/meow_bootmenu b/patch_v2/bootmenu/meow_bootmenu index bc00c12d8b9dc717935c033f65cd6762def953e6..c4282e9df815188cb339398e98cdc2ab039a9dc5 100755 GIT binary patch delta 34200 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 J>`L?E{{dizz=Z$+ delta 34029 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.log(卡不可用降级 stderr),记录 supervisor + * MeowOS/logs/meow_boot_.log(卡不可用降级 stderr),记录 supervisor * **收到**的信号(SIGTERM/SIGINT/SIGQUIT/SIGHUP——handler 只置标志,主循环 log 后 * signal(s,SIG_DFL)+raise(s) 恢复默认重抛,与未加 hook 一致终止)、**发出**给播放器的 * 信号(SIGSTOP/SIGCONT/SIGTERM/SIGKILL)、以及**播放器退出原因**(WTERMSIG/WEXITSTATUS)。 @@ -95,7 +95,7 @@ #define DEV_TOUCH "/dev/input/event1" /* goodix-ts(未 grab,可共享读) */ #define FB_DEV "/dev/fb0" #define MEOWAPPS "/usr/data/mnt/sd_0/MeowAPPs" -#define CONF_FILE MEOWAPPS "/meow.conf" /* TF 卡配置:boot_default/boot_timeout_ms/boot_menu */ +#define CONF_FILE MEOWAPPS "/meow.conf" /* TF 卡配置:boot_timeout_ms/boot_menu/bg_color;boot_default 权威在板载 /data/meow_boot_default */ #define MODE_FILE "/tmp/meow_boot_mode" /* 开机模式标记(tmpfs,开机清空) */ #define PLAYER_BIN "/usr/bin/hiby_player" #define PLAYER_LOG "/usr/data/meow_player.log" @@ -410,7 +410,7 @@ static int amixer_vol(int numid) return v; } -/* 40px 状态栏:行1=时钟(左) 电量% WIFI BT(右),行2=VOL nn%(左),y=40 分隔线 */ +/* 28px 状态栏:行1=时钟(左) 电量% WIFI BT(右),行2=VOL nn%(左),y=28 分隔线(40→28,用户定) */ static void draw_statusbar(uint32_t bg) { char timebuf[16], right[48]; @@ -441,16 +441,16 @@ static void draw_statusbar(uint32_t bg) char bt[8] = ""; if (bluetooth_present()) snprintf(bt, sizeof bt, "BT"); - draw_text(8, 4, timebuf, 1, 0x9FB8D9); + draw_text(8, 2, timebuf, 1, 0x9FB8D9); if (batt[0]) strcat(right, batt); if (wifi[0]) { if (right[0]) strcat(right, " "); strcat(right, wifi); } if (bt[0]) { if (right[0]) strcat(right, " "); strcat(right, bt); } if (right[0]) { int rx = fb_xres - 8 - (int)strlen(right) * 6; - draw_text(rx, 4, right, 1, 0x9FB8D9); + draw_text(rx, 2, right, 1, 0x9FB8D9); } - if (vol[0]) draw_text(8, 22, vol, 1, 0x9FB8D9); - fill_rect(0, 40, fb_xres, 1, 0x333355); /* 分隔线(移至 y=40) */ + if (vol[0]) draw_text(8, 14, vol, 1, 0x9FB8D9); + fill_rect(0, 28, fb_xres, 1, 0x333355); /* 分隔线(移至 y=28) */ } /* 只刷时钟数字(每秒一次,不弹 amixer/不碰菜单体) */ @@ -463,43 +463,70 @@ static void draw_clock(uint32_t bg) snprintf(timebuf, sizeof timebuf, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec); int w = (int)strlen(timebuf) * 6; - fill_rect(4, 2, 8 + w, 12, bg); - draw_text(8, 4, timebuf, 1, 0x9FB8D9); + fill_rect(4, 0, 8 + w, 12, bg); + draw_text(8, 2, timebuf, 1, 0x9FB8D9); } /* ================================================================== */ -/* 配置:boot_default / boot_timeout_ms / boot_menu(从 TF 卡尽力读) */ +/* 配置:boot_default / boot_timeout_ms / boot_menu / bg_color */ +/* boot_default:权威 = 板载 /data/meow_boot_default(O_TRUNC 单值、不写 TF 卡); + * meow.conf 的 boot_default= 仅作无板载标记时的兜底(不新增写入,见 set_boot_default_meow)。 */ /* ================================================================== */ static int cfg_boot_default = DEF_BOOT_DEFAULT; static long cfg_boot_timeout_ms = DEF_BOOT_TIMEOUT_MS; static int cfg_boot_menu = DEF_BOOT_MENU; static uint32_t cfg_bg = DEF_BG; +/* 读板载 /data/meow_boot_default(开机默认权威标记)。返回 0/1;-1=不存在/非法。 */ +static int read_boot_default_onboard(void) +{ + int fd = open("/data/meow_boot_default", O_RDONLY); + if (fd < 0) return -1; + char buf[8]; + int n = read(fd, buf, sizeof buf - 1); + close(fd); + if (n <= 0) return -1; + buf[n] = 0; + int v = atoi(buf); + return (v == 0 || v == 1) ? v : -1; +} + static void load_config(void) { cfg_boot_default = DEF_BOOT_DEFAULT; cfg_boot_timeout_ms = DEF_BOOT_TIMEOUT_MS; cfg_boot_menu = DEF_BOOT_MENU; cfg_bg = DEF_BG; + int has_conf = 1; FILE *f = fopen(CONF_FILE, "r"); - if (!f) { fprintf(stderr, "bootmenu: no %s, using defaults\n", CONF_FILE); return; } - char buf[128]; - while (fgets(buf, sizeof buf, f)) { - if (!strncmp(buf, "boot_default=", 13)) { - cfg_boot_default = atoi(buf + 13); - if (cfg_boot_default != 0 && cfg_boot_default != 1) cfg_boot_default = DEF_BOOT_DEFAULT; - } else if (!strncmp(buf, "boot_timeout_ms=", 16)) { - long t = atol(buf + 16); - if (t >= 500 && t <= 60000) cfg_boot_timeout_ms = t; - } else if (!strncmp(buf, "boot_menu=", 10)) { - int m = atoi(buf + 10); - if (m == 0 || m == 1) cfg_boot_menu = m; - } else if (!strncmp(buf, "bg_color=", 9)) { - unsigned int v; - if (sscanf(buf + 9, "%x", &v) == 1) cfg_bg = v & 0xFFFFFF; + if (!f) { fprintf(stderr, "bootmenu: no %s, using defaults\n", CONF_FILE); has_conf = 0; } + if (has_conf) { + char buf[128]; + while (fgets(buf, sizeof buf, f)) { + if (!strncmp(buf, "boot_default=", 13)) { + /* 板载 /data/meow_boot_default 为权威;此处仅作无板载标记时的兜底(不再新增写入)。 */ + cfg_boot_default = atoi(buf + 13); + if (cfg_boot_default != 0 && cfg_boot_default != 1) cfg_boot_default = DEF_BOOT_DEFAULT; + } else if (!strncmp(buf, "boot_timeout_ms=", 16)) { + long t = atol(buf + 16); + if (t >= 500 && t <= 60000) cfg_boot_timeout_ms = t; + } else if (!strncmp(buf, "boot_menu=", 10)) { + int m = atoi(buf + 10); + if (m == 0 || m == 1) cfg_boot_menu = m; + } else if (!strncmp(buf, "bg_color=", 9)) { + unsigned int v; + if (sscanf(buf + 9, "%x", &v) == 1) cfg_bg = v & 0xFFFFFF; + } } + fclose(f); + } + /* 板载标记优先(权威):覆盖 meow.conf 兜底/编译默认。 */ + int ob = read_boot_default_onboard(); + if (ob == 0 || ob == 1) { + if (ob != cfg_boot_default) + fprintf(stderr, "bootmenu: boot_default onboard=%d (override %d)\n", ob, cfg_boot_default); + cfg_boot_default = ob; } - fclose(f); fprintf(stderr, "bootmenu: cfg boot_default=%d timeout_ms=%ld boot_menu=%d bg=%06x\n", cfg_boot_default, cfg_boot_timeout_ms, cfg_boot_menu, cfg_bg); } @@ -519,10 +546,10 @@ static unsigned long long now_ms(void) * - 借鉴 launcher log_open_card/logmsg 思路,但**不共享 /tmp/meow_bootts** * (launcher 缓存的是完整基名 meow_,直接复读会拼成 meow_boot_meow_…); * 每次自行按 /proc/uptime 推开机时间戳,同一 boot 内文件名恒定。 - * - 写 /usr/data/mnt/sd_0/MeowAPPs/logs/meow_boot_.log(O_APPEND|O_CREAT 0644)。 + * - 写 /usr/data/mnt/sd_0/MeowOS/logs/meow_boot_.log(O_APPEND|O_CREAT 0644)。 * - 卡不可用(未挂载/不可写)→ 降级 stderr。**只记录,不改任何行为。** * ================================================================== */ -#define BM_LOG_DIR "/usr/data/mnt/sd_0/MeowAPPs/logs" +#define BM_LOG_DIR "/usr/data/mnt/sd_0/MeowOS/logs" static int bm_log_fd = -1; static char bm_log_file[256] = ""; @@ -1287,21 +1314,12 @@ static void write_mode(int choice) /* Bug 1:选 MeowOS → 把它持久为开机默认(写 spec 标记 + meow.conf boot_default=0),与 launcher * «回到HibyOS»(set_boot_default_hiby 写 boot_default=1)对称。效果:本次选中即进 MeowOS 主屏,且 - * 下一次开机选单默认 MeowOS(不再「选 MeowOS 仍落回 HibyOS」)。追加一行,bootmenu 逐行读、最后一行生效。 */ + * 下一次开机选单默认 MeowOS(不再「选 MeowOS 仍落回 HibyOS」)。 + * 权威标记 = 板载 /data/meow_boot_default(O_TRUNC 单值);meow.conf 不再写/承载 boot_default。 */ static void set_boot_default_meow(void) { int fd = open("/data/meow_boot_default", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd >= 0) { write(fd, "0\n", 2); close(fd); fprintf(stderr, "bootmenu: /data/meow_boot_default=0\n"); } - char conf[256]; - snprintf(conf, sizeof conf, "%s/meow.conf", MEOWAPPS); - fd = open(conf, O_WRONLY | O_CREAT | O_APPEND, 0644); - if (fd >= 0) { - if (write(fd, "boot_default=0\n", 15) < 0) fprintf(stderr, "bootmenu: write boot_default failed\n"); - close(fd); - fprintf(stderr, "bootmenu: boot_default=0 appended to meow.conf\n"); - } else { - fprintf(stderr, "bootmenu: meow.conf open failed: %s\n", strerror(errno)); - } } /* ================================================================== */ diff --git a/patch_v2/deploy_device.sh b/patch_v2/deploy_device.sh index fd24ee5..5892a93 100755 --- a/patch_v2/deploy_device.sh +++ b/patch_v2/deploy_device.sh @@ -27,8 +27,8 @@ ssh_cmd 'echo "instances=$(ps | grep hiby_player | grep -v grep | grep -v sh | w echo "==> 推送文件" scp -i "$KEY" -o StrictHostKeyChecking=no "$ROOT/hiby_player_patched_v2" root@$IP:/data/hiby_player_patched_v2 scp -i "$KEY" -o StrictHostKeyChecking=no "$ROOT/meow_watchdog/meow_watchdog" root@$IP:/usr/data/meow_watchdog -scp -i "$KEY" -o StrictHostKeyChecking=no "$ROOT/apps_assets/apps/hello/hello" root@$IP:/data/hello.bin -scp -i "$KEY" -o StrictHostKeyChecking=no "$ROOT/apps_assets/apps/hello/app.json" root@$IP:/data/hello_app.json +scp -i "$KEY" -o StrictHostKeyChecking=no "$ROOT/apps_assets/apps/os.meow.hello/hello" root@$IP:/data/hello.bin +scp -i "$KEY" -o StrictHostKeyChecking=no "$ROOT/apps_assets/apps/os.meow.hello/app.json" root@$IP:/data/hello_app.json echo "==> 设备侧组装" ssh_cmd ' @@ -36,12 +36,12 @@ set -e if ! grep -q "sd_0" /proc/mounts; then echo "!! TF 卡未挂载"; mount /dev/mmcblk0p1 /usr/data/mnt/sd_0 2>/dev/null || echo "!! 挂载失败" fi -mkdir -p /usr/data/mnt/sd_0/MeowAPPs/hello /data/overlay -cp -f /data/hello.bin /usr/data/mnt/sd_0/MeowAPPs/hello/hello -cp -f /data/hello_app.json /usr/data/mnt/sd_0/MeowAPPs/hello/app.json -chmod 755 /usr/data/mnt/sd_0/MeowAPPs/hello/hello +mkdir -p /usr/data/mnt/sd_0/MeowAPPs/os.meow.hello /data/overlay +cp -f /data/hello.bin /usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/hello +cp -f /data/hello_app.json /usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/app.json +chmod 755 /usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/hello chmod 755 /usr/data/meow_watchdog -ls /usr/data/mnt/sd_0/MeowAPPs/hello/ +ls /usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/ rm -rf /data/overlay/theme1 /data/overlay/str_zh /data/overlay/str_en cp -a /usr/resource/layout/theme1 /data/overlay/theme1 cp -a /usr/resource/str/simplified_chinese /data/overlay/str_zh diff --git a/patch_v2/meow_launcherd/meow_font.h b/patch_v2/meow_launcherd/meow_font.h index bb0f9c0..9ceb4bd 100644 --- a/patch_v2/meow_launcherd/meow_font.h +++ b/patch_v2/meow_launcherd/meow_font.h @@ -51,6 +51,62 @@ void meow_font_drawtext(uint16_t *fb, int x, int y, const char *utf8, void meow_font_drawtext_outline(uint16_t *fb, int x, int y, const char *utf8, uint16_t color, uint16_t outline_color, int ol, int size); +/* ---- 字符分类器(multi_font 逐字符路由的依据;按 codepoint 归类)---- */ +typedef enum { + MEOW_FCLS_CJK = 0, /* CJK 表意文字 + 扩展 / 兼容表意(U+3400-4DBF/4E00-9FFF/20000-2FA1F/…) */ + MEOW_FCLS_CJK_SYM = 1, /* 全角/中文标点(U+3000-303F、U+FF00-FFEF、U+2014/2018-201D/2026、U+00B7) */ + MEOW_FCLS_NUM = 2, /* 0-9 */ + MEOW_FCLS_LATIN = 3, /* A-Za-z(基本拉丁字母) */ + MEOW_FCLS_LATIN_SYM = 4, /* 半角标点(ASCII 非字母数字:空格/括号/引号/运算符/控制符) */ + MEOW_FCLS_OTHER = 5, /* 其他(假名/韩文/他文字/扩展区等) */ + MEOW_FCLS_COUNT = 6 +} meow_fclass; + +/* 把一个 codepoint 归类为上述之一。 */ +int meow_font_class_of(uint32_t cp); + +/* 校验码位是否在「将用于渲染它的字库」中存在字形(1=有,0=缺字/字库未加载→渲染为豆腐)。 + * multi_font 开启时按分类路由到对应字库,关闭时仅查全局字库(slot 0)。 */ +int meow_font_has_glyph(uint32_t cp); + +/* ---- 多字体(MEOW_FCLS* 逐字符路由)---- + * 槽位模型:slot 0 = meow_font_load() 装载的全局/默认字库(font.file);额外字库经 + * meow_font_load_slot() 装载后,用 meow_font_set_class_slot() 把某字符类指到某一槽。 + * meow_font_drawtext/width_text/line_height/... 在 meow_font_enable_multifont(1) 开启时 + * 逐字符分类 → 用该类别对应槽的字库取字形与度量;缺该类别字库/未加载 → 回退 slot 0。 + * 关闭(默认)时一律走 slot 0,行为与单字体完全一致(零额外开销)。 + * 同 family 去重、≤4 套上限由 meow_font_config_multifont() 统一处理(launcher 与 SDK 共用)。 */ +#define MEOW_MAX_SLOTS 5 /* slot0(全局) + ≤4 套类别字库;超出则截断并置 *truncated=1(调用方 WARN) */ + +int meow_font_load_slot(int slot, const char *png_path, const char *idx_path); /* 0=成功,负值失败 */ +void meow_font_unload_slot(int slot); +void meow_font_enable_multifont(int on); /* 0=单字体(默认);1=逐字符分类路由 */ +void meow_font_set_class_slot(meow_fclass cls, int slot); /* slot 越界/载入失败 → 该类别回退 slot 0 */ + +/* 一个字符类的候选字库路径(绝对路径,已由调用方按卡根解析;png=NULL/空 = 跟随全局)。 */ +typedef struct { + const char *png; + const char *idx; +} meow_font_class_cfg; + +/* 依据开关 + 各类候选配置多字体:同 png 路径去重(含与全局相同 → 直接用 slot 0), + * 独立字库数(含全局)> MEOW_MAX_SLOTS 时截断并置 *truncated=1(此前已装载的保留)。 + * 返回 0 成功,负值失败;*distinct = 实际装载的不同字库数(含全局)。 */ +int meow_font_config_multifont(int on, + const char *global_png, const char *global_idx, + const meow_font_class_cfg cfg[MEOW_FCLS_COUNT], + int *distinct, int *truncated); + +/* ---- MeowPanel 预留:TF 卡字体库 .zip 导入/导出(TODO,未实现)---- + * 当前仅声明占位,供 MeowPanel 后续接入;不实现、不接 zip 库。 + * 调用方约定(待实现):导入=校验 .zip 内含 font.png+font.idx+font.json 三件套, + * 校验通过后解压到 TF 卡 MeowOS/fonts//(family 取自 font.json 的 family 或 zip 名, + * 冲突时覆盖需先确认);导出=把 MeowOS/fonts// 三件套打包为 。 + * 目录布局与 font.json schema 见 docs/MEOWOS_SPEC_REF.md §1.2/§5.2。 + * 返回码:MEOW_OK=0 成功,负值错误码(沿用本模块失败负值约定)。 */ +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 */ + #ifdef __cplusplus } #endif diff --git a/patch_v2/meow_launcherd/meow_launcherd b/patch_v2/meow_launcherd/meow_launcherd index 12432e201855339b23988904706ff608be102cc6..38b999536177cd0dbe0ba6f77d591b6f63ca475f 100755 GIT binary patch delta 140399 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
cHLYk&P`Pwqv0fKVIFuK-M|JeO&$} z3uMnZZZtpGsoq=w=?e$?@NdYf#8F&5Zavl$Lf#Q}ck+$y%2>f|;MRi2aX6WMG0?HN zULA5bt^$Vdl5_bogaCg3&86CHHQA#LU&48SLz_hIb_gCZwnwFV`C;FZuWVZtd)dDLn6M*Wa-8Uz+2E?MlXPWyL^P2(khDz z+&bB!3!Yul3Yk9~P*1c%`3Mf>jaE4+FCW}m2=qtzhk(QkA=gy1P<~hL9-+1@lvn0G zb9w}d2V5rwjvVUw_WsjV%_4~5Xm18S$Fi?F01Way@;>C$fBqI(@2@yp6ssRClIPZh z0yEZ-DLk4P23#8sjBH=CA2-cJu1HzUts{hN{qc9=t@sTxR~>jzUI^7*9TC!XXJWhW zIb#sw)b>1{z?d_+q&(0BgWhV=VmT#hG)%2B88VB)cYTd!{CpQQV#8utB`S1>>1$nELVp~rv zX@toT?nUG%cwry;Ecd{qm07P~LsAYpjE+QDrmq7tTuUAp7=|Pkc-eK8tHYGAyxLWc zyb~)1QE?{9T@TWIm|nd&LnFQGE?0u*oEAMtSr?1~C_fX-!g10W2z>#)DBqU3zKrD; zpxy^Vmjf24KqCe{A)ZHIc`dYkgdMN#x&lnvP$)Wjg|VU}@wvib+KrwG+_v*bLjk{dJ9V9a9i{j!xf0GZe!6xf1jZL1>+c{LxL24#~&nQ5R; z04a9dqNo*|43JV!dK%tmeSX)u4#zUr;X<$xIOTUN4CA=BkST0)&n@t!c?pfsqH=kd zA{|{9?^f?Dl~u(TVOh3!&|{LUa~_sIf_4dgklBu9@}#qN!+k;w!OEIy90scvpeob8 z?R9*Cd%;+F#r++yG^Mn?4|4r`&f&>t3-dT#Vw7$+;aeydh$*0&*-iI(+@`(a!Hy*O ztt4JGk;eUUhtu5W&~2~Se0wPHla5|te5=T96`*-=n(|P_#$<=!yhnHjF6wmK%x-TE z4db*i>u4H$N1RwF3D5<_109vV%Y`ehC1K8s71DX@Zj_hG2OW_KY;R924K)SkMHPdY zS8?K4Xt`Z`#WoBz-9zXwMwxW}dsvrVoUmAMtOqjl`Od-uf}4ZBH56Fgk&m?-j%QrM z-I;Shg3^*+-Hj1)Mnhnx-I~HE3&!BxeBtTv@Kc@gEjg_C9C0}sFK{Xspz?96`pw>B z#|cmkfT>qJ2!=i_>x;2XvD!q2P~h$kLhLiZjB!EufU}`HUv|eix~u*FLw7F^uIj&Z zw+ge(#~t&bz~&Bm?h*ssArSNl!aYj3;~buvg+?GPx95*C2%n7&-*W1-W?{%or|g2P zvM)Jhv$M+HaLQT+%M$Wcr!WcDwimJl(?(J73~*o)4&h%3;~=riGoK(X8741yad zd*)$rAEe`?R#FuL|LIy9@%<8*Xtxt`+#_7gPUrnhW_{U*nPh<%N;B|IUcyUKMg`aUSQ9 z!Ip3H(-Lz*I<|^WGS|QOd3gBd0@5SIBoO&AXmb*>pKefJ`nD`huu8u^pIL=;o?D#TpIbaAUN5^EcuYr5 zZt+6Y8HKVNQ0AU?te@l5_dE6csK@iegY$!nM18{ZxR@6w5fYOX4D9@*`qQ`NIICCX ztdOS{ACG$wLV4A5h;OLc@5uaxTu^s$-Gy~Y!3sf_&Ez0CfWtZV9QGioE;+;G04eC# z<+*xaa-X{UGO5m5A;%>qIm@(ZyTLKC-SEK@wdno`Y?V|un4@q@50|p~1S?53?%dT2 zA*@9a6Cd69fY^eV_(&VdfHIqVh$8ojEsunTzgq3uN&9?o_w$vjcUH)g6KuHR1P#Mn zkINTc)y_@(CtHH`!Y8O#jCzG)m8l2Y;nhQ-tAtw)>z@eq$h(EKGkqS=NhW{@Ti|-b z47zvsya(d77`rIPSzw=p?*O9QN`3o0@sWsjgR(E_e8%%w-XzxjSvLOhyUp-#jS3_d zhBQ7EZF=#yjkw#xcWBf+;5UZN#Uf_Ems#xe0rIdASVUeXJIsKickv`2merx6037Fw z9d4KbwNghi`6w@e7&yAI)b|>q$|h*0k<12@;RhBDW<)Pgd?(f92N)3?hj1& zAQKHz4kb+Te{R{^_6q`dFGSBjFZ`JR-aG9KwJ7Tl` zUUNBQvBlfe41DJ80Y%4lg}fNnsfc40s>YHfz5*+$&n*+_-+Q_ACWcxjdm?giYWc%7 z$1cONIjhUinqy5*-^qG*OPWJ4@BARE{8~ifx#i^KxG)_4UE2oA6S6n!!T!Mq@}aEq zQ}ldI2m7G5|FV?Q zD&YvY!jZVWEZV{6b49ef8R8XZ1|1v?>I<%ri>#q)-xcrxo*gK^Yc_OTvjeBZN(((! z6|Om9uDS!-YJ%%dLcl_tmVbRuz*{YH*LR(+*p%U~<8rk#99r8#IHBEPKwqivOPG%# zWDgMz<-z$#$Aq$g=JuR%Att9-b^Jh1@$H374FHkAoAL}f--Us(@};v>|@F*P!=92Q}cc#qjT6s81*^m zXS=89&mXOuV9%EiLT7F@{-PURy!cFZPc+ZE2W8$n@SSpTp?5Pi{8|RzvqwGsBSaS+ zlQSkA2f`+|!~iVHW7N44U}er;amsas34v90OnF+D3v)6KP-4P|5Bu-rS8Gn*{z$4x&Stg z`oX&HGasHY`29rB0(b`Cr@-td20C$HNzr!V4q!W&YfG#xD@HjJuyE5ih|TMM zsRdilpd98%um|`RQZTtqD1) z`A6$$P>MsSf*uHrn;t|%E4cq(?W)J2RSTuXhnaK0?Y~gA)q&a%3eBL{&f+3fkd$?K zrX8GP1MR5u5WO|rg8^{@^I=F z#k)l>JNdI)$rA_xo*x+%i!2OsMJkkTqH8Fu#j$l zY-9bk9M`sTyuW%i%t^t!Ft$!>xQYw)pFd7-@)6ZFzfNxH6BbTgRg{^3pmtag^%DtY)dW2_h&nNQ1o+I*s zp7-SaJ%{CeJ#Wc-dk*0u_SfP0-7k9~qpfPEIwFir60u|w`Dmyd@^G=U{Qrwx2qO7Nk!U+nL6O) z`m$onG)}EkF(Q_bcDAIL4!sUeu%Tk$g)9HV=cdE$_%x^ELXi)tw*dKU?k(?w^R`RU zsBWat6}Beq#fa~R^>z#fkq1^1nRk4X-~_h;8>P8j;A||05(W;ZnjS@bm#c4F$Iu)E zhZ<3zz>WA`0M$RWoBc(6eARB(>;vRS&Wn2t_fnVnJ71HpWg-Q!-CS8T`CfGs0e@eTIsDbvZ%^>9C*KjyHl^% zYqz*$HyPIqSnjhXw^_7JPN~c*Bx$1Cjn()ma+nQ44l^%un8A$S_|mp(@q6nIkC@i> z>1uUD8^csl|D|o$?M@))P6B8WXloMipTdb8*D;TTKmrCz{ zfqYzs^W?$%l9RXU`k$16lQyI?j+J3wq*CrX7~{Cczx@w`{F@o%A`Z}nk~a$~Bo!pJ zE~z~HJ{-#+712&~8^|Zg2uV~t5bZvF?BMSf{9ar%_6(*AkgTpN3Dfoq6#r0*=og@vfIrWgs9J;kvHHUiR&FZQf z;f(Tnq1tt$Tp~XzR0Zv_lBUt-b~&krt3+KU_wmL|t0quYW6cWG=udM|9obOiq1xUq zFUkF#Ml)5kMwV$yCs`MI)De&v^PDwkf5ZWG6+ZJ^7Q|hUOUJ5**T7_t6m;*dku`Oz z(FdI(3WQZzfXqI)C<`J?nk}Z?DEIaszC?LoO(|UBB0iaZl~M&H282ta_pY zaC>a5%1g;9!~cMCrV|%{#~;S31u66kv1c(pos59(1UQ=E6xBH z(&usEQVWK5R*``REaHtf^r|JDaA>}vck@QXi0&LArap{Z#1H#K%fql6K5Xi%U7d)K zci(f$UO`!r>5p?Napoj2*M964RrC`$Lb*)FxkYN)Pvo*GL^cxcXR;8w5^fUu@c0JY z_Ta+xb38*H>Ymu{xHzH@53rmtc&Y{J?Vre!n)~+{77>igztGbp@Dff9_4N+x*t17X zUMr({H(}1iF|LJS=hP)@WkDXnUr_aMSs+~5n z+rxa}qIOTpl&LxEv9`eJ8`k51R13_Se%;Au^S7^a6ZcE#l68vSW~aUF`ZEs>Hs-mz zv0JlXeYvwweM7wV`MTWf2g09lCESMMC<+62S%z~;O}z=Zo`9&A+yqZHcmZ8^6M`(h zb&}eDlUz_^5P&w^gOZcjs0R^FlYEx?R-xQN5-J#%^Rbs{$*EI%OlO83n}+s7H)ATPwKvPCn){y^Ee)|0x9$V~$jVgW#r0a43yT{a zd}c6G19FSIfkzw}gOO^jUZ+gRkVR)OW}P!>;YiO8V;^PzK6vbJ71$&_;e)H3A!Fnx< zs!MKxXJBJQP1!07)x)<)f6<#zLC0W5_K8?o9@Nl!wqiQW$m*3_VDNXfQKcuFbt2-> z+u~L^hg5jctq}idt|Zq6E6~ncWnGc`&PC2Uy`b(ppy#dis}F9KC3Un;22sBjlsS`` zP}U75>%YMTd2vb{@AFK2*tkPYyG>rn{`TJ{YliEvu5_QL1??iR;OK2~Q4w#XB%}#h zb30|fBvikKc$>7`H4s#=96l`}0JIOBB30V}PV;626%?1K>ox;O_}sM_&OXH@mXBB4 z@ben|sb1L(%H;z2mzi_*@jRzQ9+5u}jC% zg%WlFd~$63dwG#6*e;t3ntGVbe+;(={r-*bWRsbY_5nErg3$q$$oElsV%q5 zc_nmYSs(aIyDbn;iGPvV&|%lEhME|78c+p0*&J&Oh z_QC2@7F!Qj-5qkC-gC#-XB&LC6@FO!zBr)D?tnHU+{L8v(w^XmTwGs_ZOI+7uB-yj z_K%0=zal2$6*;=UParkh6}OA!fhW+;kjAB8Gw+b+EolfFd^PW%+CI|pO62c~F1nkH zgtp7r3MpyCz;jc3eFpY!tX4RYNXQ|Kf0R()%bcpeXU`q;yK1MLJcHw^^sN%*4s1=I zEZYn_hQUXcxpT|I+}JMLS`Nx9eKDP69%GkxLIZJv@@aRLd^@8%FT8@l06{->~dr5o>_5mra>%tzdbK~tiV zJ8_)Nx=WUXOjnW4kyd#hda%;e$_yf{ zs0D#bGxo6Y*5ET;1~~Iw?Cr_B=nxfF^LEKfcYfFHf~EuWy7_L<4$L#JN6sqRKsr9V zUm|sj-rqRV$mM!4hbwzP^jC~~a95AK!Ezg!a`&-~JbgF(&NoEVqI+bGX$+z&8q>fc z%W8vwp|PF!=*HA5_sH+MZOywEmH(!Y1}%4zNr?R-!cX}WYN_ERYa z76YCR!cQWr+u)u9DBgZh)=e@_Z`k+P`*eznA!#=!Ps1S3PG%`^aowzGTsGTk-fl37 zp#y5mZZL%(l&WWU1BdbXrt_Km5XSe9QZ?xzgg$(LiN-+B@O;5T@@DS2e?BBHp(FUR zhh>@Gd*0fGLOt^;e1D&MSk}vL+3M)SAn};u(MM#NYJ5cI*KqEfn+tR|)M1tF(z$*} zU=kL4IHKLAu6hLMkA{Bx2+laCp(3{ppR;g>aCYS5>;PXgIM7=SR|uG+~?{jJD4Ys$C@+hB)Ja)?Vi zev?2mK?6VC@wn8`j3~v83q-2gpLZr+0SZ}-_;6xwJth{`ndUf{T`?1YxJQVTb>csA|ZymIm9_ z;7Exr4PJLwYfxRcA4&?aNKfpS&AC@%d}wR<0a-bm<5`ZChvLIt9#qo~$Y`Qkliet+ z;)B??JQ(iQW7nUuacMB#-5TTyoAP8Jitkg~lbDYM-z7_2TOBF92$+)7I6t`Eo*%py zmci${@m#mlj(X=JlpXJwb}{F(&7yxj~_u)5;AhgKS_U=WEX21ZFZkFvmU_zVGDVm+Gs3$ThY~fpRxu0Yqd#?rp)I<>`@q z6%bq!G(iEg0qHD^JDkui_IQU`b(2t#4>+(kfbM(@h+=~IUDs|qxwn|cy`pPTd*+=c zTO1yY%ImkJjdDQ9gJoKX=aFNcAC#7jat*;SmJyfl*+axaO?AUUEqYSsft4)n?h7wm z$@`!Qb*~8LE(g>hDs?;wb_Ykot|tMk6vk$E)#|qJ31}M@_ZY$gH5lnZMc3Kwvh!3; z50(e&y3hSjOgudMyPOxOY^W95Vt!wWPs1V#xx5-^3igrW*YT;0^F{n(J!n2WK9_MH z1_s(zZGdLOgp<^DKc};na}~q6yvx`PDt*yeb>c1G0h)BPU1lrVzf;Y68k0SoG<%NE z+nvD@OI*2z?=E(5zPJ)*e=J@Mf{YJdE%c{RVEl<`5^l~IY{Vxcrd_aDbHmO4V$%;iQ$T0D` zx8KiGv;GT)hx)Dw8mh#iHVoRGumKE&HV8i17{;yO@`WyQ06l-t_plP~IYf{@o7FFSS{Xhj3=inyvdMrgGMyd7SrVYeK&MeO2WiH3~Oq2Ylf z7N&H#%_DI~*OT3g1L<^UU~zhB03;evcPBSgAV21%P63w#GePv_V=!$K?1r*%r2Jk1 zO5%P8_(9&rcwGu5009ngMbKL7CNBIA_+?HAzWz4KxA5JEP!`9b&F>dO~ha!a2*e- zpd{nQy0oiFzt2EH>IW`4#n}42&N;%t+<|s3#b#$a<8B)C>YxJypF7WNe_>Sf%yAF^ z)GdWKmAP!p4}{(rG*MrSD&z!MwkfFco`n~O8(r7})*}uUBn~pjI9RYB`bIo1-=<)5 z`^I2tyH_mIB#<5dP7h37{;Zr+_P>E}9D>%f0L)OhsH_M^^lHSCNpb9IM7F@8zoyV( zsn|~(Cp>b$;1BX}R|M-o_DA9GYe2>DDApf(B<#VecCfBF z^cE@goSY~hs!-29hrs@pF0(RZZ|JsCe~QbI6Aj#F%70KsqXgWgU_T%lRpdJZL`KvB zCi04)nuU6kiR+gJ7k2Z?asuy)pzG$w!_lCw+tzXK?>s2WD){UQuq$LflQ@ECPlGO} z9n7i5K7AMlqupx74YFFu5EFBkxupU$RPUF~viDcO? zW#v$sslI^U5w-C5a;$pbmoiUoIz>JGOF3gQ5ti;4u2hcl*3INvr-LKQU>QK1h&is|XRq^@HUN*)LCYaC9#8`>$P9xwqq~w_*LFV| zyePc{b#`N;Qov#q<0-9>oHjvn+K9X+>p_H}G1Pt-#{+SU`bowbm3Zs~3PZ>a`cUF& zPyYbAS`4ToX87a`GHBa~HNuY~Ir49DNVCx7$LWx8Uau*48j7)lC5N?~aF zfJuYWrH}4W*FP^mml$;&6a00RS3~$9p2D8B4nm3^1UrKaQO?fffZ)Q@a3la1-9^f= z4mZ`<4uxVHt`FJIao3Hxriyv0n9jZx%@stV;^4U|js#fX3PIFUAgop|z{fpm=dU3po&_~8K0#Hi#=c-( zcc13nCu!tQ<9kTd>+d|a9}TK`FUUI04EAAj_CYFqK~6gU&bAj`fHBahd)+(R41+lI zKphNONLmZE`)nAp>VXj4Ex5xi`l6iFV(xH@58UAvtuh3{(!rtFo!Z!FcEjt=>brNi z{SvDk>r5lwc!8%N*eB?8DnuH0-Ko7VLN!cvJ&z1BrQpwaubtwg`xg9b|PO{%+9X{I~Y|=p79gINa zMO-9UZMQ8vPk8)U1^-jypV5GRpDXxiFp3m`{hmjI;k4VqFCEn7bH4uz2W=hxz6VjF zbx+7Vt=R&|#926CO}k?V@1=uH zkky#ncQA9@AMVsbYv)^H>sAl$+QaXShcG3AFpZ-xUwo;9ZG8)EVI&FN7?T_Zp~-`9 zlO`3oII1Jyd}rm4uQnmi5%{{EwzD2#&#UF*7zhWU&Cr{c;0z|Lw3P}r5;%63Q$0z z7FJtFE9ew#SnVSjFI*wFm(S9589HOIf+$!HaPc_2{9@oBLmLg=1K&SGfH1;?VFjMY ztX^^i@ws}Ywn(0MuK zLb^)3ID8#^;?)`;8l3gwg2*Wj@Hh$U!jia=5d zJb*&&Ztbzkm)(iI2*@7A0L*Wk+9AY2cklMr2B@Ah;i!3P$ZK-kIn*BH;4gsUq4*>F zH5`AV@y9gQcS8u-*^@82F@n2#A{*;)ci+Mrz@6QU^>n7I*ekh&!Nq+|h%8rJj0#CYHo$gP83|+fCXqrtkXeO(w+6 z8C;OSu=-vwVBvE>!!9Z_ZW&3b%Xa`YI%E}nJD_9~6dH$$?+(s9g$_Y5yWuQ+7rcpg z!3A-y#L;e76}~3#&a{StqRvicOkm1YnWQE>D>JR5g-|>HDC3DfUGmM0!CDLxiByt^ zwd%qYw)aTi&sGdU3;TPS74XF}x#RG6sc<0cIofeVCo9_m3~LcPz|OWhBaVySI@D`O z!>hqyN{c`ubZer<8nf`NvOWR$7!Ah3?-E;vIgKI*c@5bgf8joClW{g87(^-DR-qjP zF`ia~F!IJ&JPsplpKdUdd9_^nCas7}Z-S=a!|x%KAMZsv1u~=Lh^p!&Rw&Y1E@DfC z6l!A z4wuioyot--kg{otz;UPPObr^b7@^Gi3bl%<{Dl?QtAL|Nxq=imqa zcC3^KJ{&E|cI?IXy|U`?Ju=XDtEberP#*X*%)sDQOYv9LzgY&ZDE8pO6?|GA)9^=@ z`D}noSCe}3PjcLO1qd<1olAgwPv+Gzk%|XlUr3rw*co^H8jXUCd>KBpfdz}51E3CCWVwGMN z9!sx-g4x)^J3)1rb{^TU%cvQLZv{k%(Kb^oi;<(5JraMfVcDj3TB4;>M7_w1ezk~4 z@a*s^y1S<$y;DF|4uJ7(>hvT~Q51O)@U>8Q#lBr|d+W@}D)XT%7iDlN9jF&X8AQg; zp;=`il;K>44P~%ic(}FFkKX|dq!7=AAA@1$9F*td_i)tzDtzut!$_fXnrOwLd|=lo zaaLPMoV8K>6d`Re^;H28XFHHFJEv8C4UhkI=-#X+%sSc8;GZOGlU#%U}2JM$!&?U$5m z@mz0N2Q2{Ggm}(5_Qr^`KFJ98u$W*+I;UW5?5q=O0#2+tJ-A&?NB;kdcW7zhK-`1E z3~Hp1u8xc|`ZSZLdTdWg0!JE;Au9KO2IW4FFuq75BOMTcX!y*rKfe%qGn^sq_$2a~ zr2cUX+&ts?QsAT!mSQ1jjMwI}_`?f9Hk~$cEV4byuFOiYqSXq{piea^oe>n{5W?5# znZKuJK5CcwbOp({G{40_efK$hSEsy*bb5wApn|;>It!yMJsgZdudo?LI50iF;eCl0`7|$t=1$(L4biO<*T8!T zT6*TIx86aj`P`03#m`W(y}hboKi0g$4upqT0pG>qT|FSW+izLFWxUyyZ|Qb~94qGN zOk5!X|74Gl0mhz}3cqizh_o?X5i{UNI)CTz756*uo6nK9`zaXXt3r=^Y_;RCtSEUA z^~Ql`JuI4P`+=)ofzVL=qXhrrc=h&SIVb!V)B#%{_(`{Z{zZ@tz@W&gZq@p(T%Ukp zq$iAH95h0UC2s{w+h2A}T(RDMaVcoX3fKyhK97j&ykHA)Dtdr8i-9<+&}TkcChN5d zO9;c}F-v$I%5TRK!byfpSb-%>Uymg`kPpU1>=(&|UPEH}A&8WF2bSczj-g^2)^s%1 zR0;+Fi<3aYD}XblQZFZ$l^2WGBhLmSfqI;G!vHA`aRyp|&Eoednzerk?KeX_mZ1eJ z;vtEpMY@wvVyd3aQ6Qnc+Xn4u0xoRsIT-v7wj6o+N!V%@91AytN85}ba>%ct&6AN% z9LeePY^E?L&Jgx+G1fQ(KF$+6+z-lnbqATxD&N&2>I^P|@6F&b#3>N6#C08!#BL(h zpE{~)w*aZifmCmZfNu$qY74MDjKz9&!*J(iKfb$yAZJNG?MOotX0k1%Jt^Q9ZiDBG zO$R5zq}Q|4@RECN1Ml&PA;r z8*$cXW_V^KGD0*oA{@}j$R#ziq9P=vA|tahvy#!&%m~OI98)tyMI%DjN?W%KwS2c` zW^L=bmaduWnziPt8JfxWb?);#KtJ9;K7V}M#XI*t_vg9io^$TG=bk&!7yp%s;r%f! z@!Qn>Q9b&~iEsq1XAhJMY^F{9v$+%2&i3K6Z^hv!?PheOGGTQPa zw^b+S4cr>m0!0w(?rp}E?S~v)RT*sgVe_Dbu?Ve?3W4V;)U{ej9Q@z>RSxa`fUZ>p zT3cTWoM>HtQEO{nz!&Q2_(y9W_(Mz%Fo=6;H=AqS3u3Au^7PNvJij^Vj2;4^8Pf(O z55l$ZjcgKtGKx5(52T43u`e5nNm$YcJ7! z{Zh4ZRuLtrHOqvCPoLP%k!=mxMQiBNFNrn5`9jG;%QJ4;8y(CRTig_0LON;gl;2UUbam$|N`$tl}}N)A8j<=%sBD zHny|y$+l{3+E1|eVaNj&(*l0!nBU{@t;xc?D~AJtq}i4wi$|!8;Zss`rt{`hwdUW>0R&5BD@;Lfe zi+-^qi6YLz-$!2xoYhCBjIC$svJTfcbr>M<-8&ZYSqNt!^E1;t<4?D4>4`QiqpCa3 z1B;hpx48&A&1&pC>$T=yf$%|)T2tFmZ^;G=up9mFWdnZh%n1gas5N>;= z=IRD@#q4fgC<(j(k={i>mJp^ak(3R_Bib1cWWUkalR{!fS;xF$t9~pbV>I+_Iv<5j zc+YRJFk11R4+yjz8~!>}e3PNM24oDtdQOGy(Qw!vnLTNXu&Q>*yXBJCg z1UhSIe}MAf^(uhBlIb`Qs#15|JaGh=jc?El%@<14_>u}F4CTNkFf6PGMrbhSYgWvs zJg}|^C=JdOj|}TVx&-=s+d2+5`Tc@1+Exn8(R8*`0VF{0->90E2JQr%g2|nlAv%q0 z6P>mJpt|=3Fqv^bBdJP^3a4T!Oe*@CB}QS5UjuaqZkgkQ3nAtj>X21cnCq(E+UhDc zL}YwjiAFgz6+^GCH4PLsopxT(C-SAQtXAEfdlV{)L0awFKoU#*wfblMISyl!=n#mg zw+BmBJ*?}6T?Ux%(JMc*Xgx972S)2?nwUw8TX7eL5rQSK$|gG2swW#e-?I-I`H<2S z-fc?&Q{(aeMB2Sf^uUIEVZKPv`}JQ~CQc%tVu$eS(fw(*L!@zhlS8EF5A~;G4pFFI z+n@4uk*q)3pUS3*1X`erq59DeX`e1Uy4*=;bdjOo(x1|V@W<`!?}+ZLX-yom!fj`N zDiQcz)t~AFDn;d=3Ngd;HNNvs!45-O5%&5;jbEb?DbhJ#y%fjw)9+DDgqW=_8B7V0 z!p&iNr0~ankl}!SGZyw27b>qxr;bTRacq6 zvGqp(nRE{grh6-vfy8adcuaJ^FdvdN2l^;W(m11UEe0Qw+`%K$b2?3n6~!DLiWN=z-_mJbSCL!J{ea6U$78?f$M?^* z1Cs$c0K|JJh_gLmp$EF7&_Q&;C!u}PlsrFsOhKHJwX6#j|-TxWU#r2y%v^mz5l@@njPxR?b$Qch@M?57S>Bv6`aX`vkZw#3! zFSSF4r- z^fz)S-2>Wz_#_X|_KmCPj7Mxv_}ZGKj%dZ|mcXKB?f6w2KmDDEbm*65(7xWH$#W+< z$8`CF-+f&fSX8S8!eNL)Z&r=Vpt_62_JqkDI$$sUL|2YJR6AVRkU_H(M9@=@I#^EO z4lk=~9}GISJwX)a*yVoT0k?Tg@nG4zD1ZOpcP34k(V;wl2P#(HIDkqn5oJtR_FN*; zN;to5!pOL91J7=U`%%_`XmLF}HvZ1S(RrY@zPyL(uV_R2cH(B1r!uc_m#Z?hUWe3{ zS4N*zJ1H%7fC;lq_;0Jdnt!`sv(WzAw`ii?9PQG4>$bo>?*E?Nv3vkBZ-+9M{jA~+ zAB9!B4Q5L3NJb+>o!Fpf4nsO7xq&S->9#$BDjbAqHNECC&a63+@$2mOT znqV;S9B7y07Z7i6|JQ&p0qw>Xd{5BMx3#v1R*SwF;4a`y1p(nnFb}1?ID~qksV{T} zsoM0W0>wGw4CI*?5G8JobGj|#zyxnC#D>_Qn9AbA+#m?Es>jO?VX`ZIke>!0=qssT7E~z~d%mOUBXk&ZuZx)=HtT@5xSc}UVT810P`M8&5w%*#^ zl?PiBd;G*2v7`|=MDywjzWB~J2Y9*}zEF@fZ9yC7k&o>TUD{X`XA$T`>pLWpL^tk? zI|+0T;j|>NDLMNZIDAi)s3H=Mg+omt{IQ+jeb=A^Xb4X}Lb{Kryzd$+zEl)@u7xle z@M=&iUxXuv?oB@`%QBDf#O8Ru;+!T@L(7(x|Jy$>? zMFu?;7>h8&3MW}%gB2b!!;PMPBEjLAG1!DaEWG~n4N9N4L=j0;Fe(ik>-H?#kOnDr z+h979h8EWjrj-8RKuL$Fl*1E)slGpYc4#o2>kkRKZZLTU09~QuR-1s1H#lvGqOMT` zP^L?W>Ia~WF(GnZ35hl$d*i$-#UzLRMrdQ?KvC+@y&+Yb9uN6)5L*9gHSHNB_HexE zD)8_#FK>*^5RrO<`>RWp%(VB=Wrl;#x`0u5kZkbIdVA zHCy#)4(VB9lJ3f($ytExi{RKD$+$q{I){AU3^gZ;*X{s_QoL@V zQG%@*7+II>#&D-@@Tu4hijQn99M=shf^4W7U`O8td#gFw*t2=D2Qy83LV^!DA}TrU z4p_uo!6tyrS_f!pLEHzAy-B#^_(Lnw2RMu%uM>EANWTeUJ;FkK=Wy~(BAFhUCgQ1j zq=;5WBwCmLxS&*|?)(Jigq5~KcHx`LA@<|3X(VLIb3r zTYi$kmh%xodq@CwQ%JaiXIs0`4WmV;l&&wEuATV4H6NQXU|zI{U?9xqr>5sDO(^rz z8KVIQIE!c)EmE9gt_4k66fz%K0Ug0TxPqIp;^JkE%cj0LpSjQ-$Jisx+FduT!^yzSZhq|Hv2`Oq&yC$gU9GgIC zFz|C~j_$B4#kOI;zm_4(mF-_h1!F``K4)fMXPl7>uq{cZ=%)66mkh_-lp73#z3Ty5 z+CdDGL#`tPD@y*3vm|Iw%%}6@emXWrl*WGc72kjHLStI1!49_g7Li=wdfxW(s4)=K zESnt%0iFYFc1UGx&i%3b!f*UMo@=(5sdo!9pi1DPiV-owdg=l7AGt+jB&S;W9JuS| z(53^kXICd74P`W(bHUI8?U)FhcdH1F$;3ClT+QjcBGO;PnI>~doc8kg#?}Umm>LGG z7j|MH9GBow4e7tbt0#6kuvJY4r|*MtdrS>&N8N=#A48~Z3_M1&59XrcvGv!rI@Xyd zLm%$6G*H~;qr=oVHJb}tWi>3gyxT;O2HgcehcTl>bfh(#o>5p-fJXW#k>cVRk^6ns z$*A!x(--e&dXF4-V>GeGw6*T~a;icS2M~|n*Wl+h_=*wFFVDyOc|3Oe1vb;@LBq=~ zw~2&ERz}M%bnZ5hmiL=aXr!7j?1bktOdQelZ5Zp1=!xAk`+lV@{VdMKt1==jpWnOE zx{R&t0k>kRFX6D7kFMMBtV!6gNJ&4;uW14M)C;hHb@ahJhA_XHuG)iBD+k<~p}%Je zAVo2R*u=|aFTVH?V9gN1D>EV?mKV|h$2iofHziz0lW!L(W|=v)n)}yfHs3DNdNUQ_ z#mX1XQO?M|j?WgowHGi(w$mo>9U?~mW+{ zpQf-uof?}@lQR`+r|WS>cSr4t_I?%V{f5!jyP#E4bpj4JzgIA|>U0;0JojzG?73j) zRAVt0V@3L}k+6N*&W#lb zL7tM!26byAa7@7Y8N~LXd-dIDt5_t*Mekox(ZxE>=3&R|r>VtIqDAjtj=LTLXoI)Y z_tS=Ak&e8n72vZTMrJGT1=Nwy?RpbLa6D^hkf5PDhs}i}$YrB};&+4N0p(}hjqT0# zR^E;Tx&QTa;cm!KkD^cGAb%n(94Fj#uCd<(Dn(JxIcu0*%Up!tUdxr0ygvV~L1++qH zrtZQ_-G#>`hwrBYmpoV2t+chcs2gp-H3;~tUxD=L-4#Vjhd zaGl_ucQP#+ek*IJ-neZ1B71>9exgCm6(3{%gnIFMZpGp`;r zahm9TE0;q`Eqn5f-tVvMYTLeq!wOC@`+xhI6Jsprx2`#<@v_e%hHb?GZ!Hxa)V*}a zeZojFImBd~!#8_)E%H&C?2?L!?}2rD5x7A&n9Ic}&W>R<-6z~0dsvy`qMy+maEJr< ziAiI=+XBZ@mXyF#$Qo+y64MoaUn~xB_T8>^*;09hHC<-Aa3tnnZq(B-X=EC-U9aPi z#*w*rGu4-3(fk0g!Jwh9DXxKD4hlgIFVamKO2{}7v%cc?bignh|SVM$dcXycw>s=cpJ6perU(iZs4{H_w{TeX zGSxp|%H|1Wpj2PpKm}#kw_^=dmI;4?jU<;yW*)V=DW5k3D9r+hw5v?Gi%n)#i^(%> zYqa5b{t;VOY+tL`)`qvSJh@t%Jnz-#|4g3u>b5w|@OZ!808?FsV*|b*y1MF;;gtKJ zDDxb9ki`qk2J##{X;g5yVl&^rr(F*UPb9C4>H~DH}?>h>lGLa2DYT3l~I)J%J;0DVzzI6$R9wXrv>i zc(tKAtvYwW+@l0H83um~qvL#YHd6umoj&HkeQo|ZZ256M_97q#V1;eBZ0VMVL?EvE zfUla3j-tRLlD7s{*me*5(&fB-^hH7H!)$iFFXQE(+eJCLer~98eY9$@H@)` z)4S*dH==2QO70wUjyJ6|D!LOkX{nP$rjCgzn97!~&Sae{D9pI=hl5?AWvI?S-}FDfa**oPWXZ%^?M9LmJ+HsWj(? z{gs#=$(ykMV@KYqudvnNzA^v|?F~@au(hXh#Pme}uGVVnGPRY^j0VcpHhVAyU_PUO`9}cLtdsH|g}zC-fd{Tw_%#`2%A>2=0d7w)vnX!2BYtt) zSdqB(HUuj(u@Pb-4plY)xS%}fH^b)=jM30u4<-U$OkKFooX$YE03KxEa~i`O@E)fC z&cNry|383^9WgC=L|h!3f)#X;zOn8z$BL#~ZlwCjA}1~ky2cNODYSuquS3J>++-0< z*n4sk%LH)nWXb~z4)imCdWva*#%4VxlGFE|9NWatu{C$&=h(6#H|UuQ4*XhyH3uW* z)ymsGY>SsYh9fA{5jzDE^nz8CF$L1C4J8e9?~Z($JOyWmSeDzTK%PV1$j6~4M!f8C z#Q&5}O^<^zAbtDe(4wx)r!$X>0{%W~sz`I2n;OsrYZ=B|7#Bp|#Z#e?M4s3uM0#5u zj8QFhD^I}_Vrm3?Ro7^Fz205sB0s^T zR$$VQv)sC)j+gdsh2@b!8c7XL3HW^iy=Tg`=tlbVDQGwko}>gAKBRqdlCdnuKnN@g zRQAw)^Z4DL8|uell>~!^)(5t;b!&Ofc=^A5BO6TkRe#{O0u;2HVb2@JTR#n2QH=li zG}s_;p93mScf+sM!Ut9@jw+j>0&463q;l|FvBwR2d8GhkL8ap=^6IYOzgl~!Z;M9QR64?<(q6rC1eH!lyJ&CO zbm19evkR;wROHCyF>i$*0~6N2=AE4WOFqjyG#a?c*KVp;4Qbqa5?=3tKY({|2Ji`O zmCV2x0IS&?XS|vLxd+BH$7TpmvW=U)wV}aVFRTi}GQDjD-+{D6NV{F7QOeUeH?~zE z+!uy7Uu%rQ8nE!gIa6en?>%{sBH`7zQ`;GR`{HB{Z2jtRcf=&;y|FIUn#7vHI>YTd zQ_W(IuU)P3{OU9#to5m!Y26cKPRHv^m9^9>Tpqp$_3yDanFCcC>gWl<1LwVZe+VSq zP=TqViCj$wWpuutm^ zA5Hv?%k4as3h_+O`~YrP=ZFOV9Mr>{_k&YI8mHkmZ>%4_iEj{9`&uEC=y>sgT= z8*`N1Y?$smahx^dS%|`2meBTRK?=~%xVc!i*NmcqxuPsK8*XOUXE9zx%RF%d?U@TB zk3Wy1l;<#8Cr48rheu{n$#WthdG{$LnPGq9XyX1IJ!u$AT{=|Kuzl{TYeO{wSV)FN z({myi``)RsxXkGQGjlZ!yonAyhj#HTxdMks=vQS0mI~ta6=-v3Jzb~}=?O30ghQ1< zV>Q4}SsqL6LuGjksCmV{>C2Kd$^ae6=)xU zKHyR;<||&S$9I3@M${XlEmz|1w9#06fMlsdXDC~Ol>r;^Gs~SUL4MG|D?y!(kj@)03Fv?0S(_C4PWOXBgMQ{ ztm)vC;D~-*nZse)JQixR7J>ywmJ0ydtZLyIqBId$v8KG>)$az%^W~g$)ik!vn0mFU zqeGf`fmb&|);G?BV<~l!i2hhDI`hKVUX2BUg>k$Z(#_Mzark8a3O-Y1U^JgHD#F!0 zv~Jos7+zl+G;eOz!XDjRe33Yo;a+my!zTk3+$+^Xh?JbhfZ#foT0LN3GVsAim5#+1 zJqh%_U!#N*G~^$GO8TqWY(CY$v8z(H;0yz+{#(o%6ROv(inwBC793}9R+=^rht3Z* zeR~sq`T|z=vhOg{N5zx4^r{`LDh9E<7W)!-kvp1$yuJlzZl;2Hz}UZyrp@zUOY=7d zV>qn+9U*s6j5oudM$?6P!06V|9di4P+05)mEtM#-Au>kiv}mh zxjf>_7GRUgamGHx_bfnr2s0Lngi-DJ%++szx+DVIlwGcN_aNWRz*<+L_RD<2a(`6c z)m)+c{TS|cpx%as5Q6mCbZ#N9rkg3b1|d+bpay~r;=5{qdZ%xulN|r{1eK6T@LV~@ z5&f@L_FKRjS7ru9uy)n-tTD8VL{`_VF?^3K%HVb|x%BZ}P)$WI662Ej3ttBNs&!=$ z>m!KE&_O%x2c!)}+Ax(yHH*;l`Z2U=krEfyJZwl)G+Yk?ryjfuC=zQw}Rmd=k+TIrEXFr3$CQ}Pl~8f^k5ENZ5TBvVtj zYY8-U_`Hv^0qz%;hzuv=%P^M&94U6G^w1qkVFiasAK?j$uaaGJJBx8E`|$GGeadGD?MhsPs8u>MJI^n}`O{6T zWZo1$iSt*j8|+A6MUF>SfKf{K9cASKT@SWc=dpG@A5C}kRA|jhYEjkXidS#NzrtJ1iH@=E zPTp?C@l08qriDQx9u8iQwAR))gDb4EB~~6g9%bogxjiNu6MpgC?gzrIszbRJG=Ic& z*NRbY?o`Za!&qY#WUh1cBpjZDT!T{@M^DzGBcVpTDOwGqM>XJ9pa$Z#JR^2_)Yq_F z1hWR9{s8C{e`5%9-F4R5W$qe`&&ErxxGy*7I&B666&oM!6PICJse?@`ODVi5a)2l= zz!OFaJRe1cLs}076t50c z!2dNZ8V@Gf1LnL>;*}eFz#QkkaGVFoaeIBYg4kVm8*N?zn>{z`d*&U5tG$2X7RUz@@?jvdkmvD9%#~f1dJ)r12-an zTs8w{WtH;MR~^#qI>3IJUeGtIL>C?Y{Lig|vf75mpHV48BN2Ob{>=@<>$Pj)3@E)G z0w8yqq1HoRS%m?W)Bk*h7uv$DM4cG6diWOpBhVzrn#-aBKji9z(agL3dq zzx#G7TMdBRef!4B)xz!YtT}1g7;pz-w3$^lpW9MEo7bSgg#tRZ2K)>0!dJ}r+*iO? zk$xOuTjyZgo@q>>!UmCWaYu+j%kMID$Gu0bq{ivYDP2g+P1a76!r ztHP+W;8mQuGwqI3w3}xjq*2i^yhwOrnT%It{SK3??|KzN_1oAhHj3YMN)4-hz)lr~ z9IObvhr*3QM!HT1*jR9GBf9N6O~)HW(9MJ#dS*yktfhjN4|Lyqnv!3GkdLtJH5`r_ zrxRHu(pFntZ&G2LF;km?%P;IAZ#}O5tcN4Qp(ouZaRDGeh>Qg-!&UL}|H6 zl1%xmBhp1;=u8Uk=9LB!fLEQ3FeXU~=|5U++aR^MC8x9h%U_mff0gj?g^M4n|2tm}w)^*}4hraPn%2+d>y;`d+(&QH?W?Oq4W)r#=LpsvNvJEP3Jd=zV5_g#sy9P@lM{c z-PD(9b8;bNZ3Hq;DuyF*;dXv=rwI zUrZB@;jnMtKOIhrc}w(2G28L#IfdNLsJ_v>bF9ye5P zkv~cG4gT9uhSM*DhgY7QU#9b}n(IqNeGyiDsU7MIKy8k`C;U+)4R}obuiQm!eKM9YNcR8XdoGarg^+ks>epQ$`61yAx07o2#g=!=3q62?I zRX`}`+ae=4^)3tz+Yj52jfZKIrW;Tp&=4GkO5Zlmr<&e|evRv#g6?ZFE!|n|2T@nA zaUFSEOjfUOaeD);_HJ21W$#!T=W47~W&k+ns(A-}K)CfCG1b4_Y^SMh=|JoNr)oT9 zwSVd^D%>o}(iXko14n}&&lhqK!l^IJUtoLHG@^Q};YG-#(N*okhSuEhW21awvv5aO zStw1-M;u3{ulg3ltyQ0+a7x56s)dq@8Of%yeHm9}PAGjFQZB!H#K05oZ7#Mqv z%FeqyaFJrlYKF%4_bP2vEko`P#Wb}U>KYTNLi(&CD%gTG2a2_L3l`A+akL3>k2!%M zeS=k>(3&lzqPVSMRPe;M`ySMW0jPRL#e7fRt&B*@De^s-jCLK1GO4v%&5goM#qWtt zY4*Ul_8k6->14{)N!40p)f&>Dwrb58OZo2u0HtH8@_n#Tdx~$w6u&f)u} zf^^KRukUHU!FK}VqnQD5Dm4#OpAA*lW^hr z=?pdgL=J_N^j{%+EDLU+=L@2rWr%KFZ9<=r(t(3JISy_TN#QQT4D zi{MFG?OGn_VTe1t;YC^>TbJvG_sc102Y#peumsFr`9YU@@I>vl`Y;8`MyNxZcr`Jd z8Lvgqik+fcx={LuNDN|SrA?r0zB3I2$8@xbx%L{Fb~hc{iGu|5y9fTcV!jy8XSYSxXT1D`-lT=W8s`V@-Dl5y1VDXuHFj-!mfh?J3xoD2$x z67Vu(?kK$0f*Pc`D$loSiqh~Lg<9}x0={u>KGpl6{^#*jj?=$if*7;=FT%@g?c`rz z9}g)*4sBd$b-=5&Z@^Og zW_*mD<1@neEi@l+-%RWKvd@@k{r>J%AMSLR>A$b>8TVShzisdtxE3DsvHz{Q=&ew*O(A>RHf!$SNqTO93v~@3tM%`G_|Bkt;D52uNi}m_< zCFI@*zWIC^6>zwujF#<#mE0+--1i6rCyD?Whw&cT_`ZaW?-TQ4v+ptMW#4K;@1e@i zfr<#veGaR*9P8Uqg#10{9!mcL*GS9nq2pfwUI=6N1AeQ^D1E=EVLj-s{SZCn96Gii z=vi7yu?H}Q-S<-I0cdDq=g_7D!sGc7_3>I}#c(ZX3M+lV1{<$IpAJCX_TxR2@DGt( zes&dJf-H~9ysR2-dYD~6uI4?{w^llgLi-xgY1-wM*kxe5&iR~6fD6bAh5@Z_k;X~hzApCv{Y1Ig=Z(JX@!}OsQxAA|%Jl_C%g^TX=U;hx<`J?MlFzj{M1;O}@&fzPy z`|e|Zzw6cxzz6HC#&PU8TfV;jX4OwVDhFO#3Uiy zd=Tm}JMU%DBlO+4PYXaCI|zV9+)o)_0??!DsNhRjXg03kIspg_NDT(Z<_L4}7fQE%Q)Qx5^)b04IJLul{T z2kG1)^bhfbuSAO5!C?q(r)y~T zVUdt=1Z{9Vi*O2r1yaSoxh_uUx^z5prbAehb8rw2Wa96*@?v0o2M&vjF-PhFH|TSi z0bRkrdGgy}cu-ex~67#6ZvgW|Nt6_oc4 zDEcM$)8=nPamt|87GRL45r8#)g7~VJGLY7!{sKsp|1A&=$XN0%Rz2FT{1%i5;jV81 z2p~`1zcCgdb?Lu>T!_y`++!nAV(K3N;AULcgEWuThVPrL{JaR<_9B2o6y<)xS_rYn zK|zk(Po>9EH^RBc0YjBHbWr63LT)+^uEgzsXSM$w?ro_&5Jq_ca+_>%5z;nP@mui7 zTlXDqKAU|5?s$)8a6WSZuQnGi%G!$&dD|8#_s>dCIDz(VoUk$L1opNmHJAj@F|fC? ze%0D55G`H>C8(=fXQc;Z0?e8(xVOuyj)Dm@M0-|_&>I}H{FU~fN-vrB|=--9$G-0?lqFDs)9--~+v>;qJP z28f09LuX8i6a2wsA0eIXlcm9)ax=1U7&>c zgWE0MUO5=BaJj>i8QTK!t>Yw`K!SJlYTPesr$n{u5+2)qyyPBI{zM)^W(UMFE&#DB@+<)I zP#NW&#T}$s_{NiGSID6o`RtI(w!#d}j@$VIaLEt>K3zOvxbZudibh{h3_>{Y|agfWEvK9Px{=|j*WTB47kTV=o zohKDI{2ct|UQujwK6otHDy@T(u8^+KjNlkmO0z63q% zA#zITcV}7YI?8e#Oq$SUj+A-&$%#}cWvO2F5S^2gQA)J* z=s!Ni6&HBSdS*15pa^LuD2lY@57XvoiMRM*(-ST8_4Sh|qmxW0N?$vPE_9NUcxyDtDKng(Babzu8=Pq8 zwlE!X%EBO@kB5`uwB43%vF8ypUkG*KVyHq7)KSt|ra9dT1`5sUI?IeWze$gY893#`6!`jQ2#>imwHHfn-EE9?EZ2!7BMyBWwJWR)9 z<6PA2#YP8RyWU zDRg&}b$SEvvYT9{fA9zu#K~Oe7Z$jIuMKf>vhyb^{>@{Q++F55vmb4nwrSk~gjtW$ zp6+r~+=i)GL7`gxo5z3->q5NpGkRbZE`F3|_mCyd2UWQQUY%!vxlL|4+sW-q+~MbXd&+eG;kI(< zpBF1V`acjTSnE3i5Bb5J{A8-@3BVx#uAVZB!`OJ~)-xwlFdk^TWHL>S$GV+0nKsAE zX&h$tlB1lX+v=soy|6sKf0B;%Lix^slLz&6eT>F{dY25pZ6G*{Z39zVS$ffrAT?$l9K0Oo75&H zYY_Y}evo<0qr0~ZwrL3&G5tW8rS+8GTY54~sab22rrBYvoo3H1m$BQ_!Z5|xxNlU~ zTc+jMdb{>|xo_Exw%}_^<}UWy?S&+dEndeo?%&10!{vaJ15bVb5Ad6JF<|h{W3=O9 znU!w$hg}J`?G*l77_{MNR)Q=@*ktZ1)R?cpE-hRhk@*IbTJ#V7*^mH!X!Sz}M>?4x z^UMEV+Jnjj=A|L9G#&3NrfZGx2d2D`tF1FkgSh=)^tA;?R_1(}eGNgNfj`D7AIifE zIK6v`d{J+Fj|#kUn)BW%j8v-|OQ+BwFOc<#DHQa{{J5n^hs#2+F=lgN-l5F)fd;=Y zh0gh8mggOO55$MzZ^EmGqZ6Q<-4levNByBGG|Ddv^dXN^lOIfI_~UfQFOT~_QSCTh zdlr(0Z&80oSi7fh9Q^R#HPIMwMOR1W<&V?JMC8wZoPq`x!N|vHlmRL@@^N!9bEx+3 z&}9Hw9sK)>da5yGRska!6NGRyl&))FPK182afaIRt?P))*6s*S=@`j4uL5ALm50a5 zch6a0fO6RJqy%KK`<Gf4Ma$}2sE(k(^JPCaudvTuK zzs{4_2W@rCbM&|KBwsd_5uylYQY^kS14iZo{f+$GF17TLM!5lOI)Mu9PH`byaRPRv zm})Lp3}q_v0Dxx2JZM#}8o=R6;O|Oi!u1ffMQv`Q6f8%XI*x-npBrc=9MdkeZta6R z9JC}!UgWW%V^`8c+q{-H=ik@TjwC?n9Xv#q1k#Hz@>0aHH%-1&7L?m0g~^mjO3Y52 z=2-(){}x&LzmP&!a`3VYyWCikKBauv+xn~JcnL<`k$y7xLmDpuMqi+~KUp?86P_?p z9CUqOvdnS*2xzhiA>zTyWL~g8zV}u94xI*yG7pLZ4+8PiI+U%sOy(zyW@*&Qv!j*s z@clm?e0QV&6QuWrG@JhfmGs3@Ui}2^>kHwe_6c%cE|c>Z$m`Th14t*VrSTSKu8kKo zi$Kyr#SfCSjcZxp7=UY9{N0?WXnENE1Ns)DJyrwtW>cDFfxUc5DK42I4Ku2El^^%&qZ5?ZPv%>6 zs~;|QW4$lJq@rFh>!yA(!6R8$I;RK@Ig7ML)Wz;v%>mI7)Xys@E={JDC_wY;A$V?< z%PX1++qDizVF?Ha;fQHn8|`8hs2U3t2<>^jW*VDGf)<)OT8-&sLz?s?Gg)C~h9heQ z@L#W9)CEE0^iy;^P0n@pc&csT%;+!k+ykum_SWK{`THVXM2Gr=-VS^Uet0mhbx%?5 z04%bb+RM%xAj_N++vCnFAq=iWJTM2y{s=9;Qf^lWcd*@<3VRG^VwUeaApqm@>qQ+b}bar}%xF1mlJ<=gn?3^K@gF zj2R@Sy1%o^;EsafT+|;zh9h&>4|HUZ%s#&y1s;q33+}&u4fcrT?Rj&rktv>eR{XN) zQ~FC#X|v$j8@nddQF)ClmcxFy)Sys??CpNr%Bh&yoLZb5G;>daS#mPBxGW3Y))9t} zC7DoSL&=12g3$vC?|3-RGix?S@&B5}kmAvEJ=Tn54VDE?ZX@wTG54izFt$e!D~}JB z!SpCKyV$vD%hTZhU9B8E+^$aG?Ac?|$ryki06d>anw;3lKGK*Qa7}PwuaIh_ zCb9<`HSOs}EygtE{@^x=g)$XaSIO1)zNwV%nq^0i`6$eey!Y)eDq{CPCe5UoVnb+ zE#?iflV}IO8;_nGVQ~5cz#~`s6aLeo{Q$%PreNo0^Qww64o;`o>!knM&JgeKZ5aYX zq9R2#T3g2&_qOm$fCs@DOw$0)bM$0LS+SY*`zqi8LFGSsa;V{;Zh7)|RDGT7p~L95 z{yMC}4`Ben<*&J&g2OOJzn?(`!=yj`b=AJ6-Jy*Pnr4$;9cIcZFCMn3e8@6l#np}` z(~8Zje>j`g50e`b_E=@@y6uqrggKafJ?3}7?X>H9P}|y>bnbdt+;__ia}I2aX1h*^ zCr!2y@kvE#q(O~&a@bX@5ty zJzV;;I6kq(SCJU@>4_&m2_d6_#S0#?26Y_Xp~vEN8}ptvfi`BQBl+>{Af#`JkdgS{Tq-(G0G+Zkm^>NY_DF{t1=cWQox$iC>m zz-mWFojDaN(Qabc53vER)uER2MQ?$8Rb#w0-lrj>Op8Lu!$L7wYc_U>?> z4r$(5H2W@ysvL8Qi@O?!m=W$vF!Q~PZ$e&yM7i;9xFHNhiCC688=OwCnSw1 zmKFN(S#+Qn!^pmz$SmwJttJ(J%AEoS3;)0hmBBp={{AH)J`YD*9fDT((@^nbj} zx}q~)>YUP&i&ub}6|J1oG6EL9>^QBR7BjRt&$-rSDxWJZb~11nEvB^4@$oXK7$kms zO4@(tmdGqSeQt@It9zay_dT*n|E!+2-vj-eeY@z{XXxBLvfjgSMu%sgF(;rsPMhwP zW3t|V&3w6Y?`r|$6s|?H00K{2YJIXG(0h4%I&9F@VC;T|TPXiN8SJwjbq~74Z!r4t zH$K=Il#mFWC0?t+HCIiW)f|E6>mMwD)$bek$$0&PXK2@bGEI#HwM3ReSo%)&utN=~ z(X4^;OCflH%x^$G{f#+vq!hb0-yHJak1zrD1^0s$51m8n@0YpB*>gZGii#XjT8_H8 z_-*SDb#qarFDi1V@LmqdKLITJsX0_O0gE1&f@e>VQ#sThfY`NS4o!UkDCVuCj53)J z*MhfS1K3!!Up=7GGPz6dxrQgF%_3 z83RUuM6)Fd{+G$;->GNG|FBFMwfQ*{^z2UZ{-{GZN%Rf6EdCbF=~}M);aH&*ivc=9>g z^DtNuI(g<{oQ6D$@15ff^al^x;&(Foco*0}4BFurj-GvvvL2D1F?JiAZ#42%Hv~Zy zb71uZDc*%AmXJ3U<+wh^Ydk=r%G+stAqrMb^?8~`}vR6RQ9NJ$9C-h z$meL@qtH6Oxq|jQDsyAm^-g)#4q;XTl@9?^C~; z8`>~grbKa_&iDX*{;=$o-V?8Xu`t%Lg6N+Om=9f}6p-72OBs*Jk_5YRUIu}ajuSX--e zyW9tmkv2r>y6s9#v;b1-cm-i)mDBqkeKkb}3fBD(*w8~;0e)>hY4FPbF<)I=c!?uoWV!jDdnKlmp^YZ>?@w9;&M4EnrFZTA%jvIWe?h2 zE@2w|8WYv!Fvb;`e}DCEqwsk;Sq{BIsF9MxSlwN>v0@&_xS9ktF|;}7s4y%&?m|@E zb({Ipiys__f1_+=Sh}<9Z+LdHud@TD+UYz&&!PO%b7pz|{j&A@^B?I@Smp-LsH>$R zjmt1|+1vU~A47W=HnaR>Y#0LQsy^Vq`O{^S|1;$AC4oX*0bh0Vd5Ex^s|K%yJ_q@# zx>r)d40&8X@gv1PEhj;iGq2q(z^*6+q~`iLZ4q`nu=iCAh~s-(f=bB!qJ`E!ZLyN2 zEp+T@*$`Y-Y4&Sd*G^iX+3WbW6{g|Cyjr1^&gGGJ(@Yt3bfzz7%AR^{3p^r#3-zty zqU>#dy0Juq>wByDIW3ek3$qIbo_jpJqrQ~!tIFXaFr|3<=QI7>1NY*8!CQI zf2%_}Ub=2GQ=JKUobT!5IWc`_3k7FGaJsmPnr6$qaxTZ;6M;h!+ChkP%hmp1rHZSrBSp1)i!?rp>%TVz9#n7CrWid`N$$ijF;l&Bg5i z3O^ydt*euDaE`zTuNoMDmOKa2--IeSUBD7}x{`Lyk)z_D2ArL#UqXl<5AX%6IS!qo zMy@U5(r(dEZvaWQT@e8UsP6*^PI?xHJEzb{U`-JWY209OJu4YGT3hD@fgn4c1=53M z>2txCPgT*>xpKCG(1Mm!>js{t+sMKQ>^U%dj)JCF;xLs`q&U7R1#aKO`xjt=HPxDlDoaJII$emwTzwRpvD-CE_@#tW-b zhl%M>!S{*B3#Q!3l`Vc1zSTk*71C2;cHF?N-EdpSS>pe!B~W4eDbQWneIq)oa_@P* zO1uu%#JXz#i~4aDm%(%d0|U1kr39|Mu#WC-)mc?dn<}7BU@rp5+X(ScSyXeId+Ys; z0bEr9K|6;$aa#Zf3{b=Ev9%Xtr*iQ;N_k%9mV3{e4u?7IUTXz2Oxz9a>BUw$jtu5y zK&JS6>sU2eTxXyfGBf@zXk*h-l!$3=A9?(|!-KQw>U3-JOjz3vjzc)M2$d_~@Q2^M z08?N9zyyw(MN9zu_{oQS&g{UPg1pvL$@F}#jNPdGo=2`KbY$}HIq1xZ|G0vje`iZ7 zI#yQ&bwYJt97}c6QWcqI^Q9y(P|mZmC15nnBTqFffH?k^8PBO!Y7AcRG^tun!UjZu zlh85b%j>K~V(PY6_)>HAz_2x1qBa+gD0jwyV(`}`ldYCI9AaK5yfJ`Es*E*XM6swvY{QE}Bp4Yp^{Uja5z1mg4@#G{Uy&=J`}Z*iS&z$*fU9)*8cW09Pwe}Os{VUG^xx_OZ- zNIqg^ZEejlKEY$;?APf@D~*50s{0?(*gT(dUX=TEgb9mfn&sJ{NaL$VJ!ryW*_XSy zc(I(FYVV7$TmZy67}EMeN5$A|$L)PFe}fu8u;*yK??NhB0+w^-0y?ln2G!l+6Qh2Jo^xpg*#Za%-VBLAb0IfJV5z7JF)hDa)n5H*4q;!xD%z zq7m9PIKX@tyjL1)7vkpSIw8FJzJ*k|9J}q;7SfL8K<3VxE7NsWhl?vyS8x|@VkbaB zf<>5wDaz~&0@fMNDZ9>6LY-W!f4h)2)nT_CsG-Oe=s|W3rK|u#-&R8T8+V8NwgU0 zxmPmM!3Q6Jl*;-(Ub#0AZxepNj$b!Q@S8mzVm<%nZ!-@$*FaN->k8A>$YpVA@N>d# zgHH!wU%|SRM4MlcX*A^(+55lE1c)G0uKP9~2r6-CxsZ(jEztMwfPRAo{RSKsn$Y(` zK3hH?*0hSNViu~R7eT;x;W5@lRMP+)>juX}4Kh8t3-(8RLeA?v(5t#xV^4Idp)mIr z3wTpFku_;NtLYK1i9?NCHuJEMKN8s_eT7>6^)oj^y`!OCK z@1I_z*_&lT+U6JA^V(AIFZ1$sCGHDu_6#Q5u*WjZZ-SH^pGslkj2>Y7&# zUenUBiFY^2#T2^*91CIc7MVw>&GHeeWWMK;#SDR87rNOhgb;-$>EjpEvMr#hYE)R| zGZ)i7u5|N@bY=?{tG~Y0oyG z9PUTuyeBvKhc5v~m}6)h^1{=*bki;fyiFeEOO@Y`d~)~_^1P45j4aF3v*i8&mg!GH8pH8+&@VCUI(MnK4JDYZ5e_pnPaz2vBxIZboWP16lsGpAwOz&oR zc1E>#5e|+~FEEwktlt`}jZGb_%}B+rQ;~fxvm4)hYiSG{qK5Re-Fk+i^q*kaj>zfYYq+%I0 z{6*%*v0%sU+^`e0h#={o0VWX4(m%t=()BOXhR;AU5XSyhrYD#?N`Kfs1-D^jwnfE% zl_^SG;(jpiVM1E=GWUxYAC@gYj7!ab#eq1QfU_`@AI_(Z#22YNA zA=81bo!JW^`UT_&xF8V$3z?sT8<<8@5I|U*48VeoIYdh>P5ryfk5xujctF58XOcF) zmiGJ|Kq{{#=RQn$Q!VA~LwqS*B<%yx*4NU7eKM%Z@OFc@YABQTIgEHtal3dL+-}!4 zM(U8S@^jQRzINl*&*frlG3uyrKZte3a%$QSLi^rwI^H65mKV)!JPnbu8Z7PkU(b}w3Z=4p5v>4dU zL~DPj>Va~J)j4Pf9fu>7Ky*NFDaSKjsEgAW!?{i~zVXaIaH;T$jnn=q;kfRa6&ov$ z$hi)^?@ICoMo^ut+pWLT8g}#iwu5bh0^7kAxMGUwd156UKMEc_ zVZ`vC+MVO9&)D#H2}S)(@~eipEM zYYk01D;LK$tYK8{0pg+?_!c||c;Z{ZIbaOplh0u%ISqMu>o;x9#$D&6?%25VNBNe6 zKKlvhtFG$#j!Ev$i(ZJ(9>L$B(@yGoL5_9Tu7Vv2{)%<_u7sScX|y&o!sAoFTbs|fwzhI2{};}|yXXin!QY6H zRN4w$xOX(o+kn=ZvU=LraXb__D){tL@*P#UmOT;oiTr8tec7) z5z+d+GpH14{zsnXs-nUj$K;jmRfT6#1D8EIi*_JP9QVwxn|9^4=O6u%pjQJJ<1Wr8 z=s`zBuc&)fBx+$-{GRwRe{j%^S77iT(_B3wAu9Eg4w)PO!jUMd(j$85BR*3jiCXlT znfP%l3}TGGs=kXa{wnCh!4V0LJ+y;|+x#~=hA?XHfws(^sDJQp*dFY5M8xY;zM?#d z9K8>7s@Xg7*DkD5m|FVmTi1?_{|K^xOA%T8e&^I#TV$t_I%w|Ny z#ZkAO;IC?Q;`|9D_D2iE6hyV#RFx-+H$ zjdI>7=r+|TMns@HosKiJF`)HFy3^c9wEmu-6T8w{5#fnUG=JaNHNpeK z8M;3ML&g5r9=~24pwcK58V9{41HNk{3iaubVQ-)Lf4I#}2QQB36q&^78hs2HIW8q< zG{7F;*Ze#>qLY5Pq~{VaBbQS$5M6)ka+=Foj`pJl4hQvDMWS}6n@#ZuMBjnvlmpO_ zgE}-?J%}12P-OU3s`jFIG@ilV2h$Zsgs0Ed{M+#}yp4CXLvME`f5*I!02)PI!;wog z8iCo=;|Hrw^hwPzvviz*y&b9ujo|NH71quf$-ko%$n|gkzp}12D9R%V@6PPJ@4BG) zfe~>PRAN>negzZsFk&zXQIOT3BBEjr6F)Gl7fnopvPLdZB*H~>OyoQgKh86pkSpu3 za>kH5tIjeca98{=RaR<$@WV1xZsE9>6xCMo}TWWo)_K4XTZcE zc6!(UzX1H&Il6;A8|60e*}a>H&n4@VU^IL(5!zTEK7@sy4O`_MdPZK<%S3GQ?vO_= zn*}TGlo#TO_jP%Lo^ANwYqQ(J3D3H9fCw&U`Czon8a;$I$=k08*t206Oj!J;63~$-hPmI8wIiU*!9&Iy z4o79%o%pQ{xuJ^Mj<>Z>MDR?2RkD7g#TU+oyD5yM0PXBVd~-Xg+UkSDz5`QmG11$j z8i(d@Y1Fc=H%i|~Sfux)N5+-G5=}Ju@fV<$LXzBO2vXWBGtGo|IdDx3+YL=dF%Ogp zHWQ&WV@uwTu8jp_*e-itGtC+<#(S@6A}qVTBFWqP0d={;vS9@5TXkA9R{F8D)7Zr% z#Nrvp483Rc45aqi*kqT_DzC8v=M8TPY^E@mt<#6FEh{syyC?o2ePdvhwiX>%tm2O` zNqZc)(Sa{HaJK{ZD|~zr^tq^{L=ff%(m`V9TqKp?Sg;mr`zSgS@BGLkznOzQ`IFis zJMyt&q;O&be~I$3%qt3ajDcJYmE62fv|Qyo_pU0Q@FDPg8Xf9Q>L4CCMH@`b2LGlU z%iHpI)7l@84d}Q*)S|E)ISxF{pR3W41rVg6It?$yFu(mdd?_6s-KkyAPVFQKZb`e# zu61fnh9lX10fx!gz%QsjL8=7%rAK}~4THP%V79AErqdam*{!`k)KAPQDyek*OA98i z=NaF@9dLSC-#Hk*C#=Jf`v*Mo!>O?6zoQ1Q(|*tr5|G<1g%`;#gpKv1ZYP!Pz`d9h z`-~#ovVJnSeu{CivY(=2y%nnCevS-|#gczkOPOT-923Lh??<_L_kF!q-UmQYh>zu! zGlq+a?zE%(UmBWJgJ{*Vvk5FOFmjDneF9V=MfR_v1!ootcbH2PgCPind~Ie@)WyHmw8c{Y|w!{&&X{^Hau;a37$FvsEDm)G#G9|#>ppa z&elFf7~MMyjKSLq9a@BfYGT>-yqM4G@Et!7iG8eprS_zN2-t%wi9v6m``M^wcKNeL z3VETz=GYu?FvS;XCbbLAtBC;DYK&N;aThLH8spNnRTRj&cOvVK^|Jv7PTCP2$Fp{r z8MheVt=mD8Y<&dUnUzE#Sdh?oe8{L@6N*Y;O&aG=+6bwkvWpU4-cBO)rt-R-W|}KQ zpl=8y{tEjr2I}5=SqW9A?JH6F_N7;p1}bBm-=il`f%qC>9ihCq%A~v{do@RECLBX! z8p(UBp#TDo4hkv}?Z&WXOn3y0`;DlXzy}EHD%8%dAtFU~QLroQ(sxwvuc^Z=D<43I z!X}@*soRk_lP!f8$$C=dEoxsRE9c;Pc91Z~;WivK#`QzdrLyc=xpX?RYArFqp>daE zwMO4-s?o%L=&+uq^$BFo!*VI^kSuT{D#*?}qNdQgwkZO)5!}M8dOeU5I}g_nZORac z7{I*hWeF8>tidP{Y@ne?b`*YifkGOp@W2}tmDE`8JMu!BNO2#PzIlkD_{P{=cGwV= z6QS`jr72G3053lV@dz&K7%?N&#G@!Kl||L4-cDGCqTTk7CPPKGSnlE`(kGij?{oysoZ0DxLMkupRsdG2?y<|6u8sJJa%!}Kug=wvolMYP?I7q$3N z*@?w-dFN3ikTcwpALfgNb_b_kz2#oNM-eU=@*~-Zm=Kn4$s5_o9hRA)ZU|)9F)lmP zrt!AA05H>$vrGiMAun~L;LfpIOp;&biUlkyPTs-2pC25h1%yW-wnq^gwtNA$Utz5n zK5-uE&chClR&vx)$_K~9rslg69Yoext)^WxR`b)5%pTAjlthLwr9AE#=NP(coLOOm zv-O0Nxa=gnNfzZnJ1dE1Lhw;|C+k`7uEMOzmaZZ*COBp(b`aj%6Nnv!Xb^W?%TI(l z;yK>_>DLfhDb$D(84=G?EE!hb#21$&5X_Ht(l{Ga516xHGP>x zA^IPLw(~Srr&st0*T9Z1w-5P#q3K`dLgI zbj2Bi+Im-BY%mW#qY1jHv!#~&W-Rgw+-v(ZcyLjH*|^365`Y^FuHvlFH1-uzqki*g z1qmDXs92#l;rt5CfTbi`D>bG&kb9-_UfpzGwtDHnEmC86NI&hiBF&E@8s9LRD?XEV zSBsAJ))&NZCYzoXb-bs@(l>*9MQRD^QopKwTb|g!*`?L;#xilr+rOH`;AHEcAjZ4b zNME_wC-VwKkt{8MjJ7p;^>k;oiXAf^SuT?0!Q~>A=NHGUWj84v-1OiY{ARHw508Sm z*2Xw^xgjDmM1Y0Y%58<>D(hG)(}Us^%P5hxE3r47B{Y>5V<)aN1g>H{XPuUXF!jr9 zpNO}+hq^JPcVsE?A{mVgVykcBlmAzUQ+9X*wW1`aH+=U{ldKdQ?9fJPbq=E~)- ziUj#dk%;3bHYst(Pl{|=x*Pr3t3*Ca!{_C%68r3%_ns%h)V$eSG%;%g zX56Z0B;&G-ZS5CU!*qOPg|SFLjF29y(%6CQ0eML;=*m2!Yrgm&=t;GzLN-;6*txK>?J*nHM}HmY=AQ*?nXEi zh=tzr-HHuiL~j!sUXisMVg1BC)T)a~p0)>5A(8V^0CjI|+!NK`-#%C-;@$kRvnl&I z(dlNl_9)k6OJ0?=lu@K-ob! z0HOBM?V@uuZwMQuO81Ej))H>7-6yVkc+UYXs(Fbhl=TM?%*#t5g8ph>UL*d)?Z^RI zVzs{Zcn=!(VmMM&waDN#rX{BYzSgx_443{xBEt?Ia*Qgi)1&AW5i=MQX3M5^m}~Hm zh-Ym_q^}ks*n@(lR(#C|j+-G(VwUV$Ey{FQv`O8J#rkDcov5&TrQTjjV(f(OjpT~L z_R6>6f&5CW314))9Ok&vAS&G4|8A7EN)CoaJP*C6Tht+U+Ypd6Q!ZbHVcAD%*ol*7 z{+ETn{Z=Dlh;WQuMib`ER2$HA(+_BA<7kGA-|ls-v@+=g%j_7H-%5 zPA{aZ7(sYR5pH#dvvZntPbIxHR2aWHc!Rd;Es@Oonz6CNa2iBDP-5H5n#FY{ujh*9 z_P}Xz+nvt-O97yN=oJwCOvWF)DyLtZx$1s)Ot&i$pTLQ6R?lh6)pJ^2JS(OQi=4Jy5qeoFzdDQZIfCEPMev=C z=<>OKZ@X@v^SGA0Bh$}`DeN{rBki1+W>H(&Dft?;OqK diff --git a/patch_v2/meow_launcherd/meow_launcherd.c b/patch_v2/meow_launcherd/meow_launcherd.c index 9bad98b..b3f16c7 100644 --- a/patch_v2/meow_launcherd/meow_launcherd.c +++ b/patch_v2/meow_launcherd/meow_launcherd.c @@ -1,12 +1,25 @@ /* meow_launcherd.c — R3 Pro II Plan B zero-patch app launcher daemon. + * + * M2.53 亮度接管修正(开机/切到 MeowOS 亮度被重置为 25/16): + * - 根因:HibyOS 播放器运行/恢复画面会把它自己的背光下限(16/25)写进 sysfs, + * MeowOS 主屏接管时又以 read_brightness() 读到的该值当「当前亮度」 + * (saved_brightness / meow_saved_brightness),从不重应用用户 display.brightness, + * 故用户设的低亮度(<25,最低到 1)被覆盖。 + * - 修法:run_menu 主屏接管(had_player==0,播放器已死=真接管)时调 + * apply_user_brightness():读 settings display.brightness(经新导出的 + * meow_settings_brightness_clamped(),clamp 下限 brightness_min 默认 1、上限 100) + * → write_brightness 强写回,覆盖播放器残留的 16/25。 + * - 边界:播放器会话期间的覆盖菜单(had_player==1)不压回——播电影时亮度归播放器管; + * ensure_backlight/restore_backlight 仍用 saved_brightness(覆盖会话=播放器真实值), + * 不改为用户值,避免菜单关闭瞬间抢播放器背光。 * * M2.46-2 状态栏 5 问题(2026-08-29): * ① 亮度图标主题色(on_surface_variant 灰 → on_surface 主色)+ 拖动亮度滑条时状态栏百分比实时跟: * 根因 s_commit brightness 分支只 write_brightness 未同步 g_sys.brightness/未重绘;补 * g_sys.brightness=v + paint_sb_right()(与随后 meow_sys_refresh 从 sysfs 回读一致不打架)。 * ② 右簇音/电顺序互换:最右由「音量」改「电量」(绘制顺序先电量后音量,右→左:电→音→蓝牙→WiFi→亮度)。 - * ③ 图标视觉对齐:sb_blit_icon 按 ink 边界居中 + 主图标等比等大(SB_ICON_INK=24),音量不再「小一圈/ - * 下边距」;ink 垂直中心对齐状态栏文字中心(g_sb_icon_cy=SB_ANCHOR_Y+字行高/2),图文同中线。 + * ③ 图标视觉对齐:sb_blit_icon 按 ink 边界居中 + 主图标等比等大(SB_ICON_INK=16),音量不再「小一圈/ + * 下边距」;ink 垂直中心对齐状态栏文字中心(g_sb_icon_cy=SB_ICON_CY=14,见 sb_text_y),图文同中线。 * ④ WiFi 图标随信号分档:新增 wifi_signal_level() 读 /proc/net/wireless wlan0 level(dBm), * ≥-60 强 / [-60,-75) 中 / <-75 弱,选 SB_ICON_WIFI_STRONG/MEDIUM/WEAK(新位图追加进 sb_icons.h); * 强弱档保留自然 ink(少一格即语义),满格/强档走等大;wifi_on 为假不画(保持现状)。 @@ -765,15 +778,25 @@ static void draw_text(int x, int y, const char *s, int scale, uint32_t color) /* ================================================================== */ /* M2.6 meow_font 集成(agent-28 字体子系统,font/meow_font.a + libz.a)*/ -/* 字库放 TF 卡 MeowAPPs/fonts/font.png+font.idx(或固件 /usr/share/ */ +/* 字库放 TF 卡 MeowOS/fonts/default/font.png+font.idx(或固件 /usr/share/ */ /* meowos/font/);MeowOS 启动 ensure_card_mounted 后再 load,卡晚 */ /* 挂载时轮询重试。没加载成功时 ui_draw 回退 5x7 点阵(ASCII)。 */ /* ================================================================== */ -#define UI_FONT_PNG_CARD MEOWAPPS "/fonts/font.png" -#define UI_FONT_IDX_CARD MEOWAPPS "/fonts/font.idx" +#define UI_FONT_PNG_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/font.png" +#define UI_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/font.idx" #define UI_FONT_PNG_SYS "/usr/share/meowos/font/font.png" #define UI_FONT_IDX_SYS "/usr/share/meowos/font/font.idx" +/* M2.5x:TF 卡多字体库 —— 字体目录 = <卡根>/MeowOS/fonts//(font.png+font.idx+font.json)。 + * <卡根> 默认 /usr/data/mnt/sd_0(card_present() 判定;--fontscan 可指定宿主根以便单测)。 */ +#define MEOW_FONTS_CFG "MeowOS/fonts" +#define FONT_PRESETS_MAX 17 /* 1 默认 + ≤16 卡内字体(防爆) */ +static const char *font_presets[FONT_PRESETS_MAX]; /* SC_PRESET 值 = 卡根相对路径 */ +static const char *font_preset_labels[FONT_PRESETS_MAX]; /* SC_PRESET 中文标签 */ +static char font_preset_val_buf[FONT_PRESETS_MAX][320]; /* 值持久存储(指向上面指针数组) */ +static char font_preset_label_buf[FONT_PRESETS_MAX][64]; /* 标签持久存储 */ +static int font_presets_n = 0; /* 运行时动态计数(默认=1,仅"默认字体") */ static int mfont_ready = 0; +static int mfont_forced = 0; /* font.file 变更→强制真实(un)load,跳过 SDK 复用捷径 */ /* 24-bit RGB → RGB565(与 put_px/meow_font 一致) */ static uint16_t rgb565(uint32_t c) @@ -787,23 +810,154 @@ static void meow_font_bind_fb(void) meow_font_set_fb(fb_xres, fb_yres, fb_line); } -/* 尝试加载字库(卡上 + 固件);成功返回 1。 +/* 把 font.file 存的「卡根相对/绝对」路径解析为设备绝对路径。 + * 相对路径以 /usr/data/mnt/sd_0/ 为卡根(与 MEOWAPPS 同源);绝对路径原样。 + * 返回 0 成功,-1 参数无效。 */ +static int meow_font_resolve_path(const char *ref, char *out, int olen) +{ + if (!ref || !ref[0]) return -1; + if (ref[0] == '/') { snprintf(out, olen, "%s", ref); return 0; } + snprintf(out, olen, "/usr/data/mnt/sd_0/%s", ref); + return 0; +} + +/* 解析 font.file 指向的 png + 派生同目录 idx(font.png → font.idx)。 + * 仅当指向的 png 存在(access R_OK)返回 0;否则 -1(交由回退链)。 + * 返回 0:out_png/out_idx 已填设备绝对路径。 */ +static int meow_font_resolve_selected(char *out_png, int png_len, char *out_idx, int idx_len) +{ + char ref[MEOW_SET_MAX_LEN]; + if (meow_settings_get_str("font.file", ref, sizeof ref) != 0) return -1; + if (meow_font_resolve_path(ref, out_png, png_len) != 0) return -1; + if (access(out_png, R_OK) != 0) return -1; /* 指向不存在 → 回退卡默认/固件 */ + snprintf(out_idx, idx_len, "%s", out_png); /* 同目录 font.idx(标准三件套布局) */ + size_t n = strlen(out_idx); + if (n > 4 && !strcmp(out_idx + n - 4, ".png")) { + out_idx[n-4] = '.'; out_idx[n-3] = 'i'; out_idx[n-2] = 'd'; out_idx[n-1] = 'x'; out_idx[n] = 0; + } + return 0; +} + +/* 尝试加载字库;成功返回 1。优先级(M2.5x): + * 1) font.file(设置键,真正接管加载;相对路径按卡根解析)→ 存在且能 load 即用; + * 2) 卡 MeowOS/fonts/default/font.png(默认字库); + * 3) 固件 /usr/share/meowos/font/font.png。 * A-1/A-2:SDK meow_ui_init() 已用同一 meow_font.o 加载过(可能成功也可能未成功)。 - * 若 SDK 侧 g_mfont_ready==1(已加载),直接复用,避免重复 unload+reload;否则(卡晚挂载) - * 这里按原逻辑重试(launcher 的 mfont_ready 是独立标志,用于 CN/EN 文案选择与 ui_draw 判定)。 */ + * 当 font.file 指向「默认卡字库」或未设置时复用 SDK 已加载状态,避免重复 unload+reload; + * 当 font.file 指向自定义字库时必须显式 load(SDK 只加载默认,不读 font.file)。 + * mfont_forced=1(font.file 变更触发)时无条件真实 reload,跳过上述两条捷径。 + * 全失败 → mfont_ready=0,ui_draw 回退 5x7 点阵(系统不死)。 */ static int meow_font_ensure(void) { - if (mfont_ready) return 1; - if (meow_gfx_mfont_ready()) { mfont_ready = 1; return 1; } /* SDK 已加载,复用 */ + if (mfont_ready && !mfont_forced) return 1; + mfont_forced = 0; + + char fpng[320], fidx[320]; + int have_sel = (meow_font_resolve_selected(fpng, sizeof fpng, fidx, sizeof fidx) == 0); + + /* SDK 已加载且所选字库为「默认卡字库」(或未设置)→ 复用,避免重复解码。 */ + if (meow_gfx_mfont_ready() && (!have_sel || !strcmp(fpng, UI_FONT_PNG_CARD))) { + mfont_ready = 1; + logmsg("meow_font: reuse sdk-loaded font"); + return 1; + } + + /* font.file 优先。 */ + if (have_sel && meow_font_load(fpng, fidx) == 0) { + mfont_ready = 1; + logmsg("meow_font: loaded font.file %s (%d/%d sizes)", + fpng, meow_font_has_size(28), meow_font_has_size(20)); + return 1; + } + if (have_sel) + logmsg("meow_font: font.file %s load failed, fallback", fpng); + /* 卡默认 → 固件。 */ if (meow_font_load(UI_FONT_PNG_CARD, UI_FONT_IDX_CARD) == 0 || meow_font_load(UI_FONT_PNG_SYS, UI_FONT_IDX_SYS) == 0) { mfont_ready = 1; - logmsg("meow_font: loaded (%d/%d sizes)", meow_font_has_size(28), meow_font_has_size(20)); + logmsg("meow_font: loaded fallback (%d/%d sizes)", + meow_font_has_size(28), meow_font_has_size(20)); return 1; } return 0; } +/* 求「当前生效字体」的 png/idx 绝对路径 + family,供 launch_app 注入 MEOW_FONT_PNG/IDX/APP。 + * 优先级(与 meow_font_ensure 一致):font.file(resolve_selected)→ 卡默认 MeowOS/fonts/default。 + * family 取自 MeowOS/fonts//font.png 目录名;无法判定则 "default"。 + * 返回 0 成功(out_png/out_idx/out_family 已填),-1 无卡/无可用字体(调用方跳过注入, + * SDK 自身会回退卡默认→固件→点阵)。 */ +static int meow_font_effective_paths(char *out_png, int plen, char *out_idx, int ilen, + char *out_family, int flen) +{ + char fpng[400], fidx[400]; + if (meow_font_resolve_selected(fpng, sizeof fpng, fidx, sizeof fidx) == 0) { + snprintf(out_png, plen, "%s", fpng); + snprintf(out_idx, ilen, "%s", fidx); + const char *prefix = "/MeowOS/fonts/"; + const char *p = strstr(fpng, prefix); + if (p) { + const char *q = p + strlen(prefix); + const char *end = strrchr(q, '/'); + int rest = end ? (int)(end - q) : 0; + if (rest > 0 && rest < flen) { memcpy(out_family, q, rest); out_family[rest] = 0; } + else snprintf(out_family, flen, "default"); + } else snprintf(out_family, flen, "default"); + return 0; + } + /* 回退卡默认 MeowOS/fonts/default */ + if (access(UI_FONT_PNG_CARD, R_OK) == 0) { + snprintf(out_png, plen, "%s", UI_FONT_PNG_CARD); + snprintf(out_idx, ilen, "%s", UI_FONT_IDX_CARD); + snprintf(out_family, flen, "default"); + return 0; + } + return -1; +} + +/* M2.5x 多字体:从 settings 读 display.multi_font + 6 类 font.class_*,把各自相对路径按卡根 + * 解析为绝对路径,去重装载到 meow_font 槽位(同 family 去重 & ≤4 套上限由 + * meow_font_config_multifont 统一处理)。仅当全局字库已装载(mfont_ready / slot0)时生效; + * 关或未装载 → 单字体(on=0),零额外开销。调用时机:启动 meow_font_ensure() 之后,以及 + * 设置页变更 display.multi_font / font.class_* / font.file。 */ +static void meow_font_reload_multifont(void) +{ + static const char *cls_keys[MEOW_FCLS_COUNT] = { + "font.class_cjk", "font.class_cjk_sym", "font.class_num", + "font.class_latin", "font.class_latin_sym", "font.class_other" }; + int on = 0; + meow_settings_get_bool("display.multi_font", &on); + char gpng[400], gidx[400], gfam[96]; + if (meow_font_effective_paths(gpng, sizeof gpng, gidx, sizeof gidx, gfam, sizeof gfam) != 0) + return; /* 无全局字体:无法多字体 */ + /* 各类候选(静态缓冲保活;config_multifont 同步读取,不持指针) */ + static char cpng[MEOW_FCLS_COUNT][400], cidx[MEOW_FCLS_COUNT][400]; + meow_font_class_cfg cfg[MEOW_FCLS_COUNT]; + memset(cfg, 0, sizeof cfg); + for (int c = 0; c < MEOW_FCLS_COUNT; c++) { + char ref[MEOW_SET_MAX_LEN]; + if (meow_settings_get_str(cls_keys[c], ref, sizeof ref) == 0 && ref[0]) { + char abs[400]; + if (meow_font_resolve_path(ref, abs, sizeof abs) == 0 && access(abs, R_OK) == 0) { + snprintf(cpng[c], sizeof cpng[c], "%s", abs); + snprintf(cidx[c], sizeof cidx[c], "%s", abs); + size_t n = strlen(cidx[c]); + if (n > 4 && !strcmp(cidx[c] + n - 4, ".png")) { + cidx[c][n-4]='.'; cidx[c][n-3]='i'; cidx[c][n-2]='d'; cidx[c][n-1]='x'; cidx[c][n]=0; + } + cfg[c].png = cpng[c]; cfg[c].idx = cidx[c]; + } + } + } + int distinct = 0, truncated = 0; + if (meow_font_config_multifont(on, gpng, gidx, cfg, &distinct, &truncated) != 0) + logmsg("multi_font: config failed -> single-font"); + else if (truncated) + logmsg("multi_font: WARN >%d 套不同字体(会增内存),已按上限截断", distinct); + else if (on) + logmsg("multi_font: enabled, %d distinct fonts (global=%s)", distinct, gfam); +} + /* 统一文本绘制:字库就绪用 meow_font(真字体/中文),否则回退 5x7 点阵。 * size 为字库字号(28=状态栏/大标题,20=正文/按钮);回退时按 scale 近似。 */ static void ui_draw(int x, int y, const char *s, uint32_t color, int size) @@ -822,6 +976,13 @@ static int ui_width(const char *s, int size) return (int)strlen(s) * 6 * ((size >= 16) ? 2 : 1); } +/* 文本高度(字库行高 or 回退 7×scale 估算),用于垂直居中 / 行高预算。 */ +static int ui_text_height(int size) +{ + if (mfont_ready) return meow_font_line_height(size); + return 7 * ((size >= 16) ? 2 : 1); +} + static int fb_open(void) { int fd = open(FB_DEV, O_RDWR); @@ -1159,7 +1320,8 @@ struct meow_svc { }; struct app { - char id[MEOW_APP_ID_MAX]; /* M2.52:app_id=卡上目录名 de->d_name(与注册表 g_accts 一致),供 MEOW_LOG_APP 注入 */ + char id[MEOW_APP_ID_MAX]; /* M2.52:app_id=app.json 的 package(权威,缺省回退卡上目录名 de->d_name),供 MEOW_LOG_APP 注入 */ + char package[MEOW_APP_ID_MAX]; /* 安卓化:app.json 的 package 字段(反域名权威标识;目录名=package),scan_apps 据此定 app_id */ char name[17]; /* ASCII display name */ char exec[129]; /* absolute path */ int ok; /* exec exists and X_OK */ @@ -1418,6 +1580,156 @@ static int ensure_card_mounted(void) return 0; } +/* ================================================================== */ +/* M2.5x:TF 卡多字体库扫描 + preset 动态化 */ +/* 字体目录 = <卡根>/MeowOS/fonts//,每套含三件套: */ +/* font.png(预渲染灰度 PNG,bit depth 4|8)+ font.idx(MFNI v2 索引) */ +/* + font.json(元数据 + 完整性校验)。缺任一件 → 该套不进列表 + 日志。 */ +/* font.json schema(定版): */ +/* { */ +/* "family": "家族标识(同目录名)", // 信息用,不参与校验 */ +/* "name": "下拉显示名", // 缺则回退目录名 */ +/* "glyph_count": 10000, // 信息用 */ +/* "size_list": [28,20], // 信息用 */ +/* "bit_depth": 4, // 信息用(4|8) */ +/* "sha256_png": "<64hex>", // font.png 校验 */ +/* "sha256_idx": "<64hex>", // font.idx 校验 */ +/* "source": "...", // 可选:来源 */ +/* "license": "..." // 可选:许可 */ +/* } */ +/* 校验:sha256 不匹配仅 WARN 不硬失败(仍可选);缺三件套或 font.json */ +/* 解析不出 name → 跳过 + 日志。仅启动/进设置页扫描一次(勿放热路径)。 */ +/* ================================================================== */ + +/* 计算文件 sha256(走 busybox/宿主 sha256sum;输出小写 64hex)。返回 0 成功。 */ +static int font_sha256_file(const char *path, char *out, int olen) +{ + out[0] = 0; + /* 单引号转义防 shell 注入(路径含 ' 时安全;转义后超长则截断 → sha256sum 失败走 WARN) */ + char esc[1024]; int j = 0; + for (const char *p = path; *p && j < (int)sizeof esc - 4; ) { + if (*p == '\'') { memcpy(esc + j, "'\\''", 4); j += 4; p++; } + else esc[j++] = *p++; + } + esc[j] = 0; + char cmd[1080]; + snprintf(cmd, sizeof cmd, "sha256sum '%s' 2>/dev/null", esc); + FILE *f = popen(cmd, "r"); + if (!f) return -1; + char line[160]; + int ok = -1; + if (fgets(line, sizeof line, f)) { + int i = 0, k = 0; + while (line[i] == ' ' || line[i] == '\t') i++; /* 跳前导空白 */ + while (line[i] && k < 64) { + char c = line[i++]; + if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) + out[k++] = c; + else break; + } + out[k] = 0; + ok = (k == 64) ? 0 : -1; + } + pclose(f); + return ok; +} + +/* 比较两个 sha256 十六进制串(大小写不敏感)。均为 64hex 才比对,否则视为不匹配。 */ +static int font_sha_eq(const char *a, const char *b) +{ + if (!a || !b || strlen(a) < 64 || strlen(b) < 64) return 0; + for (int i = 0; i < 64; i++) { + char ca = a[i], cb = b[i]; + if (ca >= 'A' && ca <= 'F') ca += 32; + if (cb >= 'A' && cb <= 'F') cb += 32; + if (ca != cb) return 0; + } + return 1; +} + +/* 解析 font.json:提取 name / sha256_png / sha256_idx(文件缺失或解析不出则留空)。 */ +static void meow_font_json_probe(const char *json_path, char *name, int nlen, + char *sha_png, int slen, char *sha_idx, int xlen) +{ + name[0] = 0; sha_png[0] = 0; sha_idx[0] = 0; + FILE *f = fopen(json_path, "r"); + if (!f) return; + char buf[256]; + while (fgets(buf, sizeof buf, f)) { + if (!name[0]) json_str(buf, "name", name, nlen); + if (!sha_png[0]) json_str(buf, "sha256_png", sha_png, slen); + if (!sha_idx[0]) json_str(buf, "sha256_idx", sha_idx, xlen); + } + fclose(f); +} + +/* 探测一个 / 套件是否合法:三件套齐全 → 解析 label + 校验 sha。 + * 返回 1=进列表,0=跳过(缺组件/json 不可读)。sha 失败仅 WARN。 */ +static int meow_font_probe_family(const char *dirpath, const char *family, + char *label, int llen) +{ + char ppath[400], ipath[400], jpath[400]; + snprintf(ppath, sizeof ppath, "%s/font.png", dirpath); + snprintf(ipath, sizeof ipath, "%s/font.idx", dirpath); + snprintf(jpath, sizeof jpath, "%s/font.json", dirpath); + if (access(ppath, R_OK) != 0 || access(ipath, R_OK) != 0 || access(jpath, R_OK) != 0) { + logmsg("fonts: skip '%s' (缺 font.png/font.idx/font.json)", family); + return 0; + } + char name[64] = "", jsha_png[80] = "", jsha_idx[80] = ""; + meow_font_json_probe(jpath, name, sizeof name, jsha_png, sizeof jsha_png, + jsha_idx, sizeof jsha_idx); + snprintf(label, llen, "%s", name[0] ? name : family); /* 显示名:name 或回退目录名 */ + /* 完整性校验:仅 WARN,不硬失败;该套仍标记可选。 */ + char apng[80] = "", aidx[80] = ""; + if (font_sha256_file(ppath, apng, sizeof apng) != 0) + logmsg("fonts: '%s' sha256(png) 不可用,跳过校验", family); + else if (jsha_png[0] && !font_sha_eq(apng, jsha_png)) + logmsg("fonts: '%s' WARN sha256_png 不匹配(仍可选)", family); + if (font_sha256_file(ipath, aidx, sizeof aidx) != 0) + logmsg("fonts: '%s' sha256(idx) 不可用,跳过校验", family); + else if (jsha_idx[0] && !font_sha_eq(aidx, jsha_idx)) + logmsg("fonts: '%s' WARN sha256_idx 不匹配(仍可选)", family); + logmsg("fonts: add '%s' (%s)", label, family); + return 1; +} + +/* 扫描 /MeowOS/fonts/ 下每个家族子目录,重建字体 preset 列表(默认 + 卡内合法套件)。 + * 上限 FONT_PRESETS_MAX(1 默认 + ≤16 用户字体)。无卡/无目录时仅默认。 */ +static void meow_font_scan_presets_root(const char *card_root) +{ + font_presets_n = 0; + font_presets[0] = "MeowOS/fonts/default/font.png"; /* 默认字库 = MeowOS/fonts/default */ + font_preset_labels[0] = "默认字体"; + font_presets_n = 1; + char root[340]; + snprintf(root, sizeof root, "%s/%s", card_root, MEOW_FONTS_CFG); + DIR *d = opendir(root); + if (!d) return; /* 无卡/无字库目录:仅默认 */ + struct dirent *de; + while ((de = readdir(d)) && font_presets_n < FONT_PRESETS_MAX) { + if (de->d_name[0] == '.') continue; + char dirpath[400], label[64]; + snprintf(dirpath, sizeof dirpath, "%s/%s", root, de->d_name); + if (meow_font_probe_family(dirpath, de->d_name, label, sizeof label) == 0) + continue; /* 缺组件/损坏 → 跳过该套 */ + int i = font_presets_n; + snprintf(font_preset_val_buf[i], sizeof font_preset_val_buf[i], + "%s/%s/font.png", MEOW_FONTS_CFG, de->d_name); + snprintf(font_preset_label_buf[i], sizeof font_preset_label_buf[i], "%s", label); + font_presets[i] = font_preset_val_buf[i]; + font_preset_labels[i] = font_preset_label_buf[i]; + font_presets_n++; + } + closedir(d); +} + +/* 设备默认卡根扫描(等价现状:无卡时 opendir 失败 → 仅默认字体)。 */ +static void meow_font_scan_presets(void) +{ + meow_font_scan_presets_root("/usr/data/mnt/sd_0"); +} + /* wlan0 是否已联网(链路 up,与状态栏 WIFI 指示一致) */ static int wlan_connected(void) { @@ -1491,6 +1803,7 @@ static void meow_parse_app_json(FILE *f, struct app *a) { char buf[256]; char name[17] = "", exec[129] = "", icon[129] = ""; + char package[MEOW_APP_ID_MAX] = ""; int fram = 0, immersive = 0; struct app tmp; memset(&tmp, 0, sizeof tmp); @@ -1498,6 +1811,7 @@ static void meow_parse_app_json(FILE *f, struct app *a) if (!name[0]) json_str(buf, "name", name, sizeof name); if (!exec[0]) json_str(buf, "exec", exec, sizeof exec); if (!icon[0]) json_str(buf, "icon", icon, sizeof icon); + if (!package[0]) json_str(buf, "package", package, sizeof package); /* 安卓化权威 app_id */ /* route.=(activity 路由,M1) */ char *rp = strstr(buf, "route."); if (rp && tmp.nroute < MEOW_MAX_ROUTES) { @@ -1594,6 +1908,7 @@ static void meow_parse_app_json(FILE *f, struct app *a) } strncpy(a->name, name, 16); strncpy(a->exec, exec, 128); + strncpy(a->package, package, sizeof a->package - 1); a->framework = fram ? '1' : 0; a->immersive = immersive ? '1' : 0; strncpy(a->icon, icon, 128); @@ -1627,10 +1942,14 @@ static int scan_apps(struct app apps[MAX_APPS]) memset(a, 0, sizeof *a); memcpy(a, &a0, sizeof a0); a->ok = (access(a->exec, X_OK) == 0); - /* M2.52:记 app_id=目录名(与注册表 g_accts 同步一致),launch_app 注入 MEOW_LOG_APP 用 */ - strncpy(a->id, de->d_name, sizeof a->id - 1); - /* M1 路由注册表同步(app_id = 目录名 de->d_name) */ - meow_acct_sync(de->d_name, a); + /* 安卓化:app_id=app.json 的 package(权威),缺省回退目录名 de->d_name; + * 两者都存在且不同则 WARN(正常情况二者相等)。launch_app 注入 MEOW_LOG_APP 用 */ + const char *aid = a0.package[0] ? a0.package : de->d_name; + strncpy(a->id, aid, sizeof a->id - 1); + if (a0.package[0] && strcmp(a0.package, de->d_name) != 0) + logmsg("WARN: app '%s' package '%s' != dir '%s'", a0.name, a0.package, de->d_name); + /* M1 路由注册表同步(app_id = a->id,即 package 优先) */ + meow_acct_sync(a->id, a); n++; } closedir(d); @@ -2269,18 +2588,17 @@ static void meow_sys_light(void) * line 2: VOL nn% (left) * M2.11:值全部取自 meow_sys_state 快照(g_sys),不再每帧读 amixer/sysfs; * 音量 = settings audio.volume(非 CS43198 死镜像);蓝牙 = 实际 powered; - * WiFi = operstate 实际 up。纯黑背景 = menu_bg;分隔线 y=40。*/ + * WiFi = operstate 实际 up。纯黑背景 = menu_bg;分隔线 y=28(栏高 40→28)。*/ /* ---- 状态栏固定完整格重绘(M2.17:修「整个状态栏部件脏区残留」)---- * 根因:M2.16 各部件/数字更新只把「当前内容包围盒」标为脏区并用它清底——旧内容超出新包围盒的 * 部分(时钟数字 '0' 底弧落于新 '1' 包围盒外、电量%/WiFi/BT 出现或消失、音量数字)未被重绘 → - * 残留;按音量键全屏刷新才清。修法:状态栏(0,0,480×40)用一条固定完整格:字段更新时整条清底 + + * 残留;按音量键全屏刷新才清。修法:状态栏(0,0,480×28)用一条固定完整格:字段更新时整条清底 + * 重绘时钟/右侧 + 整条标脏(SDK back_buf 做最终态 → meow_ui_flush 原子 blit 整条,无中间态)。 - * 任何旧内容残留被整条覆盖。480×40≈38KB 极小,每秒一 blit 无碍;分隔线 y=40 单独绘制不在此列。 + * 任何旧内容残留被整条覆盖。480×28≈27KB 极小,每秒一 blit 无碍;分隔线 y=28 单独绘制不在此列。 * (逐字段固定格在 480px 宽 + 「蓝牙」CJK 全字段同现时与时钟格重叠,故取整条退化——§18.46。) */ #define SB_BAR_Y 0 -#define SB_BAR_H 40 /* rows 0..39;分隔线在 y=40 不在此 */ -#define SB_ANCHOR_Y 2 /* 字形升线锚点 y */ -#define SB_FONT 28 +#define SB_BAR_H 28 /* rows 0..27;分隔线在 y=28 不在此(40→28,用户定) */ +#define SB_FONT 20 /* 状态栏字体 28→20(28px 栏放不下 28 号;垂直居中锚点见 sb_text_y) */ static int g_sb_clock_x, g_sb_clock_y, g_sb_clock_w, g_sb_clock_h; static int g_sb_right_x, g_sb_right_y, g_sb_right_w, g_sb_right_h; @@ -2363,13 +2681,21 @@ static const meow_ui_theme_t *s_ui_theme(void) /* ---- 状态栏右簇图标化(MD3)---- 布局常量 */ #define SB_TGAP 6 /* 图标右缘 ↔ 文字间隙 */ #define SB_XGAP 10 /* 相邻两项(右→左)间隙 */ -#define SB_ICON_INK 24 /* 主图标目标 ink 高(px):按 ink 边界等比缩放对齐,音量/亮度等图标视觉一致 */ +#define SB_ICON_INK 16 /* 主图标目标 ink 高(px):24→16(28px 栏等比缩放,留上下边距) */ #define SB_CLK_MARGIN 14 /* 右簇左缘相对时钟右缘须留出的安全间距 */ -/* 图标 ink 垂直中心线 = 状态栏文字垂直中心(机测 mfont28 数字在 SB_ANCHOR_Y=2 下中心 ≈ y=17)。 +/* 图标 ink 垂直中心线 = 状态栏文字垂直中心(28px 栏文字按 (28-行高)/2 锚定后中心≈14,见 sb_text_y)。 * 图标 ink 对齐此线 → 与数字/文字同中线(修「图标比文字低」);如需微调只改这一个常量。 */ -#define SB_ICON_CY 17 +#define SB_ICON_CY 14 static int g_sb_icon_cy = SB_ICON_CY; +/* 状态栏文字垂直锚 y(行首升线顶):(栏高-行高)/2 把文字垂直居中于 28px 栏; + * 下限 1 防极端行高(>栏高)时算出负 y 溢出顶端(不越界优先)。 */ +static int sb_text_y(void) +{ + int y = (SB_BAR_H - ui_text_height(SB_FONT)) / 2; + return (y < 1) ? 1 : y; +} + /* 状态栏 MD3 图标 blit:把 sb_icons.h 的 32x32 单通道 alpha 位图画进状态栏(fb = SDK back_buf)。 * x = 图标槽左缘(槽宽 SB_ICON_SIZE);fg = 24-bit RGB 前景色(主题 on_surface / on_surface_variant)。 * M2.46-2 视觉对齐(音量图标小一圈/下边距、图标与文字垂直中心): @@ -2436,7 +2762,7 @@ static int sb_r_place(int *xr, int icon, const char *txt, uint32_t col, int ink_ int tw = txt ? ui_width(txt, SB_FONT) : 0; int x = *xr - (iconw + gap + tw); if (icon >= 0) { sb_blit_icon(icon, x, col, ink_h); x += iconw + gap; } - if (txt) ui_draw(x, SB_ANCHOR_Y, txt, col, SB_FONT); + if (txt) ui_draw(x, sb_text_y(), txt, col, SB_FONT); *xr -= (iconw + gap + tw) + SB_XGAP; return *xr + SB_XGAP; } @@ -2489,8 +2815,8 @@ static void sb_redraw(void) struct tm tm; localtime_r(&now, &tm); snprintf(timebuf, sizeof timebuf, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec); - ui_draw(8, SB_ANCHOR_Y, timebuf, sb_fg, SB_FONT); - /* 图标 ink 垂直中心 = 状态栏文字中心(SB_ICON_CY=机测 17):保证图标与数字/文字同中线。 */ + ui_draw(8, sb_text_y(), timebuf, sb_fg, SB_FONT); + /* 图标 ink 垂直中心 = 状态栏文字中心(SB_ICON_CY=14,28px 栏文字中心):同中线。 */ g_sb_icon_cy = SB_ICON_CY; /* 右簇 MD3 图标化(从右端 fb_xres-8 往左):[电量图标+%] [音量图标+%] [蓝牙图标] [WiFi图标] [亮度图标+%]。 * M2.46-2:音量/电量顺序互换(最右由音量改为电量);亮度由 on_surface_variant 改为主色 on_surface; @@ -2538,10 +2864,10 @@ static void sb_redraw(void) if (xr - bw > 8 + ui_width(timebuf, SB_FONT) + SB_CLK_MARGIN) sb_r_place(&xr, SB_I_BRIGHT, bright, sb_fg, SB_ICON_INK); /* 亮度:图标+%(主色,与时钟不重叠才画) */ } - g_sb_clock_x = 4; g_sb_clock_y = SB_ANCHOR_Y; + g_sb_clock_x = 4; g_sb_clock_y = sb_text_y(); g_sb_clock_w = 12 + ui_width(timebuf, SB_FONT); g_sb_clock_h = SB_BAR_H; g_sb_right_x = xr + SB_XGAP; - g_sb_right_y = SB_ANCHOR_Y; g_sb_right_w = (fb_xres - 8) - g_sb_right_x + 4; + g_sb_right_y = sb_text_y(); g_sb_right_w = (fb_xres - 8) - g_sb_right_x + 4; g_sb_right_h = SB_BAR_H; meow_dirty_add(0, SB_BAR_Y, fb_xres, SB_BAR_H); /* 整条标脏 */ } @@ -2555,7 +2881,7 @@ static void draw_statusbar(void) { sb_redraw(); const meow_ui_theme_t *th = s_ui_theme(); /* M2.44 分隔线随主题 outline */ - fill_rect(0, 40, fb_xres, 1, th->outline); /* separator(整条之外,单独绘制) */ + fill_rect(0, SB_BAR_H, fb_xres, 1, th->outline); /* separator(整条之外,单独绘制;栏底=28) */ } /* returns 0 on success (app ran and exited), -1 on launch failure */ @@ -2903,23 +3229,12 @@ static void sys_do_reboot(int cmd) for (;;) usleep(1000); /* 防御:永不返回 */ } -/* 回HibyOS:写 /data/meow_boot_default(规格标记)+ meow.conf boot_default=1 - * (bootmenu 读 meow.conf 定默认),再 reboot 回选单并默认 HibyOS。 */ +/* 回HibyOS:写板载 /data/meow_boot_default=1(开机默认权威标记,O_TRUNC 单值、不写 TF 卡), + * 再 reboot 回选单并默认 HibyOS。meow.conf 不再写/承载 boot_default(bootmenu 以板载标记为准)。 */ static void set_boot_default_hiby(void) { - char conf[256]; - int fd; - fd = open("/data/meow_boot_default", O_WRONLY | O_CREAT | O_TRUNC, 0644); + int fd = open("/data/meow_boot_default", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd >= 0) { write(fd, "1\n", 2); close(fd); logmsg("pm: /data/meow_boot_default=1"); } - snprintf(conf, sizeof conf, "%s/meow.conf", MEOWAPPS); - fd = open(conf, O_WRONLY | O_CREAT | O_APPEND, 0644); - if (fd >= 0) { - if (write(fd, "boot_default=1\n", 15) < 0) logmsg("pm: write boot_default failed"); - close(fd); - logmsg("pm: boot_default=1 appended to meow.conf"); - } else { - logmsg("pm: meow.conf open failed: %s", strerror(errno)); - } } /* 电源菜单(M2.6):2×2 网格,dirty 绘制消频闪;输入修复: @@ -3265,6 +3580,20 @@ static volatile sig_atomic_t sigterm_flag = 0; static long sim_exit_tap_ms = 0; static int sim_exit_tap_done = 0; +/* M2.53:MeowOS 主屏接管时把亮度强制压回用户设置值(display.brightness,经 + * meow_settings_brightness_clamped() —— 下限 brightness_min 默认 1、上限 100), + * 覆盖 HibyOS 播放器写入 sysfs 的背光下限(16/25)。仅应在「MeowOS 真正接管画面」 + * (播放器已死、主屏常驻,即 run_menu 的 had_player==0)时调用;播放器会话期间的 + * 覆盖菜单(had_player==1)不去抢亮度——尊重播放器背光(§红线)。 */ +static void apply_user_brightness(void) +{ + int v = meow_settings_brightness_clamped(); + if (v <= 0) v = 60; /* 防御:clamp 后不可能 ≤0,仅兜底 */ + write_brightness(v); + g_sys.brightness = v; + logmsg("apply user brightness (MeowOS takeover) -> %d", v); +} + /* real menu loop */ static void run_menu(struct app apps[MAX_APPS], int napps, int state, long sim_tap_ms, long sim_exit_ms) @@ -3306,8 +3635,9 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state, fbfd = fb_open(); meow_font_bind_fb(); /* M2.6 字库绑定当前 fb 几何(宽/高/pitch) */ - /* M2.6 加载字库(卡上 MeowAPPs/fonts/,卡片已挂载则该步成功;未挂载由下方轮询补载) */ + /* M2.6 加载字库(卡上 MeowOS/fonts/default/,卡片已挂载则该步成功;未挂载由下方轮询补载) */ meow_font_ensure(); + meow_font_reload_multifont(); /* M2.5x 多字体(读 settings display.multi_font + 6 类;关=单字体) */ /* M2.2:先判播放器是否存活。MeowOS 常驻主界面(播放器已死)→ 不 SIGSTOP、不备份 * 播放器帧,全程只自绘 fb0(buffer0 + pan0);仅当存在存活播放器(会话覆盖场景)才 * 冻结 + 备份,退出时恢复其帧。 */ @@ -3325,6 +3655,11 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state, } ensure_backlight(); /* only if the panel is dark, never yank brightness */ unblank_panel(); /* player frozen: it can no longer re-blank */ + /* M2.53:MeowOS 主屏接管(播放器已死,had_player==0)时强制把亮度压回用户 + * display.brightness 设定值。HibyOS 播放器运行/恢复画面会把它自己的背光下限 + * (16/25)写进 sysfs;若以它为「当前亮度」,用户设的低亮度(<25)会被覆盖。 + * 覆盖菜单(had_player==1,播放器会话中)不压回——播电影时亮度归播放器管。 */ + if (!had_player) apply_user_brightness(); if (had_player) fb_backup_player(); /* only snapshot when a live player is overlaid */ draw_menu(apps, napps, state, 0, 0, ""); /* full menu into back_buf */ fb_norm_buf0(); /* ensure LCDC shows buffer0 (yoffset=0) */ @@ -3465,6 +3800,7 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state, /* 卡在且已挂载(刚插入/重挂成功):补字库 + 重扫 · 仅真正变化才重绘 */ int was_nocard = (state == 1); if (!mfont_ready && meow_font_ensure()) { dirty = 1; logmsg("hotplug: font loaded late"); } + if (mfont_ready) meow_font_reload_multifont(); /* M2.5x:卡挂载后重配多字体(此前无全局可配) */ if (was_nocard || state != 0 || napps == 0) { struct app fresh[MAX_APPS]; int fn = scan_apps(fresh); @@ -4008,9 +4344,9 @@ static const char *const hk_preset_labels[] = { "电源+音量减", "连按序 static const char *const seq_presets[] = { "volup,volup,volup", "voldown,voldown,voldown" }; static const char *const seq_preset_labels[] = { "音量上×3", "音量下×3" }; #define SEQ_PRESETS_N 2 -static const char *const font_presets[] = { "MeowAPPs/fonts/font.png" }; -static const char *const font_preset_labels[] = { "默认字体" }; -#define FONT_PRESETS_N 1 +/* font.file 行(字体文件)不使用编译期静态表:其 presets/preset_labels 指向字体区 + * (font_presets/font_preset_labels, 见本文件字体区) 的运行时动态数组,计数由 + * s_row_presets() 动态返回(默认 1,进设置页扫描 TF 卡后更新)。见 SMENU 行。 */ static const char *const out_presets[] = { "hw:0,0" }; static const char *const out_preset_labels[] = { "CS43198 (hw:0,0)" }; #define OUT_PRESETS_N 1 @@ -4074,7 +4410,16 @@ static const srow_t SMENU[] = { { "滑动方向", "input.swipe_invert", SC_SWITCH, 0,0,0, NULL,NULL,0 }, /* 字体主题 */ { "字体主题", NULL, SC_HEADER, 0,0,0, NULL,NULL,0 }, - { "字体文件", "font.file", SC_PRESET, 0,0,0, font_presets, font_preset_labels, FONT_PRESETS_N }, + { "字体文件", "font.file", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 }, + /* M2.5x 多字体:开关 + 6 个字符类别字体(preset=可用 MeowOS/fonts// 列表; + * 值空 = 跟随全局 font.file)。仅开关开启时 6 行可见(s_row_visible)。 */ + { "多字体", "display.multi_font", SC_SWITCH, 0,0,0, NULL,NULL,0 }, + { "中文(CJK)", "font.class_cjk", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 }, + { "中文标点", "font.class_cjk_sym", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 }, + { "数字", "font.class_num", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 }, + { "拉丁字母", "font.class_latin", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 }, + { "半角标点", "font.class_latin_sym", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 }, + { "其他字符", "font.class_other", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 }, { "标题字号", "font.size.title", SC_SLIDER, 16,40,1, NULL,NULL,0 }, { "正文字号", "font.size.body", SC_SLIDER, 12,28,1, NULL,NULL,0 }, { "UI风格", "ui.style", SC_ENUM, 0,0,0, NULL,NULL,0 }, @@ -4120,7 +4465,41 @@ static int s_sel_track_y = -1; #define S_SLIDER_W (SET_CONTENT_W) /* ---- 行高/位置/导航辅助 ---- */ -static int s_setting_idx(int i) { return SMENU[i].ctrl != SC_HEADER; } +/* M2.5x 多字体:6 个「字体文件」行仅在 display.multi_font=1 时可见(开时显示)。 + * 可见性与分组的标题行无关(标题恒显示)。 */ +static int s_row_visible(int i) +{ + const srow_t *r = &SMENU[i]; + if (r->key && !strncmp(r->key, "font.class_", 11)) { + int on = 0; meow_settings_get_bool("display.multi_font", &on); + return on; + } + return 1; +} +/* 可见行数(= 压缩后占用的行数;标题行计入,隐藏的 class 行不计) */ +static int s_vis_count(void) +{ + int n = 0; + for (int i = 0; i < SMENU_N; i++) if (s_row_visible(i)) n++; + return n; +} +/* SMENU 下标 → 压缩位置(第几个可见行);不可见返回 -1 */ +static int s_vis_of(int i) +{ + if (i < 0 || i >= SMENU_N || !s_row_visible(i)) return -1; + int p = 0; + for (int k = 0; k < i; k++) if (s_row_visible(k)) p++; + return p; +} +/* 压缩位置 → SMENU 下标 */ +static int s_vis_at(int p) +{ + int c = 0; + for (int i = 0; i < SMENU_N; i++) if (s_row_visible(i)) { if (c == p) return i; c++; } + return SMENU_N - 1; +} + +static int s_setting_idx(int i) { return i >= 0 && i < SMENU_N && SMENU[i].ctrl != SC_HEADER && s_row_visible(i); } static int s_first_setting(void) { for (int i = 0; i < SMENU_N; i++) if (s_setting_idx(i)) return i; @@ -4132,10 +4511,23 @@ static int s_next_setting(int i) return s_first_setting(); /* 回绕 */ } +/* SC_PRESET 行取「选项表 + 数量」:字体行(font.file + font.class_*)用运行时动态数组 + * (TF 卡 MeowOS/fonts// 扫描结果,见字体区 font_presets/font_presets_n), + * 其余行用编译期静态表(r->presets / r->preset_n)。 */ +static const char *const *s_row_presets(const srow_t *r, int *n) +{ + if (r && r->key && (!strcmp(r->key, "font.file") || !strncmp(r->key, "font.class_", 11))) { + if (n) *n = font_presets_n; + return font_presets; + } + if (n) *n = r->preset_n; + return r->presets; +} + /* 滚动:纯范围钳制到 [0, 内容高度-视口高](列表拖动滚动用;不强制选中行可见) */ static int s_clamp_scroll_range(int scroll) { - int total = SMENU_N * SROW_H; + int total = s_vis_count() * SROW_H; if (total <= SET_CONTENT_H) return 0; int max_scroll = total - SET_CONTENT_H; if (scroll < 0) scroll = 0; @@ -4147,20 +4539,22 @@ static int s_clamp_scroll_range(int scroll) static int s_clamp_scroll(int sel, int scroll) { scroll = s_clamp_scroll_range(scroll); - int top = sel * SROW_H; + int pos = s_vis_of(sel); + if (pos < 0) pos = s_vis_of(s_first_setting()); + int top = pos * SROW_H; int bot = top + SROW_H; if (top < scroll) scroll = top; /* 选中行滑出顶部 → 上滚 */ if (bot > scroll + SET_CONTENT_H) scroll = bot - SET_CONTENT_H; /* 滑出底部 → 下滚 */ return s_clamp_scroll_range(scroll); } -/* 行 y 落在 content 坐标 cy 时对应的行号;越界返回 -1 */ +/* 行 y 落在 content 坐标 cy 时对应的行号(压缩位置 → SMENU 下标);越界返回 -1 */ static int s_row_at_content_y(int cy) { if (cy < 0) return -1; - int idx = cy / SROW_H; - if (idx >= SMENU_N) return -1; - return idx; + int pos = cy / SROW_H; + if (pos >= s_vis_count()) return -1; + return s_vis_at(pos); } /* ---- 值格式化/展示 ---- */ @@ -4231,8 +4625,15 @@ static void s_display_value(const srow_t *r, char *buf, int len) /* SC_PRESET */ char cur[MEOW_SET_MAX_LEN]; if (meow_settings_get_str(r->key, cur, sizeof cur) != 0) { snprintf(buf, len, "-"); return; } - for (int i = 0; i < r->preset_n; i++) { - if (!strcmp(r->presets[i], cur)) { + /* M2.5x 多字体:class 行空值 = 跟随全局 font.file */ + if (r->key && !strncmp(r->key, "font.class_", 11) && !cur[0]) { + snprintf(buf, len, "跟随全局"); + return; + } + int pn = 0; + const char *const *pps = s_row_presets(r, &pn); + for (int i = 0; i < pn; i++) { + if (!strcmp(pps[i], cur)) { snprintf(buf, len, "%s", (r->preset_labels && r->preset_labels[i][0]) ? r->preset_labels[i] : cur); return; } @@ -4947,6 +5348,24 @@ static void s_commit(const srow_t *r, int save_now) !strcmp(r->key, "display.theme_dark_start") || !strcmp(r->key, "display.theme_dark_end")) { paint_sb_right(); /* 状态栏文字/底色随主题刷新(整条标脏) */ } + /* M2.5x:字体文件(font.file)变更 → 立即 unload + reload(若已加载)。 + * 置 mfont_forced 跳过 meow_font_ensure 的 SDK 复用捷径,强制走 + * font.file → 卡默认 → 固件 链;全失败则回退 5x7 点阵(系统不死)。 */ + else if (!strcmp(r->key, "font.file")) { + mfont_ready = 0; + mfont_forced = 1; + if (!meow_font_ensure()) + logmsg("font.file: reload failed -> dotmatrix fallback"); + else + logmsg("font.file: reloaded"); + meow_font_reload_multifont(); /* M2.5x:全局字体变更 → 重配多字体(各类跟随全局者随之更新) */ + meow_dirty_full(); /* 字体变化可能影响所有文本渲染,整帧重绘 */ + } + /* M2.5x 多字体:开关或任一类别字体变更 → 重新去重装载类别字库并重绘(多字体可能影响文本渲染)。 */ + else if (!strcmp(r->key, "display.multi_font") || !strncmp(r->key, "font.class_", 11)) { + meow_font_reload_multifont(); + meow_dirty_full(); + } if (save_now) meow_settings_save(); } static void s_set_int_value(const srow_t *r, int v, int save_now) @@ -4993,13 +5412,15 @@ static void draw_settings_page(int sel, int scroll_off) /* M2.38 Bug5:整屏背景替代带圆角的单张卡片(用户反馈圆角非全屏底很怪)。 * 内容区从 SET_CARD_Y 起整屏 fill surface_variant、去圆角、满宽直到屏幕底; - * 状态栏(0-40)与标题区(40-92)仍为 surface,标题/分隔线/分组 outline/选中高亮/滚动条布局不变。 */ + * 状态栏(0-28)与标题区(28-92)仍为 surface,标题/分隔线/分组 outline/选中高亮/滚动条布局不变。 */ fill_rect(0, SET_CARD_Y, W, H - SET_CARD_Y, th->surface_variant); int clip_top = SET_CONTENT_Y, clip_bot = SET_CONTENT_BOT; s_sel_track_y = -1; for (int i = 0; i < SMENU_N; i++) { - int sy = SET_CONTENT_Y + i * SROW_H - scroll_off; + int pos = s_vis_of(i); + if (pos < 0) continue; /* 隐藏行(多字体关闭时 6 个 class 行) */ + int sy = SET_CONTENT_Y + pos * SROW_H - scroll_off; if (sy + SROW_H < clip_top || sy >= clip_bot) continue; /* 视野外行跳过(配滚动) */ const srow_t *r = &SMENU[i]; int selected = (i == sel); @@ -5055,8 +5476,9 @@ static void draw_settings_page(int sel, int scroll_off) } /* 滚动条(内容超出视口时显示 thumb;on_surface_variant 弱化) */ - if (SMENU_N * SROW_H > SET_CONTENT_H) { - int total = SMENU_N * SROW_H; + int vis_total = s_vis_count() * SROW_H; + if (vis_total > SET_CONTENT_H) { + int total = vis_total; int thumb_h = SET_CONTENT_H * SET_CONTENT_H / total; if (thumb_h < 24) thumb_h = 24; int th_y = SET_CONTENT_Y + (SET_CONTENT_H - thumb_h) * scroll_off / (total - SET_CONTENT_H); @@ -5077,6 +5499,20 @@ static void draw_settings_page(int sel, int scroll_off) } } +/* M2.5x 多字体:class 行下拉的「跟随全局」选项(值=空串)在 write 路径经此映射回真实存储值。 */ +static const char *s_preset_value(const srow_t *r, int pick) +{ + int is_cls = r->key && !strncmp(r->key, "font.class_", 11); + if (is_cls) { + if (pick == 0) return ""; /* 跟随全局 font.file */ + pick--; + } + int pn = 0; + const char *const *pps = s_row_presets(r, &pn); + if (pick < 0 || pick >= pn) return is_cls ? "" : (pps && pn > 0 ? pps[0] : ""); + return pps[pick]; +} + /* ---- 选择器子对话框(MD3 §3.3:居中圆角卡片,列选项;中文标签)---- * ENUM 用 meow_settings_enum_names/enum_label;PRESET 用本地预设表。 * 返回选中索引;取消返回 -1。阻塞(自开输入设备)。 @@ -5100,14 +5536,24 @@ static int s_dropdown(const srow_t *r) } else { char cstr[MEOW_SET_MAX_LEN]; meow_settings_get_str(r->key, cstr, sizeof cstr); - n = r->preset_n; - if (n > S_NET_MAX) n = S_NET_MAX; - for (int i = 0; i < n; i++) { - names[i] = r->presets[i]; - snprintf(labels[i], sizeof labels[i], "%s", - (r->preset_labels && r->preset_labels[i][0]) ? r->preset_labels[i] : r->presets[i]); - item[i] = labels[i]; - if (!strcmp(r->presets[i], cstr)) cur = i; + int pn = 0; + const char *const *pps = s_row_presets(r, &pn); + int is_cls = r->key && !strncmp(r->key, "font.class_", 11); + int base = is_cls ? 1 : 0; /* class 行 0 号留给「跟随全局」 */ + int cnt = pn; if (cnt > S_NET_MAX - base) cnt = S_NET_MAX - base; + n = base + cnt; + if (is_cls) { + names[0] = ""; + snprintf(labels[0], sizeof labels[0], "跟随全局字体"); + item[0] = labels[0]; + if (!cstr[0]) cur = 0; + } + for (int i = 0; i < cnt; i++) { + names[base + i] = pps[i]; + snprintf(labels[base + i], sizeof labels[base + i], "%s", + (r->preset_labels && r->preset_labels[i][0]) ? r->preset_labels[i] : pps[i]); + item[base + i] = labels[base + i]; + if (!strcmp(pps[i], cstr)) cur = base + i; } } if (n <= 0) return -1; @@ -5130,7 +5576,7 @@ static int s_dd_modal(const char *title, const char *item[S_NET_MAX], int n, int int opt_row = 48, pad = 20, title_h = 40; int d_h = title_h + n * opt_row + pad; int d_w = 348, d_x = (W - d_w) / 2, d_y = (H - d_h) / 2; - if (d_y < 40) d_y = 40; + if (d_y < SB_BAR_H) d_y = SB_BAR_H; /* 下拉不压状态栏(栏高 40→28,随常量) */ if (H - d_y - d_h < 20) d_y = H - d_h - 20; struct keypoll kp_power, kp_keys; @@ -5260,6 +5706,10 @@ static void run_settings_page(void) int scroll_off = 0; int H = fb_yres; + /* M2.5x:进设置页扫描一次 TF 卡 MeowOS/fonts//,刷新「字体文件」下拉。 + * 进设置时卡通常在位;此步只枚举/校验,不加载(加载由 meow_font_ensure 按 font.file 决定)。 */ + meow_font_scan_presets(); + struct keypoll kp_power, kp_keys; kp_open(&kp_power, DEV_POWER); kp_open(&kp_keys, DEV_KEYS); struct kd_state kd_vold, kd_volu, kd_play, kd_pwr; @@ -5585,7 +6035,7 @@ static void run_settings_page(void) int pick = s_dropdown(r); if (pick >= 0) { if (r->ctrl == SC_ENUM) meow_settings_set_enum(r->key, pick); - else meow_settings_set_str(r->key, r->presets[pick]); + else meow_settings_set_str(r->key, s_preset_value(r, pick)); s_commit(r, 1); } /* M2.38 Bug3:s_dropdown 返回后彻底排空本页 tfd + @@ -5636,7 +6086,7 @@ static void run_settings_page(void) if (sel_changed) { scroll_off = s_clamp_scroll(sel, scroll_off); sel_changed = 0; } draw_settings_page(sel, scroll_off); /* 局部标脏:标题+卡片+滚动条+底部(y=42 以下全部,覆盖主题/选中/值变化), - * 状态栏(0-40)由 draw_statusbar/draw_clock 各自维护,不在此列。 + * 状态栏(0-28)由 draw_statusbar/draw_clock 各自维护,不在此列。 * 选择器关闭后(after_modal)其全屏 dim 把状态栏也压暗了,须整帧重绘还原。 */ if (after_modal) { meow_dirty_full(); after_modal = 0; } else meow_dirty_add(0, 42, fb_xres, fb_yres - 42); @@ -6280,14 +6730,17 @@ static int launch_app(struct app *a) char *argv[] = { a->exec, NULL }; char env_path[320], env_log[320], env_data[320], env_cache[320]; char env_app[MEOW_APP_ID_MAX + 16], env_lvl[48]; - char *envp[12]; + char env_fpng[400], env_fidx[400], env_fapp[96]; + char env_mf[24]; + char env_cls[MEOW_FCLS_COUNT][2][400]; + char *envp[32]; /* PATH,HOME,log_dir,log_app,log_lvl,font 全局(3),多字体(1),class(≤12),data,cache */ int ne = 0; const char *cp = getenv("PATH"); snprintf(env_path, sizeof env_path, "PATH=%s", cp ? cp : "/usr/bin:/bin"); envp[ne++] = env_path; envp[ne++] = "HOME=/root"; if (log_dir[0]) { snprintf(env_log, sizeof env_log, "MEOW_LOG_DIR=%s", log_dir); envp[ne++] = env_log; } - /* M2.52:MEOW_LOG_APP=app_id(目录名,与注册表一致;无 id 则跳过,SDK 兜底 "app") */ + /* M2.52:MEOW_LOG_APP=app_id(=app.json.package,缺省回退目录名;与注册表一致;无 id 则跳过,SDK 兜底 "app") */ if (a->id[0]) { snprintf(env_app, sizeof env_app, "MEOW_LOG_APP=%s", a->id); envp[ne++] = env_app; } /* M2.52:MEOW_LOG_LEVEL 读全局 log.level(error/warn/info/debug→索引0/1/2/3), * SDK 只认大写字符串名(ERROR/WARN/INFO/DEBUG),故转大写注入;缺省兜底 info。 */ @@ -6299,6 +6752,55 @@ static int launch_app(struct app *a) snprintf(env_lvl, sizeof env_lvl, "MEOW_LOG_LEVEL=%s", lvl_name[lv]); envp[ne++] = env_lvl; } + /* M2.x:注入「当前生效字体」= 系统 font.file(launcher 已 load 的那套), + * SDK meow_ui_gfx.load_font 优先读 MEOW_FONT_PNG/IDX,app 未自带字库(§5.7 load_handle + * 未实现)时跟随系统字体,不再硬编码 MeowAPPs/fonts。无卡/无可用字体则跳过注入, + * SDK 自行回退卡默认→固件→点阵。 */ + { + static const char *cls_env_png[MEOW_FCLS_COUNT] = { + "MEOW_FONT_CLS_CJK_PNG", "MEOW_FONT_CLS_CJK_SYM_PNG", "MEOW_FONT_CLS_NUM_PNG", + "MEOW_FONT_CLS_LATIN_PNG", "MEOW_FONT_CLS_LATIN_SYM_PNG", "MEOW_FONT_CLS_OTHER_PNG" }; + static const char *cls_env_idx[MEOW_FCLS_COUNT] = { + "MEOW_FONT_CLS_CJK_IDX", "MEOW_FONT_CLS_CJK_SYM_IDX", "MEOW_FONT_CLS_NUM_IDX", + "MEOW_FONT_CLS_LATIN_IDX", "MEOW_FONT_CLS_LATIN_SYM_IDX", "MEOW_FONT_CLS_OTHER_IDX" }; + static const char *cls_keys[MEOW_FCLS_COUNT] = { + "font.class_cjk", "font.class_cjk_sym", "font.class_num", + "font.class_latin", "font.class_latin_sym", "font.class_other" }; + char fpng[400], fidx[400], fam[96]; + int have_global; + int mf_on = 0; + meow_settings_get_bool("display.multi_font", &mf_on); + snprintf(env_mf, sizeof env_mf, "MEOW_MULTI_FONT=%d", mf_on ? 1 : 0); + envp[ne++] = env_mf; + have_global = (meow_font_effective_paths(fpng, sizeof fpng, fidx, sizeof fidx, + fam, sizeof fam) == 0); + if (have_global) { + snprintf(env_fpng, sizeof env_fpng, "MEOW_FONT_PNG=%s", fpng); + snprintf(env_fidx, sizeof env_fidx, "MEOW_FONT_IDX=%s", fidx); + snprintf(env_fapp, sizeof env_fapp, "MEOW_FONT_APP=%s", fam); + envp[ne++] = env_fpng; + envp[ne++] = env_fidx; + envp[ne++] = env_fapp; + logmsg("launch: font env %s/%s (family=%s)", fpng, fidx, fam); + } + /* M2.5x 多字体:每类注入 png/idx(命中则覆盖全局;类值为空=跟随全局,不注入)。 + * SDK 读这些 env 构建多字体映射;同 family 去重由 SDK 侧 meow_font_config_multifont 处理。 */ + if (mf_on) + for (int c = 0; c < MEOW_FCLS_COUNT; c++) { + char ref[MEOW_SET_MAX_LEN], abs[400], idxabs[400]; + if (meow_settings_get_str(cls_keys[c], ref, sizeof ref) != 0 || !ref[0]) continue; + if (meow_font_resolve_path(ref, abs, sizeof abs) != 0 || access(abs, R_OK) != 0) continue; + snprintf(idxabs, sizeof idxabs, "%s", abs); + size_t n4 = strlen(idxabs); + if (n4 > 4 && !strcmp(idxabs + n4 - 4, ".png")) { + idxabs[n4-4]='.'; idxabs[n4-3]='i'; idxabs[n4-2]='d'; idxabs[n4-1]='x'; idxabs[n4]=0; + } + snprintf(env_cls[c][0], sizeof env_cls[c][0], "%s=%s", cls_env_png[c], abs); + snprintf(env_cls[c][1], sizeof env_cls[c][1], "%s=%s", cls_env_idx[c], idxabs); + envp[ne++] = env_cls[c][0]; + envp[ne++] = env_cls[c][1]; + } + } char pkg[64]; if (meow_pkg_name(a->exec, pkg, sizeof pkg) == 0) { snprintf(env_data, sizeof env_data, "MEOW_DATA_DIR=/usr/data/mnt/sd_0/MeowData/%s/data", pkg); @@ -6510,7 +7012,8 @@ static void usage(void) " [--sim-power-pick N] [--sim-no-exec]\n" " [--dumpkeys N] [--scan] [--sysdump]\n" " [--pkg-install ] [--pkg-export ]\n" - " [--ipc-only N] [--ipc-sock ] [--appinfo ]\n"); + " [--ipc-only N] [--ipc-sock ] [--appinfo ]\n" + " [--fontscan ]\n"); } int main(int argc, char **argv) @@ -6519,6 +7022,7 @@ int main(int argc, char **argv) int do_scan = 0, do_sysdump = 0, sim_vol = 0; long ipc_only_ms = 0; const char *appinfo_path = NULL; + const char *fontscan_root = NULL; /* --fontscan :宿主/qemu 测试字形扫描 */ const char *pkg_install_mpk = NULL, *pkg_export_name = NULL, *pkg_export_out = NULL; for (int i = 1; i < argc; i++) { if (!strcmp(argv[i], "--sim-hotkey") && i + 1 < argc) sim_hotkey = atol(argv[++i]); @@ -6537,6 +7041,7 @@ int main(int argc, char **argv) snprintf(ipc_sock_path, sizeof ipc_sock_path, "%s", argv[++i]); } else if (!strcmp(argv[i], "--appinfo") && i + 1 < argc) appinfo_path = argv[++i]; + else if (!strcmp(argv[i], "--fontscan") && i + 1 < argc) fontscan_root = argv[++i]; else if (!strcmp(argv[i], "--pkg-install") && i + 1 < argc) pkg_install_mpk = argv[++i]; else if (!strcmp(argv[i], "--pkg-export") && i + 2 < argc) { pkg_export_name = argv[++i]; pkg_export_out = argv[++i]; } else { usage(); return 1; } @@ -6556,6 +7061,20 @@ int main(int argc, char **argv) * menu open so edits apply without a daemon restart) */ settings_load(); /* M2.11:读系统设置(audio.volume 等),供音量/状态栏使用 */ + /* M2.5x:启动扫描一次 TF 卡 MeowOS/fonts//,填充「字体文件」下拉 preset。 + * 无卡/无字库目录 → 仅默认字体(等价现状)。进设置页时 run_settings_page 会再扫一次。 */ + meow_font_scan_presets(); + + if (fontscan_root) { + /* M2.5x 测试钩子:扫描指定卡根下的 MeowOS/fonts// 并打印 preset(qemu/宿主可测)。 + * 用于验证 font.json 解析/三件套/缺件跳过/sha 校验 + preset 动态化逻辑。 */ + meow_font_scan_presets_root(fontscan_root); + printf("fonts=%d\n", font_presets_n); + for (int i = 0; i < font_presets_n; i++) + printf(" %d %s -> %s\n", i, font_preset_labels[i], font_presets[i]); + return 0; + } + if (sim_vol > 0) { /* M2.14 sim:验证线性渐变——step=1/2/5 下设定音量按 1% 逐级爬升/回落, * 每 1% 经 meow_dac_vol_write 写一次真实 DAC,打点进 log(stderr)。 */ diff --git a/patch_v2/meow_launcherd/meow_settings.c b/patch_v2/meow_launcherd/meow_settings.c index fec094a..72a41c2 100644 --- a/patch_v2/meow_launcherd/meow_settings.c +++ b/patch_v2/meow_launcherd/meow_settings.c @@ -77,6 +77,9 @@ static const meow_setting_def g_defs[] = { { "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.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 }, @@ -92,7 +95,15 @@ static const meow_setting_def g_defs[] = { /* M2.38:设置页滑动方向切换(0=标准:手指上滑→列表往下翻;1=反向) */ { "input.swipe_invert", SET_BOOL, "0", 0, 0, NULL, NULL }, /* 字体/主题 */ - { "font.file", SET_STR, "MeowAPPs/fonts/font.png", 0,0, NULL, NULL }, + { "font.file", SET_STR, "MeowOS/fonts/default/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 }, { "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" }, @@ -205,6 +216,18 @@ static int clamp_brightness(int v) 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) { diff --git a/patch_v2/meow_launcherd/meow_settings.h b/patch_v2/meow_launcherd/meow_settings.h index 8b3ff04..34aac2c 100644 --- a/patch_v2/meow_launcherd/meow_settings.h +++ b/patch_v2/meow_launcherd/meow_settings.h @@ -70,6 +70,12 @@ int meow_settings_get_str (const char *key, char *buf, int buf_len); int meow_settings_get_enum(const char *key, int *out); /* 返回索引(名字->索引) */ int meow_settings_get_bool(const char *key, int *out); /* 0/1 */ +/* M2.53:返回当前用户亮度设定(display.brightness)经 clamp_brightness 后的值 + * (下限 display.brightness_min 默认 1=极暗但可见、上限 100=防闪屏)。 + * MeowOS 主屏接管时用它把亮度压回用户值,覆盖 HibyOS 播放器写入的背光下限 16/25。 + * settings 未初始化/键缺失时回退默认 101 再 clamp(→100);纯函数,宿主机可直接测。 */ +int meow_settings_brightness_clamped(void); + int meow_settings_set_int (const char *key, int v); /* clamp 到取值域 */ int meow_settings_set_str (const char *key, const char *v);/* 长度截断 MEOW_SET_MAX_LEN */ int meow_settings_set_enum(const char *key, int idx); /* 校验枚举合法,越界拒绝 */ diff --git a/patch_v2/onboard.sh b/patch_v2/onboard.sh index a0d38af..d8e7ca1 100755 --- a/patch_v2/onboard.sh +++ b/patch_v2/onboard.sh @@ -19,8 +19,8 @@ echo "== 2) 推送 ov.tgz / uinput_tap / hello ==" [ -f /tmp/ov.tar.gz ] || "$ROOT/patch_v2/make_ov.sh" $SCP /tmp/ov.tar.gz root@$DEV:/data/ov.tgz $SCP "$ROOT/patch_v2/uinput_tap/uinput_tap" root@$DEV:/data/uinput_tap -$SCP "$ROOT/patch_v2/apps_assets/apps/hello/hello" root@$DEV:/data/hello.bin -$SCP "$ROOT/patch_v2/apps_assets/apps/hello/app.json" root@$DEV:/data/hello_app.json +$SCP "$ROOT/patch_v2/apps_assets/apps/os.meow.hello/hello" root@$DEV:/data/hello.bin +$SCP "$ROOT/patch_v2/apps_assets/apps/os.meow.hello/app.json" root@$DEV:/data/hello_app.json $SSH root@$DEV 'chmod 755 /data/uinput_tap /data/hello.bin; md5sum /data/ov.tgz /data/uinput_tap /data/hello.bin' echo "== 3) 设备侧: 解包+bind+安全重启 ==" diff --git a/patch_v2/sdk/build_sdk.sh b/patch_v2/sdk/build_sdk.sh index 7d502e4..6589f06 100755 --- a/patch_v2/sdk/build_sdk.sh +++ b/patch_v2/sdk/build_sdk.sh @@ -9,10 +9,12 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架 rm -f meow_ui.a *.o -OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys stb_image" +# -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移) +OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol stb_image" +CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd" for f in $OBJS; do echo "== cc $f ==" - "$CC" -Os -static -c "$f.c" -o "$f.o" + "$CC" $CFLAGS -c "$f.c" -o "$f.o" done # meow_font.o(复用 launcher 字库,打进 meow_ui.a → 应用只链一个库) diff --git a/patch_v2/sdk/meow_notify_ipc.c b/patch_v2/sdk/meow_notify_ipc.c new file mode 100644 index 0000000..a4c768b --- /dev/null +++ b/patch_v2/sdk/meow_notify_ipc.c @@ -0,0 +1,243 @@ +/* + * meow_notify_ipc.c — MeowOS SDK 通知 → launcher IPC 桥(任务 #5,SDK↔IPC 关键断点)。 + * + * 让 meow_notify_send() 的通知真正经 AF_UNIX IPC 送到 launcher 中央存储(meow_notif_post) + * 并回读 MSG_NOTIFY_ACK 拿 launcher 分配的通知 id(nid)。协议帧与负载结构复用 launcher 的 + * meow_ipc.h(同源一份、杜绝两端漂移): + * - 帧头 16B:magic=0x4D4F5701 / version=1 / type / req_id / len(全小端 LE,mipsel 原生); + * - MSG_NOTIFY(0x0020) ↑ 负载 = m_notify_t;MSG_NOTIFY_ACK(0x0021) ↓ 负载 = m_ack_t(status=nid); + * - MSG_NOTIFY_DISMISS(0x0022) ↑ 负载 = uint32 nid。 + * + * 设计要点: + * - 只做「一次性 connect + send + read ACK」,不维护连接/订阅循环(与 meow_ipc.c 客户端同型); + * - 映射层(SDK 枚举 ↔ IPC 字段)集中在本文件:分类枚举 CAT_* ↔ 字符串 + * "general"/"message"/"email"/"event"/"progress"/"media";timeout_ms → expire_timeout: + * >0 该毫秒、0=不自动过期、-1(ONGOING)=0xFFFFFFFF(并置 ongoing=1);媒体类型置 media=1; + * - 优雅降级:无 socket(connect 失败)/写失败/ACK 超时(SO_RCVTIMEO)/非 ACK 应答 → + * 返回 0(调用方 meow_ui_notify.c 保留本地 id 兜底),绝不崩; + * - 线程安全:app_id 经 pthread_once 缓存;socket 路径每次调用读 env MEOW_IPC_SOCK 覆盖 + * (宿主/test 用),缺省 meow_ipc.h 的 MEOW_IPC_PATH(/usr/data/meow_ipc.sock)。 + * + * 约定:与 meow_ui_notify.c 同风格(M2.x 中文注释);本文件为纯传输层,不调 meow_log, + * 失败诊断由调用方 meow_ui_notify.c 的 notify_deliver() 统一打点。 + */ +#include "meow_notify_ipc.h" +#include "meow_ipc.h" /* launcher 端共享协议:帧头/m_notify_t/m_ack_t/MSG_* —— 防漂移 */ + +#include /* snprintf */ +#include /* getenv */ +#include +#include /* EINTR */ +#include +#include +#include +#include +#include + +/* ACK 读超时(ms):launcher 主循环为 IPC 单写者(eventfd 唤醒,正常 <10ms 响应), + * 放宽容错 500ms;超时视为降级(返回 0,不崩)。 */ +#define MEOW_NOTIFY_IPC_TIMEOUT_MS 500 + +/* app_id(env MEOW_LOG_APP,缺省 "app";与 meow_ui_log.c 的取值口径一致)。 */ +static pthread_once_t g_once = PTHREAD_ONCE_INIT; +static char g_app_id[MEOW_APP_ID_MAX] = "app"; + +static void ipc_init(void) +{ + const char *s = getenv("MEOW_LOG_APP"); + if (s && s[0]) snprintf(g_app_id, sizeof g_app_id, "%s", s); +} + +/* socket 路径:优先 env MEOW_IPC_SOCK(宿主/qemu sim 覆盖),缺省 MEOW_IPC_PATH。 */ +static const char *ipc_sock_path(void) +{ + const char *s = getenv("MEOW_IPC_SOCK"); + return (s && s[0]) ? s : MEOW_IPC_PATH; +} + +const char *meow_notify_ipc_app_id(void) +{ + pthread_once(&g_once, ipc_init); + return g_app_id; +} + +/* ============================================================ */ +/* 映射层: SDK meow_notify 枚举/字段 ↔ IPC m_notify_t 字段 */ +/* ============================================================ */ + +/* SDK 分类枚举 → IPC 字符串(与 meow_ui.h §9.1 的 MEOW_NOTIFY_CAT_* 序号对齐; + * 越界钳位为 "general"——同 meow_notify_category_str 的防御语义)。 */ +static const char *cat_to_str(meow_notify_category cat) +{ + static const char *t[] = { "general", "message", "email", "event", "progress", "media" }; + if ((int)cat >= 0 && (int)cat < (int)MEOW_NOTIFY_CAT_COUNT) return t[cat]; + return t[MEOW_NOTIFY_CAT_GENERAL]; +} + +/* SDK timeout_ms → IPC expire_timeout(存储/显示端语义): + * >0 → 该毫秒数后过期;0 → 不自动过期(NEVER);<0 → 常驻(ONGOING)= 0xFFFFFFFF。 */ +static uint32_t timeout_to_expire(int timeout_ms) +{ + if (timeout_ms < 0) return 0xFFFFFFFFu; /* ONGOING:常驻,不自动过期 */ + return (uint32_t)timeout_ms; /* 0(NEVER)/ >0(毫秒) */ +} + +/* 构建 IPC 负载 m_notify_t(app_icon/actions/hints v1 留空,M2b D-Bus 桥再填)。 + * 仅在持有调用者互斥之外、发送前调用(纯 CPU 拷贝,不阻塞)。 */ +static void build_m_notify(const meow_notify *n, uint32_t replaces_id, m_notify_t *mn) +{ + memset(mn, 0, sizeof *mn); + snprintf(mn->app_id, sizeof mn->app_id, "%s", meow_notify_ipc_app_id()); + snprintf(mn->category, sizeof mn->category, "%s", cat_to_str(n->cat)); + snprintf(mn->summary, sizeof mn->summary, "%s", n->title ? n->title : ""); + snprintf(mn->body, sizeof mn->body, "%s", n->body ? n->body : ""); + snprintf(mn->target_activity, sizeof mn->target_activity, "%s", + n->target_activity ? n->target_activity : ""); + mn->replaces_id = replaces_id; /* 0=新建;>0=更新(不存在则落为新建,见 launcher) */ + mn->expire_timeout = timeout_to_expire(n->timeout_ms); + mn->progress = (n->progress < -1) ? -1 : (n->progress > 100 ? 100 : n->progress); + mn->media = (n->cat == MEOW_NOTIFY_CAT_MEDIA) ? 1 : 0; + mn->ongoing = (n->timeout_ms == MEOW_NOTIFY_TIMEOUT_ONGOING) ? 1 : 0; +} + +/* ============================================================ */ +/* 传输层(AF_UNIX SOCK_STREAM;一次性 connect + send + read) */ +/* ============================================================ */ + +/* 连接 socket。失败返回 -1(不崩;调用方降级)。 */ +static int conn_path(const char *path) +{ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) return -1; + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, path, sizeof a.sun_path - 1); + if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { + close(fd); + return -1; + } + return fd; +} + +/* 写满 n 字节(EINTR 重试);任一次写失败返回 0。 */ +static int write_all(int fd, const uint8_t *p, size_t n) +{ + size_t off = 0; + while (off < n) { + ssize_t w = write(fd, p + off, n - off); + if (w < 0) { if (errno == EINTR) continue; return 0; } + if (w == 0) return 0; + off += (size_t)w; + } + return 1; +} + +/* 读满 n 字节(超时/断开返回 0)。 */ +static int read_all(int fd, uint8_t *p, size_t n) +{ + size_t off = 0; + while (off < n) { + ssize_t r = read(fd, p + off, n - off); + if (r < 0) { if (errno == EINTR) continue; return 0; } + if (r == 0) return 0; + off += (size_t)r; + } + return 1; +} + +/* 组装并写出 16B(LE)帧头 + 负载。 */ +static int send_frame(int fd, uint16_t type, uint32_t req_id, + const void *payload, uint32_t len) +{ + uint8_t hdr[MEOW_IPC_HDR_LEN]; + uint32_t magic = MEOW_IPC_MAGIC; + uint16_t ver = MEOW_IPC_VERSION; + memcpy(hdr + 0, &magic, 4); + memcpy(hdr + 4, &ver, 2); + memcpy(hdr + 6, &type, 2); + memcpy(hdr + 8, &req_id, 4); + memcpy(hdr + 12, &len, 4); + if (!write_all(fd, hdr, sizeof hdr)) return 0; + if (len && payload && !write_all(fd, payload, len)) return 0; + return 1; +} + +/* 给 conn 设 ACK 读超时(失败忽略——超时降级由 read_all 兜底)。 */ +static void set_ack_timeout(int fd) +{ + struct timeval tv; + tv.tv_sec = MEOW_NOTIFY_IPC_TIMEOUT_MS / 1000; + tv.tv_usec = (MEOW_NOTIFY_IPC_TIMEOUT_MS % 1000) * 1000; + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); +} + +uint32_t meow_notify_ipc_send(const meow_notify *n, uint32_t replaces_id) +{ + m_notify_t mn; + int fd; + if (!n) return 0; + build_m_notify(n, replaces_id, &mn); + + fd = conn_path(ipc_sock_path()); + if (fd < 0) return 0; /* 无 socket:优雅降级 */ + set_ack_timeout(fd); + + if (!send_frame(fd, MSG_NOTIFY, 0x4E4F5449u /* "NOTI" */, &mn, (uint32_t)sizeof mn)) { + close(fd); /* 写失败:降级 */ + return 0; + } + + /* 读回 16B 应答头 + m_ack_t,取 launcher 分配的通知 id(a.status)。 */ + uint8_t rhdr[MEOW_IPC_HDR_LEN]; + uint32_t nid = 0; + if (read_all(fd, rhdr, sizeof rhdr)) { + uint32_t rmagic, rlen; + uint16_t rver, rtype; + memcpy(&rmagic, rhdr + 0, 4); + memcpy(&rver, rhdr + 4, 2); + memcpy(&rtype, rhdr + 6, 2); + memcpy(&rlen, rhdr + 12, 4); + if (rmagic == MEOW_IPC_MAGIC && rver == MEOW_IPC_VERSION && + rtype == MSG_NOTIFY_ACK && rlen >= sizeof(m_ack_t)) { + uint8_t pay[sizeof(m_ack_t)]; + if (read_all(fd, pay, sizeof(m_ack_t))) { + m_ack_t a; + memcpy(&a, pay, sizeof a); + if (a.code == MEOW_OK) nid = a.status; /* 落库成功 → nid */ + } + } + } + close(fd); + return nid; /* 非 ACK/超时 → 0(降级) */ +} + +void meow_notify_ipc_dismiss(uint32_t nid) +{ + int fd; + if (nid == 0) return; + fd = conn_path(ipc_sock_path()); + if (fd < 0) return; /* 无 socket:静默 */ + set_ack_timeout(fd); + if (!send_frame(fd, MSG_NOTIFY_DISMISS, 0x4E4F5444u /* "NOTD" */, &nid, (uint32_t)sizeof nid)) { + close(fd); /* 写失败:静默 */ + return; + } + /* 读回应答(launcher 对 DISMISS 回 MSG_ACK,见 meow_ipc.c cmd_dismiss)。虽为 + * best-effort,但读回确保 launcher 已处理完撤下再关连接(避免写后即关的丢帧竞态)。 */ + uint8_t rhdr[MEOW_IPC_HDR_LEN]; + if (read_all(fd, rhdr, sizeof rhdr)) { + uint32_t rmagic, rlen; + uint16_t rver, rtype; + memcpy(&rmagic, rhdr + 0, 4); + memcpy(&rver, rhdr + 4, 2); + memcpy(&rtype, rhdr + 6, 2); + memcpy(&rlen, rhdr + 12, 4); + if (rmagic == MEOW_IPC_MAGIC && rver == MEOW_IPC_VERSION && + rtype == MSG_ACK && rlen >= sizeof(m_ack_t)) { + uint8_t pay[sizeof(m_ack_t)]; + (void)read_all(fd, pay, sizeof(m_ack_t)); /* 读到即确认;内容不关心 */ + } + } + close(fd); +} diff --git a/patch_v2/sdk/meow_notify_ipc.h b/patch_v2/sdk/meow_notify_ipc.h new file mode 100644 index 0000000..c1da243 --- /dev/null +++ b/patch_v2/sdk/meow_notify_ipc.h @@ -0,0 +1,44 @@ +/* + * meow_notify_ipc.h — MeowOS SDK 通知 → launcher IPC 桥(内部头,不随 meow_ui.h 发布给应用)。 + * + * 定位(任务 #5 SDK↔IPC 桥关键断点):让框架 app 调 meow_notify_send() 的通知真正经 + * AF_UNIX IPC 送到 launcher(meow_launcherd)中央存储(meow_notif.c 的 meow_notif_post), + * 通知中心(阶段 B · M5)才能显示。消息通道(FRAMEWORK_DESIGN §1 / meow_ipc.h): + * - MSG_NOTIFY 0x0020 ↑:推送/更新通知(负载 m_notify_t); + * - MSG_NOTIFY_ACK 0x0021 ↓:落库结果(m_ack_t,status=launcher 分配的通知 id nid); + * - MSG_NOTIFY_DISMISS 0x0022 ↑:撤下通知(负载 uint32 nid)。 + * + * 约定:只做「一次性发送 + 读 ACK」,不造 IPC 订阅循环;SDK 本地活动表(meow_ui_notify.c) + * 保留(app 进程内即时查询/配额自检),真正的通知中心数据在 launcher 中央存储,二者以 + * 本接口经 IPC 同步。全部失败路径优雅降级(无 socket/写失败/超时 → 返回 0,不崩)。 + * + * 本接口只暴露「SDK 类型出/进 + 状态码」,不把 m_notify_t / 帧格式泄漏给 meow_ui_notify.c; + * 映射层(SDK 枚举 ↔ IPC 字符串/uint32)实现在 meow_notify_ipc.c 内部。 + */ +#ifndef MEOW_NOTIFY_IPC_H +#define MEOW_NOTIFY_IPC_H + +#include +#include "meow_ui.h" /* meow_notify */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* 当前 app 的 app_id(env MEOW_LOG_APP,缺省 "app";pthread_once 缓存,线程安全)。 */ +const char *meow_notify_ipc_app_id(void); + +/* 把一条 SDK meow_notify 映射为 m_notify_t 并一次性发送 MSG_NOTIFY,阻塞读 + * MSG_NOTIFY_ACK 取 launcher 分配的通知 id(nid)。replaces_id:0=新建;>0=更新已存在 + * 通知(与 freedesktop 语义一致)。成功返回 nid(>0);无 socket/写失败/超时/非 ACK → + * 返回 0(调用方保留本地 id 兜底,不崩)。 */ +uint32_t meow_notify_ipc_send(const meow_notify *n, uint32_t replaces_id); + +/* 一次性发送 MSG_NOTIFY_DISMISS 撤下 launcher 侧通知。best-effort:写失败/无 socket + * 静默(不崩、不回读 ACK)。 */ +void meow_notify_ipc_dismiss(uint32_t nid); + +#ifdef __cplusplus +} +#endif +#endif /* MEOW_NOTIFY_IPC_H */ diff --git a/patch_v2/sdk/meow_ui.h b/patch_v2/sdk/meow_ui.h index b4f0552..bca5b25 100644 --- a/patch_v2/sdk/meow_ui.h +++ b/patch_v2/sdk/meow_ui.h @@ -32,7 +32,7 @@ extern "C" { #define MEOW_KEY_VOLUMEDOWN 114 #define MEOW_KEY_VOLUMEUP 115 #define MEOW_KEY_PLAYPAUSE 164 -#define MEOW_KEY_PREVIOUS 19 /* KEY_PREVIOUSSONG */ +#define MEOW_KEY_PREVIOUS 165 /* KEY_PREVIOUSSONG=165(上一曲;19 是另一码,勿混用) */ #define MEOW_KEY_NEXT 163 /* KEY_NEXTSONG */ /* 滑动方向(on_swipe 的 dir 参数) */ diff --git a/patch_v2/sdk/meow_ui_gfx.c b/patch_v2/sdk/meow_ui_gfx.c index 6397fef..83a6abe 100644 --- a/patch_v2/sdk/meow_ui_gfx.c +++ b/patch_v2/sdk/meow_ui_gfx.c @@ -29,10 +29,14 @@ #define FBIO_WAITFORVSYNC 0x4620 #define FB_DEV "/dev/fb0" -#define MEOW_FONT_PNG_CARD "/usr/data/mnt/sd_0/MeowAPPs/fonts/font.png" -#define MEOW_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowAPPs/fonts/font.idx" +#define MEOW_FONT_PNG_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/font.png" +#define MEOW_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/font.idx" #define MEOW_FONT_PNG_SYS "/usr/share/meowos/font/font.png" #define MEOW_FONT_IDX_SYS "/usr/share/meowos/font/font.idx" +/* 默认字库(卡 MeowOS/fonts/default)与固件内置之外,launcher 会把「当前生效字体」 + * 经环境变量下发给 app(见 meow_launcherd.c launch_app / meow_font_effective_paths): + * MEOW_FONT_PNG / MEOW_FONT_IDX = 绝对路径;MEOW_FONT_APP = 或 "default"。 + * SDK load_font 优先读 env(匹配系统 font.file),无则回退卡默认 → 固件。 */ #define MUI_DEFAULT_W 480 #define MUI_DEFAULT_H 720 @@ -572,13 +576,55 @@ int meow_gfx_is_device(void) { return g_fb_mem != NULL; } int meow_gfx_mfont_ready(void) { return g_mfont_ready; } /* ================= 初始化/销毁 ================= */ +/* 字库加载优先级(M2.x): + * 1) 环境变量 MEOW_FONT_PNG/MEOW_FONT_IDX(launcher 注入的「当前生效字体」= 系统 font.file) + * —— app 未自带字库(§5.7 load_handle 未实现)时跟随系统 font.file; + * 2) 卡默认 MeowOS/fonts/default/font.png(不再硬编码 MeowAPPs/fonts); + * 3) 固件 /usr/share/meowos/font/font.png。 + * 全失败 → g_mfont_ready=0,text_draw 回退 5x7 点阵(系统不死)。 */ static int load_font(void) { + const char *ep = getenv("MEOW_FONT_PNG"); + const char *ei = getenv("MEOW_FONT_IDX"); + if (ep && ep[0] && ei && ei[0] && meow_font_load(ep, ei) == 0) return 1; if (meow_font_load(MEOW_FONT_PNG_CARD, MEOW_FONT_IDX_CARD) == 0) return 1; if (meow_font_load(MEOW_FONT_PNG_SYS, MEOW_FONT_IDX_SYS) == 0) return 1; return 0; } +/* M2.5x 多字体:读 launcher 注入的环境变量,构建 6 字符类 → 字库的映射(同 family 去重、 + * ≤4 套上限由 meow_font_config_multifont 统一处理)。launcher 注入(见 meow_launcherd.c + * launch_app):MEOW_MULTI_FONT=0/1;MEOW_FONT_PNG/IDX=全局(保留);每类 + * MEOW_FONT_CLS__PNG/IDX(命中则覆盖全局;未注入 = 该类跟随全局)。 + * 仅当全局字库已装载(g_mfont_ready)才配置;关/无 env → 单字体,零额外开销。 */ +static void meow_gfx_load_multifont(void) +{ + static const char *cls_env[MEOW_FCLS_COUNT][2] = { + { "MEOW_FONT_CLS_CJK_PNG", "MEOW_FONT_CLS_CJK_IDX" }, + { "MEOW_FONT_CLS_CJK_SYM_PNG", "MEOW_FONT_CLS_CJK_SYM_IDX" }, + { "MEOW_FONT_CLS_NUM_PNG", "MEOW_FONT_CLS_NUM_IDX" }, + { "MEOW_FONT_CLS_LATIN_PNG", "MEOW_FONT_CLS_LATIN_IDX" }, + { "MEOW_FONT_CLS_LATIN_SYM_PNG", "MEOW_FONT_CLS_LATIN_SYM_IDX" }, + { "MEOW_FONT_CLS_OTHER_PNG", "MEOW_FONT_CLS_OTHER_IDX" }, + }; + const char *emf = getenv("MEOW_MULTI_FONT"); + int on = emf && emf[0] == '1'; + const char *gp = getenv("MEOW_FONT_PNG"); + const char *gi = getenv("MEOW_FONT_IDX"); + meow_font_class_cfg cfg[MEOW_FCLS_COUNT]; + memset(cfg, 0, sizeof cfg); + for (int c = 0; c < MEOW_FCLS_COUNT; c++) { + const char *p = getenv(cls_env[c][0]); + const char *idx = getenv(cls_env[c][1]); + if (p && p[0] && idx && idx[0]) { cfg[c].png = p; cfg[c].idx = idx; } + } + int distinct = 0, truncated = 0; + if (meow_font_config_multifont(on, gp, gi, cfg, &distinct, &truncated) != 0) + meow_log(MEOW_LOG_WARN, "multi_font: config failed, single-font fallback"); + else if (truncated) + meow_log(MEOW_LOG_WARN, "multi_font: >%d distinct fonts -> capped (more memory!)", distinct); +} + int meow_ui_init(const meow_ui_opts *opts) { if (g_init_ok) return 0; @@ -623,6 +669,7 @@ int meow_ui_init(const meow_ui_opts *opts) meow_font_set_fb((unsigned)g_xres, (unsigned)g_yres, (unsigned)g_line); g_mfont_ready = load_font(); + if (g_mfont_ready) meow_gfx_load_multifont(); /* M2.5x 多字体(读 env;关/无 env = 单字体) */ const char *dp = getenv("MEOW_UI_DUMP"); if (dp && *dp) g_dump_path = strdup(dp); diff --git a/patch_v2/sdk/meow_ui_input.c b/patch_v2/sdk/meow_ui_input.c index aa55289..fed0fc6 100644 --- a/patch_v2/sdk/meow_ui_input.c +++ b/patch_v2/sdk/meow_ui_input.c @@ -219,9 +219,9 @@ static void *input_thread(void *arg) else if (!val && t_down) touch_release(); } else if (code == MEOW_KEY_POWER || code == MEOW_KEY_VOLUMEDOWN || code == MEOW_KEY_VOLUMEUP || code == MEOW_KEY_PLAYPAUSE || - code == MEOW_KEY_PREVIOUS || code == MEOW_KEY_NEXT || code == 165) { + code == MEOW_KEY_PREVIOUS || code == MEOW_KEY_NEXT || code == 19) { /* M2.22(A)/M2.24 纪律:物理键(电源 116 / 音量 114,115 / 播放 164 / - * 上一曲 19,165 / 下一曲 163)全部是系统语义 —— watchdog/launcher 统一消费, + * 上一曲 165(MEOW_KEY_PREVIOUS)/19(旧码) / 下一曲 163)全部是系统语义 —— watchdog/launcher 统一消费, * 绝不下发 APP。应用不碰物理键、不把任何物理键映射成确认/exit/返回/翻页 * (KEY_CONTROL_DESIGN §1.3 / APPS.md §3.4.1);APP 只接收 tap/swipe/tick。 * 忽略该键,不发 MEOW_EV_KEY。 */ diff --git a/patch_v2/sdk/meow_ui_notify.c b/patch_v2/sdk/meow_ui_notify.c index 2a02957..dbad728 100644 --- a/patch_v2/sdk/meow_ui_notify.c +++ b/patch_v2/sdk/meow_ui_notify.c @@ -12,15 +12,20 @@ * (替换);媒体类型单例(每 app 仅保留最近 1 条,重发替换——可多媒体 App)。 * - 超时语义 [实现定案]:timeout_ms > 0 为该毫秒数后过期;0 = 不自动过期(直到 * meow_notify_cancel);-1 = 常驻(ONGOING),同样不自动过期。 - * - 落点:本阶段内部维护活动通知表 + 用既有 meow_log() 打日志作为落点;真实投递到 - * 通知中心 UI / D-Bus 是后续任务——meow_notify 结构已对齐 freedesktop 字段 - * (summary/body/…)为此预留,但不写死后端。 + * - 落点(任务 #5 SDK↔IPC 桥):本表登记后,经 IPC 把 MSG_NOTIFY(0x0020) 送到 + * launcher 中央存储(meow_notif.c 的 meow_notif_post),回读 MSG_NOTIFY_ACK(0x0021) + * 取 launcher 分配的通知 id(nid,存进 slot->ipc_id,供后续 replaces_id/dismiss 用)。 + * 通知中心 UI / D-Bus / TF 转储由 launcher 侧处理;SDK 本地活动表保留(app 进程内 + * 即时查询/配额自检)。无 socket/写失败/超时 → 优雅降级(保留本地 id 兜底,不崩)。 + * meow_notify 结构已对齐 freedesktop 字段(summary/body/…)为此预留。 * * 线程安全:pthread_mutex(同 meow_ui_log.c,launcher 前后台进程都会在已链 -lpthread - * 的应用里调用)。不依赖 /dev/fb0、不依赖板子,可宿主编译。内部只复用 - * meow_ui_time_ms()(单调毫秒)与 meow_log()(统一日志)两个既有 SDK 能力。 + * 的应用里调用),且 IPC 发送/读 ACK 在持锁之外进行(避免 socket I/O 阻塞其它查询)。 + * 不依赖 /dev/fb0、不依赖板子,可宿主编译。内部只复用 meow_ui_time_ms()(单调毫秒)、 + * meow_log()(统一日志)与 meow_notify_ipc_*(IPC 桥,纯传输层)三个既有/新增 SDK 能力。 */ #include "meow_ui.h" +#include "meow_notify_ipc.h" /* 任务 #5:通知 → launcher 中央存储的 IPC 桥(映射+传输) */ #include #include @@ -32,7 +37,8 @@ /* 活动通知槽(内部;标题/正文等复制副本,不持 app 外部指针,避免栈字符串悬垂) */ typedef struct { - uint32_t id; /* 唯一 id;0=空闲槽 */ + uint32_t id; /* 本地唯一 id(app 侧句柄);0=空闲槽 */ + uint32_t ipc_id; /* launcher 中央存储分配的通知 id(nid);0=未同步/降级 */ int active; /* 1=活动(未取消未过期),0=空闲 */ meow_notify_category cat; meow_notify_urgency urg; @@ -85,6 +91,7 @@ static void notify_purge_expired(void) if (s->timeout_ms > 0 && (now - s->created_ms) >= (uint64_t)s->timeout_ms) { s->active = 0; s->id = 0; + s->ipc_id = 0; } } } @@ -93,6 +100,7 @@ static void notify_purge_expired(void) static void notify_fill(notify_slot_t *s, const meow_notify *n, uint32_t id, uint64_t now) { s->id = id; + s->ipc_id = 0; /* 初值=未同步;发送后由 notify_deliver 回填 launcher 分配的 nid */ s->active = 1; s->cat = n->cat; s->urg = ((int)n->urgency >= 0 && (int)n->urgency < (int)MEOW_NOTIFY_URG_COUNT) @@ -106,12 +114,23 @@ static void notify_fill(notify_slot_t *s, const meow_notify *n, uint32_t id, uin s->progress = (n->progress < -1) ? -1 : (n->progress > 100 ? 100 : n->progress); } -/* 落点:本阶段用统一日志标出一条投递;真实后端(通知中心 UI / D-Bus)后续替换此处。 */ -static void notify_deliver(const notify_slot_t *s, const char *ev) +/* 落点(任务 #5):把通知经 IPC MSG_NOTIFY(0x0020) 送到 launcher 中央存储 + * (meow_notif.c 的 meow_notif_post),回读 MSG_NOTIFY_ACK(0x0021) 取 launcher 分配的 + * 通知 id(nid)。返回 nid(>0=成功);无 socket/写失败/超时 → 返回 0(调用方保留本地 id + * 兜底,不崩)。调用方须在持锁外调用(socket I/O 不占 g_mu,避免阻塞其它查询)。 + * replaces_id:0=新建;>0=更新 launcher 侧已存在通知(freedesktop 语义)。 */ +static uint32_t notify_deliver(const meow_notify *n, uint32_t replaces_id) { - meow_log(MEOW_LOG_INFO, "[notify] %s id=%u type=%s urgency=%s title=%s", - ev, (unsigned)s->id, meow_notify_category_str(s->cat), - meow_notify_urgency_str(s->urg), s->title); + uint32_t nid = meow_notify_ipc_send(n, replaces_id); + if (nid) { + meow_log(MEOW_LOG_INFO, "[notify] delivered nid=%u replaces=%u cat=%s title=%s", + (unsigned)nid, (unsigned)replaces_id, meow_notify_category_str(n->cat), + n->title ? n->title : ""); + } else { + meow_log(MEOW_LOG_INFO, "[notify] degraded (no launcher) cat=%s title=%s", + meow_notify_category_str(n->cat), n->title ? n->title : ""); + } + return nid; } /* ============================================================ */ @@ -132,7 +151,7 @@ int meow_notify_register_type(meow_notify_category cat) uint32_t meow_notify_send(const meow_notify *n) { - uint32_t id; + uint32_t id, nid, replaces_id = 0; uint64_t now, oldest; int i, slot_idx, count, oldest_idx; @@ -155,11 +174,16 @@ uint32_t meow_notify_send(const meow_notify *n) if (n->cat == MEOW_NOTIFY_CAT_MEDIA) { /* 媒体单例:复用已存在的媒体槽(重发替换同 app 上一条媒体通知) */ for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) - if (g_slots[i].active && g_slots[i].cat == MEOW_NOTIFY_CAT_MEDIA) { slot_idx = i; break; } + if (g_slots[i].active && g_slots[i].cat == MEOW_NOTIFY_CAT_MEDIA) { + slot_idx = i; + replaces_id = g_slots[i].ipc_id; /* 更新该媒体通知(0=降级/未同步) */ + break; + } } else { count = notify_count_cat(n->cat); if (count >= MEOW_NOTIFY_MAX_PER_TYPE) { - /* 满则丢该类型最旧一条(替换) */ + /* 满则丢该类型最旧一条(更新):先取被丢最旧的 launcher nid 作 replaces_id, + * 再释放槽(顺序不可反,否则 ipc_id 已被清 0)。 */ oldest_idx = -1; oldest = UINT64_MAX; for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) @@ -167,7 +191,13 @@ uint32_t meow_notify_send(const meow_notify *n) oldest = g_slots[i].created_ms; oldest_idx = i; } - if (oldest_idx >= 0) { g_slots[oldest_idx].active = 0; g_slots[oldest_idx].id = 0; } + if (oldest_idx >= 0) { + replaces_id = g_slots[oldest_idx].ipc_id; /* 复用被丢最旧的 launcher nid */ + if (replaces_id == 0) replaces_id = 0; /* 未同步时按新建处理 */ + g_slots[oldest_idx].active = 0; + g_slots[oldest_idx].id = 0; + g_slots[oldest_idx].ipc_id = 0; + } } } if (slot_idx < 0) { @@ -184,26 +214,42 @@ uint32_t meow_notify_send(const meow_notify *n) id = g_next_id++; if (g_next_id == 0) g_next_id = 1; /* 防回绕 */ notify_fill(&g_slots[slot_idx], n, id, now); - notify_deliver(&g_slots[slot_idx], "send"); pthread_mutex_unlock(&g_mu); + + /* 落点(持锁外):IPC 送 launcher 中央存储,回读 nid;无 launcher 则降级(nid=0)。 */ + nid = notify_deliver(n, replaces_id); + + /* 回填 launcher 分配的 nid(槽可能已被并发 cancel/过期清掉——仅在时回填)。 */ + if (nid) { + pthread_mutex_lock(&g_mu); + for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) + if (g_slots[i].active && g_slots[i].id == id) { g_slots[i].ipc_id = nid; break; } + pthread_mutex_unlock(&g_mu); + } return id; } int meow_notify_cancel(uint32_t id) { int i, rc = 0; + uint32_t ipc_id = 0; if (id == 0) return 0; pthread_mutex_lock(&g_mu); notify_purge_expired(); for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) { if (g_slots[i].active && g_slots[i].id == id) { + ipc_id = g_slots[i].ipc_id; /* launcher 侧 nid;0=未同步/降级 */ g_slots[i].active = 0; g_slots[i].id = 0; + g_slots[i].ipc_id = 0; rc = 1; break; } } pthread_mutex_unlock(&g_mu); + /* 落点:经 IPC MSG_NOTIFY_DISMISS(0x0022) 撤下 launcher 中央存储对应通知 + * (best-effort;无 launcher/未同步则静默降级)。 */ + if (rc && ipc_id) meow_notify_ipc_dismiss(ipc_id); return rc; } diff --git a/patch_v2/sdk/meow_ui_sys.c b/patch_v2/sdk/meow_ui_sys.c index 07c5ef8..c271f5e 100644 --- a/patch_v2/sdk/meow_ui_sys.c +++ b/patch_v2/sdk/meow_ui_sys.c @@ -14,6 +14,7 @@ */ #include "meow_ui_sys.h" #include "meow_ui_internal.h" +#include "meow_ui_vol.h" #include #include @@ -23,7 +24,6 @@ #include #define MEOW_SETTINGS_FILE "/usr/data/meow/settings.conf" -#define VOL_DEFAULT 60 /* 蓝牙 powered 缓存刷新间隔(hciconfig 较贵) */ #define BT_REFRESH_MS 30000 @@ -80,22 +80,15 @@ static int hci_powered(void) return up; } -/* 音量:读 settings.conf 的 audio.volume(launcher 同源,watchdog 写同文件)。 */ +/* 音量:读 settings.conf 的 audio.volume(launcher 同源,watchdog 写同文件)。 + * 与 launcher 口径一致:0-100,且尊重 audio.volume_max 上限 clamp(vol_clamp 同义)。 + * 注意 settings 落盘为 "key = value"(带空格),必须按 '=' 切分解析,不能朴素前缀匹配—— + * 否则永远读不到(回退默认 60,即「状态栏音量数字不对、卡在 60」的根因)。 */ static int settings_vol(void) { - FILE *f = fopen(MEOW_SETTINGS_FILE, "r"); - if (!f) return VOL_DEFAULT; - int vol = VOL_DEFAULT; - char buf[128]; - while (fgets(buf, sizeof buf, f)) { - if (!strncmp(buf, "audio.volume=", 13)) { - int v = atoi(buf + 13); - if (v >= 0 && v <= 100) vol = v; - break; - } - } - fclose(f); - return vol; + int vol = meow_ui_vol_read(MEOW_SETTINGS_FILE, "audio.volume", MEOW_UI_VOL_DEFAULT); + int vol_max = meow_ui_vol_read(MEOW_SETTINGS_FILE, "audio.volume_max", MEOW_UI_VOL_MAX_DEF); + return meow_ui_vol_clamp(vol, vol_max); } int meow_ui_sys_init(void) @@ -210,21 +203,25 @@ void meow_ui_sys_statusbar_render(void) /* 整条清底:覆盖 app 可能画进顶行的内容(z 最顶) */ meow_ui_fill_rect(0, 0, w, h, th->bg); + /* 文字垂直锚 y:(栏高-行高)/2 居中;下限 1 防极端行高 > 栏高时负 y 溢出顶端(不越界优先)。 */ + int ty = (h - meow_ui_text_height(MUI_SBAR_FONT)) / 2; + if (ty < 1) ty = 1; + /* 时钟(左):每秒现取 localtime */ char timebuf[16]; time_t t = time(NULL); struct tm tm; localtime_r(&t, &tm); snprintf(timebuf, sizeof timebuf, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec); - meow_ui_text(8, (h - meow_ui_text_height(MUI_SBAR_FONT)) / 2, timebuf, MUI_SBAR_FONT, th->fg); + meow_ui_text(8, ty, timebuf, MUI_SBAR_FONT, th->fg); /* 右侧信息串:batt% WIFI 蓝牙 VOL nn% */ char right[96]; statusbar_right_str(right, sizeof right); if (right[0]) - meow_ui_text_right(w - 8, (h - meow_ui_text_height(MUI_SBAR_FONT)) / 2, right, MUI_SBAR_FONT, th->fg); + meow_ui_text_right(w - 8, ty, right, MUI_SBAR_FONT, th->fg); - /* 分隔线(栏内最底 1px,主题次要色);内容 inset=statusbar_height()=40 从 y=40 起即干净 */ + /* 分隔线(栏内最底 1px,主题次要色);内容 inset=statusbar_height()=28 从 y=28 起即干净 */ meow_ui_fill_rect(0, h - 1, w, 1, th->dim); g_statusbar_force = 0; diff --git a/patch_v2/sdk/meow_ui_sys.h b/patch_v2/sdk/meow_ui_sys.h index 61561b3..8caeb68 100644 --- a/patch_v2/sdk/meow_ui_sys.h +++ b/patch_v2/sdk/meow_ui_sys.h @@ -10,7 +10,8 @@ * - 电量 /sys/class/power_supply/battery/{capacity,status} * - WiFi /sys/class/net/wlan0/operstate * - 蓝牙 实际 powered(hciconfig -a hci0 的 UP;spawn 贵 → 缓存 30s 刷一次) - * - 音量 /usr/data/meow/settings.conf 的 audio.volume(launcher 同文件) + * - 音量 /usr/data/meow/settings.conf 的 audio.volume(launcher 同文件; + * 0-100 并 clamp 到 audio.volume_max 上限,与 launcher vol_clamp 口径一致) * - 时钟 localtime_r(每秒在绘制时现取) */ #ifndef MEOW_UI_SYS_H @@ -28,9 +29,10 @@ typedef struct meow_sys_state { int vol; /* 音量 0-100 */ } meow_sys_state; -/* 顶部状态栏几何(与 launcher SB_BAR_H=40 / SB_FONT=28 对齐,视觉统一)。 */ -#define MUI_SBAR_H 40 -#define MUI_SBAR_FONT 28 +/* 顶部状态栏几何(与 launcher SB_BAR_H=28 / SB_FONT=20 对齐,视觉统一)。 + * 高度 40→28(用户定),字号 28→20(28px 栏放不下 28 号;20 号按 (高-行高)/2 垂直居中)。 */ +#define MUI_SBAR_H 28 +#define MUI_SBAR_FONT 20 /* 首次全量读取;失败仍返回 0(字段置 -1/0)。 */ int meow_ui_sys_init(void); @@ -44,7 +46,7 @@ const meow_sys_state *meow_ui_sys_get(void); /* 状态栏是否应绘制(= set_statusbar(1) 且 非 fullscreen)。 */ int meow_ui_sys_statusbar_active(void); -/* 状态栏高度(px):active 时 40,否则 0(供 meow_ui_statusbar_height 用)。 */ +/* 状态栏高度(px):active 时 28,否则 0(供 meow_ui_statusbar_height 用)。 */ int meow_ui_sys_statusbar_height(void); /* 自上次渲染后状态栏数据是否已变(1s 轻刷/显隐切换置位);读取即清除。 diff --git a/patch_v2/sdk/meow_ui_vol.c b/patch_v2/sdk/meow_ui_vol.c new file mode 100644 index 0000000..b428653 --- /dev/null +++ b/patch_v2/sdk/meow_ui_vol.c @@ -0,0 +1,52 @@ +/* + * meow_ui_vol.c — SDK 状态栏音量换算纯函数 + settings.conf 读取(A-0 核心数源)。 + * + * 单独成 TU 的目的: + * 1. 纯函数(clamp / 键读取)不依赖 meow_ui 其它子系统,宿主 gcc 直接可测 + * (test_meow_ui_vol.c),无需链整库/stub。 + * 2. 把「音量从 settings 读数并 clamp 到 volume_max」这一语义钉死在单点, + * 与 launcher 的 vol_clamp(meow_launcherd.c)口径对齐,杜绝两端音量漂移。 + * + * 数据源:/usr/data/meow/settings.conf 扁平 key=value。launcher meow_settings.c + * 落盘为 "key = value"(带空格),本解析兼容无空格/多空格,与 launcher 读循环 + * 及 sdk meow_ui_theme.c 的 get_setting_str 同款解析。 + */ +#include "meow_ui_vol.h" + +#include +#include +#include + +int meow_ui_vol_clamp(int vol, int vol_max) +{ + if (vol < 0) vol = 0; + if (vol > MEOW_UI_VOL_MAX) vol = MEOW_UI_VOL_MAX; + if (vol_max < 0) vol_max = 0; /* 异常上限兜底:不进负值 */ + if (vol > vol_max) vol = vol_max; + return vol; +} + +int meow_ui_vol_read(const char *path, const char *key, int def) +{ + if (!path || !key) return def; + FILE *f = fopen(path, "r"); + if (!f) return def; + int kl = (int)strlen(key); + char line[160]; + char *found = NULL; + while (fgets(line, sizeof line, f)) { + char *eq = strchr(line, '='); + if (!eq) continue; /* 非 key=value 行(注释/空行)跳过 */ + char *k = line; + while (*k == ' ' || *k == '\t') k++; /* key 左去空白 */ + int n = (int)(eq - k); + while (n > 0 && (k[n-1] == ' ' || k[n-1] == '\t')) n--; /* key 右去空白 */ + if (n != kl || strncmp(k, key, (size_t)kl) != 0) continue; /* key 不精确匹配 */ + found = eq + 1; /* 命中所求键 */ + break; + } + fclose(f); + if (!found) return def; + while (*found == ' ' || *found == '\t') found++; /* value 左去空白;atoi 天然停于非数字 */ + return atoi(found); +} diff --git a/patch_v2/sdk/meow_ui_vol.h b/patch_v2/sdk/meow_ui_vol.h new file mode 100644 index 0000000..9a94bed --- /dev/null +++ b/patch_v2/sdk/meow_ui_vol.h @@ -0,0 +1,32 @@ +/* + * meow_ui_vol.h — SDK 状态栏音量换算/读取(meow_ui_vol.c)。 + * 不随 meow_ui.h 发布给应用;仅供 meow_ui_sys.c 与宿主单测(test_meow_ui_vol.c)使用。 + */ +#ifndef MEOW_UI_VOL_H +#define MEOW_UI_VOL_H + +/* 音量语义常量(与 launcher MEOW_VOL_MAX/DEFAULT、meow_settings 表逐一致): + * audio.volume 默认 60,取值域 0-100(硬上限 MEOW_UI_VOL_MAX); + * audio.volume_max 默认 100(软上限,clamp 上限,用户可调小); + * audio.volume_step 默认 1(跳步;只影响「每次音量键改多少」,与状态栏展示无关—— + * SDK 只读当前值,步进由改动方(launcher)承担)。 */ +#define MEOW_UI_VOL_DEFAULT 60 +#define MEOW_UI_VOL_MAX 100 +#define MEOW_UI_VOL_MAX_DEF 100 + +/* 纯函数:把软件音量夹到 [0, MEOW_UI_VOL_MAX],再夹到 volume_max 软上限。 + * 语义与 launcher 的 vol_clamp(meow_launcherd.c)一致: + * - 硬上限 100(0-100 口径); + * - 尊重 audio.volume_max(用户把上限调小则音量被夹到该值); + * - volume_max 异常(<0)兜底为 0(不进负值)。 + * 无文件/全局依赖,宿主 gcc 可直接测。 */ +int meow_ui_vol_clamp(int vol, int vol_max); + +/* 读 settings.conf 里一个 int 键。扁平 "key=value" 格式(launcher meow_settings.c + * 落盘为 "key = value",带空格;这里兼容无空格/多空格两种写式)。 + * 解析:'=' 切分,key/value 去首尾空白,key 精确匹配(避免 audio.volume 误配 + * audio.volume_max)。未命中/打开失败/键缺失回退 def。 + * path 由调用方传入(设备传导 MEOW_SETTINGS_FILE;宿主单测指向临时文件)。 */ +int meow_ui_vol_read(const char *path, const char *key, int def); + +#endif /* MEOW_UI_VOL_H */ diff --git a/patch_v2/sdk/test_meow_ui_vol.c b/patch_v2/sdk/test_meow_ui_vol.c new file mode 100644 index 0000000..cf1d7a3 --- /dev/null +++ b/patch_v2/sdk/test_meow_ui_vol.c @@ -0,0 +1,145 @@ +/* + * test_meow_ui_vol.c — 宿主单元测试:SDK 状态栏音量换算纯函数 + settings.conf 读取。 + * + * 直接编译 meow_ui_vol.c(纯函数,无 meow_ui 依赖),用临时 settings 文件验证: + * V1 解析:launcher 落盘 "key = value"(带空格)形式能正确读到值(根因回归—— + * 旧代码朴素前缀匹配 "audio.volume=" 永远读不到 → 卡默认 60); + * V2 解析:无空格 "key=value" / 多空格 / 头注释行 / 键缺失回退默认 / 精确匹配 + * (audio.volume 不误配 audio.volume_max); + * V3 clamp:volume_max 上限、硬上限 100、负值兜底 0、上限异常(<0)兜底 0; + * V4 管道(镜像 meow_ui_sys.c settings_vol):读 + clamp 到 volume_max; + * V5 展示串:与 meow_ui_sys.c statusbar_right_str 同款 "VOL %d%%",断言显示值即 + * 当前夹后音量(含 step 语义:a.volume_step 只决定步进,展示原样不/舍入)。 + * + * 编译(宿主 gcc,mipsel 无关): + * gcc -o test_meow_ui_vol test_meow_ui_vol.c meow_ui_vol.c -I. + */ +#include "meow_ui_vol.h" + +#include +#include +#include +#include + +static int failures = 0; +static void check(const char *name, int cond) +{ + printf("%s %s\n", cond ? "PASS" : "FAIL", name); + if (!cond) failures++; +} + +/* 写一份临时 settings 文件(lines 为已含 '\n' 的行内容;NULL 结束) */ +static const char *tmp_path; +static void write_settings(const char **lines) +{ + FILE *f = fopen(tmp_path, "w"); + if (!f) { perror("fopen"); exit(2); } + for (int i = 0; lines[i]; i++) fputs(lines[i], f); + fclose(f); +} + +/* 镜像 meow_ui_sys.c 的 settings_vol():读 audio.volume + volume_max,再 clamp。 */ +static int settings_vol_of(const char *path) +{ + int vol = meow_ui_vol_read(path, "audio.volume", MEOW_UI_VOL_DEFAULT); + int vol_max = meow_ui_vol_read(path, "audio.volume_max", MEOW_UI_VOL_MAX_DEF); + return meow_ui_vol_clamp(vol, vol_max); +} + +/* 状态栏右侧 VOL 串组装(与 meow_ui_sys.c statusbar_right_str 同款)。 */ +static void vol_display(int vol, int vol_max, char *out, size_t n) +{ + snprintf(out, n, "VOL %d%%", meow_ui_vol_clamp(vol, vol_max)); +} + +int main(void) +{ + char base[64]; + snprintf(base, sizeof base, "/tmp/meow_vol_%d.conf", (int)getpid()); + tmp_path = base; + + /* ---- V1 解析:launcher 落盘格式 "key = value"(带空格)——根因回归 ---- */ + { + const char *L[] = { "# meow-settings 1\n", "audio.volume = 82\n", NULL }; + write_settings(L); + check("V1 launcher 空格格式 audio.volume=82 -> 82", + meow_ui_vol_read(tmp_path, "audio.volume", -1) == 82); + } + + /* ---- V2 解析:无空格 / 多空格 / 注释 / 缺键 / 精确匹配 ---- */ + { + const char *L[] = { "audio.volume=40\n", NULL }; + write_settings(L); + check("V2 无空格 audio.volume=40 -> 40", + meow_ui_vol_read(tmp_path, "audio.volume", -1) == 40); + + const char *L2[] = { " audio.volume = 7 \n", NULL }; + write_settings(L2); + check("V2 多空格 audio.volume = 7 -> 7", + meow_ui_vol_read(tmp_path, "audio.volume", -1) == 7); + + const char *L3[] = { "display.brightness = 100\n", NULL }; + write_settings(L3); + check("V2 键缺失 -> 默认 -1", meow_ui_vol_read(tmp_path, "audio.volume", -1) == -1); + + const char *L4[] = { "audio.volume_max = 90\n", NULL }; + write_settings(L4); + check("V2 audio.volume 不误配 audio.volume_max -> 默认", + meow_ui_vol_read(tmp_path, "audio.volume", -1) == -1); + check("V2 单独读 audio.volume_max=90 -> 90", + meow_ui_vol_read(tmp_path, "audio.volume_max", -1) == 90); + } + + /* ---- V3 clamp 纯函数 ---- */ + check("V3 clamp(60,100)=60", meow_ui_vol_clamp(60, 100) == 60); + check("V3 clamp(80,60)=60 (尊重 volume_max)", meow_ui_vol_clamp(80, 60) == 60); + check("V3 clamp(80,100)=80", meow_ui_vol_clamp(80, 100) == 80); + check("V3 clamp(150,100)=100 (硬上限)", meow_ui_vol_clamp(150, 100) == 100); + check("V3 clamp(150,80)=80", meow_ui_vol_clamp(150, 80) == 80); + check("V3 clamp(-5,100)=0 (负值兜底)", meow_ui_vol_clamp(-5, 100) == 0); + check("V3 clamp(60,0)=0 (上限 0)", meow_ui_vol_clamp(60, 0) == 0); + check("V3 clamp(60,-10)=0 (上限异常兜底)", meow_ui_vol_clamp(60, -10) == 0); + check("V3 clamp(120,255)=100 (硬上限优先于大上限)", meow_ui_vol_clamp(120, 255) == 100); + + /* ---- V4 管道:读 + clamp 到 volume_max(镜像 settings_vol)---- */ + { + const char *L[] = { "audio.volume = 82\n", "audio.volume_max = 100\n", NULL }; + write_settings(L); + check("V4 vol=82 max=100 -> 82", settings_vol_of(tmp_path) == 82); + + const char *L2[] = { "audio.volume = 82\n", "audio.volume_max = 60\n", NULL }; + write_settings(L2); + check("V4 vol=82 max=60 -> 60 (clamp)", settings_vol_of(tmp_path) == 60); + + const char *L3[] = { "audio.volume = 150\n", "audio.volume_max = 100\n", NULL }; + write_settings(L3); + check("V4 vol=150 max=100 -> 100 (硬上限)", settings_vol_of(tmp_path) == 100); + + const char *L4[] = { "audio.volume = -3\n", "audio.volume_max = 100\n", NULL }; + write_settings(L4); + check("V4 vol=-3 -> 0", settings_vol_of(tmp_path) == 0); + + const char *L5[] = { "display.brightness = 100\n", NULL }; /* 无音量键 */ + write_settings(L5); + check("V4 无 audio.volume -> 默认 60", settings_vol_of(tmp_path) == MEOW_UI_VOL_DEFAULT); + } + + /* ---- V5 展示串:VOL %d%(step 只决定步进,展示原样不/舍入)---- */ + { + char s[16]; + vol_display(60, 100, s, sizeof s); + check("V5 vol=60 -> \"VOL 60%\"", !strcmp(s, "VOL 60%")); + vol_display(80, 60, s, sizeof s); + check("V5 vol=80 max=60 -> \"VOL 60%\" (显示夹后值)", !strcmp(s, "VOL 60%")); + vol_display(-5, 100, s, sizeof s); + check("V5 vol=-5 -> \"VOL 0%\"", !strcmp(s, "VOL 0%")); + vol_display(37, 100, s, sizeof s); + check("V5 vol=37 -> \"VOL 37%\" (step 值原样展示)", !strcmp(s, "VOL 37%")); + } + + unlink(tmp_path); + + printf("\n%s (%d failure%s)\n", failures ? "RESULT: FAIL" : "RESULT: PASS", + failures, failures == 1 ? "" : "s"); + return failures ? 1 : 0; +} diff --git a/patch_v2/sdk/test_notify_integration.c b/patch_v2/sdk/test_notify_integration.c new file mode 100644 index 0000000..6ea53d9 --- /dev/null +++ b/patch_v2/sdk/test_notify_integration.c @@ -0,0 +1,202 @@ +/* + * test_notify_integration.c — 宿主集成测试:meow_notify_send/cancel 全链路(任务 #5)。 + * + * 直接把 meow_ui_notify.c + meow_notify_ipc.c 编进来,仅 stub 两个 SDK 能力 + * (meow_log 空实现 / meow_ui_time_ms 单调毫秒),用本地 AF_UNIX stub socket 回环: + * + * S1 send(general,timeout=0) → 返回本地 id(1),stub 回 nid=101,active_count=1; + * S2 send(media) → media=1、category=media(经 stub 捕获帧校验); + * S3 同类型再发(未满配额)→ 返回本地 id(3); + * S4 cancel(本地id=1) → 返回 1,stub 收到 MSG_NOTIFY_DISMISS(负载 nid=101); + * S5 配额满(每类型4)后发第 5 条 → 丢最旧:该 5 条 send 的 replaces_id == 被丢最旧的 nid; + * S6 无 launcher(connect 失败)→ send 仍返回本地 id(>0)、不崩;cancel 也静默不崩。 + * + * 编译(宿主 gcc): + * gcc -o test_notify_integration test_notify_integration.c \ + * meow_ui_notify.c meow_notify_ipc.c -I. -I../meow_launcherd -lpthread + */ +#include "meow_ui.h" +#include "meow_notify_ipc.h" +#include "meow_ipc.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ---- stub SDK 依赖(避免链整库) ---- */ +void meow_log(meow_log_level lvl, const char *fmt, ...) { (void)lvl; (void)fmt; } +uint64_t meow_ui_time_ms(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000; +} + +/* ---- stub server 捕获(每次连接一帧) ---- */ +#define MAXCAP 16 +typedef struct { int type; m_notify_t n; uint32_t dismiss_id; uint32_t nid; } srec_t; +static srec_t g_rec[MAXCAP]; +static int g_nrec = 0; +static int g_stop = 0; +static int g_listen_fd = -1; +static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER; + +static void *server(void *arg) +{ + (void)arg; + while (!g_stop) { + int c = accept(g_listen_fd, NULL, NULL); + if (c < 0) { + if (errno == EAGAIN && g_stop) break; /* 非阻塞:listener 被关闭 */ + if (errno == EBADF) break; + usleep(1000); + continue; + } + uint8_t hdr[MEOW_IPC_HDR_LEN]; + size_t off = 0; + while (off < sizeof hdr) { ssize_t r = read(c, hdr + off, sizeof hdr - off); if (r <= 0) break; off += (size_t)r; } + if (off < sizeof hdr) { close(c); continue; } + uint32_t type, len, req_id; + memcpy(&req_id, hdr + 8, 4); + memcpy(&len, hdr + 12, 4); + memcpy(&type, hdr + 6, 2); + if (len > MEOW_IPC_MAX) { close(c); continue; } + uint8_t pay[MEOW_IPC_MAX]; + off = 0; + while (off < len) { ssize_t r = read(c, pay + off, len - off); if (r <= 0) break; off += (size_t)r; } + + pthread_mutex_lock(&g_mu); + int idx = g_nrec < MAXCAP ? g_nrec++ : (MAXCAP - 1); + memset(&g_rec[idx], 0, sizeof g_rec[idx]); + g_rec[idx].type = (int)type; + if (type == MSG_NOTIFY && len >= sizeof(m_notify_t)) memcpy(&g_rec[idx].n, pay, sizeof(m_notify_t)); + if (type == MSG_NOTIFY_DISMISS && len >= 4) memcpy(&g_rec[idx].dismiss_id, pay, 4); + /* 分配 nid = 100 + NOTIFY 序号 */ + uint32_t seq = 0; + for (int i = 0; i <= idx; i++) if (g_rec[i].type == MSG_NOTIFY) seq++; + g_rec[idx].nid = (type == MSG_NOTIFY) ? (100 + seq) : 0; + uint32_t ack_nid = g_rec[idx].nid; + pthread_mutex_unlock(&g_mu); + + if (type == MSG_NOTIFY || type == MSG_NOTIFY_DISMISS) { + m_ack_t a; memset(&a, 0, sizeof a); a.code = MEOW_OK; a.status = ack_nid; + uint8_t rhdr[MEOW_IPC_HDR_LEN]; + uint32_t mag = MEOW_IPC_MAGIC; uint16_t ver = MEOW_IPC_VERSION, t = (uint16_t) + (type == MSG_NOTIFY ? MSG_NOTIFY_ACK : MSG_ACK); uint32_t rl = sizeof a; + memcpy(rhdr+0,&mag,4); memcpy(rhdr+4,&ver,2); memcpy(rhdr+6,&t,2); memcpy(rhdr+8,&req_id,4); memcpy(rhdr+12,&rl,4); + size_t w0 = 0; while (w0 < sizeof rhdr) { ssize_t w = write(c, rhdr+w0, sizeof rhdr-w0); if (w<=0) break; w0 += (size_t)w; } + size_t w1 = 0; while (w1 < rl) { ssize_t w = write(c, (uint8_t*)&a+w1, rl-w1); if (w<=0) break; w1 += (size_t)w; } + } + close(c); + } + return NULL; +} + +static int g_listen_made = 0; +static int make_listen(char *path, size_t n) +{ + snprintf(path, n, "/tmp/meow_notify_intg_%d.sock", (int)getpid()); + unlink(path); + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + struct sockaddr_un a; memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; strncpy(a.sun_path, path, sizeof a.sun_path - 1); + if (bind(fd, (struct sockaddr*)&a, sizeof a) < 0 || listen(fd, 4) < 0) { close(fd); return -1; } + fcntl(fd, F_SETFL, O_NONBLOCK); /* 非阻塞 listener:accept() 可被 g_stop 截断 */ + g_listen_fd = fd; g_listen_made = 1; + return 0; +} +static void stop_server(void) +{ + if (g_listen_made) { g_stop = 1; close(g_listen_fd); g_listen_made = 0; } +} + +static srec_t rec_at(int i) +{ + srec_t r; pthread_mutex_lock(&g_mu); r = g_rec[i]; pthread_mutex_unlock(&g_mu); return r; +} + +static int failures = 0; +static void check(const char *name, int cond) +{ + printf("%s %s\n", cond ? "PASS" : "FAIL", name); + if (!cond) failures++; +} + +int main(void) +{ + setenv("MEOW_LOG_APP", "intgapp", 1); + char sock[108]; + if (make_listen(sock, sizeof sock) != 0) { fprintf(stderr, "cannot listen\n"); return 2; } + setenv("MEOW_IPC_SOCK", sock, 1); + pthread_t th; pthread_create(&th, NULL, server, NULL); + + /* S1 通用通知 */ + meow_notify nt; memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_GENERAL; nt.urgency = MEOW_NOTIFY_URG_NORMAL; + nt.title = "t1"; nt.timeout_ms = 0; + uint32_t id1 = meow_notify_send(&nt); + check("S1 send 返回本地 id=1", id1 == 1); + check("S1 active_count=1", meow_notify_active_count() == 1); + check("S1 stub 回 nid=101", rec_at(0).nid == 101); + + /* S2 媒体通知:单例 + media=1 */ + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_MEDIA; nt.title = "music"; nt.timeout_ms = -1; + uint32_t id2 = meow_notify_send(&nt); + check("S2 媒体返本地 id=2", id2 == 2); + check("S2 category=media", !strcmp(rec_at(1).n.category, "media")); + check("S2 media=1", rec_at(1).n.media == 1); + check("S2 expire=0xFFFFFFFF", rec_at(1).n.expire_timeout == 0xFFFFFFFFu); + + /* S3 同类型再发(general 未满 4)→ 新本地 id */ + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_GENERAL; nt.title = "t3"; nt.timeout_ms = 0; + uint32_t id3 = meow_notify_send(&nt); + check("S3 再发返本地 id=3", id3 == 3); + check("S3 第2条general nid=103", rec_at(2).nid == 103); + + /* S4 cancel(id1) → DISMISS nid=101 */ + int rc = meow_notify_cancel(id1); + check("S4 cancel 返回 1", rc == 1); + check("S4 stub 收到 MSG_NOTIFY_DISMISS", rec_at(3).type == MSG_NOTIFY_DISMISS); + check("S4 dismiss 负载 nid=101", rec_at(3).dismiss_id == 101); + check("S4 本地表已撤(count=2)", meow_notify_active_count() == 2); + + /* S5 配额满 → 丢最旧 + replaces_id 复用被丢 nid。 + 当前 general 有 id3(1 条);再发 4 条到 4 条(第 4 条触顶后丢最旧)。 */ + for (int i = 0; i < 4; i++) { + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_GENERAL; nt.title = "quota"; nt.timeout_ms = 0; + (void)meow_notify_send(&nt); + } + /* 现在 general 应仍有 4 条(第5条丢最旧 id3=本地,即被降级替换) */ + check("S5 配额满后每类型仍=4", meow_notify_count_in_type(MEOW_NOTIFY_CAT_GENERAL) == 4); + /* 被丢最旧 = id3(第2条 general,本地 id=3),其 launcher nid=103(rec[2])。 + 触发丢最旧的是 S5 循环第 4 次 send = 第 7 条 NOTIFY(rec[7]),其 replaces_id==103。 */ + check("S5 丢最旧后 send replaces_id=被丢nid(103)", rec_at(7).n.replaces_id == 103); + + stop_server(); + pthread_join(th, NULL); + + /* S6 无 launcher 降级 */ + char dead[108]; snprintf(dead, sizeof dead, "/tmp/meow_notify_intg_dead_%d.sock", (int)getpid()); + unlink(dead); + setenv("MEOW_IPC_SOCK", dead, 1); + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_EMAIL; nt.title = "no-launcher"; nt.timeout_ms = 0; + uint32_t id6 = meow_notify_send(&nt); + check("S6 无 launcher 仍返回本地 id(>0)", id6 > 0); + check("S6 无 launcher active_count 正常", meow_notify_active_count() >= 1); + check("S6 cancel 无 launcher 静默不崩", meow_notify_cancel(id6) == 1); + + printf("\n%s (%d failure%s)\n", failures ? "RESULT: FAIL" : "RESULT: PASS", + failures, failures == 1 ? "" : "s"); + return failures ? 1 : 0; +} diff --git a/patch_v2/sdk/test_notify_ipc.c b/patch_v2/sdk/test_notify_ipc.c new file mode 100644 index 0000000..f1ba227 --- /dev/null +++ b/patch_v2/sdk/test_notify_ipc.c @@ -0,0 +1,281 @@ +/* + * test_notify_ipc.c — 宿主测试:任务 #5 SDK↔IPC 桥(meow_notify_ipc.{h,c})。 + * + * 用本地 AF_UNIX stub socket(回环)验证: + * T1 枚举↔字符串映射:CAT_PROGRESS→"progress"、CAT_MEDIA→"media" + media=1; + * T2 timeout 映射:-1(ONGOING)→expire=0xFFFFFFFF + ongoing=1;0(NEVER)→expire=0; + * >0→该毫秒 + ongoing=0; + * T3 回读 MSG_NOTIFY_ACK 拿 launcher 分配的 nid;replaces_id 原样透传;progress 钳位; + * T4 无 socket 降级:connect 失败 → meow_notify_ipc_send 返回 0,不崩; + * T5 dismiss:meow_notify_ipc_dismiss(nid) 发出 MSG_NOTIFY_DISMISS 帧(负载=nid)。 + * + * 编译(宿主 gcc,不依赖 meow_ui.a 其余 TU): + * gcc -o test_notify_ipc test_notify_ipc.c meow_notify_ipc.c \ + * -I. -I../meow_launcherd -lpthread + * 运行:./test_notify_ipc (全部 PASS 退出码 0,任一 FAIL 退出码 1) + */ +#include "meow_notify_ipc.h" +#include "meow_ui.h" +#include "meow_ipc.h" /* 帧头 / m_notify_t / m_ack_t / MSG_*(同源校验) */ + +#include +#include +#include +#include +#include +#include +#include + +/* ---- 捕获:stub 服务端解析到的帧内容(mutex 保护;客户端返回后读取) ---- */ +typedef struct { + uint32_t type; /* 收到的帧类型 */ + m_notify_t n; /* MSG_NOTIFY 负载 */ + uint32_t dismiss_id; /* MSG_NOTIFY_DISMISS 负载 */ + uint32_t req_id; + uint32_t nid_to_return; /* ACK 中回填的 status */ + int got; /* 已收到一帧 */ +} cap_t; +static cap_t g_cap; +static pthread_mutex_t g_cap_mu = PTHREAD_MUTEX_INITIALIZER; + +static void cap_store(uint32_t type, uint32_t req_id, const uint8_t *pay, uint32_t len, + uint32_t nid_to_return) +{ + pthread_mutex_lock(&g_cap_mu); + memset(&g_cap, 0, sizeof g_cap); + g_cap.type = type; + g_cap.req_id = req_id; + g_cap.nid_to_return = nid_to_return; + if (type == MSG_NOTIFY && len >= sizeof(m_notify_t)) memcpy(&g_cap.n, pay, sizeof(m_notify_t)); + if (type == MSG_NOTIFY_DISMISS && len >= 4) memcpy(&g_cap.dismiss_id, pay, 4); + g_cap.got = 1; + pthread_mutex_unlock(&g_cap_mu); +} + +/* ---- stub 服务端线程:accept 一个连接 → 读一帧 → 校验由主线程做,这里只收 + 回 ACK ---- */ +static int g_listen_fd = -1; +static void *server_thread(void *arg) +{ + (void)arg; + int c = accept(g_listen_fd, NULL, NULL); + if (c < 0) return NULL; + /* 读 16B 头 */ + uint8_t hdr[MEOW_IPC_HDR_LEN]; + uint32_t off = 0; + while (off < sizeof hdr) { + ssize_t r = read(c, hdr + off, sizeof hdr - off); + if (r <= 0) { close(c); return NULL; } + off += (size_t)r; + } + uint32_t magic, len; + uint16_t ver, type; + memcpy(&magic, hdr, 4); + memcpy(&ver, hdr + 4, 2); + memcpy(&type, hdr + 6, 2); + memcpy(&len, hdr + 12, 4); + uint32_t req_id; memcpy(&req_id, hdr + 8, 4); + if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) { close(c); return NULL; } + if (len > MEOW_IPC_MAX) { close(c); return NULL; } + uint8_t pay[MEOW_IPC_MAX]; + off = 0; + while (off < len) { + ssize_t r = read(c, pay + off, len - off); + if (r <= 0) { close(c); return NULL; } + off += (size_t)r; + } + cap_store(type, req_id, pay, len, (uint32_t)strtoul(getenv("TEST_NID"), NULL, 0)); + + /* MSG_NOTIFY → 回 m_ack_t(code=OK, status=nid_to_return); + * MSG_NOTIFY_DISMISS → 回 m_ack_t(code=OK, status=0,同 launcher 行为)。 */ + if (type == MSG_NOTIFY || type == MSG_NOTIFY_DISMISS) { + m_ack_t a; + memset(&a, 0, sizeof a); + a.code = MEOW_OK; + a.status = (type == MSG_NOTIFY) ? (uint32_t)strtoul(getenv("TEST_NID"), NULL, 0) : 0; + uint8_t rhdr[MEOW_IPC_HDR_LEN]; + uint32_t rmagic = MEOW_IPC_MAGIC; + uint16_t rver = MEOW_IPC_VERSION, rtype = (uint16_t)(type == MSG_NOTIFY ? MSG_NOTIFY_ACK : MSG_ACK); + uint32_t rlen = sizeof a; + memcpy(rhdr + 0, &rmagic, 4); + memcpy(rhdr + 4, &rver, 2); + memcpy(rhdr + 6, &rtype, 2); + memcpy(rhdr + 8, &req_id, 4); + memcpy(rhdr + 12, &rlen, 4); + off = 0; + while (off < sizeof rhdr) { ssize_t w = write(c, rhdr + off, sizeof rhdr - off); if (w <= 0) break; off += (size_t)w; } + off = 0; + while (off < rlen) { ssize_t w = write(c, (uint8_t*)&a + off, rlen - off); if (w <= 0) break; off += (size_t)w; } + } + close(c); + return NULL; +} + +static int failures = 0; + +static void check(const char *name, int cond) +{ + printf("%s %s\n", cond ? "PASS" : "FAIL", name); + if (!cond) failures++; +} + +/* 建 listening socket,返回 fd;路径写入 sock_path。 */ +static int arb_listen(char *sock_path, size_t slen) +{ + static int count = 0; + snprintf(sock_path, slen, "/tmp/meow_notify_ipc_%d_%d.sock", (int)getpid(), count++); + unlink(sock_path); + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { perror("socket"); return -1; } + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, sock_path, sizeof a.sun_path - 1); + if (bind(fd, (struct sockaddr *)&a, sizeof a) < 0) { perror("bind"); close(fd); return -1; } + if (listen(fd, 1) < 0) { perror("listen"); close(fd); return -1; } + return fd; +} + +int main(void) +{ + setenv("MEOW_LOG_APP", "testapp", 1); /* 固定 app_id,供 stub 校验 */ + + /* ---------- T1/T2/T3:映射 + ACK 回 nid ---------- */ + { + char sock[108]; + g_listen_fd = arb_listen(sock, sizeof sock); + if (g_listen_fd < 0) { fprintf(stderr, "cannot create listener\n"); return 2; } + setenv("MEOW_IPC_SOCK", sock, 1); + setenv("TEST_NID", "0x1234", 1); + pthread_t th; + pthread_create(&th, NULL, server_thread, NULL); + + meow_notify nt; + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_PROGRESS; + nt.urgency = MEOW_NOTIFY_URG_NORMAL; + nt.title = "下载中"; + nt.body = "70%"; + nt.timeout_ms = 5000; /* >0 → expire=5000, ongoing=0 */ + nt.progress = 150; /* 钳位 → 100 */ + uint32_t nid = meow_notify_ipc_send(&nt, 0); + pthread_join(th, NULL); + close(g_listen_fd); + + check("T3 send 返回 launcher nid(0x1234)", nid == 0x1234); + pthread_mutex_lock(&g_cap_mu); + int got = g_cap.got; + const m_notify_t *cn = &g_cap.n; + pthread_mutex_unlock(&g_cap_mu); + check("T3 stub 收到 MSG_NOTIFY 帧", got && g_cap.type == MSG_NOTIFY); + check("T1 CAT_PROGRESS→category=progress", !strcmp(cn->category, "progress")); + check("T2 timeout>0→expire_timeout=5000", cn->expire_timeout == 5000); + check("T2 timeout>0→ongoing=0", cn->ongoing == 0); + check("T1 media=0(非媒体)", cn->media == 0); + check("T1 progress 钳位 150→100", cn->progress == 100); + check("T2 replaces_id 透传=0", cn->replaces_id == 0); + check("T1 app_id=testapp", !strcmp(cn->app_id, "testapp")); + check("T1 summary=下载中", !strcmp(cn->summary, "下载中")); + } + + /* ---------- T1/T2:媒体类型 + ONGOING(-1) ---------- */ + { + char sock[108]; + g_listen_fd = arb_listen(sock, sizeof sock); + setenv("MEOW_IPC_SOCK", sock, 1); + setenv("TEST_NID", "0x9999", 1); + pthread_t th; + pthread_create(&th, NULL, server_thread, NULL); + + meow_notify nt; + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_MEDIA; + nt.urgency = MEOW_NOTIFY_URG_CRITICAL; + nt.title = "Now Playing"; + nt.timeout_ms = MEOW_NOTIFY_TIMEOUT_ONGOING; /* -1 */ + uint32_t nid = meow_notify_ipc_send(&nt, 0); + pthread_join(th, NULL); + close(g_listen_fd); + + check("T3 send(media/ongoing) 返回 nid(0x9999)", nid == 0x9999); + check("T1 CAT_MEDIA→category=media", !strcmp(g_cap.n.category, "media")); + check("T1 CAT_MEDIA→media=1", g_cap.n.media == 1); + check("T2 ONGOING(-1)→expire=0xFFFFFFFF", g_cap.n.expire_timeout == 0xFFFFFFFFu); + check("T2 ONGOING(-1)→ongoing=1", g_cap.n.ongoing == 1); + } + + /* ---------- T2:NEVER(0) 超时 ---------- */ + { + char sock[108]; + g_listen_fd = arb_listen(sock, sizeof sock); + setenv("MEOW_IPC_SOCK", sock, 1); + setenv("TEST_NID", "0x1", 1); + pthread_t th; + pthread_create(&th, NULL, server_thread, NULL); + meow_notify nt; + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_GENERAL; + nt.title = "x"; + nt.timeout_ms = MEOW_NOTIFY_TIMEOUT_NEVER; /* 0 */ + (void)meow_notify_ipc_send(&nt, 0); + pthread_join(th, NULL); + close(g_listen_fd); + check("T2 NEVER(0)→expire=0 & ongoing=0", + g_cap.n.expire_timeout == 0 && g_cap.n.ongoing == 0); + check("T1 CAT_GENERAL→category=general", !strcmp(g_cap.n.category, "general")); + } + + /* ---------- T3:replaces_id 透传 ---------- */ + { + char sock[108]; + g_listen_fd = arb_listen(sock, sizeof sock); + setenv("MEOW_IPC_SOCK", sock, 1); + setenv("TEST_NID", "0xBEEF", 1); + pthread_t th; + pthread_create(&th, NULL, server_thread, NULL); + meow_notify nt; + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_MESSAGE; + nt.title = "repl"; + nt.timeout_ms = 0; + (void)meow_notify_ipc_send(&nt, 0x42u); + pthread_join(th, NULL); + close(g_listen_fd); + check("T3 replaces_id 透传=0x42", g_cap.n.replaces_id == 0x42u); + } + + /* ---------- T4:无 socket 降级不崩 ---------- */ + { + /* 指向不存在路径 */ + char dead[108]; + snprintf(dead, sizeof dead, "/tmp/meow_notify_ipc_dead_%d.sock", (int)getpid()); + unlink(dead); + setenv("MEOW_IPC_SOCK", dead, 1); + meow_notify nt; + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_EMAIL; + nt.title = "no-socket"; + nt.timeout_ms = 0; + uint32_t nid = meow_notify_ipc_send(&nt, 0); + check("T4 无 socket→返回 0(降级不崩)", nid == 0); + meow_notify_ipc_dismiss(0x1234); /* 无 socket 静默,不崩 */ + check("T4 dismiss 无 socket 静默不崩", 1); + } + + /* ---------- T5:dismiss 发 MSG_NOTIFY_DISMISS ---------- */ + { + char sock[108]; + g_listen_fd = arb_listen(sock, sizeof sock); + setenv("MEOW_IPC_SOCK", sock, 1); + pthread_t th; + pthread_create(&th, NULL, server_thread, NULL); + meow_notify_ipc_dismiss(0xABCDu); + pthread_join(th, NULL); + close(g_listen_fd); + check("T5 stub 收到 MSG_NOTIFY_DISMISS 帧", g_cap.got && g_cap.type == MSG_NOTIFY_DISMISS); + check("T5 dismiss 负载=nid=0xABCD", g_cap.dismiss_id == 0xABCDu); + } + + printf("\n%s (%d failure%s)\n", failures ? "RESULT: FAIL" : "RESULT: PASS", + failures, failures == 1 ? "" : "s"); + return failures ? 1 : 0; +} diff --git a/patch_v2/sdk/test_notify_post.c b/patch_v2/sdk/test_notify_post.c new file mode 100644 index 0000000..beac070 --- /dev/null +++ b/patch_v2/sdk/test_notify_post.c @@ -0,0 +1,150 @@ +/* + * test_notify_post.c — 宿主交叉验证:SDK send 的 IPC 帧 → launcher 真·中央存储。 + * + * 把 SDK 的 meow_notify_ipc.c(映射+传输)与 launcher 的 meow_notif.c(真·中央存储, + * meow_notif_post/dismiss)接到一起,stub 掉 meow_settings 后端;服务端复刻 + * meow_launcherd.c:6427-6440 的 m_notify_t→meow_notif_t 映射 + MSG_NOTIFY_ACK 应答。 + * + * 验证:SDK meow_notify_ipc_send() 产出的帧被 meow_notif_post() 真实落库并回分配 nid>0; + * 随后 meow_notify_ipc_dismiss() 经 meow_notif_dismiss() 撤下(count 归 0)。 + * + * 编译(宿主 gcc): + * gcc -o test_notify_post test_notify_post.c meow_notify_ipc.c \ + * ../meow_launcherd/meow_notif.c -I. -I../meow_launcherd -lpthread + */ +#include "meow_notify_ipc.h" +#include "meow_ui.h" +#include "meow_ipc.h" +#include "meow_notif.h" /* launcher 真·中央存储 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ---- stub:settings 后端(键缺省 → meow_notif.c 用默认配额) ---- */ +int meow_settings_get_int(const char *key, int *out) { (void)key; (void)out; return MEOW_ERR_NOENT; } + +/* ---- 复刻 launcher 的 m_notify_t → meow_notif_t 映射(meow_launcherd.c:6427-6440) ---- + * 注:与 launcher 同款 snprintf(dest 较 src 小会有 format-truncation 提示),launcher 自身 + * 以其编译 flags 构建无警告;此处仅为宿主测试,用 pragma 压掉以免 -Werror 干扰。 */ +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-truncation" +#endif +static void launcher_map(const m_notify_t *mn, meow_notif_t *n) +{ + memset(n, 0, sizeof *n); + n->id = mn->replaces_id; /* 0=>新建;>0=>替换(不存在则新建) */ + snprintf(n->app_id, sizeof n->app_id, "%s", mn->app_id); + snprintf(n->category, sizeof n->category, "%s", mn->category); + snprintf(n->summary, sizeof n->summary, "%s", mn->summary); + snprintf(n->body, sizeof n->body, "%s", mn->body); + snprintf(n->app_icon, sizeof n->app_icon, "%s", mn->app_icon); + snprintf(n->target_activity, sizeof n->target_activity, "%s", mn->target_activity); + n->expire_timeout = mn->expire_timeout; + n->progress = mn->progress; + n->ongoing = mn->ongoing; + n->media = mn->media; +} +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +static int g_listen_fd = -1; +static int g_stop = 0; +static uint32_t g_posted_nid = 0; + +static void reply_ack(int c, uint16_t type, uint32_t req_id, uint32_t status) +{ + m_ack_t a; memset(&a, 0, sizeof a); a.code = MEOW_OK; a.status = status; + uint8_t rhdr[MEOW_IPC_HDR_LEN]; + uint32_t mag = MEOW_IPC_MAGIC; uint16_t ver = MEOW_IPC_VERSION, t = type; uint32_t rl = sizeof a; + memcpy(rhdr+0,&mag,4); memcpy(rhdr+4,&ver,2); memcpy(rhdr+6,&t,2); memcpy(rhdr+8,&req_id,4); memcpy(rhdr+12,&rl,4); + size_t o = 0; while (o < sizeof rhdr) { ssize_t w = write(c, rhdr+o, sizeof rhdr-o); if (w<=0) break; o += (size_t)w; } + o = 0; while (o < rl) { ssize_t w = write(c, (uint8_t*)&a+o, rl-o); if (w<=0) break; o += (size_t)w; } +} + +static void *server(void *arg) +{ + (void)arg; + meow_notif_init(); + while (!g_stop) { + int c = accept(g_listen_fd, NULL, NULL); + if (c < 0) { if (errno == EAGAIN && g_stop) break; if (errno == EBADF) break; usleep(1000); continue; } + uint8_t hdr[MEOW_IPC_HDR_LEN]; size_t o = 0; + while (o < sizeof hdr) { ssize_t r = read(c, hdr+o, sizeof hdr-o); if (r<=0) break; o += (size_t)r; } + if (o < sizeof hdr) { close(c); continue; } + uint32_t type, len, req_id; memcpy(&type, hdr+6, 2); memcpy(&req_id, hdr+8, 4); memcpy(&len, hdr+12, 4); + if (len > MEOW_IPC_MAX) { close(c); continue; } + uint8_t pay[MEOW_IPC_MAX]; o = 0; + while (o < len) { ssize_t r = read(c, pay+o, len-o); if (r<=0) break; o += (size_t)r; } + + if (type == MSG_NOTIFY && len >= sizeof(m_notify_t)) { + m_notify_t mn; memcpy(&mn, pay, sizeof mn); + meow_notif_t n; launcher_map(&mn, &n); + uint32_t nid = 0; + int rc = meow_notif_post(&n, &nid); + if (rc == NOTIF_OK) g_posted_nid = nid; + reply_ack(c, MSG_NOTIFY_ACK, req_id, (rc == NOTIF_OK) ? nid : (uint32_t)rc); + fprintf(stderr, "[post-srv] post rc=%d nid=%u cat=%s\n", rc, nid, mn.category); + } else if (type == MSG_NOTIFY_DISMISS && len >= 4) { + uint32_t id; memcpy(&id, pay, 4); + int rc = meow_notif_dismiss(id); + reply_ack(c, MSG_ACK, req_id, 0); + fprintf(stderr, "[post-srv] dismiss id=%u rc=%d\n", id, rc); + } + close(c); + } + return NULL; +} + +static int make_listen(char *path, size_t n) +{ + snprintf(path, n, "/tmp/meow_notify_post_%d.sock", (int)getpid()); + unlink(path); + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + struct sockaddr_un a; memset(&a, 0, sizeof a); a.sun_family = AF_UNIX; + strncpy(a.sun_path, path, sizeof a.sun_path - 1); + if (bind(fd, (struct sockaddr*)&a, sizeof a) < 0 || listen(fd, 4) < 0) { close(fd); return -1; } + fcntl(fd, F_SETFL, O_NONBLOCK); + g_listen_fd = fd; + return 0; +} + +static int failures = 0; +static void check(const char *name, int cond) { printf("%s %s\n", cond ? "PASS" : "FAIL", name); if (!cond) failures++; } + +int main(void) +{ + setenv("MEOW_LOG_APP", "intgapp", 1); + char sock[108]; + if (make_listen(sock, sizeof sock) != 0) { fprintf(stderr, "cannot listen\n"); return 2; } + setenv("MEOW_IPC_SOCK", sock, 1); + pthread_t th; pthread_create(&th, NULL, server, NULL); + + /* SDK send → launcher 真存储。media + ONGOING 走全部映射分支。 */ + meow_notify nt; memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_MEDIA; nt.urgency = MEOW_NOTIFY_URG_NORMAL; + nt.title = "正在播放"; nt.timeout_ms = -1; nt.progress = 40; + uint32_t nid = meow_notify_ipc_send(&nt, 0); + check("send 经 launcher 真存储回 nid>0", nid != 0); + check("send 拿到 launcher 分配的 nid(与 post 一致)", g_posted_nid == nid); + check("launcher 存储 count(media app)=1", meow_notif_count("intgapp") == 1); + + /* SDK dismiss → launcher 撤下 */ + meow_notify_ipc_dismiss(nid); + check("dismiss 后 launcher 存储 count=0", meow_notif_count("intgapp") == 0); + + g_stop = 1; close(g_listen_fd); + pthread_join(th, NULL); + meow_notif_shutdown(); + + printf("\n%s (%d failure%s)\n", failures ? "RESULT: FAIL" : "RESULT: PASS", failures, failures == 1 ? "" : "s"); + return failures ? 1 : 0; +} diff --git a/patch_v2/verify_apps.sh b/patch_v2/verify_apps.sh index 6a0fa82..db275cd 100755 --- a/patch_v2/verify_apps.sh +++ b/patch_v2/verify_apps.sh @@ -13,7 +13,7 @@ # ./verify_apps.sh devinfo 输入设备 abs 范围等调试信息 # # 场景: -# scen 0 正常: MeowAPPs/hello/(hello + app.json 有效) +# scen 0 正常: MeowAPPs/os.meow.hello/(hello + app.json 有效) # scen 1 无卡: umount sd_0 (测试后必须 scen 0 恢复) # scen 2 无 MeowAPPs 目录 # scen 3 MeowAPPs 空目录 @@ -93,12 +93,12 @@ wdtap() { scen() { local n=${1:-0} case $n in - 0) s "mkdir -p $SD/MeowAPPs/hello; cp -f /data/hello.bin $SD/MeowAPPs/hello/hello; cp -f /data/hello_app.json $SD/MeowAPPs/hello/app.json; chmod 755 $SD/MeowAPPs/hello/hello; ls $SD/MeowAPPs/hello/; echo SCEN0_OK" ;; + 0) s "mkdir -p $SD/MeowAPPs/os.meow.hello; cp -f /data/hello.bin $SD/MeowAPPs/os.meow.hello/hello; cp -f /data/hello_app.json $SD/MeowAPPs/os.meow.hello/app.json; chmod 755 $SD/MeowAPPs/os.meow.hello/hello; ls $SD/MeowAPPs/os.meow.hello/; echo SCEN0_OK" ;; 1) s "umount $SD 2>/dev/null || umount /usr/data/mnt/sd_0 2>/dev/null; grep sd_0 /proc/mounts || echo SCEN1_NOCARD_OK" ;; 2) s "mv $SD/MeowAPPs $SD/MeowAPPs.bak 2>/dev/null; ls $SD/ 2>/dev/null; echo SCEN2_NODIR_OK" ;; 3) s "mkdir -p $SD/MeowAPPs; ls -la $SD/MeowAPPs/; echo SCEN3_EMPTY_OK" ;; - 4) s "mkdir -p $SD/MeowAPPs/hello $SD/MeowAPPs/bad; cp -f /data/hello.bin $SD/MeowAPPs/hello/hello; cp -f /data/hello_app.json $SD/MeowAPPs/hello/app.json; chmod 755 $SD/MeowAPPs/hello/hello; echo '{\"name\":\"Bad App\"}' > $SD/MeowAPPs/bad/app.json; cat $SD/MeowAPPs/bad/app.json; echo SCEN4_BADJSON_OK" ;; - 5) s "mkdir -p $SD/MeowAPPs/hello $SD/MeowAPPs/nonexec; cp -f /data/hello.bin $SD/MeowAPPs/hello/hello; cp -f /data/hello_app.json $SD/MeowAPPs/hello/app.json; chmod 755 $SD/MeowAPPs/hello/hello; echo '{\"name\":\"Ghost App\",\"exec\":\"/usr/data/mnt/sd_0/MeowAPPs/nonexec/nope\"}' > $SD/MeowAPPs/nonexec/app.json; cat $SD/MeowAPPs/nonexec/app.json; echo SCEN5_NOEXEC_OK" ;; + 4) s "mkdir -p $SD/MeowAPPs/os.meow.hello $SD/MeowAPPs/bad; cp -f /data/hello.bin $SD/MeowAPPs/os.meow.hello/hello; cp -f /data/hello_app.json $SD/MeowAPPs/os.meow.hello/app.json; chmod 755 $SD/MeowAPPs/os.meow.hello/hello; echo '{\"name\":\"Bad App\"}' > $SD/MeowAPPs/bad/app.json; cat $SD/MeowAPPs/bad/app.json; echo SCEN4_BADJSON_OK" ;; + 5) s "mkdir -p $SD/MeowAPPs/os.meow.hello $SD/MeowAPPs/nonexec; cp -f /data/hello.bin $SD/MeowAPPs/os.meow.hello/hello; cp -f /data/hello_app.json $SD/MeowAPPs/os.meow.hello/app.json; chmod 755 $SD/MeowAPPs/os.meow.hello/hello; echo '{\"name\":\"Ghost App\",\"exec\":\"/usr/data/mnt/sd_0/MeowAPPs/nonexec/nope\"}' > $SD/MeowAPPs/nonexec/app.json; cat $SD/MeowAPPs/nonexec/app.json; echo SCEN5_NOEXEC_OK" ;; *) echo "usage: scen 0..5"; return 1 ;; esac } diff --git a/patch_v2/verify_chain.sh b/patch_v2/verify_chain.sh index b250afa..631d673 100755 --- a/patch_v2/verify_chain.sh +++ b/patch_v2/verify_chain.sh @@ -9,8 +9,8 @@ DEV="" KEY="$HOME/Project/R3ProII/ssh_keys/" PATCH="$HOME/Project/R3ProII/patch_v2/hiby_player_patched_v2" WD="$HOME/Project/R3ProII/patch_v2/meow_watchdog/meow_watchdog" -HELLO="$HOME/Project/R3ProII/patch_v2/apps_assets/apps/hello/hello" -HJ="$HOME/Project/R3ProII/patch_v2/apps_assets/apps/hello/app.json" +HELLO="$HOME/Project/R3ProII/patch_v2/apps_assets/apps/os.meow.hello/hello" +HJ="$HOME/Project/R3ProII/patch_v2/apps_assets/apps/os.meow.hello/app.json" TAP="$HOME/Project/R3ProII/patch_v2/uinput_tap/uinput_tap" KEYTAP=/data/keytap ASSETS="$HOME/Project/R3ProII/patch_v2/apps_assets" @@ -40,17 +40,17 @@ deploy() { s 'md5sum /data/hiby_player_patched_v2 | cut -c1-8' echo "== 设备侧组装 ==" s 'if ! grep -q "sd_0" /proc/mounts; then mount /dev/mmcblk0p1 /usr/data/mnt/sd_0 2>/dev/null || echo mount_fail; fi - mkdir -p /usr/data/mnt/sd_0/MeowAPPs/hello /data/overlay - cp -f /data/hello.bin /usr/data/mnt/sd_0/MeowAPPs/hello/hello 2>/dev/null - cp -f /data/hello_app.json /usr/data/mnt/sd_0/MeowAPPs/hello/app.json 2>/dev/null - chmod 755 /usr/data/mnt/sd_0/MeowAPPs/hello/hello 2>/dev/null + mkdir -p /usr/data/mnt/sd_0/MeowAPPs/os.meow.hello /data/overlay + cp -f /data/hello.bin /usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/hello 2>/dev/null + cp -f /data/hello_app.json /usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/app.json 2>/dev/null + chmod 755 /usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/hello 2>/dev/null rm -rf /data/overlay/theme1 /data/overlay/str_zh /data/overlay/str_en cp -a /usr/resource/layout/theme1 /data/overlay/theme1 cp -a /usr/resource/str/simplified_chinese /data/overlay/str_zh cp -a /usr/resource/str/english /data/overlay/str_en cp -f /data/hiby_player_patched_v2 /data/overlay/hiby_player chmod 755 /data/overlay/hiby_player - ls /usr/data/mnt/sd_0/MeowAPPs/hello/ 2>/dev/null' + ls /usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/ 2>/dev/null' # 推 hello + 资源 timeout 30 $SSH root@$DEV "cat > /data/hello.bin" < "$HELLO" timeout 30 $SSH root@$DEV "cat > /data/hello_app.json" < "$HJ" @@ -60,10 +60,10 @@ put $ASSETS/str/simplified_chinese/apps.ini /data/overlay/str_zh/apps.ini put $ASSETS/str/english/apps.ini /data/overlay/str_en/apps.ini EOF timeout 30 sftp -i "$KEY" -o StrictHostKeyChecking=accept-new -b /tmp/vc.sftp root@$DEV - s 'cp -f /data/hello.bin /usr/data/mnt/sd_0/MeowAPPs/hello/hello - cp -f /data/hello_app.json /usr/data/mnt/sd_0/MeowAPPs/hello/app.json - chmod 755 /usr/data/mnt/sd_0/MeowAPPs/hello/hello - ls -la /usr/data/mnt/sd_0/MeowAPPs/hello/' + s 'cp -f /data/hello.bin /usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/hello + cp -f /data/hello_app.json /usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/app.json + chmod 755 /usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/hello + ls -la /usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/' } restart() {