Files
MeowOS-For-R3P2/patch_v2/meowpanel/README.md
T

435 lines
44 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.
# MeowPanel 后端(`meowpanel.c`)
MeowOS(HiBy R3 Pro II 第三方系统,MIPS32 静态交叉编译)的**外置 Web 面板后端**:单进程、零第三方依赖的
C 静态二进制,内嵌 HTTP/JSON 服务器 + 前端静态托管 + 框架专项服务 IPC 客户端 + `settings.conf` 设置镜像。
- **定位**(`MEOWPANEL_DESIGN.md`):外置于 TF 卡 `MeowOS/opt/MeowPanel/` 的系统组件,由 `meow_svcd`
读 TF 卡 `MeowOS/services.d/meowpanel.conf` 启动、`meow_sysui` 监管。**不进固件**;无卡时系统核心
(launcher/sdk/设置/通知)绝不依赖它。
- **协议**:`devdocs/plan/MEOWPANEL_PROTOCOL.md`(HTTP/JSON 端点;本实现以「实现为准,协议/README 记录出入」)。
- **数据路径**(`SYSTEM_FRAMEWORK_SERVICES.md §2/§4`):读侧(`/api/{status,perf,system}`)经 `meow_svc_perf`
IPC(`MSG_PERF_QUERY→RESULT`)聚合,**绝不各自爬 /proc**(M2.73 起 perf 聚合含 `network` 段);动作侧(`/api/system/{reboot,shutdown,ota}`)经
`meow_svc_power` IPC(`MSG_POWER_ACTION→STATUS`),不再直连 `poweroff`/写 mtd5。两专项服务为**板载 svcd 托管**
(`trigger=boot`),拔卡保留,与无卡依赖最小化自洽。
- **⚠️ OTA 检查升级 ≠ 升级**:前端「检查升级」只读 `GET /api/system` 的 `ota{current,available,latest}`,**勿 `POST /api/system/ota`**(那是触发升级进升级模式)。
---
## 1. 编译
工具链:交叉编译器 `/home/Aska/Project/R3ProII/.tools/zig-mipsel-cc`(mipsel 32 位、musl 静态,可用)。
```bash
cd /home/Aska/Project/R3ProII/patch_v2/meowpanel
./build_meowpanel.sh # 交叉编译 → meowpanel(mipsel 静态,firmware-ready)
./build_meowpanel.sh host # 宿主编译 → meowpanel_host(本机 ld,仅供调试)
```
- 两分支均以 `-Os -Wall -Wextra` 复核,**0 warning**(`-Werror` 亦通过)。
- 产出(本次构建实测):
- `meowpanel`:mipsel 静态,`ELF 32-bit LSB executable, MIPS, MIPS32 rel2, statically linked, stripped`,
体积约 **218 KB**(固件阈值内)。md5:`015f9cf1c12e36d133b01cf6b6648edd`(本次含 `/api/audio` 音频状态端点 + 前端「音频」「字体」页)。
- `meowpanel_host`:aarch64 动态链接(宿主编译,仅供调试),体积约 **148 KB**。
- 依赖:仅 libc + Linux 内核头(`linux/input.h`),零第三方库(设备无 python3/lua)。鉴权用**内联 SHA-256**(拷贝自
`meow_verify/sha256.c` 公有领域参考写法,前缀 `mp_sha256_*` 避免符号冲突;**未改 `meow_verify/` 目录**)。
阶段 C 实时画面用**自研最小 baseline JPEG 编码器** `mp_jpeg.c`(顺序 DCT + 4:2:0 + 标准 Huffman 表 Annex K;
无 libm、零依赖,仅 malloc/realloc),质量 70 下 480×720 帧约 41 KB。
**字体库导入/导出(用户 2026-08-31 定案)**:与 `.mpk` 同构、**放弃 zip,改走 busybox `gzip -dc | tar` 管道**
(设备必带 tar/gzip,零新增依赖、不引入 zlib/miniz/自研 zip,见 §5.11)。
**性能**:设备为 mipsel muslsf **无 FPU**,初版逐块 `double` 矩阵 DCT 编码 480×720 一帧约 **3.96 s**(掉帧到 5-6 s/帧的根因)。
已改为**整数定点 DCT + 整数 YCbCr**(FSCALE=13,BT.601 256 系数,纯整型乘加),
实测编码降至约 **0.19 s(≈20×)**,单请求端到端(本机 localhost)约 0.20 s、LAN 约 0.22-0.31 s,
质量/体积与 double 版 PSNR 等价(主机 480×720 高频测试图两者差 <0.1 dB)。
- 头文件引用:`-I patch_v2/meow_launcherd`(`meow_ipc.h` 帧格式 / `m_perf_*`/`m_power_*`/`POWER_*`,
**header 为权威源**,结构体字段以它为准)。
## 2. 部署到 TF 卡
```bash
# 1) 后端 + 前端(web/index.html + meowpanel.css + meowpanel.js)放在同一目录
sudo mkdir -p /usr/data/mnt/sd_0/MeowOS/opt/MeowPanel
sudo cp patch_v2/meowpanel/meowpanel /usr/data/mnt/sd_0/MeowOS/opt/MeowPanel/
sudo cp patch_v2/meowpanel/web/index.html /usr/data/mnt/sd_0/MeowOS/opt/MeowPanel/
sudo cp patch_v2/meowpanel/web/meowpanel.css /usr/data/mnt/sd_0/MeowOS/opt/MeowPanel/
sudo cp patch_v2/meowpanel/web/meowpanel.js /usr/data/mnt/sd_0/MeowOS/opt/MeowPanel/
# 2) 服务配置(svcd 读 TF 卡 services.d 拉起)
sudo mkdir -p /usr/data/mnt/sd_0/MeowOS/services.d
sudo cp patch_v2/meowpanel/services.d/meowpanel.conf /usr/data/mnt/sd_0/MeowOS/services.d/
```
说明:
- 前端资源与后端**同目录**(后端 `GET /` → `index.html`,`GET /meowpanel.css`/`meowpanel.js` 同目录取)。
后端也支持 `GET /assets/<file>`(映射到同目录文件),前端当前用根相对路径。
- 服务启动后整卡由 `meow_svcd` 按 `trigger=boot` 拉起;`meow_sysui` 监管(restart/backoff/max_restart)。
- **无卡**:`MeowOS/services.d/` 不可读 → svcd 跳过本服务;系统核心照常。
## 3. 访问
浏览器(同局域网):
```
http://<host>.local:8080/
```
- mDNS 由板载 `meow_mdns` 应答 `<host>.local`。
- 端口默认 **8080**,可用 `-p PORT` / 环境变量 `MEOWPORT` / 与后端同目录 `meowpanel.conf`(`port=N`)覆盖。
(优先级:`-p` > `MEOWPORT` > `meowpanel.conf` > 默认 8080。)
- 无鉴权(设备内网信任模型;**可选**密码鉴权已实现,见 §5.9)。默认关=零回归;开启后除登录/登出/状态外 `/api/*` 需会话。
- 上传/下载/管理类请求统一带 `X-MeowToken: <token>`(或 `Authorization: Bearer <token>` / `Cookie: meow_token=<token>`);
缺/错/过期 → 401 `UNAUTHORIZED`。**默认无鉴权时请勿把 8080 暴露公网**。
---
## 4. 端点实现清单(对照 MEOWPANEL_PROTOCOL §2 端点总览)
### 已实现(返回 200/2xx 或语义错误码)
| 方法 | 路径 | 说明 | 实现 |
|---|---|---|---|
| GET | `/`、`/meowpanel.css`、`/meowpanel.js`、`/assets/<f>` | 静态托管 | ✅ |
| GET | `/api/screen.jpeg` | 实时画面:fb0 当前帧 → RGB565→JPEG(`?q=1-100` 质量,默认 70;`?delta=1` 开启增量帧,见 §5.10) | ✅(阶段 C) |
| POST | `/api/input/tap` | 触摸注入:`{x,y}`(480×720 原生坐标;write 进 goodix event1,本机实测可投递);本机触摸后 5s 内忽略注入 | ✅(阶段 C) |
| GET | `/api/auth/status` | 鉴权状态(enabled/会话有效) | ✅(公开) |
| POST | `/api/auth/login` | 密码登录发 token(sha256 带盐校验) | ✅(公开;错→401) |
| POST | `/api/auth/logout` | 登出失效 token | ✅(公开;幂等) |
| GET | `/api/status` | 性能+系统(`meow_svc_perf` IPC subset=all 透传) | ✅ |
| GET | `/api/perf` | 仅性能(subset=perf 透传) | ✅ |
| GET | `/api/system` | perf IPC + boot/network/ota 局部补全(network 含蓝牙,无网口;`ota` 只读检查,勿误触升级) | ✅ |
| POST | `/api/system/reboot`、`/shutdown`、`/ota` | `meow_svc_power` IPC | ✅ |
| GET | `/api/settings` | 全量+元数据(items/count/groups) | ✅ |
| GET | `/api/settings/:key` | 单项 | ✅ |
| PUT | `/api/settings/:key` | 写单项(clamp/枚举校验) | ✅(越界/非法枚举→422) |
| POST | `/api/settings` | 批量写 | ✅ |
| POST | `/api/settings/restore` | 恢复默认 | ✅ |
| GET | `/api/theme` | 读主题 | ✅ |
| PUT | `/api/theme` | 写主题(style/theme/background 子集) | ✅(非法→422) |
| GET | `/api/files?path=` | 目录列表(白名单) | ✅(无卡→503) |
| GET | `/api/files/read?path=` | 读文本(<1MB;二进制→415) | ✅ |
| GET | `/api/files/download?path=` | 下载流 | ✅ |
| POST | `/api/files/upload` | multipart 上传 | ✅ |
| POST | `/api/files/mkdir` | 新建目录(已存在→409) | ✅ |
| POST | `/api/files/rename` | 重命名 | ✅ |
| DELETE | `/api/files?path=` | 删除(可 recursive=1;非空未 recursive→409) | ✅ |
| GET | `/api/fonts` | 字体库列表(扫描 `MeowOS/fonts/<family>/<weight>/` 三件套齐) | ✅(无卡→503) |
| POST | `/api/fonts/import` | 上传字体库 `.tar.gz`(multipart `file`,可带 `?force=1` 覆盖) | ✅(缺件/坏json/穿越→400,冲突→409,超限→413) |
| GET | `/api/fonts/export` | 导出 `<family>/<weight>/` 三件套为 `.tar.gz`(`?family=&weight=`) | ✅(缺件→404) |
| GET | `/api/audio` | 音频状态快照:meow_audiod IPC `MSG_AUDIO_QUERY→STATE` 透传(当前流/路由/音量/underrun,见 §5.12) | ✅(audiod 不在→`available:false`,不报错) |
| GET | `/api/apps` | 扫描 `MeowAPPs/` 读 `app.json` | ✅ |
| GET | `/api/apps/:pkg` | 应用详情 | ✅ |
| POST | `/api/apps/install` | 经 `nyai install`(multipart 或 path) | ✅ |
| GET | `/api/apps/:pkg/export` | `nyai export` 下载流 | ✅ |
| DELETE | `/api/apps/:pkg` | `nyai remove [--purge]` | ✅ |
| POST | `/api/key/:code` | 按键注入(tap/long) | ✅(物理键红线内) |
| POST | `/api/key` | 批量/顺序注入 | ✅ |
| GET | `/api/logs` | `MeowOS/logs` 文件列表 | ✅(无卡→503) |
| GET | `/api/logs/read?file=&lines=` | 读日志(上限 1000 行;`..`/绝对→403;敏感名→403) | ✅ |
| GET | `/api/notifications` | 通知列表(**优先 launcher IPC 内存活跃集** `source:"ipc"`;IPC 不可达回退 TF 转储日志 `source:"tf"`;见 §5.8) | ✅(无源→空态 `source:none`,不 503) |
| DELETE | `/api/notifications/:id` | 单条删除(**优先 IPC 撤下 launcher 内存**;IPC 不可达回退重写日志移除该 id) | ✅(不存在→404) |
| POST | `/api/notifications/dismiss_all`(别名 `/api/notifications/clear`) | 一键清空:**优先 IPC 联动 launcher 内存**,保留活动中的媒体/进度通知;IPC 不可达回退重写日志 | ✅ |
| GET | `/api/files/read?path=` | 读文本:预览上限 1MB,超限 `truncated:true`(不再 404);二进制→415 | ✅ |
### 返回 501 `NOT_IMPLEMENTED` 的端点(诚实标注)
| 端点 | 原因 |
|---|---|
| `/api/key/:code` 的 `press:"hold"` | `hold` 需常驻持续注入通道(阶段 D),后端未实现;`tap`/`long` 已实现。 |
| OTA 升级模式(`/api/system/ota` 带 `url`) | 升级为草案(MEOWPANEL_PROTOCOL §6 待定案);当前 `POST /api/system/ota` 空 body 触发 `meow_svc_power` 的 `POWER_UPGRADE`(写 mtd5 进升级模式),带 `url` 的下载+升级未实现。 |
> 通知端点已从 501 落地,且现为**实时**:优先经 launcher IPC `MSG_NOTIFY_QUERY(0x25)→QUERY_RESULT(0x27)` 取
> **内存活跃集**(`source:"ipc"`);IPC 不可达/超时回退读 TF 卡通知转储日志(`source:"tf"`,会话级归档);两者皆无
> `source:"none"`。撤/清(`DELETE` / `dismiss_all`)同样**优先走 IPC**联动 launcher 内存(launcher 撤下时自动归档留档)。
> 详见 §5.8。
> 前端对 `501` 显示「该功能后端尚未实现」(`ok:false,error.code=NOT_IMPLEMENTED`)。
---
## 5. 关键实现说明
### 5.1 HTTP 路由 / JSON 序列化
- **单线程 + poll 串行**(面板访问量极小)。`accept` → `read_request`(先读头到 `\r\n\r\n`,**在缓冲区内搜索**头边界,
处理「头+body 同段到达」,再按 `Content-Length` 精确读 body)→ `handle` → 写响应 → 关闭连接(`Connection: close`)。
- 读/写均有 `poll` 超时(读头部 5s、body 分段 5s,IPC 2s)。`SIGPIPE` 忽略。
- JSON 响应信封:成功 `{"ok":true,"data":<payload>}`;失败 `{"ok":false,"error":{"code","message"}}`
(MEOWPANEL_PROTOCOL §1.1/§1.2,HTTP 状态码映射照抄)。
- **无 JSON 库**:手写最小递归下降解析器(对象/数组/字符串转义/数字/bool/null),仅覆盖实际用到的收发。
- 下载/上传用流式(`multipart/form-data` 极简解析:按 boundary 切,取 `path`+`file` 字段)。
### 5.2 IPC 对接(meow_svc_perf / meow_svc_power)
- **帧**:复用 `meow_ipc.h`——16B 头(magic `0x4D4F5701`/version 1/type/req_id/len,全小端)+ 变长负载,
`MEOW_IPC_MAX=4096`。帧读写 `x_read_full`/`x_write_full` + `send_frame`/`recv_frame`(与 meow_svc_perf.c 对称)。
- **握手**:连接后先 `MSG_HELLO`(`app_id="os.meow.panel"`,系统面板身份,§5 二期拟名)→ 读 `MSG_ACK(code=0)`。
- **读侧**:`MSG_PERF_QUERY`(`m_perf_query_t.subset`)→ `MSG_PERF_RESULT`(`m_perf_result_t`,`code`/`json_len`/`json`),
**直接透传**服务端聚合 JSON(`/api/status`=全部、`/api/perf`=perf 子集、`/api/system`=全量取 uptime/storage/tfcard/system)。
读取防溢出:`json_len` 以 `sizeof(m_perf_result_t.json)-1` 与 `cap` 双钳位。
- **动作侧**:`MSG_POWER_ACTION`(`m_power_action_t.cmd=POWER_REBOOT/SHUTDOWN/UPGRADE`)→ `MSG_POWER_STATUS`(`code`/`msg`)。
收到 `MEOW_OK` → `{"accepted":true}`;否则按 `code`(PERM→403 / NOENT/IO→503 / 其余→500)返回错误信封。
- **失败语义**:socket 不存在/连接拒/超时/应答非 OK → 返回 500 `INTERNAL`(或 503 `NO_CARD` 当缺升级文件)
并带中文 message。**绝不静默失败、绝不降级直连 /proc 或 poweroff**(单一权威源)。
- 连接超时:非阻塞 connect + `poll(POLLOUT)` 2s,`SO_RCVTIMEO`/`SO_SNDTIMEO` 兜底。
### 5.3 设置镜像(读写板载 `settings.conf`)
- **不旁路**:只经 `settings_get_value`(`=` 切分 + key 去首尾空白精确匹配,防 `audio.volume` 误配 `audio.volume_max`,
照 `meow_ui_vol_read`/`meow_svc_power settings_get_int_trim` 同款解析)与 `settings_write_one`(`"key = value\n"`,
保留 `# meow-settings 1` 头、逐行重写、缺键追加;目录缺失自动 `mkdir`)。**不绕过此函数直接改文件**。
- **键表/默认值来源**:`patch_v2/meow_launcherd/meow_settings.c` 的 `g_defs[]`(**权威来源**,逐键一致),
含 type/def/min/max/enum_names/enum_labels;`GET /api/settings` 返回 `items/count/groups`(分组按键前缀,
如 `display.*`→显示、`font.*|ui.*`→字体主题…)。
- 类型:`int`→number(带 min/max/def 字符串)、`bool`→原生布尔、`enum`→`options(name/label)+default_name+def`、
`str`→string。**越界/非法枚举 → 422 `UNSUPPORTED`**。
- 语义 clamp:`display.brightness` 读/写都 clamp 到 `[display.brightness_min(默认1), 100]`(防 101 闪屏,
与 `meow_settings_brightness_clamped`/`clamp_brightness` 同口径);`boot.default` 写后同步 `/data/meow_boot_default`
(meow→0/hiby→1,同 `meow_settings_save`)。
- 注:后端为独立进程,无法调用 launcher 进程内的 `meow_settings_*`(那是其内存表),故**直接镜像同一份
`settings.conf`**,保持与实体设置页/CLI 同一存储契约(协议 §3.2 说「经 meow_settings_*」,此处以
「镜像同一文件 + 同款 parse/save 契约」落地,见 §8 出入记录)。
### 5.4 文件白名单
- 根 `/usr/data/mnt/sd_0`;白名单子目录 `MeowOS/Music/MeowData/MeowAPPs/book`(`allowed` 数组)。
- 路径经 `normalize_path` 规范化(去 `.`/`..`,拒绝逃逸出根)后 `path_in_whitelist` 校验(== 根或属于某一白名单
一级子目录)。**`..`/绝对路径逃逸 → 403 `FORBIDDEN`**;无卡 → 503 `NO_CARD`。
- 读取拒绝敏感名(`settings.conf`/`setting.bak`/`.env`/`passwd`/`shadow`/`meow.conf`/`user.ini`/`config.json`)。
- 列表/上传/下载/读文本(1MB+二进制→415)/mkdir(已存在→409)/rename(new_name 不含 `/`)/delete(递归,非空未 recursive→409)。
### 5.5 nyai 对接
- 包管理器 CLI 绝对路径 `/usr/data/mnt/sd_0/MeowOS/opt/bin/nyai`(M2.12+ 固件经 `meow_card_path.sh` 前插 PATH,可直接调;
本后端用绝对路径,兼容更早固件)。包名 = `app.json` 的 `package` = `.mpk` 文件名茎。
- `install`:multipart 上传 `.mpk` 落临时文件(`MeowOS/tmp/`)或 JSON `{path}` → `nyai install <file>`;用 `popen` 捕获输出+退出码,
非 0 → 422。`export`:`nyai export <pkg> <tmp>` → 以 `Content-Disposition: attachment` 流式下发。`remove`:`nyai remove <pkg> [--purge]`。
- `GET /api/apps` / `:pkg` 详情:直接扫描 `MeowAPPs/<dir>/app.json`(手写 JSON 解析提取 name/version/exec/framework/immersive/fullscreen),
不依赖 nyai(nyai 未部署时列表/详情仍可用)。解析失败项 `invalid:true`。
- `nyai` 未部署 → install/export/remove 返回 500(带 nyai 输出)。
### 5.6 按键注入通道
- **复用现有先例**:`patch_v2/uinput_tap/uinput_tap.c` 的 `write`-到-evdev 注入机制。launcher 实际轮询
`meow_launcher_internal.h`:`/dev/input/event0`(md-gpio-keys: POWER/PREVIOUS)、`/dev/input/event2`
(jz adc keyboard: VOL/PLAY/NEXT)。后端 open `O_WRONLY` 写 `EV_KEY press/release + SYN_REPORT` 即可到达 launcher 按键处理。
- **物理键红线**(APPS.md / 任务约束):只注入音量(114/115)/电源(116)/播放(164)/上下曲(163/165);
**禁止注入 返回(158)/主页(172)/确认(28)/退出(96)** → 403 `FORBIDDEN`。
- `tap`(默认,抬起间隔 80ms)、`long`(按住 ms,缺省 3000);`hold` → 501。
- 局限:若 `/dev/input/event0/event2` 在设备上不可写(权限/路径差异),注入失败 → 500;注入为**即时性注入**,
不在 launcher 内维护长按状态机(与实体按键的 `longpressed` 语义近似,见 README 已知边界)。
### 5.7 日志
- `GET /api/logs`:`MeowOS/logs` 下常规文件列表(跳过敏感名)。
- `GET /api/logs/read?file=&lines=`:`file` 必须相对 `MeowOS/logs/`,**禁 `..`/绝对路径 → 403**,敏感名 → 403;
缺省 `lines=200`,上限 `1000`;返回 `{file,lines[],total,truncated}`。
### 5.8 通知(数据源 = 优先 launcher 内存活跃集,回退 TF 归档)
- **数据源优先级**:① launcher IPC 内存活跃集(实时,`source:"ipc"`)→② TF 转储文件(`source:"tf"`)→③ 空态(`source:"none"`)。
- ① **launcher IPC**:后端连 `/usr/data/meow_ipc.sock`,`MSG_HELLO` 握手(`app_id=os.meow.panel`)→ `MSG_NOTIFY_QUERY(0x25)`
(空 payload=全部 app)→ 期望 `MSG_NOTIFY_QUERY_RESULT(0x27)`(`m_notify_query_result_t`,JSON 数组文本,字段同 TF 归档,
`ts` 为 epoch **毫秒**)。launcher 在**主循环单写者**序列化(绝不把通知存储读到 IPC 线程)。后端按 `ms` 升序重排
(oldest..newest,与 TF 一致),emit 时倒序= newest first;`ts` 除以 1000 出秒(与 TF 同口径)。
- ② **TF 转储文件**(回退):`<卡根>/MeowOS/notifications/notifications.log`(launcher `meow_notif_dump_tf` 一行一条 CSV,
字段 `timestamp,id,app_id,app_icon,category,urgency,progress,title,body,target_activity,ongoing,media,dismissed`;
`meow_notif_clear_tf` 开机清空 → 会话级归档)。后端逐行流式读取(环形保留最新 `NOTIF_PREVIEW_MAX=200` 条,不全量进内存)。
注意 TF 是**归档快照**(归档时 `dismissed` 尚未置 1,故该列恒 ≈0 不可靠,且非活跃快照)。
- **`GET /api/notifications`**:返回 `{count, items:[...], notifications:[...], source}`;`items`= `notifications` 别名
(兼容前端读 `.notifications` 与任务约定空态 `items:[]`)。字段含 `id/app_id/app_name/app_icon/category/summary/body/
urgency/ts/progress/ongoing/media/target_activity/dismissed`。`progress<0`→`null`;`app_name` 对 `system`→`系统`,其余用 `app_id` 兜底。
无源(无卡/文件不存在)→ `{count:0,items:[],notifications:[],source:"none"}`(**不 503、不伪造数据**)。
- **`DELETE /api/notifications/:id`**:**优先 IPC** `MSG_NOTIFY_DISMISS(0x22)`(4B id → `MSG_ACK`;launcher 撤下时自动归档留档);
IPC 不可达/未受理 → 回退从日志移除该行(写临时 + rename)。IPC 未受理且日志也无该 id → 404。
- **`POST /api/notifications/dismiss_all`**:**优先 IPC** `MSG_NOTIFY_DISMISS_ALL(0x26)`(→ `MSG_ACK.status`=清除数;
launcher 保留 ongoing/media/progress 红线项);IPC 不可达 → 回退重写日志(`ongoing||media||progress>=0||category=="media"` 保留)。
- **实时性**:IPC 路径直接反映 launcher **内存活跃集**——新通知(含未到期/未归档)**立即**出现在面板(实测:post 一条
通知后面板同时刻即见,`source:"ipc"`);撤/清经 IPC 联动 launcher 内存(实测:面板 DELETE 后 launcher 活跃集即无该 id)。
TF 路径仅为 launcher 不可达时的诚实降级(会显示自动过期的历史通知,直到 clear)。
- **并发**:IPC 可用时面板不写 TF(撤清由 launcher 自行归档),避免与 launcher `O_APPEND` 竞写;仅 IPC 回退到「写临时 + rename」。
### 5.9 可选鉴权(panel.auth_enabled / panel.auth_pass)
- **设置键**:`panel.auth_enabled`(bool,默认 `0`=关)+ `panel.auth_pass`(str,存 `salt:hex`)。两者写 `settings.conf`,
与实体设置页/CLI 同一存储契约(面板为独立进程,直接镜像文件)。`panel.auth_pass` 的 `GET /api/settings` 恒回
`"value":"" + "set":<bool>`,**绝不回传存量哈希/明文**。
- **门禁**:`require_auth` 在路由分发前执行——未启用直接放行(零回归);启用后除 `/api/auth/{login,logout,status}` 外全部
`/api/` 需有效会话(token 头 / `X-MeowToken` / `Cookie meow_token`),缺/错/过期 → 401 `UNAUTHORIZED`。
危险端点(reboot/shutdown/ota、key 注入)在未开启时**不额外限制**(保持内网模型),开启后亦统一纳入会话校验。
- **密码**:`PUT /api/settings/panel.auth_pass` 传明文,后端生成 16B 盐(/dev/urandom)→ `salt:hex`(sha256(salt||password))
落盘;空串=清除。校验时重算并恒定比较。**哈希/明文绝不进日志/响应**。`panel.auth_enabled` 置 1 时若未设密码 → 422 `请先设置面板密码`。
- **会话**:`POST /api/auth/login` 校验后发 token(32B → 64 hex),存内存(`AUTH_MAX_SESS`=8,满淘汰最早过期)+ 卡上
`<web_root>/meowpanel.session`(`<token> <expires_at>`,重启加载、防吊销失效),有效期 12h。`logout` 失效当前 token。
- **静态屏蔽**:`serve_static` 额外拒绝后端可执行文件与 `meowpanel.session`(403),防 token 文件被 HTTP 读取。
- **前端**:api 客户端统一带 `X-MeowToken`,收到 401 → 弹登录页(`#login` overlay);登录成功 token 存 `localStorage`;
首次进入以 `/api/auth/status` 决定是否弹登录。设置页「面板鉴权」卡片含开关(`panel.auth_enabled`)+「设置/修改密码」
按钮(`panel.auth_pass`,走 `passwordDlg` 输入)。
### 5.10 实时画面(`/api/screen.jpeg`,含增量帧 `?delta=1`)+ 触摸注入(`/api/input/tap`)— 阶段 C
- **实时画面(整帧,默认)**:`GET /api/screen.jpeg` 读 `/dev/fb0` **当前显示帧**(`FBIOGET_VSCREENINFO` 取 `yoffset`/`xres`/`yres`/`bpp`,
`FBIOGET_FSCREENINFO` 取 `line_length`,`pread` 活动缓冲,**只读,不影响渲染**;16bpp RGB565)→
`mp_jpeg_encode_rgb565()` 编码为内存 JPEG(自研最小 baseline 编码器,见 §1),返回 `Content-Type: image/jpeg`。
`?q=1-100` 控制质量(默认 70)。恢复 `line_length != xres*2`(行尾填充)时先压缩为紧凑 RGB565。
- **增量帧(`?delta=1`,阶段 C 增强)**:服务端**缓存上一帧紧凑 RGB565**(单线程 accept 循环跨请求持有),按 **8×8 块**逐行
`memcmp` 比对(1 块=8×8 像素×2B),只编码**变化块的外接矩形子图**(8 对齐、钳到屏内)为独立 JPEG,并以**响应头**回传偏移/几何
(`X-Meow-Delta:1`/`X-Meow-Full:0|1`/`X-Meow-W/H`(整屏)/`X-Meow-Sub-X/Y/W/H`(子图原点与尺寸)/`X-Meow-Changed`/`X-Meow-Blocks`)。
- 冷启动/几何变化/变化块覆盖全屏 → 发**整帧**(`X-Meow-Full:1`);帧无变化 → `Content-Length:0` + `X-Meow-Changed:0`(前端跳过叠加)。
- 体积:近静态帧(只刷新状态栏时钟等)增量子图可低至数百 B(实测 6 变化块=**804 B**,对比整帧 ~27 KB @q70)。
- **不破坏默认**:未带 `?delta=1` 仍为整帧 `image/jpeg`(零回归)。
- 前端「屏幕」页新增「增量传输(省流量)」开关:开启后走 `?delta=1`,用 `<canvas>` 叠加(整帧播种/替换,子图 `drawImage(x,y)`);
关闭走原 `<img>`+objectURL 整帧。默认关。
- **触摸注入**:`POST /api/input/tap`,body `{x,y}`(0..479 × 0..719 原生像素坐标,前端按 480×720 映射,后端再 clamp)。
**机制 = write 进 goodix evdev(`/dev/input/event1`)**——**本机实测 write 注入可投递**(见 §7 修正历史结论),
事件序列与真实 goodix 一致:先 `BTN_TOUCH=1`(按下边沿)**后** `ABS_MT_*`/`ABS_X/Y`(坐标),`SYN` → 按住 100ms →
`BTN_TOUCH=0` + `ABS_MT_TRACKING_ID=-1` + `SYN`。**关键**:launcher/`meow_touch` 在按下边沿会**重置坐标 -1**,
只在 down 之后到来的首个有效 ABS 才锁原点(goodix 时序免疫)——故坐标必须**在 BTN_TOUCH=1 之后**,否则原点永不
锁定 → 抬手被判无效 tap(只高亮不启动)。注入前不写 uinput(本设备厂商内核 write 进 goodix 即可投递,逐 UB 少)。
- **仲裁(本机优先打断)**:注入前 `touch_last_local_ms()` 排空 event1,取「最近一次本机真实触摸」的单调毫秒时间
(剔除本进程自注入事件:事件时间 ≤ 上次注入时刻+200ms),若 `now-last < 5000` → **忽略注入**,返回
`{"dispatched":false,"reason":"local_touch_active","cooldown_ms":N}`;否则注入并返回 `{"dispatched":true,"x","y"}`。
MIPS 的 `struct input_event` 无 `time` 成员(ABI),按 launcher 同款读裸 16 字节取偏移 0/4 的 `tv_sec/tv_usec`(单调时钟)。
- **前端「屏幕」页**(`web/#/screen`,导航「屏幕」):`<img>` 轮询 `GET /api/screen.jpeg?t=…&q=70`(`fetch`→blob→objectURL,
防重叠 + 旧 URL 回收;后端不可达提示「后端未连接」);默认 **1fps**,滑条 **0.2–2fps**(存 `screen.fps`,页面隐藏/切走即停刷,
`visibilitychange` 回前台续刷);点击画面按 480×720 比例换算坐标 → `POST /api/input/tap`,`dispatched:false` 时 toast「本机触摸中」。
### 5.11 字体库 tar.gz 导入 / 导出(`/api/fonts*`,新增)
- **定位**(MEOWOS_SPEC_REF §5.7.6 / FONT_SYSTEM.md,**2026-08-31 用户定案:放弃 zip,与 .mpk 同构走 tar.gz**):
系统字库按 `MeowOS/fonts/<family>/<weight>/` 三件套存放(schema 见 SPEC_REF §5.2:family/weight/name/
glyph_count/charset/tier/size_list/bit_depth/sha256_png/sha256_idx…)。**设备 busybox tar/gzip 必定存在**,
后端全部走 `gzip -dc | tar` 管道(nyai .mpk 同款),**零新增依赖、不引入 zlib/miniz/自研 zip**。
- **导入 `POST /api/fonts/import`**(multipart `file`=<.tar.gz>,上限 **`MAX_FONT_UPLOAD`=32MB**,可带 `?force=1`):
1. multipart `file` 落临时 `<TF_CARD>/tmp/mp_font_...tar.gz`(`multipart_extract_file`,二进制安全 `mem_find`)。
2. **成员预校验**:`gzip -dc '<tmp>' | tar -t`,每成员经 `sanitize_member`(去前导 `/`/`./`,拒 `..` 组件与
shell 危险字符)且**恰为三件套平铺**(多出条目/子目录/绝对/`..` → 400)。
3. **解压到临时目录**:`gzip -dc | tar -x -C stg` → opendir/lstat 锁:**恰为 3 个普通文件**
(拒软链/目录/设备;总大小 ≤ `FONT_EXTRACT_MAX`=32MB 防 gzip 炸弹)→ 缺件/坏 json 400。
4. `font.json` 手写 JSON 解析 → `family`(缺省回退 `name`)/`weight`(缺省 `regular`),`safe_dir_segment`
消毒(拒 `/`、`\`、控制符、`.`/`..`)→ 非法 422。
5. **覆盖冲突**:目标目录任一文件已存在且非 `force=1` → **409 `CONFLICT`**(前端 confirm 后带 `force=1` 重试)。
6. **sha256 仅 WARN**(对齐 font.json 约定):流式重算 `sha256_png`/`sha256_idx` 与声明比对,不符写 `warning`
字段、仍导入成功(**不硬失败**);响应含 `family`/`weight`/`imported`/`hint`(提示进设备「设置→字体」页
列表自动重扫生效——**后端不触发 launcher 重扫**,扫描时机=启动/进设置页,勿加热路径)。
7. `mkdir`(系统调用,**不用 `system()`**,防注入)→ `rename` 三件套到目标目录(原子替换)。
- **导出 `GET /api/fonts/export?family=&weight=`**:校验 family/weight 段 → 目标目录三件套须齐(缺件 404)→
`tar -cf - -C <font_dir> font.png font.idx font.json | gzip -c > <tmp>` 落临时文件(求 `Content-Length`)→
`Content-Disposition: attachment; filename="<family>-<weight>.font.tar.gz"` + `application/gzip` → 流式下发。
- **列表 `GET /api/fonts`**:扫描 `MeowOS/fonts/<family>/<weight>/`,三件套齐才列出,读 `font.json` 取
name/tier/glyph_count(容错)。无卡 → 503。
- **往返验证**(宿主):Python tarfile 生成的平铺三件套 `.tar.gz` 导入→导出→逐字节比对源文件一致;本后端导出
再导入→逐字节一致;GNU tar/`tarfile` 均能读;目录穿越 `../`/绝对路径/软链成员/缺件/坏 font.json/多出条目/
子目录包裹/非 gzip——全部拒绝;打包文件名 `<family>-<weight>.font.tar.gz`。见 §7 宿主测试备注。
### 5.12 音频状态(`/api/audio`,新增,AUDIOD_DESIGN §8 面板对接)
- **数据源**:`meow_audiod` 独占音频守护(板载进固件,svcd 托管;HibyOS 模式按门控不启动)。
后端连 `/usr/data/meow_audio.sock`(可被 `--sock-audio` / env `MEOW_AUDIO_SOCK` 覆盖,宿主/qemu 测试用,
与 audiod CLI 同 env),发 `MSG_AUDIO_QUERY`(0x00C7) 空负载,收 `MSG_AUDIO_STATE`(0x00C8) 文本帧
(`active=.. app=.. sid=.. rate=.. ch=.. fmt=.. written=.. played=.. underrun=.. paused=.. route_mode=.. port=.. vol=..`,
空格分隔 k=v、NUL 结尾)。**audiod 无 MSG_HELLO 握手**(见 meow_audio.c CLI),与 perf/power/launcher 路径不同。
- **响应**:解析 k=v 为 JSON——`{available:true, active, app, sid, rate, ch, fmt, written_frames, played_frames,
underrun, paused, route_mode("auto"/"manual"), port, vol}`;`port` 为 amixer numid=9 路由口
(0=空闲 1=lineout 2=3.5 3=4.4 4=Type-C SPDIF;宿主无 amixer 时为 -1)。
- **audiod 不在**(socket 缺失/连接拒/超时/非预期应答)→ HTTP 200 `{"available":false}`,**不报错**(面板音频页显示
「audiod 不可用」)。只读端点;不放测试音(真机乱响风险,tone 留给 CLI `meow_audio tone`)。
- **前端**:「音频」页(`web/#/audio`)5s 轮询展示——当前流(app/rate/ch/fmt/已写帧/已播放帧/underrun/暂停态)+
路由口 + 音量 + audiod 可用性;切页即停轮询。
### 5.13 字体管理页(前端 `web/#/fonts`,新增)
- 后端 `/api/fonts*` 三端点(§5.11,agent-243 落地)的专属页面:表格列出 family/weight/name/tier/glyph_count,
行内「导出」(busy 防连点「打包中…」)+ 顶部「上传字体库 .tar.gz」(导入中禁用按钮=进度态)+「刷新」。
- 上传遇 **409 CONFLICT**(同 family/weight 已存在)→ 弹确认框,确认后带 `?force=1` 重试;成功 toast 带
`family/weight` 与 `warning`(sha256 不符仅提示)/`hint`。
- 设置页原「字体库」卡保留(同一后端,互不影响)。
---
## 6. 与协议的出入(「实现为准 + 原因」,供回改 MEOWPANEL_PROTOCOL.md)
1. **`ui.style` 值域**:`meow_settings.c g_defs` 源码为 `"md3,miui"`(label "Material You,MIUI"),但
SYSTEM_SETTINGS.md 定案 + MEOWPANEL_PROTOCOL §5 一致行为 **`miuix`**(对应 `MEOW_STYLE_MIUIX`),前端 `data-style` 也用 `miuix`。
→ 本表对齐为 `"md3,miuix"` / "Material You,MIUIx"。**建议**同步改回 `meow_settings.c g_defs`(否则实体设置页与面板值域漂移)。
2. **设置项映射**:协议 §3.2 称后端经 `meow_settings_*` 读写;因后端为独立进程,无法链接 launcher 内 API,
**改为直接镜像同一份 `settings.conf`**(同款 parse/save 契约)。改哪边都会落同一文件,实体/网页/CLI 仍同步(listener/落盘)。
3. **`display.brightness` 元数据**:`g_defs` 为 `min=0 max=255 def=101`;但语义 clamp 到 `[brightness_min(1),100]`
(防 101 闪屏)。后端给前端 `min=1 max=100`(协议 §3.2 期望),读值经 clamp 后返回(默认→100)。若回改协议可注明「原始表 0-255,面板展示语义 1-100」。
4. **`/api/system` 的 `network`/`ota`**:**M2.73** 起 perf 服务已聚合 `network` 段(真实 WiFi `wpa_cli`/`ip` + 蓝牙 `hci0`/sysfs + `hostname_local`,SYSTEM 子集),后端直接透传;只补 `boot.default`(读 `/data/meow_boot_default`)与 `ota{current=version,available:false,url:null}`。**网络栏以「蓝牙」为准**(设备无网口,无 `以太网` 一行,`bluetooth.available=false` 显示「无蓝牙」)。若回改协议可注明「network 已并入 perf 服务,ota 为面板占位」。
5. **`/api/files` 上传/删除/重命名等 path**:前端对这些操作传**绝对路径**(`d.path/子项`),列表传**相对 root**;
后端 `normalize_path` 对两者均容错(相对则拼 root,绝对则直用),符合协议「白名单根 + 规范化」。
6. **`GET /api/files` 返回值**:`files_list` 返回 `500` 语义时以 500 封装(协议未定义 `root` 之外错误枚举,用通用错误信封)。
7. **通知清单数据源**:协议 §3.8 称后端「经通知后端读取」。launcher 现已有**客户端查询/撤/清 IPC**(`MSG_NOTIFY_QUERY(0x25)`/
`MSG_NOTIFY_DISMISS(0x22)`/`MSG_NOTIFY_DISMISS_ALL(0x26)`,agent-199 落地),后端改为**优先经这些 IPC** 取 launcher **内存活跃集**
(实时,`source:"ipc"`);IPC 不可达/超时回退读 TF 卡通知转储日志(`source:"tf"`,会话级归档)。响应额外带 `source` 与 `items` 别名
(与 `notifications` 同内容);`items` 为任务约定空态形状,`notifications` 为前端实际读取字段,二者并存。撤/清同样**优先走 IPC**联动
launcher 内存;`clear` 语义为「保留活动中的媒体/进度通知」(`ongoing||media||progress>=0`),对应通知中心既定语义。
8. **`/api/files/read` 文本截断**:协议 §3.4 称「仅文本(<1MB)→ 二进制 415」;现实现为「预览上限 1MB,超限 `truncated:true`,
不再整体 404」,二进制仍 415(比协议更友好,返回文件真实 `size` + `truncated`)。
9. **`/api/files/upload`/`rename` 修正**:上传文件名经 `safe_filename` 取 basename、拒绝 `..`/分隔符(防 `../../etc/passwd` 逃逸),
落盘前对 dir 与拼接后路径双重 `normalize_path`+白名单校验;rename 的新路径 = **原路径父目录** + `new_name`(原实现误拼到文件尾部,
恒 ENOTDIR 失败)。上传体上限 `MAX_UPLOAD_BODY`=8MB,超限 → 413 `PAYLOAD_TOO_LARGE`。
10. **TF 卡根可覆盖(宿主测试)**:`env MEOW_TF_ROOT` 覆盖卡根(设备上不设,恒用 `/usr/data/mnt/sd_0`);`env MEOW_NOTIF_LOG`
覆盖通知转储日志路径。设 `MEOW_TF_ROOT` 时 `tf_present()` 恒真(测试用)。
11. **实时画面/触摸注入(阶段 C)**:协议 §6 待定案项称「实时画面(`/api/frame` 等,本文件暂未定义)」。本面板落地为
**`GET /api/screen.jpeg` + `POST /api/input/tap`**(命名自定:`.jpeg` 表达二进制帧 + `input/tap` 表达触摸注入),
见 §5.10。回改协议时可补这两端点(以及 `frames` 语义、`/api/input/tap` 的 `dispatched:false/reason` 软失败约定)。
12. **multipart 二进制查找修正(strstr→memmem,本次修复,属 bug 修复非出入变更)**:`parse_multipart`/`multipart_extract_file`
均用 `strstr(data_start, boundary)` 定位 part 数据边界——**strstr 遇到数据中的 NUL 字节即提前停止**(含 NUL 的二进制
.tar.gz/.mpk/图会误把后续 boundary 与拖尾算进数据 → 解压出错)。已改**二进制安全**的 `mem_find`(memmem 语义,手写字节扫描,
不依赖 glibc/musl 扩展)。这修复了字体库 tar.gz 导入,也顺带修复了二进制 `.mpk`/文件上传的潜在同病。**实现优先**,供回改协议/审计。
13. **`/api/fonts*`(新增,§5.11)**:协议 §2 端点总览无此组;按 SPEC_REF §5.7.6「MeowPanel 预留 .zip 导入/导出」落地——**但用户
2026-08-31 定案:改 tar.gz、放弃 zip**(与 .mpk 同构)。落地为 `/api/fonts`(列表)/`/api/fonts/import`(导入,POST multipart,
上限 32MB,`?force=1` 覆盖)/`/api/fonts/export`(导出,GET,`<family>-<weight>.font.tar.gz`)。前两者由前端设置页「字体库」卡接入。
还修正了 `/api/files` 排序:**目录优先 + 名称自然排序**(数字感知 track2<track10,中文按无符号字节序,见 §5.4)。
**导入不触发 launcher 重扫**(扫描时机=启动/进设置页),响应用 `hint` 提示。
14. **`/api/audio`(新增,§5.12)**:协议 §2 无此端点。按 AUDIOD_DESIGN §8「MSG_AUDIO_STATE 供 MeowPanel 系统/音频页展示」落地——
连 audiod sock 发 `MSG_AUDIO_QUERY`(0x00C7)、收 `MSG_AUDIO_STATE`(0x00C8) k=v 文本解析为 JSON 透传;**audiod 无握手**、
不在时回 `{"available":false}`(200,非错误)。前端新增「音频」页(5s 轮询只读)。同批新增前端「字体」页(§5.13,
`/api/fonts*` 的专属页;设置页字体库卡保留)。
---
## 7. 已知边界 / 风险
- **单线程串行**:面板访问量极小,但并发请求会被排队(长上传/下载期间其它请求等待)。对面板用途可接受。
- **字体导入内存/体积上限**:`POST /api/fonts/import` 上传体上限 **32MB**(`MAX_FONT_UPLOAD`,仅此端点放宽;其余
multipart 仍 8MB);tar 解压到临时目录的总大小 ≤ `FONT_EXTRACT_MAX`=32MB(防 gzip 炸弹)。**全程不做整读进内存**
(写临时文件 + `gzip -dc | tar` 管道 + rename),峰值内存很小,不依赖 dma/font.png 大件缓冲——相比内存解压方案的 14MB 约束更稳健。
⚠️ 依赖设备 busybox `gzip`/`tar`(必带);若某内核裁剪过二者,导入/导出会失败(400/500),列表不受影响。
- **无鉴权(默认)**:设备内网信任模型(协议 §1.3)。**高危端点**(`reboot/shutdown/ota/apps/删除/上传)在默认关鉴权时无鉴权,
请勿把 8080 暴露到不可信网络。`meow_svc_power` 服务端做 `SO_PEERCRED uid==0` 基础校验(root 才受理动作),后端以 root 运行可过;launcher 电源菜单/后端同链路。
**可选鉴权**(§5.9)开启后可对内网作加固(登录密码 + 会话),但密码经**局域网明文 HTTP** 传输,不提供 TLS。
- **IPC 服务未起**:`meow_svc_perf`/`meow_svc_power` 为板载 `trigger=boot` 服务,正常恒在;若异常退出,读侧返回 500 `INTERNAL`,
动作侧返回 500/503,绝不静默。请勿拔除这两服务。
- **升级保护**:`POWER_UPGRADE` 由 `meow_svc_power` 在写 mtd5 前校验卡根 `r3proii.upt`(存在/可读/非空/size>0),失败不写 mtd5;期望 md5/hash 校验留二期。
- **文件/日志/应用均依赖 TF 卡**:无卡统一 503 `NO_CARD`(不阻塞系统核心)。`settings`/`system`/`perf`/`key` 无卡也可用(板载/rootfs)。
- **`/api/files/upload` 上传路径**:multipart 需要 `path` 字段(目标目录);缺失时上传文件会被忽略(前端总是带着 `path`)。
- **数据一致性**:设置直接写 `settings.conf`;`display.brightness`/`boot.default` 的 clamp/同步与 launcher `meow_settings_*` 同口径。
若 launcher 未来改 clamp 逻辑,需同步本表。
- **通知 IPC 路径为 launcher 内存活跃集(实时)**:优先 `MSG_NOTIFY_QUERY` 取活跃集;TF 归档日志仅在 launcher 不可达时作为
回退(此时才出现「会话级归档」语义——非 activity 通知 10s TTL 自动归档后仍留日志,会显示已过期历史直到 clear;媒体/进度/ongoing 常驻)。
IPC 响应为 launcher 的 `m_notify_query_result_t` 单帧文本(≤4KB),活跃通知超限时 launcher 侧 `meow_notif_serialize_json` 保前缀截断
(理论上最多 ~30 条,实际通知数远小,可接受)。
- **通知撤/清联动 launcher 内存**:面板 delete/dismiss_all **优先走 IPC**(launcher 撤下时自动归档留档);仅 IPC 不可达时回退重写 TF 日志。
- **增量帧依赖单线程基线**:`/api/screen.jpeg?delta=1` 用上一帧缓存比对,**面板为单线程 accept 循环**(跨请求持久);对**单面板用户**直接有效。
多客户端混用(一全帧+一增量、跨浏览器)时增量基线可能被另一客户端发的全帧刷新,前端「canvas 未播种则整帧取图」兜底自愈;极端并发下增量图
可能基于过期基线(可容忍,仅多发一帧整帧即校准)。子图为独立 JPEG(4:2:0 在子图内自采样),与整帧同源同质量,无拼接色差。
- **上传为「限长缓冲」非全流式**:multipart 请求体整体读入(上限 8MB,超限 413),下载为流式(8KB 分块)。严格「socket 逐字节流式上传
解析」未实现(需增量 multipart 解析器),列为后续;14MB 内存约束下 8MB 上限 + 流式下载已覆盖大文件读侧。
- **存储为 `meowpanel` 编译时刻写死**的 socket/路径会因 `--sock-perf/--sock-power`(qemu sim)可覆盖,但默认走 `/usr/data/meow_*.sock`。
- **触摸注入在设备息屏时不生效**:launcher 主界面在 `meow_screen_on==0` 时**不处理触摸**(main_menu.c,息屏防误触);设备自动息屏
(30s 空闲)后注入的 tap 会被 launcher 忽略——面板无法用触摸唤醒(需先电源键点亮)。前端「屏幕」页已提示「设备息屏请先点亮」;
对「正在看」设备的正常使用场景(屏幕亮着)注入即有效。
- **实时画面读取 fb0 当前帧(只读)**:双层缓冲下 `pread` 活动缓冲,存在翻页瞬间撕裂一行的可能(1fps 预览可接受);
`/dev/fb0` 不可读/非 16bpp → 500 `INTERNAL`(绝不静默)。
- **JPEG 编码器为 baseline 4:2:0**:质量 70 下 480×720 ≈ 24KB;质量越高体积越大(`q=100` 约 60-80KB)。编码为单线程
串行(面板本就单线程),一帧编码约几十 ms(MIPS 单核),1fps 轮询无压力。
- **触摸注入为即时性注入**:不维护长按/多指手势状态机(与按键注入一致);`dispatched:false`(本机触摸仲裁)属软失败,
前端可按 `reason` 提示,不视为错误。
---
## 8. 文件清单(本目录)
```
patch_v2/meowpanel/
├── meowpanel.c # 后端源码(约 4650 行,-Wall -Wextra 0 warning;含可选鉴权/内联 SHA-256/实时画面+增量帧/触摸注入/通知 IPC 实时化/字体库 tar.gz 导入导出/自然排序/audiod 状态快照)
├── mp_jpeg.c # 最小 baseline JPEG 编码器(顺序 DCT + 4:2:0 + 标准 Huffman,整数定点,无 libm,-Wall -Wextra 0 warning)
├── mp_jpeg.h # mp_jpeg.c 接口(RGB565 → 内存 JPEG)
├── build_meowpanel.sh # 交叉编译 + host 分支(-Wall -Wextra 0 warning;meowpanel.c + mp_jpeg.c)
├── meowpanel # 交叉产物(mipsel 静态,约 218 KB,md5 015f9cf1…)
├── meowpanel_host # 宿主编译(本机 ld,调试用)
├── services.d/
│ └── meowpanel.conf # 部署到 TF 卡 MeowOS/services.d/meowpanel.conf
├── web/ # 前端(已存在;部署时拷到后端同目录)
│ ├── index.html # 含登录页 overlay + 设置密码对话框 + 音频/字体导航
│ ├── meowpanel.css # 含登录页样式
│ └── meowpanel.js # API 客户端带 token/401 登录、设置页鉴权卡片、音频页(5s轮询)/字体页
└── README.md # 本文件
```