根因(承接 M12/M15 finding):SDK meow_listview 双行 renderer 把 block = 2*meow_ui_text_height(font_body) + MEOW_LISTV_SUB_GAP(2) 在行内垂直居中 (ty = 行顶 + (item - block)/2),标题只在 ty >= 视口顶时才画。目录行是双行档位, 真机 source_han_serif 20px(ascent 24 + descent 6)行高 30 → block 62,而 browser_page_create 显式 set_item_h(56) → ty = 行顶 - 3:首行(行顶 == 视口顶) 标题被裁剪守卫整条跳过,只剩大小/时间副标题。 修复: - files_internal.h:APP_ROW_H 降级为行高下限(56);新增 FILES_ROW_SUB_GAP(2,与 SDK MEOW_LISTV_SUB_GAP 同口径) + files_row_h_for_line_h(line_h) = max(56, 2*line_h + 2) + files_row_h()(取当前主题 meow_theme_get()->font_body 的 meow_ui_text_height)。本地派生,不依赖 M15 新增导出 meow_listview_item_h; 显式传值在 M15 之前的 SDK(默认 56)与 M15 之后(默认已派生 62)都成立且一致。 - ui/browser.c:browser_page_create 的 set_item_h 改传 files_row_h() (行高 30 → 62)。SDK 的 draw/hit/max_scroll 全部按这一个值换算,滚动/命中天然 同步;页脚几何(r_list 单独扣 APP_FOOTER_H)与行高无关。其它几何/交互不变。 验证(宿主,未碰真机): - zig-mipsel-cc -Os -static -Wall -Wextra(另用 -Werror 复验)交叉编译 files:0 warning; - 新宿主 test/test_row_text_fit.c(32 断言,纯头文件派生、不链 SDK): 字库行高矩阵 14/21/26/27/30 下 block <= item、首行 ty >= 0;行高 30 → 62; 旧 56 → ty=-3 的 bug 形态哨兵;全 PASS。 影响:meow_ui.a 未改 → 无需重编 SDK;本 App 需重链重部署(热更 files 二进制)。
108 lines
6.8 KiB
Markdown
108 lines
6.8 KiB
Markdown
# os.meow.files — MeowOS 外置文件管理器
|
||
|
||
TF 卡应用(`framework=1` 走 `meow_ui` SDK)。浏览 TF 卡目录树,支持进/退目录、列表虚拟化、
|
||
行操作(删除/重命名/新建目录/属性)、文本预览(≤256KB 截断)、空态/错误页;写操作严格限制在
|
||
TF 卡内(`/usr/data/mnt/sd_0`),拒绝 `..` 越界与卡外路径。
|
||
|
||
## 目录结构(多文件,批次②拆分范本)
|
||
|
||
```
|
||
patch_v2/files_app/
|
||
├── app.c # main + 装配 + 事件分发(key/tap/swipe/tick/touch → 当前页)
|
||
├── files_internal.h # 跨 TU 共享头(Activity id / 布局 / chrome/browser/viewer 声明)
|
||
├── build_files.sh # 交叉编译脚本(zig-mipsel-cc 静态链 meow_ui.a libz.a -lpthread)
|
||
├── app.json # {"name":"Files","exec":".../os.meow.files/files",...,"framework":1,"immersive":1}
|
||
├── README.md
|
||
├── ui/
|
||
│ ├── browser.c # 目录浏览页(ACT_BROWSER 根页):虚拟化列表 + 空态 + 行操作
|
||
│ ├── viewer.c # 文本预览页(ACT_VIEWER 子页):只读、可滚动、≤256KB 截断
|
||
│ └── common/
|
||
│ └── chrome.c # 共享 chrome:topbar 建置 / 内容区几何 / 空态&页脚绘制
|
||
├── logic/
|
||
│ ├── fsx.h # 纯业务接口(不依赖 meow_ui)
|
||
│ └── fsx.c # 路径安全/格式化/枚举排序/虚拟化区间/写操作(可宿主测)
|
||
└── test/
|
||
├── test_fsx.c # 宿主单元自测(gcc 直接编,指向临时卡根)
|
||
└── test_row_text_fit.c # 宿主几何断言:列表行高 ≥ 双行文本块(M17)
|
||
```
|
||
|
||
## 构建
|
||
|
||
```bash
|
||
cd patch_v2/files_app && ./build_files.sh
|
||
# 产物:files(MIPS32r2 静态 ELF,~270KB)+ ./files + app.json
|
||
```
|
||
|
||
构建脚本仿 `hello_app/build_hello.sh`:`zig-mipsel-cc -Os -static -Wall -Wextra -I../sdk -I.`
|
||
链 `../sdk/meow_ui.a "$SYSROOT/lib/libz.a" -lpthread`。本应用是 **§5.5 结构化列表(`meow_listview`)的
|
||
首个消费方**(`patch_v2/sdk/meow_ui_widget.c` 新增 `W_LISTVIEW`:图标+标题+副标题+右侧操作+选中);
|
||
`-Wall -Wextra` 0 warning。行滚动/选中/命中由 SDK 收口,App 只驱动 scroll/sel 并分发(行体=进目录/
|
||
开预览;右侧 `⋯` 或长按→BottomSheet)。
|
||
|
||
## 部署
|
||
|
||
```
|
||
/usr/data/mnt/sd_0/MeowAPPs/os.meow.files/
|
||
├── app.json
|
||
└── files
|
||
```
|
||
|
||
数据目录 `MeowData/os.meow.files/{data,cache}` 由 launcher 经 `MEOW_DATA_DIR`/`MEOW_CACHE_DIR` 注入;
|
||
v1 无需持久化,暂未使用(启动恒进卡根 `/usr/data/mnt/sd_0/`)。
|
||
|
||
## 宿主自测(不跨编/不刷机)
|
||
|
||
```bash
|
||
cd patch_v2/files_app
|
||
gcc -O2 -Wall -Wextra -DFSX_SD_ROOT='"/tmp/meowfsx_fsx_test"' -Ilogic \
|
||
test/test_fsx.c logic/fsx.c -o /tmp/test_fsx && /tmp/test_fsx
|
||
|
||
# 列表行高几何(无需 meow_ui.a;Termux 下产物落 $TMPDIR)
|
||
gcc -O2 -Wall -Wextra -I. -I../sdk test/test_row_text_fit.c \
|
||
-o "$TMPDIR/test_row_text_fit" && "$TMPDIR/test_row_text_fit"
|
||
```
|
||
|
||
覆盖:路径安全(`within_sd`/`join`/`sanitize`/`dirname`/`basename`)、展示格式化(`fmt_size`/`fmt_time`)、
|
||
目录枚举排序(`scan_dir`/`sort`)、虚拟化区间(`visible_range`/`max_scroll`)、写操作(`mkdir`/`rename`/
|
||
`delete`,含卡外/越界拒绝)、非文本写拒绝。宿主全 PASS。
|
||
|
||
`test_row_text_fit` 是 M17 行高回归:目录列表行恒为双行档位,行高必须 ≥ 文本块
|
||
`2*meow_ui_text_height(font_body) + 2`(真机 20px 字库行高 30 → 62),否则 SDK 居中后首行标题
|
||
`ty < 视口顶` 被裁剪守卫整条跳过。断言矩阵覆盖真机五种字库行高 + 旧固定 56 的 bug 形态。
|
||
|
||
## 交互
|
||
|
||
- 触摸:行 body=进目录/开文本预览;右侧 `⋯` 或长按→BottomSheet(删除[确认弹窗]/重命名[IME]/新建目录[IME]/属性[大小·mtime]);
|
||
TopAppBar `NEW`=当前目录新建。
|
||
- 左缘左滑/返回钮:预览页→回目录;浏览器→上级目录;卡根再左滑=退出 App。
|
||
- 物理键不映射(APPS.md §3.4.1):息屏/音量/媒体语义归系统/SDK。
|
||
|
||
## 已知限制 / SDK 能力说明
|
||
|
||
1. **结构化列表(`meow_listview`)已落地**:`ui/browser.c` 的自绘 custom 列表已迁移到 SDK `W_LISTVIEW`
|
||
(行数据源 `browser_src2` 填 `meow_list_item`)。图标(目录/文件内置符号)、名称、大小/时间副标题、
|
||
右侧 `⋯` 操作钮、虚拟化、空态/页脚、禁用行不响应均由 SDK 收口;`fsx_scan_dir` 仍以有界数组缓存
|
||
(`FSX_MAX_ENTRIES=1024`),列表虚拟化只画可见行——大目录不一次全读进内存。
|
||
- **long-press 已下沉 SDK(§6.3)**:SDK 通用长按判定「按下 ≥ 阈值(默认 500ms,可配)未抬手且
|
||
位移 ≤ 容忍」→ 经 `meow_ui_set_long_press_cb` 注册的回调带坐标交给本页;`browser_on_long_press`
|
||
用 `meow_listview_hit` 行命中打开 BottomSheet。App 层长按计时(`browser_tick`/`g_press_ms`)已删除,
|
||
其抬手 TAP 由 SDK 抑制(长按不再触发点击)。
|
||
- **行高按字体行高派生(M17)**:`files_row_h()` = `max(APP_ROW_H, 2*行高 + 2)`(当前主题正文
|
||
`font_body` 的 `meow_ui_text_height`)。目录行是双行档位,行高小于双行文本块时 SDK 居中后首行
|
||
标题落在视口顶之上被裁剪(20px 字库行高 30 → 块 62,旧的显式 56 首行名称空白)。滚动/命中/
|
||
最大滚动量由 SDK 按同一行高换算,页脚几何独立(`r_list` 单独扣 `APP_FOOTER_H`)。
|
||
2. **中文字形**:SDK 默认 `common` 字库(≈4k)不含 CJK 全量;exFAT 中文文件名/中文文本预览在 common 档会显示缺字豆腐。
|
||
预览页不声明 `fonts`(app.json 未加 `"fonts":"full"`/`"gbk"`),如需中文预览可在 app.json 声明大字号档。
|
||
3. **预览仅文本**:按扩展名白名单 + 首 512 字节 NUL 嗅探判文本;二进制/图片无预览(提示「not a text file」)。
|
||
超 256KB 只读前 256KB,页脚标注 `>256KB (truncated)`。
|
||
4. **符号链接不追链**:v1 视 symlink 为普通文件展示/操作(lstat),避免链出卡外。
|
||
5. **单目录条目上限**:>1024 项时只展示前 1024 并在页脚标注 `showing first 1024`(分页流式读目录为二期)。
|
||
|
||
## 验证状态
|
||
|
||
- 编译:`-Wall -Wextra` 0 warning/0 error,产物 `files`(MIPS32r2 静态 ELF)。
|
||
- 宿主:`test/test_fsx.c` 全 PASS;`test/test_row_text_fit.c` 32 断言全 PASS(M17 行高几何)。
|
||
- qemu-mipsel(无 /dev/fb0 → SDK 离屏 480×720):正常运行到事件循环,导出离屏帧,
|
||
确认 init/建页/topbar(返回+NEW)/空态(NO STORAGE CARD)渲染正确、SIGTERM 干净退出。
|
||
- 实机目录列表渲染(有真实 TF 卡目录)需刷机/设备挂载验证——宿主模拟无法造出 `/usr/data/mnt/sd_0`。
|