MEOW_APP_SDK.md(相对主仓 MEOW_APP_SDK.md,保留公开仓化改写与免责声明): - 公共头模块索引补 meow_ui_str.h 行 - §5.5 新增「虚拟化层」(W_LISTVIEW 固定 item 池/多行富 item/自绘钩子;去除 ncm 引用) - §5.8 新增 TopAppBar 位图动作图标 + sb_icons[] 30 图标索引表 - §6.1 后插入 §6.4 事件分发形式化(命中/捕获/消费/层间隔离/唯一焦点) - 新增 §12 布局/动画/滚动(meow_box/meow_anim/meow_scroll)+ §13 字符串 i18n(meow_str_*) - 遗留/待实现 由 §12 重编号为 §14;去除 ncm/网易云/NetEase 字样 APP_DEV_GUIDE.md:新增 §7.11 字符串资源/i18n(保留既有公开化改写 §1.1/§5.2/§10 等) README.md:更新 include/ 子头计数(20),补「SDK 能力速览」(布局/动画/滚动/事件分发/ 虚拟列表/i18n/图标库/图片降采样) .gitignore:新增本次引入的宿主测试二进制名(test_meow_ui_box/anim/dispatch/listvirt/scroll/str)
37 KiB
MeowOS 第三方 App 开发指南(APP_DEV_GUIDE)
受众:想在 R3 Pro II / MeowOS 上发布自己 App 的第三方开发者(非 MeowOS 系统组)。 从零到上架动线:环境 → 最小 App → 工程结构 → 构建打包 → 签名安装 → 部署调试 → SDK 能力 → 红线 → FAQ。 权威口径:本文所有 API 以
sdk/meow_ui.h为准;规范以MEOW_APP_SDK.md为准。凡与meow_ui.h不一致,以头文件为准。 配套文档(本公开 SDK 仓内):docs/MEOW_APP_SDK.md(SDK 设计全量)。 说明:本文为 MeowOS-SDK 公开仓 的开发者指南。文中引入但未随本仓发布的主仓内部文档/组件(APPS.md、docs/MEOWOS_SPEC_REF.md、NOTIFICATION_SYSTEM.md、SYSTEM_SETTINGS.md、FRAMEWORK_DESIGN.md、devdocs/、qemu_env/,以及签名工具patch_v2/meow_sign/meow_sign、patch_v2/meow_verify/meow_verify_host等)均位于 MeowOS 主仓MeowOS-For-R3P2(内部)或设备固件内,仅用于说明系统行为与约定;本公开仓不含这些文件,也不含任何密钥。签名请走你自己生成的密钥对(见 §5)。
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 交叉工具链
需要一台 MeowOS 开发机(装好 mipsel musl 静态交叉工具链)。工具链不随本公开仓分发;以下为 MeowOS 开发机上现成的路径(可经 MEOW_CC / MEOW_SYSROOT 覆盖,或用你自己装好的 zig 交叉工具链):
$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 二进制(参考 examples/hello/build_hello.sh;examples/hello/ 即本仓的 SDK 示例工程):
MEOW_CC="$HOME/Project/R3ProII/.tools/zig-mipsel-cc" # 可覆盖;用你装好的 zig 交叉 cc
MEOW_SYSROOT="$HOME/Project/R3ProII/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf"
"$MEOW_CC" -Os -static -Wall -Wextra -I"<本仓>/sdk" -I. \
app.c ui/*.c logic/*.c \
"<本仓>/sdk/meow_ui.a" "$MEOW_SYSROOT/lib/libz.a" -lpthread -o myapp
要点:
- 必须
-static(加载器只跑静态 MIPS32r2 ELF;软/硬浮点均可,见APPS.md §2.1)。 - 链
meow_ui.a(sdk/build_sdk.sh已把meow_font.o打进库里)+libz.a(meow_font 的 inflate 依赖)+-lpthread。 -I"<本仓>/sdk"引meow_ui.h/meow_ui_view.h等公共头(伞头 +include/子头已在sdk/内);-I.引你自己工程的头。- 期望
-Wall -Wextra零警告(参照 hello)。
1.2 宿主编译(离屏逻辑测试)
SDK 依赖板载 /dev/fb0 与字库,宿主机没有——但你可以链真实 SDK 目标文件 + 打桩来跑逻辑单测(参考 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 全系统仿真):
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 为模板)
最快路径:拷贝 examples/hello/ 改包名与文案。逐文件讲。
2.1 app.json 全字段表
app.json 必填,放 MeowAPPs/<package>/ 根目录。示例(examples/hello/ 示例工程的 app.json 在 MeowOS 主仓 apps_assets/ 下,本公开仓未附带;下面给出可直接照抄的完整示例):
{"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.music 可声明 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「缺依赖: 」);optional 缺失 → 仅 WARN 放行 |
icon |
— | 预留:应用列表图标 |
⚠️ 解析器极简(launcher/nyai 内实现):只匹配
"name":/"exec":/"package":/framework/immersive/route.*/awake.*/service.*/background/background_audio/fullscreen/fonts/depends,不做 JSON 校验(多行/单行均可),但字段名和引号写法要规范。
2.2 main 入口:SDK 初始化 + 事件循环 + 退出约定
看 examples/hello/app.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(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 由 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命令。两种出包:
- 手工打包(从 staging 目录,
meow_sign/README §2.2的口径):mkdir -p build/os.meow.xxx # 放 app.json + meow 二进制 + 资源;app.json.package == 目录名 tar czf os.meow.xxx.mpk os.meow.xxx # .mpk 须在 tar 根(无 <pkg>/ 包裹 → 换包时注意)- 导出已装应用:设备上
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 仅显示,不比较。
三重绑定(三方各司其职):
- 签名 footer 的
package==.mpk文件名茎(meow_verify校验)——防改名。 - 签名 footer 的
package== 解包app.json.package(nyai / launcher 安装流校验)——防改 app.json。 os.meow.*前缀 + 必须验签通过(nyai / launcher 安装流)——防无签冒充系统包。
5.2 开发者自签名流程(不泄露任何密钥材料)
工具:签名用
meow_sign(MeowOS 打包工具链成员,宿主端、离线维护机运行,不落设备、设备只持公钥;随 MeowOS 主仓打包工具分发,本公开 SDK 仓不含)。私钥永不入库、永不落设备。设备侧把关的是固件/usr/bin/meow_verify(内嵌公钥数组,任一公钥通过即放行)。
# ① 首次:生成/备份你自己的密钥对(仅一次;重复生成会令旧包全部失效)
./meow_sign keygen myapp.key # myapp.key 为你的私钥(0600,离线备份);myapp.key.pub 公钥
# 私钥离线备份、永不入库/不落设备;公钥 myapp.key.pub 可提交设备厂商固化进 meow_verify 公钥数组
# ② 打包(不含签名,纯 tar.gz;app.json.package 必须 == 目录名 == .mpk 文件名茎;version_code 单调递增)
tar czf os.meow.xxx.mpk os.meow.xxx
# ③ 签名(默认 v2 含被签时间戳)
./meow_sign sign os.meow.xxx.mpk myapp.key
# ④ 产签后自测(推荐)
./meow_sign verify os.meow.xxx.mpk myapp.key.pub
# 设备侧用 meow_verify_host 复验:与固件内嵌公钥同口径(主仓打包工具提供,未随本仓分发)
# ⑤ 装到设备:把 .mpk 放卡上 → nyai install
要点:
sign的.mpk文件名茎必须等于包名(否则meow_verify二次校验拒)。- v2 块(184B,含时间戳)需 2B+ 固件;
--v1产物(180B)兼容旧 2A 固件——固件与工具链同版本发布即可避免兼容坑。 - 签名是防误装 / 防伪造包名 / 防沿途篡改,不是安全边界(见
meow_sign/README §0,诚实标注)。 - MeowOS 系统主钥不随本公开 SDK 仓分发。第三方开发者请用自己的密钥对(否则无法区分来源;用自己的私钥离线备份,公钥按需提交给设备厂商固化进
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 能力速查表
(完整签名以 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_IDXenv 注入);应用自带字库(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。字段对齐 freedesktopNotify()。 - 走 IPC
MSG_NOTIFY;10s 后自动转储到 TFMeowOS/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):cmd1=播放2=暂停3=上一首4=下一首5=seek6=播放/暂停 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。以下为最要紧的违约原因。
- 物理键禁映射返回/确认/OK/翻页。物理键是系统资源:电源=息屏/亮屏/长按菜单/8s 强关,音量=调音量,播放/上下曲=媒体控制,返回=触屏左滑(
MEOW_SWIPE_LEFT)/虚拟返回(非物理键)。绝不用on_key(POWER)→exit这类映射;on_key仅供 SDK 内部状态。界面导航一律走触摸(返回钮/左缘左滑)。 - 局部脏区刷新,别整屏重画。控件/区域变化用
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 侧再做整屏。 - 内存预算。设备内存很紧(QEMU
mem=64M参考,rootfs ≤45MB)。避免大块分配、避免大字库双驻留(大字库按档装载、卸载旧 atlas)、别把卡上大文件 mmap 当运行必需——运行期别依赖卡上资源(见 §9 卡移除)。 - 板载存储别乱写。板载
/usr/data只剩 ~10MB,是稳定框架 + KB 级设置。App 数据一律写MeowData/<pkg>/(TF,经MEOW_DATA_DIR/MEOW_CACHE_DIRenv);绝不碰 NAND 分区 / mtd //data/recovery_all(恢复出厂,红线)。 - 系统动作经专项服务。App/组件不直连
sys_do_reboot、不写 NAND/mtd5;关机/重启/升级/回 HiBy 一律经meow_svc_power(IPC)。App 一般无需做,但别绕过。 - 风控式网络请求节制。避免高频轮询/无限重试/后台自灌;尊重断网/弱网(优雅降级而非死循环/崩溃);与系统 netmgrd 管控共存(域名级,非内核 per-app)。
- 别 destroy 别占用播放器/声卡。不要
killall hiby_player(触发看门狗整机重启);不要 fork 长驻后台进程(退出要「真正退出」,否则 SIGCONT 后仍占屏抢输入)。 app.json别缺失version_code(拒装);别用os.meow.*前缀(除非你是系统签名 App)。
9. FAQ / 常见坑(真踩过的)
- 脏区透传:局部刷新抹掉上层 chrome。症状:拖动/点某个控件后,上层 FAB/标题栏被抹掉。根因:App 侧旧的「清底 + 只画单个控件」刷新。修法:改走 SDK
meow_ui_invalidate(z 栈重画下层+上层),非控件直绘注册 CUSTOM(meow_draw_custom_register),否则清底后漏画。meow_ui_paint_full()整帧按 z 重画兜底。 - IME 阻塞不喂 on_tick → 进度条/息屏卡死。阻塞
meow_ui_text_input/meow_textfield_activate的内层轮询必须喂meow_ui_activity_ping(SDK 已做);键盘区用背景快照/恢复防残影;回显条是实时输入回显非候选。 - 字体声明丢失 → 降级小字库。
app.json "fonts":"full"丢了 / 大写不符 / 该档没生成 → 按common装载,生僻字缺字(meow_font_has_glyph预判,回退或提示)。字体三件套(font.png+font.idx+font.json)缺一件 → 该套不进列表。 - 热更新
.new+mv/ 覆盖运行中程序的坑。程序主体在卡上、运行中被覆盖/替换 → 访问旧代码页可能 SIGBUS。修法:升级用nyai install(同包名 = 更新:旧目录备份.bak_<pid>→ 重启后替换 → 数据保留 → 成功自清.bak);别自己 rm/mv 运行中的可执行文件。导出时*.pre_*(热更备份)、*.bak_*会被过滤。 - 开机早于卡挂载 → amsd 注册表扫空。
am start/list报not registered(agent-250)。修法:am rescan手动触发,或等 3s 快重试 / 30s 合并重扫自愈。 SIGUSR1误当返回键。M2.89 起看门狗只对「裸沉浸」App(非framework=1)发SIGUSR1;SDK 框架 App 收到会被 SDK 的sigusr1_guard护栏记日志+吞掉。别为「返回」装SIGUSR1handler;SDK 框架 App 返回走on_swipe。- goodix 幽灵触摸。真机启动触摸尾部会发一段幽灵触摸(启动后 1.2–2.25s,见
APPS.md §3.5)。框架 App 走on_tap/on_swipe语义事件,launcher/SDK 已做 drain + 冷却;裸自绘 App 要自己加启动冷却(如COOLDOWN_MS),否则会把幽灵当下按立即退出。 - 音频:audiod 不在 / 被抢占。
meow_audio_open返回负值(MEOW_ERR_NOENT)→ audiod 不在,别隐式直开(MEOW_AUDIO_DIRECT=1仅调试)。被抢占后write返回负值 → 重新open。长流卡死(~12s 停摆)是早期驱动/ring 问题,已由 audiod 加固(period=1024×4+ 长流护栏)修掉;App 侧write阻塞背压,别一 flood 灌。 - 物理键映射 exit。旧 hello 曾
on_key(KEY_POWER)→request_exit(),违反红线——已要求删除。规范:电力键=息屏/亮屏,播放键=媒体控制,返回=左缘左滑。你的 App 别重蹈。 meow_ui.aABI 变化。改 SDK(meow_ui_theme/meow_widget结构)即 ABI 变化 → 必须同步重编 App。本公开 SDK 仓把共享协议头meow_ipc.h、meow_font.h、meow_notif.h与meow_font.c一并在sdk/内 vendored(不依赖主仓meow_launcherd/),build_sdk.sh已是自包含独立库;运行时 App 只链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>/,卡移除/无网/断流降级不崩。