Files
MeowOS-For-R3P2/docs/APP_DEV_GUIDE.md
T
AskaEth 54857bc0fd sdk(ui): 字符串资源外置(i18n 雏形 §13)—— meow_str_* + 内嵌双语表 + TF 覆盖表 + 控件接入
公开 SDK 前把 SDK/App 硬编码文案外置:新增 meow_str/meow_strf/meow_str_set_locale/
meow_str_locale,按当前 locale 取串(永不 NULL),第三方开发者可做多语言。

- 新增 include/meow_ui_str.h + meow_ui_str.c:取串优先级 = TF 覆盖表 > 内嵌当前语言
  (en_us→en 其余→zh) > 内嵌内置默认(zh_cn) > 原样返回 id。内嵌双语表(common.* 21 条 +
  ime.* 9 条);热路径零 malloc(编译期静态表 + ≤256 静态覆盖数组);纯 libc 宿主可单测。
- locale 存系统设置 settings.conf 的 ui.locale(默认 zh_cn,首次调用惰性读取);
  meow_str_set_locale 即时切换 + 重载覆盖表 + best-effort 持久化(流式改写保留其它行)。
- TF 覆盖表 MeowData/<pkg>/strings/<locale>.txt(id=text 行,UTF-8,'=' 两侧去空白);
  无卡/无文件/无该 id → 回落内嵌。meow_strf 用调用方缓冲(snprintf 语义,重入安全)。
- SDK 控件接入:dialog 默认按钮 OK/CANCEL + INPUT 占位、IME 键面/徽标(空格/确定/拼英/
  ?123/清/拼音/(无候选)/中)改走 meow_str;空态/加载态给 App 提供 common.no_data/loading。
- 测试:新增 test_meow_ui_str.c(内嵌回退/TF 覆盖优先级/未知 id 回退/切 locale 即时生效/
  查找正确性/meow_strf,tmp+env 两模式);全量宿主回归(widgets/dispatch/box/anim/scroll/
  listvirt/theme/ime_fix/pinyin/overlay/notif/sdk_input_keys/p1p2p3/config/vol/sb_collapse/
  notify_*) 0 回归;交叉 zig-mipsel -Wall -Wextra 0 warning。
- 文档:MEOW_APP_SDK.md 新增 §13(字符串资源/i18n,遗留顺延为 §14);docs/APP_DEV_GUIDE.md
  新增 §7.11。各宿主 test_*.c 头注释补 meow_ui_str.c 编译依赖。

边界(§13.7):仅 zh_cn/en_us 整串取用;不做复数规则 / BiDi / RTL / 动态字体回退(雏形)。
2026-09-02 02:37:38 +08:00

