447 lines
42 KiB
Markdown
447 lines
42 KiB
Markdown
# R3 Pro II 第三方应用加载器设计文档(APPS.md)
|
||
|
||
> 状态:**v2 定稿(Plan B:零 patch 看门狗启动器)**,2026-08-28
|
||
> 对应实现:`patch_v2/meow_launcherd/`(启动器守护进程)、`patch_v2/meow_watchdog/`(应用看门狗)、`patch_v2/hello_app/`(示例应用)
|
||
> 前置知识:ANALYSIS.md 第 9/11/12/16/17/18 节
|
||
> 变更记录:v1(2026-08-27)为 hiby_player 二进制 patch 路线(Qobuz 入口触发),cave 代码在真机不稳定,
|
||
> 已整体废弃;v2 改为零 patch 的独立守护进程 + 热键触发。
|
||
|
||
---
|
||
|
||
## 1. 目标与边界
|
||
|
||
### 1.1 目标
|
||
|
||
1. 硬件热键(**按住电源 + 单击音量减**,默认;可配 meow.conf `hotkey=power_voldown|seq`)→ meow_launcherd 打开应用列表(fb0 直绘,列出 TF 卡 `/usr/data/mnt/sd_0/MeowAPPs/` 下的应用)
|
||
2. 触摸/按键导航选择某个应用 → 守护进程 **SIGSTOP 冻结 hiby_player**(让位),fork/execve/waitpid 启动该应用的静态 MIPS 二进制
|
||
3. 应用进程退出 → **SIGCONT 恢复 hiby_player**(自动回到原界面)
|
||
4. 应用运行期间 meow_watchdog 提供虚拟返回键/沉浸手势(SIGTERM→1s→SIGKILL 兜底)
|
||
|
||
### 1.2 边界(v2 刻意不做)
|
||
|
||
- 不做应用沙箱/权限模型(应用以 root 运行,与 hiby_player 同权)
|
||
- 不做应用安装/卸载 UI(仅支持手动往 TF 卡放目录)
|
||
- 不做动态链接应用(只支持**静态** MIPS32r2 ELF)
|
||
- 不做应用内音频接入(见 §5,未来经 socket 通道)
|
||
- 不做滚动列表(应用 ≤ 8 个,单屏显示)
|
||
- 菜单内虚拟返回键不做(菜单只有「退出」语义;应用运行期的返回由看门狗提供)
|
||
|
||
### 1.3 安全红线
|
||
|
||
- 只允许触碰:TF 卡(`/usr/data/mnt/sd_0`)、`/usr/data`、bind-mount 覆盖点
|
||
- **绝不写 NAND 分区**;调试全程 bind-mount/直接替换可写卷文件,重启即回退,变砖风险为零
|
||
- hiby_player.sh 看门狗:`hiby_player 退出 → sleep 1 → reboot`。**SIGSTOP 不触发**(进程未退出);**严禁 killall hiby_player**,须先杀 hiby_player.sh 父 shell 再手动拉起
|
||
- **若 meow_launcherd 意外死亡(SIGKILL/崩溃)且播放器被冻结**:手动恢复 `kill -CONT $(pidof hiby_player)`(守护进程自身 SIGTERM 路径会自动 SIGCONT)
|
||
|
||
---
|
||
|
||
## 2. TF 卡目录规范(MeowAPPs)
|
||
|
||
挂载点:`/usr/data/mnt/sd_0`(TF 卡,exfat;hiby_player 内部虚拟盘符 `a:\` 映射到该目录)。
|
||
|
||
```
|
||
/usr/data/mnt/sd_0/ # TF 卡挂载点
|
||
└── MeowAPPs/
|
||
├── <package>/ # 目录名 = app.json 的 package(权威 app_id)
|
||
│ ├── app.json # 应用描述(必填;package 字段为权威 app_id)
|
||
│ ├── <binary> # 静态 MIPS32r2 ELF(exec 指向)
|
||
│ └── icon.png # 预留:列表图标(v2 未用)
|
||
└── ...
|
||
```
|
||
`<package>` 即 `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/os.meow.hello/hello",
|
||
"package": "os.meow.hello",
|
||
"version": "0.1",
|
||
"author": "whoever"
|
||
}
|
||
```
|
||
|
||
| 字段 | 必填 | 说明 |
|
||
|---|---|---|
|
||
| `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 不解析 |
|
||
| `fullscreen` | — | app 级全屏:`1`=进入全屏(状态栏**收合至提示线** + **保留左缘返回/顶部提示线/下滑通知中心**,见 MEOWOS_SPEC_REF §5.1);activity 级用它 `route.<id>.fullscreen`。**app.json `fullscreen` 字段此前未解析/已定未做 → 本次实现**(launcher 启动/切 activity 据声明预设 SDK 全屏,env `MEOW_FULLSCREEN`)[源:用户 2026-08-30] |
|
||
| `fonts` | — | 字库档位声明:`"full"` / `"gbk"` / `"common"`(app 需大字库时可声明 `"full"`,默认小字库 `common`≈4k;launcher 据此为该 app 加载大字库 `gbk`≈20k)。**注意**:与 `"fonts":["<handle>"]`(应用自带字库句柄数组)**同名二义**,见 MEOWOS_SPEC_REF §7 A-14 [源:用户 2026-08-30] |
|
||
|
||
- 目录扫描深度 1 级:只枚举 `MeowAPPs/` 下的直接子目录
|
||
- 应用计数上限 **8**(超出忽略;单屏 8 行)
|
||
- app.json 解析器极简(launcher 内实现):仅匹配 `"name":`、`"exec":`、`"package":` 后的引号字符串(及扩展 icon/framework/immersive/route./`awake.<id>`(keep_awake)/service.),**`fullscreen`(本次实现)与 `fonts`(档位串) 已解析**,不做 JSON 校验(多行/单行均可)[源:审计 2026-08-30][源:用户 2026-08-30]
|
||
- 列表刷新时机:每次菜单打开时重新扫描(TF 卡热插拔后立即可见)
|
||
|
||
### 2.2 边界情况行为(launcher 实现,全部真机验证)
|
||
|
||
| 场景 | 行为 |
|
||
|---|---|
|
||
| TF 卡未挂载/未插入 | 菜单提示「NO STORAGE CARD / INSERT TF CARD」(state=1,/proc/mounts 无 sd_0) |
|
||
| 卡挂载但无 MeowAPPs 目录 | 提示「NO APPS FOUND」+ 底部示例行 `MeowAPPs/<id>/app.json`(state=2) |
|
||
| MeowAPPs 为空目录 | 同上(扫描计数 0 → state=2) |
|
||
| app.json 损坏/缺 name 或 exec 字段 | 跳过该应用,列表其余项正常 |
|
||
| 二进制不存在/不可执行 | 行尾标 `[MISSING]`;点击/确认该行提示 toast「CANNOT START: <name>」(access X_OK 检查,不 fork;`launch_app` 返回 -1) |
|
||
| 二进制存在但 execve 失败 | v6.8 起 `launch_app` 用 CLOEXEC 管道检测 execve 失败(子进程写 errno),返回 -1 → toast;**不再把 127 误当「应用跑完」**。toast「CANNOT START」**仅在返回 -1(真启动失败)时显示**,应用干净退出(返回 0)不显示(Bug 3,旧版无条件显示) |
|
||
| 扫描读取出错(opendir/fopen/fgets 失败) | 各自跳过;最终无应用时按 state=2 提示 |
|
||
|
||
状态机:state 0=正常 / 1=未检测到存储卡 / 2=无可用应用。
|
||
|
||
---
|
||
|
||
## 3. 应用生命周期契约
|
||
|
||
### 3.1 启动流程(meow_launcherd 侧)
|
||
|
||
```
|
||
热键(按住电源 + 单击音量减;电源按下后 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 直绘应用列表
|
||
→ 菜单循环(10ms):
|
||
· 触摸(event1 事件读)点击行 → 启动;点击「BACK TO PLAYER」行 → 退出菜单
|
||
· 按键(EVIOCGKEY 状态轮询):音量减/加=移动光标,播放键=启动
|
||
· **电源 = 纯息屏/亮屏(launcher 不干预亮度/面板,不退出菜单)**;退出菜单仅「BACK TO PLAYER」触摸行(或 launcher SIGTERM)
|
||
· 应用启动:fork → execve(exec[i])(失败 exit 127);同时 fork meow_watchdog <pid>
|
||
· waitpid(app) 阻塞;应用内强杀 = 音量减×3,退出回菜单
|
||
→ 应用退出 → 菜单退出 → SIGCONT hiby_player → 播放器恢复重绘
|
||
```
|
||
|
||
### 3.2 让位机制(守护进程方案,2026-08-28 真机验证)
|
||
|
||
**结论:SIGSTOP 整进程冻结 hiby_player(UI+音频+全部线程),菜单期间播放器完全不动作;SIGCONT 后原样恢复。hiby_player.sh 看门狗只盯进程退出,SIGSTOP 不触发 reboot。**
|
||
|
||
依据(真机实证,见 ANALYSIS.md §18):
|
||
|
||
1. **SIGSTOP 不触发看门狗**:进程存活(状态 T),`hiby_player.sh` 的 `sleep 1; reboot` 不执行。
|
||
2. **恢复确定可靠**:菜单每条退出路径(含 SIGTERM 处理、错误路径)都会 SIGCONT;`meow_launcherd` 自身被 SIGTERM 时也先 SIGCONT 再退出。
|
||
3. **崩溃安全**:应用崩溃/被杀 → waitpid 返回 → 自动 SIGCONT 恢复。若守护进程本身被 SIGKILL,播放器会冻结——手动 `kill -CONT $(pidof hiby_player)` 恢复(详见 §1.3)。
|
||
4. **菜单期间播放器完全不刷新**(SIGSTOP),fb0 归守护进程独占绘制,无抢绘。
|
||
5. **音频**:SIGSTOP 连音频线程一起冻结(应用运行期无声音;如需出声见 §5 未来方案——v2 的 SIGSTOP 方案下播放器无法代播,需改 waitpid-in-callback 让位或音频线程解冻,留待 v3)。
|
||
6. **应用退出 → SIGCONT → 播放器首帧整页重绘**,无需应用清屏。
|
||
|
||
### 3.3 应用该做什么 / 不该做什么
|
||
|
||
**应用必须:**
|
||
|
||
| # | 规则 | 说明 |
|
||
|---|---|---|
|
||
| 1 | 干净退出 | 任意退出码均可;守护进程 waitpid 立即返回,SIGCONT 恢复。`exit(0/1)` 即可,不要 `abort()` 留下僵尸 |
|
||
| 2 | 独占屏幕自绘 | 自行 `open("/dev/fb0")` + `mmap` 后绘制;设备帧缓冲 480×720(分辨率/色深运行时用 FBIOGET_VSCREENINFO/FSCREENINFO 探测,RGB565 或 RGBA8888 自适应)。**注意:本设备 ioctl 号 VSCREENINFO=0x4600、FSCREENINFO=0x4602(勿颠倒,见 §4.3)** |
|
||
| 3 | 自己读输入 | `open("/dev/input/event*")` 逐个打开,读到任意 key/touch 事件即处理。按任意键退出是推荐约定(物理事件对所有读者多播,播放器已冻结,无需担心抢事件) |
|
||
| 4 | 及时退出 | 建议空闲超时(如 30s 无操作)自动退出,避免长期占屏(hello 60s 看门狗) |
|
||
|
||
**应用不该做:**
|
||
|
||
| # | 规则 | 原因 |
|
||
|---|---|---|
|
||
| 1 | 不要尝试"恢复" fb | SIGCONT 后播放器首帧即整页重绘,无需你清屏。你清屏反而闪一下 |
|
||
| 2 | 不要 kill hiby_player | 触发 hiby_player.sh 看门狗 → 整机重启 |
|
||
| 3 | 不要继承占用声卡 | SIGSTOP 期间声卡仍被冻结的播放器持有,直接打开会失败/挂起(v2 无音频) |
|
||
| 4 | 不要 fork 出长期后台进程 | 你的退出应"真正退出",否则 SIGCONT 后后台进程仍占屏/抢输入 |
|
||
| 5 | 不要碰 NAND 分区/固件 | 只写 TF 卡与 /usr/data |
|
||
|
||
### 3.4 退出与恢复
|
||
|
||
- 应用退出 → waitpid 返回 → 菜单关闭 → SIGCONT 播放器 → 播放器恢复原界面
|
||
- 菜单内「BACK TO PLAYER」行 → 直接 SIGCONT(不启动任何应用);**电源键在菜单内不再退出菜单(v6.5 起电源=纯息屏/亮屏,仅 sys_server/内核处理背光)**
|
||
- 整机重启 → 一切回退原版(守护进程由 S90 随开机自启)
|
||
|
||
### 3.4.1 物理按键语义 —— 强制规范(系统统一管理,APP 禁映射)
|
||
|
||
> ⚠️ **红线**:物理按键是**系统资源**,语义由 MeowOS(launcher/watchdog/内核)**统一管理**。**任何非系统组件(MeowAPP)不得将物理按键映射成任何行为**,包括但不限于:电源键→OK/确认/exit/翻页,音量键→确认/选择,播放键→确认/返回。
|
||
|
||
**为什么**:用户在任何界面都必须能靠物理键完成系统级操作——**电源键=息屏/亮屏**(MeowOS 界面也必须能息屏)。若 APP 抢走电源键当"OK/exit",用户一按电源键,屏没息反而退出了 APP,**无法息屏**;音量键/播放键同理。物理键必须留给系统。
|
||
|
||
**约定(MeowOS 界面 = launcher 主菜单/选单/电源菜单,是唯一允许映射物理键的地方)**:
|
||
|
||
> ⚠️ **适用范围**:**除了电源键菜单(`show_power_menu`,允许"电源=确认、音量=导航")以外,所有按键信号由 MeowOS 全局接管**——**任何界面(含 MeowOS 自身的设置页 / 应用列表 / 选单、以及 MeowAPP)都不得私自定义按键行为**。物理键的语义只有一份:电源=息屏/亮屏/长按菜单/8s强关,音量=调系统音量,播放/上下曲=媒体控制(未落地前「抓住」)。任何界面(如系统设置页)**不得**把电源键/播放键映射成"返回/确认"这类自定义行为;界面导航一律走触摸(返回按钮/左缘左滑),按键语义统一由系统判。
|
||
|
||
| 键 | 系统语义(APP 收到的是语义事件,不是裸 keycode) |
|
||
|---|---|
|
||
| 电源 | 息屏/亮屏、长按弹电源菜单;**APP 内=纯息屏/亮屏**(绝不 exit/OK) |
|
||
| 音量 +/− | 调系统音量;**APP 内=调音量**(绝不选中/确认/翻页) |
|
||
| 播放/暂停 | 全局媒体控制(播放/暂停当前媒体会话);**APP 内=媒体控制**(绝不确认/打开) |
|
||
| 返回 | 触屏左滑/虚拟返回键 → SDK 语义 `on_swipe(LEFT)` / watchdog 信号,**非物理键** |
|
||
|
||
**MeowAPP 侧**:
|
||
- APP **不读 `/dev/input/event*`**、**不 open/mmap fb0**、**不处理任何物理 keycode**——一切走 SDK 的语义回调(`on_tap`/`on_swipe`/`on_tick`;`on_key` **仅用于 SDk 内部状态,不含电源/音量/播放的确认/exit 语义**)。
|
||
- APP 想"返回/退出"→ 用 `meow_ui_request_exit()` 或左滑,**绝不监听物理键映射 exit**。
|
||
- SDK 的 `on_key` 回调:**禁止 APP 用它实现"电源=exit"这类映射**。SDK 内部消化物理键的系统语义(息屏/音量/媒体),**只把触摸返回/手势等语义事件给 APP**。
|
||
|
||
**落地**:hello.c 当前 `on_key(KEY_POWER)→request_exit()` 违反此规范,需删除(改由 SDK/系统处理息屏);APP 退出走左滑/`request_exit`/系统返回。**MeowOS 系统设置页(run_settings_page)曾私自定义"电源/播放键=返回",M2.45 已按本规范改为全局语义(电源=息屏/亮屏/长按菜单/8s强关,播放=抓住),退出仅靠触摸(底部返回/左缘左滑)**——任何 MeowOS 界面新增时须遵守本规范,不得私自定义按键。
|
||
|
||
### 3.5 虚拟返回键 / 沉浸模式 / 看门狗
|
||
|
||
应用启动时,launcher **同时 fork 看门狗进程** `/usr/data/meow_watchdog <app_pid>`(固件 S99ssh 首启从 `/usr/bin/meow_watchdog` 拷到可写卷;launcher 先试 /usr/data 再试 /usr/bin):
|
||
|
||
| 特性 | 行为 |
|
||
|---|---|
|
||
| 虚拟返回键 | 看门狗独立 mmap `/dev/fb0`(RGB565 自适应),每 100ms 重画左上角 48×48 返回按钮(深色底+白箭头);读 `/dev/input/event1` 触摸,按下落在按钮区 → `SIGTERM` 应用 → 1s 后 `SIGKILL`(应用卡死也能退出)。**v6.4 防抖**(针对"启动误杀"):`last_x/y=-1` 起始(无坐标的按下丢弃)、BTN_TOUCH 按实际值匹配边沿(消除失同步翻转)、仅短按 ≤600ms 有效、启动后先 drain 一次、**冷却期 2.5s**(实测幽灵触摸窗口 1.2–2.25s;此窗内返回键不响应,改 `COOLDOWN_MS` 可调) |
|
||
| 沉浸模式 | 应用写 `/tmp/meowapp_state` 内容为 `1` → 看门狗不画按钮、只识别「左缘 24px 内起点向右滑 >50px」手势 → 发 `SIGUSR1`;写 `0` 或删除 → 恢复虚拟键 |
|
||
| 看门狗自退出 | 应用进程消失(`kill(pid,0)` ESRCH)或 launcher SIGTERM → 退出 |
|
||
| 强制退出兜底 | 应用运行期(launcher 处于 waitpid):**音量减连按 3 次**(间隔 < `hotkey_seq_gap`,与主序列同阈值)→ SIGTERM → 0.5s → SIGKILL → 恢复。⚠️ 响应音量键的应用(如示例 hello)会先自行退出(`exit:key`),强杀序列是**不响应音量键/卡死应用**的兜底 |
|
||
| 长按电源 | 硬件级强制关机(设备固有),最终兜底 |
|
||
| 测试模式 | `meow_watchdog <pid> --sim-tap N` / `--sim-swipe N`,无触摸设备也可验证 |
|
||
|
||
**应用协议约定**:
|
||
- 默认(非沉浸)由看门狗画返回键,应用**不必**处理返回手势
|
||
- **⚠️ 返回键信号 = `SIGTERM`**(看门狗 `handle_touch` 按钮命中 → `kill(app_pid, SIGTERM)`→1s→`SIGKILL`);`SIGUSR1` **仅**是沉浸左缘滑动手势,不是返回键。com 与 SIGTERM 默认终止会**不画 BYE** 直接死,故示例 hello 安装 `SIGTERM` 处理器:画 BYE + `exit(0)`,使返回键呈现确定性的「bye → 立即退出」(v6.8,Bug 2)。
|
||
- **⚠️ 应用启动后须防 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 写 `apps/<app_id>/logs/<ts>_<pid>.log`;[源:审计 2026-08-30]);启动时先 drain 输入队列、只响应"按下边沿"退出(释放事件/轨迹事件忽略)
|
||
|
||
### 3.6 应用运行期卡移除(M2.11 最佳努力)
|
||
|
||
**场景**:前台 MeowAPP 的二进制/资源位于 TF 卡(`exec` 指向 `/usr/data/mnt/sd_0/MeowAPPs/…/`)。运行中卡被拔出 → 应用访问卡上代码页/资源可能触发 **SIGBUS** 崩溃。
|
||
|
||
**launcher 侧(已实现,`launch_app` waitpid 循环内)**:
|
||
- 启动应用时记录 `app_on_card = (exec 路径在 MEOWAPPS 下)`;若是,则进入「运行期卡移除监测」。
|
||
- waitpid 主循环每 ~100ms 采样 `/dev/mmcblk0` 存在性。一旦发现卡设备节点消失(被拔)且当前应用在卡上 → **主动 SIGTERM 应用**(干净退出,避免等 SIGBUS 崩),`usleep(300ms)` 内未退则 SIGKILL 兜底,然后走正常「应用退出→回菜单」路径。菜单/日志提示「卡已移除,应用已结束」。
|
||
- 不做:把应用二进制拷到内存/板载 staging 再 exec(该方案权衡大小/内存,本阶段先记录不做)。
|
||
|
||
**应用侧规范(给第三方 MeowAPP 的约定)**:
|
||
1. **别依赖卡上资源当运行必需**:字体/封面/贴图等用 RAM 缓存到内存再渲染;运行期避免持续从卡 mmap/读大文件。
|
||
2. **读错误容错**:`open/read/mmap` 返回失败(卡没了)时别 `abort()`/崩溃,优雅退出。
|
||
3. **卡掉能干净退出**:收到 `SIGTERM`(launcher 因卡移除而发,或用户虚拟返回键)一律画 `bye` + `exit(0)`;`mmap` 到卡文件失败时视为可恢复/可退出,不 SIGBUS 宿命。
|
||
|
||
---
|
||
|
||
## 4. meow_launcherd 实现细节
|
||
|
||
### 4.1 架构:被动式守护进程(零 patch、零 grab、零注入)
|
||
|
||
**关键内核实证(2026-08-28,见 ANALYSIS.md §18.2)**:
|
||
|
||
1. **hiby_player 不持有 EVIOCGRAB**:播放器运行时对 event0/event2/event1 的 EVIOCGRAB 探测全部成功(非 EBUSY)——此前"播放器独占按键"的观察不成立或仅为瞬态。
|
||
2. **注入事件永不投递**:`write()` 到任何 event 节点都成功、且更新设备键状态(EVIOCGKEY 可见),但**任何 evdev 读者(含 grabber 自身)都收不到**——厂商输入子系统修改了投递路径。⇒「grab + write 回写转发」方案在真机不可行(原方案 B 的前半段作废)。
|
||
3. **物理事件多播**:播放器自身两个 fd 同时读 event1(触摸正常),证明物理事件对所有读者多播(标准语义)。
|
||
|
||
因此 launcher 采用:
|
||
- **按键 = EVIOCGKEY 状态轮询**(10ms,只读 ioctl,与 grab/投递无关,播放器抓不抓都不影响)——音量/播放(114/115/164/163, event2)、电源/上一曲(116/165, event0)。
|
||
- **触摸 = event1 事件读**(未被抓、物理多播)——菜单点击。
|
||
- **不 grab、不注入、不改播放器**——"按键无感"由构造保证(播放器按键路径完全没被碰)。
|
||
|
||
### 4.2 热键与菜单键
|
||
|
||
| 键 | 设备 | code | 用途 |
|
||
|---|---|---|---|
|
||
| 音量加 | event2 | 115 | 菜单光标上 |
|
||
| 音量减 | event2 | 114 | **热键第二击(电源按住后单击)** / 菜单光标下 / **应用内强杀序列键** |
|
||
| 播放/暂停 | event2 | 164 | 菜单确认 |
|
||
| 电源 | event0 | 116 | **热键第一击(按住)** / 菜单/应用内=纯息屏亮屏(不再退出菜单) |
|
||
| 下一曲 | event2 | 163 | (未用) |
|
||
| 上一曲 | event0 | 165 | (弃用——GPIO PC28 硬件链断,§18.12) |
|
||
|
||
**热键判定(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 # 默认:电源按住 + 单击音量减(60-2500ms 窗口;v6.7 起下限 300ms→60ms)
|
||
hotkey=seq # 历史序列热键(单键连按 N 次,不推荐)
|
||
```
|
||
(`hotkey_seq=`/`hotkey_seq_gap=` 仅在 `hotkey=seq` 时生效。)
|
||
⚠️ **v6.1 修复**:早期版本 hotkey_seq 解析在行尾 `\n` 处死循环(外层 while 不消费 EOL),配了该行 daemon 即卡死——已修复并在真机复测。该解析器同样用于 `hotkey=` 行(无此问题,逐行 `strncmp`)。
|
||
|
||
**音量副作用与恢复**:v6.5 主热键(电源+音量减)的副作用是**音量 -1**(单击音量减,播放器收到,直接作为音量调节——**无恢复需求,接受**)。序列热键(`hotkey=seq`,默认已不使用)的每次按下仍会音量+1×N,launcher 在第 1 次按下快照 CS43198 音量、序列完成触发时写回(**⚠️ 实测该 control 写无效,恢复为尽力而为**)——因已退役,不再展开。
|
||
|
||
**热键演变记录**:v1 电源+音量减 → v2 电源+上一曲 → v3 双音量同按(物理不可能:event2 单 ADC 通道)→ v4 上一曲+音量加(上一曲 GPIO PC28 硬件链断,§18.12)→ v6 单键序列(volup×3,单键物理必可注册)→ **v6.5 电源+单击音量减(定案)**:回到"跨设备组合键"能力(电源在 event0、音量减在 event2,两键不同 ADC/GPIO,物理可同按),利用电源按下必然触发播放器息屏的既有行为,以"按住电源 + 窗口内单击音量减"作热键,同时**移除菜单/应用内电源=逐级返回**(电源在菜单/应用内改为纯息屏/亮屏,见 §3.1/§3.4)。
|
||
|
||
### 4.3 fb0 绘图
|
||
|
||
- 480×720 RGB565(运行时 ioctl 探测,RGB565/RGBA8888 自适应)
|
||
- **⚠️ 本设备 fb ioctl 号(实测)**:`FBIOGET_VSCREENINFO=0x4600`、`FBIOGET_FSCREENINFO=0x4602`(旧式无 size 编码号)。hello.c/meow_watchdog.c 曾沿用标准头文件常量写反(0x4602/0x4600),导致 ioctl 返回垃圾、mmap EINVAL——已全部修正并用探针实测通过。
|
||
- **⚠️ 双缓冲(v6.6)**:本设备 fb0 为**双缓冲**(实测 `yres_virtual=1440=2×720`、`smem_len=2 帧`、`ypanstep=1`),**面板显示哪个缓冲由 fb_var.yoffset 决定,`FBIOPAN_DISPLAY=0x4606` 可翻页**。launcher 与 hello 等 app 都只画 mmap 偏移 0(buffer0),若 yoffset 停在 buffer1(播放器电源/息屏重绘后),菜单画在 buffer0 而面板显示 buffer1 → 菜单不可见、屏幕冻结(v6.5 bug)。v6.6 起 `fb_open` **mmap 整个虚拟帧缓冲**(含两帧),菜单打开时 `fb_backup_player` 快照当前 yoffset 处缓冲 → `fb_norm_buf0`(FBIOPAN_DISPLAY 把 yoffset 归 0,菜单/app 共同绘制目标)→ 回退时 `fb_restore_player` 把备份拷回原 yoffset 缓冲并 pan 回原 yoffset(播放器事件驱动不主动重绘,必须恢复 yoffset)。**面板 blank/亮度状态仍归播放器**(§18.11 教训),launcher 只在 fb 缓冲/翻页层面介入。
|
||
- **重绘纪律(v6.9,修复列表 ~3fps 频闪)**:菜单只在**内容真正变化**时整屏重绘——光标移动(输入)、应用退出/进入(重写了 buffer0)、toast 起止、未来通知徽标变化时置 `dirty`;**绝无周期整屏重绘**。v6.8 的「500ms 安全重绘」会在 500ms 定时把**整个菜单**重写进正在显示的 buffer0(就地改写产生撕裂/脉冲,且 `draw_statusbar` 每次调用都 `amixer` popen 拖慢主循环),正是频闪根因。若真需周期刷新(时钟每秒),用**只刷时钟数字**的轻量路径 `draw_clock()`(不弹 amixer、不碰应用列表),其余状态(电量/WIFI/BT/音量)仅在事件(dirty)时随整屏刷新。
|
||
- 布局:标题 MEOWAPPS / 高亮行(光标)/ 状态提示 / `BACK TO PLAYER` 行 / 底部按键提示 / toast 区(y≈584-617);**右下角开发版本号**(v6.9)——`MeowOS v<MEOWOS_VERSION> dev`,右对齐画在底部行 y=700,与 `BACK TO PLAYER`(y≤688)和左侧按键提示(至 x≈254)均不重叠。
|
||
|
||
### 4.4 命令行(含测试钩子)
|
||
|
||
```
|
||
meow_launcherd # 守护模式
|
||
meow_launcherd --scan # 打印应用列表与状态,退出
|
||
meow_launcherd --dumpkeys N # 轮询 N 毫秒,记录所有按键状态边沿(真机抓 key code 用)
|
||
meow_launcherd --sim-hotkey N # N 毫秒后模拟热键(默认=电源按住→N+800ms 单击音量减;hotkey=seq 时=音量加 ×3 快速连按,每次 300ms 间隔)
|
||
meow_launcherd --sim-tap N # 菜单打开 N 毫秒后模拟点击第 0 行
|
||
meow_launcherd --sim-exit N # 菜单打开 N 毫秒后模拟退出
|
||
```
|
||
|
||
测试钩子用于无物理输入时的全链路自动化验证(注入事件不投递、无法远程按物理键)。
|
||
|
||
### 4.5 统一日志系统(MeowAPPs 组件共用)
|
||
|
||
- **默认位置**:TF 卡 `/usr/data/mnt/sd_0/MeowOS/logs/meow_<bootts>.log`(如 `meow_20260828_020123.log`;`bootts` = 当前墙钟 − /proc/uptime,同一 boot 会话内多次启动共用同一文件)。**系统日志**(launcher/watchdog 等 MeowOS 组件)写此文件;**应用日志**另按 `apps/<app_id>/logs/<ts>_<pid>.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` → 应用:日志目录(每 App `MeowOS/logs/apps/<app_id>/logs/`,应用写 `<ts>_<pid>.log`;[源:审计 2026-08-30])
|
||
- 内容:时间戳 + 全部按键边沿/菜单/应用事件(真机按键证据)
|
||
- PID 文件:`/usr/data/meow_launcherd.pid`;播放器发现:扫描 `/proc/N/exe` == `/usr/bin/hiby_player`
|
||
|
||
### 4.6 UI 规范(应用列表与 MeowAPPs 界面)
|
||
|
||
- **纯黑背景为默认**(`menu_bg = 0x000000`)
|
||
- **可配置**:TF 卡 `MeowAPPs/meow.conf` 支持 `bg_color=RRGGBB`(如 `bg_color=10284f`),菜单打开时重新加载
|
||
- **40px 状态栏**(v6.8 由 20px 翻倍;v6.9 时钟加秒):第 1 行 = 左侧时钟(**HH:MM:SS,每秒刷新**,v6.9;用 `draw_clock()` 只刷时钟数字,不弹 amixer、不碰应用列表——见 §4.3 重绘纪律)、右侧 `电量% WIFI BT`(电量读 `/sys/class/power_supply/battery/capacity`、WIFI 读 `/sys/class/net/wlan0/operstate`、蓝牙读 `/sys/class/bluetooth/hci0` 存在性);第 2 行 = 左侧 `VOL nn%`(音量读 CS43198 / amixer numid=1,值 0-255);下方 1px 分隔线(移至 y=40)。⚠️ 音量 amixer control 实测为**死镜像**(§18.13:播放器走 sa_hgl_dma 硬件直通,ALSA control 读回恒 0),故展示值可能恒为 0%,为已知设备限制。
|
||
- **开发版本号**(v6.9):右下角绘制 `MeowOS v<ver> dev`(字体无小写,实际渲染为大写 `MEOWOS V0.6.9 DEV`)。单一来源 = `meow_launcherd.c` 的 `MEOWOS_VERSION` 宏;`build_fw_apps.sh` 打包时用 `sed` 从该宏抓取写入 rootfs 的 `etc/meowos_version`,屏显与固件文件**同源,不会两处不一致**(版本号改一处即可)。
|
||
- 示例应用 hello 同样黑底 + 40px 状态栏(应用内左上角 48×48 返回键由 watchdog 绘制,允许盖住状态栏左侧)
|
||
|
||
### 4.7 应用退出后的菜单触摸守卫 + 启动失败检测(v6.8,Bug 2/3/4)
|
||
|
||
**Bug 2/4 根因(应用退出后菜单重放残留触摸)**:应用运行期间 launcher 的菜单触摸 fd(`touch_fd`)**不轮询**(被 `launch_app` 的 waitpid 阻塞),期间收集的触摸事件(goodix 幽灵触摸 + 真正退出应用的那次点击/按键)**全部排队**。应用退出回到菜单后,菜单下一轮 poll 会把这些残留事件**重放成新的行点击**——落在应用行则**重开应用**(症状:bye 后回 hello 主界面、需再点一次,Bug 2);落在「BACK TO PLAYER」行则**关菜单 → SIGCONT 播放器**(整屏闪回播放器,Bug 4)。
|
||
|
||
**修复(`run_menu` + `menu_after_app_exit()`)**:
|
||
1. **drain**:应用退出后立即读完并丢弃 `touch_fd` 的全部残留事件。
|
||
2. **冷却**:应用**真正跑过**(`launch_app` 返回 0)时置 `menu_touch_cooldown_until = now + 2500ms`,冷却窗内忽略一切触摸按下边沿(与 watchdog 的同值 COOLDOWN_MS 对齐,覆盖实测幽灵窗 1.2-2.25s);启动失败(返回 -1)不置冷却,避免无谓锁菜单。
|
||
3. **菜单重绘**:v6.8 曾把 `last_redraw` 从绝对时间戳 `t` 改为相对量 `el`(使 500ms 安全重绘生效)以覆盖应用退出后的残帧(如 BYE)。**v6.9 起摒弃周期整屏重绘**(它在 500ms 定时把整个菜单重写进正在显示的 buffer0,造成列表 ~3fps 频闪):改为**仅 dirty 时整屏重绘**,应用退出/进入路径显式置 `dirty=1` 主动覆盖应用帧;时钟每秒用轻量 `draw_clock()` 只刷数字。
|
||
|
||
**Bug 3 根因与修复**:旧代码在所有 `launch_app` 调用点**无条件**置「CANNOT START: <name>」toast,即使应用**正常跑完**。修复:仅当 `launch_app` 返回 **-1(真启动失败)** 才置 toast;应用干净退出(返回 0)不显示。`launch_app` 用 **CLOEXEC 管道**检测 execve 失败(子进程写 errno → 父进程读 >0 判失败并返回 -1),不再把子进程 `_exit(127)` 误当「应用跑完」。
|
||
|
||
**副作用(已知取舍)**:应用退出后 2.5s 内菜单触摸不响应(返回键误触/退出后紧急再操作需等待;与 watchdog 相同值,实测幽灵窗 1.2-2.25s)。物理触摸的幽灵无法注入复现(§18.2 注入不投递),以上防抖逻辑在注入/sim 链路已验证,物理最终确认需用户。
|
||
|
||
---
|
||
|
||
## 5. 音频通路(未来应用的出声方案)
|
||
|
||
参考 ANALYSIS.md 第 11 节结论:
|
||
|
||
- 声卡 = 内部唯一 card0(CS43198 双 DAC),**无混音**,谁打开谁独占;hiby_player 常驻持有 `hw:0,0`。
|
||
- **v2(SIGSTOP 让位)下播放器完全冻结,无法代播**。应用直接开 ALSA 会失败。v3 候选:
|
||
1. **改让位机制为"仅 UI 冻结"**(如让播放器走 waitpid-in-callback 或独立音频线程),应用经 unix socket 汇入通道(0x73d320 区,`AIRPLAY:CONNECT` 类消息 + 16-bit PCM)出声——采样率定 44.1k/16bit(shairport 路径)。
|
||
2. 应用退出后由播放器继续(现状 v2)。
|
||
- 音量/通路切换由 hiby_player 管理(硬件音量寄存器),应用只管喂 PCM。
|
||
|
||
---
|
||
|
||
## 6. 测试流程
|
||
|
||
> 设备已刷 SSH 固件:`ssh -i ssh_keys/<device_key> root@<IP>`(登录口令见私密记录,勿入库)。
|
||
> 所有设备操作遵守纪律:动播放器前数实例;重启前 `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/os.meow.hello/hello`。
|
||
2. **验证输入路径**(无物理按键时的替代证据):
|
||
- `meow_launcherd --scan` → 列出应用
|
||
- `meow_launcherd --dumpkeys 6000` + 物理按键 → 日志出现 `key VOL_DOWN/VOL_UP DOWN/UP`
|
||
- 或注入状态模拟:`evtool send /dev/input/event2 1 114 1`(仅更新键状态,无投递,供 EVIOCGKEY 观察)
|
||
3. **全链路**(自动化):`meow_launcherd --sim-hotkey 2000 --sim-tap 4000`;期间 `cat /proc/<pid>/stat` 应 T(冻结);fb0 截图验证菜单/应用画面(黑底+状态栏);注入上一曲(165)+音量加(115)按住 2s 强杀应用 → 日志 SIGCONT → 播放器回 R、实例 1、背光 101。
|
||
4. **边界 ×5**:无卡(umount sd_0 → state=1 → 重挂)、无目录(mv MeowAPPs → state=2)、空目录(state=2)、坏 app.json(跳过)、二进制缺失(`[MISSING]` + toast),各拍 fb0 截图存 `device_test/`。
|
||
5. **稳定性**:守护进程跑 ≥3 分钟,播放器 R、实例 1、无 recovery_all;SIGTERM 干净退出。
|
||
6. **固件**:`build_fw_apps.sh` → `fw_apps/r3proii_ssh_apps.upt`,回验 0 diff、rootfs ≤ 45,088,768 B。
|
||
7. **回退**:重启即回退原版(守护进程不装则无热键);设备侧删除 `/data/meow_launcherd` 等测试文件。
|
||
|
||
---
|
||
|
||
## 7. 已知风险与未决项
|
||
|
||
0. **⚠️ `/data/recovery_all` 恢复触发(设备会被出厂重置,最高优先红线)**:hiby_player 内部存在写该标志的代码路径(`0x4823d0` 无限循环 / `0x56d1c0` 消息处理器)。调试期间设备已被出厂重置两次。防护规矩:**每次重启播放器前 `rm -f /data/recovery_all`;部署后确认稳定才允许设备重启;发现该文件立即删除并告警**。Plan B 全程未触发(SIGSTOP 不崩溃播放器)。
|
||
1. **守护进程被 SIGKILL/崩溃 → 播放器冻结**:SIGTERM 路径会自动 SIGCONT,但 SIGKILL 无法拦截。恢复:`kill -CONT $(pidof hiby_player)`。v3 可加双重守护(两个进程互保)。
|
||
2. **热键副作用**:v6.5 主热键(电源+单击音量减)会触发播放器息屏流程(菜单打开用后置时序 unblank+仅灭屏拉背光,见 §3.1)、音量 **-1**(可接受);**不再碰电源键"逐级返回"**。仅当 `hotkey=seq` 时才有音量 +N(amixer 音量 control 写无效无法恢复,见 §4.2;可配 `hotkey_seq=play,play,play` 换取播放停顿)。
|
||
3. **菜单触摸依赖 event1 物理多播**:播放器不抓触摸(已实测),若未来固件抓取,菜单仍有按键导航兜底。
|
||
4. **UTF-8 应用名**:v2 仅 ASCII;中文名需实现 UTF-8→UTF-16 转换。
|
||
5. **应用挂死且虚拟返回键不可见(沉浸态)**:音量减连按 3 次强杀兜底;再不行长按电源硬件关机。
|
||
6. **SIGSTOP 冻结音频线程**:应用运行期无声(v3 方向见 §5)。
|
||
7. **开机时序**:S90 在 S92 播放器之前启动;输入设备由 S11 加载完成,launcher 启动时重试打开(30 次 × 1s)。
|
||
|
||
---
|
||
|
||
## 附录:MeowAPPs 开发规范 v2(包结构与数据目录/软件包)
|
||
|
||
### 1. 应用目录结构
|
||
|
||
```
|
||
MeowAPPs/<package>/ # 目录名 = app.json 的 package(权威 app_id)
|
||
├── app.json # 应用配置(name/exec/package/路由/图标/框架声明等)
|
||
├── meow # 程序主体(静态 MIPS 二进制;也可多文件/脚本)
|
||
└── <资源文件/图标…> # 只读程序资源
|
||
|
||
MeowData/<package>/
|
||
├── data/ # 持久应用数据(配置、播放列表、登录态…)
|
||
└── cache/ # 缓存(可清)
|
||
```
|
||
|
||
> `<package>` = `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}` 存在(否则建)。
|
||
- 启动时注入环境变量给应用:
|
||
- `MEOW_DATA_DIR=/usr/data/mnt/sd_0/MeowData/<包名>/data`
|
||
- `MEOW_CACHE_DIR=/usr/data/mnt/sd_0/MeowData/<包名>/cache`
|
||
- (与已有 `MEOW_LOG_DIR`/`MEOW_ACTIVITY`/`MEOW_CHAIN` 一致)
|
||
- **应用写数据用 `$MEOW_DATA_DIR`/$MEOW_CACHE_DIR**,无需链接;exFAT 也能跑。
|
||
- 好处:数据与程序分离;大体积数据在 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()`):
|
||
1. 校验 `.mpk`(app.json 合法、包名不冲突/是否覆盖、校验哈希);
|
||
2. 解压到 `MeowAPPs/<包名>/`(若已有同名,确认升级/覆盖);
|
||
3. 触发**首次启动数据初始化**(建 `MeowData/<包名>/{data,cache}`);
|
||
4. 登入应用列表。
|
||
- **后续**:Web 后台(阶段 C)提供上传/安装/卸载/导出 `.mpk` 界面;本地文件管理器可调用安装程序 UI(`meow_pkg_install` 的交互入口)。
|
||
|
||
### 4. 实现挂点(MeowOS/launcher)
|
||
- 启动应用入口统一调 `meow_app_prepare(pkg)`:建 `MeowData/<包名>/{data,cache}` + 设 MEOW_DATA_DIR/CACHE_DIR,失败回退提示。
|
||
- `meow_pkg_install/export`:包安装/导出(本期可先做 CLI/接口,UI 后续)。
|
||
- 这些为阶段 C(Web)/文件管理器预留。
|
||
|
||
### 5. 软件包管理器 CLI `nyai`(静态 MIPS,apt/yum 风格,含镜像源)
|
||
|
||
`nyai` 是 MeowOS 的包管理 CLI(静态 MIPS32r2/musl,无外部依赖;tar.gz 用 `gzip -dc | tar -x` 管道,与 launcher 一致)。**包名 `<pkg>` = `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` 可直接调用。
|
||
|
||
**命令**:
|
||
|
||
| 命令 | 说明 |
|
||
|---|---|
|
||
| `nyai update` | 读 sources.list 各源:本地读 `<源>/Packages`;http 用卡上 curl `-fsSL <url>/Packages` 缓存到 `MeowOS/opt/nyai/cache/`;无网正确提示、不硬失败 |
|
||
| `nyai install <包或文件.mpk>` | ① 参数是 `.mpk` 路径→直接装;② 是包名→按缓存索引取 `.mpk`(本地文件或 http 拉取)装;自动解析 Depends(递归)、校验 SHA256+app.json、同名先备份 `.bak_<pid>`、数据 init(建 MeowData/<pkg>/{data,cache}) |
|
||
| `nyai remove <pkg> [--purge]` | 卸载 MeowAPPs/<pkg>(数据默认保留);`--purge` 连 MeowData/<pkg> 删;清理 `.bak_` 残留 |
|
||
| `nyai list` | 列出 MeowAPPs/* 已装应用(名称/版本/占用),跳过 fonts/logs/.bak |
|
||
| `nyai info <pkg>` | 读 app.json 显示 name/version/exec/size/routes/services/depends/description |
|
||
| `nyai search [pattern]` | 扫索引列出可装包(含版本/描述) |
|
||
| `nyai list-upgradable` | 索引版本 > 已装版本的包 |
|
||
| `nyai index <dir>` | 为 `<dir>` 生成 `Packages` 索引(读每个 .mpk 的 app.json 提取 version/description,算 SHA256/Size/Filename) |
|
||
| `nyai export <pkg> [out.mpk]` | 打包 MeowAPPs/<pkg>/(不含 data/cache)→ `<pkg>.mpk` |
|
||
| `nyai scan [dir]` / `nyai upgrade <pkg> [file.mpk]` | 列出 .mpk / 升级(保留数据) |
|
||
| `nyai --help` | 帮助;退出码 0=成功 / 非0=错误 |
|
||
|
||
**源配置** `MeowOS/opt/nyai/sources.list`(卡;无此文件则默认本地源 `MeowOS/opt/repo`):
|
||
|
||
```
|
||
# 每行一个源,支持多源与 # 注释
|
||
local /usr/data/mnt/sd_0/MeowOS/opt/repo # 本地目录
|
||
http http://10.0.0.1/nyai # http(s) 源(url 下挂 Packages)
|
||
deb [trusted=yes] /usr/data/mnt/sd_0/MeowOS/opt/repo # apt 风格别名
|
||
file:///usr/data/mnt/sd_0/MeowOS/opt/repo # file:// 等价本地
|
||
```
|
||
|
||
**索引 `Packages`**(`nyai index <dir>` 生成,行式、空行分隔记录):
|
||
|
||
```
|
||
Package: simtest # 包名 = .mpk 文件名茎 = app.json.package
|
||
Version: 1.2.3 # 来自该 .mpk 内 app.json
|
||
Filename: simtest.mpk
|
||
Size: 388 # 字节
|
||
SHA256: 3fea0b73… # 可选;有则 install 时校验
|
||
Depends: depapp # 可选,逗号/空格分隔,递归安装
|
||
Description: A simulated app
|
||
```
|
||
|
||
**与 launcher 一致**:install/export 复用 M2.12 `meow_pkg_install/export` 同套语义(tar.gz、gzip 管道、包名=文件名茎、同名先备份、校验 app.json、数据 init);exFAT 无符号链接,数据目录由 MeowOS 经 `MEOW_DATA_DIR/CACHE_DIR` 注入,CLI 只建目录不建链接。
|