477 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MeowOS 第三方 App 开发指南(APP_DEV_GUIDE)
> 受众:想在 R3 Pro II / MeowOS 上发布自己 App 的第三方开发者(非 MeowOS 系统组)。
> 从零到上架动线:**环境 → 最小 App → 工程结构 → 构建打包 → 签名安装 → 部署调试 → SDK 能力 → 红线 → FAQ**。
> 权威口径:本文所有 API 以 `patch_v2/sdk/meow_ui.h` 为准;规范以 `MEOW_APP_SDK.md` / `APPS.md` / `docs/MEOWOS_SPEC_REF.md` 为准。凡与 `meow_ui.h` 不一致,以头文件为准。
> 配套文档:`MEOW_APP_SDK.md`(SDK 设计全量)、`APPS.md`(加载器/生命周期)、`docs/MEOWOS_SPEC_REF.md`(速查)、`NOTIFICATION_SYSTEM.md`(通知)、`SYSTEM_SETTINGS.md`(设置)。
---
## 0. 一图看懂:App 是什么
一个 MeowOS App = **一个静态 MIPS32r2 ELF 二进制** + 一份 `app.json` 描述 + 若干资源,打包成 `.mpk`(= tar.gz),装到 TF 卡 `MeowAPPs/<package>/` 下。二进制**链接 `meow_ui.a` 静态库**,通过 `meow_ui.h` 声明的一套 API 拿屏幕、输入、控件、主题、字体、通知、音频——**绝不**自己 `open("/dev/fb0")` / `mmap` / `ioctl` / `open("/dev/input/event*")`。
```
┌──────────────────────────────┐
│ MeowAPP(你的业务,只调 meow_ui API)│
├──────────────────────────────┤
│ meow_ui.a(静态库,含 meow_font) │
│ 绘制/控件/图片/输入/主题/字体/通知/音频│
├──────────────────────────────┤
│ MeowOS 系统(launcher/amsd/audiod)│
└──────────────────────────────┘
```
设备:480×720、RGB565(代码里颜色统一 24-bit `0xRRGGBB`,SDK 内部转 RGB565);文本字号约定 **28**(标题/状态栏)/ **20**(正文/按钮)。
---
## 1. 开发环境准备
### 1.1 交叉工具链
仓库自带了 mipsel musl 静态交叉工具链,直接用,不用自己装:
```sh
$HOME/Project/R3ProII/.tools/zig-mipsel-cc # zig cc 包装(--target=mipsel-linux-musleabi, soft-float)
$HOME/Project/R3ProII/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf/ # sysroot(含 libz.a 等)
```
编译一个静态 MIPS 二进制(参考 `patch_v2/hello_app/build_hello.sh`):
```sh
ROOT="$HOME/Project/R3ProII"
CC="$ROOT/.tools/zig-mipsel-cc"
SYSROOT="$ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf"
"$CC" -Os -static -Wall -Wextra -I"$ROOT/patch_v2/sdk" -I. \
app.c ui/*.c logic/*.c \
"$ROOT/patch_v2/sdk/meow_ui.a" "$SYSROOT/lib/libz.a" -lpthread -o myapp
```
要点:
- 必须 `-static`(加载器只跑静态 MIPS32r2 ELF,软/硬浮点均可,见 `APPS.md §2.1`)。
- 链 `meow_ui.a`(`build_sdk.sh` 已把 `meow_font.o` 打进库里)+ `libz.a`(meow_font 的 inflate 依赖)+ `-lpthread`。
- `-I"$ROOT/patch_v2/sdk"` 引 `meow_ui.h`/`meow_ui_view.h` 等公共头;`-I.` 引你自己工程的头。
- 期望 `-Wall -Wextra` 零警告(参照 hello)。
### 1.2 宿主编译(离屏逻辑测试)
SDK 依赖板载 `/dev/fb0` 与字库,宿主机没有——但你可以**链真实 SDK 目标文件 + 打桩**来跑逻辑单测(参考 `patch_v2/sdk/test_host_stub.c`):
- `test_host_stub.c` 提供了 `meow_font_*` / `meow_ui_global_render` 等宿主桩(无 fb → 5×7 点阵回退;无字库 → `meow_font_load` 返回 -1)。
- 宿主可 `gcc` 直接编译 SDK 各 `.c` + 你的逻辑,跑纯 C 断言,**不碰硬件**(`hello` 的 `test_p1p2p3_repro` / `test_meow_ui_widgets` / `test_pinyin` 等都是这么跑的,宿主宿主验证全 PASS)。
- 注意:`logic/` 层要写**纯 C**(不依赖 `meow_ui.h`),才能裸宿主单测;`ui/` 层才碰 SDK。
### 1.3 qemu-mipsel 全系统仿真
需要跑全链路(含内核/fb)时可用 `qemu_env/scripts/run_halley6.sh`(Ingenic X1600E 全系统仿真):
```sh
qemu_env/scripts/run_halley6.sh # 引导内核到串口控制台(默认)
qemu_env/scripts/run_halley6.sh -usbrootfs # 附加 USB rootfs 镜像
```
注意:必须带 `mem=64M`(脚本已内置,去掉会 early hang);`-usbrootfs` 的 USB 数据通路在 QEMU 里仍不完整(设备未枚举),rootfs 引导会失败——多数情况下它在宿主编译 + 真机验证之间用不上,**不是常规提交流水线**。
### 1.4 真机前置
- 出站靠 TF 卡;确保卡上至少有 `MeowAPPs/`(放 App)与 `MeowData/`(App 数据,首启自动建)。
- 设备跑 MeowOS 模式(默认)。看 `MEOWOS_SPEC_REF.md §5.4` 确认固件版本单点 `/etc/meowos_version`。
---
## 2. 五分钟最小 App(以 hello 为模板)
最快路径:拷贝 `patch_v2/hello_app/` 改包名与文案。逐文件讲。
### 2.1 app.json 全字段表
`app.json` **必填**,放 `MeowAPPs/<package>/` 根目录。参考示例(`patch_v2/apps_assets/apps/os.meow.hello/app.json`):
```json
{"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,"depends":"curl:optional"}
```
| 字段 | 必填 | 说明 |
|---|---|---|
| `name` | ✓ | 列表显示名。**当前仅 ASCII**(UTF-8 中文留待 v3,需完整 UTF-8→UTF-16 转换) |
| `exec` | ✓ | 可执行文件**绝对路径**,必须是**静态** MIPS32r2 ELF(指向 `MeowAPPs/<package>/` 内二进制) |
| `package` | — | 反域名权威标识(= app_id = 目录名 = `.mpk` 文件名茎,三者必须一致)。**`os.meow.*` 为系统保留前缀**(见 §5),第三方用自有前缀(如 `dev.*`)。缺失回退目录名;两者存在且不同 → WARN |
| `version` | — | 显示字符串(versionName 角色,如 `1.2.3-beta`,**不参与比较**) |
| `version_code` | — | **纯数字,安装/升级比较的唯一依据**;缺失 → 拒装;新版 < 已装 → 拒装(防降级),相等 → 允许重装。见 §5 |
| `author` | — | 保留字段 |
| `framework` | — | `1`=SDK 框架 App(用 `meow_ui.a`;**推荐**)。`0`/不写=裸自绘(不推荐,见红线) |
| `immersive` | — | `1`=沉浸。**注意(M2.89+)**:看门狗 `SIGUSR1` 只发给「**裸沉浸**」App(非 `framework=1`);SDK 框架 App 的返回走 `on_swipe`,别把 `SIGUSR1` 当返回键 |
| `background` | — | M4:无头后台服务注册权限(`service.<svc>` 服务二进制 = App 同目录 `<svc>` 文件) |
| `background_audio` | — | M4:后台播放保活(进程不冻结 + UI 冻结复用 §10.5 自抑);播放类 App 声明。如 `dev.player.ncm` 已声明 `background_audio=1` |
| `fullscreen` | — | app 级全屏:状态栏**收合至提示线** + 保留左缘返回/顶部提示线/下滑通知中心。activity 级用 `route.<id>.fullscreen` |
| `fonts` | — | 字库档位声明:`"full"` / `"gbk"` / `"common"`(默认 `common`≈4k;需大字库声明 `"full"`)。**注意**与 `"fonts":["<handle>"]`(应用自带字库句柄)同名二义,见 `MEOWOS_SPEC_REF §7 A-14` |
| `depends` | — | **二进制依赖声明**:`"curl,jq:optional,ffmpeg:required"`(JSON 值串)或行式 `depends=curl,jq:optional,ffmpeg:required`。逗号分项,每项 `<name>[:<mod>]`;无标注默认 required,也接受 `:opt`/`:req`;未知修饰符按 required 并 WARN。启动前查板载 `/usr/bin/<name>` 或卡上 `/usr/data/mnt/sd_0/MeowOS/opt/bin/<name>`(X_OK,命一即可)。required 缺失 → **不启动**(toast「缺依赖: <name>」);optional 缺失 → 仅 WARN 放行 |
| `icon` | — | 预留:应用列表图标 |
> ⚠️ 解析器极简(launcher/nyai 内实现):只匹配 `"name":`/`"exec":`/`"package":`/`framework`/`immersive`/`route.*`/`awake.*`/`service.*`/`background`/`background_audio`/`fullscreen`/`fonts`/`depends`,**不做 JSON 校验**(多行/单行均可),但字段名和引号写法要规范。
### 2.2 main 入口:SDK 初始化 + 事件循环 + 退出约定
看 `patch_v2/hello_app/app.c`,核心骨架如下:
```c
#include "meow_ui.h"
#include "meow_ui_nav.h"
#include "meow_ui_media.h"
#include <signal.h>
static void on_sigterm(int s) { (void)s; meow_ui_request_exit(); } /* 干净退出:不画 BYE 直接退 */
int main(void)
{
meow_log(MEOW_LOG_INFO, "start");
/* ① SDk 初始化:开 fb / 读几何 / 载字库 / 建 back_buf。opts 宽高 0=自动读,宿主导跑用 480x720 */
meow_ui_opts o = { 480, 720 };
if (meow_ui_init(&o) != 0) { meow_log(MEOW_LOG_ERROR, "init failed"); return 1; }
/* ② 主题监听 + M3 导航上报 + 媒体会话(可选,但推荐接线) */
meow_theme_add_listener(NULL);
meow_ui_nav_init(); /* 读 MEOW_ACTIVITY / MEOW_CHAIN 初始化导航栈 */
meow_ui_media_init(NULL); /* app_id = env MEOW_LOG_APP */
meow_ui_media_on_playctl(demo_media_playctl);
signal(SIGTERM, on_sigterm); /* 退出兜底:SIGTERM → request_exit */
/* ③ 建各页 view(on_create 建控件/状态)+ 弹层实例(dialog/sheet 建一次,复用)*/
meow_view *home = home_view_create();
meow_view *full = full_view_create();
meow_view *demo = demo_view_create();
/* ④ 进入根页:activity_push + home on_enter → home_redraw */
meow_view_page_push(ACT_HOME);
/* ⑤ 事件循环回调表(on_key 物理键是系统语义,SDK 不下发业务确认/exit)*/
meow_ui_events ev = { app_on_key, app_on_tap, app_on_swipe, app_on_tick,
app_on_swipe_progress, app_on_touch_down, app_on_touch_move };
meow_ui_run(&ev); /* 阻塞,直到 meow_ui_request_exit() */
/* ⑥ 清理:释放 view(触发各页 on_destroy)+ 弹层 + shutdown */
meow_view_free(full); meow_view_free(demo); meow_view_free(home);
meow_dialog_free(g_dlg); meow_sheet_free(g_sheet);
meow_ui_shutdown();
meow_log(MEOW_LOG_INFO, "exit");
return 0;
}
```
**退出约定**(`APPS.md §3.3`):
- 任意退出码均可,`exit(0/1)` 即可,**不要 `abort()` 留僵尸**——守护进程 `waitpid` 立即返回并恢复播放器。
- 用 `meow_ui_request_exit()` 或**根页左缘左滑**退出;**绝不监听物理键映射 exit**(见 §8 红线)。
- 不需要「恢复/清屏」——应用退出后系统恢复原界面,你清屏反而闪一下。
### 2.3 事件分发
`meow_ui_run` 给的平铺回调,在 `app.c` 里路由到当前页(见 `app.c` 的 `app_on_tap`/`app_on_swipe`/`app_on_tick`/`app_on_touch_*`)。典型分法:
- `on_tap` → 命中当前页控件(`meow_widget_hit` / `meow_listview_hit` / `meow_topappbar_hit` 等)。
- `on_swipe` → `LEFT`=返回(`meow_view_page_pop`,根页则 `meow_ui_request_exit`);`UP/DOWN`=滚动。
- `on_touch_down`/`on_touch_move` → 跟手拖动(slider / 列表 / 页面滚动)。
- `on_tick` → 动画/时钟(如进度条步进)。
- **`on_key` 仅供 SDK 内部状态,不含电源/音量/播放的确认/exit 语义**——别把它当输入。
- 左缘返回手势:边沿上报 `on_swipe_progress`,SDK 自动画顶部进度条(`meow_ui_swipe_hint`)。
---
## 3. 工程结构约定
hello 的分层(`hello_internal.h` 里写得很清楚),建议照抄:
```
myapp/
├── app.c # main + 装配 + 事件分发(把 meow_ui_run 回调路由到各页)
├── myapp_internal.h # 跨 TU 共享声明(只 extern 声明,不定义)
├── ui/
│ ├── home/home.c # 每页一个 view 文件(持本页视图态)
│ ├── home/home.h
│ ├── demo/demo.c
│ ├── list/list.c # 页面内 CONTAINER 子视图(列表区块)
│ ├── full.c
│ └── common/chrome.c # 共享 chrome(TopAppBar / NavigationBar / FAB)
└── logic/
├── logic.h # 纯 C 接口(不依赖 meow_ui.h)
├── state.c # 计数/状态业务 + getter/setter
└── notify.c # 通知业务(此处才 include meow_ui.h 用 SDK 通知 API)
```
**分层契约**(`logic/logic.h`):
- ① UI→业务:只调 `logic_*` / `notify_*` 纯函数命令/getter,业务**不回调 UI**。
- ② 业务→UI:业务态变化经 `logic_set_listener(cb, ctx)` 的**可选 push 监听器**通知 UI 刷新(`home_state_listener` 示例)。
- ③ 每页 `meow_view` 的 `ctx` = **视图控制器**(biz-UI 绑定点),页面后续可把这层状态吸进控制器。
**视图框架 `meow_view`**(`patch_v2/sdk/meow_ui_view.h`):
- 生命周期钩子:`on_create → on_enter → (事件循环) → on_leave → on_destroy`,另有 `on_layout`/`on_draw`/`on_event`。均可 NULL。
- **页面即文件**:每页一个 `meow_view_create(id, MEOW_VIEW_ACTIVITY, &hooks, &ctrl)`,`id` 是你命名的稳定值(`ACT_HOME=0`/`ACT_FULL=1`/`ACT_DEMO=2`)。
- 页面切换统一走 `meow_view_page_push/pop/replace`(包装 activity 栈,**on_enter/on_leave 唯一入口**),App 不再分别调 `activity_push/pop`。
- 子视图嵌套:`meow_view_add_child` 挂 CONTAINER 子 view(如 demo 页的列表区块 `list.c`),嵌套生命周期由 SDK 递归驱动(随父页进出),无需 App 显式接管。
- `on_draw` 为回调、view 不缓存像素(非保留);重绘由 `meow_ui_invalidate` 触发。
- `meow_view_dispatch_event` 是「把语义事件注入当前页谓词子树路由」的入口(先子>父按 z 降序,命中即消费);未接线于 `meow_ui_run`,你可在 `on_tap`/`on_swipe` 里改用它做 SDK 原生路由。
---
## 4. 构建与打包
### 4.1 链接 meow_ui.a
见 §1.1 命令。`meow_ui.a` 由 `patch_v2/sdk/build_sdk.sh` 构建(内含 `meow_font.o`),改 SDK 源码后**重编 meow_ui.a 必须同步重编你的 App**(结构变化即 ABI 变化,见 `MEOWOS_SPEC_REF §3.4`)。
### 4.2 `.mpk` = tar.gz
`.mpk` 内容 = `app.json` + 二进制 + 资源(**不含 data/cache**,运行时按设备生成)。包名 = `.mpk` 文件名茎 = `app.json.package`(如 `os.meow.hello` → `os.meow.hello.mpk`)。
> **nyai 无独立 `pack` 命令**。两种出包:
> 1. **手工打包**(从 staging 目录,`meow_sign/README §2.2` 的口径):
> ```sh
> mkdir -p build/os.meow.xxx
> # 放 app.json + meow 二进制 + 资源;app.json.package == 目录名
> tar czf os.meow.xxx.mpk os.meow.xxx # .mpk 须在 tar 根(无 <pkg>/ 包裹 → 换包时注意)
> ```
> 2. **导出已装应用**:设备上 `nyai export <pkg> [out.mpk]`(打 `MeowAPPs/<pkg>/`,排掉 `*.pre_*`/`*.bak_*`/`logs/` 等运行残留)。
`.mpk` 内部结构:
```
os.meow.xxx.mpk (tar.gz)
├── app.json # package/exec/version(+version_code)/framework/immersive/background_audio/...
├── meow # 静态 MIPS32r2 ELF(exec 指向)
├── <资源文件> # 只读程序资源
└── <signature footer> # 仅签名过的包有(Ed25519 v2 184B;尾部追加)
```
---
## 5. 签名与安装分级
### 5.1 分级规则(`APPS.md 附录 §3` / 签名设计)
| 包 | 规则 |
|---|---|
| **`os.meow.*`(系统保留前缀)** | **必须有有效签名**;无签 / 验签失败 → 一律拒装(防伪造系统包名) |
| 第三方(自有前缀如 `dev.*`) | 无签可**直接装**(安装时告警);**有签必须验签**(失败拒装) |
| 所有包 | 验签 + **三重绑定** + **防降级**(见下) |
**防降级**:只看 `version_code`(纯数字)。新版 code < 已装 → 拒装;相等 → 允许重装;**code 缺失 → 拒装**(提示缺 version_code)。`version` 仅显示,不比较。
**三重绑定**(三方各司其职):
1. 签名 footer 的 `package` == `.mpk` 文件名茎(`meow_verify` 校验)——防改名。
2. 签名 footer 的 `package` == 解包 `app.json.package`(nyai / launcher 安装流校验)——防改 app.json。
3. `os.meow.*` 前缀 + 必须验签通过(nyai / launcher 安装流)——防无签冒充系统包。
### 5.2 开发者自签名流程(不泄露任何密钥材料)
> 工具:`patch_v2/meow_sign/meow_sign`(**宿主端、离线维护机**,不落设备、设备只持公钥)。私钥**永不入库、永不落设备**。设备侧把关的是固件 `/usr/bin/meow_verify`(内嵌公钥数组,任一公钥通过即放行)。
```sh
# ① 首次:生成/备份密钥(仅一次;若 .keys/signing.key 已存在,直接复用,勿覆盖——覆盖会令旧包全部失效)
./patch_v2/meow_sign/meow_sign keygen .keys/signing.key
# 私钥 .keys/signing.key(0600) 离线备份;公钥 .keys/signing.key.pub 可入库/固化进固件
# ② 打包(不含签名,纯 tar.gz;app.json.package 必须 == 目录名 == .mpk 文件名茎;version_code 单调递增)
tar czf os.meow.xxx.mpk os.meow.xxx
# ③ 签名(默认 v2 含被签时间戳)
./patch_v2/meow_sign/meow_sign sign os.meow.xxx.mpk .keys/signing.key
# ④ 产签后自测(推荐)
./patch_v2/meow_sign/meow_sign verify os.meow.xxx.mpk .keys/signing.key.pub
./patch_v2/meow_verify/meow_verify_host os.meow.xxx.mpk # 用固件内嵌公钥,与设备同口径
# ⑤ 装到设备:把 .mpk 放卡上 → nyai install
```
要点:
- `sign` 的 `.mpk` 文件名茎**必须等于包名**(否则 `meow_verify` 二次校验拒)。
- v2 块(184B,含时间戳)需 **2B+ 固件**;`--v1` 产物(180B)兼容旧 2A 固件——固件与工具链同版本发布即可避免兼容坑。
- 签名是**防误装 / 防伪造包名 / 防沿途篡改**,不是安全边界(见 `meow_sign/README §0`,诚实标注)。
- **本仓库 `.keys/signing.key` 是系统主钥**——第三方开发者**不要用它**,用自己的密钥对(否则无法区分来源,且泄露私钥等于放弃系统包信任)。你自己的私钥离线备份,公钥按需提交给设备厂商固化进 `meow_verify` 公钥数组(或走公开渠道分发)。
### 5.3 安装
- 设备端:`nyai install <pkg或文件.mpk>`(固件内置 `/usr/bin/nyai` 优先,或卡上 `MeowOS/opt/bin/nyai`)。同名 = 更新(旧目录备份 `.bak_<pid>`,`MeowData/<pkg>/{data,cache}` 保留,成功自清 `.bak`)。
- `nyai list` / `nyai info <pkg>` / `nyai remove <pkg>`(数据默认保留,`--purge` 连 MeowData 删)。
---
## 6. 部署与调试
### 6.1 TF 卡目录
```
/usr/data/mnt/sd_0/
├── MeowAPPs/<package>/ # app.json + meow 二进制 + 资源(目录名 = app.json.package)
├── MeowData/<package>/{data,cache} # App 数据(首启自动建;exFAT 无软链,用 env 注入)
└── MeowOS/logs/<package>/ # App 日志(每次启动新文件 <ts>_<pid>.log)
```
启动时注入 env(SDK `meow_config_*`/`meow_ui_nav_init`/日志都读它):
`MEOW_DATA_DIR` / `MEOW_CACHE_DIR` / `MEOW_LOG_DIR` / `MEOW_LOG_APP`(=package) / `MEOW_LOG_LEVEL` / `MEOW_ACTIVITY` / `MEOW_CHAIN`(+ `MEOW_FONT_PNG`/`MEOW_FONT_IDX` 当前生效字体、`MEOW_FULLSCREEN`)。
### 6.2 面板上传
MeowPanel(外置 TF 卡 Web 面板,8080)可 `nyai install/export/remove`。**⚠️ 面板鉴权默认关**(内网信任模型)——`reboot/shutdown/ota/apps/删除/上传` 危险端点勿暴露公网。
### 6.3 am CLI(活动管理器)
`am` 是 amsd 的用户侧 CLI(固件 `/usr/bin/am`),连 `/usr/data/meow_ams.sock`(`--sock` / `env MEOW_AMS_SOCK` 覆盖):
```
am status # 在跑 App 表(pkg/状态/pid/运行时长/activity)
am list # 注册表 + 状态(pkg/name/state/pid/launches/crashes)
am history [pkg] # 事件历史(每 App 最近 8 条)
am start <pkg>[/<act>] # 拉起(root)
am kill <pkg> # 终止(SIGTERM 语义,root)
am rescan # 手动触发注册表重扫(卡刚挂载/热装后立即可见)
am resolve <pkg> <activity> # 路由决策(返回 CHAIN)
```
写操作(start/kill)**仅 root**。**已知坑(agent-250)**:开机早于卡挂载 → amsd 注册表扫空 + 永不重扫 → `am start` 报 `not registered`——用 `am rescan` 或等 3s 自愈/30s 合并重扫。
### 6.4 日志
- **App 日志**:`meow_log(lvl, fmt, ...)` 一行;SDK 写 `MeowOS/logs/<package>/<ts>_<pid>.log`(每次启动新文件,单文件 ≥2MB 自动续新,每 App 总量上限,无卡降级 stderr 不崩)。级别:`MEOW_LOG_DEBUG/INFO/WARN/ERROR`;`meow_log_set_level` 可选,默认 `INFO`。
- **崩溃日志**:系统级 `meow_crash` 捕获 + App 崩溃直报。真机看 `/usr/data/meow_*` 与 `MeowOS/logs/`。
- **离线跑**:`meow_launcherd --scan` 列应用;宿主跑逻辑单测看 stdout。
---
## 7. SDK 能力速查表
(完整签名以 `patch_v2/sdk/meow_ui.h` 为准——下面每行都可交叉印证。)
### 7.1 控件库清单(一句话)
| 控件 | 一句 |
|---|---|
| `meow_btn_create`(Filled)/ `meow_btn_create_variant`(五型:FILLED/TONAL/OUTLINED/TEXT/ELEVATED) | 按钮;命中用 `meow_widget_hit`,选中/禁用走状态机 |
| `meow_slider_create` / `meow_switch_create` | 滑条/开关;`set/get` 取值 |
| `meow_list_create`(label-only)/ `meow_lazycolumn_create`/`meow_lazyrow_create` | 数据源回调 + 可见区虚拟化;`on_swipe` 里 `meow_list_scroll_by` 驱动滚动 |
| `meow_listview_create`(`W_LISTVIEW` 结构化列表) | 行=图标+标题+副标题+右侧操作+选中;`meow_listview_hit`/`meow_listview_tap_at` |
| `meow_textfield_create`(接 IME)+ `meow_textfield_activate` | 单行文本输入(阻塞 IME) |
| `meow_card_create` + `meow_card_set_content` / `meow_divider_create` / `meow_badge_create` | 卡片/分隔线/角标 |
| `meow_checkbox_create` / `meow_radio_create` | 勾选/单选(走 set_selected) |
| `meow_progress_create`(线性)/ `meow_progress_circular_create`(圆形) | 进度条(0-100,`-1`=不定) |
| `meow_fab_create` | 悬浮按钮 |
| `meow_topappbar_create`(标题+返回+动作)/ `meow_navigationbar_create`(底部导航)/ `meow_tabbar_create`(顶部分页) | 标题栏/导航栏/页签 |
| `meow_dialog_create`(CONFIRM/SELECT/INPUT/INFO)/ `meow_sheet_create`(BottomSheet) | 阻塞模态;点外/左缘返回=取消 |
| `meow_row_create`/`meow_column_create`/`meow_box_create` + `meow_layout_add`/`arrange` + `meow_layout_spacer` | 组合布局(固定尺寸子项) |
| `meow_widget_custom_create` | 自定义画布:App 给 render/hit 回调(同 `meow_widget`) |
| 通用 | `meow_widget_set_opts`(Modifier 式 Options)、状态机 `meow_widget_set_state`(ENABLED/SELECTED/FOCUSED/PRESSED)、`meow_widget_set_z(999)` 置顶、`meow_draw_custom_register` 注册非控件直绘 |
### 7.2 主题(MD3 / MIUIx × 明暗——适配义务)
- 主题 = **顶级风格族 `ui.style`(`MEOW_STYLE_MD3`/`MEOW_STYLE_MIUIx`)× 明暗 `display.theme`(light/dark/auto)**。
- **适配义务**:用 `meow_theme_get()` 取的**语义色**(`primary`/`surface`/`surfaceContainer`/`outline`/`bg`/`fg`/`accent`/`dim`…),**不要硬编码颜色**;明暗都要能看(`AUTO` 会来回切)。当前 MIUIx 仅换配色(状态栏图标未 per-style 换表,是已知缺口)。
- 字号约定:`font_title=28` / `font_body=20`(`meow_theme` 给默认)。
- 界面底 `meow_ui_set_page_bg(th->bg)`;状态栏高度 `meow_ui_statusbar_height()`(非全屏=28,全屏=0)用于内容 inset。
### 7.3 字体档位声明 + 应用字库接口
- 档位:`MEOW_FONT_TIER_COMMON`(默认 ≈4k)/ `GBK`(≈20k+)/ `FULL`(全字符集)。App 需大字库时 `app.json "fonts":"full"`,launcher 为你加载大字库。
- SDK:`meow_font_set_tier` / `meow_font_tier`;`meow_font_get_available_count` / `meow_font_enumerate`(读 `MeowOS/config/fonts/index`)/ `meow_font_select_by_id`(App 会话内固定用某套;丢失降级默认 + WARN)。
- **判缺字**:`meow_font_has_glyph(cp)` 预判当前字库是否含该码位——缺字自行回退/提示。
- 注意:App **未自带字库**则跟随系统 `font.file`(launcher 以 `MEOW_FONT_PNG`/`MEOW_FONT_IDX` env 注入);应用自带字库(`load_handle`)为**草案/未实现**。
### 7.4 IME(输入法)
- 模式:`MEOW_IME_ASCII` / `MEOW_IME_NUM` / `MEOW_IME_PINYIN`(中文拼音)。
- 阻塞:`meow_ui_text_input(ctx)`(回车=提交+关闭、ESC/点背景=取消);`meow_textfield_activate` 聚焦并打开阻塞 IME。
- 非阻塞:`meow_ui_text_input_async` + `meow_ime_active()`/`meow_ime_text()`/`meow_ime_composition()`(App 在 `on_tick` 里轮询)。
- **坑**:阻塞 IME 的内层轮询必须喂 `meow_ui_activity_ping`(SDK 已做)否则进度条/息屏卡死;键盘区背景快照/恢复(防残影);回显条是**实时输入回显**非候选,候选框留中文。
### 7.5 音频(meow_audio_*,独占 audiod)
- 链路:`meow_audio_open(rate, ch, fmt)` → 返回 `stream_id > 0` → `meow_audio_write(pcm, frames)`(**阻塞**,socket 背压)→ `meow_audio_pause(sid, paused)` → `meow_audio_close(sid)`。
- audiod 独占 `hw:0,0`;**后开流赢**,旧流收 `MSG_AUDIO_PREEMPTED` → 你的 `on_preempt` 回调 → 之后 `write` 返回负值,应重新 `open`。
- **audiod 不在 → `open` 返回 `MEOW_ERR_NOENT`(负值),不做隐式直开回退**;`MEOW_AUDIO_DIRECT=1` 仅调试用别依赖。
- `meow_audio_query(&state)` 读快照(活跃流/采样率/underrun/路由口/音量);`meow_audio_on_preempt(cb, ctx)` 注册被抢占回调。
- DAC 原生率跟随、无 SRC;格式归一(S16 直通 / S32 升位 / mono 复制);4.4/3.5 路由随插孔。
- 播放类 App 记得 `background_audio=1` + 媒体会话上报(见 §7.7)。
### 7.6 通知(meow_notify_* / MSG_NOTIFY)
- 声明:`meow_notify_register_type(cat)`(每 App ≤4 类型);发:`meow_notify_send(&n)`(返回 id);消:`meow_notify_cancel(id)`;进度:`meow_notify_update_progress(id, p)`。
- 配额:**每 App ≤4 类型;每类型活动 ≤4(满丢最旧);媒体单例(每 App 1 条,重发替换)**。
- `timeout_ms`:`>0`=过期毫秒数;`0`=不过期(直到 cancel);`-1`(ONGOING)=常驻。
- 负载 `meow_notify{n}`:`category`/`urgency`/`title`/`body`/`icon`/`timeout_ms`/`target_activity`(点击跳转 Activity 名,M3 导航预留)/`progress`。字段对齐 freedesktop `Notify()`。
- 走 IPC `MSG_NOTIFY`;10s 后自动转储到 TF `MeowOS/notifications/`(ring 10MB)。当前 SDK 内部维护活动表 + 日志落点;真实投递到通知中心 UI(launcher 渲染)为已落地链路。
### 7.7 媒体会话(meow_ui_media_*)
- `meow_ui_media_init(app_id)` / `meow_ui_media_on_playctl(cb)`(注册播放控制并启动后台接收线程)/ `meow_ui_media_meta(state, pos_ms, dur_ms, title, artist, album, artwork, lyric)`(上报元数据)/ `meow_ui_media_stop()`。
- 控制回调 `playctl(cmd, seek_ms)`:cmd `1`=播放 `2`=暂停 `3`=上一首 `4`=下一首 `5`=seek `6`=播放/暂停 toggle(物理播放键 164 恒发 6,由你对当前态翻转——**源是状态权威**)。物理上下曲:`meow_ui_media_sys_key(code)`。
- 声明 `MF_PLAYBACK` 的 App 首次上报 → 注册为媒体源;通知中心媒体卡片 / 主界面 Now Playing 据此显示。
- best-effort:无 launcher/socket 静默降级,绝不崩。
### 7.8 后台播放声明
- `app.json "background_audio":1`(M4 已落地):进程**不解冻** + **UI 冻结**复用 `§10.5 自抑`(比任务书 SIGSTOP 方案优)。launcher 有 `meow_bgplay` 保活表(退避 1/2/4/10s + 熔断 + 广播);应用列表标注「·后台」。依赖 `background` 字段 + `service.<svc>`。
- 声明后台播放后:无头时仍喂媒体会话 + 可继续 `meow_audio_*` 出声;退出时停线程/关流。
### 7.9 应用配置(meow_config_*)
- App 自有键值偏好,落 TF 数据目录(`MEOW_DATA_DIR`,无卡回退 `/tmp/meowcfg_<pid>`),**不占板载、无 schema**(与系统 `meow_settings_*` 区分)。
- `meow_config_set/get/int/bool/remove`;写 `<data>/<sanitized_key>.cfg`(key 只允许 `[A-Za-z0-9._-]`,上限 64)。返回码 `MEOW_CFG_OK(0)` / `NOENT(-2)` 等。
### 7.10 网络注意事项
- App 一般不需要联网;确需联网时**克制请求**(见 §8 风控式节制)。
- 系统层面(`netmgrd` 期 0/1):无 iptables 用户态 / 无 cgroup / 无 conntrack → **内核级每应用管控不可行**;主杠杆 = DNS 代理(域名级)+ 连接普查(inet_diag)+ **SDK 自愿通道**。App 走 SDK 自愿通道配合管控是后续方向;当前 App 如联网,请自律、尊重断网降级。
### 7.11 字符串资源 / i18n(`meow_str_*`,2026-09-02 追加)
- **任何 UI 文案都不要硬编码**,用 `meow_str("域.名")` 按当前语言取串(示例:`meow_str("common.ok")`)。`meow_str` 永不返回 `NULL`:未知 id 原样返回 `id`,方便你打点/兜底展示。
- **`meow_strf(out, outsz, id, ...)`**:取串后按 `snprintf` 语义格式化进调用方缓冲(自带 NUL;`out==NULL`/`outsz<=0` 返回 `-1`)。
- **切换语言**:`meow_str_set_locale("zh_cn"|"en_us")` 即时生效;locale 存系统设置 `settings.conf` 的 `ui.locale`(默认 `zh_cn`,SDK 启动惰性读取)。App 里做语言切换页时调用它即可。
- **覆盖/追加**:可不改二进制在 TF 卡放 `MeowData/<pkg>/strings/<locale>.txt`(每行 `id=text`,UTF-8)覆盖内嵌或新增自己的串(如 `player.pause=暂停`);无卡/无文件/无该 id → 回落内嵌双语默认表(`common.*` + `ime.*` 内置,见 `include/meow_ui_str.h`)。
- **id 规范**:`域.名`;SDK 自带 `common.*`/`ime.*`,你自建 `域`(如 `player.*`)避免撞名。
- **边界**:当前为 i18n 雏形,仅 `zh_cn`/`en_us` 双语**整串**取用;**不做复数规则 / BiDi / RTL / 动态字体回退**(详见 `MEOW_APP_SDK.md §13.7`)。
---
## 8. 规范与红线
> 详见 `APPS.md §1.3 / §3.3 / §3.4.1`、`MEOWOS_SPEC_REF §2.4 / §5.1`。以下为最要紧的违约原因。
1. **物理键禁映射返回/确认/OK/翻页**。物理键是系统资源:电源=息屏/亮屏/长按菜单/8s 强关,音量=调音量,播放/上下曲=媒体控制,返回=**触屏左滑**(`MEOW_SWIPE_LEFT`)/虚拟返回(非物理键)。**绝不用 `on_key(POWER)→exit` 这类映射**;`on_key` 仅供 SDK 内部状态。界面导航一律走触摸(返回钮/左缘左滑)。
2. **局部脏区刷新,别整屏重画**。控件/区域变化用 `meow_ui_invalidate(x,y,w,h)`(清底 + 按 z 重画相交绘制物 + 原子上屏),别 `meow_ui_flush`/整帧重画。**非控件直绘内容**(`meow_ui_text`/`meow_ui_fill_rect` 等)**必须 `meow_draw_custom_register` 注册**(声明 bbox + 绘制回调 + z),否则区域重画清底后会漏画(hello demo 的分区标题/slider 值文本就是典型注册示例)。滚动列表的视口裁剪由 SDK 全局负责(`W_LIST` 只画视口内行),别在 App 侧再做整屏。
3. **内存预算**。设备内存很紧(QEMU `mem=64M` 参考,rootfs ≤45MB)。避免大块分配、避免大字库双驻留(大字库按档装载、卸载旧 atlas)、别把卡上大文件 mmap 当运行必需——**运行期别依赖卡上资源**(见 §9 卡移除)。
4. **板载存储别乱写**。板载 `/usr/data` 只剩 ~10MB,是稳定框架 + KB 级设置。**App 数据一律写 `MeowData/<pkg>/`(TF,经 `MEOW_DATA_DIR`/`MEOW_CACHE_DIR` env)**;绝**不碰 NAND 分区 / mtd / `/data/recovery_all`**(恢复出厂,红线)。
5. **系统动作经专项服务**。App/组件不直连 `sys_do_reboot`、不写 NAND/mtd5;关机/重启/升级/回 HiBy 一律经 `meow_svc_power`(IPC)。App 一般无需做,但别绕过。
6. **风控式网络请求节制**。避免高频轮询/无限重试/后台自灌;尊重断网/弱网(优雅降级而非死循环/崩溃);与系统 netmgrd 管控共存(域名级,非内核 per-app)。
7. **别 destroy 别占用播放器/声卡**。不要 `killall hiby_player`(触发看门狗整机重启);不要 fork 长驻后台进程(退出要「真正退出」,否则 SIGCONT 后仍占屏抢输入)。
8. **`app.json` 别缺失 `version_code`**(拒装);别用 `os.meow.*` 前缀(除非你是系统签名 App)。
---
## 9. FAQ / 常见坑(真踩过的)
1. **脏区透传:局部刷新抹掉上层 chrome**。症状:拖动/点某个控件后,上层 FAB/标题栏被抹掉。根因:App 侧旧的「清底 + 只画单个控件」刷新。**修法**:改走 SDK `meow_ui_invalidate`(z 栈重画下层+上层),非控件直绘**注册 CUSTOM**(`meow_draw_custom_register`),否则清底后漏画。`meow_ui_paint_full()` 整帧按 z 重画兜底。
2. **IME 阻塞不喂 on_tick → 进度条/息屏卡死**。阻塞 `meow_ui_text_input`/`meow_textfield_activate` 的内层轮询必须喂 `meow_ui_activity_ping`(SDK 已做);键盘区用背景快照/恢复防残影;回显条是实时输入回显非候选。
3. **字体声明丢失 → 降级小字库**。`app.json "fonts":"full"` 丢了 / 大写不符 / 该档没生成 → 按 `common` 装载,生僻字缺字(`meow_font_has_glyph` 预判,回退或提示)。字体三件套(`font.png`+`font.idx`+`font.json`)缺一件 → 该套不进列表。
4. **热更新 `.new` + `mv` / 覆盖运行中程序的坑**。程序主体在卡上、运行中被覆盖/替换 → 访问旧代码页可能 **SIGBUS**。**修法**:升级用 `nyai install`(同包名 = 更新:旧目录备份 `.bak_<pid>` → 重启后替换 → 数据保留 → 成功自清 `.bak`);别自己 rm/mv 运行中的可执行文件。导出时 `*.pre_*`(热更备份)、`*.bak_*` 会被过滤。
5. **开机早于卡挂载 → amsd 注册表扫空**。`am start/list` 报 `not registered`(agent-250)。**修法**:`am rescan` 手动触发,或等 3s 快重试 / 30s 合并重扫自愈。
6. **`SIGUSR1` 误当返回键**。M2.89 起看门狗只对「**裸沉浸**」App(非 `framework=1`)发 `SIGUSR1`;SDK 框架 App 收到会被 SDK 的 `sigusr1_guard` 护栏记日志+吞掉。**别**为「返回」装 `SIGUSR1` handler;SDK 框架 App 返回走 `on_swipe`。
7. **goodix 幽灵触摸**。真机启动触摸尾部会发一段幽灵触摸(启动后 1.2–2.25s,见 `APPS.md §3.5`)。框架 App 走 `on_tap`/`on_swipe` 语义事件,launcher/SDK 已做 drain + 冷却;裸自绘 App 要自己加启动冷却(如 `COOLDOWN_MS`),否则会把幽灵当下按立即退出。
8. **音频:audiod 不在 / 被抢占**。`meow_audio_open` 返回负值(`MEOW_ERR_NOENT`)→ audiod 不在,**别隐式直开**(`MEOW_AUDIO_DIRECT=1` 仅调试)。被抢占后 `write` 返回负值 → 重新 `open`。长流卡死(~12s 停摆)是早期驱动/ring 问题,已由 audiod 加固(`period=1024×4` + 长流护栏)修掉;App 侧 `write` 阻塞背压,别一 flood 灌。
9. **物理键映射 exit**。旧 hello 曾 `on_key(KEY_POWER)→request_exit()`,违反红线——已要求删除。规范:电力键=息屏/亮屏,播放键=媒体控制,返回=左缘左滑。你的 App 别重蹈。
10. **`meow_ui.a` ABI 变化**。改 SDK(`meow_ui_theme`/`meow_widget` 结构)即 ABI 变化 → **必须同步重编 App**(`MEOWOS_SPEC_REF §3.4`)。SDK 编译期依赖 launcher 头(`-I../meow_launcherd` 引共享 `meow_ipc.h`)是**已知设计**,不是耦合错误——运行时只链 `meow_ui.a` 不会链入 launcher 符号。
---
## 10. 上架检查单
- [ ] `app.json`:`package`(自有前缀)/`exec`(静态路径)/`version`+**`version_code`(单调递增)**/`framework:1`;需大字库加 `fonts`;播放类加 `background_audio`;依赖外部二进制加 `depends`。
- [ ] 二进制是**静态** MIPS32r2 ELF;链 `meow_ui.a` + `libz.a` + `-lpthread`;`-Wall -Wextra` 零警告。
- [ ] 颜色用主题语义色不硬编码;明暗都能看。
- [ ] 控件/区域刷新走 `meow_ui_invalidate`;直绘内容已 `meow_draw_custom_register`。
- [ ] 数据写 `MeowData/<pkg>/`,不写板载/NAND;退出干净 `exit(0)`;根页左滑 `meow_ui_request_exit`。
- [ ] `os.meow.*`(系统签名)vs 第三方(自有前缀,建议也签名防篡改)。打包 `tar czf`(或 `nyai export`,文件名茎=包名)→ `meow_sign sign` → 自测 `verify`/`meow_verify_host`。
- [ ] 真机冒烟:`am start`/`am status`/日志 `MeowOS/logs/<pkg>/`,卡移除/无网/断流降级不崩。