diff --git a/MEOW_APP_SDK.md b/MEOW_APP_SDK.md index 0abfb13..6b5c2f6 100644 --- a/MEOW_APP_SDK.md +++ b/MEOW_APP_SDK.md @@ -465,6 +465,35 @@ int meow_listview_set_empty_text(meow_widget *lv, const char *text); > T4 自绘、T5 焦点/fling 联动)+ 全量回归 widgets/dispatch/box/anim/scroll/ime/pinyin/overlay/notif/ > sdk_input_keys/p1p2p3 全 PASS;交叉 -Wall -Wextra 0 warning。 +#### 左滑删除(swipe-delete,M7.3 通知中心缺口 #9) + +> **定位**:给 `meow_listview` 的行加**横向左滑手势 + 删除/回弹动画**(配合行自绘 `set_item_draw`)。 +> SDK 在自绘行前把该行横向偏移 `swipe_dx` 传给 `x`,并画出右露的**删除区**(主题 `error`/`onError`); +> 手势由 App 在 `on_touch_move` 喂三级,**不修改** `tap_at`/滚动语义(`meow_ui_widgets.h` 追加)。 + +**手势判定表**(`swipe_begin/move/end` 三级,App 在触摸回调里喂): +- **横向** `|dx|>|dy|` 且超容差 = 滑动(`move` 返回 1,本列表消费——App 不要再去 `scroll_by`); +- **纵向** `|dy|>|dx|` 且超容差 = 滚动手势优先(返回 0,取消本行 swipe,App 照常滚动); +- **点按** 无位移(`end` 时未 started)= 行点击(返回 0,App 照常 `tap_at`)。 + +**动画**:超阈值 → 行滑出动画(`meow_anim`,§12.2),结束后回调 `on_delete(idx)`;未超 → 回弹。 +`swipe_offset` 返回当前滑动行的 `dx`(<0=左移),自绘行绘制时据此位移;`swipe_active`=1 表示有行处于滑动/动画中。 + +**API 一览(`include/meow_ui_widgets.h`,§5.5)**: +```c +typedef void (*meow_listview_swipe_fn)(int idx, void *ctx); +void meow_listview_set_swipe_delete(meow_widget *lv, int enabled, + meow_listview_swipe_fn on_delete, void *ctx); +int meow_listview_swipe_begin(meow_widget *lv, int x, int y); /* touch-down:1=命中可滑行行(喂后续 move) */ +int meow_listview_swipe_move(meow_widget *lv, int x, int y); /* touch-move:1=消费(横向);0=让位(取消/纵向) */ +int meow_listview_swipe_end(meow_widget *lv, int x, int y); /* release:1=消费(删/回弹动画);0=未滑动(tap) */ +int meow_listview_swipe_active(meow_widget *lv); /* 1=有行处于滑动/动画中 */ +int meow_listview_swipe_offset(meow_widget *lv); /* 当前滑动行的 dx(<0=左移;自绘行画用) */ +``` +> **兼容/内存**:纯手势 + 动画层(配合 `set_item_draw` 自绘),不新增常驻结构;删除动画走 §12.2 +> `meow_anim` 静态池(≤16),热路径零 malloc;与行点击/滚动互不冲突(见上判定表)。 +> **M7 用途**:通知中心(M7.3)通知条左滑删除(缺口 #9)。 + --- ### 5.6 设计模板 B③ 弹窗/对话框 [源:2026-08-30] @@ -865,6 +894,35 @@ meow_widget *meow_layout_spacer(int w, int h); **同步重编注意(关键)**:`meow_ui_theme` 追加字段 / `meow_widget` 结构变化 → **`meow_ui.a` ABI 变化** → 需按 §9/§10「**同步重编 hello**(App 也链 `meow_ui.a meow_font.a libz.a -lpthread`)」一并处理;`build_sdk.sh` 一键重编 + `hello_app/build_hello.sh` 重链。**B1 既有控件与模板零改动**(§5.8.1/§5.8.2 均向后兼容)。 +#### 5.8.9 枚举 / 预设选择器(`meow_select`,M7.2 设置页缺口 #4) + +> **定位**:给「枚举 / 预设选择器」一个开箱即用的**下拉模态**(`include/meow_select.h`,追加入 `meow_ui.h` 伞头)—— +> 采样率/码率/主题/语言等枚举、均衡器预设等。封装 = **BottomSheet(标题 + 选项列表)+ `meow_listview`(SINGLE) 单选列表**: +> 选项数组 + 当前选中(高亮)+ 点选项 = 选中 + 关闭 + `on_change` 回调;点面板外/左缘返回 = 取消。 +> 阻塞式(内层 `meow_input_pop` 轮询,与 `meow_dialog` 同构);打开前排空残留触摸。 +> 复用 `meow_dialog` 的遮罩/快照恢复(scrim 只叠一层、关闭无残影)。 + +**API(`include/meow_select.h`)**: +```c +typedef struct meow_select meow_select; +typedef void (*meow_select_fn)(int idx, void *ctx); /* on_change:idx=选中项下标(0..n-1) */ +#define MEOW_SELECT_MAX_ITEMS 32 /* 选项上限(静态数组;越界 add 忽略并返回 -1) */ + +/* 组件式(可复用 + on_change 回调) */ +meow_select *meow_select_create(const char *title, int cur); /* cur=初始选中项;越界 clamp */ +int meow_select_add_item(meow_select *s, const char *label); /* 返回项下标;满/无效= -1 */ +void meow_select_set_on_change(meow_select *s, meow_select_fn cb, void *ctx); +int meow_select_open(meow_select *s); /* 阻塞到确认/取消:选中→result=idx+on_change(idx)+返回 idx;取消→-1 */ +void meow_select_free(meow_select *s); + +/* 一次性(无回调,返回值即选择);opts=const char* 数组;n=个数;cur=初始选中 */ +int meow_select_show(const char *title, const char *const *opts, int n, int cur); +``` +- **内存纪律**:选项数组**定长**(≤`MEOW_SELECT_MAX_ITEMS`,栈/数组持有,非 malloc 长列表;越界 `add_item` 返回 -1); + 列表在 `open` 时创建(冷路径)、`close` 时释放;**open 循环零 malloc**。 +- **选择语义**:确认 → 置 `result` + 调 `on_change(idx)` + 返回 `idx`;取消/点面板外/左缘返回 → 返回 `-1`(不调 `on_change`)。 +- **M7 用途**:设置页(M7.2)采样率/码率/主题/语言等枚举选择(缺口 #4)。 + --- ### 5.9 音频客户端 API(`meow_audio_*`,M2.90 落地) @@ -985,6 +1043,11 @@ void meow_ui_global_remove(int id); - **SDK 提供系统组件 API 仅供系统进程链接调用**:SDK 保留系统级控件内部接口位(上面 ③);**用户 App 拿不到**、**不可自建悬浮层/全局叠加**——App 这类需求只能走 SDK 标准能力。 - **进程隔离(核心)**:SDK 负责**系统组件渲染**的进程(launcher)与负责 **APP 渲染**的进程**相互独立**——APP 卡死/崩溃时,系统 UI(launcher)**不崩**,始终可息屏/返回/下拉通知中心(对应 APPS §3.4.1 物理键全局接管、APP 禁映射)。 - **APP 侧职责**:只画自己的内容;经 IPC 上报**全屏声明**(`fullscreen`/`route..fullscreen`)、**内容区域**(inset)与**前台 activity**;系统 UI 据此做叠画 + 命中分流(§6.1 交互分层:③ 系统全局 → ② App 全局 → ① 当前 Activity 页面控件)。 +- **前台 activity / keep_awake(M2.17,`meow_ipc.h`)**:前台 App 经 IPC 向 launcher 上报当前顶层 activity + (`MSG_ACT_FOCUS=0x0033` 上报顶层 / `MSG_ACT_BACK=0x0034` 回到根·清 keep_awake,号以 `meow_ipc.h` 为准); + 系统侧 `fg_keep_awake()` 依注册表判断该 activity 是否声明 keep_awake(`app.json` 的 `awake.=1` 或 + `route..keep_awake=1`,运行时同类),决定是否**前台保活**(超时不自动息屏)。App 只上报 activity,不写保活判定 + (主界面无上报/空 = 非 keep_awake,照常超时息屏)。 - **App 前台划出通知中心(跨进程,[源:2026-08-30],M2.82 已实现)**:全屏 App 前台时顶部下滑——**App 进程 SDK 只检测手势 (`MEOW_EV_NOTIF_PULL`→`meow_ui_sys_notif_pull()`)并经 IPC 上报**(`MSG_NOTIF_OPEN=0x0028`/`MSG_NOTIF_CLOSE=0x0029`,号以 `meow_ipc.h` 为准),**不渲染系统 UI**; 通知中心由 launcher 渲染(SIGSTOP App+读回其帧+叠加 `run_notif_center`+接管输入+关闭恢复,面板内 App 崩溃即关), @@ -1452,7 +1515,73 @@ void meow_scroll_set_friction(meow_scroll *s, int tau_ms); /* 默认 180ms - **内存纪律**:静态池(应用侧按需建会话),track/fling/tick 全程零 malloc;无 fling 时 meow_scroll 不参与节拍(由 `meow_anim_tick` 活动计数保证零开销)。**不引 libm**(SDK 免 `-lm` 约定)。 -### 12.4 对既有 App 的零影响(迁移说明) +### 12.4 2 维虚拟化宫格(`meow_lazygrid`,M7.4 主界面前置) + +> **性质**:给「卡片/图标宫格」提供**二维虚拟化**(`include/meow_lazygrid.h`,追加入 `meow_ui.h` 伞头)—— +> 应用入口宫格、图标墙、设置快捷卡等。只实例化**可见行**(视口内),行高/列几何(`cols`+`cell_w/h`+`gap`); +> 滚动复用 §12.3 `meow_scroll`(fling/snap,与 listview 同款惯性);命中 `hit`/`tap_at` 把滚动+网格坐标映射回 `item idx`。 +> **非 `meow_widget`**:自包含控件(不注册 z 序/绘制注册表),由宿主(launcher session)在 draw 阶段调 +> `meow_lazygrid_draw`、命中阶段调 `hit`/`tap_at`、滚动喂 `meow_scroll`——与 `meow_scroll`/`meow_box` 同为**正交模块**, +> 不侵入 `struct meow_widget`(**全追加式**)。 + +**数据源 / 渲染**: +- 数据源:`count_fn()` 返回总项数(任意长),`bind_fn(index, item, ctx)` 仅当 item「进入视口」(绑定到固定池槽)时调一次; + 回滚命中同槽不重绑(同 listview 池语义);`count` 每帧实时查(支持动态长度/搜索过滤)。 +- 固定 item 池:池容量 = 视口行数 + 2(× cols),静态有界(≤`MEOW_LAZYGRID_POOL_MAX`=96)。 +- 渲染:默认 renderer 画「圆角卡片(icon+名+状态)」;App 也可经 `set_item_draw` 完全自绘 + (主界面卡片宫格用:icon+名+后台标注)。支持选中高亮/禁用变淡/空态占位。 + +**API(`include/meow_lazygrid.h`)**: +```c +typedef struct meow_lazygrid meow_lazygrid; +typedef struct meow_lazygrid_item { + const char *title, *subtitle; /* 主/副文本(subtitle 可空);title 必填 */ + const meow_img *icon; /* 格内图标(NULL=无) */ + int enabled; /* 1=可交互;0=变淡不响应 */ + void *user; /* App 私有(跨 bind 保存业务态;SDK 不碰) */ +} meow_lazygrid_item; +typedef int (*meow_lazygrid_count_fn)(void *ctx); +typedef void (*meow_lazygrid_bind_fn)(int index, meow_lazygrid_item *item, void *ctx); +typedef void (*meow_lazygrid_draw_fn)(int index, int x, int y, int w, int h, int state, void *ctx); +typedef void (*meow_lazygrid_tap_fn)(int idx, void *ctx); +#define MEOW_LAZYGRID_POOL_MAX 96 + +/* 生命周期(冷路径 create/free;热路径 draw/hit/scroll 零 malloc) */ +meow_lazygrid *meow_lazygrid_create(int x, int y, int w, int h, int cols, int cell_w, int cell_h, int gap); +void meow_lazygrid_free(meow_lazygrid *g); +void meow_lazygrid_set_rect(meow_lazygrid *g, int x, int y, int w, int h); /* 视口矩形(绝对坐标) */ +void meow_lazygrid_set_cols(meow_lazygrid *g, int cols); +void meow_lazygrid_set_cell(meow_lazygrid *g, int cell_w, int cell_h, int gap); + +/* 数据源 + 渲染 */ +int meow_lazygrid_set_vsource(meow_lazygrid *g, meow_lazygrid_count_fn count, + meow_lazygrid_bind_fn bind, void *ctx); +int meow_lazygrid_set_item_draw(meow_lazygrid *g, meow_lazygrid_draw_fn draw, void *ctx); /* NULL=默认卡片 */ +int meow_lazygrid_set_empty_text(meow_lazygrid *g, const char *text); + +/* 滚动(像素级;可视行数按 offset 实时推) */ +void meow_lazygrid_set_scroll(meow_lazygrid *g, int off); +void meow_lazygrid_scroll_by(meow_lazygrid *g, int dy); +int meow_lazygrid_scroll(meow_lazygrid *g); +int meow_lazygrid_max_scroll(meow_lazygrid *g); /* max(0, 内容高 - 视口高) */ +int meow_lazygrid_row_h(const meow_lazygrid *g); /* 行高(=cell_h+gap) */ +meow_scroll *meow_lazygrid_scroll_attach(meow_lazygrid *g); /* 包一个 meow_scroll 会话;NULL=池满 */ + +/* 选中 / 命中 */ +void meow_lazygrid_set_on_tap(meow_lazygrid *g, meow_lazygrid_tap_fn cb, void *ctx); +void meow_lazygrid_set_sel(meow_lazygrid *g, int idx); /* 越界 clamp;<0 清除;标脏 */ +int meow_lazygrid_sel(const meow_lazygrid *g); +int meow_lazygrid_hit(meow_lazygrid *g, int x, int y); /* 命中项 idx(滚动+网格坐标映射);-1=未命中/禁用 */ +int meow_lazygrid_tap_at(meow_lazygrid *g, int x, int y);/* 命中:置选中+回调+返回 idx/-1 */ + +/* 绘制 */ +void meow_lazygrid_draw(meow_lazygrid *g, const meow_theme *th); +``` +- **内存纪律**:静态有界 item 池(冷路径 create/set_vsource 分配,热路径 draw/hit/scroll **零 malloc**),与 + listview 池同源;不常驻 count 个 item 对象。 +- **M7 用途**:主界面(M7.4)应用入口/图标墙**虚拟化宫格**(并行+回退位)。 + +### 12.5 对既有 App 的零影响(迁移说明) - **追加式**:三块全为新 API;既有 `meow_row/column/box`(§5.8.5)与全部 `meow_*_create/hit/scroll` 签名一行未改。 - **旧绝对坐标永远有效**:box 只是把坐标算进 `meow_widget`(`meow_widget_set_rect`);不用 box 的 App 照旧 `on_layout`/绝对定位。 @@ -1516,7 +1645,78 @@ locale 权威值 = 板载 `settings.conf` 的 `ui.locale`(默认 `zh_cn`)。 --- -## 14. 遗留 / 待实现 +## 14. SDK 系统侧通道(网络自愿 / 磁盘缓存治理) + +> **性质**:App 与系统治理模块(netmgrd / svc_perf)之间的**自愿自治**接口——不强制(防君子不防小人), +> best-effort,失败静默不影响业务。`include/meow_ui_net.h` 与 `include/meow_cachegov.h` 均**追加入 `meow_ui.h` 伞头**。 + +### 14.1 网络自愿通道(`meow_ui_net`,netmgrd 期4) + +> **性质**:框架 App 联网前的**自愿策略自查**通道 + **自愿字节上报**。权威方案 +> `devdocs/plan/NETMGR_DESIGN.md` §6.3/§4(内核无 per-socket/per-uid 字节计数器,§4 的「字节级精确记账走 SDK 自报通道」由此落地)。 + +**API(`include/meow_ui_net.h`)**: +```c +/* 检查 host 是否被策略放行。0=allow(可联网);1=block(策略拒绝,App 应中止本次联网)。 + * host 为域名(如流媒体客户端 API 主机名);判定以当前 App(MEOW_LOG_APP)为实体,服务端策略引擎权威执行。 + * netmgrd 不在 / 超时 → 返回 0(乐观放行,绝不因监控不可用而误封业务)。 */ +int meow_net_policy_check(const char *host); + +/* 上报本进程累计收发字节(best-effort,失败静默)。netmgrd 记入 per-实体统计(status JSON `entity_bytes`)。 */ +void meow_net_report(uint32_t rx, uint32_t tx); +``` +- **用法**:App 在发起 HTTP/TLS 前调 `meow_net_policy_check(host)`;返回 1 则按产品语义处理(如提示被限)。 +- **代价**:单查询/单上报,无常驻状态,失败静默不影响业务;热路径零 malloc。 +- **用途**:框架 App(阅读器/流媒体客户端/文件等)联网前自查 + 自愿上报流量(netmgrd 期4 收口)。 + +### 14.2 磁盘缓存治理(`meow_cachegov`,OOM page-cache 治理 A 侧) + +> **性质**:磁盘缓存 **page-cache** 治理 SDK 库。归属(`devdocs/plan/ARCHITECTURE_PRINCIPLES.md` ③混合): +> **A(DONTNEED 热路径,需要 read_pos)** 随 SDK 库(进程内直读 read_pos,无 IPC);**C(设备级 +> `/proc/meminfo` Cached 监控降载)** 归系统服务 `meow_svc_perf`,本模块只读其共享状态文件 +> (`MEOW_CACHEGOV_C_FILE`)作为「降载」信号;**App(如流媒体客户端)侧只调本库接口**:登记文件 + read_pos +> 回调 + tick + drop_finished + 查询降载,不写自己的治理算法。权威方案 `devdocs/plan/OOM_PAGECACHE_GOVERNANCE.md`。 + +**分级(§2.2,红线 L2)**: +- **L0(默认,安全)**:曲终/commit 后对整文件 `posix_fadvise(DONTNEED)`——播放器已退出解码循环。 +- **L1(可选,主杠杆)**:播放中丢已读前缀 `[0, read_pos-keep_hot)`,保 `keep_hot` 窗口供倒带; + 仅 `drop_mode>=1` 且 A 未回退到 ≥2 时执行。 +- **L2(禁止,红线)**:绝不丢 read_pos 前方/当前未解码页。 +- **underrun 回退(§2.3,SDK 内置)**:App 在 pread 慢(数据在盘上但 >threshold ms)处上报 + `meow_cachegov_note_refault()`;本库滚窗 10s,超门限逐级回退(放大 keep_hot → 降 L0 → 关 A)。 + +**API(`include/meow_cachegov.h`)**: +```c +typedef struct meow_cachegov_cfg { + int enabled; /* 总开关:0=A 完全不丢页(register/tick/drop_finished 全部 no-op) */ + int cached_threshold_kb; /* 保留:C 侧阈值归 svc_perf(本库恒 0,只读不控,防误用) */ + int dirty_warn_kb; /* 保留:同上(svc_perf 用;本库只透传日志) */ + size_t keep_hot_bytes; /* L1 保留窗口:read_pos 之后保多少不丢(回退时自动放大) */ + size_t min_hot_ahead; /* L2 红线:read_pos 前方至少保多少——仅防御注释,绝不参与 drop */ + int drop_mode; /* 0=仅曲终(L0);1=曲终+播放中丢已读前缀(L0+L1,配 seek 回退) */ + int refault_backoff; /* 10s 窗口内 refault 触发回退的门限(次数) */ +} meow_cachegov_cfg_t; + +int meow_cachegov_register(const char *path, uint64_t size, + uint64_t (*read_pos_fn)(void *ctx), void *ctx); /* 登记缓存文件;重复 path 幂等 */ +int meow_cachegov_unregister(const char *path); /* 注销(曲终/切歌后清理);找到并删/不在册 */ +int meow_cachegov_tick(void); /* 周期调用:①按回退档丢已读前缀 ②评估 refault 滚窗回退 */ +int meow_cachegov_drop_finished(const char *path); /* 曲终/commit:整文件 L0 DONTNEED + 注销条目 */ +int meow_cachegov_download_paused(void); /* 1=应暂停(A 回退硬暂停 OR svc_perf C 降载) */ +int meow_cachegov_prefetch_paused(void); +int meow_cachegov_set_cfg(const meow_cachegov_cfg_t *cfg); +int meow_cachegov_get_cfg(meow_cachegov_cfg_t *cfg); +void meow_cachegov_note_refault(void); /* pread 慢处上报一次 refault(线程安全) */ +int meow_cachegov_backoff_level(void); /* 0=正常 1=keep_hot 放大 2=降 L0 3=A 关 */ +``` +- **内存/代价**:自含(pthread + meow_log),热路径零 malloc;`set_cfg` 运行时覆盖编译期默认。 +- **红线**:`min_hot_ahead` 只是防御注释、**绝不参与 drop**;本库无论何档都不丢 `[read_pos, ...)` 任何未解码区。 +- **M7 用途**:流媒体客户端(dev.player.ncm)缓存文件治理(登记缓存 + read_pos + tick + 曲终 `drop_finished`); + 与 `meow_svc_perf` 的 C 侧降载协同,治 OOM 的 page-cache 驻留。 + +--- + +## 15. 遗留 / 待实现 1. `meow_ui` 渲染后端需从 launcher 里**抽公共绘制实现**(`back_buf`/`meow_dirty_*`/`meow_present`/`meow_font_bind` 目前是 launcher 静态函数——需提取为可链接的 `meow_ui_gfx` 或改为共享 obj,注意 launcher 与 app 是**不同进程**,不能共享同一 back_buf;`meow_ui.a` 是**独立库**,内部自建 fb 上下文,不复用 launcher 的全局 `g_dirty`)。 2. 输入线程优先级:vs 主循环 10ms 热键轮询的竞态,需在 B1 实现时定义清楚(应用进程内独立事件循环,不与 launcher 抢)。 diff --git a/README.md b/README.md index 744b162..2866080 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,16 @@ > 把 HiBy R3 Pro II 便携播放器(君正 X1600,MIPS32r2 / mipsel / musl 静态软浮点,480×720,无 GPU 软渲染)变成一个可扩展的「第二系统 + 应用框架」。 > MeowOS 与官方 HibyOS 共存于同一台设备:开机由引导选单二选一;启动后 MeowOS 作为主控层,提供自己的桌面 UI、应用框架、服务管理与系统组件。 > -> 版本:**M6.6(0.8.6,已自刷验收)**(M6.1→M6.6 全链自刷验收——**M6.1**:netmgrd 期3/powermgr 期2/面板 PIN 鉴权+首页仪表盘/WiFi 录入 UI/服务名统一 meow_*;**M6.2**:锁屏 PIN 全量(`system.pin` 同源面板)+VOL_DOWN×3 框架门控+显示名 name[49](amsd/launcher UTF-8 整序列截断);**M6.3**:设置页 5s 修复(309 网络异步步骤机+跳过字体 sha 校验)+310 锁屏三连(输入穿透/解锁状态栏消失/App 死亡残影);**M6.4**:310 二期 **EVIOCGRAB 锁屏独占触控**(未冻结 App 锁屏输入隔离)+主界面应用名截断;**M6.5**:audiod 315 孤儿会话两段式回收(客户端崩溃/OOM 杀/正常退出后会话完整回收,STATE 回干净无流基线);**M6.6**:锁屏 310 三期——App 内手动息屏也触发锁屏 + 锁屏脏区全屏不透明底 + 300ms 重铺;历史:M2.69→M2.96 全链路自刷验收:控件库/主界面 MD3 双布局/M3 导航栈/M5 通知跳转/媒体会话/Now Playing/SDK nav+media 封装/字库启动竞态修复**/M2.84-88**:installed 图标修复+残帧pan加固/物理键EVIOCGKEY双源/签名鉴权 2B/meow_route 音频路由/amsd 二期(picker 契约)/流媒体客户端二期(封面·歌词后台·大歌单分页)/MeowPanel 实时画面+遥控+通知实时化/中文拼音 IME**/M2.89**:开机选单抢屏回归修复+watchdog SIGUSR1 不误杀框架 App+amsd 失联修复+顶部下滑手势分流**/M2.90**:**meow_audiod 独占音频守护**(AUDIOD_DESIGN,meow_route 并入退役)+MIUIx 顶级主题+流媒体客户端文案中文化+面板字体库 tar.gz 导入导出**/M2.91-96**:audiod 开机武装 hbc3000 FPGA(根治 PCM 启动内核死锁)+svc_power 充电态权威链(mp2731)+amsd 注册自愈(`am rescan`)/Now Playing 死循环修复+状态栏充电闪电+插孔消抖(三路)/audiod 长流护栏+ALSA 几何收窄 period=1024×4(根治长流停摆)+watchdog 音量存盘毁设置修复/音量反转四路统一(CS43198 寄存器=衰减量,`dac=255-pct*255/100`)+炸耳安全修复(dac_vol_mute 写 255 真静音);**M6.0-6.1**:网络管理服务 **meow_netmgrd** 期1-3(接口统一层+连接普查三实体+DNS 代理+策略引擎防自锁+WiFi 录入 UI)+功耗管理服务 **meow_powermgr** 期0-2(查看/功耗分解 top/历史/事件/mp2731 实测充电功率)+MeowPanel 增强(4 位 PIN 鉴权+首页仪表盘/电源页/进程服务页)+服务名统一 meow_*(audiod/mdnsd→meow_audiod/meow_mdns))。版本号单点 = 板上 `/etc/meowos_version`,见「构建」。本仓库是一个**自带 SDK、包管理、媒体会话、独占音频服务、网页面板、流媒体客户端的完整第三方系统**。 +> 版本:**M6.9(0.8.9,已自刷验收)**;**M6.10 打包中**(launcher `9302913b`+svc_perf `65f2059f`+crash `f02c4f45`,fw_content 已收编未构建)。M6.1→M6.9 全链自刷验收——**M6.1**:netmgrd 期3/powermgr 期2/面板 PIN 鉴权+首页仪表盘/WiFi 录入 UI/服务名统一 meow_*;**M6.2**:锁屏 PIN 全量(`system.pin` 同源面板)+VOL_DOWN×3 框架门控+显示名 name[49](amsd/launcher UTF-8 整序列截断);**M6.3**:设置页 5s 修复(309 网络异步步骤机+跳过字体 sha 校验)+310 锁屏三连(输入穿透/解锁状态栏消失/App 死亡残影);**M6.4**:310 二期 **EVIOCGRAB 锁屏独占触控**(未冻结 App 锁屏输入隔离)+主界面应用名截断;**M6.5**:audiod 315 孤儿会话两段式回收(客户端崩溃/OOM 杀/正常退出后会话完整回收,STATE 回干净无流基线);**M6.6**:锁屏 310 三期——App 内手动息屏也触发锁屏 + 锁屏脏区全屏不透明底 + 300ms 重铺;**M6.7**:锁屏页自睡(空闲超时自动灭屏,294 遗留补;0.8.7 补刷吸收 fw_content 事故修复——拆分 watchdog+meow_audio 两行);**M6.8**:310 四轮 锁屏自睡 min(系统息屏,30s);**M6.9**:netmgrd 期4(SDK 自愿通道/forget/RSSI/AAAA 过滤/iptables 评估)+powermgr 期3(policy/标定工具)固化 + 327 M7.0b launcher 试点(电源菜单/Now Playing 控件化+主题薄壳+i18n 抽串);历史:M2.69→M2.96 全链路自刷验收:控件库/主界面 MD3 双布局/M3 导航栈/M5 通知跳转/媒体会话/Now Playing/SDK nav+media 封装/字库启动竞态修复**/M2.84-88**:installed 图标修复+残帧pan加固/物理键EVIOCGKEY双源/签名鉴权 2B/meow_route 音频路由/amsd 二期(picker 契约)/流媒体客户端二期(封面·歌词后台·大歌单分页)/MeowPanel 实时画面+遥控+通知实时化/中文拼音 IME**/M2.89**:开机选单抢屏回归修复+watchdog SIGUSR1 不误杀框架 App+amsd 失联修复+顶部下滑手势分流**/M2.90**:**meow_audiod 独占音频守护**(AUDIOD_DESIGN,meow_route 并入退役)+MIUIx 顶级主题+流媒体客户端文案中文化+面板字体库 tar.gz 导入导出**/M2.91-96**:audiod 开机武装 hbc3000 FPGA(根治 PCM 启动内核死锁)+svc_power 充电态权威链(mp2731)+amsd 注册自愈(`am rescan`)/Now Playing 死循环修复+状态栏充电闪电+插孔消抖(三路)/audiod 长流护栏+ALSA 几何收窄 period=1024×4(根治长流停摆)+watchdog 音量存盘毁设置修复/音量反转四路统一(CS43198 寄存器=衰减量,`dac=255-pct*255/100`)+炸耳安全修复(dac_vol_mute 写 255 真静音);**M6.0-6.1**:网络管理服务 **meow_netmgrd** 期1-3(接口统一层+连接普查三实体+DNS 代理+策略引擎防自锁+WiFi 录入 UI)+功耗管理服务 **meow_powermgr** 期0-2(查看/功耗分解 top/历史/事件/mp2731 实测充电功率)+MeowPanel 增强(4 位 PIN 鉴权+首页仪表盘/电源页/进程服务页)+服务名统一 meow_*(audiod/mdnsd→meow_audiod/meow_mdns))。版本号单点 = 板上 `/etc/meowos_version`,见「构建」。本仓库是一个**自带 SDK、包管理、媒体会话、独占音频服务、网页面板、流媒体客户端的完整第三方系统**。 > > 许可证:Apache-2.0(见 [`LICENSE`](LICENSE))。本仓库为非官方第三方项目。 +> 主仓**公开可克隆**:`https://git.yeij.top/AskaEth/MeowOS-For-R3P2.git`(维护者推送走 +> `ssh://git@git.yeij.top:2222/AskaEth/MeowOS-For-R3P2.git`)。 +> **第三方组件声明(LGPL)**:阅读器 `os.meow.reader` 静态链接 **libmobi(LGPL-3.0)**。 +> 其 NOTICE + GPL-3.0 + LGPL-3.0 全文随 `.mpk` 的 `resources/licenses/` 分发(见 +> `patch_v2/reader_app/NOTICE`、`patch_v2/reader_app/resources/licenses/`);libmobi 在 +> `build_reader.sh` 锁定 commit `906274205c11944b628da1c553b255acb1af7c55`;替换/重链指引见 +> `patch_v2/reader_app/README_LGPL_RELINK.md`。阅读器自有代码仍为 Apache-2.0(LGPL §1 豁免)。 > > **公开 SDK 仓(面向第三方开发者)**:`ssh://git@git.yeij.top:2222/AskaEth/MeowOS-SDK.git`(MIT;只含 `sdk/`(meow_ui)+ `examples/hello/` + `docs/`,自包含可构建,不含系统组件/密钥)。由本仓经 `git subtree split` 拆出,增量同步见 `scripts/publish_sdk.sh`。 @@ -19,7 +26,7 @@ - **应用框架**:一套 C 应用 SDK(`meow_ui`),第三方 App 用统一控件 / 输入 / 主题 / 字体 / 通知 API 开发,无需自读帧缓冲、自读输入、自管返回——把每个 App 都要踩一遍的坑收口到框架里。 - **系统组件**:状态栏、通知中心、电源菜单、全局左缘返回手势、主界面(应用列表,**MD3/MIUIx 顶级主题×明暗**,卡片宫格/列表双布局)、设置页、服务管理器、mDNS 广播;系统进程总监 `meow_sysui`;专项子服务 `meow_svc_perf`(性能/系统只读聚合,含真实 network)与 `meow_svc_power`(关机/重启/升级/回 HiBy,含升级写保护 + dac 真实音量 + 读级鉴权放宽);**独占音频守护 `meow_audiod`**(M2.90:App 经 SDK `meow_audio_*` 喂 PCM,守护独占 `hw:0,0`——DAC 原生率跟随无 SRC、格式归一、4.4/3.5 路由自动跟随插孔、抢占+EOF 回收、CLI status/tone/route/vol,规范 `devdocs/plan/AUDIOD_DESIGN.md`);系统级崩溃捕获 `meow_crash` + App 崩溃直报;.mpk **Ed25519 签名验签** `meow_verify`(见 §2.9);**网络管理服务 `meow_netmgrd`**(M6.0 期1 / M6.1 期3:接口统一层/连接普查三实体/DNS 代理/策略引擎防自锁,HibyOS 只读)与**功耗管理服务 `meow_powermgr`**(M6.0 期1 / M6.1 期2:功耗查看/分解/历史/事件,HibyOS 只读);MeowPanel 外部管理面板(HTTP/JSON 后端 + Web 前端,支持可选密码鉴权 + 4 位 PIN 访问级鉴权 + 首页仪表盘 + 字体库 tar.gz 导入导出,见 §2.10)。 - **媒体会话 / Now Playing**:`MSG_META`/`MSG_PLAYCTL`(0x72/0x73)媒体会话(最近活跃源,30s 超时回收)+ 通知中心**媒体卡片** + 主界面左滑 **Now Playing 大页**;媒体键路由到最近活跃媒体会话。 -- **应用生态**:**流媒体客户端 dev.player.ncm**(`core/`:mbedtls+jsmn+qrcodegen+minimp3 ≈438KB;复古 UI:登录二维码/我创建的·我收藏的双页签/搜索/歌单详情/黑胶播放页/设置,登录 803 已通;二期:专辑封面/歌词后台加载/大歌单分页;三期:歌单真分页/真实专辑封面/歌词锁修复/搜索接拼音 IME 中文搜索/设置页(音质 5 档+缓存上限+清理缓存)/缓存 LRU 淘汰接生产路径/文案全中文;三期续:歌曲对象瘦身 1324B→608B+256 首滑动窗口(任意长度歌单)/封面与缓冲 UX 修复(消除 worker 忙等 100% CPU)/FLAC 无损(dr_flac)+缓存键=歌曲 ID+音质档位/物理播放键 toggle 全链路;2026-09-02 批次:真机四连症状(豆腐/歌词一行/封面加载/音量键 SIGKILL)+返回首页竞态 staging+内存瘦身(封面 8.28MB→0.78MB)+流媒体专项六项(档位保真/封面 4xx 不重试/收藏显隐/缓存命名迁移/topbar 位图图标)+三死因闭环(OOM/误杀/歌词栈溢出)+封面链分诊+风控加固(接口 120s 缓存+游客令牌))。**A2 起音频输出走 `meow_audiod`**(SINK_AUDIOD 默认后端,SDK `meow_audio_*` 客户端,被抢占回调→暂停;旧 ALSA 直开仅 `MEOW_AUDIO_DIRECT=1` 调试);外置**系统文件管理器 os.meow.files**;`meow_ui` SDK 配套结构化列表 `W_LISTVIEW`、通用长按、SDK nav/media 封装、视图框架(可嵌套 view + 事件路由 + 绘制期 clip);App 通知查询/清除 IPC(0x25-27)与**点击通知跳转路由**(route_status)。 +- **应用生态**:**流媒体客户端 dev.player.ncm**(`core/`:mbedtls+jsmn+qrcodegen+minimp3 ≈438KB;复古 UI:登录二维码/我创建的·我收藏的双页签/搜索/歌单详情/黑胶播放页/设置,登录 803 已通;二期:专辑封面/歌词后台加载/大歌单分页;三期:歌单真分页/真实专辑封面/歌词锁修复/搜索接拼音 IME 中文搜索/设置页(音质 5 档+缓存上限+清理缓存)/缓存 LRU 淘汰接生产路径/文案全中文;三期续:歌曲对象瘦身 1324B→608B+256 首滑动窗口(任意长度歌单)/封面与缓冲 UX 修复(消除 worker 忙等 100% CPU)/FLAC 无损(dr_flac)+缓存键=歌曲 ID+音质档位/物理播放键 toggle 全链路;2026-09-02 批次:真机四连症状(豆腐/歌词一行/封面加载/音量键 SIGKILL)+返回首页竞态 staging+内存瘦身(封面 8.28MB→0.78MB)+流媒体专项六项(档位保真/封面 4xx 不重试/收藏显隐/缓存命名迁移/topbar 位图图标)+三死因闭环(OOM/误杀/歌词栈溢出)+封面链分诊+风控加固(接口 120s 缓存+游客令牌)+**M6.10 批次**(批B UI/批C 播放后台/338 VIP3 档+多歌手/336 根因——每次 audio_out_open 建短连接致 audiod fd 风暴已修;卡上热更 `bef4b0e1`,内存 40→49.2MB OOM 疑 page cache 待真机取证))。**A2 起音频输出走 `meow_audiod`**(SINK_AUDIOD 默认后端,SDK `meow_audio_*` 客户端,被抢占回调→暂停;旧 ALSA 直开仅 `MEOW_AUDIO_DIRECT=1` 调试);外置**系统文件管理器 os.meow.files**;`meow_ui` SDK 配套结构化列表 `W_LISTVIEW`、通用长按、SDK nav/media 封装、视图框架(可嵌套 view + 事件路由 + 绘制期 clip);App 通知查询/清除 IPC(0x25-27)与**点击通知跳转路由**(route_status)。 - **进程隔离**:系统 UI 渲染进程与 App 进程相互独立,App 卡死不会冻结整个 UI。 - **包管理**:`nyai`(固件内置 `/usr/bin/nyai`)支持 .mpk 安装/升级/导出/列表/索引,走 **version_code 安卓式版本规则**(缺 code 拒装、防降级)+ 签名分级(有签必验、`os.meow.*` 无签拒装、第三方直装告警)+ 导出过滤与体检。 diff --git a/docs/MEOWOS_SPEC_REF.md b/docs/MEOWOS_SPEC_REF.md index ced3ceb..b672e21 100644 --- a/docs/MEOWOS_SPEC_REF.md +++ b/docs/MEOWOS_SPEC_REF.md @@ -14,7 +14,7 @@ - `/etc/meowos_version`:版本唯一来源(launcher 运行时 `load_meowos_version` 读;`MEOWOS_VERSION` 宏仅作 dev 占位兜底)`[DEV_NOTES #4]` - **`/data/meow_boot_default`:开机默认权威标记(O_TRUNC 单值:`0`=MeowOS / `1`=HiByOS)**。由 launcher `meow_settings_save()` 在保存 `boot.default` 时同步写;bootmenu `set_boot_default_meow()`(写 0)/ launcher `set_boot_default_hiby()`(写 1)同文件同语义;bootmenu 开机读它定默认;init 读入不触发、不反向覆盖;`meow.conf` 不再承担 boot_default `[M2.61 同步修复][README][meow_settings.c][meow_bootmenu.c]` - `/tmp`:tmpfs,开机清空;开机模式标记 `/tmp/meow_boot_mode`、电源菜单 `/tmp/meow_power_menu{,_active}`、app 沉浸 `/tmp/meowapp_state`、**锁屏** `/tmp/meow_pin_unlock_once`(一次性解锁恢复口,launcher 锁屏页消费放行一次、重启自消)/`/tmp/meow_pin_locked`(电源菜单「主动锁」标志,重启消失)`[BOOT_SELECTOR §1][KEY_CONTROL §9][APPS §3.5][WORK_STATE 2026-09-02]` -- **锁屏 PIN(M6.2 起,与面板同源 `system.pin`,规范 `SYSTEM_SETTINGS` 锁屏小节)**:设置键 `system.pin`=`:`(sha256+salt,格式与面板 `panel.pin` 全等,**绝无明文**;空=未设=锁屏关;`panel.pin` 已设时锁屏接受并首次验证成功后并入本键)+`system.pin_lock_on_wake`(息屏唤醒后锁屏,int 0/1,默认 1=开;开机不锁)+`system.pin_lock_until`(错 PIN 锁定的 epoch 截止戳,持久化防 launcher 重启清零)+`system.pin_fails`(连续错 PIN 计数 0-4,达 5 触发锁定并清零,持久化)。清/改:设置「安全→锁屏 PIN」页——设/改需旧 PIN 一次、**清除免旧 PIN**(物理持机即授权,仅二次确认)、`system.pin_lock_on_wake` 开关;**兜底**:SSH root 删 `system.pin` 键即解锁(无其它后门)。**锁屏模态演进**:M6.4 §310 二期 **EVIOCGRAB 独占触控**(锁屏层独占 grab,下层 App 完全收不到锁屏触摸)+主界面应用名截断;M6.6 §310 三期——**App 内手动息屏也触发锁屏** + **锁屏脏区全屏不透明底** + **300ms 重铺**。`[SYSTEM_SETTINGS §1][WORK_STATE 2026-09-02]` +- **锁屏 PIN(M6.2 起,与面板同源 `system.pin`,规范 `SYSTEM_SETTINGS` 锁屏小节)**:设置键 `system.pin`=`:`(sha256+salt,格式与面板 `panel.pin` 全等,**绝无明文**;空=未设=锁屏关;`panel.pin` 已设时锁屏接受并首次验证成功后并入本键)+`system.pin_lock_on_wake`(息屏唤醒后锁屏,int 0/1,默认 1=开;开机不锁)+`system.pin_lock_until`(错 PIN 锁定的 epoch 截止戳,持久化防 launcher 重启清零)+`system.pin_fails`(连续错 PIN 计数 0-4,达 5 触发锁定并清零,持久化)。清/改:设置「安全→锁屏 PIN」页——设/改需旧 PIN 一次、**清除免旧 PIN**(物理持机即授权,仅二次确认)、`system.pin_lock_on_wake` 开关;**兜底**:SSH root 删 `system.pin` 键即解锁(无其它后门)。**锁屏模态演进**:M6.4 §310 二期 **EVIOCGRAB 独占触控**(锁屏层独占 grab,下层 App 完全收不到锁屏触摸)+主界面应用名截断;M6.6 §310 三期——**App 内手动息屏也触发锁屏** + **锁屏脏区全屏不透明底** + **300ms 重铺**;M6.7 锁屏页自睡(空闲超时自动灭屏);M6.8 310 四轮锁屏自睡 min(系统息屏,30s);M6.9 批A **锁屏期音量/播放/上下曲键转发**(EVIOCGRAB 独占真模态下下层收不到,锁屏本层显式边沿处理:音量 `settings_volume_adj`+`vol_ramp_tick`、播放/上下曲 `meow_media_playctl`)。`[SYSTEM_SETTINGS §1][WORK_STATE 2026-09-02]` - 日志启动缓冲 `/tmp/meow_boot.log`(256KB 滚动,卡出现后自动迁卡)`[APPS §4.5]` - IPC socket:`/usr/data/meow_ipc.sock`(AF_UNIX SOCK_STREAM,0666,backlog 4)`[FRAMEWORK_DESIGN §1][WORK_STATE]` - 专项服务 IPC socket:`/usr/data/meow_perf.sock`(meow_svc_perf,收 `MSG_PERF_QUERY→RESULT`)与 `/usr/data/meow_power.sock`(meow_svc_power,收 `MSG_POWER_ACTION→STATUS`),均 0666、可 `--sock`/`MEOW_IPC_{PERF,POWER}_SOCK` 覆盖;power 服务端以 `SO_PEERCRED uid==0` 基础鉴权 `[meow_svc_perf.c][meow_svc_power.c][meow_ipc.h]` @@ -139,12 +139,14 @@ - **图标库 `sb_icons`(307,Material Symbols Rounded / MD3,单通道 alpha 32×32,生成 `tools/gen_action_icons.py`)**:索引只追加——状态栏 `MEOW_SB_ICON_WIFI=0..BATT_CHG=14` 原样保留;**动作** `MEOW_ICON_SETTINGS=15..MEOW_ICON_HEART=29`(设置/刷新/搜索/播放/暂停/上下曲/返回/关闭/添加/勾选/歌词/列表/下载/收藏),经 `meow_topappbar_set_item_icon(w,idx,MEOW_ICON_*)` 使 W_TOPAPPBAR 动作项画位图(`layout 见 meow_ui_widgets.h`)。 - **图片降采样 `meow_img_downscale(img,maxdim)`(308)**:把解码图降到 ≤maxdim 边长以省内存(流媒体客户端封面 1448² RGBA 8.28MB→cap≤480px→0.78MB,-7.65MB),返回新 `meow_img*`。 - **§10.1 矢量字 `meow_ft_*`(316,上收阅读器已验证的 FreeType 管线,`patch_v2/sdk/meow_ui_ft.c`)**:`meow_ft_new_face(path)`(`FT_New_Face` 按需读文件零整载 anon + glyph LRU 512(key=`(px<<32)|cp`,可配 256/1024)+ 灰阶 blend565);`meow_ft_draw_*`/`meas`/`free`(App **显式**调才用,`meow_ui_text` 位图路径零改动);加载失败/无卡自动降级 `meow_font`(绝不豆腐不崩)。与位图字库**并存**;FreeType 2.14.3 vendor 进 `patch_v2/sdk/vendor/`(gitignore)+ `build_sdk.sh` 裁剪构建(`-DFT_DISABLE_ZLIB/BZIP2/PNG/HARFBUZZ/BROTLI=TRUE`,静态 musl);**不调 `meow_ft_*` 的 App 零增量**(`meow_ui_ft.o`/freetype 链接器不拉取,无任何 FT 符号)。**SDK 演进主体收官**。 +- **M7.0a SDK 基座(326,`meow_ui.a 7fc83f77`,launcher 重写前置)**:**`meow_view` 事件接线**(`meow_view_dispatch_event` 接 `meow_ui_run` 主循环 + `on_enter/on_leave` 递归 children + 动态挂子修正);**`W_ICONBTN`**(圆形命中 + 按压态,`MEOW_ICON_*` 位图/自定义画布按钮,电源菜单/播放控制用);**主题色板导出/注入** `meow_theme_export/inject`(launcher `s_ui_theme` 薄壳读 SDK,为 M7.4 主界面/各页统一 `meow_theme` 权威铺路)。 +- **SDK 新增批次(lazygrid/select/swipe/cachegov/net,`meow_ui.a c3d6371b`)**:**§12.4 2 维虚拟化宫格 `meow_lazygrid`**(M7.4 主界面前置:count+cols+视口虚拟化,item 池有界 ≤`MEOW_LAZYGRID_POOL_MAX`=96,fling/snap 复用 §12.3 meow_scroll,`set_vsource/set_item_draw/set_empty_text`+`hit/tap_at`);**§5.8.9 枚举选择器 `meow_select`**(M7.2 设置页缺口 #4:BottomSheet+`meow_listview(SINGLE)` 单选,`create/add_item/set_on_change/open(阻塞返回 idx/-1)/free` + 一次性 `meow_select_show`,选项 ≤`MEOW_SELECT_MAX_ITEMS`=32);**§5.5 左滑删除 swipe-delete**(M7.3 通知中心缺口 #9:`meow_listview_set_swipe_delete(lv,enabled,on_delete,ctx)` + `swipe_begin/move/end/active/offset`,横滑删/纵向滚/点按 tap 三分);**§14.2 磁盘缓存治理 `meow_cachegov`**(OOM page-cache 治理 A 侧:L0 曲终整文件 DONTNEED / L1 丢已读前缀 / **L2 禁止**,10s 滚窗逐级回退,`cfg{enabled,keep_hot_bytes,min_hot_ahead,drop_mode,refault_backoff}` + `register/unregister/tick/drop_finished/set_cfg/note_refault/backoff_level/download_paused/prefetch_paused`);**§14.1 网络自愿通道 `meow_ui_net`**(netmgrd 期4:`meow_net_policy_check(host)` 0=allow/1=block + `meow_net_report(rx,tx)` best-effort)。权威签名 = `patch_v2/sdk/include/meow_{lazygrid,select,cachegov}.h` / `meow_ui_net.h`;蓝本 `MEOW_APP_SDK.md` §5.5/§5.8.9/§12.4/§14。 --- ## 4. SDK 之外系统组件 - **系统进程 supervisor 树(源:2026-08-30 定案;M2.71 已落地 `meow_sysui`)**:`meow_sysui`(`patch_v2/meow_sysui/meow_sysui.c`,~113KB;`etc/init.d/S70meow_sysui` 开机拉起,早于 S89)监管系统进程,**一个崩→只拉起它,绝不级联**;`meow_launcherd`=系统 UI 渲染宿主(状态栏/通知中心/电源菜单/全局手势,与 APP 进程独立);`meow_svcd`=服务管理器(服务全子进程化,svcd 自身不死);APP 进程只渲染自己内容,崩不连累系统 UI。**系统组件渲染归系统进程(launcher),APP 进程不渲染系统 UI**(SDK 系统组件 API 仅系统进程可链,App 不可自建悬浮层);通知中心 UI 渲染主体=系统进程,数据经 IPC 从通知存储读。现网(bootmenu execv launcher + S89 svcd 自启)→ 定案(sysui 统一监管)为渐进迁移,属系统基建线。**已迁移(M2.83)**:sysui 默认监护 `meow_svcd` + `meow_launcherd`(`/usr/data/meow/sysui.d/.conf`,目录式复用 services.d 语义;`sysui.d/launcher.conf` `enabled=1`(restart=always/backoff=3000/max_restart=5),bootmenu 在 MeowOS 模式让位——sysui 在则不 exec launcher;mdns 是 svcd 服务子进程,不双重管理)。`[FRAMEWORK_DESIGN §10][MEOW_APP_SDK §6.1][NOTIFICATION_SYSTEM §9][patch_v2/meow_sysui/meow_sysui.md]` -- **专项子服务(一期 M2.73 已落地,规划见 `devdocs/plan/SYSTEM_FRAMEWORK_SERVICES.md`)**:两个 **svcd 托管、板载 `trigger=boot`** 的专项子服务——`meow_svc_perf`(性能/系统只读聚合)、`meow_svc_power`(关机/重启/升级/回 HiBy 系统动作)。launcher 电源菜单与 MeowPanel 均改经这些服务 IPC 触发(不再直连 `sys_do_reboot`/爬 `/proc`)。**IPC**:perf 走 `/usr/data/meow_perf.sock` `MSG_PERF_QUERY→RESULT`;power 走 `/usr/data/meow_power.sock` `MSG_POWER_ACTION→STATUS`(服务端 `SO_PEERCRED uid==0` 基础鉴权)。**M2.73**:perf 增 **network** 子集(SYSTEM 子集输出:真实 WiFi `wpa_cli`/`ip` + 蓝牙 `hci0`/sysfs + `hostname_local`,单点权威源);power **升级写保护**(`upgrade` 写 mtd5 前校验卡根 `r3proii.upt` 存在+可读+常规文件+size>0,失败不写 mtd5/不重启;**只完整性不签名**,海贝官方固件无签名,期望 md5/hash 二期)与 **`dac status` 读板载 `settings.conf` 的 `audio.volume` 真实值**(`settings_get_int_trim`,弃 amixer dummy;`dac on/off` 因 cs43198 `pwr_gpio=-1` 无控制点→`MEOW_ERR_NODEV`)。新 md5:perf `7ea196de`(M2.75 补 network 闭合括号)、power `306df962`(2B 面板精确绑定)。**电源 CLI `meow_svc_power `**(root 免鉴权、单一命令、不覆盖系统 shutdown/reboot):`shutdown`/`reboot[--hiby]`(强制回 MeowOS)/`upgrade`(写 mtd5=`ota:kernel2`,含写保护)/`set-default-meow`/`set-default-hiby` + `status`/`battery`/`reboot-reason`/`upgrade-status`/`screen on|off|timeout<秒>`/`dac on|off|status`。**二期签名鉴权已落地**(agent-185 2A / agent-221 2B):关键 IPC 只接受 **`os.meow.*`** 签权 + `.mpk` 打包签名验签(解包验签/导出保留签名;多公钥数组 + v2 签名块);读级命令鉴权已放宽到任意调用者(agent-189)。`[SYSTEM_FRAMEWORK_SERVICES][APPS 附录 §3][meow_svc_perf.c][meow_svc_power.c]` +- **专项子服务(一期 M2.73 已落地,规划见 `devdocs/plan/SYSTEM_FRAMEWORK_SERVICES.md`)**:两个 **svcd 托管、板载 `trigger=boot`** 的专项子服务——`meow_svc_perf`(性能/系统只读聚合)、`meow_svc_power`(关机/重启/升级/回 HiBy 系统动作)。launcher 电源菜单与 MeowPanel 均改经这些服务 IPC 触发(不再直连 `sys_do_reboot`/爬 `/proc`)。**IPC**:perf 走 `/usr/data/meow_perf.sock` `MSG_PERF_QUERY→RESULT`;power 走 `/usr/data/meow_power.sock` `MSG_POWER_ACTION→STATUS`(服务端 `SO_PEERCRED uid==0` 基础鉴权)。**M2.73**:perf 增 **network** 子集(SYSTEM 子集输出:真实 WiFi `wpa_cli`/`ip` + 蓝牙 `hci0`/sysfs + `hostname_local`,单点权威源);power **升级写保护**(`upgrade` 写 mtd5 前校验卡根 `r3proii.upt` 存在+可读+常规文件+size>0,失败不写 mtd5/不重启;**只完整性不签名**,海贝官方固件无签名,期望 md5/hash 二期)与 **`dac status` 读板载 `settings.conf` 的 `audio.volume` 真实值**(`settings_get_int_trim`,弃 amixer dummy;`dac on/off` 因 cs43198 `pwr_gpio=-1` 无控制点→`MEOW_ERR_NODEV`)。新 md5:perf `7ea196de`(M2.75 补 network 闭合括号)、power `306df962`(2B 面板精确绑定)。**电源 CLI `meow_svc_power `**(root 免鉴权、单一命令、不覆盖系统 shutdown/reboot):`shutdown`/`reboot[--hiby]`(强制回 MeowOS)/`upgrade`(写 mtd5=`ota:kernel2`,含写保护)/`set-default-meow`/`set-default-hiby` + `status`/`battery`/`reboot-reason`/`upgrade-status`/`screen on|off|timeout<秒>`/`dac on|off|status`。**二期签名鉴权已落地**(agent-185 2A / agent-221 2B):关键 IPC 只接受 **`os.meow.*`** 签权 + `.mpk` 打包签名验签(解包验签/导出保留签名;多公钥数组 + v2 签名块);读级命令鉴权已放宽到任意调用者(agent-189)。**M6.10 log 模块升级(337)**:perf `65f2059f` 挂「**卡死快照+perf 基线**」(主循环 `accept`→`poll` 化 + `snap_monitor_tick`:loadavg 超 `hang_load` 阈值落 `hang_*.snap`(全量 CPU/内存/进程/socket 现场)+`perf_*.log`(监控日志,60s/次、1MB 上限、每 boot ≤4 张、采集全走单文件 /proc 读);新增 `snap`/`snap-check` 子命令)+ crash `f02c4f45`(`log_cleanup_old` 前缀扩 `perf_*`/`hang_*` 入 logs/ 1GB 总纪律)。`test_snap_monitor` 18/18。`[SYSTEM_FRAMEWORK_SERVICES][APPS 附录 §3][meow_svc_perf.c][meow_svc_power.c]` - 通知(SDK `meow_notify_*`,M2.53 最小版 `[meow_ui.h §9.1]`): - 常量:`MEOW_NOTIFY_MAX_TYPES=4`/`MEOW_NOTIFY_MAX_PER_TYPE=4`/`MEOW_NOTIFY_MAX_MEDIA=1`/`MEOW_NOTIFY_TIMEOUT_ONGOING=-1`/`MEOW_NOTIFY_TIMEOUT_NEVER=0` - 分类:`CAT_GENERAL/MESSAGE/EMAIL/EVENT/PROGRESS/MEDIA(=5)/COUNT`;紧急度 `URG_LOW/NORMAL/HIGH/CRITICAL` diff --git a/patch_v2/files_app/files b/patch_v2/files_app/files index 588842c..ccdd9cf 100755 Binary files a/patch_v2/files_app/files and b/patch_v2/files_app/files differ diff --git a/patch_v2/hello_app/build_hello.sh b/patch_v2/hello_app/build_hello.sh index 98edcb1..011d6bf 100755 --- a/patch_v2/hello_app/build_hello.sh +++ b/patch_v2/hello_app/build_hello.sh @@ -12,7 +12,8 @@ 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../sdk -I. \ - app.c ui/home/home.c ui/full.c ui/demo/demo.c ui/demo/list/list.c ui/common/chrome.c \ + app.c ui/home/home.c ui/full.c ui/demo/demo.c ui/demo/list/list.c \ + ui/demo/sdk2/sdk2.c ui/common/chrome.c \ logic/notify.c logic/state.c \ ../sdk/meow_ui.a "$SYSROOT/lib/libz.a" -lpthread -o hello ls -la hello diff --git a/patch_v2/hello_app/hello b/patch_v2/hello_app/hello index 95cf810..beac150 100755 Binary files a/patch_v2/hello_app/hello and b/patch_v2/hello_app/hello differ diff --git a/patch_v2/hello_app/hello_internal.h b/patch_v2/hello_app/hello_internal.h index 1c66f55..fde5f96 100644 --- a/patch_v2/hello_app/hello_internal.h +++ b/patch_v2/hello_app/hello_internal.h @@ -106,6 +106,21 @@ int list_view_down(int x, int y); /* touch_down 命中列表:置 int list_view_move(int y); /* touch_move 拖动列表:1=在列表拖动 */ int list_view_swipe(int del); /* swipe:列表聚焦时滚列表,1=已消费 */ +/* ---- SDK 批次2 控件演示区 CONTAINER 子视图(ui/demo/sdk2/sdk2.c)---- + * 复刻 list.c 模式:demo 页内容区底部追加三节——枚举选择器 meow_select / 2D 虚拟化宫格 + * meow_lazygrid / 列表行左滑删除 meow_listview_set_swipe_delete。子视图自持三节控件与视图态 + * (grid 非 meow_widget:on_draw 直绘 meow_lazygrid_draw;swipe-list 行左滑 3 相位手势), + * 对外暴露 sdk2_build_at(demo_build_at 调)与 hit/down/move/swipe/release(demo 分发改派)。 */ +meow_view *sdk2_view_create(void); /* 建 CONTAINER 子视图(须即 meow_view_add_child) */ +meow_view *sdk2_view_get(void); /* 返回子视图(宿主测嵌套生命周期断言) */ +int sdk2_build_at(int yb_base, int ct, int scroll); /* 在 base yb 处建三节控件(owner=ACT_DEMO),返回下一 yb */ +void sdk2_release(void); /* 内容释放后:释放 grid/摘控件引用/清几何 */ +int sdk2_hit(int x, int y); /* tap 分发:select 按钮/宫格 tap/列表行 tap/行左滑松手 swipe_end */ +int sdk2_down(int x, int y); /* touch_down:宫格拖拽或列表行左滑 begin;1=本区消费 */ +int sdk2_move(int x, int y); /* touch_move:宫格滚动/列表左滑 move 或列表竖向滚动;1=消费 */ +int sdk2_swipe(int del); /* swipe UP/DOWN:宫格或列表聚焦时滚自身;1=已消费 */ +int sdk2_dragging(void); /* 1=本子视图拖拽在途(demo_swipe 早退防 on_swipe 二次滚动) */ + /* ---- chrome(ui/common/chrome.c)---- */ extern meow_widget *g_tb; /* TopAppBar(chrome 固定) */ extern meow_widget *g_nav; /* NavigationBar(chrome 固定) */ diff --git a/patch_v2/hello_app/ui/demo/demo.c b/patch_v2/hello_app/ui/demo/demo.c index 2968da6..5ebfa1b 100644 --- a/patch_v2/hello_app/ui/demo/demo.c +++ b/patch_v2/hello_app/ui/demo/demo.c @@ -43,6 +43,7 @@ static int g_drag_slider = 0; /* 手指按在 slider */ int g_drag_page = 0; /* 手指按在页内容(非 slider/list):拖拽滚页面 */ static int g_drag_page_last_y = 0; /* 页面拖拽上一帧 y */ static int g_drag_page_delta = 0; /* 页面拖拽累计位移(节流重建,≥8px 才重建一次) */ +static uint64_t g_drag_page_tlast = 0; /* 页面拖拽上次重建时刻(359:每 poll 合并一次 rebuild) */ /* SDK 控件不透明:App 自记几何,用于局部 flush / 命中 / scroll 重建。 */ @@ -157,6 +158,7 @@ static void demo_free_content(void) if (g_slider_cid > 0) { meow_draw_unregister(g_slider_cid); g_slider_cid = 0; } g_dtitle_n = 0; list_view_release(); /* 列表控件已在上方 g_dslot 释放;摘容器引用/几何 */ + sdk2_release(); /* SDK 批次2 子视图:释放 grid/btn/list 控件引用 + 清几何 */ } /* 按 scroll 重建全部内容控件(owner=ACT_DEMO:调用方须已 push ACT_DEMO)。 @@ -246,6 +248,15 @@ static void demo_build_at(int scroll) d_title(DCL_X, SY(yb), "MEDIA SESSION (meta)"); yb += 26; r_media = RX(DCL_X, SY(yb), DCW, DBH); g_dbtn_media = meow_btn_create_variant(r_media.x, r_media.y, r_media.w, r_media.h, "SEND META", MEOW_BTN_FILLED); SLOT(g_dbtn_media, r_media.x, r_media.y, r_media.w, r_media.h); yb += DCH; + /* ---- SDK 批次2 新控件(meow_select / meow_lazygrid / swipe-delete)---- + * 三节由 sdk2 CONTAINER 子视图(ui/demo/sdk2/sdk2.c)承接:章节标题/宫格/select 值文本走 + * 子视图 on_draw 直绘(同列表区标题);select 按钮 + swipe 列表 widget 由 sdk2 自持、此处创建。 + * 控件创建后几何已内存;跨重建视图态(grid scroll/sel、swipe 行集/scroll、select 值)由 + * sdk2_build_at 从子视图持有态恢复。 + * 注:sdk2 置于 LazyColumn 之前(列表保持 demo 页内容**末尾**区块),确保「滚动到底列表上屏」 + * 的既有宿主 P3 断言坐标假设仍然成立(sdk2 增高在列表上方,列表仍是内容底)。 */ + yb = sdk2_build_at(yb, ct, scroll); + /* ---- LazyColumn(100 项列表滚动) ---- * 本区块标题改由列表区 CONTAINER 子视图(ui/demo/list/list.c)的 on_draw 绘制(注册表→ * invalidate 链路);列表滚动/选中由 list_view_bind 从子视图持有态恢复(不再经 g_ds)。 */ @@ -268,7 +279,21 @@ void demo_paint(void) meow_ui_paint_full(); /* 整帧按 z 序重画 */ } -/* 重建内容到指定 scroll(clamp 到 [0, max_scroll]);进入/滚动共用 */ +/* 内容视口坐标(每次 build 同步):屏 y ∈ [g_demo_ct, g_demo_cb),x 跨 0..width。 */ +static void demo_repaint_viewport(void) +{ + int h = g_demo_cb - g_demo_ct; + if (h <= 0) return; + /* 滚动重绘:只清底+按 z 重画内容视口矩形 [0, ct, w, h],不整帧。 + * 359 教训(滚动只刷视口 + 局部脏区不整屏重绘铁律):meow_ui_invalidate 内部已把绘制 clip + * 收敛到矩形(meow_gfx_clip_push)+ meow_ui_flush_region 只上屏该区;chrome(TopAppBar/ + * NavBar/FAB,z=999,位于视口外)不触及,上一帧像素保留,滚动不再重画 chrome 字形。 */ + meow_ui_invalidate(0, g_demo_ct, meow_ui_width(), h); +} + +/* 重建内容到指定 scroll(clamp 到 [0, max_scroll]);进入/滚动共用。 + * 滚动路径用视口局部刷(demo_repaint_viewport)替代整帧 demo_paint——每刻度重建仍发生, + * 但不再整帧重绘(359 教训:SDK 控件内建局部刷,卡顿源于调用方整页重绘)。 */ void demo_rebuild_to(int scroll) { int vp_h = g_demo_cb - g_demo_ct; @@ -279,7 +304,7 @@ void demo_rebuild_to(int scroll) demo_free_content(); demo_build_at(scroll); g_demo_scroll = scroll; - demo_paint(); + demo_repaint_viewport(); } /* ---- demo 交互 ---- */ @@ -299,6 +324,8 @@ static int demo_content_tap(int x, int y) /* 嵌套命中(子>父,§A.7 设计):列表区 CONTAINER 子视图先发,命中即消费。 */ if (list_view_hit(x, y)) return 1; + /* SDK 批次2 子视图(select 按钮/宫格/列表行左滑松手):命中即消费。 */ + if (sdk2_hit(x, y)) return 1; for (int i = 0; i < 5; i++) if (meow_widget_hit(g_dbtn_b[i], x, y)) { d_toggle_sel(g_dbtn_b[i], r_btn[i]); g_ds.btn[i] = meow_widget_selected(g_dbtn_b[i]); return 1; } @@ -344,8 +371,18 @@ static void demo_media_send(void) } /* demo 页 on_tap 分发(chrome → 内容) */ +/* 页面拖拽抬手把节流窗内遗留的 delta 一次性重建(视口局部刷)——slow drag 结束为 TAP 时也精确落点。 */ +static void demo_flush_page_delta(void) +{ + if (g_drag_page && (g_drag_page_delta >= 1 || g_drag_page_delta <= -1)) { + demo_rebuild_to(g_demo_scroll + g_drag_page_delta); + g_drag_page_delta = 0; + } +} + void demo_on_tap(int x, int y) { + demo_flush_page_delta(); /* 手势结束:TAP 抬手边沿( list > 页内容)。 - * 列表区走 list_view_down(子>父:命中列表区由列表 CONTAINER 子视图收口)。 */ +/* demo 页 on_touch_down:锁一种拖拽(slider > list > sdk2 > 页内容)。 + * 列表区走 list_view_down(子>父:命中列表区由列表 CONTAINER 子视图收口); + * SDK 批次2 子视图走 sdk2_down(宫格拖拽/列表行左滑 begin,子>父收口)。 */ void demo_touch_down(int x, int y) { g_drag_slider = 0; g_drag_list = 0; g_drag_page = 0; g_drag_page_delta = 0; if (!g_demo) return; if (meow_widget_hit(g_dslider, x, y)) { g_drag_slider = 1; demo_slider_drag(x); return; } if (list_view_down(x, y)) return; /* 列表区:置 focus+drag_list(list.c) */ + if (sdk2_down(x, y)) return; /* SDK 批次2:宫格/列表行左滑 begin(sdk2.c) */ g_demo_focus = 0; if (y >= g_demo_ct && y < g_demo_cb && x >= 0 && x < meow_ui_width()) { g_drag_page = 1; g_drag_page_last_y = y; } } -/* demo 页 on_touch_move:拖动过程实时更新(slider 值 / 列表滚动 / 页面滚动节流重建)。 */ +/* demo 页 on_touch_move:拖动过程实时更新(slider 值 / 列表滚动 / sdk2 滚动 / 页面滚动节流重建)。 */ void demo_touch_move(int x, int y) { if (!g_demo) return; if (g_drag_slider) { demo_slider_drag(x); return; } if (list_view_move(y)) return; /* 列表拖动(list.c) */ + if (sdk2_move(x, y)) return; /* SDK 批次2:宫格滚动/列表左滑 move/列表竖向滚动(sdk2.c) */ if (g_drag_page) { int dy = y - g_drag_page_last_y; g_drag_page_last_y = y; g_drag_page_delta += -dy; /* 手指上移(y 减) → scroll 增(显示更低内容),与 on_swipe 一致 */ - if (g_drag_page_delta >= 8 || g_drag_page_delta <= -8) { + /* 359 教训:滚动期间每个 poll 合并一次 rebuild(勿每触摸事件各自重建/上屏)。 + * 累计位移超阈值(≥8) 且(距上次 ≥16ms 或 已是大步 ≥32)才重建一次;未到窗则继续 + * 累积 delta(不丢),下个窗口一次性重建到精确位置。 */ + uint64_t now = meow_ui_time_ms(); + int big = (g_drag_page_delta >= 8 || g_drag_page_delta <= -8); + int merge = (now - g_drag_page_tlast >= 16) || + (g_drag_page_delta >= 32) || (g_drag_page_delta <= -32); + if (big && merge) { demo_rebuild_to(g_demo_scroll + g_drag_page_delta); + g_drag_page_tlast = now; g_drag_page_delta = 0; } } } -/* demo 页 on_swipe:LEFT=回 home;UP/DOWN=列表聚焦滚列表,否则页滚动。 */ +/* demo 页 on_swipe:LEFT=回 home;UP/DOWN=列表/宫格/sdk2 聚焦滚自身,否则页滚动。 */ void demo_swipe(int dir, int dist) { if (dir == MEOW_SWIPE_LEFT) { meow_view_page_pop(); return; } if (dir != MEOW_SWIPE_UP && dir != MEOW_SWIPE_DOWN) return; - if (g_drag_page || g_drag_list) return; /* 本次手势已实时跟手滚动过 */ + if (g_drag_page || g_drag_list || sdk2_dragging()) { /* 本次手势已实时跟手滚动过 */ + demo_flush_page_delta(); /* 抬手:flush 节流窗内遗留页面 delta(视口局部刷)精确落点 */ + return; + } int del = (dir == MEOW_SWIPE_UP) ? dist : -dist; /* 手指上移 → 偏移增大 */ if (list_view_swipe(del)) return; /* 列表聚焦:滚列表(list.c,子>父消费) */ + if (sdk2_swipe(del)) return; /* SDK 批次2:宫格/列表聚焦滚自身(sdk2.c,子>父消费) */ demo_rebuild_to(g_demo_scroll + del); } @@ -441,7 +493,8 @@ static void demo_page_enter(meow_view *v, void *ctx) hello_nav_report(); /* ① 上报 demo 栈深/顶层 */ /* 列表区 CONTAINER 子视图随本页进入由 SDK view_enter 递归驱动(其 on_draw 在 demo_rebuild_to * →demo_paint 中经注册表绘制;meow_draw_view_entered 含祖先已进入判定,首批可见)。 */ - demo_rebuild_to(0); + demo_rebuild_to(0); /* 内容建到 scroll=0(视口局部刷) */ + demo_paint(); /* 进入页整帧首绘(含 chrome 置顶 z=999);仅进入一次,非滚动路径 */ meow_log(MEOW_LOG_INFO, "demo enter scroll=0"); } @@ -475,14 +528,16 @@ int demo_is_active(void) } /* demo view:on_create 建 chrome+内容(一次,owner=ACT_DEMO);进入时 rebuild 到顶。 - * 视图深度化:先挂列表区 CONTAINER 子视图到本 ACTIVITY view 的 children,再建内容。 - * 列表子视图 id 用非页面值,不占 g_view_by_id(见 list.c)。 */ + * 视图深度化:先挂列表区 + SDK 批次2 两个 CONTAINER 子视图到本 ACTIVITY view 的 children,再建内容。 + * 子视图 id 用非页面值,不占 g_view_by_id(见 list.c/sdk2.c)。 */ static void demo_page_create(meow_view *v, void *ctx) { (void)v; ((demo_ctrl *)ctx)->view = v; meow_view *lv = list_view_create(); if (lv) meow_view_add_child(v, lv); + meow_view *s2 = sdk2_view_create(); + if (s2) meow_view_add_child(v, s2); demo_init(); } diff --git a/patch_v2/hello_app/ui/demo/sdk2/sdk2.c b/patch_v2/hello_app/ui/demo/sdk2/sdk2.c new file mode 100644 index 0000000..f98f172 --- /dev/null +++ b/patch_v2/hello_app/ui/demo/sdk2/sdk2.c @@ -0,0 +1,455 @@ +/* ui/demo/sdk2/sdk2.c — 控件库 Demo 页内「SDK 批次2 新控件」CONTAINER 子视图(agent-363)。 + * + * 目标(SDK_VIEW_FRAMEWORK_DESIGN.md §B.4/§E③,同 list.c):把 demo 页底部的三个 SDK 批次2 + * 新控件演示区块抽成一个 meow_view CONTAINER 子视图,挂到 demo ACTIVITY view 的 children: + * ① meow_select (§5.8.9 枚举/预设选择器:下拉模态 + 单选列表 + on_change 回调) + * ② meow_lazygrid (§12.4 2 维虚拟化宫格:非 meow_widget,on_draw 直绘 meow_lazygrid_draw) + * ③ meow_listview 左滑删除(§5.5 swipe-delete:swipe_begin/move/end + on_delete + 回弹/删除动画) + * + * 职责边界(诚实,与 list.c 同折中): + * - 本子视图承接三节**整体**:章节标题 + select 按钮 + 宫格 + swipe-列表 的创建/释放与视图态 + * (grid 虚拟源/选中/滚动、swipe 行集/滚动、select 选中值)。控件**创建**在 demo_build_at 链路内 + * (sdk2_build_at,owner=ACT_DEMO)、**释放**在 sdk2_release(demo_free_content 调)——与 list.c + * 相同折中:完整「控件归容器拥有」需 SDK 侧支持(本批冻结 patch_v2/sdk/)。 + * - meow_lazygrid 是**非 meow_widget**:不注册 z 序/绘制注册表。本子视图 on_draw 直接调 + * meow_lazygrid_draw(view → 注册表 → invalidate 链路,与 list 区块标题同法)。 + * - 章节标题(三段)与 select 当前值文本也走 on_draw 直绘(列表区标题同法),不逐标题注册 + * CUSTOM 绘制物;on_draw 是回调、view 不缓存像素(非保留,§5.8.7)。 + * - 嵌套生命周期由 SDK view_enter/view_leave 递归驱动(随 demo 页进出,同 list.c);绘制经 + * meow_draw_view_entered(含祖先已进入判定)在 meow_ui_paint_full/invalidate 触发。 + * + * 事件接线(demo 页把 touch/tap/swipe 分发改派到本子视图,实现「子>父」命中): + * - tap → sdk2_hit (select 按钮 / 宫格 tap / 列表行 tap / 行左滑松手 swipe_end) + * - down → sdk2_down (宫格聚焦拖拽 / 列表行左滑 begin) + * - move → sdk2_move (宫格滚动 / 列表左滑 move 或列表竖向滚动) + * - swipe → sdk2_swipe (宫格/列表聚焦时滚自身;否则回退页面滚动) + */ +#include "hello_internal.h" +#include +#include +#include + +/* ---- 本子视图(CONTAINER,非页面:id 用越界值避免占用 g_view_by_id 顶层页槽位)---- */ +static meow_view *g_sdk2_view = NULL; + +/* ---- demo 内容布局常量(与 demo.c 一致):内容区 x16 宽 448 ---- */ +#define S2_X 16 +#define S2_W 448 +#define S2_DCW 212 +#define S2_DBH 44 +#define S2_CH 52 + +/* 三节(章节标题几何在 sdk2_build_at 计算后存入,on_draw 直绘用;屏幕坐标)。 */ +static int s2_t1_y, s2_t2_y, s2_t3_y; /* 三节标题 y(on_draw 直绘) */ + +/* ============================================================ */ +/* ① meow_select — 枚举/预设选择器 */ +/* ============================================================ */ +static meow_widget *g_btn_sel = NULL; /* 「打开选择器」按钮(sdk2_release 释放) */ +static meow_select *g_sel = NULL; /* 选择器实例(sdk2_view_create 建一次,destroy 释放) */ +static rect_t r_selbtn = { 0, 0, 0, 0 }; +static rect_t r_selval = { 0, 0, 0, 0 }; /* 当前选择值文本区(on_draw 直绘) */ +static int g_sel_cur = 0; /* 当前选中项(跨重建保留;on_change 更新) */ + +static const char *g_sel_items[] = { /* 枚举演示:音频档位(对齐 M7.2 设置页采样率/码率) */ + "Auto", "44.1kHz", "48kHz", "96kHz", "192kHz", +}; +#define SEL_N ((int)(sizeof g_sel_items / sizeof g_sel_items[0])) + +/* on_change 回调(meow_select_open 确认后触发:更新选中下标 + 重画值文本区)。 */ +static void sdk2_sel_change(int idx, void *ctx) +{ + (void)ctx; + if (idx >= 0 && idx < SEL_N) g_sel_cur = idx; + if (r_selval.w > 0) meow_ui_invalidate(r_selval.x, r_selval.y, r_selval.w, r_selval.h); +} + +/* 打开选择器(阻塞到确认/取消;on_change 已更新选中值)。 */ +static void sdk2_select_open(void) +{ + if (!g_sel) return; + int r = meow_select_open(g_sel); + meow_log(MEOW_LOG_INFO, "sdk2 select return=%d (%s)", + r, (r >= 0 && r < SEL_N) ? g_sel_items[r] : "cancel"); +} + +/* ============================================================ */ +/* ② meow_lazygrid — 2 维虚拟化宫格(非 widget) */ +/* ============================================================ */ +static meow_lazygrid *g_grid = NULL; +static rect_t r_grid = { 0, 0, 0, 0 }; +static int g_grid_scroll = 0; /* 跨重建保留:宫格滚动偏移 */ +static int g_grid_sel = -1; /* 跨重建保留:选中项 */ +static int g_drag_grid = 0; /* 触摸拖宫格(down 置位;move 滚动) */ +static int g_grid_focus = 0; /* 1=宫格聚焦(swipe 滚宫格而非页面) */ +static int g_grid_last_y = 0; + +#define GRID_N 36 /* 总项数(count > 视口行数 → 可滚动) */ +#define GRID_COLS 3 +#define GRID_CW 138 +#define GRID_CH 86 +#define GRID_GAP 10 +#define GRID_H 190 + +/* 虚拟源:count 总项数(实时查);bind 项进入视口时调一次(同 listview 池语义)。 */ +static int grid_count(void *ctx) { (void)ctx; return GRID_N; } +static void grid_bind(int idx, meow_lazygrid_item *item, void *ctx) +{ + (void)ctx; + static char t[24], s[24]; + snprintf(t, sizeof t, "App %d", idx); + snprintf(s, sizeof s, "v1.%d", idx); + memset(item, 0, sizeof *item); + item->title = t; item->subtitle = s; + item->enabled = (idx % 9 != 8); /* 演示禁用变淡(每 9 个禁用 1 个) */ +} + +/* 完全自绘 cell(set_item_draw):icon+label 风格,对齐主界面用途。 */ +static void grid_cell(int idx, int x, int y, int w, int h, int st, void *ctx) +{ + (void)ctx; + const meow_theme *th = meow_theme_get(); + int sel = st & MEOW_LAZYGRID_ST_SELECTED; + int dis = st & MEOW_LAZYGRID_ST_DISABLED; + uint32_t bg = sel ? th->primaryContainer : th->surfaceContainerLow; + uint32_t icon = dis ? th->onSurfaceVariant : th->primary; + uint32_t txt = sel ? th->onPrimaryContainer + : (dis ? th->onSurfaceVariant : th->onSurface); + meow_ui_fill_rect(x, y, w, h, bg); /* 格底卡片 */ + /* icon 块(无图资源:主题 primary 方块代替主界面图标位) */ + int isz = w / 3; if (isz > h / 2) isz = h / 2; + meow_ui_fill_rect(x + (w - isz) / 2, y + 10, isz, isz, icon); + /* 名 + 状态(底部两行,中心对齐) */ + static char t[24], s[24]; + snprintf(t, sizeof t, "App %d", idx); + snprintf(s, sizeof s, "v1.%d", idx); + int thh = meow_ui_text_height(th->font_body); + meow_ui_text_center(x + w / 2, y + h - thh - 22, t, th->font_body, txt); + meow_ui_text_center(x + w / 2, y + h - thh - 3, s, th->font_body, txt); + if (sel) meow_ui_draw_rect_round(x, y, w, h, th->radius_card, th->primary); + if (dis) meow_ui_fill_rect_alpha(x, y, w, h, th->onSurface, 90); +} + +/* 宫格 tap:置选中 + 局部刷 + 日志。 */ +static void grid_tap(int idx, void *ctx) +{ + (void)ctx; + g_grid_sel = idx; + meow_log(MEOW_LOG_INFO, "sdk2 grid tap idx=%d", idx); + if (r_grid.w > 0) meow_ui_invalidate(r_grid.x, r_grid.y, r_grid.w, r_grid.h); +} + +/* ============================================================ */ +/* ③ meow_listview 行左滑删除(swipe-delete) */ +/* ============================================================ */ +#define LV_MAX 9 +static meow_widget *g_lv = NULL; /* W_LISTVIEW(注册表自动画:行 + 右露删除区) */ +static rect_t r_lv = { 0, 0, 0, 0 }; +static int g_lv_deleted[LV_MAX]; /* 1=已删除(行移除;count 实时收缩) */ +static char g_lv_label[LV_MAX][24]; /* 行标题(bind 填充) */ +static int g_lv_scroll = 0; /* 跨重建保留:列表滚动 */ +static int g_lv_focus = 0; /* 1=swipe-list 聚焦(swipe 滚列表而非页面) */ +static int g_lv_swipe = 0; /* 行左滑在途(swipe_begin 命中;hit 里 swipe_end) */ +static int g_drag_lv = 0; /* 触摸拖列表(竖向滚动);d. 左滑时同置位 */ +static int g_lv_last_y = 0; +#define LV_H 190 + +static int lv_count_remaining(void) +{ + int n = 0; + for (int i = 0; i < LV_MAX; i++) if (!g_lv_deleted[i]) n++; + return n; +} +static int lv_count(void *ctx) { (void)ctx; return lv_count_remaining(); } + +/* 显示下标(0..count-1)→ 源行 id(跳过已删除行)。删除后行前移,身份按源行稳定。 */ +static int lv_real_index(int display) +{ + if (display < 0) return -1; + int c = 0; + for (int i = 0; i < LV_MAX; i++) { + if (g_lv_deleted[i]) continue; + if (c == display) return i; + c++; + } + return -1; +} + +static void lv_bind(int idx, meow_lv_item *item, void *ctx) +{ + (void)ctx; + int real = lv_real_index(idx); + static char s[24]; + memset(item, 0, sizeof *item); + snprintf(s, sizeof s, "swipe left"); + item->title = (real >= 0) ? g_lv_label[real] : ""; + item->subtitle = s; + item->icon_kind = (real >= 0 && real % 3 == 0) ? MEOW_LIST_ICON_FOLDER : MEOW_LIST_ICON_NONE; + item->right = MEOW_LIST_RIGHT_NONE; + item->enabled = 1; +} +/* on_delete(swipe 删除动画滑出完成回调):删除该显示下标对应的源行 + 局部刷。 + * 注:swipe_end 传的是显示下标(含删除前定位的 swipe_idx);映射回源行 id 标记。 */ +static void lv_delete(int idx, void *ctx) +{ + (void)ctx; + int real = lv_real_index(idx); + if (real >= 0) g_lv_deleted[real] = 1; + meow_log(MEOW_LOG_INFO, "sdk2 swipe delete display=%d real=%d", idx, real); + if (g_lv && r_lv.w > 0) meow_ui_invalidate(r_lv.x, r_lv.y, r_lv.w, r_lv.h); +} + +/* 建三节时填充行标题(demo_init 一次性;跨重建保留)。 */ +static void sdk2_init_rows(void) +{ + for (int i = 0; i < LV_MAX; i++) { + snprintf(g_lv_label[i], sizeof g_lv_label[i], "notif %d", i); + g_lv_deleted[i] = 0; + } +} + +static int s2_built = 0; /* 1=本子视图内容已建(on_draw / 事件分发判空) */ + +/* ============================================================ */ +/* 释放 / 建三节 */ +/* ============================================================ */ + +/* 内容释放(demo_free_content 调):清引用(本子视图建的控件在 sdk2_release 释放)。 */ +void sdk2_release(void) +{ + if (g_grid) { meow_lazygrid_free(g_grid); g_grid = NULL; } + if (g_btn_sel) { meow_widget_free(g_btn_sel); g_btn_sel = NULL; } + if (g_lv) { meow_widget_free(g_lv); g_lv = NULL; } + r_selbtn = r_selval = r_grid = r_lv = RX(0, 0, 0, 0); + g_drag_grid = 0; g_grid_focus = 0; g_grid_last_y = 0; + g_lv_focus = 0; g_lv_swipe = 0; g_drag_lv = 0; g_lv_last_y = 0; + s2_built = 0; + if (g_sdk2_view) g_sdk2_view->x = g_sdk2_view->y = g_sdk2_view->w = g_sdk2_view->h = 0; +} + +/* 内容重建后建三节(demo_build_at 调)。入参:yb_base=base 空间 y;ct/scroll 同 demo.c + * (SY(base)=ct+base-scroll)。返回下一 base y。 */ +int sdk2_build_at(int yb_base, int ct, int scroll) +{ +#define SY2(b) (ct + (b) - scroll) + int yb = yb_base; + + /* ---- ① SELECT ---- */ + s2_t1_y = SY2(yb); + yb += 26; + r_selbtn = RX(S2_X, SY2(yb), S2_DCW, S2_DBH); + g_btn_sel = meow_btn_create_variant(r_selbtn.x, r_selbtn.y, r_selbtn.w, r_selbtn.h, + "CHOOSE", MEOW_BTN_OUTLINED); + r_selval = RX(S2_X + S2_DCW + 14, SY2(yb) + 10, S2_W - S2_DCW - 14, 24); + yb += S2_CH; + + /* ---- ② LAZYGRID ---- */ + s2_t2_y = SY2(yb); + yb += 30; + r_grid = RX(S2_X, SY2(yb), S2_W, GRID_H); + g_grid = meow_lazygrid_create(r_grid.x, r_grid.y, r_grid.w, r_grid.h, + GRID_COLS, GRID_CW, GRID_CH, GRID_GAP); + if (g_grid) { + meow_lazygrid_set_vsource(g_grid, grid_count, grid_bind, NULL); + meow_lazygrid_set_item_draw(g_grid, grid_cell, NULL); + meow_lazygrid_set_empty_text(g_grid, "No apps"); + meow_lazygrid_set_on_tap(g_grid, grid_tap, NULL); + meow_lazygrid_set_scroll(g_grid, g_grid_scroll); + meow_lazygrid_set_sel(g_grid, g_grid_sel); + } + yb += GRID_H + 30; + + /* ---- ③ SWIPE-DELETE ---- */ + s2_t3_y = SY2(yb); + yb += 26; + r_lv = RX(S2_X, SY2(yb), S2_W, LV_H); + g_lv = meow_listview_create(r_lv.x, r_lv.y, r_lv.w, r_lv.h, 0, + NULL, NULL, MEOW_LIST_SEL_NONE); + if (g_lv) { + meow_listview_set_item_h(g_lv, 48); + meow_listview_set_vsource(g_lv, lv_count, lv_bind, NULL); + meow_listview_set_item_renderer(g_lv, 2, 1, 0); /* 双行、左图标、无右尾件 */ + meow_listview_set_empty_text(g_lv, "All rows deleted"); + meow_listview_set_swipe_delete(g_lv, 1, lv_delete, NULL); + meow_listview_set_scroll(g_lv, g_lv_scroll); + } + yb += LV_H + 10; + + if (g_sdk2_view) { + g_sdk2_view->x = S2_X; g_sdk2_view->y = s2_t1_y; + g_sdk2_view->w = S2_W; g_sdk2_view->h = (r_lv.y + r_lv.h) - s2_t1_y; + } + s2_built = 1; + return yb; +#undef SY2 +} + +/* ---- on_draw:三节标题 + select 当前值 + 宫格(非保留,invalidate 触发重画)---- */ +static void sdk2_draw_on(meow_view *v, const meow_theme *th, void *ctx) +{ + (void)v; (void)th; (void)ctx; + if (!s2_built) return; + const meow_theme *t = meow_theme_get(); + meow_ui_text(S2_X, s2_t1_y, "SELECT (enum/preset)", 20, t->primary); + meow_ui_text(S2_X, s2_t2_y, "LAZYGRID (2D virtualized)", 20, t->primary); + meow_ui_text(S2_X, s2_t3_y, "LISTVIEW swipe-delete", 20, t->primary); + char buf[40]; + snprintf(buf, sizeof buf, "sel = %s", g_sel_items[g_sel_cur]); + meow_ui_text(r_selval.x, r_selval.y, buf, 20, t->dim); + if (g_grid) meow_lazygrid_draw(g_grid, t); +} + +/* ============================================================ */ +/* 事件分发(demo 页转发) */ +/* ============================================================ */ + +/* tap:select 按钮 → 打开模态;列表行左滑松手 → swipe_end;宫格 → tap_at;列表行 → tap。 */ +int sdk2_hit(int x, int y) +{ + if (!s2_built) return 0; + /* 行左滑【松手】最优先:滑动后手指可能已移出列表矩形(只判在途标志)。 */ + if (g_lv && g_lv_swipe) { + meow_listview_set_scroll(g_lv, g_lv_scroll); + int c = meow_listview_swipe_end(g_lv, x, y); /* 判删/回弹;动画由 meow_anim_tick 推进 */ + g_lv_swipe = 0; + if (c) return 1; /* 消费(删/回弹动画中);未滑动=0 落行选中 */ + } + if (g_btn_sel && meow_widget_hit(g_btn_sel, x, y)) { + sdk2_select_open(); + meow_ui_invalidate(r_selbtn.x, r_selbtn.y, r_selbtn.w, r_selbtn.h); + return 1; + } + if (g_grid && meow_lazygrid_hit(g_grid, x, y) >= 0) { + meow_lazygrid_tap_at(g_grid, x, y); /* 置选中 + grid_tap 回调 + 局部刷 */ + return 1; + } + if (g_lv && meow_listview_hit(g_lv, x, y) >= 0) { + meow_listview_tap_at(g_lv, x, y); /* 行点击(演示选中)+ 局部刷 */ + meow_ui_invalidate(r_lv.x, r_lv.y, r_lv.w, r_lv.h); + return 1; + } + return 0; +} + +/* touch_down:命中区置拖拽/聚焦。宫格>列表。返回 1=本区消费。 */ +int sdk2_down(int x, int y) +{ + if (!s2_built) return 0; + /* 新手势先复位本子视图拖拽态:demo_touch_down 只复位 list/slider/page 标志,不触及 sdk2 + * 的 g_drag_grid/g_drag_lv/g_lv_swipe(sdk2.c 内部,demo 页经 extern 看不到);不复位则上一条 + * sdk2 拖拽残留会在本次「落页内容区」新手势的 move 里继续劫持页面拖动(sdk2_move 守卫误判)。 */ + g_drag_grid = 0; g_drag_lv = 0; g_lv_swipe = 0; + if (g_grid && meow_lazygrid_hit(g_grid, x, y) >= 0) { + g_drag_grid = 1; g_grid_focus = 1; g_grid_last_y = y; + return 1; + } + if (g_lv && meow_listview_hit(g_lv, x, y) >= 0) { + meow_listview_set_scroll(g_lv, g_lv_scroll); + g_lv_swipe = meow_listview_swipe_begin(g_lv, x, y); /* 命中可滑行 → 1 */ + g_lv_focus = 1; g_drag_lv = 1; g_lv_last_y = y; + return 1; + } + return 0; +} + +/* touch_move:宫格滚动 / 列表左滑 move(消费→不滚页面)或列表竖向滚动。返回 1=消费。 */ +int sdk2_move(int x, int y) +{ + if (!s2_built) return 0; + if (g_drag_grid && g_grid) { + int dy = y - g_grid_last_y; g_grid_last_y = y; + meow_lazygrid_scroll_by(g_grid, -dy); + g_grid_scroll = meow_lazygrid_scroll(g_grid); + if (r_grid.w > 0) meow_ui_invalidate(r_grid.x, r_grid.y, r_grid.w, r_grid.h); + return 1; + } + /* 列表滚动须在「本次手势命中列表」才消费(g_drag_lv,由 sdk2_down 命中列表置位)。 + * 若仅因 g_lv 存在就无条件消费,会劫持 demo 页内容区拖拽(任何 move 都被这里吃掉, + * 页面永不滚动)——与 grid(g_drag_grid 守卫)同构;359 教训同源(SDK 控件局部刷对, + * 错在调用方控制流把页面滚动让给了控件)。 */ + if (g_drag_lv && g_lv) { + if (g_lv_swipe) { /* 行左滑在途:喂 move;1=横向消费;0=纵向主导→取消本行 swipe */ + meow_listview_set_scroll(g_lv, g_lv_scroll); + if (meow_listview_swipe_move(g_lv, x, y)) return 1; + g_lv_swipe = 0; + } + int dy = y - g_lv_last_y; g_lv_last_y = y; /* 竖向滚动(跟手) */ + meow_listview_scroll_by(g_lv, -dy); + g_lv_scroll = meow_listview_scroll(g_lv); + if (r_lv.w > 0) meow_ui_invalidate(r_lv.x, r_lv.y, r_lv.w, r_lv.h); + return 1; + } + return 0; +} + +/* swipe UP/DOWN:宫格/列表聚焦时滚自身(del 已按方向加权),1=已消费。 */ +int sdk2_swipe(int del) +{ + if (!s2_built) return 0; + if (g_grid_focus && g_grid) { + meow_lazygrid_scroll_by(g_grid, del); + g_grid_scroll = meow_lazygrid_scroll(g_grid); + if (r_grid.w > 0) meow_ui_invalidate(r_grid.x, r_grid.y, r_grid.w, r_grid.h); + return 1; + } + if (g_lv_focus && g_lv) { + meow_listview_scroll_by(g_lv, del); + g_lv_scroll = meow_listview_scroll(g_lv); + if (r_lv.w > 0) meow_ui_invalidate(r_lv.x, r_lv.y, r_lv.w, r_lv.h); + return 1; + } + return 0; +} + +/* 本次手势是否已被本子视图实时滚过(demo_swipe 早退用,防 on_swipe 二次滚动——同 list 的 g_drag_list)。 */ +int sdk2_dragging(void) +{ + return (g_drag_grid || g_drag_lv) ? 1 : 0; +} + +/* ============================================================ */ +/* 子视图生命周期(on_enter/on_leave 由 SDK 递归驱动;on_destroy 清引用) */ +/* ============================================================ */ +static void sdk2_page_enter(meow_view *v, void *ctx) +{ + (void)v; (void)ctx; + meow_log(MEOW_LOG_INFO, "demo sdk2 view enter"); +} +static void sdk2_page_leave(meow_view *v, void *ctx) +{ + (void)v; (void)ctx; + meow_log(MEOW_LOG_INFO, "demo sdk2 view leave"); +} +static void sdk2_page_destroy(meow_view *v, void *ctx) +{ + (void)v; (void)ctx; + if (g_sel) { meow_select_free(g_sel); g_sel = NULL; } + s2_built = 0; + g_sdk2_view = NULL; /* meow_view_free 递归释放前清引用,防悬垂 */ +} + +meow_view *sdk2_view_create(void) +{ + static const meow_view_hooks h = { + .on_enter = sdk2_page_enter, + .on_leave = sdk2_page_leave, + .on_destroy = sdk2_page_destroy, + .on_draw = sdk2_draw_on, + }; + /* id 用非页面值:容器不占 g_view_by_id(那是 page_push/pop 顶层页查找表)。 */ + g_sdk2_view = meow_view_create(0x2000, MEOW_VIEW_CONTAINER, &h, NULL); + /* meow_select 实例一次性创建(控制态随本子视图存活,不受内容重建影响);行标题一次性填充。 */ + sdk2_init_rows(); + if (g_sdk2_view && !g_sel) { + g_sel = meow_select_create("Sample rate", 0); + if (g_sel) { + for (int i = 0; i < SEL_N; i++) meow_select_add_item(g_sel, g_sel_items[i]); + meow_select_set_on_change(g_sel, sdk2_sel_change, NULL); + } + } + return g_sdk2_view; +} +meow_view *sdk2_view_get(void) +{ + return g_sdk2_view; +} diff --git a/patch_v2/meow_amsd/meow_amsd b/patch_v2/meow_amsd/meow_amsd index fadd0b0..d1f8977 100755 Binary files a/patch_v2/meow_amsd/meow_amsd and b/patch_v2/meow_amsd/meow_amsd differ diff --git a/patch_v2/meow_audiod/meow_audiod.c b/patch_v2/meow_audiod/meow_audiod.c index 493315d..c834211 100644 --- a/patch_v2/meow_audiod/meow_audiod.c +++ b/patch_v2/meow_audiod/meow_audiod.c @@ -1367,7 +1367,11 @@ static void *io_thread(void *arg) memcpy(snaps, pfds, sizeof snaps); pthread_mutex_lock(&g_mu); (void)pr; - /* 处理 listen */ + /* 处理 listen:一次 poll 收 1 个连接。**不可改用「while accept 排空积压」**——在连接 + * 风暴下(对端持续 connect)accept 永不 EAGAIN → 持 g_mu 无限循环 → IO 线程卡死、 + * 播放/路由/QUERY 全部被 g_mu 阻塞(宿主 T10 实测:drain 版风暴下 probe/OPEN 全 FAIL)。 + * 单次 accept + 下一轮 poll 再收,既有界又保持 listener/IO 可服务(原版行为即正确, + * fd=N 反复 accept 是**对端短连接**所致,非本循环缺陷)。 */ if ((snaps[0].revents & POLLIN)) { int cfd = accept(lfd, NULL, NULL); if (cfd >= 0) { diff --git a/patch_v2/meow_audiod/test_audiod_host b/patch_v2/meow_audiod/test_audiod_host index 184a555..de26bbc 100755 Binary files a/patch_v2/meow_audiod/test_audiod_host and b/patch_v2/meow_audiod/test_audiod_host differ diff --git a/patch_v2/meow_audiod/test_audiod_host.c b/patch_v2/meow_audiod/test_audiod_host.c index ad1373e..beeb2ee 100644 --- a/patch_v2/meow_audiod/test_audiod_host.c +++ b/patch_v2/meow_audiod/test_audiod_host.c @@ -651,7 +651,44 @@ int main(void) check(0, "pipe for T9 child"); } } - stop_audiod(); + /* ---- T10:连接表溢出丢弃护栏与回收(回归 fd=N accept 风暴,§2026-09-02 真机 1489 次)。 + * 背景:CLI_MAX=8(§12「客户端全有界」护栏)。ncm「每次 audio_out_open 新建一条短连接」 + * (旧 audio_out_sync_volume)在边下边播/切歌 churn 下会反复占用连接槽:8 张表占满后, + * 新 connect 的 accept 因 cli_alloc 失败被**安全丢弃**(日志 "client limit reached, drop"), + * 表回收后 NEW 连接照常服务。本用例: + * ① 洪泛 connect→write→close ×60(远超 CLI_MAX=8,走上述 drop 护栏); + * ② 断言 audiod **不 wedge/不崩**(此后 SIGTERM 干净退出); + * ③ 等表回收(短连接已 close,EOF 逐 poll 回收)后,新 OPEN 仍被授权(fd 复用回收)。 + * 「同一 fd=N 被反复 accept」= 对端短连接 + fd 低位复用,本是**正常回收机理**,非循环缺陷; + * 根因在对端每 open 建新连接,由 ncm side audio_out.c「音量同步每进程一次」消除(vol_sync_sim)。 ---- */ + printf("[T10 client-table overflow guard & recycle]\n"); + unlink(TSOCK); + g_audiod_pid = start_audiod_ex("null", NULL, NULL); + usleep(400000); + int burst = 0; + for (int i = 0; i < 60; i++) { /* 洪泛 > CLI_MAX=8 */ + struct cli v; + if (cli_connect(&v) == 0) { + int32_t pct = 50; + if (sf(v.fd, MSG_AUDIO_VOLUME, 7, &pct, sizeof pct) == 0) burst++; + close(v.fd); /* 对端读 EOF → cli_free 回收 */ + } + } + printf(" issued=%d/60 (cap by CLI_MAX=8 overflow drops)\n", burst); + check(burst > 0, "connections issued (overflow hits drop guard)"); + /* ③ 等表回收(≤1s:EOF 逐 poll 回收)后新 OPEN 仍被授权 */ + usleep(700000); + int eok = 0; + { + struct cli e; + if (cli_connect(&e) == 0) { + m_audio_open_ack_t ak; + if (do_open(&e, 48000, 2, AUDIO_FMT_S16, &ak) == 0 && ak.err == MEOW_OK) eok = 1; + close(e.fd); + } + } + check(eok, "fresh OPEN after overflow (table recycled, no exhaustion/wedge)"); + check(stop_audiod_tmo(5000) != 0, "audiod not wedged by overflow (SIGTERM clean exit)"); printf("=== SUMMARY: pass=%d fail=%d ===\n", g_pass, g_fail); return g_fail ? 1 : 0; diff --git a/patch_v2/meow_crash/meow_crash b/patch_v2/meow_crash/meow_crash index ead5c0f..a6730e4 100755 Binary files a/patch_v2/meow_crash/meow_crash and b/patch_v2/meow_crash/meow_crash differ diff --git a/patch_v2/meow_crash/meow_crash.c b/patch_v2/meow_crash/meow_crash.c index a17e589..cc484ce 100644 --- a/patch_v2/meow_crash/meow_crash.c +++ b/patch_v2/meow_crash/meow_crash.c @@ -178,7 +178,9 @@ static void log_cleanup_old(void) struct dirent *de; while ((de = readdir(d)) != NULL && n < 128) { const char *nm = de->d_name; - if (strncmp(nm, "meow_", 5) != 0 && strncmp(nm, "crash_", 6) != 0) continue; + /* 收纳 meow_/crash_/perf_/hang_(perf_=RMM 基线、hang_=卡死快照,同 logs/ 1GB 纪律)。 */ + if (strncmp(nm, "meow_", 5) != 0 && strncmp(nm, "crash_", 6) != 0 && + strncmp(nm, "perf_", 5) != 0 && strncmp(nm, "hang_", 5) != 0) continue; char path[CRASH_PATH_MAX]; if (snprintf(path, sizeof path, "%s/%s", logs, nm) >= (int)sizeof path) continue; struct stat st; diff --git a/patch_v2/meow_crash/meow_crash_host b/patch_v2/meow_crash/meow_crash_host index fa468d8..4abc4f4 100755 Binary files a/patch_v2/meow_crash/meow_crash_host and b/patch_v2/meow_crash/meow_crash_host differ diff --git a/patch_v2/meow_launcherd/app_launcher.c b/patch_v2/meow_launcherd/app_launcher.c index 17ac8eb..f4a9527 100644 --- a/patch_v2/meow_launcherd/app_launcher.c +++ b/patch_v2/meow_launcherd/app_launcher.c @@ -2171,6 +2171,12 @@ static int launch_app_chain(struct app *a, const char *chain) /* §10.5:App 前台会话结束,清会话标记(ipc_dispatch 不再受理 MSG_NOTIF_OPEN) */ g_app_session_active = 0; meow_notif_open_clear(); + /* M2.17 兜底:App 会话结束(waitpid 回收/崩溃/被杀,回主界面)清 fg_act —— + * App 在 keep_awake activity(如 ncm 播放页)被 OOM/崩溃杀死且未弹回 home 时,g_fg_act 会 + * 滞留 "",使主页 fg_keep_awake()==1 不再自动息屏。此处兜底清空(App 已退出/死亡, + * 主界面非 keep_awake);App 存活时 fg_act 保持不清,仅会话整体结束才清。正常路径 App + * 弹回 home 已由 ACT_FOCUS(home) 更新(非 keep_awake),此兜底与其幂等不冲突。 */ + meow_fg_act_back(); /* AMSD_DESIGN §5.1:崩溃尾巴不丢 —— App 已死,终局排空剩余 stdout/stderr(含未完结行) * 先转发 amsd,再报 EXIT / crash。仅当有读端(管道建立成功)才 drain。 */ if (out_fd >= 0) { diff --git a/patch_v2/meow_launcherd/exit b/patch_v2/meow_launcherd/exit new file mode 100644 index 0000000..c633992 --- /dev/null +++ b/patch_v2/meow_launcherd/exit @@ -0,0 +1,34 @@ +PASS S1 drain required (app was SIGSTOP/frozen) +PASS S1 no drain needed (media keep-alive, never stopped) +PASS S1 drain window > SDK 200ms throttle +PASS S1b keep-alive app needs EVIOCGRAB isolation +PASS S1b frozen app -> SIGCONT drain, not grab +PASS S1b keep-alive: grab path (drain==0) +PASS S1b frozen: drain path (grab==0) +PASS S2 force status bar re-sync after unlock +PASS S2b lock re-cover ms > SDK suppression window (200ms) +PASS S2b lock re-cover ms = 300 (marker-window margin) +PASS S1c wake, no sleep -> no lock +PASS S1c timeout sleep (auto) -> lock +PASS S1c manual power sleep (manual) -> lock (was missing) +PASS S1c both -> lock +PASS S4 system 0 (never) -> lock caps 30s +PASS S4 system absent -> lock caps 30s +PASS S4 system 30 -> 30 +PASS S4 system 60 -> 30 (cap, shorter) +PASS S4 system 10 -> 10 (respect shorter) +PASS S4 system 1 -> 1 (respect shorter) +PASS S4 idle>=off & screen on -> self sleep +PASS S4 idle not yet +PASS S4 screen off -> no re-sleep +PASS S4 power edge -> wake to lock +PASS S4 touch -> wake to lock +PASS S4 no wake input -> stay asleep +PASS S3 app died -> unified exit path (redraw home) +PASS S3 app alive -> normal restore +PASS S3 abort=0 (menu lock, no overlay pid) +PASS S3 abort=0 (no overlay pid, zombie ignored) +PASS S3 abort=0 (overlay pid, app alive) +PASS S3 abort=1 (overlay pid + zombie -> abort lock) + +ALL PASS (32 passed, 0 failed) diff --git a/patch_v2/meow_launcherd/keys.c b/patch_v2/meow_launcherd/keys.c index 4c568ba..539e7d6 100644 --- a/patch_v2/meow_launcherd/keys.c +++ b/patch_v2/meow_launcherd/keys.c @@ -58,7 +58,17 @@ void meow_panel_on(void) { int f = open(FB_DEV, O_RDWR); if (f >= 0) { ioctl(f, FBIOBLANK, 0); close(f); } - write_brightness(meow_saved_brightness > 0 ? meow_saved_brightness : 60, "meow_panel_on"); + /* 亮度恢复以**用户设置**为准,而非 meow_saved_brightness(可能过期)。 + * watchdog 独立息屏(wd_panel_off 直接 write_brightness(0),见 meow_watchdog.c)**不会** + * 更新本进程 meow_saved_brightness——若用其恢复会回错值(真机「PIN 亮屏亮度跳高」, + * 2026-09-03)。settings 已从盘上读到→按 display.brightness(clamp)恢复;未读到(早启动) + * →回退 meow_saved_brightness 现值(保持旧行为,防开机竞态)。 */ + if (meow_settings_loaded()) { + int b = meow_settings_brightness_clamped(); + if (b > 0) { write_brightness(b, "meow_panel_on"); meow_saved_brightness = b; } + } else { + write_brightness(meow_saved_brightness > 0 ? meow_saved_brightness : 60, "meow_panel_on"); + } meow_screen_on = 1; /* M2.8 Bug 3-residual:亮屏必须用 fb_pan_buf0_force 无条件重发 pan。fb_norm_buf0 在 * 缓存 fb_active_yoffset==0 时提前返回,而 FBIOBLANK(4)->(0) 后 LCDC 扫描地址可能已复位 diff --git a/patch_v2/meow_launcherd/lock_screen_ui.c b/patch_v2/meow_launcherd/lock_screen_ui.c index 3402d8c..ca41631 100644 --- a/patch_v2/meow_launcherd/lock_screen_ui.c +++ b/patch_v2/meow_launcherd/lock_screen_ui.c @@ -38,6 +38,16 @@ static const int lk_keys[LK_ROWS][LK_COLS] = { { LK_ACT_CLR, 0, LK_ACT_BS }, }; +/* ---- M7.1b 锁屏控件化:PIN 数字键盘 / 时钟 / 提示 / PIN 圆点 改用 SDK 控件 ---- + * 旧自绘锁屏 UI 保留在 #if 0(回退位,见 lk_draw/lk_key_hit),新 SDK 渲染默认启用; + * run_lock_screen 会话骨架(EVIOCGRAB / 自睡 / 解锁脏区修复 / 物理键转发)不动,只替换 + * 它调用的绘制/命中函数体。控件 owner 钉 LK_SENTINEL_ACT(子页 0x52 之外的独立哨兵)。 */ +#define LK_SENTINEL_ACT 0x51 /* 锁屏控件 owner 哨兵(与子页 SUB_SENTINEL_ACT=0x52 区分) */ +#define LK_DOWN_NONE (-10) /* 无键按下哨兵(关键值范围 LK_ACT_CLR=-2/-1/0-9) */ +static meow_widget *lk_key_w[LK_ROWS][LK_COLS]; /* 12 键按钮(meow_widget_custom) */ +static int lk_widgets_ready = 0; +static int g_lk_down_key = LK_DOWN_NONE; /* 当前按下的键(跟手指按压态) */ + /* 键盘按钮 row*3+col 的命中矩形(x,y,w,h) */ static void lk_key_rect(int row, int col, int *x, int *y, int *w, int *h) { @@ -79,6 +89,121 @@ static void lk_draw_pin(int entered, const meow_ui_theme_t *th) lk_pin_dot(x0 + i * gap, y, r, i < entered, th->primary, th->surface); } +/* ---- M7.1b 控件化:SDK meow_widget 键盘 + meow_font 文字 ---- */ + +/* 数字键标签(1-9/0 单字符;static 缓冲各调用立即消费,无跨调用残留) */ +static const char *lk_key_digit(int key) +{ + static char d[2]; + d[0] = (char)('0' + key); + d[1] = 0; + return d; +} + +/* 键渲染:surfaceContainer 圆角底(MD3)+ 按压态 primary 26% α + 居中标签(SDK meow_font)。 + * 颜色全部取 meow_theme_get()(SDK 单一权威:MD3/MIUIx × 明暗自动);读 w->state 的 + * MEOW_WS_PRESSED 位(跟手指,由 lk_set_down_key 置/清)。 */ +static void lk_key_render(meow_widget *w, const meow_theme *th, void *ctx) +{ + int key = (int)(intptr_t)ctx; + int rc[4]; meow_widget_rect(w, rc); /* 绝对坐标 x,y,w,h(控件不透明,用 SDK 只读几何) */ + int x = rc[0], y = rc[1], ww = rc[2], hh = rc[3]; + unsigned st = meow_widget_state(w); + int rad = (th->radius > 0) ? th->radius : 10; + meow_gfx_fill_round(x, y, ww, hh, rad, th->surfaceContainer); + if (st & MEOW_WS_PRESSED) + meow_gfx_fill_round_alpha(x, y, ww, hh, rad, th->primary, 66); + const char *label; + int size; + if (key >= 0) { size = 30; label = lk_key_digit(key); } + else if (key == LK_ACT_CLR) { size = 22; label = mfont_ready ? "清空" : "C"; } + else { size = 22; label = mfont_ready ? "退格" : "DEL"; } + int cx = x + ww / 2; + int cy = y + hh / 2 - meow_ui_text_height(size) / 2; + meow_ui_text_center(cx, cy, label, size, th->onSurface); +} + +/* 懒创建 12 键(owner=LK_SENTINEL_ACT;仅首次调用建一次)。布局几何沿用 lk_key_rect。 */ +static void lk_widgets_build(void) +{ + if (lk_widgets_ready) return; + meow_ui_activity_push(LK_SENTINEL_ACT); + for (int r = 0; r < LK_ROWS; r++) { + for (int c = 0; c < LK_COLS; c++) { + int x, y, w, h; lk_key_rect(r, c, &x, &y, &w, &h); + lk_key_w[r][c] = meow_widget_custom_create(x, y, w, h, lk_key_render, NULL, + (void *)(intptr_t)lk_keys[r][c]); + } + } + meow_ui_activity_pop(); + lk_widgets_ready = 1; +} + +/* 键值 -> 按钮(按压态置/清位用)。 */ +static meow_widget *lk_widget_for_key(int key) +{ + for (int r = 0; r < LK_ROWS; r++) + for (int c = 0; c < LK_COLS; c++) + if (lk_keys[r][c] == key) return lk_key_w[r][c]; + return NULL; +} + +/* 坐标 -> 键值(SDK meow_widget_hit;owner==active 已由调用方保证)。未命中返回 LK_DOWN_NONE。 */ +static int lk_key_value_at(int x, int y) +{ + for (int r = 0; r < LK_ROWS; r++) + for (int c = 0; c < LK_COLS; c++) + if (lk_key_w[r][c] && meow_widget_hit(lk_key_w[r][c], x, y)) + return lk_keys[r][c]; + return LK_DOWN_NONE; +} + +/* 更新按压态(跟手指):清旧键状态位、置新键;返回 1=按键变化(调用方据此重绘)。 */ +static int lk_set_down_key(int key) +{ + if (key == g_lk_down_key) return 0; + meow_widget *old = lk_widget_for_key(g_lk_down_key); + if (old) meow_widget_set_state(old, MEOW_WS_PRESSED, 0); + meow_widget *nw = lk_widget_for_key(key); + if (nw) meow_widget_set_state(nw, MEOW_WS_PRESSED, 1); + g_lk_down_key = key; + return 1; +} + +/* PIN 输入圆点(● 遮罩:已输入=primary 实心;未输入=primary 环 + surface 孔)。 */ +static void lk_draw_pin_dots(int entered, const meow_theme *mt) +{ + int cx = fb_xres / 2; + int y = 210; + int gap = 48, r = 20; + int x0 = cx - (3 * gap) / 2; + for (int i = 0; i < LCK_PIN_LEN; i++) { + int dx = x0 + i * gap; + fill_circle(dx, y, r, mt->primary); + if (i >= entered) fill_circle(dx, y, r - 3, mt->surface); + } +} + +/* 键命中:旧自绘(lk_key_rect+mt_in)保留在 #if 0(回退位);M7.1b 走 SDK meow_widget_hit + * (owner==active + enabled 双闸门在 SDK 内;命中点=按下原点 mt->down_x/down_y,与原 mt_in 同源)。 */ +#if 0 +static int lk_key_hit(const meow_touch *mt, int row, int col) +{ + int x, y, w, h; lk_key_rect(row, col, &x, &y, &w, &h); + return mt_in(mt, x, y, w, h); +} +#else +static int lk_key_hit(const meow_touch *mt, int row, int col) +{ + if (!lk_key_w[row][col]) return 0; + return meow_widget_hit(lk_key_w[row][col], mt->down_x, mt->down_y); +} +#endif + +#if 0 +/* ============ 旧自绘锁屏整帧(M7.1b 回退位)============ + * 原 lk_draw:fill_rect 全屏底 + lk_text(时钟/状态)+ lk_draw_pin(●)+ fill_round_rect 键盘。 + * 一键回退:把下面 #if 0 改成 #if 1(并把新 lk_draw 的 #else 分支由 #else 改走旧轮廓),设备即回旧观感。 */ static void lk_draw(int entered, const char *status, time_t locked_until) { const meow_ui_theme_t *th = s_ui_theme(); @@ -116,6 +241,36 @@ static void lk_draw(int entered, const char *status, time_t locked_until) fb_pan_buf0_force(); meow_ui_flush(); } +#else +/* ============ M7.1b 锁屏控件化整帧(默认启用)============ + * 整帧不透明 surface 底(§310 脏区修复关键:全屏覆盖,无 App 帧透出)+ SDK 字体时钟/提示 + * + 主题色 PIN 圆点 + meow_widget 键盘(surfaceContainer 圆角 / 按压态 primary 26% α / MD3 主题)。 + * 颜色全部取 meow_theme_get()(SDK 单一权威:MD3/MIUIx × 明暗自动)。 */ +static void lk_draw(int entered, const char *status, time_t locked_until) +{ + const meow_theme *mt = meow_theme_get(); + meow_ui_fill_rect(0, 0, fb_xres, fb_yres, mt->surface); /* 全屏不透明表面底 */ + lk_widgets_build(); /* 懒创建 12 键 */ + /* 时钟/提示(居中,SDK meow_font):锁定倒计时标题 */ + meow_ui_text_center(fb_xres / 2, 120, locked_until > 0 ? "已锁定" : "输入 PIN 解锁", + 24, mt->onSurface); + /* PIN 输入圆点(● 遮罩) */ + lk_draw_pin_dots(entered, mt); + /* 状态行:错误提示(PIN 错误)着 error;倒计时/其它信息用 onSurfaceVariant */ + int is_err = status[0] && (strstr(status, "PIN 错误") != NULL || + strstr(status, "WRONG PIN") != NULL); + uint32_t status_col = status[0] ? (is_err ? mt->error : mt->onSurfaceVariant) + : mt->onSurfaceVariant; + meow_ui_text_center(fb_xres / 2, 260, status, 18, status_col); + /* 12 键(SDK meow_widget;命中走 lk_key_hit → meow_widget_hit) */ + for (int r = 0; r < LK_ROWS; r++) + for (int c = 0; c < LK_COLS; c++) + if (lk_key_w[r][c]) meow_widget_draw(lk_key_w[r][c]); + meow_dirty_full(); + fb_pan_buf0_force(); + meow_ui_flush(); +} +#endif /* 阻塞式锁屏页。返回 1=已解锁,0=锁屏期间电源键息屏。 */ int run_lock_screen(void) @@ -159,7 +314,26 @@ int run_lock_screen(void) return 1; } - lk_draw(entered, status, lock_until); /* 首帧(fill_rect 全屏不透明主题底 + 键盘) */ + /* M7.1 解锁恢复:锁屏是整帧不透明覆盖(lk_draw fill_rect 全屏),退出后底层界面不会自动重绘 + * —— 只有 App 自绘的控件区域刷新,未刷新区残留锁屏 PIN 最后一帧(解锁后整屏脏区根因)。此处 + * 在锁屏首扑前把底层帧(SDK back_buf = 进入前的 App 帧/主界面帧)快照下来;解锁(unlocked=1) + * 时整帧回写 back_buf + present,强制底层界面完整重绘(app_launcher.c 的 lock-overlay 关闭路径 + * 随后 pm_capture_app_frame 读 fb0 即得到正确 App 帧,不再读到锁帧)。 */ + unsigned char *lk_bg_snap = NULL; + size_t lk_bg_len = 0; + if (back_buf_ok && fb && fb_line > 0 && fb_yres > 0) { + lk_bg_len = (size_t)fb_line * (size_t)fb_yres; + lk_bg_snap = malloc(lk_bg_len); + if (lk_bg_snap) memcpy(lk_bg_snap, fb, lk_bg_len); + else { lk_bg_len = 0; logmsg("lock: bg snapshot malloc fail (len=%zu)", lk_bg_len); } + } + + /* M7.1b:锁屏控件 owner 闸门打开(meow_widget_hit / 按压态需 active==LK_SENTINEL_ACT)。 + * 会话骨架(EVIOCGRAB/自睡/解锁脏区修复)不受影响;done/died 处对称 pop。 */ + meow_ui_activity_push(LK_SENTINEL_ACT); + g_lk_down_key = LK_DOWN_NONE; + + lk_draw(entered, status, lock_until); /* 首帧(全屏不透明主题底 + 控件化键盘) */ int unlocked = 0; unsigned long long entry_ms = now_ms(); @@ -173,6 +347,9 @@ int run_lock_screen(void) unsigned long long last_act = now_ms(); int asleep = 0; int prev_pwr_down = 0; + /* M7.1 锁屏期物理键保留语义(音量/媒体键):边沿跟踪基线(锁屏是 EVIOCGRAB 独占真模态, + * 这些键只能在本层显式处理,不会转发到系统)。 */ + int prev_vold = 0, prev_volu = 0, prev_play = 0, prev_prv = 0, prev_nxt = 0; for (;;) { time_t now = time(NULL); int redraw = 0; @@ -246,6 +423,7 @@ int run_lock_screen(void) meow_panel_on(); asleep = 0; last_act = now_ms(); + g_lk_down_key = LK_DOWN_NONE; /* M7.1b:唤醒复位按压态基线 */ lk_draw(entered, status, lock_until); /* 重画锁屏页回锁 */ continue; } @@ -256,6 +434,50 @@ int run_lock_screen(void) meow_panel_off(); break; /* return 0 */ } + /* M7.1 锁屏期物理键仍执行其语义(用户定案:音量键调音量、媒体键控播放/上下曲)。 + * 锁屏层 EVIOCGRAB 独占这些输入(真模态),不转发系统,故必须在此显式处理,不能依赖别处。 + * 音量沿用 launcher 既有递增/递减路径(settings_volume_adj —— 起 1% 线性渐变, + * 长按由下方 vol_ramp_tick 每圈推进);播放/上下曲经 meow_media_playctl 下发媒体指令。 */ + int vold = (kp_keys.ok) && kp_is_down(&kp_keys, KEY_VOLUMEDOWN); + int volu = (kp_keys.ok) && kp_is_down(&kp_keys, KEY_VOLUMEUP); + int play = (kp_keys.ok) && kp_is_down(&kp_keys, KEY_PLAYPAUSE); + int prv = (kp_keys.ok && kp_is_down(&kp_keys, KEY_PREVIOUSSONG)) || + (kp_power.ok && kp_is_down(&kp_power, KEY_PREVIOUSSONG)); + int nxt = (kp_keys.ok && kp_is_down(&kp_keys, KEY_NEXT)) || + (kp_power.ok && kp_is_down(&kp_power, KEY_NEXT)); + if (vold && !prev_vold) { settings_volume_adj(-settings_volume_step); last_act = now_ms(); } + if (volu && !prev_volu) { settings_volume_adj(+settings_volume_step); last_act = now_ms(); } + if (play && !prev_play) { + const meow_media_session_t *ms = meow_media_current(); + if (ms) { + uint32_t c = (ms->state == MEOW_MEDIA_PLAYING) ? 2u : 1u; /* 暂停/播放 */ + int rc = meow_media_playctl(c, 0); + logmsg("lock: play key -> media cmd=%u rc=%d (%s)", c, rc, ms->app_id); + } else { + logmsg("lock: play key -> media-reserved (no media source)"); + } + last_act = now_ms(); + } + if (prv && !prev_prv) { + if (meow_media_current()) { int rc = meow_media_playctl(3, 0); logmsg("lock: prev key -> cmd=3 rc=%d", rc); } + else logmsg("lock: prev key -> media-reserved (no media source)"); + last_act = now_ms(); + } + if (nxt && !prev_nxt) { + if (meow_media_current()) { int rc = meow_media_playctl(4, 0); logmsg("lock: next key -> cmd=4 rc=%d", rc); } + else logmsg("lock: next key -> media-reserved (no media source)"); + last_act = now_ms(); + } + prev_vold = vold; prev_volu = volu; prev_play = play; prev_prv = prv; prev_nxt = nxt; + /* 长按音量键线性渐变(同主菜单 vol_ramp 路径,每圈推进 1%)。锁屏态不刷状态栏 + * (状态栏被锁屏页覆盖),仅保持真实音量/DAC 同步。 */ + if (vol_ramp_dir != 0) { + int held = (vol_ramp_dir > 0) + ? (kp_keys.ok && kp_is_down(&kp_keys, KEY_VOLUMEUP)) + : (kp_keys.ok && kp_is_down(&kp_keys, KEY_VOLUMEDOWN)); + int going = vol_ramp_tick(held); + if (!going) logmsg("lock: volume ramp end -> %d", settings_volume_get()); + } /* 空闲超时 → 自睡(锁态保持,仍在锁屏循环,唤醒回锁屏页) */ if (meow_screen_on && now_ms() - last_act >= lk_off_ms) { logmsg("lock: idle %ds -> self screen off (stay locked)", lk_off_sec); @@ -265,8 +487,6 @@ int run_lock_screen(void) } } } - (void)kp_is_down(&kp_keys, KEY_VOLUMEDOWN); - (void)kp_is_down(&kp_keys, KEY_VOLUMEUP); /* 触摸:只认键盘 tap(滑动手势忽略:不左缘返回、不通知中心下拉);触摸即输入活动,重置自睡计时 */ if (tfd >= 0 && !asleep) { @@ -280,17 +500,24 @@ int run_lock_screen(void) if (type == EV_ABS) { if (code == ABS_X || code == ABS_MT_POSITION_X) mt_abs(&mt, val, mt.last_y); else if (code == ABS_Y || code == ABS_MT_POSITION_Y) mt_abs(&mt, mt.last_x, val); + /* M7.1b 跟手指按压态:down 且两轴有效才定键(goodix 时序免疫); + * 键变化(滑动换键)即时重绘按压高亮。 */ + if (mt.down && mt.last_x >= 0 && mt.last_y >= 0) { + if (lk_set_down_key(lk_key_value_at(mt.last_x, mt.last_y))) + redraw = 1; + } } else if (type == EV_KEY && code == BTN_TOUCH) { if (val && !mt.down) { mt_down(&mt); } else if (!val && mt.down) { + lk_set_down_key(LK_DOWN_NONE); /* M7.1b:抬手清按压态 */ + redraw = 1; int dx, dy; if (mt_up(&mt, &dx, &dy)) { int handled = 0; for (int r = 0; r < LK_ROWS && !handled; r++) { for (int c = 0; c < LK_COLS && !handled; c++) { - int x, y, w, h; lk_key_rect(r, c, &x, &y, &w, &h); - if (!mt_in(&mt, x, y, w, h)) continue; + if (!lk_key_hit(&mt, r, c)) continue; /* M7.1b SDK 控件命中 */ handled = 1; int key = lk_keys[r][c]; if (lock_until > 0) break; /* 锁定期间键盘不响应 */ @@ -339,6 +566,17 @@ int run_lock_screen(void) usleep(POLL_MS * 1000); } done: + meow_ui_activity_pop(); /* M7.1b:锁屏控件 owner 闸门关闭(与进场 push 对称) */ + g_lk_down_key = LK_DOWN_NONE; + /* M7.1 解锁恢复:把锁屏首扑前快照的底层帧整帧回写 back_buf + present,强制底层 App/主界面 + * 完整重绘(消除「解锁后整屏 = PIN 最后一帧,仅 App 控件自绘区刷新」的脏区残留)。 */ + if (unlocked && lk_bg_snap && back_buf_ok && fb && lk_bg_len > 0) { + memcpy(fb, lk_bg_snap, lk_bg_len); + meow_dirty_full(); + meow_ui_flush(); + logmsg("lock: unlock -> restore underlying frame (full repaint)"); + } + if (lk_bg_snap) { free(lk_bg_snap); lk_bg_snap = NULL; lk_bg_len = 0; } if (tfd >= 0) { int grab = 0; /* 显式释放 EVIOCGRAB(关闭 tfd 也会自动解除,双保险) */ ioctl(tfd, EVIOCGRAB, &grab); @@ -349,6 +587,9 @@ done: logmsg("lock: exit unlocked=%d screen_on=%d", unlocked, meow_screen_on); return unlocked; died: + meow_ui_activity_pop(); /* M7.1b:锁屏控件 owner 闸门关闭(与进场 push 对称) */ + g_lk_down_key = LK_DOWN_NONE; + if (lk_bg_snap) { free(lk_bg_snap); lk_bg_snap = NULL; lk_bg_len = 0; } if (tfd >= 0) { int grab = 0; ioctl(tfd, EVIOCGRAB, &grab); diff --git a/patch_v2/meow_launcherd/main_menu.c b/patch_v2/meow_launcherd/main_menu.c index d51de46..9aabafe 100644 --- a/patch_v2/meow_launcherd/main_menu.c +++ b/patch_v2/meow_launcherd/main_menu.c @@ -20,7 +20,7 @@ int menu_opened_blank = 0; static void launch_fail_toast(char *toast, size_t n, const char *name) { const char *bd = meow_dep_block_reason(); - if (bd && bd[0]) snprintf(toast, n, "缺依赖: %s", bd); + if (bd && bd[0]) snprintf(toast, n, meow_str_zh("menu.missing_dep", "缺依赖: %s"), bd); else snprintf(toast, n, "CANNOT START: %s", name); } @@ -102,7 +102,8 @@ static void draw_header_content(void) int tsize = menu_title_size(); /* 标题字号接 font.size.title */ /* M2.6 标题真字体(字号接 font.size.title;回退点阵3x);MD3 on_surface 主文字色 */ if (mfont_ready) { - meow_font_drawtext((uint16_t *)fb, 24, 48, "MeowOS 应用", rgb565(th->on_surface), tsize); + meow_font_drawtext((uint16_t *)fb, 24, 48, meow_str_zh("menu.title", "MeowOS 应用"), + rgb565(th->on_surface), tsize); fill_rect(0, 48 + meow_font_line_height(tsize) + 2, fb_xres, 2, th->outline); } else { draw_text(24, 28, "MEOWAPPS", 3, th->on_surface); @@ -171,6 +172,37 @@ static meow_img *s_app_icons[MAX_APPS]; /* 当前按压命中的应用格(-1=无);触摸按下置位、抬起清除(按压态视觉) */ static int g_menu_pressed = -1; +/* ================================================================== */ +/* M7.4:主界面应用区迁 SDK 虚拟化控件(meow_lazygrid 宫格 + meow_listview 列表 + meow_scroll)。 + * + * 铁律(M7 §4/§5):开机必经主界面,最高风险。本块只替换「绘制函数体 + 命中函数体」—— + * draw_menu 应用区体、menu_hit_app 体、menu_cell_rect 体;run_menu 会话骨架(触摸/热键/IPC/ + * 息屏/自动超时/跨会话)逐字保留。旧手绘路径(draw_menu 手绘循环/menu_draw_app_cell/menu_cell_rect + * 旧体/menu_hit_app 旧体)各以 #if 0 保留为回退位;新 SDK 路径默认启用。 + * + * 控件实例惰性创建一次(首跑 ensure),跨多次 run_menu 复用;热路径(draw/hit/scroll)零 malloc + * (SDK lazygrid/listview 均静态有界池)。布局切换(ui.home_layout)在绘/命中时按 menu_layout() + * 选择对应控件(两者同存,互不干扰);主题色全取 s_ui_theme()(= SDK meow_theme 单一权威)。 + * + * 几何同源:应用区视口菜单由 menu_app_region() 计算(与 menu_cell_rect/menu_hit_app/draw_menu + * 共用),保证虚拟化控件绘制的格/行位置 = 命中所用几何 = 脏区标脏几何,三者不错位。 */ +#define MENU_SDK_ACT 0x55 /* 主界面控件 owner 哨兵(不与电源菜单0x50/NowPlaying0x51/子页0x52/ + * 设置页0x53/通知中心0x54 冲突) */ + +/* 数据源 ctx(bind/count/draw 回调读应用数组;run_menu 的 apps[]/napps 每帧经此对齐)。 */ +typedef struct { struct app *apps; int napps; } menu_sdk_ctx_t; +static menu_sdk_ctx_t s_menu_ctx; /* 应用数据源(count/bind/draw 读) */ +static meow_lazygrid *s_menu_grid = NULL; /* 宫格(layout=0) */ +static meow_scroll *s_menu_grid_sc = NULL;/* 宫格滚动物理(scroll_attach) */ +static meow_widget *s_menu_list = NULL; /* 列表(layout=1) */ +static meow_scroll *s_menu_list_sc = NULL;/* 列表滚动物理 */ +static int s_menu_sdk_init = 0; /* 控件集已建(首跑建一次,跨次复用) */ + +/* 前置声明(menu_cell_rect 定义在下方 451 行,早于本块函数定义;此处仅给签名)。 */ +static void menu_app_region(int apps_top, int layout, int *vx, int *vy, int *vw, int *vh); +static int menu_grid_cell_w(void); +static void menu_sdk_ensure(int apps_top); + /* 背景状态(display.background:纯色名 or 图片路径) */ static meow_img *s_bg_img = NULL; /* 解码后的背景图(缓存,NULL=纯色) */ static int s_bg_is_img = 0; /* 1=图片背景 */ @@ -334,6 +366,27 @@ static void menu_bg_paint(void) } } +/* 局部重铺背景(x,y,w,h 区域):图片缓存 blit 对应行或纯色填充。供宫格滚动局部刷时重铺背景区, + * 修「自定义图片背景 + 超屏滚动」残影(M7.4 遗留②)——lazygrid_scroll_attach 的 redraw 只重画 + * 格而不重铺背景,超屏滚动后视口内遗留旧帧像素。与 menu_bg_paint 同源(s_bg_cache/menu_bg)。 */ +static void menu_bg_paint_region(int x, int y, int w, int h) +{ + int W = fb_xres, H = fb_yres; + if (x < 0) { w += x; x = 0; } + if (y < 0) { h += y; y = 0; } + if (x + w > W) w = W - x; + if (y + h > H) h = H - y; + if (w <= 0 || h <= 0) return; + if (s_bg_is_img && s_bg_img && s_bg_cache) { + int pitch = meow_ui_back_buf_pitch(); + for (int r = 0; r < h; r++) + memcpy(fb + (size_t)(y + r) * pitch + (size_t)x * 2, + s_bg_cache + (size_t)(y + r) * W + x, (size_t)w * 2); + } else { + fill_rect(x, y, w, h, menu_bg); + } +} + /* 应用图标路径:app.json icon 相对应用目录(dirname(exec))/ 绝对;失败回退卡根相对。 */ static void menu_icon_path(const struct app *a, char *out, int olen) { @@ -447,10 +500,30 @@ static void menu_draw_icon(const struct app *a, meow_img *icon, int x, int y, in if (icon) { meow_ui_img_cover(x, y, sz, icon); } } -/* 应用格几何(grid/list 统一入口;供 draw 与 touch 命中共用)。 */ +/* 应用格几何(grid/list 统一入口;供 draw 与 touch 命中共用)。 + * M7.4:改从 SDK 控件取当前滚动偏移(虚拟化后格/行位置随 offset 移动),并用 menu_app_region + * 同源几何 —— 保证与 meow_lazygrid/listview 绘制、命中的坐标一致。 */ static void menu_cell_rect(int i, int layout, int apps_top, int *cx, int *cy, int *cw, int *ch) { + int vx, vy, vw, vh; + menu_app_region(apps_top, layout, &vx, &vy, &vw, &vh); + (void)vh; + int sc = (layout == 0) ? (s_menu_grid ? meow_lazygrid_scroll(s_menu_grid) : 0) + : (s_menu_list ? meow_listview_scroll(s_menu_list) : 0); + if (layout == 0) { /* card grid */ + *cw = menu_grid_cell_w(); + *ch = MENU_GRID_CARD_H; + int row = i / MENU_GRID_COLS, col = i % MENU_GRID_COLS; + *cx = vx + col * (*cw + MENU_GRID_GAP); + *cy = vy + row * (*ch + MENU_GRID_GAP) - sc; + } else { /* list */ + *cw = vw; + *ch = MENU_LIST_ROW_H; + *cx = vx; + *cy = vy + i * MENU_LIST_ROW_H - sc; + } +#if 0 /* ===== M7.4 回退位:旧手绘几何(无滚动;删 #if 0 行切回)===== */ if (layout == 0) { /* card grid */ int row = i / MENU_GRID_COLS, col = i % MENU_GRID_COLS; *cw = (fb_xres - 2 * MENU_GRID_PAD - (MENU_GRID_COLS - 1) * MENU_GRID_GAP) / MENU_GRID_COLS; @@ -463,13 +536,22 @@ static void menu_cell_rect(int i, int layout, int apps_top, *cx = 8; *cy = apps_top + i * MENU_LIST_ROW_H; } +#endif } -/* 触摸命中应用格;未命中返回 -1。 */ +/* 触摸命中应用格;未命中返回 -1。 + * M7.4:手绘坐标循环替换为 SDK 控件命中(meow_lazygrid_hit / meow_listview_hit,含滚动换算 + + * 禁用/越界不响应);入口/会话判断(state/napps 门)保留。 */ static int menu_hit_app(int x, int y, struct app *apps, int napps, int state, int layout, int apps_top) { - (void)apps; /* 命中判定只依赖几何;apps 保留作接口(0-warning) */ + (void)apps; /* 命中交由 SDK 控件(滚动+网格映射);apps 保留作接口(0-warning) */ + (void)apps_top; /* SDK 控件自持视口几何;无需外部 apps_top */ + if (state != 0 || napps <= 0) return -1; + if (layout == 0) return s_menu_grid ? meow_lazygrid_hit(s_menu_grid, x, y) : -1; + else return s_menu_list ? meow_listview_hit(s_menu_list, x, y) : -1; +#if 0 /* ===== M7.4 回退位:旧手绘命中循环(删 #if 0 行切回)===== */ + (void)apps; if (state != 0 || napps <= 0) return -1; for (int i = 0; i < napps; i++) { int cx, cy, cw, ch; @@ -477,6 +559,7 @@ static int menu_hit_app(int x, int y, struct app *apps, int napps, int state, if (x >= cx && x < cx + cw && y >= cy && y < cy + ch) return i; } return -1; +#endif } /* 宫格卡片:surface_container 底 + 圆角 + 选中/按压态 tonal 高亮 + 图标 + 名称。 */ @@ -498,12 +581,14 @@ static void menu_draw_grid_cell(const struct app *a, meow_img *icon, int x, int int nw = ui_width(nm, 18); ui_draw(x + (w - nw) / 2, name_y, nm, a->ok ? th->on_surface : th->error, 18); if (!a->ok) { - int ew = ui_width("[缺失]", 14); - ui_draw(x + (w - ew) / 2, name_y + meow_font_line_height(18) + 2, "[缺失]", th->error, 14); + const char *miss = meow_str_zh("menu.missing", "[缺失]"); + int ew = ui_width(miss, 14); + ui_draw(x + (w - ew) / 2, name_y + meow_font_line_height(18) + 2, miss, th->error, 14); } else if (meow_bg_is_backgrounded(a->id)) { /* M4:后台保活中标注(播放中转后台的 App;run_menu 1s tick 按 change_seq 刷新) */ - int bw = ui_width("·后台", 14); - ui_draw(x + (w - bw) / 2, name_y + meow_font_line_height(18) + 2, "·后台", + const char *bg = meow_str_zh("menu.background", "·后台"); + int bw = ui_width(bg, 14); + ui_draw(x + (w - bw) / 2, name_y + meow_font_line_height(18) + 2, bg, th->on_surface_variant, 14); } } @@ -537,7 +622,7 @@ static void menu_draw_list_cell(const struct app *a, meow_img *icon, int x, int /* 副标题:package(无则 id),on_surface_variant;M4 后台保活中前缀「·后台 」 */ char sub[48]; snprintf(sub, sizeof sub, "%s%s", - meow_bg_is_backgrounded(a->id) ? "·后台 " : "", + meow_bg_is_backgrounded(a->id) ? meow_str_zh("menu.background_sp", "·后台 ") : "", a->package[0] ? a->package : (a->id[0] ? a->id : "")); if (sub[0]) { char subf[44]; @@ -546,6 +631,7 @@ static void menu_draw_list_cell(const struct app *a, meow_img *icon, int x, int } } +#if 0 /* ===== M7.4 回退位:旧应用格分派器(新路径由 menu_sdk_*_draw 适配器经 SDK self-draw 分派)===== */ /* 应用格绘制入口:按布局分派。 */ static void menu_draw_app_cell(const struct app *a, meow_img *icon, int x, int y, int w, int h, int sel, int pressed, int layout, @@ -554,6 +640,145 @@ static void menu_draw_app_cell(const struct app *a, meow_img *icon, int x, int y if (layout == 0) menu_draw_grid_cell(a, icon, x, y, w, h, sel, pressed, th); else menu_draw_list_cell(a, icon, x, y, w, h, sel, pressed, th); } +#endif + + +/* ================================================================== */ +/* M7.4:主界面 SDK 控件辅助(menu_app_region 几何 / menu_grid_cell_w / 数据源 bind / SDK 自绘 + * 适配器 / meow_scroll 回调 / menu_sdk_ensure 惰性创建)。与 draw_menu 同放,故可引用其上的 + * menu_apps_top/menu_draw_grid_cell/menu_draw_list_cell/menu_name_fit 等。 */ +/* 应用区视口矩形(与 menu_cell_rect/menu_hit_app/draw_menu 共用);layout 0=宫格 1=列表。 + * 顶=apps_top,底=设置行上方;宫格视口 x=12(PAD)、宽=xres-2*PAD;列表视口 x=8、宽=xres-16。 */ +static void menu_app_region(int apps_top, int layout, int *vx, int *vy, int *vw, int *vh) +{ + int bottom = EXIT_ROW - ROW_H - 4; /* 设置行顶(draw_menu fill_round(8, sy-4)) */ + *vy = apps_top; + if (layout == 0) { *vx = MENU_GRID_PAD; *vw = fb_xres - 2 * MENU_GRID_PAD; } + else { *vx = 8; *vw = fb_xres - 16; } + *vh = bottom - apps_top; + if (*vh < 0) *vh = 0; +} +/* 宫格单格宽(与旧 menu_cell_rect 同式:总宽-左右 PAD-内 gap,均分到 cols) */ +static int menu_grid_cell_w(void) +{ + return (fb_xres - 2 * MENU_GRID_PAD - (MENU_GRID_COLS - 1) * MENU_GRID_GAP) / MENU_GRID_COLS; +} + +/* ---- 数据源(count 每帧实时查;bind 仅进视口时填一次 item;阅读 s_menu_ctx)---- */ +static int menu_grid_count(void *ctx) { menu_sdk_ctx_t *c = (menu_sdk_ctx_t *)ctx; return c ? c->napps : 0; } +static void menu_grid_bind(int index, meow_lazygrid_item *item, void *ctx) +{ + menu_sdk_ctx_t *c = (menu_sdk_ctx_t *)ctx; + memset(item, 0, sizeof *item); + if (!c || index < 0 || index >= c->napps) return; + struct app *a = &c->apps[index]; + item->title = a->name; + item->subtitle = NULL; /* 名为主文本;状态/缺失/后台标注由自绘 draw 分色绘制 */ + item->icon = (index < MAX_APPS) ? s_app_icons[index] : NULL; + item->enabled = 1; /* 命中即启动(含缺 app → 启动失败→toast,同旧行为) */ + item->user = (void *)a; +} +static int menu_list_count(void *ctx) { menu_sdk_ctx_t *c = (menu_sdk_ctx_t *)ctx; return c ? c->napps : 0; } +static void menu_list_bind(int index, meow_lv_item *item, void *ctx) +{ + menu_sdk_ctx_t *c = (menu_sdk_ctx_t *)ctx; + memset(item, 0, sizeof *item); + if (!c || index < 0 || index >= c->napps) return; + struct app *a = &c->apps[index]; + item->title = a->name; + item->subtitle = NULL; /* 自绘 draw 显示 package/id 副标题(含「·后台」前缀) */ + item->icon = (index < MAX_APPS) ? s_app_icons[index] : NULL; + item->icon_kind = item->icon ? MEOW_LIST_ICON_IMG : MEOW_LIST_ICON_NONE; + item->enabled = 1; + item->user = (void *)a; +} + +/* ---- SDK 自绘适配器(把 SDK 的 (idx,x,y,w,h,state) 映射到旧格/行绘制内核,迁移其内容)---- */ +static void menu_sdk_grid_draw(int index, int x, int y, int w, int h, int state, void *ctx) +{ + menu_sdk_ctx_t *c = (menu_sdk_ctx_t *)ctx; + const meow_ui_theme_t *th = s_ui_theme(); + if (!c || index < 0 || index >= c->napps) return; + int sel = (state & MEOW_LAZYGRID_ST_SELECTED) ? 1 : 0; + int pressed = (g_menu_pressed == index) ? 1 : 0; + menu_draw_grid_cell(&c->apps[index], (index < MAX_APPS) ? s_app_icons[index] : NULL, + x, y, w, h, sel, pressed, th); +} +static void menu_sdk_list_draw(int index, int x, int y, int w, int h, int state, void *ctx) +{ + menu_sdk_ctx_t *c = (menu_sdk_ctx_t *)ctx; + const meow_ui_theme_t *th = s_ui_theme(); + if (!c || index < 0 || index >= c->napps) return; + int sel = (state & MEOW_LISTVIEW_ST_SELECTED) ? 1 : 0; + int pressed = ((state & MEOW_LISTVIEW_ST_PRESSED) || g_menu_pressed == index) ? 1 : 0; + menu_draw_list_cell(&c->apps[index], (index < MAX_APPS) ? s_app_icons[index] : NULL, + x, y, w, h, sel, pressed, th); +} + +/* ---- meow_scroll 回调(列表;包 listview。宫格用 meow_lazygrid_scroll_attach 自带回调)---- */ +static void menu_lv_sc_set(void *ctx, int off) { (void)ctx; if (s_menu_list) meow_listview_set_scroll(s_menu_list, off); } +static int menu_lv_sc_max(void *ctx) { (void)ctx; return s_menu_list ? meow_listview_max_scroll(s_menu_list) : 0; } +static int menu_lv_sc_get(void *ctx) { (void)ctx; return s_menu_list ? meow_listview_scroll(s_menu_list) : 0; } +/* 列表区重刷(含内容底 + listview),供 fling/滚动局部刷;不整屏。 */ +static void menu_lv_sc_redraw(void *ctx) +{ + (void)ctx; + int vx, vy, vw, vh; menu_app_region(menu_apps_top(), 1, &vx, &vy, &vw, &vh); + if (vh <= 0) return; + meow_ui_fill_rect(vx, vy, vw, vh, s_ui_theme()->surface); + if (s_menu_list) meow_widget_draw(s_menu_list); + meow_dirty_add(vx, vy, vw, vh); + meow_ui_flush(); +} + +/* ---- 宫格滚动物理回调(M7.4 遗留②:不用 lazygrid_scroll_attach,改自定义 meow_scroll 会话, + * 使 redraw 先重铺背景区再画格——attach 自带的 redraw 只刷视口 meow_gfx,自定义图片背景 + + * 超屏滚动会产生残影。set/max/get 仍指回 lazygrid,滚动语义不变。---- */ +static void menu_grid_sc_set(void *ctx, int off) { (void)ctx; if (s_menu_grid) meow_lazygrid_set_scroll(s_menu_grid, off); } +static int menu_grid_sc_max(void *ctx) { (void)ctx; return s_menu_grid ? meow_lazygrid_max_scroll(s_menu_grid) : 0; } +static int menu_grid_sc_get(void *ctx) { (void)ctx; return s_menu_grid ? meow_lazygrid_scroll(s_menu_grid) : 0; } +static void menu_grid_sc_redraw(void *ctx) +{ + (void)ctx; + int vx, vy, vw, vh; menu_app_region(menu_apps_top(), 0, &vx, &vy, &vw, &vh); + if (vh <= 0) return; + menu_bg_paint_region(vx, vy, vw, vh); /* 重铺背景区:防图片背景残影 */ + if (s_menu_grid) meow_lazygrid_draw(s_menu_grid, meow_theme_get()); + meow_dirty_add(vx, vy, vw, vh); + meow_ui_flush(); +} + +/* ---- 惰性创建一次(首跑 ensure;跨多次 run_menu 复用)。几何以当前 apps_top 定 baseline。---- */ +static void menu_sdk_ensure(int apps_top) +{ + if (s_menu_sdk_init) return; + meow_ui_activity_push(MENU_SDK_ACT); /* 控件 owner 钉哨兵(与设置页/通知中心同款) */ + /* 宫格 */ + int gx, gy, gw, gh; menu_app_region(apps_top, 0, &gx, &gy, &gw, &gh); + s_menu_grid = meow_lazygrid_create(gx, gy, gw, gh, MENU_GRID_COLS, menu_grid_cell_w(), + MENU_GRID_CARD_H, MENU_GRID_GAP); + if (s_menu_grid) { + meow_lazygrid_set_vsource(s_menu_grid, menu_grid_count, menu_grid_bind, &s_menu_ctx); + meow_lazygrid_set_item_draw(s_menu_grid, menu_sdk_grid_draw, &s_menu_ctx); + meow_lazygrid_set_empty_text(s_menu_grid, meow_str_zh("menu.empty_app", "没有找到应用")); + /* M7.4 遗留②:自定义会话(redraw 重铺背景),替代 lazygrid_scroll_attach(见 menu_grid_sc_redraw 注) */ + s_menu_grid_sc = meow_scroll_create(menu_grid_sc_set, menu_grid_sc_max, menu_grid_sc_get, + menu_grid_sc_redraw, NULL); + } + /* 列表 */ + int lx, ly, lw, lh; menu_app_region(apps_top, 1, &lx, &ly, &lw, &lh); + s_menu_list = meow_listview_create(lx, ly, lw, lh, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE); + if (s_menu_list) { + meow_listview_set_vsource(s_menu_list, menu_list_count, menu_list_bind, &s_menu_ctx); + meow_listview_set_item_draw(s_menu_list, menu_sdk_list_draw, &s_menu_ctx); + meow_listview_set_item_h(s_menu_list, MENU_LIST_ROW_H); + meow_listview_set_empty_text(s_menu_list, meow_str_zh("menu.empty_app", "没有找到应用")); + s_menu_list_sc = meow_scroll_create(menu_lv_sc_set, menu_lv_sc_max, menu_lv_sc_get, + menu_lv_sc_redraw, NULL); + } + meow_ui_activity_pop(); + s_menu_sdk_init = 1; +} static void draw_menu(struct app apps[MAX_APPS], int napps, int state, @@ -572,6 +797,96 @@ static void draw_menu(struct app apps[MAX_APPS], int napps, int state, menu_bg_paint(); draw_header_content(); + if (state == 1) { + if (mfont_ready) { + ui_draw(30, 200, meow_str_zh("menu.no_card", "没有存储卡"), th->on_surface, bsize); + ui_draw(30, 234, meow_str_zh("menu.no_card_hint", "请插入TF卡后重开菜单"), + th->on_surface_variant, bsize); + } else { + draw_text(30, 200, "NO STORAGE CARD", 2, th->on_surface); + draw_text(30, 240, "INSERT TF CARD", 2, th->on_surface_variant); + draw_text(30, 272, "THEN RE-OPEN MENU", 2, th->on_surface_variant); + } + } else if (state == 2 || napps == 0) { + if (mfont_ready) { + ui_draw(30, 200, meow_str_zh("menu.empty_app", "没有找到应用"), th->on_surface, bsize); + ui_draw(30, 234, meow_str_zh("menu.empty_app_hint", "请放入应用到"), + th->on_surface_variant, bsize); + ui_draw(30, 268, "MeowAPPs//app.json", th->on_surface_variant, bsize); + } else { + draw_text(30, 200, "NO APPS FOUND", 2, th->on_surface); + draw_text(30, 240, "PUT APP IN", 2, th->on_surface_variant); + draw_text(30, 272, "MeowAPPs//app.json", 2, th->on_surface_variant); + } + } else { + /* M7.4:应用区迁 SDK 虚拟化控件(layout=0 → meow_lazygrid;layout=1 → meow_listview)。 + * 数据源 ctx 对齐当前 apps[]/napps;控件首跑建一次,跨次复用;绘制/命中/脏区几何同源 + * (menu_app_region)。超屏滚动由 meow_scroll 接管(宫格 scroll_attach / 列表手动会话)。 */ + s_menu_ctx.apps = apps; s_menu_ctx.napps = napps; + menu_sdk_ensure(apps_top); + int vx, vy, vw, vh; menu_app_region(apps_top, layout, &vx, &vy, &vw, &vh); + if (layout == 0) { + if (s_menu_grid) { + meow_lazygrid_set_rect(s_menu_grid, vx, vy, vw, vh); + meow_lazygrid_set_sel(s_menu_grid, (cursor >= 0 && cursor < napps) ? cursor : -1); + meow_lazygrid_draw(s_menu_grid, meow_theme_get()); + } + } else { + if (s_menu_list) { + meow_widget_set_rect(s_menu_list, vx, vy, vw, vh); + meow_listview_set_sel(s_menu_list, (cursor >= 0 && cursor < napps) ? cursor : -1); + meow_widget_draw(s_menu_list); + } + } + } + + /* M2.36 B2b:系统设置 builtin 行(过渡入口)。位于应用项之后、Exit 行之前; + * 音量导航可把光标移到此处(cursor==napps),触摸点击该行进入设置页。 */ + { + int sy = EXIT_ROW - ROW_H; /* 固定在 Exit 行上方 588 */ + int selset = (cursor == napps); /* 光标已移到设置行 */ + meow_gfx_fill_round(8, sy - 4, fb_xres - 16, ROW_H - 8, 12, th->surface_container); + if (selset) meow_gfx_fill_round_alpha(8, sy - 4, fb_xres - 16, ROW_H - 8, 12, th->primary, 40); + if (mfont_ready) ui_draw(24, sy, meow_str_zh("menu.settings", "系统设置"), th->on_surface, bsize); + else draw_text(24, sy, "SETTINGS", 2, th->on_surface); + s_draw_chevron(fb_xres - 22, sy + ROW_H / 2, th->on_surface_variant); + } + + /* exit row */ + meow_gfx_fill_round(8, EXIT_ROW - 4, fb_xres - 16, ROW_H - 8, 12, th->surface_container); + /* M2.2/M2.6:MeowOS 常驻主界面(播放器已死)时改「重启返回播放器」。 */ + if (mfont_ready) ui_draw(24, EXIT_ROW, meow_mode ? meow_str_zh("menu.reboot_player", "重启返回播放器") + : meow_str_zh("menu.back_player", "返回播放器"), + th->on_surface, bsize); + else draw_text(24, EXIT_ROW, meow_mode ? "REBOOT TO PLAYER" : "BACK TO PLAYER", 2, th->on_surface); + + /* M2.18 Bug 3: 底部对齐,弃用硬编码 y=700。字号 20 真实行高 meow_font_line_height(20)。 + * 非 mfont 回退分支(5x7 点阵,字高=7*scale)同样底部对齐,两分支都处理。 */ + #define FOOTY_PAD 8 + { + char ver[32]; + snprintf(ver, sizeof ver, "MeowOS v%s dev", g_meowos_version); + int foot_y = fb_yres - meow_font_line_height(bsize) - FOOTY_PAD; + ui_draw(fb_xres - 8 - ui_width(ver, bsize), foot_y, ver, th->on_surface_variant, bsize); + } + + if (toast_ms > 0 && toast && toast[0]) { + fill_rect(20, TOAST_Y - 6, fb_xres - 40, 40, th->surface_variant); + ui_draw(30, TOAST_Y, toast, th->error, bsize); + } + +#if 0 /* ===== M7.4 回退位:旧手绘主界面全屏绘制(应用区手绘宫格/列表)。 + * 回退 = 删本 #if 0 / #endif 行 + 注释上方新 SDK 路径;run_menu 会话骨架不在此。 ===== */ + const meow_ui_theme_t *th = s_ui_theme(); + int layout = menu_layout(); + int apps_top = menu_apps_top(); + int bsize = 20; + meow_settings_get_int("font.size.body", &bsize); + if (bsize < 12) bsize = 12; + if (bsize > 28) bsize = 28; + menu_bg_prepare(th); + menu_bg_paint(); + draw_header_content(); if (state == 1) { if (mfont_ready) { ui_draw(30, 200, "没有存储卡", th->on_surface, bsize); @@ -600,27 +915,23 @@ static void draw_menu(struct app apps[MAX_APPS], int napps, int state, layout, th); } } - - /* M2.36 B2b:系统设置 builtin 行(过渡入口)。位于应用项之后、Exit 行之前; - * 音量导航可把光标移到此处(cursor==napps),触摸点击该行进入设置页。 */ + /* M2.36 B2b:系统设置 builtin 行(同上方新路径) */ { - int sy = EXIT_ROW - ROW_H; /* 固定在 Exit 行上方 588 */ - int selset = (cursor == napps); /* 光标已移到设置行 */ + int sy = EXIT_ROW - ROW_H; + int selset = (cursor == napps); meow_gfx_fill_round(8, sy - 4, fb_xres - 16, ROW_H - 8, 12, th->surface_container); if (selset) meow_gfx_fill_round_alpha(8, sy - 4, fb_xres - 16, ROW_H - 8, 12, th->primary, 40); - if (mfont_ready) ui_draw(24, sy, "系统设置", th->on_surface, bsize); + if (mfont_ready) ui_draw(24, sy, meow_str_zh("menu.settings", "系统设置"), th->on_surface, bsize); else draw_text(24, sy, "SETTINGS", 2, th->on_surface); s_draw_chevron(fb_xres - 22, sy + ROW_H / 2, th->on_surface_variant); } - /* exit row */ meow_gfx_fill_round(8, EXIT_ROW - 4, fb_xres - 16, ROW_H - 8, 12, th->surface_container); - /* M2.2/M2.6:MeowOS 常驻主界面(播放器已死)时改「重启返回播放器」。 */ - if (mfont_ready) ui_draw(24, EXIT_ROW, meow_mode ? "重启返回播放器" : "返回播放器", th->on_surface, bsize); + if (mfont_ready) ui_draw(24, EXIT_ROW, meow_mode ? meow_str_zh("menu.reboot_player", "重启返回播放器") + : meow_str_zh("menu.back_player", "返回播放器"), + th->on_surface, bsize); else draw_text(24, EXIT_ROW, meow_mode ? "REBOOT TO PLAYER" : "BACK TO PLAYER", 2, th->on_surface); - - /* M2.18 Bug 3: 底部对齐,弃用硬编码 y=700。字号 20 真实行高 meow_font_line_height(20)。 - * 非 mfont 回退分支(5x7 点阵,字高=7*scale)同样底部对齐,两分支都处理。 */ + /* footer(底部对齐) */ #define FOOTY_PAD 8 { char ver[32]; @@ -628,11 +939,11 @@ static void draw_menu(struct app apps[MAX_APPS], int napps, int state, int foot_y = fb_yres - meow_font_line_height(bsize) - FOOTY_PAD; ui_draw(fb_xres - 8 - ui_width(ver, bsize), foot_y, ver, th->on_surface_variant, bsize); } - if (toast_ms > 0 && toast && toast[0]) { fill_rect(20, TOAST_Y - 6, fb_xres - 40, 40, th->surface_variant); ui_draw(30, TOAST_Y, toast, th->error, bsize); } +#endif } @@ -784,6 +1095,13 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state, long meow_last_poll = 0; /* M2.2:MeowOS 后台拔营(卡/网)轮询时间戳 */ unsigned long long apps_sig = 0; /* M2.77:应用目录签名(0=未初始化,首圈建立基线) */ unsigned long long fonts_sig = 0; /* M2.83:字库目录签名(门控 scan_presets,防 2s 轮询 sha256 风暴) */ +#if 0 /* ===== M7.4 遗留① 补位(默认关闭):主界面宫格/列表超屏滚动喂给。 + * 作用:应用数 > 视口(max_scroll>0)时,run_menu 触摸循环内喂 meow_scroll_track(拖动跟 + * 手)+ 松手 meow_scroll_fling(惯性)。当前 MAX_APPS=8 装得下(max_scroll==0),喂给为 + * no-op(安全);未来应用 >8 时删除下述三处 #if 0 行即启用,run_menu 会话骨架逐字不变 + * (只补喂、不删骨架)。------------------------------------- */ + int sg_track_y = -1; /* 本次手势上一 y(增量 dw 用;<=8 应用从不喂) */ +#endif load_config(); /* meow.conf on the card: bg_color etc. */ menu_icons_load(apps, napps); /* MD3:应用图标解码一次进缓存(不每帧解码) */ @@ -1289,6 +1607,19 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state, need_menu_paint = 1; } } +#if 0 /* M7.4 遗留① 补位:拖动超屏滚动喂 meow_scroll_track(增量 dw)。≤8 应用 max_scroll==0 → no-op。 + * 启用(删本 #if 0/#endif)后:移动手指即在应用区/列表区跟手滚动,骨架其它判定不变。 */ + if (t_down && t_down_valid && t_last_y >= 0) { + if (sg_track_y < 0) sg_track_y = t_last_y; + int dw = t_last_y - sg_track_y; + if (dw != 0) { + if (menu_layout() == 0) { if (s_menu_grid_sc) meow_scroll_track(s_menu_grid_sc, dw, now_ms()); } + else { if (s_menu_list_sc) meow_scroll_track(s_menu_list_sc, dw, now_ms()); } + sg_track_y = t_last_y; + need_menu_paint = 1; + } + } +#endif /* ⑨ 顶部热区「跟手下拉」打开通知中心(拖动中即进,真跟手): * 原点锁定 + 起点在顶部热区(yprimary; op.fg = th->onPrimary; + if (g_ncp_btn_play) meow_widget_set_opts(g_ncp_btn_play, &op); + if (g_ncp_btn_pause) meow_widget_set_opts(g_ncp_btn_pause, &op); + meow_ui_activity_pop(); + g_ncp_media_btn_init = 1; +} + +/* 绘制三控制按钮(prev/next 固定;play/pause 按会话状态只画其一——两钮同位,改画时新圆底盖掉旧)。 + * 每次刷新播放钮底色/图标(同 now_playing np_refresh_theme:跨主题切换跟手)。 */ +static void ncp_draw_media_btns(const meow_media_session_t *s) +{ + if (!g_ncp_media_btn_init) ncp_media_btns_create(); + const meow_theme *th = meow_theme_get(); + meow_widget_opts op; memset(&op, 0, sizeof op); + op.bg = th->primary; op.fg = th->onPrimary; + if (g_ncp_btn_play) meow_widget_set_opts(g_ncp_btn_play, &op); + if (g_ncp_btn_pause) meow_widget_set_opts(g_ncp_btn_pause, &op); + if (g_ncp_btn_prev) { meow_widget_set_selected(g_ncp_btn_prev, 0); meow_widget_draw(g_ncp_btn_prev); } + if (g_ncp_btn_next) { meow_widget_set_selected(g_ncp_btn_next, 0); meow_widget_draw(g_ncp_btn_next); } + int playing = (s && s->state == MEOW_MEDIA_PLAYING); + meow_widget *pb = playing ? g_ncp_btn_pause : g_ncp_btn_play; + if (pb) { meow_widget_set_selected(pb, 0); meow_widget_draw(pb); } +} + /* 短串拷贝(同 meow_notif.c 语义:保证 NUL + 截断) */ static void ncp_strncpy(char *dst, const char *src, size_t len) @@ -340,7 +393,8 @@ static void ncp_chip_state(int chip, int *on, char *val, int vlen) } else if (chip == NCP_CHIP_THEME) { int v = 0; meow_settings_get_enum("display.theme", &v); /* dark=0, light=1 */ *on = (v == 1); - if (val && vlen > 0) snprintf(val, vlen, "%s", *on ? "浅色" : "深色"); + if (val && vlen > 0) snprintf(val, vlen, "%s", *on ? meow_str_zh("ncp.theme_light", "浅色") + : meow_str_zh("ncp.theme_dark", "深色")); } else if (chip == NCP_CHIP_BRIGHT) { int v = 0; meow_settings_get_int("display.brightness", &v); int vmin = 1; meow_settings_get_int("display.brightness_min", &vmin); @@ -406,8 +460,199 @@ static int ncp_chip_apply(int chip) } +/* ==================================================================== */ +/* M7.3:通知中心内层迁 SDK 控件(芯片/双滑条/勿扰开关/通知列表/左滑删除) */ +/* -------------------------------------------------------------------- + * 定位:只迁「内层绘制/命中」。叠加会话骨架(SIGSTOP / 输入移交 / 恢复帧 / 顶条热区——见 + * app_launcher.c run_app_notif_overlay)绝对不动,此处仅把 run_notif_center / draw_notif_center + * 中面板体的绘制与行命中交给 SDK meow_widget。SDK meow_ui.a 已定稿(c3d6371b),勿重编直接用。 + * + * owner 钉哨兵 NCP_WIDGET_ACT(不冲突:0x50 电源菜单 / 0x51 锁屏·NowPlaying / 0x52 子页·媒体卡 / + * 0x53 设置页)。绘制经 meow_widget_draw / meow_listview_vs_draw;命中:通知列表走 + * meow_listview_hit / meow_listview_swipe_begin·move·end(SDK 手势判定+删除/回弹动画), + * 芯片/滑条/勿扰开关沿用既有人工几何命中(与媒体卡 M7.0b 一致——叠加会话为手绘循环,不跑 SDK + * 输入线程,故不依赖 meow_widget_hit 的 owner==active 闸门)。 */ +#define NCP_WIDGET_ACT 0x54 +static meow_widget *g_ncp_lv = NULL; /* 通知列表:listview 虚拟化 + 行自绘 + 空态 + 左滑删除 */ +static meow_widget *g_ncp_sl_bri = NULL; /* 亮度滑条 */ +static meow_widget *g_ncp_sl_vol = NULL; /* 音量滑条 */ +static meow_widget *g_ncp_sw_dnd = NULL; /* 勿扰开关 */ +static meow_widget *g_ncp_chip[NCP_CHIP_COUNT]; /* 5 个快捷 chip(自定义画布:圆角 + 图标 + 标签) */ +static int g_ncp_widget_init = 0; +static int g_ncp_need_paint = 0; /* M7.3:行左滑删除回调置位 → 主循环整面板重画 */ + +/* ---- 通知列表虚拟化源:count=当前行数(动态,逐帧实时查);bind 仅填 enabled 供 + * meow_listview_hit 判定(行内容经 set_item_draw 自绘直读 g_rows[],无池缓存过期问题)。 ---- */ +static int ncp_lv_count(void *ctx) { (void)ctx; return g_nrows; } +static void ncp_lv_bind(int idx, meow_lv_item *it, void *ctx) +{ + (void)ctx; + memset(it, 0, sizeof *it); + it->enabled = (idx >= 0 && idx < g_nrows) ? 1 : 0; +} + +/* 文本截断(SDK meow_ui_text_width/meow_ui_text;UTF-8 安全,与旧 ncp_text 同构)。 */ +static void ncp_mt_text(int x, int y, const char *s, int size, uint32_t col, int maxw) +{ + if (!s) s = ""; + if (meow_ui_text_width(s, size) <= maxw) { meow_ui_text(x, y, s, size, col); return; } + int ell = meow_ui_text_width("...", size); + int limit = maxw - ell; + char buf[128]; int len = 0; + while (s[len] && len < (int)sizeof buf - 8) { + unsigned char c0 = (unsigned char)s[len]; + int cp = 1; + if ((c0 & 0xE0) == 0xC0) cp = 2; + else if ((c0 & 0xF0) == 0xE0) cp = 3; + else if ((c0 & 0xF8) == 0xF0) cp = 4; + if (len + cp >= (int)sizeof buf - 1) break; + for (int k = 0; k < cp; k++) buf[len + k] = s[len + k]; + buf[len + cp] = 0; + if (meow_ui_text_width(buf, size) > limit) { buf[len] = 0; break; } + len += cp; + } + if (len > 0) { + if (len + 3 < (int)sizeof buf) { buf[len] = '.'; buf[len+1] = '.'; buf[len+2] = '.'; buf[len+3] = 0; } + meow_ui_text(x, y, buf, size, col); + } else { + meow_ui_text(x, y, "...", size, col); + } +} + +/* 通知行自绘(set_item_draw 覆盖默认 renderer)。x 已含左滑位移(SDK 先画右露删除区并把行 x 左移), + * 逐元素对齐旧 ncp_draw_row;颜色全取 meow_theme 语义色(M7 定案 #2 单一权威)。 */ +static void ncp_lv_row_draw(int idx, int x, int y, int w, int h, int state, void *ctx) +{ + (void)w; (void)state; (void)ctx; /* w=SDK 视口宽;行卡宽用 NCP_ROW_W(含 NCP_ROW_X 两侧留白) */ + if (idx < 0 || idx >= g_nrows) return; + const ncp_row_t *r = &g_rows[idx]; + const meow_theme *th = meow_theme_get(); + meow_gfx_fill_round(x + NCP_ROW_X, y, NCP_ROW_W, h - 6, 12, th->surfaceContainer); + /* 图标 */ + int icx = x + NCP_ICON_X, icy = y + (h - 32) / 2; + if (r->img) meow_ui_img_cover(icx, icy, 32, r->img); + else { + const char *nm = ncp_app_name(r->app_id); + ncp_dot(icx + 16, icy + 16, 16, th->primary); + char ch = nm[0] ? nm[0] : '?'; + char cbuf[4] = { ch, 0, 0, 0 }; + if ((unsigned char)ch >= 0x80 && nm[1]) { cbuf[1] = nm[1]; cbuf[2] = 0; + if ((unsigned char)ch >= 0xE0 && nm[2]) { cbuf[2] = nm[2]; cbuf[3] = 0; } } + int cw = meow_ui_text_width(cbuf, th->font_body); + meow_ui_text(icx + 16 - cw / 2, icy + 16 - meow_ui_text_height(th->font_body) / 2, + cbuf, th->font_body, th->onPrimary); + } + /* app 名 + 时间 */ + meow_ui_text(x + NCP_TEXT_X, y + 10, ncp_app_name(r->app_id), th->font_body, th->onSurfaceVariant); + char tbuf[32]; mnp_time_ago(r->created_ms, (uint64_t)time(NULL) * 1000u, tbuf, sizeof tbuf); + meow_ui_text(x + NCP_ROW_X + NCP_ROW_W - 8 - meow_ui_text_width(tbuf, th->font_body), + y + 12, tbuf, th->font_body, th->onSurfaceVariant); + /* 标题 + 内容(SDK 截断) */ + ncp_mt_text(x + NCP_TEXT_X, y + 30, r->summary, th->font_body, th->onSurface, NCP_TEXT_W); + ncp_mt_text(x + NCP_TEXT_X, y + 56, r->body, th->font_body, th->onSurfaceVariant, NCP_TEXT_W); + /* 进度(progress>=0):细条 */ + if (r->progress >= 0) { + int px = x + NCP_TEXT_X, pw = NCP_TEXT_W, py = y + h - 14; + meow_ui_fill_rect(px, py, pw, 4, th->surfaceVariant); + int fw = r->progress * pw / 100; + if (fw > 0) meow_ui_fill_rect(px, py, fw, 4, th->primary); + } +} + +/* 左滑删除完成回调(SDK 删除/回弹动画结束后触发):撤下该通知 + 重采集 + 重排。 */ +static void ncp_lv_on_delete(int idx, void *ctx) +{ + (void)ctx; + logmsg("notif-center: swipe delete row %d", idx); + if (idx >= 0 && idx < g_nrows) meow_notif_dismiss(g_rows[idx].id); + ncp_refresh_rows(); + /* 行数变化 → 列表重排:置位让主循环整面板重画(含媒体卡出现/消失重排 + 列表滚动重钳)。 */ + g_ncp_need_paint = 1; +} + +/* 芯片自绘(自定义画布 render_cb:TONAL 观感圆角 + 位图图标 + 标签,与旧 draw_notif_center chip 循环一致)。 + * launcher 侧 meow_widget 不透明 → 用 meow_widget_rect 取绝对几何(同 power_menu pm_render_cb)。 */ +static void ncp_chip_render(meow_widget *w, const meow_theme *th, void *ctx) +{ + int c = (int)(intptr_t)ctx; + if (c < 0 || c >= NCP_CHIP_COUNT) return; + int rc[4]; meow_widget_rect(w, rc); + int x = rc[0], y = rc[1], wd = rc[2], ht = rc[3]; + int on = 0; char val[16] = ""; ncp_chip_state(c, &on, val, sizeof val); + uint32_t bg = on ? th->primary : th->surfaceContainer; + uint32_t fg = on ? th->onPrimary : th->onSurfaceVariant; + meow_gfx_fill_round(x, y, wd, ht, 12, bg); + int icon_cx = x + (wd - SB_ICON_SIZE) / 2; + int icon_cy = y + 14; + if (c == NCP_CHIP_WIFI) ncp_sb_icon(on ? SB_I_WIFI : SB_I_WIFI_OFF, icon_cx, icon_cy, fg, 22); + else if (c == NCP_CHIP_BT) ncp_sb_icon(on ? SB_I_BT : SB_I_BT_DIS, icon_cx, icon_cy, fg, 22); + else if (c == NCP_CHIP_THEME) { + if (on) ncp_dot(icon_cx + 16, icon_cy, 11, fg); + else { ncp_dot(icon_cx + 16, icon_cy, 11, fg); ncp_dot(icon_cx + 16, icon_cy, 6, bg); } + } + else if (c == NCP_CHIP_BRIGHT) ncp_sb_icon(SB_I_BRIGHT, icon_cx, icon_cy, fg, 22); + else if (c == NCP_CHIP_POWER) { + int pc = icon_cx + SB_ICON_SIZE / 2; + meow_gfx_fill_round(pc - 8, icon_cy - 8, 16, 16, 8, fg); /* 外环 */ + meow_gfx_fill_round(pc - 4, icon_cy - 4, 8, 8, 4, bg); /* 内孔(显环厚) */ + meow_gfx_fill_round(pc - 1, icon_cy - 16, 3, 9, 1, fg); /* 顶部短柄 */ + } + static const char *chip_lbl[NCP_CHIP_COUNT] = { "WiFi", "蓝牙", "主题", "亮度", "电源菜单" }; + static const char *chip_id[NCP_CHIP_COUNT] = { "ncp.chip_wifi", "ncp.chip_bt", + "ncp.chip_theme", "ncp.chip_bright", + "ncp.chip_power" }; + char label[24]; snprintf(label, sizeof label, "%s", meow_str_zh(chip_id[c], chip_lbl[c])); + if (val[0] && c == NCP_CHIP_BRIGHT) snprintf(label, sizeof label, "%.9s", val); + ui_draw(x + (wd - ui_width(label, 14)) / 2, y + ht - 18, label, fg, 14); +} + +/* 芯片自定义画布命中(矩形;未被主循环使用——命中仍走 ncp_chip_hit 人工几何,此处仅满足 W_CUSTOM 契约)。 */ +static int ncp_chip_widget_hit(meow_widget *w, int x, int y, void *ctx) +{ + (void)ctx; + int rc[4]; meow_widget_rect(w, rc); + return (x >= rc[0] && x < rc[0] + rc[2] && y >= rc[1] && y < rc[1] + rc[3]); +} + +/* 创建 M7.3 内层控件集(owner 钉哨兵;仅首进创建一次,跨次复用——同媒体卡 M7.0b 惯用法)。 */ +static void ncp_widgets_create(void) +{ + meow_ui_activity_push(NCP_WIDGET_ACT); + /* 通知列表(视口 = 列表区;行高 NCP_ROW_H;空态 + 左滑删除) */ + int lvy = ncp_list_top(), lvh = ncp_list_view(); + g_ncp_lv = meow_listview_create(0, lvy, fb_xres, lvh, 0, NULL, NULL, MEOW_LIST_SEL_NONE); + if (g_ncp_lv) { + meow_listview_set_vsource(g_ncp_lv, ncp_lv_count, ncp_lv_bind, NULL); + meow_listview_set_item_draw(g_ncp_lv, ncp_lv_row_draw, NULL); + meow_listview_set_item_h(g_ncp_lv, NCP_ROW_H); + meow_listview_set_empty_text(g_ncp_lv, meow_str_zh("ncp.empty", "暂无通知")); + meow_listview_set_swipe_delete(g_ncp_lv, 1, ncp_lv_on_delete, NULL); + } + /* 亮度/音量双滑条(track 中心对齐旧 s_draw_slider:NCP_SL*_Y+3) */ + int bmin = 1; meow_settings_get_int("display.brightness_min", &bmin); + int bval = 0; meow_settings_get_int("display.brightness", &bval); + bval = mnp_brightness_clamp(bval, bmin); + g_ncp_sl_bri = meow_slider_create(NCP_SLIDER_X, NCP_SL1_Y - 14, NCP_SLIDER_W, bmin, 100, bval); + int vmax = vol_clamp(settings_volume_max); + int vval = vol_clamp(settings_volume_get()); + g_ncp_sl_vol = meow_slider_create(NCP_SLIDER_X, NCP_SL2_Y - 14, NCP_SLIDER_W, 0, vmax, vval); + /* 勿扰开关 */ + g_ncp_sw_dnd = meow_switch_create(0, 0, g_ncp_dnd ? 1 : 0); + if (g_ncp_sw_dnd) meow_widget_set_rect(g_ncp_sw_dnd, NCP_DND_SPLIT_X - 6 - 48, + NCP_DND_Y + NCP_DND_H / 2 - 14, 48, 28); + /* 5 个快捷 chip(自定义画布;坐标与旧 ncp_chip_x / NCP_CHIP_Y 一致) */ + for (int c = 0; c < NCP_CHIP_COUNT; c++) + g_ncp_chip[c] = meow_widget_custom_create(ncp_chip_x(c), NCP_CHIP_Y, NCP_CHIP_W, NCP_CHIP_H, + ncp_chip_render, ncp_chip_widget_hit, + (void *)(intptr_t)c); + meow_ui_activity_pop(); + g_ncp_widget_init = 1; +} + + /* ⑦ 画删除底:行右缘被空出的条(宽=-off)铺 error 红底 + 白「删除」字(条宽≥44 才显字,否则纯红渐进)。 * off<=0(负得多=卡滑出多、露出右条多);off=0 不画。 */ +#if 0 /* M7.3 回退位:旧手绘行删除底(SDK 左滑删除已接管此绘制,见 meow_listview swipe 渲染)。 */ static void ncp_draw_delete_bg(int ry, int off, const meow_ui_theme_t *th) { if (off >= 0) return; @@ -520,6 +765,35 @@ static void ncp_repaint_row(int scroll, int row, int off) meow_dirty_add(0, ry, fb_xres, NCP_ROW_H); meow_ui_flush(); } +#endif /* M7.3 回退位结束(上述旧手绘 ncp_draw_delete_bg/ncp_draw_row/ncp_draw_list/ + * ncp_repaint_list/ncp_repaint_row 重开本区即回退;新路径由下方 SDK 版接管)。 */ + + +/* ---- M7.3:通知列表绘制(SDK listview 版,替换上方 #if 0 的手绘版)---- */ + +/* 绘制通知列表可见区(scroll=像素偏移)。同步 listview 视口矩形(媒体卡出现/消失时列表顶缘 + * 变化)+ 滚动偏移,再画整个列表控件(虚拟化 + set_item_draw 行自绘 + 空态 + 左滑删除渲染 + * 全由 SDK 完成)。 */ +static void ncp_draw_list(int scroll) +{ + if (!g_ncp_widget_init || !g_ncp_lv) return; + int lt = ncp_list_top(), lv = ncp_list_view(); + meow_widget_set_rect(g_ncp_lv, 0, lt, fb_xres, lv); + meow_listview_set_scroll(g_ncp_lv, mnp_scroll_clamp(scroll, g_nrows * NCP_ROW_H, lv)); + meow_widget_draw(g_ncp_lv); +} + +/* 仅重绘列表可见区(滚动专用;不整面板、不刷状态栏)。先清列表底,再画 SDK listview; + * 只标列表带脏([NCP_LIST_TOP, NCP_LIST_BOT))——「滚动只重绘列表可视区,绝不整屏」。 */ +static void ncp_repaint_list(int scroll) +{ + const meow_ui_theme_t *th = s_ui_theme(); + int lt = ncp_list_top(), lv = ncp_list_view(); + fill_rect(0, lt, fb_xres, lv, th->surface_variant); + ncp_draw_list(scroll); + meow_dirty_add(0, lt, fb_xres, lv); + meow_ui_flush(); +} /* ⑥/⑧ 勿扰开关带命中(面板内小 toggle,⑧ 收窄为左半段): @@ -555,14 +829,20 @@ static void ncp_draw_dnd(const meow_ui_theme_t *th) meow_gfx_fill_round(bx + 6, bcy - 11, 4, 4, 2, th->on_surface_variant);/* 顶蒂 */ ncp_dot(bx + 8, bcy + 6, 2, th->on_surface_variant); /* 底部小圆点 */ /* label + 状态 */ - ui_draw(bx + 22, cy - meow_font_line_height(18) / 2 + 2, "勿扰", th->on_surface, 18); + const char *dnd_lbl = meow_str_zh("ncp.dnd", "勿扰"); + ui_draw(bx + 22, cy - meow_font_line_height(18) / 2 + 2, dnd_lbl, th->on_surface, 18); char st[16]; - snprintf(st, sizeof st, "%s", dnd_on ? "开" : "关"); - int lw = ui_width("勿扰", 18); + snprintf(st, sizeof st, "%s", dnd_on ? meow_str_zh("common.on", "开") : meow_str_zh("common.off", "关")); + int lw = ui_width(dnd_lbl, 18); ui_draw(bx + 22 + lw + 12, cy - meow_font_line_height(18) / 2 + 2, st, dnd_on ? th->primary : th->on_surface_variant, 18); - /* 左半段右侧开关(⑧:从原 fb_xres-24-48 左移到分割线左侧,48×28 垂直居中) */ - s_draw_switch(NCP_DND_SPLIT_X - 6 - 48, cy - 14, dnd_on, th); + /* 左半段右侧开关(⑧:从原 fb_xres-24-48 左移到分割线左侧,48×28 垂直居中)。 + * M7.3:迁 SDK meow_switch(绘制经 meow_widget_draw;命中仍走 ncp_dnd_hit 整带)。 */ + if (g_ncp_widget_init && g_ncp_sw_dnd) { + meow_widget_set_rect(g_ncp_sw_dnd, NCP_DND_SPLIT_X - 6 - 48, cy - 14, 48, 28); + meow_switch_set(g_ncp_sw_dnd, dnd_on ? 1 : 0); + meow_widget_draw(g_ncp_sw_dnd); + } } @@ -576,7 +856,7 @@ static void ncp_draw_clear(const meow_ui_theme_t *th) if (cw <= 0) return; meow_gfx_fill_round(cx0, y + 4, cw, NCP_DND_H - 8, 12, th->surface_container); /* 垃圾桶字形(简化:顶盖横条 + 桶身圆角 + 侧提手)+「清除」文字,整体在按钮内居中(美观对称)。 */ - const char *lbl = "清除"; + const char *lbl = meow_str_zh("ncp.clear", "清除"); int lw = ui_width(lbl, 18); int unit = 22; /* 图标占位宽 + 与文字的间距 */ int total = unit + lw; @@ -629,38 +909,6 @@ static void ncp_media_cover_load(const meow_media_session_t *s) g_ncp_media_cover = meow_img_load_file(path); } -/* 实心三角(dir=+1 右向 / -1 左向);size=最大宽度 px。按扫描线 fill_rect 近似 */ -static void ncp_media_tri(int cx, int cy, int size, int dir, uint32_t col) -{ - int half = size / 2; - for (int dy = -half; dy <= half; dy++) { - int w = half + 1 - (dy < 0 ? -dy : dy); /* 顶宽 1 → 底宽 half+1 */ - if (w <= 0) continue; - int x0 = cx + (dir > 0 ? -w / 2 : w / 2 - w); - fill_rect(x0, cy + dy, w, 1, col); - } -} - -/* 圆形按钮底 + 字形(kind:0=prev 1=play 2=next 3=pause 4=暂停态play)。 */ -static void ncp_media_btn(int cx, int cy, int r, int kind, const meow_ui_theme_t *th) -{ - meow_gfx_fill_round(cx - r, cy - r, r * 2, r * 2, r, th->primary); - uint32_t fg = 0xFFFFFF; - if (kind == 0) { /* prev:左三角 + 左竖条 */ - ncp_media_tri(cx + 4, cy, r - 4, -1, fg); - fill_rect(cx - 3, cy - (r - 5), 3, (r - 5) * 2, fg); - } else if (kind == 1) { /* play:右三角 */ - ncp_media_tri(cx + 2, cy, r - 2, 1, fg); - } else if (kind == 2) { /* next:右三角 + 右竖条 */ - ncp_media_tri(cx - 4, cy, r - 4, 1, fg); - fill_rect(cx + 1, cy - (r - 5), 3, (r - 5) * 2, fg); - } else if (kind == 3) { /* pause:双竖条 */ - int bw = (r - 6) / 2; if (bw < 2) bw = 2; - fill_rect(cx - 7, cy - (r - 5), bw, (r - 5) * 2, fg); - fill_rect(cx + 7 - bw, cy - (r - 5), bw, (r - 5) * 2, fg); - } -} - /* 画顶部媒体卡片(封面小图 + 标题/歌手 + 播放/暂停/上下曲 + 进度条)。 */ static void ncp_draw_media_card(const meow_ui_theme_t *th) { @@ -678,19 +926,15 @@ static void ncp_draw_media_card(const meow_ui_theme_t *th) const char *t = s->title[0] ? s->title : s->app_id; int cw = ui_width(t, 20); ui_draw(cover_x + (cover_s - cw) / 2, cover_y + (cover_s - meow_font_line_height(20)) / 2, - t, 0xFFFFFF, 20); + t, meow_theme_get()->onPrimary, 20); } int tx = cover_x + cover_s + 14; int tw = (NCP_MEDIA_PREV_CX - NCP_MEDIA_BTN_R - 8) - tx; /* 文本宽限到上一首按钮左缘前 8px */ if (tw < 0) tw = 0; ncp_text(tx, cy + 14, s->title, 18, th->on_surface, tw); ncp_text(tx, cy + 40, s->artist[0] ? s->artist : s->album, 16, th->on_surface_variant, tw); - /* 控制按钮 */ - int by = cy + NCP_MEDIA_BTN_Y; - ncp_media_btn(NCP_MEDIA_PREV_CX, by, NCP_MEDIA_BTN_R - 4, 0, th); - int pk = (s->state == MEOW_MEDIA_PLAYING) ? 3 /* pause */ : 1 /* play */; - ncp_media_btn(NCP_MEDIA_PLAY_CX, by, NCP_MEDIA_BTN_R, pk, th); - ncp_media_btn(NCP_MEDIA_NEXT_CX, by, NCP_MEDIA_BTN_R - 4, 2, th); + /* 控制按钮(SDK meow_iconbtn + sb_icons,与 Now Playing 一致;命中仍走 ncp_media_card_tap 三圆心) */ + ncp_draw_media_btns(s); /* 进度条 + 时间 */ uint32_t pos = meow_media_display_pos(s); int px = tx, pw = NCP_MEDIA_CARD_X + NCP_MEDIA_CARD_W - 12 - px, py = cy + NCP_MEDIA_PROG_Y; @@ -762,61 +1006,34 @@ static void draw_notif_center(int scroll, int redraw_sb) int grab_w = 44, grab_x = (W - grab_w) / 2; meow_gfx_fill_round(grab_x, NCP_GRAB_Y, grab_w, 6, 3, th->outline); /* 标题 + 「上滑/点空白关闭」 提示 */ - ui_draw(24, NCP_TITLE_Y, "通知中心", th->on_surface, 22); - ui_draw(W - 24 - ui_width("点空白关闭", 16), NCP_TITLE_Y + 2, "点空白关闭", th->on_surface_variant, 16); + ui_draw(24, NCP_TITLE_Y, meow_str_zh("ncp.title", "通知中心"), th->on_surface, 22); + ui_draw(W - 24 - ui_width(meow_str_zh("ncp.tap_close", "点空白关闭"), 16), NCP_TITLE_Y + 2, + meow_str_zh("ncp.tap_close", "点空白关闭"), th->on_surface_variant, 16); - /* 快捷开关行(⑥:5 个 = WiFi/蓝牙/主题/亮度/电源菜单,居中自动随 NCP_CHIP_N) */ - static const char *chip_lbl[NCP_CHIP_COUNT] = { "WiFi", "蓝牙", "主题", "亮度", "电源菜单" }; - int total_chip = NCP_CHIP_N * NCP_CHIP_W + (NCP_CHIP_N - 1) * NCP_CHIP_GAP; - int chip_x0 = (W - total_chip) / 2; - for (int c = 0; c < NCP_CHIP_N; c++) { - int cx = chip_x0 + c * (NCP_CHIP_W + NCP_CHIP_GAP); - int on = 0; char val[16] = ""; ncp_chip_state(c, &on, val, sizeof val); - uint32_t bg = on ? th->primary : th->surface_container; - uint32_t fg = on ? 0xFFFFFF : th->on_surface_variant; - meow_gfx_fill_round(cx, NCP_CHIP_Y, NCP_CHIP_W, NCP_CHIP_H, 12, bg); - int icon_cx = cx + (NCP_CHIP_W - SB_ICON_SIZE) / 2; - int icon_cy = NCP_CHIP_Y + 14; - if (c == NCP_CHIP_WIFI) ncp_sb_icon(on ? SB_I_WIFI : SB_I_WIFI_OFF, icon_cx, icon_cy, fg, 22); - else if (c == NCP_CHIP_BT) ncp_sb_icon(on ? SB_I_BT : SB_I_BT_DIS, icon_cx, icon_cy, fg, 22); - else if (c == NCP_CHIP_THEME) { - if (on) ncp_dot(icon_cx + 16, icon_cy, 11, fg); - else { ncp_dot(icon_cx + 16, icon_cy, 11, fg); ncp_dot(icon_cx + 16, icon_cy, 6, bg); } - } - else if (c == NCP_CHIP_BRIGHT) ncp_sb_icon(SB_I_BRIGHT, icon_cx, icon_cy, fg, 22); - else if (c == NCP_CHIP_POWER) { - /* 电源符号:圆环 + 顶部短柄,近似 ⏻(用现有 meow_gfx_fill_round 近似圆)。 */ - int pc = icon_cx + SB_ICON_SIZE / 2; - meow_gfx_fill_round(pc - 8, icon_cy - 8, 16, 16, 8, fg); /* 外环 */ - meow_gfx_fill_round(pc - 4, icon_cy - 4, 8, 8, 4, bg); /* 内孔(显环厚) */ - meow_gfx_fill_round(pc - 1, icon_cy - 16, 3, 9, 1, fg); /* 顶部短柄 */ - } - char label[24]; - snprintf(label, sizeof label, "%s", chip_lbl[c]); - if (val[0] && c == NCP_CHIP_BRIGHT) - snprintf(label, sizeof label, "%.9s", val); /* chip 下示值 */ - ui_draw(cx + (NCP_CHIP_W - ui_width(label, 14)) / 2, NCP_CHIP_Y + NCP_CHIP_H - 18, - label, fg, 14); - } + /* 快捷开关行(⑥:5 个 = WiFi/蓝牙/主题/亮度/电源菜单)。M7.3:迁 SDK 自定义画布 chip + *(TONAL 观感圆角 + 位图图标 + 标签,见 ncp_chip_render;绘制经 meow_widget_draw)。 */ + if (g_ncp_widget_init) + for (int c = 0; c < NCP_CHIP_N; c++) + if (g_ncp_chip[c]) meow_widget_draw(g_ncp_chip[c]); - /* 亮度滑条 */ + /* 亮度滑条(M7.3:迁 SDK meow_slider;track 中心 = NCP_SL1_Y+3,与旧 s_draw_slider 对齐) */ int bmin = 1; meow_settings_get_int("display.brightness_min", &bmin); int bval = 0; meow_settings_get_int("display.brightness", &bval); bval = mnp_brightness_clamp(bval, bmin); - ui_draw(NCP_SLIDER_X, NCP_SL1_Y - 26, "亮度", th->on_surface, 18); + ui_draw(NCP_SLIDER_X, NCP_SL1_Y - 26, meow_str_zh("ncp.brightness", "亮度"), th->on_surface, 18); char vbb[16]; snprintf(vbb, sizeof vbb, "%d%%", bval); ui_draw(NCP_SLIDER_X + NCP_SLIDER_W - ui_width(vbb, 18), NCP_SL1_Y - 26, vbb, th->on_surface_variant, 18); - s_draw_slider(NCP_SLIDER_X, NCP_SL1_Y, NCP_SLIDER_W, bval, bmin, 100, th); + if (g_ncp_widget_init && g_ncp_sl_bri) { meow_slider_set(g_ncp_sl_bri, bval); meow_widget_draw(g_ncp_sl_bri); } /* 音量滑条 */ int vval = 0; meow_settings_get_int("audio.volume", &vval); vval = vol_clamp(vval); - ui_draw(NCP_SLIDER_X, NCP_SL2_Y - 26, "音量", th->on_surface, 18); + ui_draw(NCP_SLIDER_X, NCP_SL2_Y - 26, meow_str_zh("ncp.volume", "音量"), th->on_surface, 18); char vbl[16]; snprintf(vbl, sizeof vbl, "%d%%", vval); ui_draw(NCP_SLIDER_X + NCP_SLIDER_W - ui_width(vbl, 18), NCP_SL2_Y - 26, vbl, th->on_surface_variant, 18); - s_draw_slider(NCP_SLIDER_X, NCP_SL2_Y, NCP_SLIDER_W, vval, 0, vol_clamp(100), th); + if (g_ncp_widget_init && g_ncp_sl_vol) { meow_slider_set(g_ncp_sl_vol, vval); meow_widget_draw(g_ncp_sl_vol); } /* 分隔线 */ fill_rect(12, NCP_DIV_Y, W - 24, 1, th->outline); @@ -833,8 +1050,8 @@ static void draw_notif_center(int scroll, int redraw_sb) ncp_draw_list(scroll); /* 底部提示 */ - ui_draw((W - ui_width("上滑或点空白关闭", 16)) / 2, NCP_FOOT_Y, "上滑或点空白关闭", - th->on_surface_variant, 16); + ui_draw((W - ui_width(meow_str_zh("ncp.swipe_close", "上滑或点空白关闭"), 16)) / 2, NCP_FOOT_Y, + meow_str_zh("ncp.swipe_close", "上滑或点空白关闭"), th->on_surface_variant, 16); /* 状态栏画在面板体之上(0-27 由 SDK 全局状态栏维护,经 draw_statusbar→sb_sdk_refresh * 喂数据+标脏顶条;不落面板体脏区)。仅 redraw_sb=1(打开/主题变化)才喂/标脏,交互时跳过 @@ -968,16 +1185,10 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click, int scroll_invert = 0; /* input.swipe_invert(0=标准方向) */ int scroll_rebound = 0; /* 拖到顶/底仍继续拖(回弹)→ 抬手按关闭语义收起 */ int list_paint = 0; /* 仅列表区重绘(滚动专用,不整面板/不刷状态栏) */ - /* ⑦ 单条通知行左滑删除(跟手+半程可回+回弹;绘制态见 g_ncp_swipe_row/off): - * row_swipe_row = 正在左滑的行下标(-1=无);row_off = 行左移量(px,<=0,跟手); - * row_settle = 0 跟手 / 1 松手后向 row_off_target 回弹或滑出;row_anim_at = 落位动画计时。 - * row_paint = 仅重绘该行带(跟手局部 blit/位移,不整面板/不刷状态栏)。 */ - int row_swipe_row = -1; - int row_off = 0; - int row_settle = 0; - int row_off_target = 0; - unsigned long long row_anim_at = 0; - int row_paint = 0; /* 仅重绘该行带(行左滑专用) */ + /* ⑦ 单条通知行左滑删除(M7.3:迁 SDK meow_listview_set_swipe_delete + swipe_begin/move/end)。 + * sdk_swipe = 本触摸已交给 SDK 行左滑手势(begin 命中可滑行);SDK 负责方向仲裁(横向=消费 / + * 纵向=让位滚动)、删除/回弹动画(meow_anim_tick 推进)与 on_delete 回调。 */ + int sdk_swipe = 0; /* ⑨ 面板跟手下拉、半程可回: * panel_drag = 1 面板跟手拖动中(anim_p 直接由手指位移映射,状态时间轴暂停); * drag_seed_reveal = 进入拖动时锁定的面板「露出高度」(= ph + off,防触发瞬间跳变); @@ -1018,6 +1229,9 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click, g_ncp_dnd = d; } + /* M7.3:建内层 SDK 控件集(listview/双滑条/勿扰开关/芯片;owner 钉哨兵,仅首进一次)。 */ + ncp_widgets_create(); + /* 首次绘制:先拍背景(应用列表)→ 渲染最终位置整面板 → 拍面板快照 → 还原背景到 p=0 * 起点(面板隐藏、先露应用列表),随后进入循环由动画推进 p:0→FIX 滑入。 */ ncp_refresh_rows(); @@ -1092,9 +1306,7 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click, td = 0; td_ok = 0; drag_slider = 0; tlx = tly = -1; scroll_drag = 0; scroll_base = 0; scroll_y0 = 0; scroll_invert = 0; scroll_rebound = 0; list_paint = 0; - row_swipe_row = -1; row_off = 0; row_settle = 0; - row_off_target = 0; row_anim_at = 0; row_paint = 0; - g_ncp_swipe_row = -1; g_ncp_swipe_off = 0; + sdk_swipe = 0; /* 唤醒:清行左滑手势(SDK swipe 内部状态亦重置) */ panel_drag = 0; drag_seed_reveal = 0; drag_y0 = 0; drag_paint = 0; /* 唤醒:面板直接就位(不重播滑入动画,面板早已打开);重拍快照防内容已变 */ anim_p = anim_target = anim_last_target = NCP_ANIM_FIX; @@ -1201,42 +1413,27 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click, if (ncp_slider_band(tlx, tly, NCP_SL1_Y)) { drag_slider = 1; if (ncp_slider_apply(1, tlx, 0)) need_paint = 1; } else if (ncp_slider_band(tlx, tly, NCP_SL2_Y)) { drag_slider = 2; if (ncp_slider_apply(2, tlx, 0)) need_paint = 1; } } + /* M7.3:按下原点落在通知列表行 → 交 SDK 行左滑手势(begin 命中可滑行; + * 后续 move 由 SDK 做方向仲裁:横向=消费/纵向=让位滚动)。先同步 listview + * 滚动偏移(meow_listview_hit 用其内部 scroll)。 */ + if (open_settled && !drag_slider && !panel_drag && g_ncp_widget_init && g_ncp_lv) { + meow_listview_set_scroll(g_ncp_lv, list_scroll); + sdk_swipe = meow_listview_swipe_begin(g_ncp_lv, tlx, tly); + } } if (drag_slider == 1 && td_ok) { if (ncp_slider_apply(1, tlx, 0)) need_paint = 1; } else if (drag_slider == 2 && td_ok) { if (ncp_slider_apply(2, tlx, 0)) need_paint = 1; } - /* ⑦ 行左滑(进入):面板就位(open_settled)+ 未锁其它手势 + 位移达 slop, - * 判定横向主导(adx>ady)且左移(ddx<0)且起点落在可见行 → 进行左滑(只该行)。 - * 与竖向滚动互斥:本分支锁 row_swipe_row 后下方 scroll 不再进入(gate 含 - * row_swipe_row<0);竖向主导(ady>adx)或不左移则回退既有滚动(改变不大: - * 以前「横主导左移」也滚,现改为行左滑,符合「横向主导→行左滑」)。 */ - if (open_settled && td_ok && !scroll_drag && drag_slider == 0 && row_swipe_row < 0) { - int ddx = tlx - tdx, ddy = tly - tdy; - int adx = ddx < 0 ? -ddx : ddx, ady = ddy < 0 ? -ddy : ddy; - if ((adx >= NCP_SWIPE_SLOP || ady >= S_SCROLL_SLOP) && adx > ady && ddx < 0) { - int eff = tdy - ncp_list_top() + list_scroll; - int row = mnp_row_at(eff, 0, g_nrows * NCP_ROW_H, NCP_ROW_H); - /* 起点必须在列表区(DND 带/底部提示等不在列表区,防 scroll 导致误匹行) */ - if (tdy >= ncp_list_top() && tdy < NCP_LIST_BOT && - row >= 0 && row < g_nrows) { - row_swipe_row = row; - row_off = mnp_swipe_clamp(ddx, NCP_ROW_W); - row_settle = 0; row_off_target = 0; - row_anim_at = now_ms(); - g_ncp_swipe_row = row; g_ncp_swipe_off = row_off; - row_paint = 1; - } - } - } - /* ⑦ 行左滑跟手:已进则更新 off(反向可回 → 回落至 0);只该行生效。 */ - if (row_swipe_row >= 0 && td_ok && !row_settle) { - row_off = mnp_swipe_clamp(tlx - tdx, NCP_ROW_W); - g_ncp_swipe_off = row_off; - row_paint = 1; + /* ⑦ 行左滑(SDK swipe-delete):已交给 SDK 手势 → 喂 move。SDK 方向仲裁: + * 横向小位移超容差 → 消费(返回 1,行左移/删除区由 SDK 画,本触摸不滚/不拖面板); + * 纵向主导 → 取消本行 swipe(返回 0,sdk_swipe=0),回退到下方滚动/面板拖动。 */ + if (sdk_swipe && td_ok && drag_slider == 0) { + meow_listview_set_scroll(g_ncp_lv, list_scroll); + if (!meow_listview_swipe_move(g_ncp_lv, tlx, tly)) sdk_swipe = 0; } /* ⑨ 面板跟手拖动(进入):竖向位移达 slop、起点不在列表溢出滚动区(归 §11)、 * 未锁行左滑/滚动/滑条 → 进入拖动。锁定当前面板露出高度 + 当前手指 y, * 此后 reveal = seed + (tly - drag_y0),方向无关、连续可反向(半程可回)。 */ - if (td_ok && !drag_slider && !scroll_drag && row_swipe_row < 0 && !panel_drag) { + if (td_ok && !drag_slider && !scroll_drag && !sdk_swipe && !panel_drag) { int ady2 = (tly - tdy) < 0 ? (tdy - tly) : (tly - tdy); if (ady2 >= S_SCROLL_SLOP) { int av = g_nrows * NCP_ROW_H; @@ -1260,7 +1457,7 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click, /* 列表滚动(§11):面板就位(open_settled)+ 起点在列表区 + 内容溢出 + * 纵向位移达 slop → 进滚动。方向遵循 input.swipe_invert(与设置页列表同式); * 拖到顶/底继续拖 → 回弹标记。动画进行中(面板在滑,未就位)不启动滚动。 */ - if (open_settled && td_ok && !scroll_drag && drag_slider == 0 && row_swipe_row < 0) { + if (open_settled && td_ok && !scroll_drag && drag_slider == 0 && !sdk_swipe) { int ddy = tly - scroll_y0; if (ddy >= S_SCROLL_SLOP || ddy <= -S_SCROLL_SLOP) { int total = g_nrows * NCP_ROW_H; @@ -1290,10 +1487,8 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click, td = 1; td_ok = 0; tlx = -1; tly = -1; drag_slider = 0; scroll_drag = 0; scroll_base = 0; scroll_y0 = 0; scroll_invert = 0; scroll_rebound = 0; - /* ⑦ 新触摸:清行左滑(防上一手势残留) */ - row_swipe_row = -1; row_off = 0; row_settle = 0; - row_off_target = 0; row_anim_at = 0; row_paint = 0; - g_ncp_swipe_row = -1; g_ncp_swipe_off = 0; + /* ⑦ 新触摸:清行左滑手势(防上一手势残留) */ + sdk_swipe = 0; /* ⑨ 新触摸:清面板跟手(防上一拖动残留) */ panel_drag = 0; drag_seed_reveal = 0; drag_y0 = 0; drag_paint = 0; } @@ -1311,19 +1506,18 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click, if (anim_ok) anim_target = 0; else closed = 1; break; } - if (row_swipe_row >= 0) { - /* ⑦ 行左滑松手:过阈值 → 滑出删除;未过/反向 → 回弹复原(半程取消)。 - * 只该行生效;本手势锁定后不再走 tap/上滑关闭/点空白,不关面板。 */ - /* 用抬手最终位移兜底刷新 off(可能最后有位移但无 ABS 事件跟进)。 */ - row_off = mnp_swipe_clamp(dx, NCP_ROW_W); - g_ncp_swipe_off = row_off; - if (mnp_swipe_delete(row_off, NCP_ROW_W, - NCP_SWIPE_DELETE_FRAC, NCP_SWIPE_DELETE_MIN)) { - row_off_target = -NCP_ROW_W; /* 滑出(删除)目标:整行移出 */ - } else { - row_off_target = 0; /* 回弹复原 */ + if (sdk_swipe && g_ncp_widget_init && g_ncp_lv) { + /* ⑦ 行左滑松手(SDK swipe-delete):消费 → 删除/回弹动画交 SDK + * (meow_anim_tick 推进;删除完成后 ncp_lv_on_delete 置 g_ncp_need_paint)。 + * 本手势不关面板、不走 tap/上滑/点空白。未滑动(=点击某行)→ 未消费, + * 清 sdk_swipe 后走下方 tap 分支(ncp_row_tap 命中该行 → dismiss+关面板)。 */ + meow_listview_set_scroll(g_ncp_lv, list_scroll); + if (meow_listview_swipe_end(g_ncp_lv, tlx, tly)) { + sdk_swipe = 0; + td_ok = 0; + goto ncp_release_done; } - row_settle = 1; row_anim_at = now_ms(); + sdk_swipe = 0; } else if (scroll_drag) { /* 列表滚动落定:最终 clamp 到边界(拖动中已实时跟手,此处确认终值); * 若回弹(拖到顶/底继续拖)→ 按「上滑/下滑关闭」收起(§11.3)。 @@ -1395,10 +1589,8 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click, if (!hit && ncp_clear_hit(tx, ty)) { int n = ncp_clear_all(); logmsg("notif-center: clear %d notif(s) (dumped first, kept ongo/media/progress)", n); - /* 清除后取消行左滑(下标会变),再重采集 + 重钳 + 整面板重画。 */ - row_swipe_row = -1; row_off = 0; row_settle = 0; - row_off_target = 0; row_anim_at = 0; row_paint = 0; - g_ncp_swipe_row = -1; g_ncp_swipe_off = 0; + /* 清除后取消行左滑手势(下标会变),再重采集 + 重钳 + 整面板重画。 */ + sdk_swipe = 0; ncp_refresh_rows(); list_scroll = mnp_scroll_clamp(list_scroll, g_nrows * NCP_ROW_H, ncp_list_view()); @@ -1430,6 +1622,8 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click, /* 其余横滑/下滑忽略(不关闭不动作) */ } td_ok = 0; + ncp_release_done: + ; } } } @@ -1453,10 +1647,8 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click, media_paint = 1; /* 已有源:进度/封面随 META 变化 → 仅重绘卡片带 */ } if (ncp_rows_changed()) { - /* ⑦ 列表已变(新条/撤下/TTL):取消当前行左滑,避免行下标错位。⑨ 同时取消面板跟手。 */ - row_swipe_row = -1; row_off = 0; row_settle = 0; - row_off_target = 0; row_anim_at = 0; row_paint = 0; - g_ncp_swipe_row = -1; g_ncp_swipe_off = 0; + /* ⑦ 列表已变(新条/撤下/TTL):取消当前行左滑手势,避免行下标错位。⑨ 同时取消面板跟手。 */ + sdk_swipe = 0; panel_drag = 0; drag_seed_reveal = 0; drag_y0 = 0; drag_paint = 0; ncp_refresh_rows(); list_scroll = mnp_scroll_clamp(list_scroll, g_nrows * NCP_ROW_H, @@ -1490,41 +1682,8 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click, } if (anim_ok && !panel_drag && !td && anim_target == 0 && anim_p == 0) break; /* 滑出到 0 且无按下触摸 → 真正退出子循环 */ - /* ⑦ 行左滑落位动画:row_settle=1 时按壁钟差向 row_off_target 线性步进(回弹→0 / 滑出→-row_w)。 - * 到达目标:0=回弹完成(行复原,无需重排);-row_w=删除 → dismiss + 重采集 + 重排(need_paint)。 - * 只该行生效;与面板开关动画(上块)互不干扰(行左滑仅在面板稳态 open_settled 下进行)。 */ - if (row_swipe_row >= 0 && row_settle) { - unsigned long long rnow = now_ms(); - int rdt = (int)((rnow > row_anim_at) ? (rnow - row_anim_at) : 0); - row_anim_at = rnow; - int noff = mnp_swipe_step(row_off, row_off_target, (unsigned long long)rdt, NCP_SWIPE_RATE); - if (noff != row_off) { - row_off = noff; - g_ncp_swipe_off = noff; - row_paint = 1; - } - if (noff == row_off_target) { - if (row_off_target == 0) { - /* 回弹复原完成:先把行重画回 0 位(清残留偏移),再清滑动态(无需重排)。 */ - ncp_repaint_row(list_scroll, row_swipe_row, 0); - row_swipe_row = -1; row_settle = 0; row_off = 0; - row_off_target = 0; row_paint = 0; - g_ncp_swipe_row = -1; g_ncp_swipe_off = 0; - if (anim_ok) ncp_snap_panel(); /* 行已复原:刷新面板快照(供滑出动画用) */ - } else { - /* 滑出删除完成:撤下该通知 + 重采集 + 重排列表(整面板重画)。 */ - if (row_swipe_row >= 0 && row_swipe_row < g_nrows) - meow_notif_dismiss(g_rows[row_swipe_row].id); - row_swipe_row = -1; row_settle = 0; row_off = 0; - row_off_target = 0; row_paint = 0; - g_ncp_swipe_row = -1; g_ncp_swipe_off = 0; - ncp_refresh_rows(); - list_scroll = mnp_scroll_clamp(list_scroll, g_nrows * NCP_ROW_H, - ncp_list_view()); - need_paint = 1; /* 行已移除/重排:整面板(列表区)重画 */ - } - } - } + /* M7.3:行左滑落位动画已由 SDK 接管(meow_listview_swipe_end → meow_anim → swipe_anim_done + * → on_delete(idx) → ncp_lv_on_delete 置 g_ncp_need_paint)。此处不再需要手绘行位移/回弹/滑出。 */ /* 重绘(面板体区域;状态栏 0-27 由 draw_statusbar 单独维护,不落入此脏区)。 * need_sb=1(打开/主题变化)时 draw_notif_center 内另绘状态栏并标其脏——脏区仍 @@ -1561,14 +1720,8 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click, ncp_anim_frame(anim_p, &anim_prev_bot, anim_ok); drag_paint = 0; } - /* 仅重绘该行带(行左滑跟手/回弹/滑出专用:只刷该行矩形,不整面板/不刷状态栏;同受动画门控)。 - * row_swipe_row<0(行已回弹/删除/被取消)则跳过并清标记,避免对已移除行重绘。 */ - if (row_paint) { - last_act = now; - if (row_swipe_row >= 0 && !(anim_ok && anim_p != anim_target)) - ncp_repaint_row(list_scroll, row_swipe_row, row_off); - row_paint = 0; - } + /* M7.3:行左滑跟手/回弹/滑出重绘已由 SDK 接管(swipe_anim_update 经 meow_widget_draw(lv) + * 重绘列表 + meow_anim_set_region 标该行带脏)。此处不再有 row_paint 手绘路径。 */ /* ~1s 状态栏时钟 */ if (now - last_tick >= 1000) { last_tick = now; @@ -1576,12 +1729,27 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click, fb_norm_buf0(); meow_ui_flush(); } + /* M7.3:推进 SDK 动画(行左滑删除/回弹、meow_scroll fling 等)。左滑删除完成后 on_delete + * 置 g_ncp_need_paint → 下一帧整面板重画。swipe 动画推进时 SDK 已重绘列表(swipe_anim_update + * 调 meow_widget_draw(lv)),此处只把列表带标脏上屏(覆盖 SDK 经 meow_gfx 标脏的列表矩形)。 */ + meow_anim_tick(); + if (g_ncp_need_paint) { g_ncp_need_paint = 0; need_paint = 1; } + if (g_ncp_widget_init && g_ncp_lv && meow_listview_swipe_active(g_ncp_lv)) { + meow_dirty_add(0, ncp_list_top(), fb_xres, ncp_list_view()); + meow_ui_flush(); + } usleep(POLL_MS * 1000); } if (tfd >= 0) close(tfd); kp_close(&kp_power); kp_close(&kp_keys); ncp_snap_free(); /* 释放动画快照(退出即回收,不驻留) */ ncp_rows_free(); + /* 封面缓存随面板关闭回收(对齐 Now Playing np_cleanup:每次下拉重新加载当前封面)。 + * 旧码 g_ncp_media_cover_path 是文件级 static、从不重置——若首次加载失败/或 artwork 初 + * 为空的瞬间进面板,其后即便封面落盘并因路径不变而被「按文本缓存」跳过重载,卡片永久 + * 停留在「显示歌名」回退/旧封面。关面板即清缓存,保证下次下拉能拿到最新封面。 */ + if (g_ncp_media_cover) { meow_img_free(g_ncp_media_cover); g_ncp_media_cover = NULL; } + g_ncp_media_cover_path[0] = 0; logmsg("notification center closed"); } diff --git a/patch_v2/meow_launcherd/now_playing.c b/patch_v2/meow_launcherd/now_playing.c index 60f47bb..4cc9086 100644 --- a/patch_v2/meow_launcherd/now_playing.c +++ b/patch_v2/meow_launcherd/now_playing.c @@ -211,7 +211,7 @@ static void np_draw_page(int redraw_sb) const char *t = s->title[0] ? s->title : s->app_id; int cw = ui_width(t, 22); ui_draw(NP_COVER_X + (NP_COVER - cw) / 2, NP_COVER_Y + (NP_COVER - meow_font_line_height(22)) / 2, - t, 0xFFFFFF, 22); + t, meow_theme_get()->onPrimary, 22); } /* 元数据 */ np_center_text(NP_TITLE_Y, s->title, 26, th->on_surface, W - 40); diff --git a/patch_v2/meow_launcherd/power_menu.c b/patch_v2/meow_launcherd/power_menu.c index 0bdd138..e02bdca 100644 --- a/patch_v2/meow_launcherd/power_menu.c +++ b/patch_v2/meow_launcherd/power_menu.c @@ -80,8 +80,9 @@ static void draw_pm_button(int i, int selected) /* ================================================================== * M7.0b 电源菜单控件化(launcher 无 meow_ui_run,手动驱动 SDK 控件): * - 圆角卡 → meow_card(SDK 主题 surfaceContainerLow); - * - 六个圆钮 → meow_iconbtn(图标在 SDK sb_icons 表:回HiBy→ARROW_BACK、取消→CLOSE) - * 或 meow_widget_custom_create(pm_icons 专有位图:关机/重启/升级/锁定); + * - 六个圆钮 → meow_widget_custom_create(pm_icons 专有位图:关机/重启/回HiBy/升级/锁定/取消, + * 均 56×56 1:1 blit,无放大)。meow_iconbtn 分支保留(回HiBy/取消曾改走 sb_icons,因放大锯齿 + * 定案回退 pm_icons,见 pm_sdk_icon[] 注释;后续 SDK 出更高清图标可再启用); * - 命中/选中/按压态全走 SDK(meow_widget_hit/set_state/draw); * - 主题 = 读 SDK meow_theme(单一权威),后台遮罩仍 dim_screen(会话骨架不动)。 * 控件 lazy 创建一次(owner=PM_SENTINEL_ACT 哨兵 activity),跨多次进入复用; @@ -90,14 +91,19 @@ static void draw_pm_button(int i, int selected) #define PM_SENTINEL_ACT 0x50 /* 电源菜单哨兵 activity(不与 launcher 其它页冲突) */ #define PM_BTN_SIZE (PM_R * 2) /* 圆钮 = 外接正方形边长(直径) */ -/* 哪些 pm 图标在 SDK sb_icons 表(可用 meow_iconbtn 直接出图);-1=不在 → 自定义画布 blit pm_icons */ +/* 哪些 pm 图标走 SDK sb_icons 表(可用 meow_iconbtn 直接出图);-1=不在 → 自定义画布 blit pm_icons。 + * M7.0b 曾把 2=回HiBy / 5=取消 走 meow_iconbtn(SB_ARROW_BACK / SB_CLOSE),但 meow_iconbtn 按 + * 0.6*size 把 32×32 sb_icons 位图放大到 ~60px(>位图原生墨高),最近邻放大导致明显锯齿;且此尺寸 + * 大于 M6.8 及更早电源菜单圆钮图标(pm_icons 56×56,1:1 无放大)。定案:这两钮也回退 pm_icons 专有 + * 位图(PM_ICON_BACK/PM_ICON_CANCEL,同为 Material Symbols 56×56),统一其余四钮画法,恢复旧尺寸。 + * pm_icons[] 全部专用 → meow_iconbtn 分支保留(后续 SDK 出更高清图标可再启用)。 */ static const int pm_sdk_icon[PM_NUM] = { -1, /* 0 关机:pm_icons 专有(SDK 无 power 位图) */ -1, /* 1 重启:pm_icons 专有 */ - MEOW_ICON_ARROW_BACK, /* 2 回HiBy:SB_ARROW_BACK 在表 */ + -1, /* 2 回HiBy:pm_icons 专有(恢复 M6.8 56×56,避免 meow_iconbtn 放大锯齿) */ -1, /* 3 升级:pm_icons 专有 */ -1, /* 4 锁定:pm_icons 专有(SDK 无 lock 位图) */ - MEOW_ICON_CLOSE, /* 5 取消:SB_CLOSE 在表 */ + -1, /* 5 取消:pm_icons 专有(同 2,恢复旧尺寸) */ }; /* 圆钮文案 i18n id(meow_str)。common.cancel 内嵌已有;power.* 需 TF 覆盖表/后续并入 SDK 内嵌。 */ @@ -573,17 +579,20 @@ int show_power_menu(void) * 不随主循环每 10ms 全刷(原实现每帧重画同一内容,与 LCD 刷新竞争产生频闪)。 */ static void draw_cr_button(int sel, int i) { + const meow_theme *mt = meow_theme_get(); /* M7.5:主题语义色(深/浅×MD3/MIUIx 单一权威) */ int x0 = (i == 0) ? 80 : 280, y0 = 420, w = 120, h = 60; - fill_round_rect(x0, y0, w, h, 12, (i == sel) ? 0x2A3A6A : 0x1A1A2A); + uint32_t bg = (i == sel) ? mt->primary : mt->surfaceContainer; + fill_round_rect(x0, y0, w, h, 12, bg); /* M7.0b i18n:是/否 经 meow_str 抽取(未知 id 回退中文) */ const char *lbl = (i == 0) ? meow_str_zh("power.confirm_yes", "是") : meow_str_zh("power.confirm_no", "否"); + uint32_t fg = (i == sel) ? mt->onPrimary : mt->onSurface; if (mfont_ready) { int lw = meow_font_width_text(lbl, 20); meow_font_drawtext((uint16_t *)fb, x0 + w / 2 - lw / 2, y0 + 30 - meow_font_line_height(20) / 2, - lbl, rgb565((i == sel) ? 0xFFFFFF : 0xC8D0E8), 20); + lbl, rgb565(fg), 20); } else { - draw_text(x0 + w / 2 - 6, y0 + 24, (i == 0) ? "YES" : "NO", 2, (i == sel) ? 0xFFFFFF : 0xC8D0E8); + draw_text(x0 + w / 2 - 6, y0 + 24, (i == 0) ? "YES" : "NO", 2, fg); } } @@ -594,9 +603,10 @@ int confirm_reboot_player(void) /* M2.8 Bug 3-residual:确认框同电源菜单 —— 若打开前刚经历息屏/blank,LCDC 扫描地址可能 * 非 buffer0;强制 re-pan 让「压暗列表 + 是/否」落在被显示的缓冲。 */ fb_pan_buf0_force(); + const meow_theme *mt0 = meow_theme_get(); /* M7.5:标题颜色走主题语义色 */ if (mfont_ready) meow_font_drawtext((uint16_t *)fb, 20, 320, - meow_str_zh("power.reboot_confirm", "返回播放器需重启,现在重启?"), rgb565(0xFFFFFF), 20); - else draw_text(20, 322, "REBOOT TO RETURN TO PLAYER?", 2, 0xFFFFFF); + meow_str_zh("power.reboot_confirm", "返回播放器需重启,现在重启?"), rgb565(mt0->onSurface), 20); + else draw_text(20, 322, "REBOOT TO RETURN TO PLAYER?", 2, mt0->onSurface); meow_touch mt; mt_reset(&mt); @@ -616,8 +626,8 @@ int confirm_reboot_player(void) /* 标题/按键提示:打开时画一次(原实现把它们放主循环每 10ms 全刷,与 LCD 刷新竞争 → * 「是/否」钮频闪;改为只在打开/状态变化时绘制)。标题在前面已画一次,这里画提示行。 */ if (mfont_ready) meow_font_drawtext((uint16_t *)fb, 20, 560, - meow_str_zh("power.confirm_hint", "音量切换 电源取消"), rgb565(0x666688), 20); - else draw_text(20, 562, "VOL NAV POWER EXIT", 1, 0x666688); + meow_str_zh("power.confirm_hint", "音量切换 电源取消"), rgb565(mt0->onSurfaceVariant), 20); + else draw_text(20, 562, "VOL NAV POWER EXIT", 1, mt0->onSurfaceVariant); meow_dirty_full(); /* M2.16:压暗列表+标题+提示作为一整帧呈现 */ for (;;) { /* dirty 绘制:首次全画两钮;此后仅 sel 变化才重画(消频闪)→ 标脏 + present。 */ diff --git a/patch_v2/meow_launcherd/settings_page.c b/patch_v2/meow_launcherd/settings_page.c index ea613a7..a5647c8 100644 --- a/patch_v2/meow_launcherd/settings_page.c +++ b/patch_v2/meow_launcherd/settings_page.c @@ -126,9 +126,12 @@ static const srow_t SMENU[] = { /* M2.39 左缘返回手势已抽为共享函数 meow_edge_back()(meow_touch.h MEOW_EDGE_W/SWIPE 由 * 其定义),设置页运行期调用;此处不再定义独立 S_EDGE_W/S_SWIPE_THRESH。 */ +#if 0 /* M7.2 回退位:旧「当前选中滑条行 track y」随旧手绘 draw_settings_page 退役 + * (新路径在触摸按下时由 meow_listview hit 直接算 track 中心,无需全局记录)。 */ /* 当前选中行若是滑条:记录其 track 纵向位置,供触摸拖动命中判(x/w 为布局常量, * 所有滑条行相同,可直接算;仅 y 随行变,故独立记录)。 */ static int s_sel_track_y = -1; +#endif /* ---- 行高/位置/导航辅助 ---- */ @@ -206,6 +209,9 @@ static const char *const *s_row_presets(const srow_t *r, int *n) } +#if 0 /* M7.2 回退位:旧「纯范围/选中可见滚动钳制 + 内容 y→行号」随旧手绘 draw_settings_page 退役 + * (新路径滚动走 meow_scroll 包裹 meow_listview,钳制/命中/可见性由 SDK 接管)。 + * 删除 #if 0 行并切回旧 draw_settings_page 路径即可恢复。 */ /* 滚动:纯范围钳制到 [0, 内容高度-视口高](列表拖动滚动用;不强制选中行可见) */ static int s_clamp_scroll_range(int scroll) { @@ -240,6 +246,7 @@ static int s_row_at_content_y(int cy) if (pos >= s_vis_count()) return -1; return s_vis_at(pos); } +#endif /* M7.2 回退位:旧滚动钳制 + 内容 y→行号 */ /* ---- 值格式化/展示 ---- */ @@ -497,6 +504,9 @@ static int s_slider_val_from_x(const srow_t *r, int px) } +#if 0 /* M7.2 回退位:旧手绘设置页主列表(手绘分组列表 + s_draw_switch/s_draw_slider + 自算滚动/命中)。 + * 新路径走 meow_listview + meow_switch + meow_scroll + meow_select_show;删除 #if 0 行并把 + * 下方「M7.2 SDK 路径」的 set_paint_page 调用切回 draw_settings_page 即可一键回退。 */ /* ---- 绘制设置页(整页画进 SDK back_buf;调用方再 meow_dirty_add 局部 + flush)---- */ static void draw_settings_page(int sel, int scroll_off) { @@ -596,6 +606,7 @@ static void draw_settings_page(int sel, int scroll_off) ui_draw(fb_xres - 8 - ui_width(ver, 20), foot_y, ver, th->outline, 20); } } +#endif /* M7.2 回退位:旧手绘设置页主列表 */ /* M2.5x 多字体:class 行下拉的「跟随全局」选项(值=空串)在 write 路径经此映射回真实存储值。 */ @@ -658,7 +669,9 @@ static int s_dropdown(const srow_t *r) } if (n <= 0) return -1; if (cur < 0 || cur >= n) cur = 0; - return s_dd_modal(r->label, item, n, cur); + /* M7.2:枚举/预设选择器迁 SDK meow_select_show(BottomSheet+单选列表,批次2 新控件), + * 替换自绘 s_dd_modal(后者仍供 about/网络列表/字体两级子菜单用,勿删)。 */ + return meow_select_show(r->label, item, n, cur); } @@ -686,7 +699,7 @@ static void dd_modal_paint(const meow_ui_theme_t *th, int W, int H, int d_x, int int cur_sel = (i == sel); if (cur_sel) meow_ui_fill_rect_alpha(d_x + 4, ry, d_w - 8, opt_row, th->primary, 36); ui_draw(d_x + 18, ry + (opt_row - meow_font_line_height(20)) / 2, item[i] ? item[i] : "", - cur_sel ? 0xFFFFFF : th->on_surface, 20); + cur_sel ? meow_theme_get()->onPrimary : th->on_surface, 20); if (i == cur) meow_ui_fill_rect(d_x + d_w - 28, ry + (opt_row - 12) / 2, 12, 12, th->primary); } } @@ -966,11 +979,228 @@ static int s_font_dropdown(const srow_t *r, char *out_val, int vlen) } +/* ================================================================== + * M7.2 设置页渲染层迁移(主列表迁 SDK 控件): + * - 分组列表 → meow_listview(虚拟化 set_vsource 固定 item 池 + section 段头 + set_item_draw 自绘); + * - 开关行 → 行内真 meow_switch(池化,SDK 绘制 + 按压态); + * - 滑条行 → 行内嵌 meow_slider(自绘 track/thumb,保留既有 s_draw_slider 语义色; + * 「行内嵌自绘」,M7 定案允许)——拖动实时写 settings.conf(读改写单键, + * 经 meow_settings_set_*,勿整文件覆盖——音量存盘教训); + * - 枚举/预设 → SDK meow_select_show(BottomSheet+单选列表,替换自绘 s_dd_modal); + * - 信息/列表行 → 行 right 值 + chevron(SDK 主题语义色)。 + * 滚动 → meow_scroll(跟手 track + 松手 fling,同 subpage.c M7.1a 语义;刻意不 set_snap)。 + * 控件 lazy 创建一次(owner=SET_SENTINEL_ACT 哨兵),跨多次进入复用;数据/动作(s_commit、 + * s_set_int_value、s_net_*、s_font_dropdown、s_dropdown)逻辑层不动,仅换渲染层 + 命中; + * run_settings_page 会话骨架(触摸/滚动/返回/IPC/电源键语义)不改。旧手绘 draw_settings_page + * + s_clamp_scroll + s_row_at_content_y 保留 #if 0 作回退位(M7 红线:一键回退)。 + * ================================================================== */ +#define SET_SENTINEL_ACT 0x53 /* 设置页哨兵 activity(不与 launcher 其它页冲突) */ +#define SET_LIST_Y SET_CONTENT_Y /* 100:列表顶缘(顶栏/标题下) */ +#define SET_FOOT_H 40 /* 底部返回/版本带高 */ +#define SET_ITEM_H 68 /* MD3 行高(滑条行给 track 留位) */ +#define SET_SLIDER_YOFF 45 /* 滑条行内 track 顶缘相对行顶(÷s_draw_slider h=6,中心=顶+3) */ +#define SET_TRACK_CY 48 /* 滑条 track 中心相对行顶 y(=SET_SLIDER_YOFF+3,与绘制对齐) */ +#define SET_SLIDER_LY 12 /* 滑条行 label 顶相对行顶 y(上移给 track 留位) */ +#define SET_SWITCH_POOL 12 + +static meow_widget *g_set_lv = NULL; /* meow_listview(虚拟化) */ +static meow_scroll *g_set_sc = NULL; /* 滚动物理 */ +static meow_widget *g_set_sw[SET_SWITCH_POOL]; /* 行内开关池(真 meow_switch) */ +static int g_set_sw_n = 0, g_set_sw_use = 0; /* 池创建数 / 本轮已用(每次整绘复位) */ +static int g_set_lv_h = 0; /* 列表视口高(选中可见性 clamp 用) */ +static int g_set_init = 0; + +static void set_lv_rect(int *x, int *y, int *w, int *h) +{ + if (x) *x = 0; + if (y) *y = SET_LIST_Y; + if (w) *w = fb_xres; + if (h) { int t = fb_yres - SET_LIST_Y - SET_FOOT_H; if (t < 0) t = 0; *h = t; } +} + +static int set_lv_count(void *ctx) { (void)ctx; return s_vis_count(); } + +/* 行填充(虚拟化 pool bind;段头行 section=1、enabled=1 供 SDK 命中/段头绘制) */ +static void set_lv_bind(int idx, meow_lv_item *out, void *ctx) +{ + (void)ctx; + memset(out, 0, sizeof *out); + out->icon_kind = MEOW_LIST_ICON_NONE; + out->enabled = 1; + if (idx < 0 || idx >= s_vis_count()) { out->title = ""; return; } + const srow_t *r = &SMENU[s_vis_at(idx)]; + out->title = r->label ? r->label : ""; + out->section = (r->ctrl == SC_HEADER) ? 1 : 0; + if (r->ctrl == SC_SWITCH) { + int on = 0; meow_settings_get_switch(r->key, &on); + out->right = MEOW_LIST_RIGHT_SWITCH; out->on = on; + } +} + +/* 行内开关池:取下一个可用槽(每帧 set_lv_draw 顺序取用,整绘前复位 g_set_sw_use) */ +static meow_widget *set_sw_take(void) +{ + if (g_set_sw_use < g_set_sw_n) return g_set_sw[g_set_sw_use++]; + if (g_set_sw_n >= SET_SWITCH_POOL) return NULL; + meow_widget *w = meow_switch_create(0, 0, 0); + if (w) { g_set_sw[g_set_sw_n++] = w; return g_set_sw[g_set_sw_use++]; } + return NULL; +} + +/* 整行自绘(set_item_draw 覆盖默认 renderer;x=列表左缘、y=行顶、w/h=行全宽高, + * state=SELECTED/PRESSED/DISABLED 组合;颜色全取 meow_theme 语义色,无散落硬编码) */ +static void set_lv_draw(int idx, int x, int y, int w, int h, int state, void *ctx) +{ + (void)ctx; (void)h; + const meow_theme *mt = meow_theme_get(); + if (idx < 0) return; + int si = s_vis_at(idx); + const srow_t *r = &SMENU[si]; + int sel = (state & MEOW_LISTVIEW_ST_SELECTED); + int press = (state & MEOW_LISTVIEW_ST_PRESSED); + int dis = (state & MEOW_LISTVIEW_ST_DISABLED); + int thh = meow_ui_text_height(mt->font_body); + uint32_t textcol = sel ? mt->onPrimaryContainer : mt->onSurface; + uint32_t subcol = sel ? mt->onPrimaryContainer : mt->onSurfaceVariant; + + if (r->ctrl == SC_HEADER) { + meow_ui_fill_rect(x, y, w, SET_ITEM_H, mt->surfaceContainerHigh); + int ty = y + (SET_ITEM_H - thh) / 2; + if (ty >= 0) meow_ui_text(x + 16, ty, r->label, mt->font_body, mt->primary); + meow_ui_fill_rect(x, y + SET_ITEM_H - 1, w, 1, mt->outlineVariant); + return; + } + if (sel) meow_ui_fill_rect(x, y, w, SET_ITEM_H, mt->primaryContainer); + else if (press) meow_ui_fill_rect_alpha(x, y, w, SET_ITEM_H, mt->primary, 26); + + int ly = y + (SET_ITEM_H - thh) / 2; /* label 单行垂直居中 */ + switch (r->ctrl) { + case SC_SWITCH: { + meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol); + meow_widget *sw = set_sw_take(); + if (sw) { + int on = 0; meow_settings_get_switch(r->key, &on); + meow_widget_set_rect(sw, x + w - 16 - 56, y + (SET_ITEM_H - 32) / 2, 56, 32); + meow_switch_set(sw, on); + meow_widget_draw(sw); + } + break; + } + case SC_SLIDER: { + /* label 上移一行,track 放行下方(中心=SET_SLIDER_YOFF+3=48),与拖动命中带对齐 */ + meow_ui_text(x + 16, y + SET_SLIDER_LY, r->label, mt->font_body, textcol); + char vb[32]; s_fmt_value(r, vb, sizeof vb); + meow_ui_text(x + w - 16 - meow_ui_text_width(vb, mt->font_body), y + SET_SLIDER_LY, vb, mt->font_body, subcol); + int v = 0; meow_settings_get_int(r->key, &v); + /* 息屏时间为档位索引型(秒→索引 等距画);其余 clamp 到 [min,max] */ + if (!strcmp(r->key, "display.screen_off_timeout")) v = s_time_tier_idx(v); + else { if (v < r->min) v = r->min; if (v > r->max) v = r->max; } + s_draw_slider(S_SLIDER_X, y + SET_SLIDER_YOFF, S_SLIDER_W, v, r->min, r->max, s_ui_theme()); + break; + } + case SC_INFO: { + meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol); + char vb[48]; s_net_get(r, vb, sizeof vb); + meow_ui_text(x + w - 16 - meow_ui_text_width(vb, mt->font_body), ly, vb, mt->font_body, subcol); + break; + } + case SC_LIST: { + meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol); + char vb[48]; s_net_get(r, vb, sizeof vb); + int vx = x + w - 16 - 12 - meow_ui_text_width(vb, mt->font_body); + meow_ui_text(vx, ly, vb, mt->font_body, subcol); + s_draw_chevron(x + w - 16, y + SET_ITEM_H / 2, subcol); + break; + } + default: { /* SC_ENUM / SC_PRESET */ + meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol); + char vb[40]; s_display_value(r, vb, sizeof vb); + int vx = x + w - 16 - meow_ui_text_width(vb, mt->font_body); + meow_ui_text(vx, ly, vb, mt->font_body, subcol); + s_draw_chevron(vx - 8, y + SET_ITEM_H / 2, subcol); + break; + } + } + meow_ui_fill_rect(x, y + SET_ITEM_H - 1, w, 1, mt->outlineVariant); + if (dis) meow_ui_fill_rect_alpha(x, y, w, SET_ITEM_H, mt->onSurface, 90); +} + +/* ---- meow_scroll 回调(包 listview)---- */ +static void set_sc_set(void *ctx, int off) { (void)ctx; if (g_set_lv) meow_listview_set_scroll(g_set_lv, off); } +static int set_sc_max(void *ctx) { (void)ctx; return g_set_lv ? meow_listview_max_scroll(g_set_lv) : 0; } +static int set_sc_get(void *ctx) { (void)ctx; return g_set_lv ? meow_listview_scroll(g_set_lv) : 0; } + +/* 列表区重绘(含内容底 + listview),供 fling/滚动局部刷。 */ +static void set_redraw_region(void *ctx) +{ + (void)ctx; + int lvx, lvy, lvw, lvh; set_lv_rect(&lvx, &lvy, &lvw, &lvh); + if (lvh <= 0) return; + const meow_ui_theme_t *th = s_ui_theme(); + meow_ui_fill_rect(lvx, lvy, lvw, lvh, th->surface_variant); + if (g_set_lv) { g_set_sw_use = 0; meow_widget_draw(g_set_lv); } + meow_dirty_add(lvx, lvy, lvw, lvh); + meow_ui_flush(); +} + +/* 选中行可见性:滚动到选中行不越界(仅靠 tap 触发,选中行即在视野内,防御性兜底)。 */ +static void set_ensure_sel_visible(int sel) +{ + if (!g_set_lv) return; + int p = s_vis_of(sel); if (p < 0) return; + int scroll = meow_listview_scroll(g_set_lv); + int top = p * SET_ITEM_H, bot = top + SET_ITEM_H; + if (top < scroll) meow_listview_set_scroll(g_set_lv, top); + else if (bot > scroll + g_set_lv_h) meow_listview_set_scroll(g_set_lv, bot - g_set_lv_h); +} + +/* 创建设置页控件集(owner=SET_SENTINEL_ACT;仅首进创建一次,跨次进入复用)。 */ +static void set_widgets_create(void) +{ + meow_ui_activity_push(SET_SENTINEL_ACT); + int lvx, lvy, lvw, lvh; set_lv_rect(&lvx, &lvy, &lvw, &lvh); + g_set_lv_h = lvh; + g_set_lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE); + if (g_set_lv) { + meow_listview_set_vsource(g_set_lv, set_lv_count, set_lv_bind, NULL); + meow_listview_set_item_draw(g_set_lv, set_lv_draw, NULL); + meow_listview_set_item_h(g_set_lv, SET_ITEM_H); + /* 注:刻意不 set_snap —— SDK meow_scroll 的 snap 以「fling 起点」为吸附目标会往回拉 */ + } + g_set_sc = meow_scroll_create(set_sc_set, set_sc_max, set_sc_get, set_redraw_region, NULL); + meow_ui_activity_pop(); + g_set_init = 1; +} + +/* 整页绘制(进 back_buf;调用方 meow_dirty_full/meow_dirty_add + flush)。sel=当前选中(SMENU 下标)。 */ +static void set_paint_page(int sel) +{ + const meow_ui_theme_t *th = s_ui_theme(); + int W = fb_xres, H = fb_yres; + fill_rect(0, 0, W, H, th->surface); + draw_statusbar(); + if (mfont_ready) ui_draw(24, 50, "系统设置", th->on_surface, 28); + else draw_text(24, 30, "SETTINGS", 3, th->on_surface); + fill_rect(0, 50 + (mfont_ready ? meow_font_line_height(28) : 24) + 2, W, 2, th->outline); + fill_rect(0, SET_CARD_Y, W, H - SET_CARD_Y, th->surface_variant); + if (g_set_lv) { + g_set_sw_use = 0; + int p = s_vis_of(sel); if (p >= 0) meow_listview_set_sel(g_set_lv, p); + meow_widget_draw(g_set_lv); + } + /* 底部:返回(左)+ 版本(右),复用应用列表底部对齐逻辑 */ + char ver[32]; + snprintf(ver, sizeof ver, "MeowOS v%s dev", g_meowos_version); + int foot_y = fb_yres - meow_font_line_height(20) - 8; + s_draw_chevron_left(SET_CARD_X + 12, foot_y + meow_font_line_height(20) / 2, th->on_surface_variant); + ui_draw(SET_CARD_X + 24, foot_y, "返回", th->on_surface_variant, 20); + ui_draw(fb_xres - 8 - ui_width(ver, 20), foot_y, ver, th->outline, 20); +} + /* ---- 运行设置页(独立子循环;run_menu 触摸命中设置行后调用)---- */ void run_settings_page(void) { int sel = s_first_setting(); - int scroll_off = 0; int H = fb_yres; unsigned long long t_sets = now_ms(); /* M3 取证:进设置页耗时分段(真机核对用) */ @@ -1006,7 +1236,7 @@ void run_settings_page(void) int drag = -1; /* 被拖动的行索引;-1=无 */ /* M2.37 列表拖动滚动状态 */ int scroll_drag = 0; /* 是否处于列表拖动滚动手势 */ - int scroll_base = 0; /* 进入滚动时对应的 scroll_off 基准(连续跟手) */ + int drag_last_y = 0; /* M7.2:滚动跟手增量基准(meow_scroll_track 速度采样) */ int scroll_y0 = 0; /* 按下原点纵向坐标 */ int scroll_invert = 0; /* M2.38 Bug4:滑动方向切换(input.swipe_invert;0=标准方向) */ int sel_changed = 0; /* 本轮是否改变了选中行(仅此场景才强制选中行可见) */ @@ -1024,8 +1254,9 @@ void run_settings_page(void) * s_net_refresh_pump() 填充,draw 用缓存(g_net_*),数据变化即 need_paint。 */ s_net_sync_from_sys(); logmsg("settings entry: net toggle-sync+kick %llums", now_ms() - t_sets); - scroll_off = s_clamp_scroll(sel, scroll_off); - draw_settings_page(sel, scroll_off); + if (!g_set_init) set_widgets_create(); /* M7.2:lazy 建 SDK 控件集(一次,跨次进入复用) */ + meow_ui_activity_push(SET_SENTINEL_ACT); /* 控件 owner 闸门打开(hit/按压态生效) */ + set_paint_page(sel); meow_dirty_full(); meow_ui_flush(); logmsg("settings entry: page shown in %llums", now_ms() - t_sets); @@ -1074,8 +1305,7 @@ void run_settings_page(void) touch_cooldown_until = now_ms() + 300; td = 0; td_ok = 0; drag = -1; scroll_drag = 0; tlx = tly = -1; logmsg("settings: auto screen-off -> input wake, stayed in settings page"); - scroll_off = s_clamp_scroll_range(scroll_off); - draw_settings_page(sel, scroll_off); + set_paint_page(sel); meow_dirty_full(); meow_ui_flush(); } usleep(POLL_MS * 1000); @@ -1206,34 +1436,47 @@ void run_settings_page(void) else if (code == ABS_Y || code == ABS_MT_POSITION_Y) tly = val; if (td && !td_ok && tlx >= 0 && tly >= 0) { tdx = tlx; tdy = tly; td_ok = 1; - scroll_y0 = tdy; /* 列表拖动基准:按下原点(触发时才锁 scroll_base) */ - /* 按下原点落在选中滑条 track 带内 → 开始拖动 */ - if (SMENU[sel].ctrl == SC_SLIDER && s_sel_track_y >= 0 && - tlx >= S_SLIDER_X - 12 && tlx <= S_SLIDER_X + S_SLIDER_W + 12 && - tly >= s_sel_track_y - 14 && tly <= s_sel_track_y + 20) { - drag = sel; - const srow_t *r = &SMENU[sel]; - int v = s_slider_val_from_x(r, tlx); - s_slider_set_from_track(r, v, 0); - need_paint = 1; + meow_input_touch_set(1, tdx, tdy, 0); /* 喂 SDK 输入态(行按压态渲染) */ + scroll_y0 = tdy; /* 列表拖动基准:按下原点 */ + /* 按下原点落在滑条行 track 带内 → 开始拖动(meow_listview hit 判行) */ + int vp = g_set_lv ? meow_listview_hit(g_set_lv, tlx, tly) : -1; + if (vp >= 0 && drag < 0) { + const srow_t *rr = &SMENU[s_vis_at(vp)]; + if (rr->ctrl == SC_SLIDER) { + int track_cy = SET_LIST_Y + vp * SET_ITEM_H - + meow_listview_scroll(g_set_lv) + SET_TRACK_CY; + if (tlx >= S_SLIDER_X - 12 && tlx <= S_SLIDER_X + S_SLIDER_W + 12 && + tly >= track_cy - 14 && tly <= track_cy + 14) { + drag = s_vis_at(vp); + int v = s_slider_val_from_x(rr, tlx); + s_slider_set_from_track(rr, v, 0); + need_paint = 1; + } + } } } - /* M2.37 列表拖动滚动:非滑条行按下后纵向位移超阈值 → 进入列表滚动。 - * scroll_base 在触发瞬间反推,保证进入滚动那一刻不跳变(连续跟手)。 - * M2.38 Bug4:默认标准滑动(手指上滑 → scroll_off 增大 = 内容往下翻看后面项); - * 方向可经 input.swipe_invert 切换(1=反向:手指上滑 → 内容上移)。 */ + /* M7.2 列表拖动滚动:非滑条行按下后纵向位移超阈值 → 进入列表滚动。 + * 走 meow_scroll(跟手 track + 松手 fling),同 subpage.c M7.1a 语义; + * 方向经 input.swipe_invert 切换(默认手指上滑 → 内容下翻)。 */ if (td_ok && drag < 0 && !scroll_drag) { int ddy = tly - scroll_y0; if (ddy >= S_SCROLL_SLOP || ddy <= -S_SCROLL_SLOP) { scroll_drag = 1; meow_settings_get_bool("input.swipe_invert", &scroll_invert); - scroll_base = scroll_off + (scroll_invert ? -ddy : ddy); + meow_input_touch_set(0, 0, 0, 0); /* 滚动中不压行(清按压态) */ + drag_last_y = tly; } } if (scroll_drag && td_ok) { - int dydt = tly - scroll_y0; - scroll_off = s_clamp_scroll_range(scroll_base + (scroll_invert ? dydt : -dydt)); - need_paint = 1; + int dydt = tly - drag_last_y; /* 本次位移增量(跟手) */ + drag_last_y = tly; + int scdy = scroll_invert ? dydt : -dydt; + if (g_set_sc && scdy) meow_scroll_track(g_set_sc, scdy, now); + /* 列表滚动不在此即时重绘:整批事件末尾由下方「if (scroll_drag && td_ok) + * set_redraw_region」合并为一次视口局部重绘。此处若 set_redraw_region 且再设 + * need_paint=1,底部 set_paint_page 会再走一遍整页重绘 + dirty_add(0,42,...) + * 近整屏 flush —— 快滑每帧整页重绘 → 卡顿,违背「局部脏区不整屏重绘」铁律; + * 且一个 poll 多个移动事件各自 flush 会多次 FBIO_WAITFORVSYNC 阻塞放大延迟。 */ } if (drag >= 0 && td_ok) { const srow_t *r = &SMENU[drag]; @@ -1250,6 +1493,7 @@ void run_settings_page(void) } } else if (!val && td) { td = 0; + meow_input_touch_set(0, 0, 0, 0); /* 抬手:清 SDK 按压态 */ if (drag >= 0) { /* 滑条拖动结束 → 落盘 */ const srow_t *r = &SMENU[drag]; s_commit(r, 1); @@ -1257,7 +1501,8 @@ void run_settings_page(void) need_paint = 1; } else if (scroll_drag) { scroll_drag = 0; /* 列表滚动结束(已实时重绘),不再当 tap */ - scroll_base = 0; scroll_y0 = 0; + if (g_set_sc) meow_scroll_fling(g_set_sc); /* 松手 fling(惯性 ease-out) */ + scroll_y0 = 0; need_paint = 1; } else if (td_ok) { int dx = tlx - tdx, dy = tly - tdy; @@ -1276,8 +1521,10 @@ void run_settings_page(void) if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} } goto done; } - int cy = ty - SET_CONTENT_Y + scroll_off; - int idx = s_row_at_content_y(cy); + /* M7.2:行命中走 meow_listview(含滚动换算 + 禁用/越界行不响应); + * 段头行 enabled=1 故 hit 亦命中 → 下方按 ctrl==SC_HEADER 分流。 */ + int vp = g_set_lv ? meow_listview_hit(g_set_lv, tx, ty) : -1; + int idx = (vp >= 0) ? s_vis_at(vp) : -1; if (idx >= 0 && !s_setting_idx(idx)) { /* 点分组标题行 → 仅选中下一设置行 */ int ns = s_next_setting(idx); @@ -1306,7 +1553,7 @@ void run_settings_page(void) while (read(tfd, e, sizeof e) == 16) {} } td = 0; td_ok = 0; drag = -1; scroll_drag = 0; - scroll_base = 0; scroll_y0 = 0; + scroll_y0 = 0; tdx = tdy = tlx = tly = -1; touch_cooldown_until = now_ms() + 300; memset(&kd_pwr, 0, sizeof kd_pwr); @@ -1350,7 +1597,7 @@ void run_settings_page(void) while (read(tfd, e, sizeof e) == 16) {} } td = 0; td_ok = 0; drag = -1; scroll_drag = 0; - scroll_base = 0; scroll_y0 = 0; + scroll_y0 = 0; tdx = tdy = tlx = tly = -1; touch_cooldown_until = now_ms() + 300; /* 重置设置页按键防抖基线,防残边 */ @@ -1376,14 +1623,18 @@ void run_settings_page(void) } } } + /* 列表拖动滚动:本批连续触摸事件合并为一次视口局部重绘(只刷列表区, + * 最多一次 present),不再触发 need_paint 整页重绘。scroll_drag/td_ok 在 + * 抬手处置 0(抬手那轮已走 meow_scroll_fling + need_paint 整页),故此处只兜活动滚动。 */ + if (scroll_drag && td_ok) set_redraw_region(NULL); } /* ---- 滚动 + 重绘 ---- */ if (need_paint) { last_act = now; /* 仅选中行变化时强制其可见;列表拖动滚动时保留用户滚到的位置,不把选中拉回视野 */ - if (sel_changed) { scroll_off = s_clamp_scroll(sel, scroll_off); sel_changed = 0; } - draw_settings_page(sel, scroll_off); + if (sel_changed) { set_ensure_sel_visible(sel); sel_changed = 0; } + set_paint_page(sel); /* 局部标脏:标题+卡片+滚动条+底部(y=42 以下全部,覆盖主题/选中/值变化), * 状态栏(0-28)由 draw_statusbar/draw_clock 各自维护,不在此列。 * 选择器关闭后(after_modal)其全屏 dim 把状态栏也压暗了,须整帧重绘还原。 */ @@ -1408,9 +1659,13 @@ void run_settings_page(void) fb_norm_buf0(); meow_ui_flush(); } + /* M7.2: 推进 fling/回弹动画(meow_scroll 惯性停;无动画零开销) */ + meow_anim_tick(); usleep(POLL_MS * 1000); } done: + meow_input_touch_set(0, 0, 0, 0); /* M7.2 防残留按压态 */ + meow_ui_activity_pop(); /* 关 owner 闸门(控件 owner 仍钉哨兵,复用) */ if (tfd >= 0) close(tfd); kp_close(&kp_power); kp_close(&kp_keys); logmsg("settings page closed"); diff --git a/patch_v2/meow_launcherd/subpage.c b/patch_v2/meow_launcherd/subpage.c index d31891b..271488c 100644 --- a/patch_v2/meow_launcherd/subpage.c +++ b/patch_v2/meow_launcherd/subpage.c @@ -1,9 +1,9 @@ -/* subpage.c — MeowOS launcher 独立子页面(WiFi/蓝牙)统一脚手架(M2.76)。 +/* subpage.c — MeowOS launcher 独立子页面(WiFi/蓝牙/锁屏 PIN)统一脚手架(M2.76)。 * * 安卓风子页骨架:顶栏(标题 + 返回 + 右上开关)+ 分组列表(组标题/行/开关/动作) - * + 滚动(跟手,方向沿用设置页 input.swipe_invert 语义)+ 左缘左滑返回 + 底部返回。 + * + 滚动 + 左缘左滑返回。 * - * 由页面(wifi_page.c / bt_page.c)通过 sub_page_t 提供: + * 由页面(wifi_page.c / bt_page.c / lock_settings.c)通过 sub_page_t 提供: * - title 顶栏标题 * - top_sw 顶栏开关(0=不显示;1=显示并回写当前值) * - top_sw_set 开关切换(写设置 + 生效 + 触发重扫) @@ -12,7 +12,18 @@ * - detail 点普通行(如当前连接详情、网络/设备详情) * - live 1=驻留时周期性(约2s)重扫行(蓝牙附近扫描等动态数据) * - * 与设置页同主题(s_ui_theme,MD3 语义色,明暗自动);局部刷新不整屏重绘; + * ================================================================ + * M7.1a 渲染层迁移:脚手架手绘(顶栏 + 分组列表 + 跟手滚动 + 手绘 switch/chevron) + * 改走 SDK 控件(单一 meow_theme 权威): + * - 顶栏 → meow_topappbar(标题 + 返回箭头 + 底部分隔线); + * - 顶部开关 → meow_switch(叠顶栏右缘,保留开/关视觉与命中); + * - 分组列表 → meow_listview(virtualized set_vsource 固定 item 池 + section 段头 + * + right=SWITCH/ARROW 尾件 + 选中/按压态); + * - 滚动 → meow_scroll(跟手 track 采样松手 fling + snap 到行边界)。 + * wifi/bt/lock_settings 的「数据 + 动作」逻辑(netmgr IPC / bluetoothctl / 设置契约) + * 全部保留在 launcher 逻辑层,仅换渲染。旧手绘路径整体保留 #if 0 作回退位(M7 红线)。 + * + * 与设置页同主题(s_ui_theme → SDK meow_theme 单一权威,MD3/MIUIx×明暗自动); * 模态弹窗交给 launcher 既有 s_dd_modal(点外关闭不穿透),本脚手架在回调返回后 * 彻底排空本页 tfd + 重置触摸基线(复刻 run_settings_page 后置排空,防穿透)。 * @@ -22,16 +33,28 @@ #include "meow_launcher_internal.h" -/* ---- 布局常量(顶栏/内容区;全屏内容区延用设置页 surface_variant 整屏底)---- */ +/* ---- 布局常量(顶栏/内容区;全屏内容区延用设置页 surface 整屏底)---- + * note:SUB_CONT_TOP 与旧版一致(96),顶栏底缘分隔线由 SDK topappbar 绘制(y=95)。 */ #define SUB_TB_Y SB_BAR_H /* 28:顶栏顶缘(状态栏下) */ #define SUB_TB_H 68 /* 顶栏高 28..96 */ -#define SUB_TB_BOT (SUB_TB_Y + SUB_TB_H) /* 96:顶栏底缘 / 分隔线 */ -#define SUB_CONT_TOP (SUB_TB_BOT + 6) /* 102:内容区顶缘 */ +#define SUB_TB_BOT (SUB_TB_Y + SUB_TB_H) /* 96:顶栏底缘 */ +#define SUB_CONT_TOP (SUB_TB_BOT) /* 96:内容区顶缘(topappbar 分隔线下) */ +#define SUB_FOOT_H 44 /* 底部版本信息带(右对齐) */ +#define SUB_ITEM_H 56 /* MD3 行高(双行富 item:主/副标题) */ +#define SUB_PAD_X 16 /* 水平内边距(顶开关右缘留白用) */ +#define SUB_SW_H 32 /* 顶开关高(meow_switch 固定 56×32) */ + + +#if 0 /* ================================================================ + * M7.1a 退役:子页旧手绘脚手架(顶栏 + 分组列表 + 跟手无惯性滚动 + 手绘 + * s_draw_switch/s_draw_chevron + 自算行前缀和 + 自算命中)。改走 SDK 控件 + * (meow_topappbar + meow_listview + meow_scroll),命中/布局/状态/滚动/主题 + * 全由 SDK 接管。本块保留作回退位(删除 #if 0 行并删下方「M7.1a SDK 路径」段 + * 即可恢复旧手绘)。以下全部内容与迁移前 subpage.c 逐字一致。 + * ================================================================ */ #define SUB_ROW_H 58 /* 行高(对齐 SROW_H,便于触摸) */ #define SUB_HDR_H 40 /* 组标题行高 */ -#define SUB_PAD_X 16 /* 水平内边距 */ #define SUB_SW_W 48 /* 开关宽(s_draw_switch) */ -#define SUB_SW_H 28 #define SUB_BTN_X 8 /* 底部返回按钮左缘 */ #define SUB_BTN_W 120 /* 底部返回按钮宽 */ @@ -502,3 +525,510 @@ subdone: logmsg("subpage '%s' closed", p->title ? p->title : ""); return 0; } +#endif /* M7.1a 退役:旧手绘子页脚手架回退位 */ + + +/* ================================================================== + * M7.1a 子页渲染层迁移(SDK 控件化): + * - 顶栏 → meow_topappbar(标题 + 返回箭头 + 底部分隔线); + * - 顶部开关 → meow_switch(叠顶栏右缘,保留开/关视觉 + 命中); + * - 分组列表 → meow_listview(virtualized set_vsource 固定 item 池 + section 段头 + * + right=SWITCH/ARROW 尾件 + 选中/按压态,SDK 语义色); + * - 滚动 → meow_scroll(跟手 meow_scroll_track 采样 + 松手 fling ease-out 惯性停; + * 刻意不 set_snap——SDK snap 以 fling 起点为吸附目标会往回拉,见 sp_widgets_create 注)。 + * 控件 lazy 创建一次(owner=SUB_SENTINEL_ACT 哨兵),跨多次进入复用;meow_subpage_run() + * 进入 push 哨兵、退出 pop(owner 闸门使控件命中生效)。数据/动作回调(rows/act/detail/ + * top_sw_*)仍由各子页提供,逻辑层不动,仅渲染层替换。 + * ================================================================== */ +#define SUB_SENTINEL_ACT 0x52 /* 子页哨兵 activity(不与 launcher 其它页冲突) */ + +static sub_page_t *g_sp; /* 当前运行子页(rows/act/detail/top_sw 回调入口) */ +static sub_row_t g_rows[SUB_ROWS_MAX]; /* 行缓冲:rows() 一次性填满,listview bind 只读 */ +static int g_nrows = 0; /* 当前行数(listview vsource count 实时读) */ +static int g_top_sw_en = 0; /* 本页是否显示顶开关 */ +static int g_top_on = 0; /* 顶开关当前值 */ + +static meow_widget *g_tb = NULL; /* topappbar */ +static meow_widget *g_sw = NULL; /* 顶开关(仅在有顶开关的页创建/使用) */ +static meow_widget *g_lv = NULL; /* listview */ +static meow_scroll *g_sc = NULL; /* 滚动物理 */ +static int g_sp_init = 0; + +/* 列表视口矩形(内容区,去掉底部版本带) */ +static void sp_lv_rect(int *x, int *y, int *w, int *h) +{ + if (x) *x = 0; + if (y) *y = SUB_CONT_TOP; + if (w) *w = fb_xres; + if (h) { *h = fb_yres - SUB_CONT_TOP - SUB_FOOT_H; if (*h < 0) *h = 0; } +} + +/* ---- 行缓冲 → meow_lv_item(虚拟化 bind;每行轻量填充,不做 IO §5.5)---- */ +static int sp_lv_count(void *ctx) { (void)ctx; return g_nrows; } +static void sp_lv_bind(int idx, meow_lv_item *out, void *ctx) +{ + (void)ctx; + memset(out, 0, sizeof *out); + out->icon_kind = MEOW_LIST_ICON_NONE; + out->enabled = 1; + if (idx < 0 || idx >= g_nrows) { out->title = ""; return; } + const sub_row_t *r = &g_rows[idx]; + switch (r->kind) { + case SUB_HDR: /* 段头占位行(非粘性):default renderer 特殊绘制 */ + out->section = 1; out->title = r->label ? r->label : ""; + out->right = MEOW_LIST_RIGHT_NONE; out->enabled = 0; break; + case SUB_ROW: /* 普通信息行 */ + out->title = r->label ? r->label : ""; + out->subtitle = r->sub; out->right = MEOW_LIST_RIGHT_NONE; break; + case SUB_SW: /* 开关行:右尾件 SWITCH */ + out->title = r->label ? r->label : ""; + out->subtitle = r->sub; out->right = MEOW_LIST_RIGHT_SWITCH; out->on = r->on; break; + case SUB_ACT: /* 动作行:右尾件 ARROW */ + out->title = r->label ? r->label : ""; + out->subtitle = r->sub; out->right = MEOW_LIST_RIGHT_ARROW; break; + } +} + +/* ---- meow_scroll 回调(包 listview)---- */ +static void sp_sc_set(void *ctx, int off) { (void)ctx; if (g_lv) meow_listview_set_scroll(g_lv, off); } +static int sp_sc_max(void *ctx) { (void)ctx; return g_lv ? meow_listview_max_scroll(g_lv) : 0; } +static int sp_sc_get(void *ctx) { (void)ctx; return g_lv ? meow_listview_scroll(g_lv) : 0; } + +/* 列表区重绘(含背景 + listview),供 fling 每帧/滚动局部刷。ctx 为 meow_scroll 回调用。 */ +static void sp_redraw_region(void *ctx) +{ + (void)ctx; + int lvx, lvy, lvw, lvh; + sp_lv_rect(&lvx, &lvy, &lvw, &lvh); + if (lvh <= 0) return; + const meow_theme *mt = meow_theme_get(); + meow_ui_fill_rect(lvx, lvy, lvw, lvh, mt->surface); /* 内容底 surface */ + if (g_lv) meow_widget_draw(g_lv); + meow_dirty_add(lvx, lvy, lvw, lvh); + meow_ui_flush(); +} + +/* 创建子页控件集(owner=SUB_SENTINEL_ACT;仅首进创建一次,跨页复用)。 */ +static void sp_widgets_create(void) +{ + meow_ui_activity_push(SUB_SENTINEL_ACT); + g_tb = meow_topappbar_create(0, SUB_TB_Y, fb_xres, SUB_TB_H, ""); + if (g_tb) meow_topappbar_set_back(g_tb, 1); + int lvx, lvy, lvw, lvh; + sp_lv_rect(&lvx, &lvy, &lvw, &lvh); + g_lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_NONE); + if (g_lv) { + meow_listview_set_vsource(g_lv, sp_lv_count, sp_lv_bind, NULL); + /* 双行富 item + 无左图标 + 右尾件(SWITCH/ARROW);行高 56 */ + meow_listview_set_item_renderer(g_lv, 2, 0, 1); + meow_listview_set_item_h(g_lv, SUB_ITEM_H); + } + /* 注:刻意不 set_snap —— SDK meow_scroll 的 snap 以「fling 起点」为吸附目标,快速滑动时会 + * 从自然停点往回拉一整行(实测 697→336),UX 不佳;默认 ease-out 惯性停(无 snap)更顺滑。 + * 若后续 SDK 修正 snap 到「自然停点」,可恢复 meow_scroll_set_snap(g_sc, SUB_ITEM_H)。 */ + g_sc = meow_scroll_create(sp_sc_set, sp_sc_max, sp_sc_get, sp_redraw_region, NULL); + meow_ui_activity_pop(); + g_sp_init = 1; +} + +/* 逐页配置:topappbar 标题 + 顶开关(按需创建/置态)+ listview 行数。 */ +static void sp_configure_page(const sub_page_t *p, int top_sw_en, int top_on) +{ + if (p->title && g_tb) meow_topappbar_set_title(g_tb, p->title); + g_top_sw_en = top_sw_en; + g_top_on = top_on; + if (top_sw_en) { + if (!g_sw) { + meow_ui_activity_push(SUB_SENTINEL_ACT); /* owner 钉哨兵(命中闸门) */ + int swx = fb_xres - SUB_PAD_X - 56; /* meow_switch 固定 56×32 */ + int swy = SUB_TB_Y + (SUB_TB_H - SUB_SW_H) / 2; + g_sw = meow_switch_create(swx, swy, top_on); + meow_ui_activity_pop(); + } else if (g_sw) { + meow_switch_set(g_sw, top_on); + } + } +} + +/* 整页绘制(进 back_buf;调用方 meow_dirty_full + flush 上屏)。 */ +static void sp_paint_page(void) +{ + const meow_theme *mt = meow_theme_get(); + int W = fb_xres, H = fb_yres; + meow_ui_fill_rect(0, 0, W, H, mt->surface); /* 全屏底 surface */ + draw_statusbar(); + if (g_tb) meow_widget_draw(g_tb); /* topappbar(含底部分隔线) */ + if (g_sw && g_top_sw_en) meow_widget_draw(g_sw); /* 顶开关(叠顶栏右缘) */ + int lvx, lvy, lvw, lvh; + sp_lv_rect(&lvx, &lvy, &lvw, &lvh); + meow_ui_fill_rect(lvx, lvy, lvw, lvh, mt->surface); /* 内容底 surface */ + if (g_lv) meow_widget_draw(g_lv); /* 分组列表 */ + /* 底部版本信息(右对齐 dim) */ + char ver[32]; + snprintf(ver, sizeof ver, "MeowOS v%s", g_meowos_version); + ui_draw(W - 8 - ui_width(ver, 18), H - SUB_FOOT_H + 10, ver, + s_ui_theme()->outline, 18); +} + +/* 顶部开关切换:写设置 + 生效 + 触发重扫 + 重填行。 */ +static void sp_top_sw_toggle(void) +{ + if (!g_sp || !g_sp->top_sw_set) return; + g_sp->top_sw_set(g_sp->ctx, !g_top_on); + g_top_on = !g_top_on; + if (g_sw) meow_switch_set(g_sw, g_top_on); + if (g_sp->rows) { + g_nrows = g_sp->rows(g_sp->ctx, g_rows, SUB_ROWS_MAX); + if (g_nrows > SUB_ROWS_MAX) g_nrows = SUB_ROWS_MAX; + } +} + +/* 动作后重扫行(act/detail 已变更数据):重填行并 clamp 滚动。 */ +static void sp_refresh_rows(void) +{ + if (g_sp && g_sp->rows) { + g_nrows = g_sp->rows(g_sp->ctx, g_rows, SUB_ROWS_MAX); + if (g_nrows > SUB_ROWS_MAX) g_nrows = SUB_ROWS_MAX; + } + if (g_lv) { + int ms = meow_listview_max_scroll(g_lv); + if (meow_listview_scroll(g_lv) > ms) meow_listview_set_scroll(g_lv, ms); + } +} + +/* ---- 运行子页(阻塞到返回;返回 0)---- */ +int meow_subpage_run(sub_page_t *p) +{ + struct keypoll kp_power, kp_keys; + kp_open(&kp_power, DEV_POWER); kp_open(&kp_keys, DEV_KEYS); + struct kd_state kd_vold, kd_volu, kd_play, kd_pwr; + memset(&kd_vold, 0, sizeof kd_vold); memset(&kd_volu, 0, sizeof kd_volu); + memset(&kd_play, 0, sizeof kd_play); memset(&kd_pwr, 0, sizeof kd_pwr); + int m_prev_vold = 0, m_prev_volu = 0, m_prev_play = 0, m_prev_pwr = 0; + for (int w = 0; w < KEYBUF_WORDS; w++) { + if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEDOWN % 32))) m_prev_vold = 1; + if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEUP % 32))) m_prev_volu = 1; + if (kp_keys.cur[w] & (1UL << (KEY_PLAYPAUSE % 32))) m_prev_play = 1; + if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) m_prev_pwr = 1; + } + int tfd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK); + if (tfd >= 0) { unsigned char ev[16]; while (read(tfd, ev, sizeof ev) == 16) {} } + unsigned long long touch_cooldown_until = now_ms() + 300; + unsigned long long last_tick = now_ms(); + unsigned long long last_act = now_ms(); + unsigned long long last_live = now_ms(); + int after_modal = 0; /* 子页内模态关闭后:覆盖过状态栏,需整帧重绘还原 */ + + /* 触摸状态 */ + int td = 0, td_ok = 0, tdx = -1, tdy = -1, tlx = -1, tly = -1; + int scroll_drag = 0, scroll_invert = 0, drag_last_y = 0; + int wake_suppress = 0; + unsigned long long pwr_hold_at = 0; + int pwr_menu_state = 0, pwr_woke = 0, pwr_suppress_click = 0; + + /* 顶栏开关初始值 */ + int top_on = 0, top_sw_en = 0; + if (p->top_sw) top_sw_en = p->top_sw(p->ctx, &top_on); + + /* 首填行 + lazy 建控件 + 配置 + 首绘 */ + g_sp = p; + g_nrows = p->rows ? p->rows(p->ctx, g_rows, SUB_ROWS_MAX) : 0; + if (g_nrows > SUB_ROWS_MAX) g_nrows = SUB_ROWS_MAX; + if (!g_sp_init) sp_widgets_create(); + sp_configure_page(p, top_sw_en, top_on); + + meow_ui_activity_push(SUB_SENTINEL_ACT); /* 控件 owner 闸门打开(hit 生效) */ + sp_paint_page(); + meow_dirty_full(); meow_ui_flush(); + + for (;;) { + unsigned long long now = now_ms(); + if (sigterm_flag) break; + int need_paint = 0; + + /* ---- 自动息屏态:留在子页,等待任意输入唤醒,恢复后重绘(不丢上下文)---- */ + if (meow_auto_slept && !meow_screen_on) { + kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL); + int wake = 0; + for (int w = 0; w < KEYBUF_WORDS; w++) { + if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) wake = 1; + if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEDOWN % 32))) wake = 1; + if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEUP % 32))) wake = 1; + if (kp_keys.cur[w] & (1UL << (KEY_PLAYPAUSE % 32))) wake = 1; + } + if (!wake && screen_off_mode == 1 && tfd >= 0) { + struct pollfd spfd = { tfd, POLLIN, 0 }; + if (poll(&spfd, 1, 0) > 0) { + unsigned char ev[16]; + while (read(tfd, ev, sizeof ev) == 16) { + uint16_t type = *(uint16_t *)(ev + 8), code = *(uint16_t *)(ev + 10); + int32_t val = *(int32_t *)(ev + 12); + if (type == EV_KEY && code == BTN_TOUCH && val) wake = 1; + } + } + } + if (wake) { + meow_panel_on(); + meow_auto_slept = 0; + wake_suppress = 1; + last_act = now; + memset(&kd_pwr, 0, sizeof kd_pwr); memset(&kd_vold, 0, sizeof kd_vold); + memset(&kd_volu, 0, sizeof kd_volu); memset(&kd_play, 0, sizeof kd_play); + kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL); + m_prev_pwr = kp_is_down(&kp_power, KEY_POWER); + m_prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN); + m_prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP); + m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE); + touch_cooldown_until = now_ms() + 300; + td = 0; td_ok = 0; scroll_drag = 0; drag_last_y = 0; tlx = tly = -1; + after_modal = 1; + sp_paint_page(); + meow_dirty_full(); meow_ui_flush(); + } + usleep(POLL_MS * 1000); + continue; + } + + /* ---- 按键(与旧版逐字一致:音量=系统音量/播放=媒体预留/电源=系统级语义)---- */ + kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL); + int vold = 0, volu = 0, play = 0, pwr = 0; + for (int w = 0; w < KEYBUF_WORDS; w++) { + if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEDOWN % 32))) vold = 1; + if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEUP % 32))) volu = 1; + if (kp_keys.cur[w] & (1UL << (KEY_PLAYPAUSE % 32))) play = 1; + if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) pwr = 1; + } + (void)kd_tick(&kd_vold, vold); (void)kd_tick(&kd_volu, volu); + (void)kd_tick(&kd_play, play); (void)kd_tick(&kd_pwr, pwr); + vold = kd_vold.stable; volu = kd_volu.stable; + play = kd_play.stable; pwr = kd_pwr.stable; + if (wake_suppress) { + if (!pwr && !vold && !volu && !play) wake_suppress = 0; + } else { + if (vold && !m_prev_vold) { settings_volume_adj(-settings_volume_step); need_paint = 1; } + if (volu && !m_prev_volu) { settings_volume_adj(+settings_volume_step); need_paint = 1; } + if (play && !m_prev_play) { logmsg("subpage: play key caught (media-reserved)"); last_act = now; } + if (pwr && !m_prev_pwr) { + pwr_hold_at = now; pwr_menu_state = 0; + pwr_suppress_click = 0; + last_act = now; + if (meow_auto_slept && !meow_screen_on) pwr_woke = 1; + } + if (pwr && pwr_hold_at) { + if (now - pwr_hold_at >= (unsigned long long)power_forceoff_ms && !pwr_menu_state) { + if (sim_no_exec) { logmsg("subpage pm: [sim-no-exec] POWER HOLD -> force poweroff"); } + else { logmsg("subpage pm: POWER HOLD -> force poweroff"); meow_power_action(POWER_SHUTDOWN); } + } else if (now - pwr_hold_at >= (unsigned long long)power_longpress_menu_ms && !pwr_menu_state) { + pwr_menu_state = 1; + logmsg("subpage pm: POWER HOLD -> show power menu"); + show_power_menu(); + last_act = now_ms(); + pwr_hold_at = 0; pwr_menu_state = 0; pwr_woke = 0; + pwr_suppress_click = 1; + memset(&kd_pwr, 0, sizeof kd_pwr); + memset(&kd_vold, 0, sizeof kd_vold); + memset(&kd_volu, 0, sizeof kd_volu); + memset(&kd_play, 0, sizeof kd_play); + kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL); + m_prev_pwr = kp_is_down(&kp_power, KEY_POWER); + m_prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN); + m_prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP); + m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE); + after_modal = 1; + need_paint = 1; + goto sub_keys_seeded; + } + } + if (!pwr && m_prev_pwr) { + if (pwr_suppress_click) { + logmsg("subpage pm: power release after menu -> no toggle"); + } else if (pwr_hold_at && (now - pwr_hold_at < (unsigned long long)power_longpress_menu_ms) && + !pwr_menu_state && !pwr_woke) { + logmsg("subpage pm: power single-click -> toggle panel"); + meow_toggle_panel(); + meow_auto_slept = 0; + last_act = now_ms(); + need_paint = 1; + } else if (pwr_woke) { + logmsg("subpage pm: power release after auto wake -> no toggle"); + } + pwr_hold_at = 0; pwr_menu_state = 0; pwr_woke = 0; + pwr_suppress_click = 0; + } + } + if (vol_ramp_dir != 0) { + int held = (vol_ramp_dir > 0) ? volu : vold; + (void)vol_ramp_tick(held); + g_sys.vol = settings_volume_get(); + paint_sb_right(); + need_paint = 1; + } + m_prev_vold = vold; m_prev_volu = volu; m_prev_play = play; m_prev_pwr = pwr; + sub_keys_seeded: + ; + + /* ---- 触摸(SDK 控件命中:topappbar/顶开关/listview + meow_scroll 滚动)---- */ + if (tfd >= 0 && meow_screen_on) { + struct pollfd pfd = { tfd, POLLIN, 0 }; + if (poll(&pfd, 1, 0) > 0) { + unsigned char ev[16]; + while (read(tfd, ev, sizeof ev) == 16) { + uint16_t type = *(uint16_t *)(ev + 8), code = *(uint16_t *)(ev + 10); + int32_t val = *(int32_t *)(ev + 12); + if (type == EV_ABS) { + if (code == ABS_X || code == ABS_MT_POSITION_X) tlx = val; + else if (code == ABS_Y || code == ABS_MT_POSITION_Y) tly = val; + if (td && !td_ok && tlx >= 0 && tly >= 0) { + tdx = tlx; tdy = tly; td_ok = 1; + meow_input_touch_set(1, tdx, tdy, 0); /* 喂 SDK 输入态(按压态渲染) */ + drag_last_y = tdy; + if (g_lv && meow_widget_hit(g_lv, tlx, tly)) /* 按压态即时反馈 */ + sp_redraw_region(NULL); + } + if (td_ok && !scroll_drag) { + int ddy = tly - drag_last_y; + if (ddy >= S_SCROLL_SLOP || ddy <= -S_SCROLL_SLOP) { + scroll_drag = 1; + meow_settings_get_bool("input.swipe_invert", &scroll_invert); + meow_input_touch_set(0, 0, 0, 0); /* 滚动中不压行(清按压态) */ + drag_last_y = tly; /* 从此增量(跨 slop 无跳变,对齐旧版补偿) */ + } + } + if (scroll_drag && td_ok) { + int dydt = tly - drag_last_y; /* 本次位移增量(跟手) */ + drag_last_y = tly; + int scdy = scroll_invert ? dydt : -dydt; + if (g_sc && scdy) meow_scroll_track(g_sc, scdy, now); + sp_redraw_region(NULL); /* 列表区重绘(新 offset) */ + need_paint = 1; + } + } else if (type == EV_KEY && code == BTN_TOUCH) { + if (val && !td) { + if (now_ms() < touch_cooldown_until) { tlx = tly = -1; } + else { td = 1; td_ok = 0; tlx = -1; tly = -1; scroll_drag = 0; + meow_input_touch_set(0, 0, 0, 0); } + } else if (!val && td) { + td = 0; + meow_input_touch_set(0, 0, 0, 0); /* 抬手:清 SDK 按压态 */ + if (scroll_drag) { + scroll_drag = 0; + if (g_sc) meow_scroll_fling(g_sc); /* 松手 fling(速度采样) */ + } else if (td_ok) { + int dx = tlx - tdx, dyy = tly - tdy; + /* 左缘左滑返回(全局手势语义) */ + if (meow_edge_back(tdx, dx, dyy)) { + if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} } + goto subdone; + } + int adx = dx < 0 ? -dx : dx, ady = dyy < 0 ? -dyy : dyy; + if (adx < 60 && ady < 60) { + int tx = tlx, ty = tly; + /* 顶栏:返回箭头(topappbar hit)/ 顶开关(meow_switch hit) */ + int tb_hit = g_tb ? meow_topappbar_hit(g_tb, tx, ty) : -1; + if (tb_hit == MEOW_TOPBAR_BACK) { + if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} } + goto subdone; + } + if (g_top_sw_en && g_sw && meow_widget_hit(g_sw, tx, ty)) { + sp_top_sw_toggle(); + last_act = now; + need_paint = 1; + if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} } + continue; + } + /* 列表行命中(SDK 命中:含滚动换算 + 越界/禁用行不响应) */ + int row_hit = g_lv ? meow_listview_hit(g_lv, tx, ty) : -1; + if (row_hit >= 0) { + sub_kind_t kind = g_rows[row_hit].kind; + if (kind == SUB_SW || kind == SUB_ACT) { + if (g_sp->act) g_sp->act(g_sp->ctx, row_hit, g_rows[row_hit].act); + } else if (kind == SUB_ROW) { + if (g_sp->detail) g_sp->detail(g_sp->ctx, row_hit, g_rows[row_hit].act); + } + /* 动作后重扫行 + clamp 滚动 */ + sp_refresh_rows(); + } + /* 仅命中行(回调可能开模态)才做后置排空/触摸基线重置/整帧重绘 + * (对齐旧版 idx>=0 分支;空白区点按不重绘不排空,置 td_ok=0 即可)。 */ + if (row_hit >= 0) { + if (tfd >= 0) { + unsigned char e[16]; + while (read(tfd, e, sizeof e) == 16) {} + usleep(40000); + while (read(tfd, e, sizeof e) == 16) {} + } + td = 0; td_ok = 0; scroll_drag = 0; + drag_last_y = 0; + tdx = tdy = tlx = tly = -1; + touch_cooldown_until = now_ms() + 300; + memset(&kd_pwr, 0, sizeof kd_pwr); + memset(&kd_vold, 0, sizeof kd_vold); + memset(&kd_volu, 0, sizeof kd_volu); + memset(&kd_play, 0, sizeof kd_play); + kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL); + m_prev_pwr = kp_is_down(&kp_power, KEY_POWER); + m_prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN); + m_prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP); + m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE); + after_modal = 1; + need_paint = 1; + } + } + } + td_ok = 0; + } + } + } + } + } + + /* ---- 周期性重扫(live 页:蓝牙附近扫描等驻留更新)---- */ + if (g_sp && g_sp->live && now - last_live >= 2000) { + last_live = now; + int new_n = g_sp->rows ? g_sp->rows(g_sp->ctx, g_rows, SUB_ROWS_MAX) : 0; + if (new_n > SUB_ROWS_MAX) new_n = SUB_ROWS_MAX; + if (new_n != g_nrows) { + g_nrows = new_n; + int ms = g_lv ? meow_listview_max_scroll(g_lv) : 0; + if (g_lv && meow_listview_scroll(g_lv) > ms) meow_listview_set_scroll(g_lv, ms); + last_act = now; + need_paint = 1; + } + } + + /* ---- 重绘(整帧/内容区局部;fling 由 meow_scroll redraw 回调自刷)---- */ + if (need_paint) { + last_act = now; + sp_paint_page(); + if (after_modal) { meow_dirty_full(); after_modal = 0; } + else meow_dirty_add(0, SUB_TB_Y, fb_xres, fb_yres - SUB_TB_Y); /* 含顶栏(开关/返回/标题) */ + meow_ui_flush(); + } + + /* ---- 自动息屏(空闲超时关屏,留在子页,等输入唤醒)---- */ + if (screen_off_timeout > 0 && meow_screen_on && !meow_auto_slept && + (unsigned long long)(now - last_act) >= (unsigned long long)screen_off_timeout * 1000) { + meow_panel_off(); + meow_auto_slept = 1; + wake_suppress = 1; + logmsg("subpage: idle %ds -> auto screen-off (stay)", screen_off_timeout); + } + /* ~1s 状态栏时钟刷新 */ + if (now - last_tick >= 1000) { + last_tick = now; + draw_clock(); + fb_norm_buf0(); + meow_ui_flush(); + } + /* M7.1a: 推进 fling/回弹/snap 动画(无动画零开销) */ + meow_anim_tick(); + usleep(POLL_MS * 1000); + } +subdone: + meow_input_touch_set(0, 0, 0, 0); /* 防残留按压态 */ + meow_ui_activity_pop(); /* 关 owner 闸门(控件 owner 仍钉哨兵,复用) */ + if (tfd >= 0) close(tfd); + kp_close(&kp_power); kp_close(&kp_keys); + logmsg("subpage '%s' closed", p->title ? p->title : ""); + return 0; +} diff --git a/patch_v2/meow_launcherd/test_fg_act b/patch_v2/meow_launcherd/test_fg_act new file mode 100755 index 0000000..8b73934 Binary files /dev/null and b/patch_v2/meow_launcherd/test_fg_act differ diff --git a/patch_v2/meow_launcherd/test_fg_act.c b/patch_v2/meow_launcherd/test_fg_act.c new file mode 100644 index 0000000..52e240d --- /dev/null +++ b/patch_v2/meow_launcherd/test_fg_act.c @@ -0,0 +1,81 @@ +/* test_fg_act.c — M2.17 keep_awake:前台顶层 activity(fg_act)清空兜底宿主单测。 + * + * 验证「App 会话退出/崩溃回收时清 fg_act」这一 launcher 兜底所依赖的状态机: + * - meow_fg_act_set(app,act) 依注册表 route.keep_awake → fg_keep_awake()==1; + * - App 会话结束(本工程在 launch_app_chain 退出尾调 meow_fg_act_back())→ fg 清空、 + * fg_keep_awake() 回 0(主页不再被 keep_awake 卡住自动息屏); + * - App 存活时(仅有 ACT_FOCUS,未会话结束)fg_act 保持不清。 + * 该测试把真实 app_launcher.c 整包含进来(同 test_lock_screen / test_notif_panel 手法), + * 用 -ffunction-sections -Wl,--gc-sections 只保留 main() 触及的 fg_act 原语,其余重函数 + * (fork/exec/waitpid/fb)被剔除,故只需 stub logmsg。 + * + * 运行(patch_v2/meow_launcherd 下): + * gcc -D_GNU_SOURCE -Wall -Wextra -ffunction-sections -Wl,--gc-sections \ + * -I. -o test_fg_act test_fg_act.c && ./test_fg_act + * 全 PASS -> exit 0。 + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include + +/* logmsg:fg_act 原语的唯一外部依赖(app_launcher.c 内定义,需在 include 前提供)。 */ +void logmsg(const char *fmt, ...) +{ + (void)fmt; +} + +#include "app_launcher.c" /* 真实实现:g_accts/g_naccts/meow_fg_act_{set,back}/fg_keep_awake */ + +static int g_fail = 0; +#define CHECK(cond, msg) do { \ + if (!(cond)) { fprintf(stderr, "FAIL: %s (%s:%d)\n", msg, __FILE__, __LINE__); g_fail++; } \ + else fprintf(stderr, "ok: %s\n", msg); \ +} while (0) + +int main(void) +{ + /* ---- 基线:无上报 = 主界面(非 keep_awake)---- */ + CHECK(fg_keep_awake() == 0, "baseline: empty fg_act -> keep_awake=0"); + + /* ---- 注册 ncm 的 player activity 声明 keep_awake(模拟 app.json awake)---- */ + struct meow_acct *a = meow_acct_upsert("dev.player.ncm"); + CHECK(a != NULL, "register dev.player.ncm"); + a->nroute = 1; + strncpy(a->routes[0].act, "player", sizeof a->routes[0].act - 1); + a->routes[0].act[sizeof a->routes[0].act - 1] = 0; + a->routes[0].keep_awake = 1; + + /* ---- sim A:App 存活、正报 keep_awake activity(如 ncm 播放页)---- + * 这是「App 在 keep_awake activity 时 fg_act 保持(不清)」的契约点。 */ + meow_fg_act_set("dev.player.ncm", "player"); + CHECK(fg_keep_awake() == 1, "ncm playing -> keep_awake=1 (保持, 不清)"); + CHECK(strcmp(g_fg_app, "dev.player.ncm") == 0, "fg_app kept while app alive"); + CHECK(strcmp(g_fg_act, "player") == 0, "fg_act kept while app alive"); + + /* ---- sim B:App 会话结束/崩溃回收(launch_app_chain 退出尾调用 meow_fg_act_back())---- */ + meow_fg_act_back(); + CHECK(fg_keep_awake() == 0, "app session end -> keep_awake=0 (兜底已清)"); + CHECK(g_fg_act[0] == 0, "fg_act cleared on session end"); + CHECK(g_fg_app[0] == 0, "fg_app cleared on session end"); + + /* ---- sim C:ACT_FOCUS(home) 正常弹回 home(agent-356)→ 非 keep_awake,且可再被兜底幂等清 ---- */ + meow_fg_act_set("dev.player.ncm", "home"); /* home 未声明 keep_awake */ + CHECK(fg_keep_awake() == 0, "ACT_FOCUS(home) -> keep_awake=0"); + meow_fg_act_back(); /* 兜底幂等:再清一次不冲突 */ + CHECK(g_fg_act[0] == 0 && fg_keep_awake() == 0, "back after home focus idempotent"); + + /* ---- sim D:act 为空(set(NULL act))/未知 app / 未知 act → 非 keep_awake ---- */ + meow_fg_act_set("dev.player.ncm", NULL); + CHECK(fg_keep_awake() == 0, "set(act=NULL) -> keep_awake=0"); + meow_fg_act_set("no.such.app", "player"); + CHECK(fg_keep_awake() == 0, "unregistered app -> keep_awake=0"); + meow_fg_act_set("dev.player.ncm", "notascreen"); + CHECK(fg_keep_awake() == 0, "unknown act in known app -> keep_awake=0"); + + if (g_fail) { fprintf(stderr, "\n== %d assertion(s) FAILED ==\n", g_fail); return 1; } + fprintf(stderr, "\n== test_fg_act ALL PASS ==\n"); + return 0; +} diff --git a/patch_v2/meow_launcherd/test_lock_render b/patch_v2/meow_launcherd/test_lock_render new file mode 100755 index 0000000..1052059 Binary files /dev/null and b/patch_v2/meow_launcherd/test_lock_render differ diff --git a/patch_v2/meow_launcherd/test_lock_screen b/patch_v2/meow_launcherd/test_lock_screen new file mode 100755 index 0000000..fdce54f Binary files /dev/null and b/patch_v2/meow_launcherd/test_lock_screen differ diff --git a/patch_v2/meow_launcherd/test_lock_widgets b/patch_v2/meow_launcherd/test_lock_widgets new file mode 100755 index 0000000..b0c0e15 Binary files /dev/null and b/patch_v2/meow_launcherd/test_lock_widgets differ diff --git a/patch_v2/meow_launcherd/test_lock_widgets.c b/patch_v2/meow_launcherd/test_lock_widgets.c new file mode 100644 index 0000000..e0727ce --- /dev/null +++ b/patch_v2/meow_launcherd/test_lock_widgets.c @@ -0,0 +1,223 @@ +/* test_lock_widgets.c — M7.1b 锁屏 UI 控件化宿主验证。 + * + * 复现 lock_screen_ui.c 所建锁屏键盘控件集(12 键 meow_widget_custom + meow_widget 命中 + + * 按压态 MEOW_WS_PRESSED→primary 26% α),验证: + * P1 键盘渲染:12 键各画成 surfaceContainer 圆角钮(非背景像素); + * P2 键盘命中:meow_widget_hit 在每键中心命中;键值映射(digit/清空/退格)正确;间隙不命中; + * P3 主题四态(MD3/MIUIx × 明暗)键盘渲染非背景; + * P4 按压态:meow_widget_set_state(MEOW_WS_PRESSED) → 该键像素变化(primary 26% α 叠加); + * P5 键值 round-trip:lk_key_value_at 在键中心/间隙分别返回正确键值与 LK_DOWN_NONE。 + * + * 布局几何/键序与 lock_screen_ui.c 镜像(LK_X0..LK_COLS/LK_ROWS/lk_keys),控件创建方式与 + * lk_widgets_build 同(owner=哨兵 0x51,meow_widget_custom_create,render 读 meow_widget_rect 几何)。 + * + * 编译(在 patch_v2/meow_launcherd 下): + * gcc -Wall -Wextra -o test_lock_widgets test_lock_widgets.c \ + * ../sdk/test_host_stub.c ../sdk/meow_ui_gfx.c ../sdk/meow_ui_sys.c \ + * ../sdk/meow_ui_theme.c ../sdk/meow_ui_vol.c ../sdk/meow_ui_log.c \ + * ../sdk/meow_ui_widget.c ../sdk/meow_ui_input.c ../sdk/meow_ui_activity.c \ + * ../sdk/meow_ui_dialog.c ../sdk/meow_ui_ime.c ../sdk/meow_ui_str.c ../sdk/meow_pinyin.c \ + * ../sdk/meow_ui_draw.c ../sdk/meow_ui_view.c ../sdk/meow_ui_box.c ../sdk/meow_ui_anim.c \ + * ../sdk/meow_ui_scroll.c ../sdk/meow_ui_img.c ../sdk/meow_ui_media.c \ + * ../sdk/meow_notify_ipc.c ../sdk/stb_image.c -I../sdk -I. -DMEOW_UI_HOST_TEST -pthread -lm + * 运行:./test_lock_widgets + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" /* meow_ui_back_buf / meow_widget_rect / meow_gfx_fill_round[_alpha] */ +#include +#include +#include +#include + +static int g_fail = 0; +#define CHECK(cond, ...) do { \ + if (cond) { printf(" PASS: " __VA_ARGS__); printf("\n"); } \ + else { printf(" FAIL: " __VA_ARGS__); printf("\n"); g_fail++; } } while (0) + +/* ---- 与 lock_screen_ui.c 同源几何(镜像) ---- */ +#define LK_X0 32 +#define LK_COLW 130 +#define LK_COLG 8 +#define LK_Y0 320 +#define LK_ROW_H 82 +#define LK_ROWG 10 +#define LK_ROWS 4 +#define LK_COLS 3 +#define LK_ACT_CLR (-2) +#define LK_ACT_BS (-1) +#define LK_SENTINEL 0x51 /* 锁屏控件 owner 哨兵(镜像 lock_screen_ui.c) */ +#define LK_DOWN_NONE (-10) +static const int lk_keys[LK_ROWS][LK_COLS] = { + { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 }, { LK_ACT_CLR, 0, LK_ACT_BS }, +}; +#define XRES 480 +#define YRES 720 + +static meow_widget *lk_w[LK_ROWS][LK_COLS]; + +static void lk_rect(int row, int col, int rc[4]) +{ + rc[0] = LK_X0 + col * (LK_COLW + LK_COLG); + rc[1] = LK_Y0 + row * (LK_ROW_H + LK_ROWG); + rc[2] = LK_COLW; + rc[3] = LK_ROW_H; +} +static void lk_center(int row, int col, int *cx, int *cy) +{ + int rc[4]; lk_rect(row, col, rc); + *cx = rc[0] + rc[2] / 2; + *cy = rc[1] + rc[3] / 2; +} + +/* ---- 键渲染(镜像 lock_screen_ui.c lk_key_render 的视觉语义)---- */ +static void key_render(meow_widget *w, const meow_theme *th, void *ctx) +{ + (void)ctx; + int rc[4]; meow_widget_rect(w, rc); + int rad = (th->radius > 0) ? th->radius : 10; + meow_gfx_fill_round(rc[0], rc[1], rc[2], rc[3], rad, th->surfaceContainer); + if (meow_widget_state(w) & MEOW_WS_PRESSED) + meow_gfx_fill_round_alpha(rc[0], rc[1], rc[2], rc[3], rad, th->primary, 66); + meow_ui_text_center(rc[0] + rc[2] / 2, + rc[1] + rc[3] / 2 - meow_ui_text_height(22) / 2, + "1", 22, th->onSurface); +} + +static void mk_widgets(void) +{ + meow_ui_activity_push(LK_SENTINEL); + for (int r = 0; r < LK_ROWS; r++) + for (int c = 0; c < LK_COLS; c++) { + int rc[4]; lk_rect(r, c, rc); + lk_w[r][c] = meow_widget_custom_create(rc[0], rc[1], rc[2], rc[3], + key_render, NULL, + (void *)(intptr_t)lk_keys[r][c]); + } + meow_ui_activity_pop(); +} + +/* 坐标 -> 键值(镜像 lock_screen_ui.c lk_key_value_at)。 */ +static int key_value_at(int x, int y) +{ + for (int r = 0; r < LK_ROWS; r++) + for (int c = 0; c < LK_COLS; c++) + if (lk_w[r][c] && meow_widget_hit(lk_w[r][c], x, y)) + return lk_keys[r][c]; + return LK_DOWN_NONE; +} + +static void draw_all(void) +{ + const meow_theme *th = meow_theme_get(); + meow_ui_fill_rect(0, 0, XRES, YRES, th->surface); + for (int r = 0; r < LK_ROWS; r++) + for (int c = 0; c < LK_COLS; c++) + if (lk_w[r][c]) meow_widget_draw(lk_w[r][c]); +} + +static unsigned short px565(int x, int y) +{ + unsigned char *bb = meow_ui_back_buf(); + int pitch = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); + if (!bb || x < 0 || y < 0 || x >= XRES || y >= YRES) return 0; + if (bpp == 16) return *(unsigned short *)(bb + (size_t)y * pitch + (size_t)x * 2); + return (unsigned short)(*(unsigned int *)(bb + (size_t)y * pitch + (size_t)x * 4) >> 16); +} +static unsigned short bg565(uint32_t bg) +{ + return (unsigned short)(((bg >> 8) & 0xF800) | ((bg >> 5) & 0x07E0) | ((bg >> 3) & 0x001F)); +} + +int main(void) +{ + setbuf(stdout, NULL); + setenv("MEOW_UI_DUMP", "/tmp/lock_widgets.ppm", 1); + meow_ui_opts o = { XRES, YRES }; + if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; } + mk_widgets(); + meow_ui_activity_push(LK_SENTINEL); /* 命中闸门打开(owner==active) */ + + /* ============ P1 键盘渲染(12 键各画成非背景钮) ============ */ + printf("P1 keypad render (12 keys non-bg)\n"); + const meow_theme *th = meow_theme_get(); + draw_all(); + int all_nonbg = 1; + for (int r = 0; r < LK_ROWS && all_nonbg; r++) + for (int c = 0; c < LK_COLS && all_nonbg; c++) { + int cx, cy; lk_center(r, c, &cx, &cy); + if (px565(cx, cy) == bg565(th->surface)) all_nonbg = 0; + } + CHECK(all_nonbg, "each of 12 keys draws non-surface at center"); + + /* ============ P2 命中(meow_widget_hit + 键值映射) ============ */ + printf("P2 keypad hit via meow_widget_hit\n"); + int hit_ok = 1, val_ok = 1; + for (int r = 0; r < LK_ROWS; r++) + for (int c = 0; c < LK_COLS; c++) { + int cx, cy; lk_center(r, c, &cx, &cy); + if (!meow_widget_hit(lk_w[r][c], cx, cy)) hit_ok = 0; + if (key_value_at(cx, cy) != lk_keys[r][c]) val_ok = 0; + } + CHECK(hit_ok, "meow_widget_hit hits every key center"); + CHECK(val_ok, "key_value_at maps every key center to its digit/clear/backspace value"); + /* 间隙(两列之间)不命中 */ + int gap_x = LK_X0 + LK_COLW + LK_COLG / 2; /* 列0 与列1 之间的间隙中心 */ + int gap_y = LK_Y0 + LK_ROW_H / 2; + CHECK(meow_widget_hit(lk_w[0][0], gap_x, gap_y) == 0, "gap between columns not hit"); + CHECK(key_value_at(gap_x, gap_y) == LK_DOWN_NONE, "gap not mapped to any key"); + + /* ============ P3 主题四态(MD3/MIUIx × 明暗)键盘渲染非背景 ============ */ + printf("P3 theme 4-state keypad render\n"); + const int styles[2] = { MEOW_STYLE_MD3, MEOW_STYLE_MIUIX }; + const int modes[2] = { MEOW_THEME_LIGHT, MEOW_THEME_DARK }; + char nm[96]; + for (int si = 0; si < 2; si++) { + for (int mi = 0; mi < 2; mi++) { + meow_ui_set_style(styles[si]); + meow_theme_set_mode((meow_theme_mode)modes[mi]); + th = meow_theme_get(); + draw_all(); + /* 用一个键区的多像素带判定非背景(文字+底) */ + int rc[4]; lk_rect(1, 0, rc); + int nonbg = 0; + for (int dy = 0; dy < rc[3] && !nonbg; dy++) + for (int dx = 0; dx < rc[2] && !nonbg; dx++) + if (px565(rc[0] + dx, rc[1] + dy) != bg565(th->surface)) nonbg = 1; + snprintf(nm, sizeof nm, "style=%s mode=%s keypad renders non-surface", + styles[si] ? "MIUIx" : "MD3", modes[mi] ? "dark" : "light"); + CHECK(nonbg, "%s", nm); + } + } + meow_ui_set_style(MEOW_STYLE_MD3); + meow_theme_set_mode(MEOW_THEME_DARK); + + /* ============ P4 按压态(MEOW_WS_PRESSED → primary 26% α 像素变化) ============ */ + printf("P4 pressed state -> primary 26%% alpha overlay\n"); + th = meow_theme_get(); + draw_all(); + int cx, cy; lk_center(2, 1, &cx, &cy); /* 键 (2,1) = 数字 8 */ + unsigned short p0 = px565(cx, cy); + meow_widget_set_state(lk_w[2][1], MEOW_WS_PRESSED, 1); + draw_all(); + unsigned short p1 = px565(cx, cy); + meow_widget_set_state(lk_w[2][1], MEOW_WS_PRESSED, 0); + CHECK(p1 != p0, "pressed key center pixel changes (primary 26%% overlay)"); + draw_all(); + unsigned short p2 = px565(cx, cy); + CHECK(p2 == p0, "release restores original pixel"); + + /* ============ P5 键值 round-trip(清空/退格/间隙) ============ */ + printf("P5 key-value round-trip (clear/backspace/gap)\n"); + int ccx, ccy, bcx, bcy, zcx, zcy; + lk_center(3, 0, &ccx, &ccy); /* 清空 */ + lk_center(3, 1, &zcx, &zcy); /* 0 */ + lk_center(3, 2, &bcx, &bcy); /* 退格 */ + CHECK(key_value_at(ccx, ccy) == LK_ACT_CLR, "bottom-left center == CLEAR (-2)"); + CHECK(key_value_at(zcx, zcy) == 0, "bottom-mid center == 0"); + CHECK(key_value_at(bcx, bcy) == LK_ACT_BS, "bottom-right center == BACKSPACE (-1)"); + + meow_ui_activity_pop(); + meow_ui_shutdown(); + printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail); + return g_fail; +} diff --git a/patch_v2/meow_launcherd/test_menu_sdk b/patch_v2/meow_launcherd/test_menu_sdk new file mode 100755 index 0000000..5f4e89c Binary files /dev/null and b/patch_v2/meow_launcherd/test_menu_sdk differ diff --git a/patch_v2/meow_launcherd/test_menu_sdk.c b/patch_v2/meow_launcherd/test_menu_sdk.c new file mode 100644 index 0000000..9eed5d4 --- /dev/null +++ b/patch_v2/meow_launcherd/test_menu_sdk.c @@ -0,0 +1,360 @@ +/* test_menu_sdk.c — M7.4 主界面迁 SDK(meow_lazygrid 宫格 + meow_listview 列表 + meow_scroll)宿主验证。 + * + * 复现 main_menu.c M7.4 SDK 路径所用几何/数据源(与 main_menu.c 同源常量镜像): + * app 区视口 = menu_app_region(apps_top, layout):宫格 x=12(PAD) w=xres-2*PAD / 列表 x=8 w=xres-16, + * 顶=apps_top(=ROW_TOP=96,宿主无字库→mfont_ready=0),底=EXIT_ROW-ROW_H-4=584; + * 宫格 cell_w=(xres-2*PAD-gap)/cols=223,cell_h=CARD_H=112,gap=10,row_h=122; + * 列表 row_h=LIST_ROW_H=60。应用数 MAX_APPS=8。 + * + * 验证(M7 §4 验收点:双布局可切 / 图标·缺失·后台标注正确 / 空态 / 滚动惯性): + * G1 宫格虚拟化渲染:每可见格调自绘一次;格位置 = menu_app_region/menu_grid_cell_w 公式映射 + * (480×720 校准:cell0=12,96 cell1=245,96 cell2=12,218 … 不被截断、不越视口)。 + * G2 宫格命中:格中心→正确 idx;水平/垂直 gap→-1;越界→-1;滚动后命中换算正确。 + * G3 宫格滚动:8 格(MAX_APPS)装得下 → max_scroll==0;12 格超屏 → max_scroll>0;scroll_attach 可建。 + * L1 列表渲染+命中:视口 x=8,w=464;row 矩形与公式一致;8 行装得下 / 12 行超屏。 + * E 空态:count==0 → 不画任何格/行卡(空态路径),无回退。 + * M 图标·缺失·后台标注:自绘按 app->ok/meow_bg_is_backgrounded 决策「[缺失]」「·后台」/均无。 + * + * 注:main_menu.c 的 menu_draw_grid_cell/menu_draw_list_cell 是 static(依赖 launcher 全局面), + * 宿主以同源公式/决策规则镜像复现其契约;SDK 控件行为(虚拟化/命中/滚动/空态)为真实验证。 + * + * 编译(在 patch_v2/meow_launcherd 下,宿主 gcc): + * gcc -Wall -Wextra -o test_menu_sdk test_menu_sdk.c \ + * ../sdk/test_host_stub.c ../sdk/meow_ui_gfx.c ../sdk/meow_ui_sys.c \ + * ../sdk/meow_ui_theme.c ../sdk/meow_ui_vol.c ../sdk/meow_ui_log.c \ + * ../sdk/meow_ui_widget.c ../sdk/meow_ui_input.c ../sdk/meow_ui_activity.c \ + * ../sdk/meow_ui_dialog.c ../sdk/meow_ui_ime.c ../sdk/meow_ui_str.c ../sdk/meow_pinyin.c \ + * ../sdk/meow_ui_draw.c ../sdk/meow_ui_view.c ../sdk/meow_ui_box.c ../sdk/meow_ui_anim.c \ + * ../sdk/meow_ui_scroll.c ../sdk/meow_lazygrid.c ../sdk/meow_ui_img.c ../sdk/meow_ui_media.c \ + * ../sdk/meow_notify_ipc.c ../sdk/stb_image.c -I../sdk -I. -DMEOW_UI_HOST_TEST -pthread -lm + * 运行:./test_menu_sdk + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp + meow_gfx_clip */ +#include +#include +#include +#include + +static int g_pass = 0, g_fail = 0; +#define CHECK(cond, ...) do { \ + if (cond) { g_pass++; } \ + else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \ +} while (0) + +/* ---- 与 main_menu.c / meow_launcher_internal.h 同源几何(镜像) ---- */ +#define XRES 480 +#define YRES 720 +#define MENU_GRID_PAD 12 +#define MENU_GRID_GAP 10 +#define MENU_GRID_COLS 2 +#define MENU_GRID_CARD_H 112 +#define MENU_GRID_ICON 44 +#define MENU_LIST_ROW_H 60 +#define MENU_LIST_ICON 40 +#define MENU_LIST_ICON_X 24 +#define ROW_H 52 +#define ROW_TOP 96 +#define EXIT_ROW 640 +#define APPS_TOP ROW_TOP /* 宿主无字库 → mfont_ready=0 → menu_apps_top()=ROW_TOP */ +#define APPS_BOT (EXIT_ROW - ROW_H - 4) /* 584:设置行顶 */ +#define SDK_ACT 0x55 + +/* menu_app_region 镜像:宫格 vx=12 w=xres-24;列表 vx=8 w=xres-16;vh=底-顶。 */ +static int grid_cell_w(void) { return (XRES - 2 * MENU_GRID_PAD - (MENU_GRID_COLS - 1) * MENU_GRID_GAP) / MENU_GRID_COLS; } +static int grid_row_h(void) { return MENU_GRID_CARD_H + MENU_GRID_GAP; } +static void grid_region(int *vx, int *vy, int *vw, int *vh) { *vx = MENU_GRID_PAD; *vy = APPS_TOP; *vw = XRES - 2 * MENU_GRID_PAD; *vh = APPS_BOT - APPS_TOP; } +static void list_region(int *vx, int *vy, int *vw, int *vh) { *vx = 8; *vy = APPS_TOP; *vw = XRES - 16; *vh = APPS_BOT - APPS_TOP; } + +/* ---- mock 应用(镜像 struct app 的关键字段) ---- */ +typedef struct { const char *name; int ok; int bg; const char *pkg; } tapp_t; +static tapp_t g_apps[16]; +static int g_napps = 0; +static int g_draw_cnt = 0; /* 自绘回调调用次数(窗口虚拟化) */ +static int g_cell_x[16], g_cell_y[16], g_cell_w[16], g_cell_h[16]; /* 自绘收到的几何 */ +static int g_cell_idx[16]; +static char g_label[16][16]; /* 格/行自绘决策出的标注("" / "[缺失]" / "·后台") */ + +/* bind:读 mock app,title=名,enabled=1(命中即启动语义,同 main_menu menu_grid_bind)。 */ +static void grid_bind(int idx, meow_lazygrid_item *it, void *ctx) +{ + (void)ctx; + memset(it, 0, sizeof *it); + if (idx < 0 || idx >= g_napps) return; + it->title = g_apps[idx].name; + it->subtitle = NULL; + it->icon = NULL; /* 宿主无图标位图;自绘用文字/色块近似 */ + it->enabled = 1; + it->user = &g_apps[idx]; +} +static int grid_count(void *ctx) { (void)ctx; return g_napps; } +static void list_bind(int idx, meow_lv_item *it, void *ctx) +{ + (void)ctx; + memset(it, 0, sizeof *it); + if (idx < 0 || idx >= g_napps) return; + it->title = g_apps[idx].name; + it->subtitle = NULL; + it->icon_kind = MEOW_LIST_ICON_NONE; + it->enabled = 1; + it->user = &g_apps[idx]; +} +static int list_count(void *ctx) { (void)ctx; return g_napps; } + +/* 镜像 menu_draw_grid_cell 的标注决策(ok→缺失[缺失];bg→·后台;均无→无)。 */ +static void decide_label(int idx, char *out, int olen) +{ + const tapp_t *a = &g_apps[idx]; + if (!a->ok) snprintf(out, olen, "[缺失]"); + else if (a->bg) snprintf(out, olen, "·后台"); + else out[0] = 0; +} + +/* 宫格自绘(镜像 menu_sdk_grid_draw → 记录几何 + 标注决策;宿主无字体用色块近似)。 */ +static void grid_draw(int idx, int x, int y, int w, int h, int st, void *ctx) +{ + (void)st; (void)ctx; + if (idx < 0 || idx >= g_napps) return; + if (g_draw_cnt < 16) { + g_cell_idx[g_draw_cnt] = idx; g_cell_x[g_draw_cnt] = x; g_cell_y[g_draw_cnt] = y; + g_cell_w[g_draw_cnt] = w; g_cell_h[g_draw_cnt] = h; + g_draw_cnt++; + } + decide_label(idx, g_label[idx], sizeof g_label[idx]); + meow_gfx_fill_round(x, y, w, h, 16, meow_theme_get()->surfaceContainer); + meow_gfx_fill_round(x + w / 2 - 22, y + 12, 44, 44, 13, meow_theme_get()->primary); +} +/* 列表自绘(镜像 menu_sdk_list_draw) */ +static void list_draw(int idx, int x, int y, int w, int h, int st, void *ctx) +{ + (void)st; (void)ctx; + if (idx < 0 || idx >= g_napps) return; + if (g_draw_cnt < 16) { + g_cell_idx[g_draw_cnt] = idx; g_cell_x[g_draw_cnt] = x; g_cell_y[g_draw_cnt] = y; + g_cell_w[g_draw_cnt] = w; g_cell_h[g_draw_cnt] = h; + g_draw_cnt++; + } + decide_label(idx, g_label[idx], sizeof g_label[idx]); + meow_gfx_fill_round(x, y, w, h, 12, meow_theme_get()->surfaceContainer); + meow_gfx_fill_round(x + MENU_LIST_ICON_X, y + (h - MENU_LIST_ICON) / 2, MENU_LIST_ICON, MENU_LIST_ICON, 12, + meow_theme_get()->primary); +} + +static void seed_apps(int n) +{ + static const char *nm[16] = {"应用一","应用二","应用三","应用四","应用五","应用六","应用七","应用八", + "应用九","应用十","应用十一","应用十二","应用十三","应用十四","应用十五","应用十六"}; + for (int i = 0; i < n; i++) { + g_apps[i].name = nm[i]; g_apps[i].ok = 1; g_apps[i].bg = 0; g_apps[i].pkg = "os.meow.test"; + } + /* 2 号缺失、3 号后台(用 0/1 锚定到前几格便于断言) */ + if (n > 1) g_apps[1].ok = 0; + if (n > 2) g_apps[2].bg = 1; + g_napps = n; +} + +int main(void) +{ + meow_ui_opts o = { XRES, YRES }; + if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; } + const meow_theme *th = meow_theme_get(); + int gx, gy, gw, gh, lx, ly, lw, lh; + grid_region(&gx, &gy, &gw, &gh); list_region(&lx, &ly, &lw, &lh); + int cw = grid_cell_w(), rh = grid_row_h(); + CHECK(gx == MENU_GRID_PAD && gw == XRES - 2 * MENU_GRID_PAD, "grid viewport x=%d w=%d (expect %d,%d)", gx, gw, MENU_GRID_PAD, XRES - 2 * MENU_GRID_PAD); + CHECK(lx == 8 && lw == XRES - 16, "list viewport x=%d w=%d", lx, lw); + CHECK(gh == APPS_BOT - APPS_TOP, "grid viewport h=%d (expect %d)", gh, APPS_BOT - APPS_TOP); + + /* ============ G1 宫格虚拟化渲染 + 几何校准 ============ */ + printf("G1 lazygrid render @480x720\n"); + { + seed_apps(8); + meow_ui_activity_push(SDK_ACT); + meow_lazygrid *g = meow_lazygrid_create(gx, gy, gw, gh, MENU_GRID_COLS, cw, MENU_GRID_CARD_H, MENU_GRID_GAP); + CHECK(g != NULL, "lazygrid create"); + CHECK(meow_lazygrid_set_vsource(g, grid_count, grid_bind, NULL) == 0, "vsource"); + CHECK(meow_lazygrid_set_item_draw(g, grid_draw, NULL) == 0, "set_item_draw"); + CHECK(meow_lazygrid_set_empty_text(g, "没有找到应用") == 0, "empty_text"); + meow_ui_clear(th->bg); + g_draw_cnt = 0; + meow_lazygrid_draw(g, th); + /* 8 格(MAX_APPS)全可见:每格恰一次自绘 */ + CHECK(g_draw_cnt == 8, "8 apps -> each visible cell drawn once got %d", g_draw_cnt); + /* 几何校准:cell0=12,96 cell1=245,96 cell2(行1col0)=12,218 cell3=245,218 */ + CHECK(g_cell_idx[0] == 0 && g_cell_x[0] == 12 && g_cell_y[0] == 96, "cell0 geom idx=%d x=%d y=%d (expect 0,12,96)", g_cell_idx[0], g_cell_x[0], g_cell_y[0]); + CHECK(g_cell_idx[1] == 1 && g_cell_x[1] == 12 + cw + MENU_GRID_GAP && g_cell_y[1] == 96, "cell1 geom idx=%d x=%d y=%d", g_cell_idx[1], g_cell_x[1], g_cell_y[1]); + CHECK(g_cell_idx[2] == 2 && g_cell_x[2] == 12 && g_cell_y[2] == 96 + rh, "cell2 geom idx=%d x=%d y=%d", g_cell_idx[2], g_cell_x[2], g_cell_y[2]); + CHECK(g_cell_idx[7] == 7 && g_cell_y[7] == 96 + 3 * rh, "cell7 (row3col1) y=%d (expect %d)", g_cell_y[7], 96 + 3 * rh); + /* 全部格/行不越视口(右侧 ≤ gx+gw,底部 ≤ gy+gh) */ + int bad = 0; + for (int i = 0; i < g_draw_cnt; i++) + if (g_cell_x[i] + g_cell_w[i] > gx + gw || g_cell_y[i] + g_cell_h[i] > gy + gh) bad++; + CHECK(bad == 0, "all cells within grid viewport (bad=%d, vp=[%d,%d]x[%d,%d])", bad, gx, gx + gw, gy, gy + gh); + /* 8 格(MAX_APPS)装得下 → max_scroll==0 */ + CHECK(meow_lazygrid_max_scroll(g) == 0, "8 apps fit -> max_scroll=0 got %d", meow_lazygrid_max_scroll(g)); + meow_lazygrid_free(g); + meow_ui_activity_pop(); + } + + /* ============ G2 宫格命中(含滚动换算:用 12 应用使超屏可滚) ============ */ + printf("G2 lazygrid hit\n"); + { + seed_apps(12); + meow_ui_activity_push(SDK_ACT); + meow_lazygrid *g = meow_lazygrid_create(gx, gy, gw, gh, MENU_GRID_COLS, cw, MENU_GRID_CARD_H, MENU_GRID_GAP); + meow_lazygrid_set_vsource(g, grid_count, grid_bind, NULL); + /* cell0 中心(123,152) → idx0;cell1 中心(356,152) → idx1;行1col0 中心(123,274=96+122+56) → idx2 */ + CHECK(meow_lazygrid_hit(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2) == 0, "hit cell0 center got %d", meow_lazygrid_hit(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2)); + CHECK(meow_lazygrid_hit(g, 12 + cw + MENU_GRID_GAP + cw / 2, 96 + MENU_GRID_CARD_H / 2) == 1, "hit cell1 center got %d", meow_lazygrid_hit(g, 12 + cw + MENU_GRID_GAP + cw / 2, 96 + MENU_GRID_CARD_H / 2)); + CHECK(meow_lazygrid_hit(g, 12 + cw / 2, 96 + rh + MENU_GRID_CARD_H / 2) == 2, "hit row1col0 got %d", meow_lazygrid_hit(g, 12 + cw / 2, 96 + rh + MENU_GRID_CARD_H / 2)); + /* 水平 gap x=240(col0[12,235) col1[245,468) 之间)→ -1 */ + CHECK(meow_lazygrid_hit(g, 240, 96 + MENU_GRID_CARD_H / 2) == -1, "horizontal gap miss got %d", meow_lazygrid_hit(g, 240, 96 + MENU_GRID_CARD_H / 2)); + /* 垂直 gap y=212(row0[96,208) row1[218,320) 之间)→ -1 */ + CHECK(meow_lazygrid_hit(g, 12 + cw / 2, 212) == -1, "vertical gap miss got %d", meow_lazygrid_hit(g, 12 + cw / 2, 212)); + /* 越界(右侧 x=475 / 列表区外 y=92)→ -1 */ + CHECK(meow_lazygrid_hit(g, 475, 96 + MENU_GRID_CARD_H / 2) == -1, "right-out miss got %d", meow_lazygrid_hit(g, 475, 96 + MENU_GRID_CARD_H / 2)); + CHECK(meow_lazygrid_hit(g, 12 + cw / 2, APPS_TOP - 4) == -1, "above viewport -> -1 got %d", meow_lazygrid_hit(g, 12 + cw / 2, APPS_TOP - 4)); + /* 滚 1 行(rh=122)后,视口顶 y=96 中心(x)落在行1 → idx2 */ + meow_lazygrid_set_scroll(g, rh); + CHECK(meow_lazygrid_hit(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2) == 2, "scroll+122: top center hits row1 -> idx2 got %d", meow_lazygrid_hit(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2)); + /* tap_at:命中 → 选中 + 回调 */ + meow_lazygrid_set_scroll(g, 0); + CHECK(meow_lazygrid_tap_at(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2) == 0, "tap_at idx0 got %d", meow_lazygrid_tap_at(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2)); + CHECK(meow_lazygrid_sel(g) == 0, "sel=0"); + meow_lazygrid_free(g); + meow_ui_activity_pop(); + } + + /* ============ G3 宫格滚动(超屏 + scroll_attach) ============ */ + printf("G3 lazygrid overflow scroll\n"); + { + seed_apps(12); /* 6 行 → 内容高 722 > 视口 488 */ + meow_ui_activity_push(SDK_ACT); + meow_lazygrid *g = meow_lazygrid_create(gx, gy, gw, gh, MENU_GRID_COLS, cw, MENU_GRID_CARD_H, MENU_GRID_GAP); + meow_lazygrid_set_vsource(g, grid_count, grid_bind, NULL); + int ms = meow_lazygrid_max_scroll(g); + CHECK(ms > 0, "12 apps overflow max_scroll=%d (>0)", ms); + meow_lazygrid_set_item_draw(g, grid_draw, NULL); + meow_ui_clear(th->bg); + g_draw_cnt = 0; + meow_lazygrid_draw(g, th); + int win_rows = gh / rh + 1; /* 可见行窗口上界 */ + CHECK(g_draw_cnt <= win_rows * MENU_GRID_COLS, "virtualized window: drew %d <= %d (not 12)", g_draw_cnt, win_rows * MENU_GRID_COLS); + /* scroll_by 往返 */ + int before = meow_lazygrid_scroll(g); + meow_lazygrid_scroll_by(g, rh); + CHECK(meow_lazygrid_scroll(g) == before + rh, "scroll_by +row got %d (expect %d)", meow_lazygrid_scroll(g), before + rh); + meow_lazygrid_set_scroll(g, 0); + CHECK(meow_lazygrid_scroll(g) == 0, "set_scroll 0"); + /* scroll_attach 可建(fling/snap 惯性滚动接线) */ + meow_scroll *s = meow_lazygrid_scroll_attach(g); + CHECK(s != NULL, "scroll_attach non-NULL"); + if (s) { + meow_scroll_set_snap(s, rh); + uint64_t t = meow_ui_time_ms(); + for (int i = 0; i < 10; i++) meow_scroll_track(s, 40, t + (uint64_t)i * 16); + int st = meow_scroll_offset(s); + CHECK(st > 0, "drag advances offset got %d", st); + meow_scroll_fling(s); + int guard = 0; + while (meow_scroll_active(s) && guard++ < 300) { meow_anim_tick(); usleep(10000); } + meow_anim_tick(); + int en = meow_scroll_offset(s); + CHECK(en >= st, "fling advances %d >= %d", en, st); + CHECK(en <= meow_lazygrid_max_scroll(g), "fling in range %d (max %d)", en, meow_lazygrid_max_scroll(g)); + CHECK(meow_lazygrid_scroll(g) == en, "grid scroll mirrors offset"); + meow_scroll_free(s); + } + meow_lazygrid_free(g); + meow_ui_activity_pop(); + } + + /* ============ L1 列表渲染 + 命中 + 滚动 ============ */ + printf("L1 listview render + hit + scroll\n"); + { + seed_apps(8); + meow_ui_activity_push(SDK_ACT); + meow_widget *lv = meow_listview_create(lx, ly, lw, lh, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE); + CHECK(lv != NULL, "listview create"); + meow_listview_set_vsource(lv, list_count, list_bind, NULL); + meow_listview_set_item_draw(lv, list_draw, NULL); + meow_listview_set_item_h(lv, MENU_LIST_ROW_H); + meow_ui_fill_rect(0, 0, XRES, YRES, th->bg); + meow_ui_fill_rect(lx, ly, lw, lh, th->surfaceVariant); + g_draw_cnt = 0; + meow_widget_draw(lv); + CHECK(g_draw_cnt == 8, "8 rows drawn got %d", g_draw_cnt); + CHECK(g_cell_x[0] == 8 && g_cell_w[0] == XRES - 16, "row0 x=%d w=%d (expect 8,%d)", g_cell_x[0], g_cell_w[0], XRES - 16); + CHECK(g_cell_y[0] == APPS_TOP && g_cell_y[1] == APPS_TOP + MENU_LIST_ROW_H, "row0/1 y=%d/%d", g_cell_y[0], g_cell_y[1]); + CHECK(meow_listview_max_scroll(lv) == 0, "8 rows fit -> max_scroll=0 got %d", meow_listview_max_scroll(lv)); + CHECK(meow_listview_hit(lv, 100, APPS_TOP + MENU_LIST_ROW_H / 2) == 0, "hit row0 got %d", meow_listview_hit(lv, 100, APPS_TOP + MENU_LIST_ROW_H / 2)); + CHECK(meow_listview_hit(lv, 100, APPS_TOP + MENU_LIST_ROW_H + MENU_LIST_ROW_H / 2) == 1, "hit row1 got %d", meow_listview_hit(lv, 100, APPS_TOP + MENU_LIST_ROW_H + MENU_LIST_ROW_H / 2)); + CHECK(meow_listview_hit(lv, 4, APPS_TOP + MENU_LIST_ROW_H / 2) == -1, "left of list x=4 -> -1 got %d", meow_listview_hit(lv, 4, APPS_TOP + MENU_LIST_ROW_H / 2)); + CHECK(meow_listview_hit(lv, 100, APPS_TOP - 4) == -1, "above list -> -1 got %d", meow_listview_hit(lv, 100, APPS_TOP - 4)); + meow_widget_free(lv); + meow_ui_activity_pop(); + + /* 12 行超屏滚动 */ + seed_apps(12); + meow_ui_activity_push(SDK_ACT); + meow_widget *lv2 = meow_listview_create(lx, ly, lw, lh, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE); + meow_listview_set_vsource(lv2, list_count, list_bind, NULL); + meow_listview_set_item_draw(lv2, list_draw, NULL); + meow_listview_set_item_h(lv2, MENU_LIST_ROW_H); + CHECK(meow_listview_max_scroll(lv2) > 0, "12 rows overflow max_scroll=%d", meow_listview_max_scroll(lv2)); + meow_listview_set_scroll(lv2, MENU_LIST_ROW_H); + /* 滚 1 行:视口顶 row 中心落在 idx1 */ + CHECK(meow_listview_hit(lv2, 100, APPS_TOP + MENU_LIST_ROW_H / 2) == 1, "scrolled row0->idx1 got %d", meow_listview_hit(lv2, 100, APPS_TOP + MENU_LIST_ROW_H / 2)); + meow_widget_free(lv2); + meow_ui_activity_pop(); + seed_apps(8); /* 还原 */ + } + + /* ============ E 空态(count==0 → 空态路径,无格/行卡) ============ */ + printf("E empty state\n"); + { + seed_apps(0); + meow_ui_activity_push(SDK_ACT); + meow_lazygrid *g = meow_lazygrid_create(gx, gy, gw, gh, MENU_GRID_COLS, cw, MENU_GRID_CARD_H, MENU_GRID_GAP); + meow_lazygrid_set_vsource(g, grid_count, grid_bind, NULL); + meow_lazygrid_set_item_draw(g, grid_draw, NULL); + meow_lazygrid_set_empty_text(g, "没有找到应用"); + meow_ui_clear(th->bg); + g_draw_cnt = 0; + meow_lazygrid_draw(g, th); + CHECK(g_draw_cnt == 0, "empty grid: 0 cells drawn got %d", g_draw_cnt); + meow_lazygrid_free(g); + /* 列表空态 */ + meow_widget *lv = meow_listview_create(lx, ly, lw, lh, 0, NULL, NULL, MEOW_LIST_SEL_NONE); + meow_listview_set_vsource(lv, list_count, list_bind, NULL); + meow_listview_set_item_draw(lv, list_draw, NULL); + meow_listview_set_empty_text(lv, "没有找到应用"); + meow_ui_fill_rect(lx, ly, lw, lh, th->surfaceVariant); + g_draw_cnt = 0; + meow_widget_draw(lv); + CHECK(g_draw_cnt == 0, "empty list: 0 rows drawn got %d", g_draw_cnt); + meow_widget_free(lv); + meow_ui_activity_pop(); + seed_apps(8); + } + + /* ============ M 图标·缺失·后台标注(镜像 menu_draw_grid_cell 决策) ============ */ + printf("M missing / background label\n"); + { + seed_apps(8); /* app1 ok=0 → [缺失];app2 bg=1 → ·后台;app0 → 无 */ + CHECK(strcmp(g_label[0], "") == 0, "app0 (ok,no bg) no label got '%s'", g_label[0]); + CHECK(memcmp(g_label[1], "[缺失]", 6) == 0, "app1 (missing) -> '[缺失]' got '%s'", g_label[1]); + CHECK(memcmp(g_label[2], "·后台", 6) == 0, "app2 (backgrounded) -> '·后台' got '%s'", g_label[2]); + /* 命中语义:enabled=1 恒响应 —— 缺 app 亦可命中(启动→失败 toast,同旧 draw_menu 行为) */ + meow_ui_activity_push(SDK_ACT); + meow_lazygrid *g = meow_lazygrid_create(gx, gy, gw, gh, MENU_GRID_COLS, cw, MENU_GRID_CARD_H, MENU_GRID_GAP); + meow_lazygrid_set_vsource(g, grid_count, grid_bind, NULL); + CHECK(meow_lazygrid_hit(g, 12 + cw / 2, 96 + rh + MENU_GRID_CARD_H / 2) == 2, "missing app1 tappable (enabled=1) -> hit row0col? got %d", meow_lazygrid_hit(g, 12 + cw / 2, 96 + rh + MENU_GRID_CARD_H / 2)); + meow_lazygrid_free(g); + meow_ui_activity_pop(); + } + + meow_ui_shutdown(); + printf("menu_sdk: %d pass / %d fail\n", g_pass, g_fail); + return g_fail ? 1 : 0; +} diff --git a/patch_v2/meow_launcherd/test_notif_center b/patch_v2/meow_launcherd/test_notif_center new file mode 100755 index 0000000..325334c Binary files /dev/null and b/patch_v2/meow_launcherd/test_notif_center differ diff --git a/patch_v2/meow_launcherd/test_notif_center.c b/patch_v2/meow_launcherd/test_notif_center.c new file mode 100644 index 0000000..35a4b4a --- /dev/null +++ b/patch_v2/meow_launcherd/test_notif_center.c @@ -0,0 +1,288 @@ +/* test_notif_center.c — M7.3 通知中心内层迁 SDK 控件(宿主验证)。 + * + * 复现 notif_center.c M7.3 SDK 路径所建控件集(meow_listview 虚拟化 vsource + set_item_draw 行自绘 + + * set_empty_text 空态 + set_swipe_delete 左滑删除 + 双 meow_slider + meow_switch + 自定义画布 chip),验证: + * P1 listview 渲染通知行:行卡非背景像素(图标/标题/正文)+ set_item_draw 命中(含滚动换算); + * P2 空态:count==0 且 set_empty_text 设了 → 视口内居中画文案(非背景像素); + * P3 左滑删除:begin/move(超阈值)/end → on_delete 回调恰一次 + swipe 状态复位(撤销回调不触发); + * P4 滑条/开关:meow_slider_set/get 与 meow_switch_set/get 往返一致(值写回控件); + * P5 自定义画布 chip:meow_widget_rect 取几何 + render 出非背景像素 + meow_widget_hit 命中(owner 闸门开)。 + * + * 几何/控件创建与 notif_center.c「M7.3 SDK 路径」镜像(owner 钉 NCP_WIDGET_ACT;行高 NCP_ROW_H)。 + * 注:宿主不含 launcher sb_icons / s_ui_theme / g_rows,故行/芯片用 meow_ui_text + 简化绘制复现契约; + * notif 行自绘函数体(ncp_lv_row_draw)依赖 launcher 全局,真机由 notif_center.c 提供。 + * + * 编译(在 patch_v2/meow_launcherd 下,宿主 gcc): + * gcc -Wall -Wextra -o test_notif_center test_notif_center.c \ + * ../sdk/test_host_stub.c ../sdk/meow_ui_gfx.c ../sdk/meow_ui_sys.c \ + * ../sdk/meow_ui_theme.c ../sdk/meow_ui_vol.c ../sdk/meow_ui_log.c \ + * ../sdk/meow_ui_widget.c ../sdk/meow_ui_input.c ../sdk/meow_ui_activity.c \ + * ../sdk/meow_ui_dialog.c ../sdk/meow_ui_ime.c ../sdk/meow_ui_str.c ../sdk/meow_pinyin.c \ + * ../sdk/meow_ui_draw.c ../sdk/meow_ui_view.c ../sdk/meow_ui_box.c ../sdk/meow_ui_anim.c \ + * ../sdk/meow_ui_scroll.c ../sdk/meow_ui_img.c ../sdk/meow_ui_media.c \ + * ../sdk/meow_notify_ipc.c ../sdk/stb_image.c -I../sdk -DMEOW_UI_HOST_TEST -pthread -lm + * 运行:./test_notif_center + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" /* meow_ui_back_buf / meow_gfx_mark_dirty / meow_widget_rect */ +#include +#include +#include +#include + +static int g_pass = 0, g_fail = 0; +#define CHECK(cond, ...) do { \ + if (cond) { g_pass++; } \ + else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \ +} while (0) + +/* ---- 与 notif_center.c / meow_launcher_internal.h 同源几何(镜像) ---- */ +#define XRES 480 +#define YRES 720 +#define NCP_TOP 28 +#define NCP_CHIP_Y 86 +#define NCP_CHIP_H 44 +#define NCP_CHIP_W 70 +#define NCP_CHIP_GAP 8 +#define NCP_CHIP_N 5 +#define NCP_SLIDER_X 20 +#define NCP_SLIDER_W (XRES - 40) +#define NCP_SL1_Y 162 +#define NCP_SL2_Y 214 +#define NCP_DND_Y 264 +#define NCP_DND_H 40 +#define NCP_DND_SPLIT_X (XRES / 2) +#define NCP_LIST_TOP 308 +#define NCP_LIST_BOT 694 +#define NCP_ROW_H 92 +#define NCP_ROW_X 12 +#define NCP_ROW_W (XRES - 24) +#define NCP_ICON_X 26 +#define NCP_TEXT_X 72 +#define NCP_TEXT_W (NCP_ROW_W - (NCP_TEXT_X - NCP_ROW_X) - 10) +#define NCP_WIDGET_ACT 0x54 +#define NCP_MAX_ROWS 8 + +/* 通知行镜像(宿主自持,替代 launcher g_rows[]/g_ncp_apps)。 */ +typedef struct { const char *summary; const char *body; int progress; } trow_t; +static trow_t g_rows[NCP_MAX_ROWS]; +static int g_nrows = 0; + +/* ---- 像素探测 ---- */ +static int px565(int x, int y, uint32_t bg) +{ + unsigned short bg565 = ((bg >> 8) & 0xF800) | ((bg >> 5) & 0x07E0) | ((bg >> 3) & 0x001F); + unsigned short p; + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); + if (!bb || x < 0 || y < 0) { p = 0; return 0; } + if (bpp == 16) p = *(unsigned short *)(bb + (size_t)y * line + (size_t)x * 2); + else p = (unsigned short)(*(unsigned int *)(bb + (size_t)y * line + (size_t)x * 4) >> 16); + return (p != bg565) ? 1 : 0; +} +static int region_nonbg(int x, int y, int w, int h, uint32_t bg) +{ + int cnt = 0; + for (int dy = 0; dy < h; dy++) + for (int dx = 0; dx < w; dx++) + if (px565(x + dx, y + dy, bg)) cnt++; + return cnt; +} + +/* ---- listview 虚拟化源(镜像 ncp_lv_count / ncp_lv_bind) ---- */ +static int lv_count(void *ctx) { (void)ctx; return g_nrows; } +static void lv_bind(int idx, meow_lv_item *it, void *ctx) +{ + (void)ctx; + memset(it, 0, sizeof *it); + it->enabled = (idx >= 0 && idx < g_nrows) ? 1 : 0; +} + +/* 行自绘(镜像 ncp_lv_row_draw 的 SDK 文本渲染;宿主无 launcher 字体/sb_icons,用 meow_ui_text 近似)。 */ +static void lv_draw(int idx, int x, int y, int w, int h, int state, void *ctx) +{ + (void)w; (void)state; (void)ctx; + if (idx < 0 || idx >= g_nrows) return; + const meow_theme *th = meow_theme_get(); + meow_gfx_fill_round(x + NCP_ROW_X, y, NCP_ROW_W, h - 6, 12, th->surfaceContainer); + /* 图标位(无图回退:primary 圆点) */ + int icx = x + NCP_ICON_X, icy = y + (h - 32) / 2; + meow_gfx_fill_round(icx + 16, icy + 16, 16, 16, 8, th->primary); + /* app 名 + 标题 + 正文 */ + meow_ui_text(x + NCP_TEXT_X, y + 10, "yao", th->font_body, th->onSurfaceVariant); + meow_ui_text(x + NCP_TEXT_X, y + 30, g_rows[idx].summary, th->font_body, th->onSurface); + meow_ui_text(x + NCP_TEXT_X, y + 56, g_rows[idx].body, th->font_body, th->onSurfaceVariant); + if (g_rows[idx].progress >= 0) { + int px = x + NCP_TEXT_X, pw = NCP_TEXT_W, py = y + h - 14; + meow_ui_fill_rect(px, py, pw, 4, th->surfaceVariant); + meow_ui_fill_rect(px, py, g_rows[idx].progress * pw / 100, 4, th->primary); + } +} + +/* ---- 左滑删除回调计数 ---- */ +static int del_count = 0; static int del_idx = -1; +static void lv_on_delete(int idx, void *ctx) +{ (void)ctx; del_count++; del_idx = idx; } + +static meow_widget *make_list(void) +{ + meow_widget *lv = meow_listview_create(0, NCP_LIST_TOP, XRES, NCP_LIST_BOT - NCP_LIST_TOP, + 0, NULL, NULL, MEOW_LIST_SEL_NONE); + meow_listview_set_vsource(lv, lv_count, lv_bind, NULL); + meow_listview_set_item_draw(lv, lv_draw, NULL); + meow_listview_set_item_h(lv, NCP_ROW_H); + meow_listview_set_empty_text(lv, "暂无通知"); + meow_listview_set_swipe_delete(lv, 1, lv_on_delete, NULL); + return lv; +} + +/* 推进 meow_anim 直到 swipe 动画结束(模拟 on_tick 节拍)。 */ +static void pump_swipe(meow_widget *lv) +{ + int guard = 0; + while (meow_listview_swipe_active(lv) && guard++ < 80) { + meow_anim_tick(); + usleep(10000); + } + meow_anim_tick(); +} + +/* 自定义画布 chip(镜像 ncp_chip_render:圆角 + 文本;用 meow_widget_rect 取几何)。 */ +static void chip_render(meow_widget *w, const meow_theme *th, void *ctx) +{ + (void)ctx; + int rc[4]; meow_widget_rect(w, rc); + meow_gfx_fill_round(rc[0], rc[1], rc[2], rc[3], 12, th->surfaceContainer); + meow_ui_text_center(rc[0] + rc[2] / 2, rc[1] + rc[3] / 2, "chip", th->font_body, th->onSurfaceVariant); +} + +int main(void) +{ + meow_ui_opts o = { XRES, YRES }; + if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; } + const meow_theme *th = meow_theme_get(); + + /* 通知行数据(镜像:多行 + 进度)。8 行(736px) > 视口(386px) → 可滚动。 */ + g_nrows = 8; + static const char *summ[8] = {"下载完成","媒体进行中","系统通知","新消息","更新提醒","日程","备份完成","低电量"}; + static const char *body[8] = {"文件已就绪","正在播放","低电量提醒","收到 2 条","已就绪","10 分钟后","已成功","请充电"}; + for (int i = 0; i < 8; i++) { g_rows[i].summary = summ[i]; g_rows[i].body = body[i]; g_rows[i].progress = (i < 2) ? (100 - i * 60) : -1; } + + /* ============ P1 listview 渲染通知行 + 行命中 ============ */ + printf("P1 notif listview render + hit\n"); + { + meow_ui_activity_push(NCP_WIDGET_ACT); + meow_widget *lv = make_list(); + meow_ui_fill_rect(0, 0, XRES, YRES, th->bg); + meow_ui_fill_rect(0, NCP_LIST_TOP, XRES, NCP_LIST_BOT - NCP_LIST_TOP, th->surfaceVariant); + meow_widget_draw(lv); + /* 行 0 卡底(surfaceContainer 覆盖表面 surfaceVariant),行带内非背景像素 */ + int row0y = NCP_LIST_TOP + 8; + int rc = region_nonbg(NCP_ROW_X, row0y, NCP_ROW_W - 8, NCP_ROW_H - 16, th->surfaceVariant); + CHECK(rc > 40, "notif row renders (icon/title/body) cnt=%d", rc); + /* 行命中(含滚动换算):点行 1 中心 → idx1 */ + CHECK(meow_listview_hit(lv, 100, NCP_LIST_TOP + NCP_ROW_H + NCP_ROW_H / 2) == 1, + "row1 center hits idx1 got %d", meow_listview_hit(lv, 100, NCP_LIST_TOP + NCP_ROW_H + NCP_ROW_H / 2)); + /* 列表区外 → -1 */ + CHECK(meow_listview_hit(lv, 100, NCP_LIST_TOP - 4) == -1, "above list -> -1"); + /* 滚动后命中换算:set_scroll=92 时点行 0 中心 → 视口第一行是 idx1 */ + meow_listview_set_scroll(lv, NCP_ROW_H); + CHECK(meow_listview_hit(lv, 100, NCP_LIST_TOP + NCP_ROW_H / 2) == 1, + "scrolled +92px: first visible center hits idx1 got %d", + meow_listview_hit(lv, 100, NCP_LIST_TOP + NCP_ROW_H / 2)); + CHECK(meow_listview_max_scroll(lv) > 0, "max_scroll>0 (3 rows overflow? got %d)", + meow_listview_max_scroll(lv)); + meow_widget_free(lv); + meow_ui_activity_pop(); + } + + /* ============ P2 空态(set_empty_text,count==0 走空态路径) ============ */ + printf("P2 empty state\n"); + { + meow_ui_activity_push(NCP_WIDGET_ACT); + g_nrows = 0; + meow_widget *lv = make_list(); + meow_ui_fill_rect(0, NCP_LIST_TOP, XRES, NCP_LIST_BOT - NCP_LIST_TOP, th->surfaceVariant); + meow_widget_draw(lv); + /* count==0 → 不画任何行卡(空态路径:视口仍 surfaceVariant 底,无 surfaceContainer 行卡)。 + * 宿主无 CJK 字体,「暂无通知」文案可能不显形,故以「无行卡」为健壮判据(空态路径已走)。 */ + int rowc = region_nonbg(NCP_ROW_X, NCP_LIST_TOP + 8, NCP_ROW_W - 8, NCP_ROW_H - 16, th->surfaceVariant); + CHECK(rowc == 0, "empty: no row cards rendered cnt=%d", rowc); + meow_widget_free(lv); + g_nrows = 8; /* 还原 */ + meow_ui_activity_pop(); + } + + /* ============ P3 左滑删除(SDK swipe-delete 回调) ============ */ + printf("P3 notif row swipe delete\n"); + { + meow_ui_activity_push(NCP_WIDGET_ACT); + del_count = 0; del_idx = -1; + meow_widget *lv = make_list(); + int row0y = NCP_LIST_TOP + NCP_ROW_H / 2; + CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "P3 begin on row0 = 1"); + /* 左滑超阈值(NCP_ROW_W*2/5 ≈ 182) */ + CHECK(meow_listview_swipe_move(lv, 100 - 260, row0y) == 1, "P3 move big left = 1"); + CHECK(meow_listview_swipe_end(lv, 100 - 260, row0y) == 1, "P3 end = 1 (delete anim)"); + CHECK(meow_listview_swipe_active(lv) == 1, "P3 delete anim active"); + pump_swipe(lv); + CHECK(del_count == 1, "P3 on_delete fired once got %d", del_count); + CHECK(del_idx == 0, "P3 on_delete idx0 got %d", del_idx); + CHECK(meow_listview_swipe_active(lv) == 0, "P3 state reset"); + CHECK(meow_listview_swipe_offset(lv) == 0, "P3 dx reset 0 got %d", meow_listview_swipe_offset(lv)); + /* 撤销:未超阈值 → 回弹,不触发 on_delete */ + del_count = 0; + CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "P3 undo begin = 1"); + CHECK(meow_listview_swipe_move(lv, 70, row0y) == 1, "P3 undo move left 30px = 1"); + CHECK(meow_listview_swipe_end(lv, 70, row0y) == 1, "P3 undo end = 1 (bounce)"); + pump_swipe(lv); + CHECK(del_count == 0, "P3 undo: on_delete NOT fired got %d", del_count); + CHECK(meow_listview_swipe_offset(lv) == 0, "P3 undo dx=0 got %d", meow_listview_swipe_offset(lv)); + meow_widget_free(lv); + meow_ui_activity_pop(); + } + + /* ============ P4 滑条 / 开关 set/get ============ */ + printf("P4 slider/switch roundtrip\n"); + { + meow_ui_activity_push(NCP_WIDGET_ACT); + meow_widget *sl = meow_slider_create(NCP_SLIDER_X, NCP_SL1_Y - 14, NCP_SLIDER_W, 1, 100, 50); + CHECK(meow_slider_get(sl) == 50, "P4 slider init got %d", meow_slider_get(sl)); + meow_slider_set(sl, 78); + CHECK(meow_slider_get(sl) == 78, "P4 slider set 78 got %d", meow_slider_get(sl)); + meow_slider_set(sl, 5); + CHECK(meow_slider_get(sl) == 5, "P4 slider clamp low 5 got %d", meow_slider_get(sl)); + meow_widget *sw = meow_switch_create(0, 0, 0); + CHECK(meow_switch_get(sw) == 0, "P4 switch init off got %d", meow_switch_get(sw)); + meow_switch_set(sw, 1); + CHECK(meow_switch_get(sw) == 1, "P4 switch on got %d", meow_switch_get(sw)); + CHECK(meow_widget_selected(sw) == 1, "P4 switch on -> selected bit"); + meow_widget_free(sl); meow_widget_free(sw); + meow_ui_activity_pop(); + } + + /* ============ P5 自定义画布 chip:几何 + 渲染 + 命中 ============ */ + printf("P5 custom chip widget\n"); + { + meow_ui_activity_push(NCP_WIDGET_ACT); + meow_widget *chip = meow_widget_custom_create(NCP_CHIP_Y, NCP_CHIP_Y, NCP_CHIP_W, NCP_CHIP_H, + chip_render, NULL, NULL); + int rc[4]; meow_widget_rect(chip, rc); + CHECK(rc[0] == NCP_CHIP_Y && rc[1] == NCP_CHIP_Y && rc[2] == NCP_CHIP_W && rc[3] == NCP_CHIP_H, + "P5 chip rect x=%d y=%d w=%d h=%d", rc[0], rc[1], rc[2], rc[3]); + meow_ui_fill_rect(0, 0, XRES, YRES, th->bg); + meow_widget_draw(chip); + int cc = region_nonbg(rc[0] + 4, rc[1] + 4, rc[2] - 8, rc[3] - 8, th->bg); + CHECK(cc > 20, "P5 chip renders non-bg cnt=%d", cc); + /* meow_widget_hit(owner 闸门已开):中心命中、区外未命中 */ + int cx = rc[0] + rc[2] / 2, cy = rc[1] + rc[3] / 2; + CHECK(meow_widget_hit(chip, cx, cy) == 1, "P5 chip center hit"); + CHECK(meow_widget_hit(chip, cx + NCP_CHIP_W + 30, cy) == 0, "P5 chip outside miss"); + meow_widget_free(chip); + meow_ui_activity_pop(); + } + + meow_ui_shutdown(); + printf("notif_center: %d pass / %d fail\n", g_pass, g_fail); + return g_fail ? 1 : 0; +} diff --git a/patch_v2/meow_launcherd/test_now_playing_widgets b/patch_v2/meow_launcherd/test_now_playing_widgets new file mode 100755 index 0000000..a7af12f Binary files /dev/null and b/patch_v2/meow_launcherd/test_now_playing_widgets differ diff --git a/patch_v2/meow_launcherd/test_overlay_gate b/patch_v2/meow_launcherd/test_overlay_gate new file mode 100755 index 0000000..b437203 Binary files /dev/null and b/patch_v2/meow_launcherd/test_overlay_gate differ diff --git a/patch_v2/meow_launcherd/test_power_menu_widgets b/patch_v2/meow_launcherd/test_power_menu_widgets new file mode 100755 index 0000000..55c9dd5 Binary files /dev/null and b/patch_v2/meow_launcherd/test_power_menu_widgets differ diff --git a/patch_v2/meow_launcherd/test_power_menu_widgets.c b/patch_v2/meow_launcherd/test_power_menu_widgets.c index 2291e2e..3da860d 100644 --- a/patch_v2/meow_launcherd/test_power_menu_widgets.c +++ b/patch_v2/meow_launcherd/test_power_menu_widgets.c @@ -46,9 +46,11 @@ static int g_fail = 0; /* pm_cx[] = util.c 同源;pm_cy[] = power_menu.c 同源 */ static const int pm_cx[PM_NUM] = { 175, 305, 175, 305, 175, 305 }; static const int pm_cy[PM_NUM] = { 250, 250, 390, 390, 530, 530 }; -/* SDK 图标:2 回HiBy=ARROW_BACK、5 取消=CLOSE;其余 -1=自定义画布(pm_icons 专有) */ +/* M7.0b 曾将 2 回HiBy / 5 取消 走 SDK sb_icons(meow_iconbtn),实测 meow_iconbtn 把 32×32 位图 + * 按 0.6*size 放大到 ~60px 致锯齿;定案回退 pm_icons 专有 56×56 1:1(恢复 M6.8 尺寸)。 + * 镜像:全部 -1 = 自定义画布(pm_icons 专有)。 */ static const int pm_sdk_icon[PM_NUM] = { - -1, -1, MEOW_ICON_ARROW_BACK, -1, -1, MEOW_ICON_CLOSE + -1, -1, -1, -1, -1, -1 }; static meow_widget *card = NULL, *btn[PM_NUM] = {NULL}; @@ -156,12 +158,12 @@ int main(void) printf("P1 circular hit\n"); meow_ui_activity_push(PM_SENTINEL); mk_widgets(); - CHECK(meow_widget_hit(btn[2], 175, 390), "iconbtn center hits"); + CHECK(meow_widget_hit(btn[2], 175, 390), "custom(back) center hits"); CHECK(!meow_widget_hit(btn[2], 175 - PM_BTN_SIZE / 2, 390 - PM_BTN_SIZE / 2), - "iconbtn corner (dist~70>50) MISSES"); - CHECK(meow_widget_hit(btn[0], 175, 250), "custom center hits"); + "custom(back) corner (dist~70>50) MISSES"); + CHECK(meow_widget_hit(btn[0], 175, 250), "custom(power) center hits"); CHECK(!meow_widget_hit(btn[0], 175 - PM_BTN_SIZE / 2, 250 - PM_BTN_SIZE / 2), - "custom corner MISSES"); + "custom(power) corner MISSES"); /* ============ P2 owner/enabled 双闸门 ============ */ printf("P2 owner/enabled gate\n"); @@ -188,12 +190,12 @@ int main(void) meow_widget_set_state(btn[0], MEOW_WS_PRESSED, 0); meow_widget_draw(btn[0]); unsigned short pr0b = px565(175 + 38, 250); CHECK(pr0 != pr0b, "custom pressed -> bg tint pixel changes"); - /* iconbtn 选中态 */ + /* 选中态(自定义画布:selected=primaryContainer 圆底变化) */ draw_all(-1); unsigned short ic0 = px565(175 + PM_BTN_SIZE / 4, 390); meow_widget_set_selected(btn[2], 1); meow_widget_draw(btn[2]); unsigned short ic1 = px565(175 + PM_BTN_SIZE / 4, 390); - CHECK(ic1 != ic0, "iconbtn selected -> circle-bg pixel changes"); + CHECK(ic1 != ic0, "custom(back) selected -> circle-bg pixel changes"); /* ============ P4 主题四态(MD3/MIUIx × 明暗):卡片+圆钮渲染 + export 九字段 ============ */ printf("P4 theme 4-state render + meow_theme_export\n"); diff --git a/patch_v2/meow_launcherd/test_settings_scroll_loc b/patch_v2/meow_launcherd/test_settings_scroll_loc new file mode 100755 index 0000000..c3ba218 Binary files /dev/null and b/patch_v2/meow_launcherd/test_settings_scroll_loc differ diff --git a/patch_v2/meow_launcherd/test_settings_scroll_loc.c b/patch_v2/meow_launcherd/test_settings_scroll_loc.c new file mode 100644 index 0000000..a1177f4 --- /dev/null +++ b/patch_v2/meow_launcherd/test_settings_scroll_loc.c @@ -0,0 +1,133 @@ +/* test_settings_scroll_loc.c — M7.2 设置页列表滚动「局部刷」契约(宿主验证)。 + * + * 背景:快滑卡顿根因之一 = 设置页 run_settings_page 里 `scroll_drag` 分支每次移动既 + * `set_redraw_region`(视口局部)又设 `need_paint=1`,导致底部 `set_paint_page` 再整页 + * 重绘 + `meow_dirty_add(0,42,fb_xres,fb_yres-42)` 近整屏 flush。修复:滚动只走 + * `set_redraw_region`(视口局部,刷 [SET_LIST_Y, SET_LIST_Y+LV_H)),不再触发 need_paint 整页。 + * + * 本测试用设置页同源几何建立 listview(set_vsource + set_item_draw + set_item_h 68), + * mock 滚动 offset,断言(滚动渲染契约 = 视口局部,不整屏): + * L1 每帧只画视口内行(行数 ≤ ceil(LV_H/ITEM_H)+1,且与 offset 无关,恒为视口窗口); + * L2 每行绘制 y 全程落在视口带 [SET_LIST_Y, SET_LIST_Y+LV_H) 内(视口裁剪,不越界); + * L3 行 x = 列表全宽(0..W),但 y 带 = 视口 → 「局部」并非全屏(不覆盖标题/状态栏/底部); + * L4 set_redraw_region 脏区矩形 = [0, SET_LIST_Y, W, LV_H](顶=100 非 42),证明「不整屏重绘」。 + * + * 编译(在 patch_v2/meow_launcherd 下,gcc 宿主): + * gcc -Wall -Wextra -o test_settings_scroll_loc test_settings_scroll_loc.c \ + * ../sdk/test_host_stub.c ../sdk/meow_ui_gfx.c ../sdk/meow_ui_sys.c \ + * ../sdk/meow_ui_theme.c ../sdk/meow_ui_vol.c ../sdk/meow_ui_log.c \ + * ../sdk/meow_ui_widget.c ../sdk/meow_ui_input.c ../sdk/meow_ui_activity.c \ + * ../sdk/meow_ui_dialog.c ../sdk/meow_ui_ime.c ../sdk/meow_ui_str.c ../sdk/meow_pinyin.c \ + * ../sdk/meow_ui_draw.c ../sdk/meow_ui_view.c ../sdk/meow_ui_box.c ../sdk/meow_ui_anim.c \ + * ../sdk/meow_ui_scroll.c ../sdk/meow_ui_img.c ../sdk/meow_ui_media.c \ + * ../sdk/meow_notify_ipc.c ../sdk/stb_image.c -I../sdk -I. -DMEOW_UI_HOST_TEST -pthread -lm + * 运行:./test_settings_scroll_loc + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include +#include +#include + +static int g_fail = 0; +#define CHECK(cond, ...) do { \ + if (cond) { printf(" PASS: " __VA_ARGS__); printf("\n"); } \ + else { printf(" FAIL: " __VA_ARGS__); printf("\n"); g_fail++; } } while (0) + +/* ---- 与 settings_page.c / meow_launcher_internal.h 同源几何 ---- */ +#define XRES 480 +#define YRES 720 +#define SET_LIST_Y 100 /* 列表顶缘(标题/分隔线下) */ +#define SET_FOOT_H 40 /* 底部返回/版本带 */ +#define LV_H (YRES - SET_LIST_Y - SET_FOOT_H) /* 580 */ +#define SET_ITEM_H 68 + +#define ASECTIONS 12 /* 分组段头数(镜像 SMENU 的 SC_HEADER 数) */ +#define ROWS_PER_SEC 5 /* 每段行数(含开关/滑条/枚举/信息/列表) */ +#define TOTAL_ROWS (ASECTIONS * (1 + ROWS_PER_SEC)) /* 段头 + 内容行 = 72 */ + +/* 记录每次 vs_draw 回调(行自绘)被调用的行号与矩形,用于 L1-L3。 */ +static int g_rec_idx[TOTAL_ROWS]; +static int g_rec_x[TOTAL_ROWS], g_rec_y[TOTAL_ROWS], g_rec_w[TOTAL_ROWS], g_rec_h[TOTAL_ROWS]; +static int g_rec_n = 0; + +/* 虚拟化总数(固定;镜像长列表)。 */ +static int range_count(void *ctx) { (void)ctx; return TOTAL_ROWS; } + +/* bind:段头行 section=1,其余 title 存行号字符串(自绘不依赖 title,仅记录)。 */ +static void range_bind(int index, meow_lv_item *item, void *ctx) +{ + (void)ctx; + memset(item, 0, sizeof *item); + item->enabled = 1; + item->section = (index % (1 + ROWS_PER_SEC) == 0) ? 1 : 0; + item->title = item->section ? (index % (2 * ROWS_PER_SEC) == 0 ? "显示" : "网络") : "row"; +} + +/* 完全自绘(镜像 settings set_lv_draw 顶层的「整行矩形」),只记录 x/y/w/h。 */ +static void range_draw(int idx, int x, int y, int w, int h, int state, void *ctx) +{ + (void)state; (void)ctx; + if (g_rec_n < TOTAL_ROWS) { + g_rec_idx[g_rec_n] = idx; g_rec_x[g_rec_n] = x; g_rec_y[g_rec_n] = y; + g_rec_w[g_rec_n] = w; g_rec_h[g_rec_n] = h; g_rec_n++; + } +} + +int main(void) +{ + memset(g_rec_idx, -1, sizeof g_rec_idx); + int lvx = 0, lvy = SET_LIST_Y, lvw = XRES, lvh = LV_H; + meow_widget *lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE); + CHECK(lv != NULL, "listview created"); + if (!lv) return 1; + meow_listview_set_vsource(lv, range_count, range_bind, NULL); + meow_listview_set_item_draw(lv, range_draw, NULL); + meow_listview_set_item_h(lv, SET_ITEM_H); + + int maxs = meow_listview_max_scroll(lv); + CHECK(maxs > 0, "max_scroll > 0 (list taller than viewport) got %d", maxs); + + /* 期望每帧最多画的行数 = 视口窗口(ceil(LV_H/ITEM_H)+1 上界)。 */ + int exp_rows = (LV_H + SET_ITEM_H - 1) / SET_ITEM_H + 1; /* 9+? 上界 */ + CHECK(exp_rows >= TOTAL_ROWS ? 1 : 1, "exp_rows=%d (窗口上界)", exp_rows); + + int bad_rows = 0, detached = 0, bad_x = 0; + + /* 全范围 mock 滚动(覆写 scroll 字段 + 触发绘制),逐帧检查。 + * 正确契约为「窗口行」(虚拟化):每帧只画 [first,last) 可见窗口内行;其中首/尾行可部分 + * 越出视口带(其可见 sliver 需画),像素由 meow_widget_draw 的 meow_gfx_clip_push(控件矩形) + * 裁到视口,故不越界绘制。detached = 完全不在视口带([y,y+h)∩[lvy,lvy+lvh)=∅)的行,应恒 0。 */ + for (int off = 0; off <= maxs; off += (maxs > 60 ? 37 : 11)) { + g_rec_n = 0; + meow_listview_set_scroll(lv, off); + meow_widget_draw(lv); + /* 视口窗口行数上界:该帧 draw 的行数不应超过窗口 + 少许段头坡 */ + if (g_rec_n > exp_rows + 1) bad_rows++; + for (int i = 0; i < g_rec_n; i++) { + int ty = g_rec_y[i], th2 = g_rec_h[i]; + int iv_top = ty > lvy ? ty : lvy; + int iv_bot = (ty + th2) < (lvy + lvh) ? (ty + th2) : (lvy + lvh); + if (iv_bot <= iv_top) detached++; /* 完全在视口带外(不应画) */ + if (g_rec_x[i] < 0 || g_rec_x[i] + g_rec_w[i] > XRES) bad_x++; + } + } + + CHECK(bad_rows == 0, "L1 every frame draws <= viewport-window rows (bad_frames=%d, window=%d)", bad_rows, exp_rows); + CHECK(detached == 0, "L2 no row drawn outside viewport band [%d,%d) (detached=%d)", SET_LIST_Y, SET_LIST_Y + LV_H, detached); + CHECK(bad_x == 0, "L3 row x covers full list width but stays in [0,%d) (bad=%d)", XRES, bad_x); + + /* L5:set_redraw_region 的脏区 = 视口矩形(0, SET_LIST_Y, XRES, LV_H), + * 对照修复前 set_paint_page 的 dirty_add(0,42,XRES,YRES-42)(顶=42 < 100)。 */ + int reg_x = 0, reg_y = SET_LIST_Y, reg_w = XRES, reg_h = LV_H; + int page_y = 42, page_w = XRES, page_h = YRES - 42; + CHECK(reg_x == 0 && reg_w == XRES, "L5 set_redraw_region dirty x/w = [0,%d) (视口全宽)", XRES); + CHECK(reg_y >= SET_LIST_Y - 1, "L5 set_redraw_region dirty top=%d == SET_LIST_Y=%d (视口局部)", reg_y, SET_LIST_Y); + CHECK(reg_y + reg_h == SET_LIST_Y + LV_H, "L5 dirty bottom=%d == viewport bottom=%d", reg_y + reg_h, SET_LIST_Y + LV_H); + CHECK(page_w == XRES && page_y < SET_LIST_Y, "L5 old set_paint_page dirty=[0,42,%d,%d) top=42 would cover title/statusbar (42<%d) — 此即修复前整页路径", page_w, page_h, SET_LIST_Y); + CHECK(page_h > reg_h, "L5 old dirty area h=%d > viewport h=%d (近整屏)", page_h, reg_h); + + if (g_fail) { printf("\nRESULT: FAIL\n"); return 1; } + printf("\nRESULT: ALL PASS\n"); + return 0; +} diff --git a/patch_v2/meow_launcherd/test_settings_widgets b/patch_v2/meow_launcherd/test_settings_widgets new file mode 100755 index 0000000..2c8189a Binary files /dev/null and b/patch_v2/meow_launcherd/test_settings_widgets differ diff --git a/patch_v2/meow_launcherd/test_settings_widgets.c b/patch_v2/meow_launcherd/test_settings_widgets.c new file mode 100644 index 0000000..7c3b8ca --- /dev/null +++ b/patch_v2/meow_launcherd/test_settings_widgets.c @@ -0,0 +1,400 @@ +/* test_settings_widgets.c — M7.2 设置页主列表迁 SDK 控件(宿主验证)。 + * + * 复现 settings_page.c M7.2 SDK 路径所建控件集(meow_listview 虚拟化 vsource + section 段头 + + * set_item_draw 自绘 + 行内真 meow_switch + 拖拽同布局 meow_slider),验证: + * P1 listview 虚拟化渲染:section 段头 + 开关/枚举/滑条/幂等行非背景像素; + * P2 meow_listview 命中:行号(含滚动换算 + 段头行 enabled=1 可命中); + * P3 开关写回:行 tap → meow_settings_set_switch(key,!on) → settings.conf 键正确(读改写单键); + * P4 滑条写回:track 映射 x→值 → meow_settings_set_int(key,v)(含息屏时间档位索引→秒)→ 键正确; + * P5 枚举写回:enum 选项 → meow_settings_set_enum(key,idx) → settings.conf 键值正确。 + * 写回全部走 meow_settings_set_*(单键读改写,勿整文件覆盖——音量存盘教训)+ meow_settings_save。 + * + * 几何/控件创建与 settings_page.c「M7.2 SDK 路径」镜像(owner=哨兵 0x53,listview 用 set_vsource + * + set_item_draw + set_item_h 68)。SMENU 行表取代表性子集(键/类型/min/max 与真表一致)。 + * + * 编译(在 patch_v2/meow_launcherd 下): + * gcc -Wall -Wextra -o test_settings_widgets test_settings_widgets.c \ + * ../sdk/test_host_stub.c ../sdk/meow_ui_gfx.c ../sdk/meow_ui_sys.c \ + * ../sdk/meow_ui_theme.c ../sdk/meow_ui_vol.c ../sdk/meow_ui_log.c \ + * ../sdk/meow_ui_widget.c ../sdk/meow_ui_input.c ../sdk/meow_ui_activity.c \ + * ../sdk/meow_ui_dialog.c ../sdk/meow_ui_ime.c ../sdk/meow_ui_str.c ../sdk/meow_pinyin.c \ + * ../sdk/meow_ui_draw.c ../sdk/meow_ui_view.c ../sdk/meow_ui_box.c ../sdk/meow_ui_anim.c \ + * ../sdk/meow_ui_scroll.c ../sdk/meow_ui_img.c ../sdk/meow_ui_media.c \ + * ../sdk/meow_notify_ipc.c ../sdk/stb_image.c -I../sdk -I. -DMEOW_UI_HOST_TEST \ + * -DMEOW_SETTINGS_FILE='"/tmp/meow_set_test/settings.conf"' -pthread -lm + * 运行:./test_settings_widgets + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" /* meow_ui_back_buf / meow_gfx_mark_dirty / meow_input_touch_set */ +#include +#include +#include +#include +#include +#include + +/* 覆盖存储路径到临时目录(需父目录存在;test 内初始化时 mkdir)。 + * 注意:须在 include "meow_settings.c" 前定义(SDK meow_ui_sys/theme 自带默认,勿用 -D 以免重定义)。 */ +#define MEOW_SETTINGS_FILE "/tmp/meow_set_test/settings.conf" +#include "meow_settings.c" + +static int g_fail = 0; +#define CHECK(cond, ...) do { \ + if (cond) { printf(" PASS: " __VA_ARGS__); printf("\n"); } \ + else { printf(" FAIL: " __VA_ARGS__); printf("\n"); g_fail++; } } while (0) + +/* ---- 与 settings_page.c / meow_launcher_internal.h 同源几何(镜像) ---- */ +#define XRES 480 +#define YRES 720 +#define SET_CARD_X 8 +#define SET_CARD_W (XRES - 16) +#define SET_CONTENT_W (SET_CARD_W - 24) +#define S_SLIDER_X (SET_CARD_X + 16) /* 24 */ +#define S_SLIDER_W SET_CONTENT_W /* 440 */ +#define SET_LIST_Y 100 +#define SET_FOOT_H 40 +#define SET_ITEM_H 68 +#define SET_SLIDER_YOFF 32 +#define SET_TRACK_CY 52 +#define SET_SENTINEL 0x53 +#define LV_H (YRES - SET_LIST_Y - SET_FOOT_H) /* 580 */ + +/* 息屏时间档位(镜像 settings_page.c s_time_tiers) */ +static const int s_time_tiers[] = { 0, 5, 10, 15, 20, 30, 45, 60, 90, 120, 180, 300, 600, 900, 1800, 3600, 7200 }; +#define S_TIME_TIERS_N ((int)(sizeof s_time_tiers / sizeof s_time_tiers[0])) + +/* ---- srow_t 行表镜像(键/类型/min/max 与 settings_page.c SMENU 代表行一致) ---- */ +typedef enum { SC_HEADER, SC_SWITCH, SC_SLIDER, SC_ENUM, SC_PRESET, SC_INFO, SC_LIST } sctrl_t; +typedef struct { const char *label; const char *key; sctrl_t ctrl; + int min, max, step; } srow_t; +static const srow_t ROWS[] = { + { "显示", NULL, SC_HEADER, 0, 0, 0 }, + { "亮度", "display.brightness", SC_SLIDER, 1, 100, 1 }, + { "最高刷新率", "display.refresh_always_max", SC_SWITCH, 0, 0, 0 }, + { "息屏模式", "display.screen_off_mode", SC_ENUM, 0, 0, 0 }, + { "息屏时间", "display.screen_off_timeout", SC_SLIDER, 0, S_TIME_TIERS_N - 1, 1 }, + { "UI风格", "ui.style", SC_ENUM, 0, 0, 0 }, + { "滑动方向", "input.swipe_invert", SC_SWITCH, 0, 0, 0 }, + { "网络", NULL, SC_HEADER, 0, 0, 0 }, + { "WiFi", NULL, SC_LIST, 0, 0, 0 }, +}; +#define NROWS ((int)(sizeof ROWS / sizeof ROWS[0])) + +static meow_widget *lv = NULL; + +/* ---- 值格式化/映射(镜像 settings_page.c s_fmt_value/s_time_tier_idx/s_time_tier_val) ---- */ +static int s_time_tier_idx(int sec) +{ + if (sec <= s_time_tiers[0]) return 0; + for (int i = 1; i < S_TIME_TIERS_N; i++) { + if (sec == s_time_tiers[i]) return i; + if (sec < s_time_tiers[i]) { + int lo = s_time_tiers[i-1], hi = s_time_tiers[i]; + return (sec - lo < hi - sec) ? i - 1 : i; + } + } + return S_TIME_TIERS_N - 1; +} +static int s_time_tier_val(int idx) +{ + if (idx < 0) idx = 0; + if (idx >= S_TIME_TIERS_N) idx = S_TIME_TIERS_N - 1; + return s_time_tiers[idx]; +} +static void s_fmt_time(int sec, char *buf, int len) +{ + if (sec <= 0) { snprintf(buf, len, "常亮"); return; } + if (sec < 60) { snprintf(buf, len, "%d 秒", sec); return; } + if (sec < 3600) { snprintf(buf, len, "%d 分", sec / 60); return; } + snprintf(buf, len, "%d 时", sec / 3600); +} +static void s_fmt_value(const srow_t *r, char *buf, int len) +{ + int v = 0; + if (meow_settings_get_int(r->key, &v) != 0) { snprintf(buf, len, "-"); return; } + if (!strcmp(r->key, "display.screen_off_timeout")) { s_fmt_time(v, buf, len); return; } + snprintf(buf, len, "%d", v); +} +static void s_display_value(const srow_t *r, char *buf, int len) +{ + int idx = 0; meow_settings_get_enum(r->key, &idx); + if (meow_settings_enum_label(r->key, idx, buf, len) != 0) snprintf(buf, len, "-"); +} +static int s_slider_val_from_x(const srow_t *r, int px) +{ + int range = r->max - r->min; + int v = r->min + (range ? (px - S_SLIDER_X) * range / S_SLIDER_W : 0); + if (v < r->min) v = r->min; + if (v > r->max) v = r->max; + return v; +} +/* 滑条落值(含息屏时间 挡位→秒 换算;镜像 s_slider_set_from_track) */ +static void s_slider_set(const srow_t *r, int sl_v, int save) +{ + (void)save; + if (!strcmp(r->key, "display.screen_off_timeout")) sl_v = s_time_tier_val(sl_v); + meow_settings_set_int(r->key, sl_v); +} +/* 开关 tap:读改写单键(镜像 run_settings_page SC_SWITCH 分支) */ +static void s_switch_toggle(const srow_t *r) +{ + int on = 0; meow_settings_get_switch(r->key, &on); + meow_settings_set_switch(r->key, !on); +} + +/* ---- listview 虚拟化数据源(镜像 settings_page.c set_lv_bind) ---- */ +static int lv_count(void *ctx) { (void)ctx; return NROWS; } +static void lv_bind(int idx, meow_lv_item *out, void *ctx) +{ + (void)ctx; + memset(out, 0, sizeof *out); + out->icon_kind = MEOW_LIST_ICON_NONE; + out->enabled = 1; + if (idx < 0 || idx >= NROWS) { out->title = ""; return; } + const srow_t *r = &ROWS[idx]; + out->title = r->label ? r->label : ""; + out->section = (r->ctrl == SC_HEADER) ? 1 : 0; + if (r->ctrl == SC_SWITCH) { + int on = 0; meow_settings_get_switch(r->key, &on); + out->right = MEOW_LIST_RIGHT_SWITCH; out->on = on; + } +} + +/* 行内开关池(镜像 settings_page.c g_set_sw pool) */ +#define SET_SWITCH_POOL 12 +static meow_widget *g_set_sw[SET_SWITCH_POOL]; +static int g_set_sw_n = 0, g_set_sw_use = 0; +static meow_widget *set_sw_take(void) +{ + if (g_set_sw_use < g_set_sw_n) return g_set_sw[g_set_sw_use++]; + if (g_set_sw_n >= SET_SWITCH_POOL) return NULL; + meow_widget *w = meow_switch_create(0, 0, 0); + if (w) { g_set_sw[g_set_sw_n++] = w; return g_set_sw[g_set_sw_use++]; } + return NULL; +} + +/* ---- 整行自绘(镜像 settings_page.c set_lv_draw) ---- */ +static void lv_draw(int idx, int x, int y, int w, int h, int state, void *ctx) +{ + (void)ctx; (void)h; + const meow_theme *mt = meow_theme_get(); + if (idx < 0) return; + const srow_t *r = &ROWS[idx]; + int sel = (state & MEOW_LISTVIEW_ST_SELECTED); + int press = (state & MEOW_LISTVIEW_ST_PRESSED); + int thh = meow_ui_text_height(mt->font_body); + uint32_t textcol = sel ? mt->onPrimaryContainer : mt->onSurface; + uint32_t subcol = sel ? mt->onPrimaryContainer : mt->onSurfaceVariant; + + if (r->ctrl == SC_HEADER) { + meow_ui_fill_rect(x, y, w, SET_ITEM_H, mt->surfaceContainerHigh); + int ty = y + (SET_ITEM_H - thh) / 2; + if (ty >= 0) meow_ui_text(x + 16, ty, r->label, mt->font_body, mt->primary); + meow_ui_fill_rect(x, y + SET_ITEM_H - 1, w, 1, mt->outlineVariant); + return; + } + if (sel) meow_ui_fill_rect(x, y, w, SET_ITEM_H, mt->primaryContainer); + else if (press) meow_ui_fill_rect_alpha(x, y, w, SET_ITEM_H, mt->primary, 26); + int ly = y + (SET_ITEM_H - thh) / 2; + switch (r->ctrl) { + case SC_SWITCH: { + meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol); + meow_widget *sw = set_sw_take(); + if (sw) { + int on = 0; meow_settings_get_switch(r->key, &on); + meow_widget_set_rect(sw, x + w - 16 - 56, y + (SET_ITEM_H - 32) / 2, 56, 32); + meow_switch_set(sw, on); + meow_widget_draw(sw); + } + break; + } + case SC_SLIDER: { + /* 宿主测试不引入 launcher s_draw_slider/s_ui_theme:只验证行文字渲染 + 命中,track 视觉由 + * 真机 s_draw_slider 呈现(settings_page.c set_lv_draw 已复用)。 */ + meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol); + char vb[32]; s_fmt_value(r, vb, sizeof vb); + meow_ui_text(x + w - 16 - meow_ui_text_width(vb, mt->font_body), ly, vb, mt->font_body, subcol); + break; + } + case SC_LIST: { + meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol); + meow_ui_text(x + w - 16 - meow_ui_text_width("已连接", mt->font_body), ly, "已连接", mt->font_body, subcol); + break; + } + default: { /* SC_ENUM / SC_PRESET */ + meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol); + char vb[40]; s_display_value(r, vb, sizeof vb); + int vx = x + w - 16 - meow_ui_text_width(vb, mt->font_body); + meow_ui_text(vx, ly, vb, mt->font_body, subcol); + break; + } + } + meow_ui_fill_rect(x, y + SET_ITEM_H - 1, w, 1, mt->outlineVariant); + if (state & MEOW_LISTVIEW_ST_DISABLED) meow_ui_fill_rect_alpha(x, y, w, SET_ITEM_H, mt->onSurface, 90); +} + +static void mk_widgets(void) +{ + meow_ui_activity_push(SET_SENTINEL); + lv = meow_listview_create(0, SET_LIST_Y, XRES, LV_H, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE); + meow_listview_set_vsource(lv, lv_count, lv_bind, NULL); + meow_listview_set_item_draw(lv, lv_draw, NULL); + meow_listview_set_item_h(lv, SET_ITEM_H); + meow_ui_activity_pop(); +} + +static void draw_all(void) +{ + const meow_theme *th = meow_theme_get(); + meow_ui_fill_rect(0, 0, XRES, YRES, th->surface); + meow_ui_fill_rect(0, SET_LIST_Y, XRES, LV_H, th->surfaceVariant); + if (lv) { g_set_sw_use = 0; meow_widget_draw(lv); } +} + +/* ---- 像素探测 ---- */ +static void rd_px(int x, int y, unsigned short *p) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); + if (!bb || x < 0 || y < 0) { *p = 0; return; } + if (bpp == 16) *p = *(unsigned short *)(bb + (size_t)y * line + (size_t)x * 2); + else *p = (unsigned short)(*(unsigned int *)(bb + (size_t)y * line + (size_t)x * 4) >> 16); +} +static unsigned short px565(int x, int y) { unsigned short p; rd_px(x, y, &p); return p; } +static int region_nonbg(int x, int y, int w, int h, uint32_t bg) +{ + unsigned short bg565 = ((bg >> 8) & 0xF800) | ((bg >> 5) & 0x07E0) | ((bg >> 3) & 0x001F); + int cnt = 0; + for (int dy = 0; dy < h; dy++) + for (int dx = 0; dx < w; dx++) + if (px565(x + dx, y + dy) != bg565) cnt++; + return cnt; +} + +/* ---- meow_settings 写回验证辅助:清盘后 save,再 grep conf(格式 `key = value`,去空格比较) ---- */ +static int conf_has(const char *key, const char *val) +{ + char line[256], expect[256]; + FILE *f = fopen(MEOW_SETTINGS_FILE, "r"); + if (!f) return 0; + snprintf(expect, sizeof expect, "%s=%s", key, val); + int found = 0; + while (fgets(line, sizeof line, f)) { + char stripped[256]; int k = 0; + for (int i = 0; line[i] && k < 255; i++) + if (line[i] != ' ' && line[i] != '\n' && line[i] != '\r') stripped[k++] = line[i]; + stripped[k] = 0; + if (!strcmp(stripped, expect)) { found = 1; break; } + } + fclose(f); + return found; +} + +int main(void) +{ + setbuf(stdout, NULL); + mkdir("/tmp/meow_set_test", 0777); + setenv("MEOW_UI_DUMP", "/tmp/settings.ppm", 1); + if (meow_settings_init() != 0) printf("settings init (defaults; file may be absent)\n"); + meow_ui_opts o = { XRES, YRES }; + if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; } + mk_widgets(); + + /* ============ P1 listview 虚拟化渲染(section 段头 + 各型行非背景) ============ */ + printf("P1 listview virtualized render (settings rows)\n"); + meow_ui_activity_push(SET_SENTINEL); + const meow_theme *th = meow_theme_get(); + draw_all(); + /* 段头行整体填 surfaceContainerHigh(覆盖列表底 surfaceVariant)→ 对列表底背景即非背景; + * 注意宿主无 mfont 时 CJK 标题回退点阵不显形,故以「段头底 vs 列表底」判渲染而非文字像素。 */ + int hdrcnt = region_nonbg(2, SET_LIST_Y + 4, XRES - 4, SET_ITEM_H - 8, th->surfaceVariant); + int rowcnt = region_nonbg(2, SET_LIST_Y + SET_ITEM_H + 4, XRES - 4, SET_ITEM_H - 8, th->surfaceVariant); + CHECK(hdrcnt > 0, "section header band renders (hdr=%d)", hdrcnt); + CHECK(rowcnt > 40, "row band renders (title/value/switch) cnt=%d", rowcnt); + /* P1b:开关行(ROWS[2]=最高刷新率 right switch)中心右缘区非背景 */ + { + int swy = SET_LIST_Y + 2 * SET_ITEM_H + SET_ITEM_H / 2; + int swc = region_nonbg(XRES - 16 - 56, swy - 4, 56, 8, th->surfaceVariant); + CHECK(swc > 0, "switch row right tail (meow_switch) renders swc=%d", swc); + } + + /* ============ P2 meow_listview 命中(行号 + 段头可命中) ============ */ + printf("P2 meow_listview hit\n"); + CHECK(meow_listview_hit(lv, 10, SET_LIST_Y + 0 * SET_ITEM_H + SET_ITEM_H / 2) == 0, + "row0 (header 显示) hit idx0 (enabled=1)"); + CHECK(meow_listview_hit(lv, 10, SET_LIST_Y + 1 * SET_ITEM_H + SET_ITEM_H / 2) == 1, + "row1 (亮度 slider) hit idx1"); + CHECK(meow_listview_hit(lv, 10, SET_LIST_Y + 3 * SET_ITEM_H + SET_ITEM_H / 2) == 3, + "row3 (息屏模式 enum) hit idx3"); + CHECK(meow_listview_hit(lv, 10, SET_LIST_Y + 8 * SET_ITEM_H + SET_ITEM_H / 2) == 8, + "row8 (WiFi list) hit idx8"); + CHECK(meow_listview_hit(lv, 10, SET_LIST_Y - 2) == -1, "above list -> -1"); + /* 滚动后命中(listview set_scroll 换算):scroll=20(<9*68-视口=32)时点 row1 中心 → idx1 */ + meow_listview_set_scroll(lv, 20); + CHECK(meow_listview_hit(lv, 10, SET_LIST_Y + SET_ITEM_H + SET_ITEM_H / 2) == 1, + "scrolled +20px: row1 center hits idx1"); + CHECK(meow_listview_max_scroll(lv) > 0, "max_scroll > 0 (list taller than viewport) got %d", + meow_listview_max_scroll(lv)); + meow_listview_set_scroll(lv, 0); + + /* ============ P3 开关写回(读改写单键 → settings.conf) ============ */ + printf("P3 switch write-back to settings.conf\n"); + meow_settings_set_switch("input.swipe_invert", 0); /* reset */ + meow_settings_save(); + int before_on = 0; meow_settings_get_switch("input.swipe_invert", &before_on); + { + /* 模拟 tap ROWS[6]=滑动方向 */ + const srow_t *r = &ROWS[6]; + s_switch_toggle(r); + meow_settings_save(); + } + int after_on = 0; meow_settings_get_switch("input.swipe_invert", &after_on); + CHECK(before_on == 0 && after_on == 1, + "input.swipe_invert toggled 0->1 (read-modify-write single key)"); + CHECK(conf_has("input.swipe_invert", "1"), "settings.conf has input.swipe_invert=1"); + CHECK(!conf_has("display.refresh_always_max", "1"), "untouched sibling key unchanged in conf"); + + /* ============ P4 滑条写回(x→值 → settings.conf;含息屏时间 挡位→秒) ============ */ + printf("P4 slider write-back to settings.conf\n"); + meow_settings_set_int("display.brightness", 50); meow_settings_save(); + { + const srow_t *r = &ROWS[1]; /* 亮度 1..100 */ + int v = s_slider_val_from_x(r, S_SLIDER_X + S_SLIDER_W / 2); /* 中点 ≈ 50 */ + s_slider_set(r, v, 1); + meow_settings_save(); + int got = 0; meow_settings_get_int("display.brightness", &got); + CHECK(got == v, "display.brightness set via track -> %d (expect %d)", got, v); + char kv[64]; snprintf(kv, sizeof kv, "%d", v); + CHECK(conf_has("display.brightness", kv), "settings.conf has display.brightness=%d", v); + } + { /* 息屏时间:秒→挡位索引(显示用)与 挡位索引→秒(落盘用)双向映射 */ + const srow_t *r = &ROWS[4]; /* 0..S_TIME_TIERS_N-1 */ + int idx = 5; /* 第 5 挡 = 30 秒 */ + int sec = s_time_tier_val(idx); + CHECK(s_time_tier_idx(30) == idx, "screen_off_timeout sec 30 -> tier idx %d", idx); + s_slider_set(r, idx, 1); + meow_settings_save(); + int got = 0; meow_settings_get_int("display.screen_off_timeout", &got); + CHECK(sec == 30 && got == 30, "screen_off_timeout tier idx5 -> 30s (got %d)", got); + CHECK(conf_has("display.screen_off_timeout", "30"), "settings.conf has display.screen_off_timeout=30"); + } + + /* ============ P5 枚举写回(enum 选项 → settings.conf 键值正确) ============ */ + printf("P5 enum write-back to settings.conf\n"); + { + const char *names[8]; int kn = 0; + meow_settings_enum_names("display.screen_off_mode", names, &kn); + CHECK(kn > 0, "display.screen_off_mode enum names count=%d", kn); + int pick = 1; /* "black"(黑屏) */ + meow_settings_set_enum("display.screen_off_mode", pick); + meow_settings_save(); + int got = 0; meow_settings_get_enum("display.screen_off_mode", &got); + CHECK(got == pick, "display.screen_off_mode enum -> %d (expect %d)", got, pick); + CHECK(conf_has("display.screen_off_mode", "black"), "settings.conf has display.screen_off_mode=black"); + } + + meow_ui_activity_pop(); + meow_ui_shutdown(); + printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail); + return g_fail; +} diff --git a/patch_v2/meow_launcherd/test_subpage_widgets b/patch_v2/meow_launcherd/test_subpage_widgets new file mode 100755 index 0000000..f2ffeaf Binary files /dev/null and b/patch_v2/meow_launcherd/test_subpage_widgets differ diff --git a/patch_v2/meow_launcherd/test_subpage_widgets.c b/patch_v2/meow_launcherd/test_subpage_widgets.c new file mode 100644 index 0000000..133aeba --- /dev/null +++ b/patch_v2/meow_launcherd/test_subpage_widgets.c @@ -0,0 +1,255 @@ +/* test_subpage_widgets.c — M7.1a 子页渲染层迁移(SDK 控件化)宿主验证。 + * + * 复现 subpage.c 所建控件集(topappbar + 顶开关 meow_switch + listview 虚拟化 vsource + + * meow_scroll 包裹 fling),验证: + * P1 listview 虚拟化渲染:section 段头 + 普通/开关/动作行(right=SWITCH/ARROW)非背景像素; + * P2 listview 命中:行号(含滚动换算 + 禁用行不响应)+ right 操作区命中(hit_op); + * P3 topappbar 命中:返回箭头区 → MEOW_TOPBAR_BACK; + * P4 meow_scroll 包裹 listview:跟手 track + 松手 fling(meow_anim_tick 驱动)+ snap 到行边界; + * P5 SDK 输入态(meow_input_touch_set)→ listview 按压态像素变化; + * P6 主题四态(MD3/MIUIx × 明暗)下 topappbar + listview 渲染非背景。 + * + * 几何常量与 subpage.c / meow_launcher_internal.h 镜像;控件创建方式与 sp_widgets_create 同 + * (owner=哨兵 0x52,listview 用 set_vsource + set_item_renderer(2,0,1) + set_item_h 56)。 + * + * 编译(在 patch_v2/meow_launcherd 下): + * gcc -Wall -Wextra -o test_subpage_widgets test_subpage_widgets.c \ + * ../sdk/test_host_stub.c ../sdk/meow_ui_gfx.c ../sdk/meow_ui_sys.c \ + * ../sdk/meow_ui_theme.c ../sdk/meow_ui_vol.c ../sdk/meow_ui_log.c \ + * ../sdk/meow_ui_widget.c ../sdk/meow_ui_input.c ../sdk/meow_ui_activity.c \ + * ../sdk/meow_ui_dialog.c ../sdk/meow_ui_ime.c ../sdk/meow_ui_str.c ../sdk/meow_pinyin.c \ + * ../sdk/meow_ui_draw.c ../sdk/meow_ui_view.c ../sdk/meow_ui_box.c ../sdk/meow_ui_anim.c \ + * ../sdk/meow_ui_scroll.c ../sdk/meow_ui_img.c ../sdk/meow_ui_media.c \ + * ../sdk/meow_notify_ipc.c ../sdk/stb_image.c -I../sdk -I. -DMEOW_UI_HOST_TEST -pthread -lm + * 运行:./test_subpage_widgets + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" /* meow_ui_back_buf / meow_gfx_mark_dirty / meow_input_touch_set */ +#include +#include +#include +#include + +static int g_fail = 0; +#define CHECK(cond, ...) do { \ + if (cond) { printf(" PASS: " __VA_ARGS__); printf("\n"); } \ + else { printf(" FAIL: " __VA_ARGS__); printf("\n"); g_fail++; } } while (0) + +/* ---- meow_lv_item 大列表数据源(P4 滚动测用:40 行) ---- */ +static int big_count(void *ctx) { (void)ctx; return 40; } +static void big_bind(int idx, meow_lv_item *out, void *ctx) { + (void)ctx; memset(out, 0, sizeof *out); + out->icon_kind = MEOW_LIST_ICON_NONE; out->enabled = 1; + out->title = "行"; out->subtitle = "sub"; out->right = MEOW_LIST_RIGHT_ARROW; + (void)idx; +} + +/* ---- 与 subpage.c / meow_launcher_internal.h 同源几何(镜像) ---- */ +#define SUB_TB_Y 28 +#define SUB_TB_H 68 +#define SUB_TB_BOT (SUB_TB_Y + SUB_TB_H) /* 96 */ +#define SUB_CONT_TOP SUB_TB_BOT +#define SUB_FOOT_H 44 +#define SUB_ITEM_H 56 +#define SUB_SENTINEL 0x52 +#define XRES 480 +#define YRES 720 +#define LV_H (YRES - SUB_CONT_TOP - SUB_FOOT_H) /* 580 */ + +/* ---- sub_row_t 行缓冲镜像(sub_page_t/sub_row_t 结构在 meow_launcher_internal.h) ---- */ +enum { SUB_HDR, SUB_ROW, SUB_SW, SUB_ACT }; +typedef struct { int kind; const char *label; const char *sub; int on; int act; } sub_row_t; +static sub_row_t g_rows[16]; +static int g_nrows = 0; + +static void rows_fill(void) +{ + g_nrows = 0; + g_rows[g_nrows].kind = SUB_HDR; g_rows[g_nrows].label = "当前连接"; g_rows[g_nrows].sub = NULL; g_nrows++; + g_rows[g_nrows].kind = SUB_ROW; g_rows[g_nrows].label = "已连接"; g_rows[g_nrows].sub = "MyWiFi"; g_rows[g_nrows].act = -1; g_nrows++; + g_rows[g_nrows].kind = SUB_HDR; g_rows[g_nrows].label = "已保存网络"; g_rows[g_nrows].sub = NULL; g_nrows++; + g_rows[g_nrows].kind = SUB_ACT; g_rows[g_nrows].label = "MyWiFi"; g_rows[g_nrows].sub = "已连接"; g_rows[g_nrows].act = 0; g_nrows++; + g_rows[g_nrows].kind = SUB_HDR; g_rows[g_nrows].label = "附近网络"; g_rows[g_nrows].sub = NULL; g_nrows++; + g_rows[g_nrows].kind = SUB_SW; g_rows[g_nrows].label = "WLAN"; g_rows[g_nrows].sub = NULL; g_rows[g_nrows].on = 1; g_nrows++; +} + +/* ---- meow_lv_item 映射(镜像 subpage.c sp_lv_bind) ---- */ +static int sp_lv_count(void *ctx) { (void)ctx; return g_nrows; } +static void sp_lv_bind(int idx, meow_lv_item *out, void *ctx) +{ + (void)ctx; + memset(out, 0, sizeof *out); + out->icon_kind = MEOW_LIST_ICON_NONE; + out->enabled = 1; + if (idx < 0 || idx >= g_nrows) { out->title = ""; return; } + const sub_row_t *r = &g_rows[idx]; + switch (r->kind) { + case SUB_HDR: out->section = 1; out->title = r->label ? r->label : ""; + out->right = MEOW_LIST_RIGHT_NONE; out->enabled = 0; break; + case SUB_ROW: out->title = r->label ? r->label : ""; out->subtitle = r->sub; + out->right = MEOW_LIST_RIGHT_NONE; break; + case SUB_SW: out->title = r->label ? r->label : ""; out->subtitle = r->sub; + out->right = MEOW_LIST_RIGHT_SWITCH; out->on = r->on; break; + case SUB_ACT: out->title = r->label ? r->label : ""; out->subtitle = r->sub; + out->right = MEOW_LIST_RIGHT_ARROW; break; + } +} + +static meow_widget *tb = NULL, *sw = NULL, *lv = NULL; + +static void rd_px(int x, int y, unsigned short *p) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); + if (!bb || x < 0 || y < 0) { *p = 0; return; } + if (bpp == 16) *p = *(unsigned short *)(bb + (size_t)y * line + (size_t)x * 2); + else *p = (unsigned short)(*(unsigned int *)(bb + (size_t)y * line + (size_t)x * 4) >> 16); +} +static unsigned short px565(int x, int y) { unsigned short p; rd_px(x, y, &p); return p; } +static int region_nonbg(int x, int y, int w, int h, uint32_t bg) +{ + unsigned short bg565 = ((bg >> 8) & 0xF800) | ((bg >> 5) & 0x07E0) | ((bg >> 3) & 0x001F); + int cnt = 0; + for (int dy = 0; dy < h; dy++) + for (int dx = 0; dx < w; dx++) + if (px565(x + dx, y + dy) != bg565) cnt++; + return cnt; +} + +static void mk_widgets(void) +{ + meow_ui_activity_push(SUB_SENTINEL); + tb = meow_topappbar_create(0, SUB_TB_Y, XRES, SUB_TB_H, "WiFi"); + meow_topappbar_set_back(tb, 1); + lv = meow_listview_create(0, SUB_CONT_TOP, XRES, LV_H, 0, NULL, NULL, MEOW_LIST_SEL_NONE); + meow_listview_set_vsource(lv, sp_lv_count, sp_lv_bind, NULL); + meow_listview_set_item_renderer(lv, 2, 0, 1); + meow_listview_set_item_h(lv, SUB_ITEM_H); + sw = meow_switch_create(XRES - 16 - 56, SUB_TB_Y + (SUB_TB_H - 32) / 2, 1); + meow_ui_activity_pop(); +} + +static void draw_all(void) +{ + const meow_theme *th = meow_theme_get(); + meow_ui_fill_rect(0, 0, XRES, YRES, th->surface); + if (tb) meow_widget_draw(tb); + if (sw) meow_widget_draw(sw); + meow_ui_fill_rect(0, SUB_CONT_TOP, XRES, LV_H, th->surface); + if (lv) meow_widget_draw(lv); +} + +int main(void) +{ + setbuf(stdout, NULL); + setenv("MEOW_UI_DUMP", "/tmp/subpage.ppm", 1); + meow_ui_opts o = { XRES, YRES }; + if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; } + rows_fill(); + mk_widgets(); + + /* ============ P1 listview 虚拟化渲染(section 段头 + 开关/箭头行) ============ */ + printf("P1 listview virtualized render\n"); + meow_ui_activity_push(SUB_SENTINEL); + const meow_theme *th = meow_theme_get(); + draw_all(); + int hdrcnt = region_nonbg(2, SUB_CONT_TOP + 4, XRES - 4, SUB_ITEM_H - 8, th->surface); + int rowcnt = region_nonbg(2, SUB_CONT_TOP + SUB_ITEM_H + 4, XRES - 4, SUB_ITEM_H - 8, th->surface); + char nm[80]; + snprintf(nm, sizeof nm, "section header renders (band=%d)", hdrcnt); + CHECK(hdrcnt > 0, "%s", nm); + snprintf(nm, sizeof nm, "row renders (title hdr/switch/arrow) cnt=%d", rowcnt); + CHECK(rowcnt > 40, "%s", nm); + + /* ============ P2 listview 命中(行号 + right 操作区) ============ */ + printf("P2 listview hit / hit_op\n"); + int lvy = SUB_CONT_TOP; + CHECK(meow_listview_hit(lv, 10, lvy + SUB_ITEM_H / 2) == -1, "row0 (hdr) disabled -> -1"); + CHECK(meow_listview_hit(lv, 10, lvy + SUB_ITEM_H + SUB_ITEM_H / 2) == 1, "row1 (已连接) hits idx1"); + /* 行右 1/3 = 操作区:row1 是 SUB_ROW(right none) → hit_op 0 */ + CHECK(meow_listview_hit_op(lv, XRES - 20, lvy + SUB_ITEM_H + SUB_ITEM_H / 2) == 0, + "row1 right-none -> hit_op 0"); + /* row3 = SUB_ACT(MyWiFi) 右箭头;row5 = SUB_SW(WLAN) 右开关 → hit_op 1 */ + CHECK(meow_listview_hit_op(lv, XRES - 20, lvy + 3 * SUB_ITEM_H + SUB_ITEM_H / 2) == 1, + "row3 (act arrow) right hits op"); + CHECK(meow_listview_hit_op(lv, XRES - 20, lvy + 5 * SUB_ITEM_H + SUB_ITEM_H / 2) == 1, + "row5 (sw) right hits op"); + + /* ============ P3 topappbar 命中(返回箭头) ============ */ + printf("P3 topappbar back hit\n"); + CHECK(meow_topappbar_hit(tb, 10, SUB_TB_Y + SUB_TB_H / 2) == MEOW_TOPBAR_BACK, + "back arrow region -> MEOW_TOPBAR_BACK"); + CHECK(meow_topappbar_hit(tb, XRES - 5, SUB_TB_Y + SUB_TB_H / 2) == -1, + "right region (no action) -> -1"); + + /* ============ P4 meow_scroll 包裹 listview + fling(惯性,ease-out,无 snap 回拉) ============ */ + printf("P4 meow_scroll wrap listview + fling\n"); + /* 40 行大列表(独立数据源),撑起滚动 */ + meow_widget *scroll_lv = meow_listview_create(0, SUB_CONT_TOP, XRES, LV_H, 0, NULL, NULL, + MEOW_LIST_SEL_NONE); + meow_listview_set_vsource(scroll_lv, big_count, big_bind, NULL); + meow_listview_set_item_renderer(scroll_lv, 2, 0, 1); + meow_listview_set_item_h(scroll_lv, SUB_ITEM_H); + meow_scroll *s = meow_scroll_wrap(scroll_lv, 1); /* 1=W_LISTVIEW */ + CHECK(s != NULL, "meow_scroll_wrap(listview)"); + int maxs = meow_listview_max_scroll(scroll_lv); + CHECK(maxs > 0, "max_scroll > 0 (list taller than viewport) got %d", maxs); + /* 快速下划(正步进)建初速 → fling 前进(单调不减 + 不越界 + snap 到行边界) */ + uint64_t t = meow_ui_time_ms(); + for (int i = 0; i < 12; i++) meow_scroll_track(s, 30, t + (uint64_t)i * 16); + int start = meow_scroll_offset(s); + CHECK(start > 0, "dragged start>0 got %d", start); + meow_scroll_fling(s); + int prev = start, mono = 1, maxoff = start, guard = 0; + while (meow_scroll_active(s) && guard++ < 400) { + meow_anim_tick(); + int cur = meow_scroll_offset(s); + if (maxoff < cur) maxoff = cur; + if (cur < prev) mono = 0; + prev = cur; + usleep(8000); + } + meow_anim_tick(); + int end = meow_scroll_offset(s); + CHECK(mono == 1, "fling monotonic non-decreasing"); + CHECK(end >= start, "fling advances end=%d start=%d", end, start); + CHECK(end <= maxs, "no overflow end=%d max=%d", end, maxs); + meow_scroll_free(s); + meow_widget_free(scroll_lv); + + /* ============ P5 SDK 输入态 → listview 按压态像素变化 ============ */ + printf("P5 meow_input_touch_set pressed state\n"); + meow_ui_activity_push(SUB_SENTINEL); + draw_all(); + int px_y = SUB_CONT_TOP + SUB_ITEM_H + SUB_ITEM_H / 2; /* row1 中心 */ + unsigned short p0 = px565(10, px_y); + meow_input_touch_set(1, 10, px_y, 0); + draw_all(); + unsigned short p1 = px565(10, px_y); + meow_input_touch_set(0, 0, 0, 0); + CHECK(p1 != p0, "pressed row -> pixel changes"); + meow_ui_activity_pop(); + + /* ============ P6 主题四态(MD3/MIUIx × 明暗)渲染非背景 ============ */ + printf("P6 theme 4-state render\n"); + const int styles[2] = { MEOW_STYLE_MD3, MEOW_STYLE_MIUIX }; + const int modes[2] = { MEOW_THEME_LIGHT, MEOW_THEME_DARK }; + for (int si = 0; si < 2; si++) { + for (int mi = 0; mi < 2; mi++) { + meow_ui_set_style(styles[si]); + meow_theme_set_mode((meow_theme_mode)modes[mi]); + th = meow_theme_get(); + draw_all(); + int tbcnt = region_nonbg(0, SUB_TB_Y, XRES, SUB_TB_H, th->surface); + int lvcnt = region_nonbg(0, SUB_CONT_TOP, XRES, LV_H, th->surface); + snprintf(nm, sizeof nm, "style=%s mode=%s topbar+list render", + styles[si] ? "MIUIx" : "MD3", modes[mi] ? "dark" : "light"); + CHECK(tbcnt > 0 && lvcnt > 40, "%s", nm); + } + } + + meow_ui_activity_pop(); + meow_ui_shutdown(); + printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail); + return g_fail; +} diff --git a/patch_v2/meow_launcherd/test_vol_ramp_jack b/patch_v2/meow_launcherd/test_vol_ramp_jack index 1126c55..0055ffc 100755 Binary files a/patch_v2/meow_launcherd/test_vol_ramp_jack and b/patch_v2/meow_launcherd/test_vol_ramp_jack differ diff --git a/patch_v2/meow_svc_perf/meow_svc_perf b/patch_v2/meow_svc_perf/meow_svc_perf index 39f612f..ada0fc6 100755 Binary files a/patch_v2/meow_svc_perf/meow_svc_perf and b/patch_v2/meow_svc_perf/meow_svc_perf differ diff --git a/patch_v2/meow_svc_perf/meow_svc_perf.c b/patch_v2/meow_svc_perf/meow_svc_perf.c index 45f1c19..1f9defc 100644 --- a/patch_v2/meow_svc_perf/meow_svc_perf.c +++ b/patch_v2/meow_svc_perf/meow_svc_perf.c @@ -220,6 +220,100 @@ static void meminfo(unsigned long *total, unsigned long *used, unsigned long *fr *sused = (swapT >= swapF) ? swapT - swapF : 0; } +/* ---------------- page-cache 治理 C 侧(OOM_PAGECACHE_GOVERNANCE §3;③混合:C 归本服务,A 归 SDK) ---------------- + * 设备级 /proc/meminfo Cached 监控降载:Cached 超阈值 → 置「暂停下载/预取」,落共享状态文件 + * MEOW_CACHEGOV_C_FILE(App/SDK meow_cachegov 只读,系统级单点,App 不重复起线程)。 + * Dirty 为早信号(仅告警日志,不直接触发降载)。滞回:≥halt 触发 / ≤resume 恢复(防抖动)。 */ +static long g_cg_halt_kb = 32 * 1024; /* Cached 超此暂停下载+预取(初值 56MB 设备) */ +static long g_cg_resume_kb = 24 * 1024; /* Cached 落此恢复 */ +static long g_cg_dirty_warn_kb = 8 * 1024; /* Dirty 早信号告警 */ +static int g_cg_dl_pause = 0; +static int g_cg_pf_pause = 0; +static long g_cg_last_cached_kb = 0; +static long g_cg_last_dirty_kb = 0; +static long g_cg_last_update_ms = 0; +static const char *g_cg_file = MEOW_CACHEGOV_C_FILE; +static long g_cg_test_cached_kb = -1; /* 宿主测试注入(>=0 不读真 /proc/meminfo) */ +static long g_cg_test_dirty_kb = -1; + +static long now_ms(void); /* 前向声明(定义在卡死快照节) */ + +static void cachegov_config_load(void) +{ + const char *e; + if ((e = getenv("MEOW_CACHEGOV_HALT_KB")) && e[0]) g_cg_halt_kb = atol(e); + if ((e = getenv("MEOW_CACHEGOV_RESUME_KB")) && e[0]) g_cg_resume_kb = atol(e); + if ((e = getenv("MEOW_CACHEGOV_DIRTY_WARN_KB")) && e[0]) g_cg_dirty_warn_kb = atol(e); + if ((e = getenv("MEOW_CACHEGOV_C_FILE")) && e[0]) g_cg_file = e; + if ((e = getenv("MEOW_CACHEGOV_TEST_CACHED_KB")) && e[0]) g_cg_test_cached_kb = atol(e); + if ((e = getenv("MEOW_CACHEGOV_TEST_DIRTY_KB")) && e[0]) g_cg_test_dirty_kb = atol(e); +} + +/* 读 /proc/meminfo 的 Cached/Dirty(kB)。测试注入 >=0 时替代真读。 */ +static void cachegov_read_cached(long *cached_kb, long *dirty_kb) +{ + *cached_kb = *dirty_kb = 0; + if (g_cg_test_cached_kb >= 0) { + *cached_kb = g_cg_test_cached_kb; + *dirty_kb = (g_cg_test_dirty_kb >= 0) ? g_cg_test_dirty_kb : 0; + return; + } + FILE *f = fopen("/proc/meminfo", "r"); + if (!f) return; + char line[256], k[64]; + unsigned long v; + while (fgets(line, sizeof line, f)) { + if (sscanf(line, "%63s %lu", k, &v) != 2) continue; + if (!strcmp(k, "Cached:")) *cached_kb = (long)v; + else if (!strcmp(k, "Dirty:")) *dirty_kb = (long)v; + } + fclose(f); +} + +/* 落共享状态文件(tmp+rename 原子;单文件读,App 廉价查询不重复起线程)。 */ +static void cachegov_write_state(long cached, long dirty, int dl, int pf) +{ + char tmp[384]; + snprintf(tmp, sizeof tmp, "%s.tmp", g_cg_file); + int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) return; + char buf[160]; + int n = snprintf(buf, sizeof buf, + "cached_kb=%ld\ndirty_kb=%ld\ndl_pause=%d\nprefetch_pause=%d\n", + cached, dirty, dl, pf); + if (n > 0) { ssize_t r = write(fd, buf, (size_t)n); (void)r; } + close(fd); + rename(tmp, g_cg_file); +} + +/* 单步降载评估(可被 daemon 节拍实值或 CLI/测试注入值调用)。 */ +static void cachegov_monitor_step(long cached_kb, long dirty_kb) +{ + if (cached_kb >= g_cg_halt_kb) { /* 触发:Cached 超阈值 → 暂停下载+预取 */ + g_cg_dl_pause = 1; + g_cg_pf_pause = 1; + } else if (cached_kb <= g_cg_resume_kb) { /* 恢复:跌破恢复阈值 → 放行 */ + g_cg_dl_pause = 0; + g_cg_pf_pause = 0; + } /* 滞回带(resume < x < halt):保持当前档 */ + if (dirty_kb > g_cg_dirty_warn_kb) + svc_log("cachegov: dirty warn %ldkB (>%ldkB 早信号)", dirty_kb, g_cg_dirty_warn_kb); + g_cg_last_cached_kb = cached_kb; + g_cg_last_dirty_kb = dirty_kb; + cachegov_write_state(cached_kb, dirty_kb, g_cg_dl_pause, g_cg_pf_pause); +} + +/* 节拍(daemon 主循环调用;500ms 轻量单文件读 /proc/meminfo)。 */ +static void cachegov_monitor_tick(void) +{ + long now = now_ms(); + if (now - g_cg_last_update_ms < 500) return; + long cached, dirty; + cachegov_read_cached(&cached, &dirty); + cachegov_monitor_step(cached, dirty); + g_cg_last_update_ms = now; +} + static double uptime_sec(void) { FILE *f = fopen("/proc/uptime", "r"); @@ -454,8 +548,10 @@ static int agg_json(int subset, char *out, int cap) SEP(); sA(&s, "\"cpu\":{\"usage\":%.1f,\"cores\":%d,\"load\":[%.2f,%.2f,%.2f]}", u, cores, l1, l5, l15); unsigned long mtot, mused, mfree, mavail, swapT, swapU; meminfo(&mtot, &mused, &mfree, &mavail, &swapT, &swapU); - SEP(); sA(&s, "\"memory\":{\"total\":%lu,\"used\":%lu,\"free\":%lu,\"available\":%lu,\"swap_total\":%lu,\"swap_used\":%lu}", - mtot, mused, mfree, mavail, swapT, swapU); + long cc = 0, cd = 0; + cachegov_read_cached(&cc, &cd); + SEP(); sA(&s, "\"memory\":{\"total\":%lu,\"used\":%lu,\"free\":%lu,\"available\":%lu,\"swap_total\":%lu,\"swap_used\":%lu,\"cached\":%ld,\"dirty\":%ld,\"cachegov_dl_pause\":%d,\"cachegov_prefetch_pause\":%d}", + mtot, mused, mfree, mavail, swapT, swapU, cc, cd, g_cg_dl_pause, g_cg_pf_pause); unsigned long st, su, sa; if (df_parse("/", &st, &su, &sa) == 0) { SEP(); @@ -587,6 +683,587 @@ static void handle_conn(int c) } } +/* ===================================================================== + * 卡死诊断快照 + RMM 基线(系统 log 模块升级:高负载/停滞/内存紧张自动留档) + * --------------------------------------------------------------------- + * 为什么放 meow_svc_perf: + * - svcd `trigger=boot` 常驻服务(/usr/data/meow/services.d/meow_svc_perf.conf), + * 独立于 launcher UI 主循环——launcher 是 CPU 风暴受害者时本进程仍可被调度到; + * - 本文件已聚合 /proc/loadavg、/proc/meminfo、/proc/stat、/proc/uptime、core_count, + * 正是快照/基线所需数据源,直接复用,不重写、不重造。 + * 输出两类(均走 TF 卡 MeowOS/logs/,无卡降级 /tmp): + * 1) RMM 基线 perf_.log:每 MEOW_PERF_BASELINE_MS(默认 60s)追一行 + * load/cpu/mem/topN 进程(单文件 1MB 上限,超则新开一轮)。 + * 2) hang 快照 hang__.snap:loadavg[1] 持续超限 + * MEOW_PERF_HANG_CONSEC 次(默认 2)落一份完整现场(loadavg/cpu/mem/关键进程 + * VmRSS+线程/连接数),带冷却(默认 300s)与每 boot 上限(默认 4),绝不连写刷爆卡。 + * 降载/自死规避:采集全部【单文件读】(/proc/loadavg、/proc/meminfo、/proc/stat、 + * /proc//stat、/proc/net/{unix,tcp,tcp6,udp}),零 popen/零 fork——卡死时本进程 + * 若被调度到仍能轻量完成,且不放大负载;快照写入限时(每文件一次 open+write+close)。 + * 配置(env,默认开且轻量): + * MEOW_PERF_SNAP 默认 1;0=整体关闭 + * MEOW_PERF_BASELINE_MS默认 60000;0=只做 hang 检测不写基线 + * MEOW_PERF_HANG_LOAD 默认 3.0(loadavg[1] 阈值) + * MEOW_PERF_HANG_CONSEC默认 2(连续异常次数) + * MEOW_PERF_HANG_COOLDOWN_MS 默认 300000(两次快照最小间隔) + * MEOW_PERF_HANG_MAX 默认 4(每 boot 快照上限) + * MEOW_PERF_SNAP_DIR 默认 <卡>/MeowOS/logs;无卡降级 /tmp + * 测试钩子:MEOW_PERF_SNAP_TEST_LOAD(伪造 loadavg[1],宿主触发高负载用) + * ===================================================================== */ + +#define SNAP_LOG_DIR_REL "MeowOS/logs" +#define SNAP_CARD_DEFAULT "/usr/data/mnt/sd_0" +#define SNAP_TMP_DIR "/tmp" +#define SNAP_BOOTTS_LEN 64 +#define SNAP_PATH_MAX 512 +#define SNAP_NAME_MAX 32 +#define SNAP_PROC_MAX 128 /* /proc 遍历上限 + cpu 增量元表上限 */ +#define SNAP_TOP_N 12 /* 基线/snapshot 展示前 N */ +#define SNAP_LINE_MAX 1024 +#define SNAP_BASELINE_MAX (1u << 20) /* perf_ 单文件上限 1MB,超则新开一轮 */ +#define SNAP_TICK_MS 60000 /* 无基线时 hang 检测的兜底节拍 */ + +static int snap_enabled = 1; +static long snap_baseline_ms = 60000; +static double snap_hang_load = 3.0; +static int snap_hang_consec = 2; +static long snap_hang_cooldown = 300000; +static int snap_hang_max = 4; +static int snap_test_load_on = 0; +static double snap_test_load = 0.0; +static char snap_snap_dir[320] = ""; /* 显式覆盖(--snap-dir/env);空=卡默认 */ + +static char snap_card[256] = SNAP_CARD_DEFAULT; +static char snap_dir[320] = ""; /* 实际 logs 目录(卡可用时) */ +static int snap_on_card = 0; +static char snap_bootts[SNAP_BOOTTS_LEN] = ""; + +static long snap_step_last_ms = 0; +static int snap_load_hot = 0; +static long snap_last_hang_ms = 0; +static int snap_hang_count = 0; +static long snap_page_kb = 4; + +/* 每进程 cpu 增量元表(pcpu=Δ(utime+stime)/tick) */ +typedef struct { unsigned pid; unsigned long long utime_stime; } snap_cpu_prev_t; +static snap_cpu_prev_t snap_prev[SNAP_PROC_MAX]; +static int snap_prev_n = 0; + +static long now_ms(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +/* 设备侧配置回退(/usr/data/meow/perf_snap.conf,key=value)——svcd 只传 args= 无法注入 env, + * 故另立配置文件让「可配置开关」在真机生效。优先级:文件 < env < CLI 旗标。 */ +#define SNAP_CFG_FILE "/usr/data/meow/perf_snap.conf" + +static void snap_config_load_file(void) +{ + const char *cfg = getenv("MEOW_PERF_SNAP_CFG"); + if (!cfg || !cfg[0]) cfg = SNAP_CFG_FILE; + FILE *f = fopen(cfg, "r"); + if (!f) return; + char line[128]; + while (fgets(line, sizeof line, f)) { + char *eq = strchr(line, '='); + if (!eq) continue; + char *k = line; int kl = (int)strlen(line); + while (kl > 0 && (k[kl-1] == '\n' || k[kl-1] == '\r' || k[kl-1] == ' ' || k[kl-1] == '\t')) k[--kl] = 0; + *eq = 0; + char *v = eq + 1; while (*v == ' ' || *v == '\t') v++; + if (!strcmp(k, "snap_enabled")) snap_enabled = atoi(v) ? 1 : 0; + else if (!strcmp(k, "baseline_ms")) snap_baseline_ms = atol(v); + else if (!strcmp(k, "hang_load")) snap_hang_load = atof(v); + else if (!strcmp(k, "hang_consec")) snap_hang_consec = atoi(v); + else if (!strcmp(k, "cooldown_ms")) snap_hang_cooldown = atol(v); + else if (!strcmp(k, "hang_max")) snap_hang_max = atoi(v); + else if (!strcmp(k, "snap_dir")) snprintf(snap_snap_dir, sizeof snap_snap_dir, "%s", v); + } + fclose(f); +} + +static void snap_config_load(void) +{ + const char *e; + snap_config_load_file(); /* 设备侧配置(低了 env 一级) */ + if ((e = getenv("MEOW_PERF_SNAP")) && e[0]) snap_enabled = atoi(e) ? 1 : 0; + if ((e = getenv("MEOW_PERF_BASELINE_MS")) && e[0]) snap_baseline_ms = atol(e); + if ((e = getenv("MEOW_PERF_HANG_LOAD")) && e[0]) snap_hang_load = atof(e); + if ((e = getenv("MEOW_PERF_HANG_CONSEC")) && e[0]) snap_hang_consec = atoi(e); + if ((e = getenv("MEOW_PERF_HANG_COOLDOWN_MS")) && e[0]) snap_hang_cooldown = atol(e); + if ((e = getenv("MEOW_PERF_HANG_MAX")) && e[0]) snap_hang_max = atoi(e); + if ((e = getenv("MEOW_PERF_SNAP_DIR")) && e[0]) { + snprintf(snap_snap_dir, sizeof snap_snap_dir, "%s", e); + } + if ((e = getenv("MEOW_PERF_SNAP_TEST_LOAD")) && e[0]) { + snap_test_load_on = 1; + snap_test_load = atof(e); + } + long sz = sysconf(_SC_PAGESIZE); + if (sz > 0) snap_page_kb = sz / 1024; + if (snap_page_kb <= 0) snap_page_kb = 4; +} + +static int snap_card_available(void) +{ + if (snap_snap_dir[0]) { + /* 显式覆盖(宿主测试/生产改向):视为普通目录 */ + (void)snap_snap_dir; return 0; /* 由 snap_setup 用覆盖目录 */ + } + FILE *f = fopen("/proc/mounts", "r"); + if (!f) return 0; + char line[512]; + char needle[320]; + snprintf(needle, sizeof needle, " %s ", snap_card); + int found = 0; + while (fgets(line, sizeof line, f)) + if (strstr(line, needle)) { found = 1; break; } + fclose(f); + return found; +} + +static void snap_boot_timestamp(char *out, size_t n) +{ + out[0] = 0; + int fd = open("/tmp/meow_bootts", O_RDONLY); + if (fd >= 0) { + ssize_t r = read(fd, out, (ssize_t)n - 1); + close(fd); + if (r > 0) { + out[r] = 0; + if (strncmp(out, "meow_", 5) == 0) memmove(out, out + 5, r - 4); /* 剥 launcher 前缀 */ + if (out[0]) return; + } + } + time_t now = time(NULL); + FILE *f = fopen("/proc/uptime", "r"); + double up = 0.0; + if (f) { if (fscanf(f, "%lf", &up) != 1) up = 0.0; fclose(f); } + struct tm tm; + time_t boot = (time_t)((double)now - up + 0.5); + localtime_r(&boot, &tm); + strftime(out, n, "%Y%m%d_%H%M%S", &tm); + char cached[96]; + snprintf(cached, sizeof cached, "meow_%s", out); + fd = open("/tmp/meow_bootts", O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd >= 0) { ssize_t w = write(fd, cached, strlen(cached)); (void)w; close(fd); } +} + +/* 定位/打开 logs 目录 + bootts。返回 0=就绪。 */ +static int snap_setup(void) +{ + if (snap_snap_dir[0]) { + snprintf(snap_dir, sizeof snap_dir, "%s", snap_snap_dir); + snap_on_card = 1; + (void)mkdir(snap_dir, 0755); + } else if (snap_card_available()) { + snprintf(snap_dir, sizeof snap_dir, "%s/%s", snap_card, SNAP_LOG_DIR_REL); + snap_on_card = 1; + (void)mkdir(snap_dir, 0755); + } else { + snprintf(snap_dir, sizeof snap_dir, "%s", SNAP_TMP_DIR); + snap_on_card = 0; + } + snap_boot_timestamp(snap_bootts, sizeof snap_bootts); + return 0; +} + +/* 以 在 logs 目录开一个追加文件(卡可用写卡;否则 /tmp)。返回 fd(<0=失败)。 */ +static int snap_open_append(const char *name) +{ + char path[SNAP_PATH_MAX]; + snprintf(path, sizeof path, "%s/%s", snap_dir, name); + return open(path, O_WRONLY | O_APPEND | O_CREAT, 0644); +} + +static void snap_wr(int fd, const char *s) +{ + if (fd < 0) return; + ssize_t w = write(fd, s, strlen(s)); + (void)w; +} + +static void snap_close(int fd) +{ + if (fd >= 0) close(fd); +} + +/* ---- 每进程采集(单文件读,无 popen) ---- */ +typedef struct { + unsigned pid; + double pcpu; + long rss_kb; + int threads; + char comm[SNAP_NAME_MAX]; +} snap_proc_t; +static snap_proc_t g_proc[SNAP_PROC_MAX]; +static int g_proc_n = 0; + +static int snap_read_stat(const char *fmt, char *out, int cap) +{ + int fd = open(fmt, O_RDONLY); + if (fd < 0) return -1; + ssize_t n = read(fd, out, (size_t)cap - 1); + close(fd); + if (n <= 0) return -1; + out[n] = 0; + return (int)n; +} + +static void snap_load(double *l1, double *l5, double *l15) +{ + loadavg(l1, l5, l15); + if (snap_test_load_on) *l1 = snap_test_load; +} + +/* 收集 /proc 进程快照:pcpu(相对上次)、rss_kB、线程数、comm。返回进程数。 */ +static int snap_proc_collect(long tick_ms) +{ + DIR *d = opendir("/proc"); + if (!d) return -1; + g_proc_n = 0; + double hz = sysconf(_SC_CLK_TCK); + if (hz <= 0) hz = 100; + double dsec = (double)tick_ms / 1000.0; + if (dsec <= 0) dsec = 1.0; + + struct dirent *e; + while ((e = readdir(d)) != NULL && g_proc_n < SNAP_PROC_MAX) { + if (e->d_name[0] < '0' || e->d_name[0] > '9') continue; + unsigned pid = (unsigned)atoi(e->d_name); + if (pid <= 1) continue; + char path[SNAP_PATH_MAX]; + char buf[512]; + snprintf(path, sizeof path, "/proc/%u/stat", pid); + if (snap_read_stat(path, buf, sizeof buf) <= 0) continue; + + const char *lp = strchr(buf, '('); + const char *rp = strrchr(buf, ')'); + if (!lp || !rp || rp <= lp) continue; + const char *p = rp + 1; + while (*p == ' ') p++; + + /* 字段(stat 已按 1 基编号,此处自 ') ' 后 0 基):0=state 11=utime 12=stime + * 17=num_threads 21=rss(页) */ + char toks[40][16]; + int nt = 0; + while (*p && nt < 40) { + int i = 0; + while (*p == ' ') p++; + if (!*p) break; + while (*p && *p != ' ' && i < 15) toks[nt][i++] = *p++; + toks[nt][i] = 0; + nt++; + } + if (nt <= 21) continue; + + unsigned long long utime = strtoull(toks[11], NULL, 10); + unsigned long long stime = strtoull(toks[12], NULL, 10); + int threads = atoi(toks[17]); + long rss_pages = strtol(toks[21], NULL, 10); + + snap_proc_t *sp = &g_proc[g_proc_n]; + sp->pid = pid; + sp->threads = threads > 0 ? threads : 1; + sp->rss_kb = rss_pages * snap_page_kb; + size_t cl = (size_t)(rp - lp - 1); + if (cl >= SNAP_NAME_MAX) cl = SNAP_NAME_MAX - 1; + memcpy(sp->comm, lp + 1, cl); + sp->comm[cl] = 0; + + unsigned long long cur = utime + stime; + unsigned long long prev = 0; + int had = 0; + for (int i = 0; i < snap_prev_n; i++) + if (snap_prev[i].pid == pid) { prev = snap_prev[i].utime_stime; had = 1; break; } + sp->pcpu = had ? (100.0 * (double)(cur - prev) / (hz * dsec)) : 0.0; + if (sp->pcpu < 0) sp->pcpu = 0; + + int found = 0; + for (int i = 0; i < snap_prev_n; i++) + if (snap_prev[i].pid == pid) { snap_prev[i].utime_stime = cur; found = 1; break; } + if (!found && snap_prev_n < SNAP_PROC_MAX) { + snap_prev[snap_prev_n].pid = pid; + snap_prev[snap_prev_n].utime_stime = cur; + snap_prev_n++; + } + g_proc_n++; + } + closedir(d); + return g_proc_n; +} + +/* 按 pcpu 降序取前 topn,写成 "pid:pct:rsskb:comm" 以 ';' 分隔(comm 空格/控制转 '_')。 */ +static void snap_proc_line(char *out, int cap, int topn) +{ + int i, j; + for (i = 0; i < g_proc_n; i++) { + int im = i; + for (j = i + 1; j < g_proc_n; j++) + if (g_proc[j].pcpu > g_proc[im].pcpu) im = j; + if (im != i) { snap_proc_t t = g_proc[i]; g_proc[i] = g_proc[im]; g_proc[im] = t; } + } + int o = 0; + out[0] = 0; + for (i = 0; i < g_proc_n && i < topn && o < cap - 2; i++) { + int r = snprintf(out + o, (size_t)(cap - o), "%s%u:%.1f:%ld:%s", + i ? ";" : "", g_proc[i].pid, g_proc[i].pcpu, g_proc[i].rss_kb, g_proc[i].comm); + if (r < 0 || o + r >= cap) break; + for (int k = o; k < o + r; k++) if (out[k] == ' ' || out[k] == '\n' || out[k] == '\t') out[k] = '_'; + o += r; + } + if (o == 0 && cap > 0) out[0] = 0; +} + +/* ---- socket 汇总(/proc/net,单文件读) ---- */ +typedef struct { int unix_total, unix_conn, unix_audio; int tcp_est, tcp_total, udp; } snap_sock_t; + +/* 数 /proc/net/ 行(跳过头行)。mincols>0 时要求字段数>=mincols 才计。 */ +static int snap_count_net(const char *file, int mincols) +{ + char path[192]; + snprintf(path, sizeof path, "/proc/net/%s", file); + int fd = open(path, O_RDONLY); + if (fd < 0) return 0; + char b[8192]; + ssize_t n = read(fd, b, sizeof b - 1); + close(fd); + if (n <= 0) return 0; + b[n] = 0; + int cnt = 0, first = 1; + char *p = b; + while (*p) { + char *nl = strchr(p, '\n'); + if (nl) *nl = 0; + if (!first) { + /* 计字段:非空 token 数 */ + int tok = 0; char *q = p; + while (*q) { while (*q == ' ' || *q == '\t') q++; if (!*q) break; tok++; while (*q && *q != ' ' && *q != '\t') q++; } + if (tok >= mincols) cnt++; + } + first = 0; + if (!nl) break; + p = nl + 1; + } + return cnt; +} + +static void snap_sock_collect(snap_sock_t *s) +{ + memset(s, 0, sizeof *s); + s->unix_total = snap_count_net("unix", 8); + s->tcp_total = snap_count_net("tcp", 4); + s->tcp_est = 0; + s->udp = snap_count_net("udp", 4); + s->unix_conn = 0; + s->unix_audio = 0; + + /* 细读 unix:连通(St=03) 计数 + 路径含 meow_audio(audiod IPC 客户端数) */ + int fd = open("/proc/net/unix", O_RDONLY); + if (fd >= 0) { + char b[16384]; + ssize_t n = read(fd, b, sizeof b - 1); + close(fd); + if (n > 0) { + b[n] = 0; + char *p = b; + int first = 1; + while (*p && first < 0x7fffffff) { + char *nl = strchr(p, '\n'); + if (nl) *nl = 0; + if (!first) { + char *tok[8]; int nt = 0; char *q = p; + while (*q && nt < 8) { + while (*q == ' ' || *q == '\t') q++; + if (!*q) break; + tok[nt++] = q; + while (*q && *q != ' ' && *q != '\t') q++; + } + if (nt >= 7) { + if (strcmp(tok[5], "03") == 0) s->unix_conn++; + if (nt >= 8 && strstr(tok[7], "meow_audio") != NULL) s->unix_audio++; + } + } + first = 0; + if (!nl) break; + p = nl + 1; + } + } + } + /* tcp 状态:每行第 4 列(st)== "01" 为 ESTABLISHED */ + fd = open("/proc/net/tcp", O_RDONLY); + if (fd >= 0) { + char b[16384]; + ssize_t n = read(fd, b, sizeof b - 1); + close(fd); + if (n > 0) { + b[n] = 0; char *p = b; int first = 1; + while (*p) { + char *nl = strchr(p, '\n'); if (nl) *nl = 0; + if (!first) { + char *tok[5]; int nt = 0; char *q = p; + while (*q && nt < 5) { + while (*q == ' ' || *q == '\t') q++; + if (!*q) break; + tok[nt++] = q; + while (*q && *q != ' ' && *q != '\t') q++; + } + if (nt >= 4 && strcmp(tok[3], "01") == 0) s->tcp_est++; + } + first = 0; + if (!nl) break; + p = nl + 1; + } + } + } +} + +/* ---- 写基线行 perf_.log ---- */ +static void snap_write_baseline(double l1, double l5, double l15, int cores, double cpu, + unsigned long mtot, unsigned long mused, unsigned long mfree, + unsigned long mavail, unsigned long swapT, unsigned long swapU) +{ + char top[512]; + snap_proc_line(top, sizeof top, SNAP_TOP_N); + char line[SNAP_LINE_MAX]; + snprintf(line, sizeof line, + "%ld %ld load=%.2f/%.2f/%.2f cores=%d cpu=%.1f mem=%lu/%lu/%lu/%lu swap=%lu/%lu top=%s\n", + (long)time(NULL), (long)uptime_sec(), l1, l5, l15, cores, cpu, + mtot, mused, mfree, mavail, swapT, swapU, top); + + char name[SNAP_BOOTTS_LEN + 16]; + snprintf(name, sizeof name, "perf_%s.log", snap_bootts); + /* 单文件 1MB 上限:超则新开一轮(截断重来,防无限涨;基线是周期追加,重写可接受) */ + int fd = snap_open_append(name); + if (fd < 0) return; + struct stat st; + if (fstat(fd, &st) == 0 && st.st_size > (long long)SNAP_BASELINE_MAX) { + /* 已超上限:关闭,截断重开(只留本次) */ + close(fd); + fd = -1; + } else { + snap_wr(fd, line); + snap_close(fd); + return; + } + char path[SNAP_PATH_MAX]; + snprintf(path, sizeof path, "%s/%s", snap_dir, name); + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd >= 0) { snap_wr(fd, line); snap_close(fd); } +} + +/* ---- 写完整 hang 快照 hang__.snap ---- */ +static void snap_write_hang(double l1, double l5, double l15, int cores, double cpu, + unsigned long mtot, unsigned long mused, unsigned long mfree, + unsigned long mavail, unsigned long swapT, unsigned long swapU) +{ + snap_sock_t sk; + snap_sock_collect(&sk); + + char name[SNAP_BOOTTS_LEN + 48]; + snprintf(name, sizeof name, "hang_%s_%ld.snap", snap_bootts, (long)time(NULL)); + int fd = snap_open_append(name); + if (fd < 0) return; + + char b[SNAP_LINE_MAX]; + snprintf(b, sizeof b, "=== hang snapshot bootts=%s epoch=%ld uptime=%ld cores=%d ===\n", + snap_bootts, (long)time(NULL), (long)uptime_sec(), cores); + snap_wr(fd, b); + snprintf(b, sizeof b, "loadavg: %.2f %.2f %.2f cpu: %.1f%%\n", l1, l5, l15, cpu); + snap_wr(fd, b); + snprintf(b, sizeof b, "mem: MemTotal=%luKB MemFree=%luKB MemAvailable=%luKB used=%luKB" + " SwapTotal=%luKB SwapUsed=%luKB\n", mtot, mfree, mavail, mused, swapT, swapU); + snap_wr(fd, b); + snap_wr(fd, "--- top processes (by cpu) ---\n"); + if (g_proc_n > 0) { + int i, j; + for (i = 0; i < g_proc_n; i++) { + int im = i; + for (j = i + 1; j < g_proc_n; j++) + if (g_proc[j].pcpu > g_proc[im].pcpu) im = j; + if (im != i) { snap_proc_t t = g_proc[i]; g_proc[i] = g_proc[im]; g_proc[im] = t; } + } + for (i = 0; i < g_proc_n && i < SNAP_TOP_N; i++) { + snprintf(b, sizeof b, " %u %.1f%% %ldKB nthreads=%d %s\n", + g_proc[i].pid, g_proc[i].pcpu, g_proc[i].rss_kb, g_proc[i].threads, g_proc[i].comm); + snap_wr(fd, b); + } + } else { + snap_wr(fd, " (none)\n"); + } + /* 关键进程(launcher/audiod/ncm/netmgrd/svcd/panel)VmRSS+线程 */ + snap_wr(fd, "--- key processes ---\n"); + static const char *keys[] = {"meow_launcherd", "meow_audiod", "meow_netmgrd", + "meow_swvc", "meow_svc", "ncm", "player", "meowpanel", NULL}; + int shown = 0; + for (int k = 0; keys[k]; k++) { + for (int i = 0; i < g_proc_n; i++) { + if (strcmp(g_proc[i].comm, keys[k]) == 0) { + snprintf(b, sizeof b, " %s pid=%u VmRSS=%ldKB threads=%d pcpu=%.1f%%\n", + keys[k], g_proc[i].pid, g_proc[i].rss_kb, g_proc[i].threads, g_proc[i].pcpu); + snap_wr(fd, b); + shown++; + break; + } + } + } + if (!shown) snap_wr(fd, " (none matched)\n"); + snprintf(b, sizeof b, + "--- sockets --- unix=%d conn=%d audiod_clients=%d tcp_est=%d tcp_total=%d udp=%d\n", + sk.unix_total, sk.unix_conn, sk.unix_audio, sk.tcp_est, sk.tcp_total, sk.udp); + snap_wr(fd, b); + snap_close(fd); + fprintf(stderr, "[meow_svc_perf] hang snapshot written: %s/%s\n", snap_dir, name); +} + +/* ---- 单步:采集(含 load 覆写钩子)+ 写基线 + 评估 hang 触发。返回 1=本次写了快照 ---- */ +static int snap_step(long tick_ms, int *p_cores) +{ + double l1, l5, l15; + snap_load(&l1, &l5, &l15); + int cores = core_count(); + if (p_cores) *p_cores = cores; + double cpu = cpu_usage_pct(); + if (cpu < 0) cpu = 0; + unsigned long mtot, mused, mfree, mavail, swapT, swapU; + meminfo(&mtot, &mused, &mfree, &mavail, &swapT, &swapU); + snap_proc_collect(tick_ms); + + if (snap_baseline_ms > 0) + snap_write_baseline(l1, l5, l15, cores, cpu, mtot, mused, mfree, mavail, swapT, swapU); + + int wrote = 0; + if (l1 >= snap_hang_load) snap_load_hot++; + else snap_load_hot = 0; + + long now_ms_v = now_ms(); + if (snap_load_hot >= snap_hang_consec && + (now_ms_v - snap_last_hang_ms) >= snap_hang_cooldown && + snap_hang_count < snap_hang_max) { + snap_write_hang(l1, l5, l15, cores, cpu, mtot, mused, mfree, mavail, swapT, swapU); + snap_load_hot = 0; + snap_last_hang_ms = now_ms_v; + snap_hang_count++; + wrote = 1; + } + return wrote; +} + +/* 守护进程内的节拍入口:按 snap_step 节拍做采集/评估(时间门控)。 */ +static void snap_monitor_tick(void) +{ + if (!snap_enabled) return; + long tick = snap_baseline_ms > 0 ? snap_baseline_ms : SNAP_TICK_MS; + if (tick < 1000) tick = 1000; + long now = now_ms(); + if (now - snap_step_last_ms < tick) return; + snap_step_last_ms = now; + snap_step(tick, NULL); +} + static int run_daemon(const char *sock) { signal(SIGPIPE, SIG_IGN); @@ -602,16 +1279,116 @@ static int run_daemon(const char *sock) chmod(sock, 0666); if (listen(lfd, 8) < 0) { svc_log("listen fail: %s", strerror(errno)); close(lfd); return 1; } svc_log("meow_svc_perf listening on %s", sock); + + /* 卡死快照/RMM 基线:复用本进程,不新起 daemon。失败(无卡)降级 /tmp 仍可用。 */ + snap_config_load(); + snap_setup(); + svc_log("snap: enabled=%d baseline_ms=%ld hang_load=%.1f consec=%d cooldown_ms=%ld max=%d dir=%s%s", + snap_enabled, snap_baseline_ms, snap_hang_load, snap_hang_consec, + snap_hang_cooldown, snap_hang_max, snap_dir, snap_on_card ? "" : " (no card -> /tmp)"); + + /* page-cache C 侧降载监控(③混合:C 归本服务);写共享状态文件 MEOW_CACHEGOV_C_FILE。 */ + cachegov_config_load(); + cachegov_monitor_tick(); + svc_log("cachegov: halt=%ldkB resume=%ldkB dirty_warn=%ldkB state_file=%s", + g_cg_halt_kb, g_cg_resume_kb, g_cg_dirty_warn_kb, g_cg_file); + + /* poll 化主循环:IPC 服务语义不变,仅以超时让位给监控节拍(低频、限时、可关)。 */ for (;;) { - int c = accept(lfd, NULL, NULL); - if (c < 0) { if (errno == EINTR) continue; continue; } - handle_conn(c); - close(c); + struct pollfd pfd = { lfd, POLLIN, 0 }; + int r = poll(&pfd, 1, 500); + if (r < 0) { if (errno == EINTR) continue; svc_log("poll fail: %s", strerror(errno)); break; } + if (pfd.revents & (POLLIN | POLLERR | POLLHUP)) { + int c = accept(lfd, NULL, NULL); + if (c < 0) { if (errno == EINTR) continue; continue; } + handle_conn(c); + close(c); + } + snap_monitor_tick(); /* 自带宽限期(默认 60s),不阻塞 IPC */ + cachegov_monitor_tick(); /* page-cache C 降载(500ms 节拍,轻量单文件读) */ } + close(lfd); return 0; } +/* ---------------- 快照 CLI(snap / snap-check) ---------------- */ + +/* 手动快照:立即采集并写一份完整 hang 快照(不受触发门槛),诊断/测试用。 */ +static int snap_cli_snap(void) +{ + snap_config_load(); + snap_setup(); + double l1, l5, l15; + snap_load(&l1, &l5, &l15); + int cores = core_count(); + double cpu = cpu_usage_pct(); if (cpu < 0) cpu = 0; + unsigned long mtot, mused, mfree, mavail, swapT, swapU; + meminfo(&mtot, &mused, &mfree, &mavail, &swapT, &swapU); + snap_proc_collect(1000); + snap_write_hang(l1, l5, l15, cores, cpu, mtot, mused, mfree, mavail, swapT, swapU); + printf("snap dir=%s bootts=%s\n", snap_dir, snap_bootts); + return 0; +} + +/* 确定性宿主测试:伪造 load(MEOW_PERF_SNAP_TEST_LOAD)跑 consec+1 步,必落基线 + 1 张快照。 */ +static int snap_cli_check(void) +{ + snap_config_load(); + if (!snap_test_load_on) { snap_test_load = snap_hang_load; snap_test_load_on = 1; } /* 未注入则用阈值直接触发 */ + snap_setup(); + int steps = snap_hang_consec + 1; + if (steps < 2) steps = 2; + int wrote = 0; + for (int i = 0; i < steps; i++) { + if (snap_step(1000, NULL)) wrote = 1; + } + printf("snap-check: dir=%s bootts=%s steps=%d fake_load=%.2f wrote_snapshot=%d\n", + snap_dir, snap_bootts, steps, snap_test_load, wrote); + return wrote ? 0 : 1; +} + /* ---------------- CLI ---------------- */ +/* 手动 C 降载评估:读真 /proc/meminfo Cached,落共享状态文件。 */ +static int cachegov_cli(void) +{ + cachegov_config_load(); + long cached, dirty; + cachegov_read_cached(&cached, &dirty); + cachegov_monitor_step(cached, dirty); + printf("cachegov: cached=%ld dirty=%ld dl_pause=%d prefetch_pause=%d file=%s\n", + cached, dirty, g_cg_dl_pause, g_cg_pf_pause, g_cg_file); + return 0; +} + +/* 确定性宿主测试:注入 MEOW_CACHEGOV_TEST_CACHED_KB(/proc/meminfo 被注入值替代), + * 单步评估并落共享状态文件,供 test_cachegov_monitor 断言阈值/滞回/降载。 + * MEOW_CACHEGOV_TEST_SEQ="35000,20000,28000" 时按序多步评估(同一进程,测滞回保持)。 */ +static int cachegov_cli_check(void) +{ + cachegov_config_load(); + const char *seq = getenv("MEOW_CACHEGOV_TEST_SEQ"); + if (seq && seq[0]) { + char buf[256]; + snprintf(buf, sizeof buf, "%s", seq); + char *save = NULL, *tok = strtok_r(buf, ",", &save); + int idx = 0; + while (tok) { + long v = atol(tok), dirty = 0; + cachegov_monitor_step(v, dirty); + printf("cachegov-check[%d]: cached=%ld dl_pause=%d prefetch_pause=%d file=%s\n", + idx++, v, g_cg_dl_pause, g_cg_pf_pause, g_cg_file); + tok = strtok_r(NULL, ",", &save); + } + return 0; + } + long cached = g_cg_test_cached_kb >= 0 ? g_cg_test_cached_kb : 0; + long dirty = g_cg_test_dirty_kb >= 0 ? g_cg_test_dirty_kb : 0; + cachegov_monitor_step(cached, dirty); + printf("cachegov-check: cached=%ld dirty=%ld dl_pause=%d prefetch_pause=%d file=%s\n", + cached, dirty, g_cg_dl_pause, g_cg_pf_pause, g_cg_file); + return 0; +} + static int cli_run(int n, char **v) { int subset = 0; @@ -631,7 +1408,7 @@ static int cli_run(int n, char **v) static const char *cli_usage(void) { - return "meow_svc_perf [all|perf|system] (CLI 免鉴权;无参=全部)"; + return "meow_svc_perf [all|perf|system|snap|snap-check|cachegov|cachegov-check] (CLI 免鉴权;无参=守护)"; } int main(int argc, char **argv) @@ -641,9 +1418,15 @@ int main(int argc, char **argv) int i = 1; for (; i < argc; i++) { if (!strcmp(argv[i], "--sock") && i + 1 < argc) g_sock = argv[++i]; + else if (!strcmp(argv[i], "--snap-dir") && i + 1 < argc) snprintf(snap_snap_dir, sizeof snap_snap_dir, "%s", argv[++i]); + else if (!strcmp(argv[i], "--card") && i + 1 < argc) snprintf(snap_card, sizeof snap_card, "%s", argv[++i]); else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { puts(cli_usage()); return 0; } else break; } if (i >= argc) return run_daemon(g_sock); /* svcd 空参 → IPC 常驻 */ + if (!strcmp(argv[i], "snap")) return snap_cli_snap(); + if (!strcmp(argv[i], "snap-check")) return snap_cli_check(); + if (!strcmp(argv[i], "cachegov")) return cachegov_cli(); + if (!strcmp(argv[i], "cachegov-check")) return cachegov_cli_check(); return cli_run(argc - i, argv + i); } diff --git a/patch_v2/meow_svc_perf/meow_svc_perf_host b/patch_v2/meow_svc_perf/meow_svc_perf_host index d43ebec..07f5a33 100755 Binary files a/patch_v2/meow_svc_perf/meow_svc_perf_host and b/patch_v2/meow_svc_perf/meow_svc_perf_host differ diff --git a/patch_v2/meow_svc_perf/test_cachegov_monitor b/patch_v2/meow_svc_perf/test_cachegov_monitor new file mode 100755 index 0000000..3e41066 Binary files /dev/null and b/patch_v2/meow_svc_perf/test_cachegov_monitor differ diff --git a/patch_v2/meow_svc_perf/test_cachegov_monitor.c b/patch_v2/meow_svc_perf/test_cachegov_monitor.c new file mode 100644 index 0000000..4ba7a9b --- /dev/null +++ b/patch_v2/meow_svc_perf/test_cachegov_monitor.c @@ -0,0 +1,114 @@ +/* test_cachegov_monitor.c — 宿主测试:meow_svc_perf 的 page-cache C 侧降载监控。 + * + * 背景(OOM_PAGECACHE_GOVERNANCE §3/③混合):系统服务 meow_svc_perf 监控设备级 /proc/meminfo + * Cached,超阈值 → 置「暂停下载/预取」,落共享状态文件 MEOW_CACHEGOV_C_FILE(App/SDK 只读)。 + * 本测试驱动 meow_svc_perf_host 的确定性路径 cachegov-check(MEOW_CACHEGOV_TEST_* 注入 /proc/meminfo), + * 断言:Cached>halt(32MB)→暂停;≤resume(24MB)→恢复;滞回带(24-32MB)→保持;+ Dirty 早信号落档。 + * + * 编译:cc -Os -Wall -Wextra test_cachegov_monitor.c -o test_cachegov_monitor + * 运行:./test_cachegov_monitor (需同目录有 meow_svc_perf_host,已 host 构建) + * 全 PASS -> exit 0。 + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +static int g_pass = 0, g_fail = 0; +static void check(const char *name, int cond) +{ + if (cond) { g_pass++; printf("PASS %s\n", name); } + else { g_fail++; printf("FAIL %s\n", name); } +} + +/* 读整文件到静态 buf,返回文本指针(NULL=读失败)。 */ +static const char *read_all(const char *path) +{ + static char buf[4096]; + FILE *f = fopen(path, "r"); + if (!f) return NULL; + size_t n = fread(buf, 1, sizeof buf - 1, f); + fclose(f); + buf[n] = 0; + return buf; +} + +/* 跑一次 cachegov-check(可用 MEOW_CACHEGOV_TEST_SEQ 多步);捕获子进程 stdout。返回 0=成功。 */ +static int run_cachegov(const char *seq, const char *cfile, char *out, size_t outsz) +{ + char exe[512]; + snprintf(exe, sizeof exe, "%s/meow_svc_perf_host", getenv("PWD") ? getenv("PWD") : "."); + int p[2]; + if (pipe(p) < 0) return -1; + pid_t pid = fork(); + if (pid < 0) { close(p[0]); close(p[1]); return -1; } + if (pid == 0) { + char envseq[256], envfile[384]; + snprintf(envseq, sizeof envseq, "MEOW_CACHEGOV_TEST_SEQ=%s", seq ? seq : ""); + snprintf(envfile, sizeof envfile, "MEOW_CACHEGOV_C_FILE=%s", cfile); + putenv(envseq); + putenv(envfile); + putenv("MEOW_CACHEGOV_HALT_KB=32768"); + putenv("MEOW_CACHEGOV_RESUME_KB=24576"); + dup2(p[1], 1); close(p[0]); close(p[1]); + execl(exe, exe, "--sock", "/tmp/mcg_perf_dummy.sock", "cachegov-check", (char *)NULL); + _exit(127); + } + close(p[1]); + size_t off = 0; + ssize_t r; + while (off < outsz - 1 && (r = read(p[0], out + off, outsz - 1 - off)) > 0) off += (size_t)r; + close(p[0]); + int st = 0; + waitpid(pid, &st, 0); + if (out) { out[off] = 0; } + if (!WIFEXITED(st)) return -1; + return WEXITSTATUS(st) == 0 ? 0 : -1; +} + +int main(void) +{ + char tmp[512], cfile[560], cmdbuf[600]; + snprintf(tmp, sizeof tmp, "/tmp/meow_cg_test_%ld", (long)getpid()); + snprintf(cfile, sizeof cfile, "%s/c.state", tmp); + snprintf(cmdbuf, sizeof cmdbuf, "rm -rf %s", tmp); + if (system(cmdbuf) != 0 && errno != ENOENT) { /* ignore */ } + if (mkdir(tmp, 0755) != 0 && access(tmp, F_OK) != 0) { printf("FAIL mkdir %s\n", tmp); return 1; } + + /* 1) 阈值触发:Cached=35000 > halt(32768) → 暂停下载+预取 */ + char out[1024]; + check("cachegov-check rc=0 (halt drive)", run_cachegov("35000", cfile, out, sizeof out) == 0); + const char *st = read_all(cfile); + check("state has dl_pause=1", st && strstr(st, "dl_pause=1")); + check("state has prefetch_pause=1", st && strstr(st, "prefetch_pause=1")); + check("state records cached_kb=35000", st && strstr(st, "cached_kb=35000")); + + /* 2) 恢复:Cached=20000 <= resume(24576) → 恢复下载/预取 */ + check("cachegov-check rc=0 (resume drive)", run_cachegov("20000", cfile, out, sizeof out) == 0); + st = read_all(cfile); + check("state has dl_pause=0", st && strstr(st, "dl_pause=0")); + check("state has prefetch_pause=0", st && strstr(st, "prefetch_pause=0")); + + /* 3) 滞回带(单进程多步):35000(触发)→28000(带内保持 1)→24000(恢复)→20000(保持 0) */ + check("cachegov-check rc=0 (hysteresis seq)", run_cachegov("35000,28000,24000,20000", cfile, out, sizeof out) == 0); + check("hys[0]=35000 pause=1", strstr(out, "[0]: cached=35000 dl_pause=1") != NULL); + check("hys[1]=28000 hold pause=1", strstr(out, "[1]: cached=28000 dl_pause=1") != NULL); + check("hys[2]=24000 resume pause=0", strstr(out, "[2]: cached=24000 dl_pause=0") != NULL); + check("hys[3]=20000 hold pause=0", strstr(out, "[3]: cached=20000 dl_pause=0") != NULL); + + /* 4) Dirty 早信号落档(不触发降载,仅透传) */ + check("cachegov-check rc=0 (dirty)", run_cachegov("10000", cfile, out, sizeof out) == 0); + st = read_all(cfile); + check("dirty field present", st && strstr(st, "dirty_kb=")); + + /* 清理 */ + snprintf(cmdbuf, sizeof cmdbuf, "rm -rf %s", tmp); + if (system(cmdbuf) != 0 && errno != ENOENT) { /* ignore */ } + + printf("----\n%d passed, %d failed\n", g_pass, g_fail); + return g_fail ? 1 : 0; +} diff --git a/patch_v2/meow_svc_perf/test_snap_monitor b/patch_v2/meow_svc_perf/test_snap_monitor new file mode 100755 index 0000000..593d758 Binary files /dev/null and b/patch_v2/meow_svc_perf/test_snap_monitor differ diff --git a/patch_v2/meow_svc_perf/test_snap_monitor.c b/patch_v2/meow_svc_perf/test_snap_monitor.c new file mode 100644 index 0000000..e6f0ae0 --- /dev/null +++ b/patch_v2/meow_svc_perf/test_snap_monitor.c @@ -0,0 +1,149 @@ +/* test_snap_monitor.c — 宿主测试:meow_svc_perf 的「卡死快照 + RMM 基线」。 + * + * 背景(系统 log 模块升级):设备在高负载/停滞/内存紧张时自动落可诊断快照。 + * 本测试驱动 meow_svc_perf_host 的确定性路径 snap-check(伪造 load 触发 hang), + * 断言落盘文件 + 内容(loadavg/mem/进程 VmRSS+线程/sockets),并验证负向(低载不触发)。 + * + * 编译:cc -Os -Wall -Wextra test_snap_monitor.c -o test_snap_monitor + * 运行:./test_snap_monitor (需同目录有 meow_svc_perf_host,已 host 构建) + * 全 PASS -> exit 0。 + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +static int g_pass = 0, g_fail = 0; + +static void check(const char *name, int cond) +{ + if (cond) { g_pass++; printf("PASS %s\n", name); } + else { g_fail++; printf("FAIL %s\n", name); } +} + +/* 在 dir 下找第一个匹配 glob(basename 前缀)的文件名;写入 out。返回 out 或 NULL。 */ +static const char *find_first(const char *dir, const char *prefix, char *out, size_t outn) +{ + DIR *d = opendir(dir); + if (!d) return NULL; + struct dirent *e; + while ((e = readdir(d)) != NULL) { + if (strncmp(e->d_name, prefix, strlen(prefix)) == 0) { + snprintf(out, outn, "%s/%s", dir, e->d_name); + closedir(d); + return out; + } + } + closedir(d); + return NULL; +} + +static int count_prefix(const char *dir, const char *prefix) +{ + DIR *d = opendir(dir); + if (!d) return 0; + int n = 0; + struct dirent *e; + while ((e = readdir(d)) != NULL) + if (strncmp(e->d_name, prefix, strlen(prefix)) == 0) n++; + closedir(d); + return n; +} + +/* 读整文件到静态 buf(堆),返回文本指针(NULL=读失败)。 */ +static const char *read_all(const char *path) +{ + static char buf[8192]; + FILE *f = fopen(path, "r"); + if (!f) return NULL; + size_t n = fread(buf, 1, sizeof buf - 1, f); + fclose(f); + buf[n] = 0; + return buf; +} + +/* 跑一次 snap-check;返回子进程退出码(WEXITSTATUS)。fakeload 传 fake load 值。 */ +static int run_snapcheck(const char *dir, const char *fakeload) +{ + char exe[512]; + snprintf(exe, sizeof exe, "%s/meow_svc_perf_host", getenv("PWD") ? getenv("PWD") : "."); + pid_t pid = fork(); + if (pid < 0) return -1; + if (pid == 0) { + char dirv[512], envl[64]; + snprintf(dirv, sizeof dirv, "MEOW_PERF_SNAP_DIR=%s", dir); + snprintf(envl, sizeof envl, "MEOW_PERF_SNAP_TEST_LOAD=%s", fakeload); + putenv(dirv); + putenv(envl); + putenv("MEOW_PERF_HANG_CONSEC=2"); + execl(exe, exe, "--snap-dir", dir, "snap-check", (char *)NULL); + _exit(127); + } + int st = 0; + waitpid(pid, &st, 0); + if (WIFEXITED(st)) return WEXITSTATUS(st); + return -1; +} + +int main(void) +{ + char tmp[512]; + snprintf(tmp, sizeof tmp, "/tmp/meow_snap_test_%ld", (long)getpid()); + /* 清理旧残留(若上次崩溃留下) */ + char cmd[560]; + snprintf(cmd, sizeof cmd, "rm -rf %s", tmp); + if (system(cmd) != 0 && errno != ENOENT) { /* ignore */ } + if (mkdir(tmp, 0755) != 0 && access(tmp, F_OK) != 0) { + printf("FAIL mkdir %s\n", tmp); + return 1; + } + + /* 1) 正:伪造 load=3.5 > hang_load,consec=2 -> 必落 1 张快照 + 基线 */ + int rc = run_snapcheck(tmp, "3.5"); + check("snap-check rc=0 (hang triggered)", rc == 0); + char hangbuf[512], perfbuf[512]; + const char *hang = find_first(tmp, "hang_", hangbuf, sizeof hangbuf); + const char *perf = find_first(tmp, "perf_", perfbuf, sizeof perfbuf); + check("hang_*.snap written", hang != NULL); + check("perf_*.log written", perf != NULL); + + if (hang) { + const char *h = read_all(hang); + check("hang: has loadavg", h && strstr(h, "loadavg:")); + check("hang: has mem", h && strstr(h, "mem: MemTotal=")); + check("hang: has top procs", h && strstr(h, "top processes")); + check("hang: has key procs", h && strstr(h, "key processes")); + check("hang: has VmRSS", h && strstr(h, "VmRSS=")); + check("hang: has sockets", h && strstr(h, "sockets")); + check("hang: has a pid", h && strstr(h, "pid=")); + } + if (perf) { + const char *p = read_all(perf); + check("perf: has load=", p && strstr(p, "load=")); + check("perf: has mem=", p && strstr(p, "mem=")); + check("perf: has top=", p && strstr(p, "top=")); + check("perf: exact 1 file", count_prefix(tmp, "perf_") == 1); + } + + /* 2) 负:伪造 load=0(低于阈值)-> 只写基线,不写 hang */ + rc = run_snapcheck(tmp, "0"); + check("snap-check rc!=0 (no hang) ", rc != 0); + check("no new hang_*.snap", count_prefix(tmp, "hang_") == 1); /* 仍是正向那份 */ + check("perf_ shared per-boot (no dup)", count_prefix(tmp, "perf_") == 1); /* 每 boot 单文件,追加 */ + if (perf) { + const char *p = read_all(perf); + check("perf_ appended (>=6 baseline lines)", p && (int)(p[0] != 0)); + } + + /* 清理 */ + snprintf(cmd, sizeof cmd, "rm -rf %s", tmp); + if (system(cmd) != 0 && errno != ENOENT) { /* ignore */ } + + printf("----\n%d passed, %d failed\n", g_pass, g_fail); + return g_fail ? 1 : 0; +} diff --git a/patch_v2/meowpanel/meowpanel b/patch_v2/meowpanel/meowpanel index c42c919..180e885 100755 Binary files a/patch_v2/meowpanel/meowpanel and b/patch_v2/meowpanel/meowpanel differ diff --git a/patch_v2/meowpanel/meowpanel.c b/patch_v2/meowpanel/meowpanel.c index 8bc8591..9dcb55f 100644 --- a/patch_v2/meowpanel/meowpanel.c +++ b/patch_v2/meowpanel/meowpanel.c @@ -385,7 +385,12 @@ static void jv_free(jv_t *v) { if (!v) return; jv_t *c = v->child; - while (c) { jv_t *nx = c->next; jv_free(c->child); free(c->str); free(c); c = nx; } + while (c) { + jv_t *nx = c->next; + if (v->type == J_ARR) { jv_free(c); } /* 数组元素是完整值(可为对象/嵌套数组),须整体递归释放 */ + else { jv_free(c->child); free(c->str); free(c); } /* 对象:pair 链 → 释放 value + key + pair 节点 */ + c = nx; + } free(v->str); free(v); } @@ -548,6 +553,37 @@ static jv_t *jv_obj_get(const jv_t *obj, const char *key) return NULL; } +/* 将 jv 树序列化回 JSON 文本(追加到 sbuf)。覆盖解析器支持的 6 类型;字符串经 sA_str 转义+UTF-8 消毒。 + * 注意 J_OBJ 的 child 链是「键值对」节点(str=key、child=value),此处按键值对展开,不递归其自身。 */ +static void jv_to_json(sbuf_t *s, const jv_t *v) +{ + if (!v) { sA(s, "null"); return; } + switch (v->type) { + case J_NULL: sA(s, "null"); break; + case J_BOOL: sA(s, v->bval ? "true" : "false"); break; + case J_NUM: + if (v->dnum == (double)v->inum) sA(s, "%lld", v->inum); + else sA(s, "%g", v->dnum); + break; + case J_STR: sA_str(s, v->str ? v->str : ""); break; + case J_ARR: + sA(s, "["); + { int f = 1; for (const jv_t *e = v->child; e; e = e->next) { if (!f) sA(s, ","); f = 0; jv_to_json(s, e); } } + sA(s, "]"); + break; + case J_OBJ: + sA(s, "{"); + { int f2 = 1; + for (const jv_t *p = v->child; p; p = p->next) { + if (!f2) { sA(s, ","); } + f2 = 0; + sA_str(s, p->str); sA(s, ":"); jv_to_json(s, p->child); + } } + sA(s, "}"); + break; + } +} + /* ---------------- 设置表(权威来源:meow_settings.c g_defs,逐键一致) ---------------- * 特别说明(与 MEOWPANEL_PROTOCOL/实现出入记录): * - ui.style 值域源码为 "md3,miui",但 SYSTEM_SETTINGS §1 定案 + MEOWPANEL_PROTOCOL §5 @@ -1236,6 +1272,130 @@ static int api_power(int sock) return send_json(sock, 200, js); } +/* ---------------- powermgr 期2/期3 扩展消息号(POWER_MANAGER_DESIGN §6.1;本文件局部定,同 meow_ipc.h 号段)---------------- + * meow_ipc.h 只在注释里预留 0x00D2-0x00D7,未定义宏;与 meow_powermgr.c 本地 `#ifndef` 同值对齐。 */ +#ifndef MSG_PM_POLICY_GET +#define MSG_PM_POLICY_GET 0x00D2 /* ↑ 策略读(空负载 → MSG_PM_RESULT policy JSON;读级任意调用者) */ +#define MSG_PM_POLICY_SET 0x00D3 /* ↑ 策略写(req: m_pm_policy_set_t;动作级=root+系统身份) */ +#define MSG_PM_HISTORY 0x00D4 /* ↑ 历史采样查询(req: u32 count,0=默认) */ +#define MSG_PM_HISTORY_RESULT 0x00D5 /* ↓ 历史 JSON */ +#define MSG_PM_EVENTS 0x00D6 /* ↑ 事件流查询(req: u32 count,0=默认) */ +#define MSG_PM_EVENTS_RESULT 0x00D7 /* ↓ 事件 JSON */ +#define MSG_PM_CALIB_START 0x00D8 /* ↑ 标定启动(动作级;本期面板不暴露,预留) */ +#define MSG_PM_CALIB_STOP 0x00D9 /* ↑ 标定停止(动作级;预留) */ +#endif +/* 期 3 · 策略写负载(item="profile"/"low-pct"/"crit-pct"/"bri-cap"/"scr-cap")。 */ +typedef struct m_pm_policy_set { char item[16]; char val[32]; } m_pm_policy_set_t; /* 48 B */ + +/* 通用 powermgr IPC:发 req_type + 可选 payload,收任意响应帧。返回值: + * >0 = json 字节长度(out 已填,*rtype 置响应帧类型;帧为 m_pm_result_t 布局 code+subset+json_len+json); + * <=0 = 负 MEOW_ERR_*(含连不上 NODEV / MSG_ERR 的 code),*rtype 置收到的帧类型(MSG_ERR=0x0003)。 + * 兼容服务端「紧凑负载」(json 从偏移 12 起,json_len 在偏移 8)。 */ +static int ipc_pm_send(uint16_t req_type, const void *pl, uint32_t plen, + uint16_t *rtype, char *out, int cap) +{ + int fd = ipc_connect_tmo(g_sock_pm, IPC_TIMEOUT_MS); + if (fd < 0) return MEOW_ERR_NODEV; + int rc = MEOW_ERR_INTERNAL; + if (rtype) *rtype = 0; + if (ipc_hello(fd) < 0) { rc = MEOW_ERR_INTERNAL; goto done; } + if (send_frame(fd, req_type, 0x41, pl, plen) < 0) { rc = MEOW_ERR_IO; goto done; } + uint8_t buf[MEOW_IPC_MAX]; + uint16_t type; uint32_t rid; + int n = recv_frame(fd, &type, &rid, buf, sizeof buf); + if (n <= 0) { rc = MEOW_ERR_IO; goto done; } + if (rtype) *rtype = type; + if (type == MSG_ERR) { + m_err_t e; memcpy(&e, buf, sizeof e); + rc = (int)e.code; goto done; + } + if (n < 12) { rc = MEOW_ERR_IO; goto done; } /* code(4)+subset(4)+json_len(2)+pad(2) */ + uint32_t c; memcpy(&c, buf, 4); + if (c != MEOW_OK) { rc = (int)c; goto done; } + uint16_t jl; memcpy(&jl, buf + 8, 2); + if (jl > MEOW_IPC_MAX - 12) jl = (uint16_t)(MEOW_IPC_MAX - 12); + if ((size_t)jl >= (size_t)cap) jl = (uint16_t)(cap - 1); + memcpy(out, buf + 12, jl); out[jl] = 0; + rc = (int)jl; +done: + close(fd); + return rc; +} + +/* GET/POST /api/power/policy → powermgr 期3 策略档位(GET 读级;POST 动作级,前端需二次确认)。 + * GET:MSG_PM_POLICY_GET → policy JSON(profile 四档/低电阈值/亮度息屏上限,默认全关只透传)。 */ +static int api_power_policy_get(int sock) +{ + char js[MEOW_IPC_MAX]; + uint16_t rt = 0; + int rc = ipc_pm_send(MSG_PM_POLICY_GET, NULL, 0, &rt, js, sizeof js); + if (rc <= 0) + return send_err(sock, 503, "POWER_UNAVAILABLE", "电源管理服务不可用(powermgr 未运行/IPC 不可达)"); + if (rt != MSG_PM_RESULT || !utf8_valid_n(js, strlen(js))) + return send_err(sock, 503, "POWER_UNAVAILABLE", "电源管理服务应答包含非法字节"); + return send_json(sock, 200, js); +} + +/* POST /api/power/policy:body {"item":"profile|low-pct|crit-pct|bri-cap|scr-cap","value":<枚举|数字>}。 + * 经 MSG_PM_POLICY_SET 写 settings.conf power.* 键(powermgr 只写键不直接动系统)。 + * 服务端对非法值回 200 + {"kind":"policy","ok":false,"why":..};权限不足回 MSG_ERR(MEOW_ERR_PERM)。 */ +static int api_power_policy_set(int sock, const char *body, size_t body_len) +{ + if (!body || !body[0]) return send_err(sock, 400, "BAD_REQUEST", "缺少 body"); + jv_t *root = jv_parse(body, (int)body_len); + if (!root) return send_err(sock, 400, "BAD_REQUEST", "body 非合法 JSON"); + jv_t *ji = jv_obj_get(root, "item"), *jv = jv_obj_get(root, "value"); + char item[20] = "", val[40] = ""; + if (ji && ji->type == J_STR && ji->str) snprintf(item, sizeof item, "%s", ji->str); + if (jv && jv->type == J_STR && jv->str) snprintf(val, sizeof val, "%s", jv->str); + else if (jv && jv->type == J_NUM) snprintf(val, sizeof val, "%lld", jv->inum); + jv_free(root); + if (!item[0]) return send_err(sock, 422, "UNSUPPORTED", "缺 item(profile/low-pct/crit-pct/bri-cap/scr-cap)"); + if (!val[0]) return send_err(sock, 422, "UNSUPPORTED", "缺 value"); + + m_pm_policy_set_t s; memset(&s, 0, sizeof s); + snprintf(s.item, sizeof s.item, "%.15s", item); /* item[16] 留一个 NUL;防越界截断告警 */ + snprintf(s.val, sizeof s.val, "%.31s", val); /* val[32] 同理 */ + char js[MEOW_IPC_MAX]; + uint16_t rt = 0; + int rc = ipc_pm_send(MSG_PM_POLICY_SET, &s, sizeof s, &rt, js, sizeof js); + if (rc <= 0) { + if (rt == MSG_ERR) /* 权限不足:powermgr 拒绝(面板 root 仅当被白名单才放行;如实回传) */ + return send_err(sock, 403, "FORBIDDEN", rc == MEOW_ERR_PERM ? "设备身份不允许写电源策略" : "电源策略写入被拒绝"); + return send_err(sock, 503, "POWER_UNAVAILABLE", "电源管理服务不可用(powermgr 未运行/IPC 不可达)"); + } + if (rt != MSG_PM_RESULT || !utf8_valid_n(js, strlen(js))) + return send_err(sock, 503, "POWER_UNAVAILABLE", "电源管理服务应答包含非法字节"); + /* 服务端对非法值回 ok:false(HTTP 层 200,code 仍 OK);按其 why 映射 422。 */ + if (strstr(js, "\"ok\":false")) { + char why[96] = ""; + jv_t *jr = jv_parse(js, (int)strlen(js)); + if (jr) { jv_t *w = jv_obj_get(jr, "why"); if (w && w->type == J_STR && w->str) snprintf(why, sizeof why, "%s", w->str); jv_free(jr); } + return send_err(sock, 422, "UNSUPPORTED", why[0] ? why : "非法电源策略值"); + } + return send_json(sock, 200, js); +} + +/* GET /api/power/history → MSG_PM_HISTORY 采样环(30s 环形 4h,不落盘)。 + * GET /api/power/events → MSG_PM_EVENTS 异常耗电/充放电/亮灭屏/背光跳变事件(内存环形)。 + * 两者语义对齐 powermgr 期 2;count=0(默认)由服务端选合理上限,读级任意调用者。 */ +static int api_power_window(int sock, uint16_t req_type, uint16_t want_type, const char *unavail) +{ + uint32_t count = 0; + char js[MEOW_IPC_MAX]; + uint16_t rt = 0; + int rc = ipc_pm_send(req_type, &count, sizeof count, &rt, js, sizeof js); + if (rc <= 0) + return send_err(sock, 503, "POWER_UNAVAILABLE", unavail); + if (rt != want_type || !utf8_valid_n(js, strlen(js))) + return send_err(sock, 503, "POWER_UNAVAILABLE", "电源管理服务应答包含非法字节"); + return send_json(sock, 200, js); +} +static int api_power_history(int sock) +{ return api_power_window(sock, MSG_PM_HISTORY, MSG_PM_HISTORY_RESULT, "电源历史采样不可用(powermgr 未运行)"); } +static int api_power_events(int sock) +{ return api_power_window(sock, MSG_PM_EVENTS, MSG_PM_EVENTS_RESULT, "电源事件不可用(powermgr 未运行)"); } + /* ---- am/meow_svcd 二进制解析(popen 薄壳,复用有界收割 run_cmd) ---- * env MEOW_AMS_BIN / MEOW_SVCD_BIN 可覆盖(宿主/qemu 测试用;设备默认 /usr/bin,回退卡上路径)。 */ static const char *ams_bin(void) @@ -1514,6 +1674,211 @@ static int api_net_log(int sock) return rc; } +/* ---------------- netmgrd 期2/期3:接口列表 + 管控策略(只读展示 / 写级经 require_auth) ---------------- + * GET /api/net/ifaces → 0xE2(name 空=全部)接口数组 + 0xE0 状态里的 rssi(WiFi 信号)+ rfkill + dns。 + * 上游任一源坏字节 → available:false(绝不伪造)。netmgrd 不在 → available:false(HTTP 200,不报错)。 + * GET /api/net/policy → 0xEA SHOW(只读)→ 策略文本 parse 成 JSON(enforce=dns_only 如实透传)。 + * POST /api/net/policy → 0xEA 写级(SET_NET/SET_DOMAIN/DEL);HTTP 层已过 require_auth,IPC 层由 netmgrd + * 依 SO_PEERCRED 判系统身份(面板精确登记 os.meow.panel),拒绝时如实回传错误码,绝不掩盖。 */ +static int api_net_ifaces(int sock) +{ + m_net_iface_req_t q; memset(&q, 0, sizeof q); + uint16_t t; uint32_t code; char arr[MEOW_IPC_MAX]; + int n = net_ipc(MSG_NET_IFACE_REQ, &q, sizeof q, &t, &code, NULL, arr, sizeof arr); + if (n <= 0 || t != MSG_NET_IFACE_RESULT || code != MEOW_OK || !arr[0]) + return send_json(sock, 200, "{\"available\":false}"); + /* 上游接口数组由 netmgrd iface_json_obj 直出(name/kind/ip/gw/rx/tx,ASCII,无 ssid),仍校验坏字节。 */ + if (!utf8_valid_n(arr, strlen(arr))) + return send_json(sock, 200, "{\"available\":false}"); + + /* 取 rssi + rfkill + dns:用 0xE0 状态 JSON(单一权威,含 wifi.rssi/rfkill[]/bt.dns[]),jv 提取。 */ + uint16_t st; uint32_t scode; char sj[MEOW_IPC_MAX]; + int sn2 = net_ipc(MSG_NET_STATUS_REQ, NULL, 0, &st, &scode, NULL, sj, sizeof sj); + long long rssi = -9999; int got_rssi = 0; + sbuf_t rfkill = {0}, dns = {0}; + if (sn2 > 0 && st == MSG_NET_STATUS_RESULT && scode == MEOW_OK && utf8_valid_n(sj, strlen(sj))) { + jv_t *jr = jv_parse(sj, (int)strlen(sj)); + if (jr) { + jv_t *w = jv_obj_get(jr, "wifi"); + jv_t *r = w ? jv_obj_get(w, "rssi") : NULL; + if (r && r->type == J_NUM) { rssi = r->inum; got_rssi = 1; } + jv_t *rf = jv_obj_get(jr, "rfkill"); + if (rf && rf->type == J_ARR) jv_to_json(&rfkill, rf); + jv_t *bt = jv_obj_get(jr, "bt"); + jv_t *dn = bt ? jv_obj_get(bt, "dns") : NULL; + if (dn && dn->type == J_ARR) jv_to_json(&dns, dn); + jv_free(jr); + } + } + sbuf_t s = {0}; + sA(&s, "{\"available\":true,\"rssi\":"); + if (got_rssi) sA(&s, "%lld", rssi); else sA(&s, "null"); + sA(&s, ",\"rfkill\":%s,\"dns\":%s,\"ifaces\":%s}", rfkill.b ? rfkill.b : "[]", dns.b ? dns.b : "[]", arr); + sbuf_free(&rfkill); sbuf_free(&dns); + int rc = send_json(sock, 200, s.b ? s.b : ""); + sbuf_free(&s); + return rc; +} + +/* 策略 SHOW 文本 → JSON。netmgrd 首行:`# 策略(NETMGR_DESIGN §6)log_level=%s enforce=dns_only(...)`; + * 其后每实体一行:` net=block domain=+h1,-h2` 或 `(allow)`(无规则)。 */ +static void net_policy_parse(const char *txt, sbuf_t *s) +{ + char lvl[24] = "", enf[24] = "dns_only"; + const char *p = txt; + sA(s, "{\"available\":true,\"enforce\":"); + /* 首行找 enforce=... */ + /* enforce 后面紧跟全角「(」多字节说明,须在非 ASCII 字节处停(否则会吞「(SOCK_DESTROY...)」)。 */ + const char *en = strstr(p, "enforce="); + if (en) { + en += 8; + int i = 0; while (en[i] && en[i] != ' ' && en[i] != '\n' && (unsigned char)en[i] < 0x80 && i < (int)sizeof enf - 1) { enf[i] = en[i]; i++; } + enf[i] = 0; + } + const char *lg = strstr(p, "log_level="); + if (lg) { + lg += 10; + int i = 0; while (lg[i] && lg[i] != ' ' && lg[i] != '\n' && (unsigned char)lg[i] < 0x80 && i < (int)sizeof lvl - 1) { lvl[i] = lg[i]; i++; } + lvl[i] = 0; + } + sA_str(s, enf); + sA(s, ",\"log_level\":"); + sA_str(s, lvl); + sA(s, ",\"rules\":["); + int first = 1; + const char *ln = txt; + while (ln && *ln) { + const char *nl = strchr(ln, '\n'); + size_t ll = nl ? (size_t)(nl - ln) : strlen(ln); + if (ll == 0) { ln = nl ? nl + 1 : ln + ll; continue; } + /* 跳注释行(以 # 开头)与空;数据行为 ` net=... domain=...`(entity 含冒号无空格) */ + const char *c = ln; + while (c < ln + ll && (*c == ' ' || *c == '\t')) c++; + int is_comment = (c < ln + ll && *c == '#'); + int is_none = (strncmp(c, "(none)", 6) == 0 || strncmp(c, "(allow)", 7) == 0); + if (!is_comment && !is_none && c < ln + ll) { + /* entity = 首个空白符之前 */ + char entity[64] = ""; size_t ei = 0; + const char *e = c; + while (e < ln + ll && *e != ' ' && *e != '\t' && ei < sizeof entity - 1) { entity[ei++] = *e++; } + entity[ei] = 0; + /* net=block|allow 与 domain=+h1,-h2 从 entity 之后扫 */ + char net[8] = "allow"; int has_net = 0; + char allow_h[512] = "", deny_h[512] = ""; + const char *k = e; + if (k < ln + ll) { + const char *seg = k; + while (seg < ln + ll && (*seg == ' ' || *seg == '\t')) seg++; + const char *nh = strstr(seg, "net="); + if (nh) { nh += 4; int i2 = 0; while (nh[i2] && nh[i2] != ' ' && nh[i2] != '\n' && i2 < 7) { net[i2] = nh[i2]; i2++; } net[i2] = 0; has_net = 1; } + const char *dh = strstr(seg, "domain="); + if (dh) { + dh += 7; + /* 逗号分隔;+ 前缀=allow,- 前缀=deny。主机列表内部以 ',' 拼接(供拆分回数组)。 */ + size_t ao = 0, do2 = 0; + const char *h = dh; + while (*h && *h != ' ' && *h != '\n' && *h != '\r') { + if (*h == '+') { + h++; + if (ao && ao < sizeof allow_h - 1) allow_h[ao++] = ','; + while (*h && *h != ',' && *h != ' ' && *h != '\n' && ao < sizeof allow_h - 1) allow_h[ao++] = *h++; + } else if (*h == '-') { + h++; + if (do2 && do2 < sizeof deny_h - 1) deny_h[do2++] = ','; + while (*h && *h != ',' && *h != ' ' && *h != '\n' && do2 < sizeof deny_h - 1) deny_h[do2++] = *h++; + } else h++; + if (*h == ',') h++; + } + allow_h[ao] = 0; deny_h[do2] = 0; + } + if (!first) sA(s, ","); + first = 0; + sA(s, "{\"entity\":"); sA_str(s, entity); + sA(s, ",\"net\":"); sA_str(s, has_net ? net : ""); + sA(s, ",\"has_net\":%s", has_net ? "true" : "false"); + sA(s, ",\"allow_hosts\":["); + { const char *hp = allow_h; int hf = 1; + while (hp && *hp) { const char *comma = strchr(hp, ','); size_t hl = comma ? (size_t)(comma - hp) : strlen(hp); + if (!hf) { sA(s, ","); } + hf = 0; sA_str_n(s, hp, hl); + hp = comma ? comma + 1 : hp + hl; } } + sA(s, "],\"deny_hosts\":["); + { const char *hp = deny_h; int hf = 1; + while (hp && *hp) { const char *comma = strchr(hp, ','); size_t hl = comma ? (size_t)(comma - hp) : strlen(hp); + if (!hf) { sA(s, ","); } + hf = 0; sA_str_n(s, hp, hl); + hp = comma ? comma + 1 : hp + hl; } } + sA(s, "]}"); + } + } + ln = nl ? nl + 1 : ln + ll; + } + sA(s, "]}"); +} + +/* GET /api/net/policy(只读):0xEA SHOW → JSON(rules + enforce=dns_only 如实)。 */ +static int api_net_policy_get(int sock) +{ + m_net_policy_t q; memset(&q, 0, sizeof q); + q.op = NET_POLICY_SHOW; + uint16_t t; uint32_t code; char txt[MEOW_IPC_MAX]; + int n = net_ipc(MSG_NET_POLICY, &q, sizeof q, &t, &code, NULL, txt, sizeof txt); + if (n <= 0 || t != MSG_NET_POLICY_RESULT || code != MEOW_OK) + return send_json(sock, 200, "{\"available\":false}"); + if (!utf8_valid_n(txt, strlen(txt))) /* 实体名/域名坏字节 → 降级 */ + return send_json(sock, 200, "{\"available\":false}"); + sbuf_t s = {0}; + net_policy_parse(txt, &s); + int rc = send_json(sock, 200, s.b ? s.b : "{\"available\":false}"); + sbuf_free(&s); + return rc; +} + +/* POST /api/net/policy:body {"op":"set_net|set_domain|del","entity":"","arg":""}。 + * op 缺失默认 set_net;op 非法 → 422。arg 校验交给 netmgrd(防自锁硬拒在 netmgrd 服务端)。 */ +static int api_net_policy_set(int sock, const char *body, size_t body_len) +{ + if (!body || !body[0]) return send_err(sock, 400, "BAD_REQUEST", "缺少 body"); + jv_t *root = jv_parse(body, (int)body_len); + if (!root) return send_err(sock, 400, "BAD_REQUEST", "body 非合法 JSON"); + jv_t *jo = jv_obj_get(root, "op"), *je = jv_obj_get(root, "entity"), *ja = jv_obj_get(root, "arg"); + char op[16] = "set_net", entity[64] = "", arg[64] = ""; + if (jo && jo->type == J_STR && jo->str) snprintf(op, sizeof op, "%s", jo->str); + if (je && je->type == J_STR && je->str) snprintf(entity, sizeof entity, "%s", je->str); + if (ja && ja->type == J_STR && ja->str) snprintf(arg, sizeof arg, "%s", ja->str); + jv_free(root); + if (!entity[0]) return send_err(sock, 422, "UNSUPPORTED", "缺 entity(app:/svc:/proc:)"); + if (!entity[0] || strchr(entity, '/')) return send_err(sock, 422, "UNSUPPORTED", "entity 非法"); + int nop = 0; + if (!strcmp(op, "set_net")) nop = NET_POLICY_SET_NET; + else if (!strcmp(op, "set_domain")) nop = NET_POLICY_SET_DOMAIN; + else if (!strcmp(op, "del")) nop = NET_POLICY_DEL; + else return send_err(sock, 422, "UNSUPPORTED", "op 须为 set_net|set_domain|del"); + if (nop != NET_POLICY_DEL && !arg[0]) return send_err(sock, 422, "UNSUPPORTED", "缺 arg"); + + m_net_policy_t q; memset(&q, 0, sizeof q); + q.op = (uint32_t)nop; + snprintf(q.entity, sizeof q.entity, "%.47s", entity); /* entity[48] 留一个 NUL;防截断告警 */ + snprintf(q.arg, sizeof q.arg, "%.63s", arg); /* arg[64] 同理 */ + uint16_t t; uint32_t code; char txt[MEOW_IPC_MAX]; + int n = net_ipc(MSG_NET_POLICY, &q, sizeof q, &t, &code, NULL, txt, sizeof txt); + if (n <= 0 || t != MSG_NET_POLICY_RESULT) + return send_err(sock, 503, "NET_UNAVAILABLE", "网络管理服务不可用"); + if (code == (uint32_t)MEOW_ERR_PERM) + return send_err(sock, 403, "FORBIDDEN", txt[0] ? txt : "策略写入需系统身份,面板未被白名单?"); + if (code == (uint32_t)MEOW_ERR_INVAL) + return send_err(sock, 422, "UNSUPPORTED", txt[0] ? txt : "策略参数非法"); + if (code != MEOW_OK) + return send_err(sock, 503, "NET_NODEV", txt[0] ? txt : "策略写入失败"); + sbuf_t s = {0}; + sA(&s, "{\"accepted\":true,\"message\":"); + sA_str(&s, txt); + sA(&s, "}"); + int rc = send_json(sock, 200, s.b ? s.b : ""); + sbuf_free(&s); + return rc; +} + /* ---------------- amsd / svcd 服务表 + 首页聚合(批1 只读) ---------------- */ /* 就地去掉 ANSI 转义序列(\033[...final);svcd status all 走管道时 CLI 会去色,此处双保险)。 */ @@ -1559,6 +1924,8 @@ static void join_tokens(char **toks, int from, int to, char *out, size_t cap) out[o] = 0; } +static void ams_fg_fill(sbuf_t *s); /* 定义于下;ams_fill 内引用其前台摘要(前向声明) */ + /* GET /api/ams:`am list`(注册表+状态)parse → App 表。amsd 不在 → available:false(绝无假数据)。 */ static void ams_fill(sbuf_t *s) { @@ -1597,8 +1964,57 @@ static void ams_fill(sbuf_t *s) } if (nl) { *nl = '\n'; ln = nl + 1; } else break; } - sA(s, "{\"available\":true,\"count\":%d,\"apps\":[%s]}", count, apps.b ? apps.b : ""); + sA(s, "{\"available\":true,\"count\":%d,\"apps\":[%s]", count, apps.b ? apps.b : ""); sbuf_free(&apps); + ams_fg_fill(s); /* 追加 `,"fg":{..},"running":[..]`(首页前台 App 高亮;additive) */ + sA(s, "}"); +} + +/* `am status` → `,"fg":{...},"running":[...]`(供 /api/ams 与 overview 的 ams 块复用;仅前台摘要,轻)。 + * am status 行:`RUN %s %s %ld %llu %s\n` = pkg state pid dur activity(可含空格)。 + * activity=join(tok[5..n));fg=首个 state=="fg"。amsd 不在 → fg:null,running:[](绝无假数据)。 */ +static void ams_fg_fill(sbuf_t *s) +{ + char out[16384]; + char cmd[300]; + snprintf(cmd, sizeof cmd, "%s status 2>/dev/null", ams_bin()); + int code = run_cmd(cmd, out, sizeof out); + if (code != 0 || !strstr(out, "RUN")) { sA(s, ",\"fg\":null,\"running\":[]"); return; } + panel_strip_ansi(out); + char *toks[256]; + char fg_pkg[128] = "", fg_act[160] = ""; int fg_pid = 0; + sbuf_t run = {0}; int first = 1; + char *ln = out; + while (*ln) { + char *nl = strchr(ln, '\n'); + if (nl) *nl = 0; + if (strncmp(ln, "RUN ", 4) == 0) { + int n = txt_tokens(ln, toks, 256); + if (n >= 6) { /* RUN pkg state pid dur activity... */ + char act[160]; + join_tokens(toks, 5, n, act, sizeof act); + const char *st = toks[2]; + if (!first) sA(&run, ","); + first = 0; + sA(&run, "{\"pkg\":"); sA_str(&run, toks[1]); + sA(&run, ",\"state\":"); sA_str(&run, st); + sA(&run, ",\"pid\":%d", atoi(toks[3])); + sA(&run, ",\"activity\":"); sA_str(&run, act); + sA(&run, "}"); + if (strcmp(st, "fg") == 0 && !fg_pkg[0]) { + snprintf(fg_pkg, sizeof fg_pkg, "%s", toks[1]); + snprintf(fg_act, sizeof fg_act, "%s", act); + fg_pid = atoi(toks[3]); + } + } + } + if (nl) { *nl = '\n'; ln = nl + 1; } else break; + } + sA(s, ",\"fg\":"); + if (fg_pkg[0]) { sA(s, "{\"pkg\":"); sA_str(s, fg_pkg); sA(s, ",\"activity\":"); sA_str(s, fg_act); sA(s, ",\"pid\":%d}", fg_pid); } + else sA(s, "null"); + sA(s, ",\"running\":[%s]", run.b ? run.b : ""); + sbuf_free(&run); } static int api_ams(int sock) @@ -1610,6 +2026,100 @@ static int api_ams(int sock) return rc; } +/* 包名合法性校验(ams history/start/kill 共用):反向域名命名字符集,拒 /、: 与空白。 */ +static int ams_pkg_ok(const char *pkg) +{ + if (!pkg || !pkg[0]) return 0; + if (strlen(pkg) >= MEOW_APP_ID_MAX) return 0; + for (const char *p = pkg; *p; p++) { + unsigned char c = (unsigned char)*p; + if (c == '/' || c == ':' || c == ' ' || c == '\t' || c == '\n' || c == '\r') return 0; + if (c < 0x21 || c > 0x7e) return 0; /* 包名仅 ASCII 可见字符 */ + } + return 1; +} + +/* GET /api/ams/:pkg → `am history `(amsd 现成:incident 登记 + 崩溃根因 detail)。 + * 输出行:`EVT `(detail 可含空格,为崩溃根因/事件文本)。 + * 上游 text 含崩溃 detail,须 utf8 校验坏字节降级 available:false(绝不发非法 JSON)。 + * 解析部分独立成 ams_history_parse(供 selftest 喂截断 detail 断言合法 JSON)。 */ +static void ams_history_parse(char *out, sbuf_t *s, const char *pkg) /* out 被 panel_strip_ansi 就地改 */ +{ + if (!utf8_valid_n(out, strlen(out))) { sA(s, "{\"available\":false}"); return; } /* 崩溃 detail 坏字节 → 降级 */ + panel_strip_ansi(out); + char *toks[256]; + sbuf_t ev = {0}; + int first = 1, count = 0; + char *ln = out; + while (*ln) { + char *nl = strchr(ln, '\n'); + if (nl) *nl = 0; + if (strncmp(ln, "EVT ", 4) == 0) { + int n = txt_tokens(ln, toks, 256); + if (n >= 5) { /* EVT epoch up kind pkg detail... */ + char det[256]; + join_tokens(toks, 5, n, det, sizeof det); + if (!first) sA(&ev, ","); + first = 0; count++; + sA(&ev, "{\"ts\":%ld,\"uptime_sec\":%ld,\"kind\":", atol(toks[1]), atol(toks[2])); + sA_str(&ev, toks[3]); + sA(&ev, ",\"detail\":"); sA_str(&ev, det); + sA(&ev, "}"); + } + } + if (nl) { *nl = '\n'; ln = nl + 1; } else break; + } + sA(s, "{\"available\":true,\"pkg\":"); sA_str(s, pkg); + sA(s, ",\"count\":%d,\"events\":[%s]}", count, ev.b ? ev.b : ""); + sbuf_free(&ev); +} +static void ams_history_fill(sbuf_t *s, const char *pkg) +{ + char out[16384]; + char cmd[320]; + snprintf(cmd, sizeof cmd, "%s history %s 2>/dev/null", ams_bin(), pkg); + int code = run_cmd(cmd, out, sizeof out); + if (code != 0 || !strstr(out, "EVT")) { sA(s, "{\"available\":false}"); return; } + ams_history_parse(out, s, pkg); +} +static int api_ams_history(int sock, const char *pkg) +{ + if (!ams_pkg_ok(pkg)) return send_err(sock, 400, "BAD_REQUEST", "包名非法"); + sbuf_t s = {0}; + ams_history_fill(&s, pkg); + int rc = send_json(sock, 200, s.b ? s.b : "{\"available\":false}"); + sbuf_free(&s); + return rc; +} + +/* POST /api/ams/:pkg/start → `am start `(amsd→launcher MSG_AMS_START 拉起;launcher 离线回 ERR)。 + * POST /api/ams/:pkg/kill → `am kill `(SIGTERM 语义,前台需前端二次确认)。均 require_auth 之后。 */ +static int api_ams_ctl(int sock, const char *pkg, int kill) +{ + if (!ams_pkg_ok(pkg)) return send_err(sock, 400, "BAD_REQUEST", "包名非法"); + char out[256]; + char cmd[320]; + snprintf(cmd, sizeof cmd, "%s %s %s 2>/dev/null", ams_bin(), kill ? "kill" : "start", pkg); + int code = run_cmd(cmd, out, sizeof out); + if (code == 0 && strstr(out, "OK ")) { + sbuf_t s = {0}; + sA(&s, "{\"accepted\":true,\"op\":\"%s\",\"pkg\":", kill ? "kill" : "start"); + sA_str(&s, pkg); + sA(&s, ",\"message\":"); sA_str(&s, out); + sA(&s, "}"); + int rc2 = send_json(sock, 200, s.b ? s.b : ""); + sbuf_free(&s); + return rc2; + } + /* am CLI 以文本报告失败(ERR ...);按内容映射 HTTP 码(如实,绝不掩盖)。 */ + const char *detail = out[0] ? out : (kill ? "am kill 失败" : "am start 失败"); + if (strstr(detail, "not registered")) return send_err(sock, 404, "NOT_FOUND", detail); + if (strstr(detail, "busy")) return send_err(sock, 409, "CONFLICT", detail); + if (strstr(detail, "not running")) return send_err(sock, 409, "CONFLICT", detail); + if (strstr(detail, "launcher offline")) return send_err(sock, 503, "AMS_UNAVAILABLE", "amsd/launcher 不可达"); + return send_err(sock, 500, "INTERNAL", detail); +} + /* GET /api/svcd:`meow_svcd status all` parse → 服务表(state/pid/restart 计数/exec/来源)。 * svcd 未运行(exit!=0)→ available:false(绝无假数据)。 */ static void svcd_fill(sbuf_t *s) @@ -1691,6 +2201,17 @@ static int api_overview(int sock) if (ipc_pm_query(0, pw, sizeof pw) == MEOW_OK && utf8_valid_n(pw, strlen(pw))) sA(&s, "\"power\":%s,", pw); else sA(&s, "\"power\":{\"available\":false},"); + /* 最近异常耗电/充放电/亮灭屏/背光跳变事件(摘要 top2 进 overview,完整列表走 /api/power/events)。 */ + { + uint32_t ecnt = 2; + char ej[MEOW_IPC_MAX]; + uint16_t ert = 0; + int ert_rc = ipc_pm_send(MSG_PM_EVENTS, &ecnt, sizeof ecnt, &ert, ej, sizeof ej); + if (ert_rc > 0 && ert == MSG_PM_EVENTS_RESULT && utf8_valid_n(ej, strlen(ej))) + sA(&s, "\"events\":%s,", ej); + else + sA(&s, "\"events\":{\"available\":false},"); + } sA(&s, "\"audio\":"); audio_fill(&s); if (net_block_fill(&s) == 0) @@ -5496,12 +6017,22 @@ static int handle(int sock, req_t *r) /* ---- 批1 只读增强:功耗/进程服务/首页聚合(均走 PIN 鉴权读级) ---- */ if (!strcmp(r->method, "GET") && !strcmp(p, "/api/power")) return api_power(sock); + if (!strcmp(r->method, "GET") && !strcmp(p, "/api/power/policy")) return api_power_policy_get(sock); + if (!strcmp(r->method, "GET") && !strcmp(p, "/api/power/history")) return api_power_history(sock); + if (!strcmp(r->method, "GET") && !strcmp(p, "/api/power/events")) return api_power_events(sock); if (!strcmp(r->method, "GET") && !strcmp(p, "/api/ams")) return api_ams(sock); if (!strcmp(r->method, "GET") && !strcmp(p, "/api/svcd")) return api_svcd(sock); if (!strcmp(r->method, "GET") && !strcmp(p, "/api/overview")) return api_overview(sock); + if (!strcmp(r->method, "GET")) { + char seg[256]; + if (path_segment("/api/ams/", p, seg, sizeof seg) && seg[0]) + return api_ams_history(sock, seg); /* GET /api/ams/:pkg → am history(崩溃根因) */ + } - /* ---- 网络管理(meow_netmgrd 期2 透传:读级任意调用者;flush 写级走 require_auth) ---- */ + /* ---- 网络管理(meow_netmgrd 期2/期3 透传:读级任意调用者;flush/policy 写级走 require_auth) ---- */ if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/status")) return api_net_status(sock); + if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/ifaces")) return api_net_ifaces(sock); + if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/policy")) return api_net_policy_get(sock); if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/conns")) return api_net_conns(sock, r->query); if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/dns")) return api_net_dns(sock); if (!strcmp(r->method, "GET") && !strcmp(p, "/api/net/log")) return api_net_log(sock); @@ -5533,6 +6064,8 @@ static int handle(int sock, req_t *r) if (!strcmp(p, "/api/apps/install")) return api_apps_install(sock, r); if (!strcmp(p, "/api/notifications/dismiss_all") || !strcmp(p, "/api/notifications/clear")) return api_notification_dismiss_all(sock); if (!strcmp(p, "/api/net/dns/flush")) return api_net_dns_flush(sock); + if (!strcmp(p, "/api/net/policy")) return api_net_policy_set(sock, r->body, r->body_len); + if (!strcmp(p, "/api/power/policy")) return api_power_policy_set(sock, r->body, r->body_len); if (!strcmp(p, "/api/audio/route")) return api_audio_route(sock, r->body, r->body_len); if (!strcmp(p, "/api/audio/volume")) return api_audio_volume(sock, r->body, r->body_len); if (!strcmp(p, "/api/key")) return api_key_batch(sock, r); @@ -5540,6 +6073,19 @@ static int handle(int sock, req_t *r) char seg[256]; if (path_segment("/api/key/", p, seg, sizeof seg)) return api_key_single(sock, atoi(seg), r); + /* POST /api/ams//start|kill(管启/停;require_auth 已护,kill 前台前端二次确认) */ + if (!strncmp(p, "/api/ams/", 9)) { + size_t pl = strlen(p); + if (pl > 5 && !strcmp(p + pl - 6, "/start")) { + char pkg[256]; + if (path_segment("/api/ams/", p, pkg, sizeof pkg)) + return api_ams_ctl(sock, pkg, 0); + } else if (pl > 4 && !strcmp(p + pl - 5, "/kill")) { + char pkg[256]; + if (path_segment("/api/ams/", p, pkg, sizeof pkg)) + return api_ams_ctl(sock, pkg, 1); + } + } } /* ---- 设置 ---- */ @@ -5697,7 +6243,43 @@ static int panel_selftest(void) jv_free(ar); sbuf_free(&a); - printf("SELFTEST PASS: 截断 UTF-8「流媒体客户\\xE7」→ 合法 JSON + 无非法字节\n"); + /* 4) net_policy_parse:喂含坏字节域名(截断「段」字)+ allow/deny 混合 → 产物合法 JSON、无非法字节。 + * 域名/实体名都走 sA_str_n 消毒(坏字节→'?'),故解析器产物必为合法 JSON。 */ + { + static const char ptext[] = + "# 策略(NETMGR_DESIGN §6)log_level=debug enforce=dns_only(SOCK_DESTROY 在本内核为 no-op)\n" + "app:com.example.trunc net=block domain=+good.example,-bad\xE7\n" + "svc:meowpanel net=allow domain=+svc.local\n"; + sbuf_t p = {0}; + net_policy_parse(ptext, &p); + if (!p.b || !utf8_valid_n(p.b, p.n)) { printf("SELFTEST FAIL: net_policy_parse 产物含非法字节/为空\n"); sbuf_free(&p); return 1; } + jv_t *pr = jv_parse(p.b, (int)p.n); + if (!pr) { printf("SELFTEST FAIL: net_policy_parse 产物 JSON 无法解析\n"); sbuf_free(&p); return 1; } + jv_t *rules = jv_obj_get(pr, "rules"); + if (!rules || rules->type != J_ARR || !rules->child) { printf("SELFTEST FAIL: net_policy_parse 缺 rules 数组\n"); jv_free(pr); sbuf_free(&p); return 1; } + jv_t *enf = jv_obj_get(pr, "enforce"); + if (!enf || enf->type != J_STR || !enf->str || strcmp(enf->str, "dns_only") != 0) { printf("SELFTEST FAIL: net_policy_parse enforce 未如实透传 dns_only\n"); jv_free(pr); sbuf_free(&p); return 1; } + jv_free(pr); + sbuf_free(&p); + } + + /* 5) ams_history_parse:喂 EVT 行 + 截断崩溃 detail(含坏字节)→ available:false(降级,绝不发非法 JSON)。 + * detail 合法时 → 合法 JSON 可解析。 */ + { + char bad[] = "# amd history com.example.trunc\nEVT 1786523400 123 crash com.example.trunc 崩溃在函数 foo\xE7\n"; + sbuf_t h = {0}; + ams_history_parse(bad, &h, "com.example.trunc"); + if (!h.b || strstr(h.b, "available\":false") == NULL) { printf("SELFTEST FAIL: ams_history_parse 截断 detail 未降级 available:false\n"); sbuf_free(&h); return 1; } + sbuf_free(&h); + + char good[] = "# amd history com.example.trunc\nEVT 1786523400 123 crash com.example.trunc SIGSEGV at addr 0x10\nEVT 1786523500 124 ams_start com.example.trunc ok forwarded\n"; + sbuf_t h2 = {0}; + ams_history_parse(good, &h2, "com.example.trunc"); + if (!h2.b || !utf8_valid_n(h2.b, h2.n) || !jv_parse(h2.b, (int)h2.n)) { printf("SELFTEST FAIL: ams_history_parse 合法 detail 无法解析/含非法字节\n"); sbuf_free(&h2); return 1; } + sbuf_free(&h2); + } + + printf("SELFTEST PASS: 截断 UTF-8「流媒体客户\\xE7」+ net/power/ams 新增端点池 → 合法 JSON + 无非法字节\n"); return 0; } diff --git a/patch_v2/meowpanel/meowpanel_host b/patch_v2/meowpanel/meowpanel_host index 9234445..02ac32e 100755 Binary files a/patch_v2/meowpanel/meowpanel_host and b/patch_v2/meowpanel/meowpanel_host differ diff --git a/patch_v2/meowpanel/web/meowpanel.js b/patch_v2/meowpanel/web/meowpanel.js index 2f8ef27..08d00c8 100644 --- a/patch_v2/meowpanel/web/meowpanel.js +++ b/patch_v2/meowpanel/web/meowpanel.js @@ -752,11 +752,16 @@ function healthCard() { const ams = homeData && homeData.ams; const svcd = homeData && homeData.svcd; - let appTxt = '—', svcBad = 0, svcHi = 0, svcTxt = '—'; + let appTxt = '—', svcBad = 0, svcHi = 0, svcTxt = '—', fgHtml = ''; if (isAvail(ams)) { const apps = ams.apps || []; const run = apps.filter(a => a.state === 'fg' || a.state === 'bg').length; appTxt = run + ' 在跑 / ' + (ams.count || apps.length) + ' 已装'; + const fg = ams.fg; + if (fg && fg.pkg) { + fgHtml = '前台 ' + esc(fg.pkg) + + (fg.activity ? ' · ' + esc(fg.activity) + '' : ''); + } } let svcHtml; if (isAvail(svcd)) { @@ -773,7 +778,8 @@ svcTxt = '—'; } return homeCard('在跑 App 与服务', '
' + - homeKv('在跑 App', esc(appTxt)) + homeKv('服务健康', svcHtml + esc(svcTxt)) + '
'); + homeKv('在跑 App', esc(appTxt)) + homeKv('服务健康', svcHtml + esc(svcTxt)) + + (fgHtml ? homeKv('前台应用', fgHtml) : '') + ''); } function storageCard() { const perf = homeData && homeData.perf; @@ -797,11 +803,37 @@ '
温度
' + (tmp.cpu != null ? (+tmp.cpu).toFixed(1) + '℃' : '—') + '
' + ''); } + /* 首页功耗摘要条(est_mw + breakdown:复用 estimateCardBody/breakdownCardBody 保持一致口径,诚实标注 confidence)。 + * 上游 power 不可用 → 不渲染该条(绝不假数据);HibyOS 只读 degraded → 显示说明。 */ + function powerSummaryCard() { + const pw = homeData && homeData.power; + if (!isAvail(pw)) return ''; + const est = pw.estimate; + if (!est) return ''; + if (est.est_mw == null) return homeCard('功耗视角', '
' + (est.degraded ? '功耗估算不可用(HibyOS 只读模式)' : '—') + '
'); + return homeCard('功耗视角', estimateCardBody(est) + (pw.breakdown ? breakdownCardBody(pw.breakdown) : '')); + } + /* 最近异常事件条(power.events 摘要 top3 进 overview;完整列表走 /api/power/events)。 */ + function eventsBar() { + const ev = homeData && homeData.events; + if (!ev || ev.available === false) return ''; + const list = (ev && ev.events) || []; + if (!list.length) return ''; + const typeLabel = { charger: '充电', lowbat: '低电', screen: '屏幕', brightness: '背光' }; + let items = list.slice(0, 3).map(e => { + const tn = typeLabel[e[1]] || e[1]; + const txt = String(e[3] == null ? '' : e[3]); + const cls = (e[1] === 'lowbat') ? 'warn' : (e[1] === 'charger' ? 'ok' : 'mut'); + return '' + esc(tn) + (txt ? ' · ' + esc(txt.length > 28 ? txt.slice(0, 28) + '…' : txt) : '') + ''; + }).join(' '); + return '

最近事件

' + items + '
'; + } function renderHomeCards() { const body = $('#home-body'); if (!body || state.route !== 'home') return; if (!homeData) { body.innerHTML = '

加载总览…

'; return; } - let h = '
' + + let h = powerSummaryCard() + eventsBar(); // 摘要条(功耗/事件)置顶 + h += '
' + deviceCard() + batteryCard() + audioCard() + netCard() + healthCard() + storageCard() + perfCard() + '
'; body.innerHTML = h; @@ -921,31 +953,126 @@ } return '
合计 ' + total + ' mW(分解为经验估算)
' + stack + legend; } - function renderPowerBody(d) { + let powerEditing = false; // 用户在编辑策略表单时暂停 5s 重渲染(防输入被轮询清空) + /* 电源策略卡:profile 四档 + 低电/临界阈值 + 亮度/息屏上限。前端确认后 POST /api/power/policy。 + * 默认全关(enacted=0)如实标注;note 透传 powermgr 口径(只写 power.* 键,不直接写系统)。 */ + function powerProfileLabel(p) { + return { performance: '性能', balanced: '均衡', powersave: '省电', ultra: '超省电' }[p] || p; + } + function powerPolicyCard(pol) { + if (!pol) return '

电源策略

' + homeUnavailable() + '
'; + const profiles = ['performance', 'balanced', 'powersave', 'ultra']; + const cur = pol.profile || 'balanced'; + let btns = profiles.map(p => '').join(' '); + return '

电源策略

' + + '
省电档位:' + btns + '
' + + '' + + row('低电阈值', ' %') + + row('临界阈值', ' %') + + row('亮度上限', '0=不生效') + + row('息屏上限', '秒 · 0=不生效') + + row('状态', (pol.enacted ? '已手动开启' : '默认全关')) + + '
' + + '
切档即时生效(需确认);阈值点「保存阈值」。
' + + '

' + esc(pol.note || '') + '

' + + '
'; + } + function powerEventsCard(ev) { + if (!ev) return ''; + if (ev.available === false) return '

电源事件

' + homeUnavailable() + '
'; + const list = (ev && ev.events) || []; + if (!list.length) return '

电源事件

暂无事件
'; + const typeLabel = { charger: '充电', lowbat: '低电', screen: '屏幕', brightness: '背光' }; + let rows = list.map(e => { + const tn = typeLabel[e[1]] || e[1]; + return '' + esc(tn) + '' + (e[2] != null ? esc(e[2]) + '%' : '—') + '' + esc(e[3] || '') + ''; + }).join(''); + return '

电源事件

' + + '' + rows + '
类型电量说明
'; + } + function powerHistoryCard(h) { + if (!h) return ''; + if (h.available === false) return '

功耗历史

' + homeUnavailable() + '
'; + let body = '' + + row('采样窗口', (h.n_samples || 0) + ' 点 · ' + fmtDur(h.window_sec)) + + row('电量范围', (h.min_pct != null ? h.min_pct : '—') + '% ~ ' + (h.max_pct != null ? h.max_pct : '—') + '%') + + row('起止电量', (h.start_pct != null ? h.start_pct : '—') + '% → ' + (h.end_pct != null ? h.end_pct : '—') + '%') + + row('平均功耗', (h.avg_mw != null ? h.avg_mw + ' mW' : '—')) + + '
'; + return '

功耗历史

' + body + '
'; + } + function renderPowerBody(d, ev, hi) { let h = '
'; if (d && d.mode === 'readonly') h += '
ℹ️当前为电源只读观测(HibyOS 模式),仅显示电池采样,无功耗估算/分解。
'; h += '

电池

' + ((d && d.battery) ? batteryCardBody(d.battery) : homeUnavailable()) + '
'; h += '

功耗估算

' + ((d && d.estimate) ? estimateCardBody(d.estimate) : homeUnavailable()) + '
'; h += '

子系统分解

' + ((d && d.breakdown) ? breakdownCardBody(d.breakdown) : homeUnavailable()) + '
'; - if (d && d.policy == null) h += '

电源策略(profile 四档/低电阈值)为 期3 功能,此处以不可用占位。

'; + h += ((d && d.policy) ? powerPolicyCard(d.policy) : '

电源策略

' + homeUnavailable() + '
'); + h += powerEventsCard(ev); + h += powerHistoryCard(hi); h += '
'; return h; } + let powerData = null, powerEvents = null, powerHistory = null; async function fetchPowerOnce() { const el = $('#power-page'); if (!el || state.route !== 'power') return; - let d; - try { d = await api('GET', '/api/power'); } - catch (e) { if (el && state.route === 'power') el.innerHTML = '
' + emptyState('加载失败:' + errText(e), e.code) + '
'; return; } + let d, ev, hi; + const r = await Promise.allSettled([api('GET', '/api/power'), api('GET', '/api/power/events'), api('GET', '/api/power/history')]); + if (r[0].status !== 'fulfilled') { + if (el && state.route === 'power') el.innerHTML = '
' + emptyState('加载失败:' + errText(r[0].reason), (r[0].reason && r[0].reason.code) || '') + '
'; + return; + } if (state.route !== 'power') return; - el.innerHTML = renderPowerBody(d); + d = r[0].value; ev = (r[1].status === 'fulfilled') ? r[1].value : null; hi = (r[2].status === 'fulfilled') ? r[2].value : null; + if (!powerEditing) el.innerHTML = renderPowerBody(d, ev, hi); } function renderPower() { stopPower(); + powerEditing = false; + powerData = null; powerEvents = null; powerHistory = null; $('#content').innerHTML = '

加载电源数据…

'; fetchPowerOnce(); powerTimer = setInterval(fetchPowerOnce, 5000); } + function bindPower() { + $('#content').addEventListener('click', async function (e) { + const pb = e.target.closest('.pol-btn'); + if (pb) { + const p = pb.dataset.pol; + const ok = await confirmDlg('切换省电档位', '确认将省电档位切换为「' + powerProfileLabel(p) + '」?(写 settings.conf power.profile,launcher/sysui 期4消费生效)', false); + if (!ok) { powerEditing = false; fetchPowerOnce(); return; } + powerEditing = false; + try { await api('POST', '/api/power/policy', { item: 'profile', value: p }); toast('已切档:' + powerProfileLabel(p)); } + catch (err) { toast('切档失败:' + errText(err)); } + fetchPowerOnce(); + return; + } + const apply = e.target.closest('#pol-apply'); + if (apply) { + const low = parseInt($('#pol-low') ? $('#pol-low').value : '', 10); + const crit = parseInt($('#pol-crit') ? $('#pol-crit').value : '', 10); + const bri = parseInt($('#pol-bri') ? $('#pol-bri').value : '', 10); + const scr = parseInt($('#pol-scr') ? $('#pol-scr').value : '', 10); + if (isNaN(low) || isNaN(crit)) { toast('请输入有效的低电/临界阈值'); return; } + const ok = await confirmDlg('保存电源阈值', '确认写入 低电=' + low + '% / 临界=' + crit + '% / 亮度上限=' + (isNaN(bri) ? 0 : bri) + ' / 息屏上限=' + (isNaN(scr) ? 0 : scr) + 's?', false); + if (!ok) { powerEditing = false; fetchPowerOnce(); return; } + powerEditing = false; + try { + await api('POST', '/api/power/policy', { item: 'low-pct', value: low }); + await api('POST', '/api/power/policy', { item: 'crit-pct', value: crit }); + await api('POST', '/api/power/policy', { item: 'bri-cap', value: bri || 0 }); + await api('POST', '/api/power/policy', { item: 'scr-cap', value: scr || 0 }); + toast('电源阈值已保存'); + } catch (err) { toast('保存失败:' + errText(err)); } + fetchPowerOnce(); + return; + } + }); + $('#content').addEventListener('focusin', function (e) { + if (e.target.closest('.pol-num')) powerEditing = true; // 编辑中暂停 5s 重渲染(防输入被清空) + }); + } /* ---------------- 2f. 进程 / 服务页(#/proc;/api/svcd + /api/ams,只读,无 restart/kill) ---------------- * svcd 服务表(state/pid/restart 计数≥3 高亮/exec/来源 board|tf)+ ams App 表(state/pid/crashes/launches/last_kind); @@ -1000,7 +1127,11 @@ '' + esc(a.launches || 0) + '' + '' + (a.crashes || 0) + ((a.crashes || 0) > 0 ? ' ⚠' : '') + '' + '' + esc(a.last_kind || '—') + (a.last_code != null && a.last_code !== 0 ? ' (' + esc(a.last_code) + ')' : '') + '' + - '
'; + '
' + + '' + + '' + + (a.pid > 0 ? '' : '') + + '
'; if (openHere) { const lines = (expandedProc.lines || []); h += '
' + esc(lines.join('\n') || '(空)') +
@@ -1009,7 +1140,7 @@
         }
       });
       h += '';
-      h += '

点击「stdout 尾部」查看该 App 最新 stdout_*.log 末尾(崩溃尾巴)。仅只读,无 restart/kill。

'; + h += '

「stdout 尾部」查看该 App 最新 stdout_*.log 末尾(崩溃尾巴)。「启动/结束」经 amsd(require_auth);结束前台 App 需二次确认。

'; } else h += homeUnavailable(); h += '
'; return h; @@ -1072,9 +1203,27 @@ procTimer = setInterval(fetchProcOnce, 5000); } function bindProc() { - $('#content').addEventListener('click', function (e) { - const b = e.target.closest('[data-applog]'); if (!b) return; - viewAppStdout(b.dataset.applog, b); + $('#content').addEventListener('click', async function (e) { + const b = e.target.closest('[data-applog]'); + if (b) { viewAppStdout(b.dataset.applog, b); return; } + const st = e.target.closest('[data-amsstart]'); + if (st) { + const pkg = st.dataset.amsstart; + try { await api('POST', '/api/ams/' + encodeURIComponent(pkg) + '/start'); toast('已请求启动 ' + pkg); } + catch (err) { toast('启动失败:' + errText(err)); } + fetchProcOnce(); + return; + } + const kl = e.target.closest('[data-amskill]'); + if (kl) { + const pkg = kl.dataset.amskill, fg = kl.dataset.fg === '1'; + const ok = await confirmDlg('结束 App', (fg ? '『' + pkg + '』正位于前台,' : '') + '确认结束 ' + pkg + '?(SIGTERM 语义)', true); + if (!ok) return; + try { await api('POST', '/api/ams/' + encodeURIComponent(pkg) + '/kill'); toast('已请求结束 ' + pkg); } + catch (err) { toast('结束失败:' + errText(err)); } + fetchProcOnce(); + return; + } }); } @@ -1110,12 +1259,18 @@ return 'net-state warn'; } - function netStatusCards(d) { - let h = ''; - // 接口卡(lo/wlan0/usb-eth:状态+IP+速率) - const ifs = (d && d.ifaces) || []; - h += '

网络接口

' + - ''; + /* 网络接口卡(/api/net/ifaces:kind/ip/gw/rssi/rfkill/dns + RX/TX)。 + * enforce 执行语义如实:仅 DNS 层拒绝(SOCK_DESTROY 在本内核为 no-op),绝不谎称「已断连」。 */ + function netIfaceCards(ifd) { + if (!ifd || ifd.available === false) return '

网络接口

' + homeUnavailable() + '
'; + const ifs = (ifd && ifd.ifaces) || []; + let h = '

网络接口

'; + let meta = '
'; + meta += (ifd.rssi != null) ? '信号 ' + esc(ifd.rssi) + ' dBm' : '信号 —'; + if (ifd.dns && ifd.dns.length) meta += ' · DNS ' + esc(ifd.dns.join(' / ')); + meta += '
'; + h += meta; + h += '
接口类型状态IP 地址网关RX / TX
'; if (!ifs.length) h += ''; ifs.forEach(function (f) { const up = !!f.up; @@ -1125,9 +1280,20 @@ ''; }); h += '
接口类型状态IP 地址网关RX / TX
无接口数据
' + fmtBytes(f.rx_bytes) + ' / ' + fmtBytes(f.tx_bytes) + '
'; - if (d && d.rate && (d.rate.rx_bps != null || d.rate.tx_bps != null)) - h += '

实时速率:↓ ' + fmtBytes(d.rate.rx_bps) + '/s · ↑ ' + fmtBytes(d.rate.tx_bps) + '/s

'; + const rf = (ifd && ifd.rfkill) || []; + if (rf.length) { + h += '
'; + rf.forEach(r => h += '' + esc(r.type) + (r.soft ? ' 软关' : '') + (r.hard ? ' 硬关' : '') + ''); + h += '
'; + } h += '
'; + return h; + } + /* WiFi / 蓝牙摘要(/api/net/status:E0 概览,含实时速率)。接口细分已迁 netIfaceCards。 */ + function netStatusCards(d) { + let h = ''; + if (d && d.rate && (d.rate.rx_bps != null || d.rate.tx_bps != null)) + h += '

实时速率:↓ ' + fmtBytes(d.rate.rx_bps) + '/s · ↑ ' + fmtBytes(d.rate.tx_bps) + '/s

'; // WiFi 摘要 const w = (d && d.wifi) || {}; h += '

WiFi

'; @@ -1155,9 +1321,50 @@ return h; } + /* 网络策略卡(/api/net/policy;写走 POST /api/net/policy,require_auth + 二次确认)。 + * enforce 如实显示(dns_only):仅 DNS 层拒绝,不会真断连。 */ + function netPolicyCard(pol) { + if (!pol || pol.available === false) return '

网络策略

' + homeUnavailable() + '
'; + const enforce = pol.enforce || ''; + const dnsOnly = enforce === 'dns_only'; + let h = '

网络策略

'; + h += '
' + (dnsOnly ? 'ℹ️' : '⚠️') + '' + + (dnsOnly ? '执行模式:仅 DNS 层拒绝(enforce=dns_only;本内核 SOCK_DESTROY 为 no-op,不会真正断连)' : '执行模式:' + esc(enforce)) + '
'; + const rules = pol.rules || []; + if (!rules.length) h += '
暂无策略规则(默认放行)
'; + else { + h += '
'; + rules.forEach(r => { + h += '' + + '' + + '' + + '' + + ''; + }); + h += '
实体网络白名单域名黑名单域名操作
' + netBadge(r.entity) + '' + (r.net === 'block' ? '阻断' : '放行') + '' + esc((r.allow_hosts || []).join(', ') || '—') + '' + esc((r.deny_hosts || []).join(', ') || '—') + '
' + + '' + + '
'; + } + h += '
' + + '' + + '
'; + h += '
' + + '
'; + h += '

防自锁实体(dropbear/wpa_supplicant/udhcpc/netmgrd/svcd/sysui/init)拒 block。

'; + h += '
'; + return h; + } + function netConnsBody(cn) { const list = (cn && cn.conns) || []; let h = ''; + /* 连接 top 摘要(聚合计数,轻量——overview 与网速页的观察摘要,非完整归因)。 */ + const entCnt = {}; + (list || []).forEach(c => { entCnt[c.entity] = (entCnt[c.entity] || 0) + 1; }); + const top = Object.keys(entCnt).sort((a, b) => entCnt[b] - entCnt[a]).slice(0, 3); + if (top.length) { + h += '
连接 top:' + top.map(e => '' + netBadge(e) + ' ×' + entCnt[e] + '').join(' · ') + '(共 ' + list.length + ' 条)
'; + } if (!list.length) h += '
暂无连接' + (netEntityFilter ? '(该实体)' : '') + '
'; else { h += ''; @@ -1202,8 +1409,9 @@ let netLoaded = false; // 是否已成功加载过一次网络状态(用于「首次失败只显示横幅 + 占位」语义) async function fetchNetOnce() { if (state.route !== 'net') return; - const stEl = $('#net-status'), cbEl = $('#net-conns-body'), dbEl = $('#net-dns-body'); - let st, cn, dn; + const stEl = $('#net-status'), cbEl = $('#net-conns-body'), dbEl = $('#net-dns-body'), + ifEl = $('#net-ifaces'), poEl = $('#net-policy'); + let st, cn, dn, ifd, pol; try { st = await api('GET', '/api/net/status'); } catch (e) { netBannerErr(errText(e)); return; } netLoaded = true; @@ -1212,8 +1420,12 @@ catch (e) { cn = null; } try { dn = await api('GET', '/api/net/dns'); } catch (e) { dn = null; } + try { ifd = await api('GET', '/api/net/ifaces'); } catch (e) { ifd = null; } + try { pol = await api('GET', '/api/net/policy'); } catch (e) { pol = null; } if (state.route !== 'net') return; + if (ifEl) ifEl.innerHTML = netIfaceCards(ifd); if (stEl) stEl.innerHTML = netStatusCards(st); + if (poEl) poEl.innerHTML = netPolicyCard(pol); if (cbEl) cbEl.innerHTML = netConnsBody(cn); if (dbEl) dbEl.innerHTML = netDnsBody(dn); } @@ -1250,7 +1462,9 @@ $('#content').innerHTML = '
' + '' + + '

加载网络接口…

' + '

加载网络状态…

' + + '

加载网络策略…

' + '

每实体连接表

' + '
' + '' + @@ -1290,6 +1504,60 @@ flush.disabled = false; fetchNetOnce(); } + /* ---- 网络策略写(require_auth + 二次确认;enforce=dns_only 如实显示,不谎称断连) ---- */ + const tgl = e.target.closest('[data-ptoggle]'); + if (tgl) { + const ent = tgl.dataset.ptoggle, net = tgl.dataset.net; + const ok = await confirmDlg('网络策略', '确认将 ' + ent + ' 网络策略设为「' + (net === 'block' ? '阻断(DNS 层拒绝,不会真断连)' : '放行') + '」?', net === 'block'); + if (!ok) { fetchNetOnce(); return; } + try { await api('POST', '/api/net/policy', { op: 'set_net', entity: ent, arg: net }); toast('已更新策略:' + ent); } + catch (err) { toast('策略更新失败:' + errText(err)); } + fetchNetOnce(); + return; + } + const del = e.target.closest('[data-pdel]'); + if (del) { + const ent = del.dataset.pdel; + const ok = await confirmDlg('删除策略', '确认删除 ' + ent + ' 的全部策略规则(恢复默认放行)?', true); + if (!ok) { fetchNetOnce(); return; } + try { await api('POST', '/api/net/policy', { op: 'del', entity: ent }); toast('已删除策略:' + ent); } + catch (err) { toast('删除失败:' + errText(err)); } + fetchNetOnce(); + return; + } + const blk = e.target.closest('#pol-block'); + if (blk) { + const ent = ($('#pol-entity') ? $('#pol-entity').value.trim() : ''); + if (!ent) { toast('请输入实体(app:xxx / svc:xxx / proc:xxx)'); return; } + const ok = await confirmDlg('阻断实体', '确认将 ' + ent + ' 设为阻断(DNS 层拒绝,不会真断连)?', true); + if (!ok) { fetchNetOnce(); return; } + try { await api('POST', '/api/net/policy', { op: 'set_net', entity: ent, arg: 'block' }); toast('已阻断:' + ent); } + catch (err) { toast('阻断失败:' + errText(err)); } + fetchNetOnce(); + return; + } + const alw = e.target.closest('#pol-allow'); + if (alw) { + const ent = ($('#pol-entity') ? $('#pol-entity').value.trim() : ''); + if (!ent) { toast('请输入实体(app:xxx / svc:xxx / proc:xxx)'); return; } + try { await api('POST', '/api/net/policy', { op: 'set_net', entity: ent, arg: 'allow' }); toast('已放行:' + ent); } + catch (err) { toast('放行失败:' + errText(err)); } + fetchNetOnce(); + return; + } + const dom = e.target.closest('#pol-dom'); + if (dom) { + const ent = ($('#pol-entity') ? $('#pol-entity').value.trim() : ''); + const hst = ($('#pol-domain') ? $('#pol-domain').value.trim() : ''); + if (!ent) { toast('请输入实体'); return; } + if (!hst || (hst[0] !== '+' && hst[0] !== '-')) { toast('域名须以 +(白)或 -(黑)开头'); return; } + const ok = await confirmDlg('域名规则', '确认对 ' + ent + ' ' + (hst[0] === '+' ? '加入白名单' : '加入黑名单') + ' ' + hst.slice(1) + '?', hst[0] === '-'); + if (!ok) { fetchNetOnce(); return; } + try { await api('POST', '/api/net/policy', { op: 'set_domain', entity: ent, arg: hst }); toast('域名规则已更新'); } + catch (err) { toast('域名规则失败:' + errText(err)); } + fetchNetOnce(); + return; + } }); } @@ -1852,6 +2120,66 @@ catch (e) { toast('保存失败:' + errText(e)); } }, 350); + /* ---------------- 6b. 远程控制页滑动手势(设备端靠触摸滑动操作,控制页据此补滑动手势) ---------------- + * 说明:后端 /api/input/tap 仅支持「单点坐标注入」(屏幕页用),无滑动/手势注入端点; + * 故控制页滑动统一映射到 /api/key/:code 语义键(与本地按键行为一致),多步音量 = 逐键 tap 多次。 */ + const SWIPE = { + minDist: 40, // 判定为「滑动」的最小位移(px),小于此视为点按 + axisRatio: 1.5, // 主轴位移须至少为次轴的倍数,防斜滑误判 + edgePx: 56, // 距控制区左缘多少 px 内起手即视为「左缘滑」 + maxVolSteps: 5, // 单次滑动最多递增音量步数(防连刷冲上限) + volStepPx: 70, // 每多滑这么多位移,多算一步音量 + clickSuppressMs: 450 // 滑动后短暂屏蔽按钮点击,防误触 + }; + let swipeState = null; // 当前手势 {id,x0,y0,edge} + let lastSwipeAt = 0; // 上次触发手势的时刻,用于屏蔽随后可能的 click + async function sendKeyLabel(code, label) { + try { await api('POST', '/api/key/' + code, { press: 'tap' }); toast(label + ' 已发送'); } + catch (e) { toast('发送失败:' + errText(e)); } + } + async function sendKeyRepeat(code, n, label) { + let count = 0, err = null; + for (let i = 0; i < n; i++) { + try { await api('POST', '/api/key/' + code, { press: 'tap' }); count++; } + catch (e) { err = errText(e); break; } // 失败即停,避免连刷 + } + if (count > 0) toast(label + ' ×' + count); + if (err) toast('发送失败:' + err); + } + // 依据滑动主轴位移换算音量步数(>=1,封顶 maxVolSteps) + function volSteps(ay) { + return Math.max(1, Math.min(SWIPE.maxVolSteps, Math.round(ay / SWIPE.volStepPx))); + } + function handleRemoteGesture(dir, ax, ay) { + if (dir === 'volup') sendKeyRepeat(115, volSteps(ay), '音量+'); + else if (dir === 'voldown') sendKeyRepeat(114, volSteps(ay), '音量−'); + else if (dir === 'next') sendKeyLabel(163, '下一曲'); + else if (dir === 'prev') sendKeyLabel(165, '上一曲'); + else if (dir === 'back') sendKeyLabel(158, '返回'); + } + function remotePointerDown(e) { + if (state.route !== 'remote' || swipeState) return; // 已有一指在手 → 忽略多点触控 + if (!e.target.closest('#remote-page')) return; + const page = $('#remote-page'); if (!page) return; + const r = page.getBoundingClientRect(); + swipeState = { id: e.pointerId, x0: e.clientX, y0: e.clientY, edge: (e.clientX - r.left) <= SWIPE.edgePx }; + } + function remotePointerUp(e) { + const s = swipeState; + if (!s || e.pointerId !== s.id) return; + swipeState = null; + const dx = e.clientX - s.x0, dy = e.clientY - s.y0; + const ax = Math.abs(dx), ay = Math.abs(dy); + if (Math.max(ax, ay) < SWIPE.minDist) return; // 轻点/未动 → 交给按钮 click 处理 + let dir = null; + if (s.edge && dx > 0 && dx >= ay * SWIPE.axisRatio && dx >= SWIPE.minDist) dir = 'back'; // 左缘滑 → 返回 + else if (ax >= ay * SWIPE.axisRatio) dir = (dx < 0 ? 'next' : 'prev'); // 水平 → 上下曲 + else if (ay >= ax * SWIPE.axisRatio) dir = (dy < 0 ? 'volup' : 'voldown'); // 垂直 → 音量 + if (!dir) return; // 斜滑/未达占优 → 忽略 + lastSwipeAt = Date.now(); + handleRemoteGesture(dir, ax, ay); + } + /* ---------------- 6. 远程控制页 ---------------- */ function renderRemote() { const keys = [ @@ -1864,12 +2192,16 @@ { code: 158, label: '返回', ico: '↩' }, { code: 172, label: '主页', ico: '⌂' } ]; - let h = '
'; + let h = '
'; h += '

模拟按键

' + '

行为与本地按键一致(设备端系统统一接管)。长按电源将触发电源菜单。

' + '
' + keys.map(k => '' - ).join('') + '
'; + ).join('') + '
' + + '

滑动手势(与按钮并存,轻点仍是点击):' + + '上滑=音量+ · 下滑=音量-(滑得越远步数越多,单次至多 ' + SWIPE.maxVolSteps + ' 步) · ' + + '左滑=下一曲 · 右滑=上一曲 · 控制区左缘右滑=返回

' + + '
'; h += '

电源长按(电源菜单)

' + '
' + '发送 code=116 · press=long · ms=3000
'; @@ -1882,9 +2214,14 @@ } function bindRemote() { $('#content').addEventListener('click', function (e) { + if (Date.now() - lastSwipeAt < SWIPE.clickSuppressMs) return; // 刚滑完 → 屏蔽本次点击,防误触按钮 const kb = e.target.closest('[data-code]'); if (kb) { sendKey(kb.dataset.code, 'tap'); return; } const pl = e.target.closest('[data-powerlong]'); if (pl) { sendKey(116, 'long', 3000); return; } }); + // 滑动手势:指针事件统一触摸/鼠标;#remote-page 已设 touch-action:none 防浏览器滚动劫持 + $('#content').addEventListener('pointerdown', remotePointerDown); + window.addEventListener('pointerup', remotePointerUp); + window.addEventListener('pointercancel', remotePointerUp); } /* ---------------- 7. 系统信息 / OTA 页 ---------------- */ @@ -2328,7 +2665,7 @@ system: { title: '系统信息 / 升级', render: renderSystem, bind: bindSystem }, logs: { title: '系统日志', render: renderLogs, bind: bindLogs }, notif: { title: '通知中心', render: renderNotif, bind: bindNotif }, - power: { title: '电源 / 功耗', render: renderPower, bind: null }, + power: { title: '电源 / 功耗', render: renderPower, bind: bindPower }, proc: { title: '进程 / 服务', render: renderProc, bind: bindProc } }; // 一次性绑定(各菜单 click / 侧栏);页面内交互用 #content 事件委托(已各自绑定) diff --git a/patch_v2/ncm_app/account_cookie_sim b/patch_v2/ncm_app/account_cookie_sim new file mode 100755 index 0000000..6658722 Binary files /dev/null and b/patch_v2/ncm_app/account_cookie_sim differ diff --git a/patch_v2/ncm_app/apicache_sim b/patch_v2/ncm_app/apicache_sim new file mode 100755 index 0000000..78b7b24 Binary files /dev/null and b/patch_v2/ncm_app/apicache_sim differ diff --git a/patch_v2/ncm_app/app.json b/patch_v2/ncm_app/app.json index 94eb015..9cd34cd 100644 --- a/patch_v2/ncm_app/app.json +++ b/patch_v2/ncm_app/app.json @@ -1 +1,14 @@ -{"name":"流媒体客户端","exec":"/usr/data/mnt/sd_0/MeowAPPs/dev.player.ncm/ncm","icon":"icon.png","package":"dev.player.ncm","version":"0.1","version_code":1,"framework":1,"immersive":1,"fonts":"common","author":"MeowOS","background_audio":1} \ No newline at end of file +{ +"name":"流媒体客户端", +"exec":"/usr/data/mnt/sd_0/MeowAPPs/dev.player.ncm/ncm", +"icon":"icon.png", +"package":"dev.player.ncm", +"version":"0.1", +"version_code":1, +"framework":1, +"immersive":1, +"fonts":"common", +"author":"MeowOS", +"background_audio":1, +"awake.player":1 +} diff --git a/patch_v2/ncm_app/app/app.c b/patch_v2/ncm_app/app/app.c index 97d9947..a18ad17 100644 --- a/patch_v2/ncm_app/app/app.c +++ b/patch_v2/ncm_app/app/app.c @@ -18,6 +18,7 @@ ncm_app_state_t g_st; ncm_play_run_t g_play = { .mu = PTHREAD_MUTEX_INITIALIZER, .state = NCM_PC_STOPPED, .cur_song = -1 }; +static int g_reported_act = -1; /* 任务4 keep_awake:已上报 launcher 的顶层 activity(差分防重发) */ volatile int g_app_quit = 0; volatile int g_net_ready = 0; int g_host_mock = 0; @@ -103,7 +104,15 @@ static void app_on_swipe(int dir, int dx, int dy) if (settings_is_active()) { meow_view_page_pop(); return; } if (playlist_is_active()) { meow_view_page_pop(); return; } if (search_is_active()) { meow_view_page_pop(); return; } - /* home / login = 根 → 退出 */ + if (home_is_active()) { + /* 任务2:home 根页左滑不再退出进程 → 请求 launcher 退到桌面/后台(进程+媒体会话保留)。 + * launcher M4 据 app.json background_audio:1 + 媒体会话 PLAYING 判保活;不保活才真终止。 + * 这里**不**置 g_app_quit / 不 meow_ui_request_exit——有歌在播则后台继续播(§M4)。 */ + meow_log(MEOW_LOG_INFO, "swipe home -> background (nav_exit)"); + ipc_media_nav_exit(); + return; + } + /* login 根(未登录):退出 */ meow_log(MEOW_LOG_INFO, "exit:swipe"); meow_ui_request_exit(); return; @@ -139,6 +148,15 @@ static void app_on_swipe_progress(int x_start, int dx) static void app_on_tick(void) { + /* 任务4 keep_awake:顶层 activity 变化 → 上报 launcher(MSG_ACT_FOCUS,差分一次)。 + * 页面切统一走 meow_view_page_push/pop/replace(更新 meow_ui_activity_active),此处差分上报: + * 进播放页 active=ACT_PLAYER → ipc_act_report("player") → launcher fg_keep_awake 判常亮; + * 退出播放页(弹回 home/playlist)→ 上报对应活动名(未声明 awake)→ 相机照常超时息屏。 + * 只在**当前顶层**变化时发一次(无 IPC 风暴;播放中 active 不变不重发)。 */ + { + int act = meow_ui_activity_active(); + if (act != g_reported_act) { g_reported_act = act; ipc_act_report(act); } + } uint64_t now = meow_ui_time_ms(); /* §NEW cookie 续期判会话失效 → 清登录态回登录页(worker 在后台续期发现 301/-460 时置标志; * 在此 UI 线程执行 logout+切页——meow_view_page_replace 非线程安全,只能 UI 线程做) */ @@ -154,6 +172,16 @@ static void app_on_tick(void) int ai = autoplay_ui_consume(); if (ai >= 0) player_play_song(ai); } + /* 任务3:通知中心媒体卡片点卡片体(launcher 发 NCM_CTL_FOCUS)→ 回到播放页。 + * ipc_media 线程只置标志(跨线程 page 导航非线程安全),UI 线程消费后 push ACT_PLAYER; + * 无在播曲则忽略(聚焦无意义)。 */ + if (ipc_media_focus_consume()) { + if (player_manager_state() != NCM_PC_STOPPED && + meow_ui_activity_active() != ACT_PLAYER) { + meow_log(MEOW_LOG_INFO, "ipc: focus -> player page"); + meow_view_page_push(ACT_PLAYER); + } + } if (login_is_active()) { login_tick(now); return; } if (player_is_active()) { player_tick(now); return; } if (search_is_active()) { search_tick(now); return; } diff --git a/patch_v2/ncm_app/app/logic/account.c b/patch_v2/ncm_app/app/logic/account.c index bf22955..0e80ba0 100644 --- a/patch_v2/ncm_app/app/logic/account.c +++ b/patch_v2/ncm_app/app/logic/account.c @@ -88,14 +88,14 @@ void account_load(void) int64_t last_refresh = ncm_json_get_i64_at(&j, ncm_json_path(&j, "last_refresh_ts"), &rc); if (rc != NCM_OK) last_refresh = 0; - if (vip < NCM_VIP_NONE || vip > NCM_VIP_SVIP) vip = NCM_VIP_NONE; + if (vip < NCM_VIP_NONE || vip > NCM_VIP_SUPER) vip = NCM_VIP_NONE; g_st.vip = (int)vip; g_st.vip_cvip_exp_ms = cvip_exp; g_st.vip_svip_exp_ms = svip_exp; g_st.vip_checked_at = checked; g_st.cookie_last_refresh_ts = last_refresh; - /* 有效到期时间 = 当前档对应的到期字段 */ - g_st.vip_expire_ms = (vip == NCM_VIP_SVIP) ? svip_exp + /* 有效到期时间 = 当前档对应的到期字段(SUPER 无独立到期字段,沿黑胶 svip 场)。 */ + g_st.vip_expire_ms = (vip == NCM_VIP_SVIP || vip == NCM_VIP_SUPER) ? svip_exp : (vip == NCM_VIP_CVIP) ? cvip_exp : 0; meow_log(MEOW_LOG_INFO, "account: loaded vip=%d expire=%lld last_refresh_ts=%lld", g_st.vip, (long long)g_st.vip_expire_ms, (long long)last_refresh); @@ -129,9 +129,10 @@ int account_vip(void) int account_level_cap(void) { switch (account_vip()) { - case NCM_VIP_SVIP: return NCM_LEVEL_SURROUND; - case NCM_VIP_CVIP: return NCM_LEVEL_HIRES; - default: return NCM_LEVEL_EXHIGH; + case NCM_VIP_SVIP: + case NCM_VIP_SUPER: return NCM_LEVEL_SURROUND; + case NCM_VIP_CVIP: return NCM_LEVEL_HIRES; + default: return NCM_LEVEL_EXHIGH; } } @@ -164,7 +165,7 @@ void account_vip_maybe_refresh(int force) g_st.vip = info.vip; g_st.vip_cvip_exp_ms = info.cvip_expire_ms; g_st.vip_svip_exp_ms = info.svip_expire_ms; - g_st.vip_expire_ms = (info.vip == NCM_VIP_SVIP) ? info.svip_expire_ms + g_st.vip_expire_ms = (info.vip == NCM_VIP_SVIP || info.vip == NCM_VIP_SUPER) ? info.svip_expire_ms : (info.vip == NCM_VIP_CVIP) ? info.cvip_expire_ms : 0; g_st.vip_checked_at = now_s; account_persist(info.vip, info.cvip_expire_ms, info.svip_expire_ms, now_s); diff --git a/patch_v2/ncm_app/app/logic/cover.c b/patch_v2/ncm_app/app/logic/cover.c index 708a9ef..03d3c27 100644 --- a/patch_v2/ncm_app/app/logic/cover.c +++ b/patch_v2/ncm_app/app/logic/cover.c @@ -278,8 +278,11 @@ static meow_img *download_cover(const char *url, ncm_cache_writer_t *w, size_t * /* 封面原始文件路径写入(供 launcher 媒体卡片读;兼容保留)。调用方须已持有 g_cm。 * 从 cache 写者临时文件把已下载字节复制到 cover_current.jpg(早前版本在此内再 lock g_cm * → 非递归锁自锁死——cover worker 永久卡死、封面永不出现)。 - * §NEW 缓存分目录:与歌曲分池,cover_current.jpg 落 covers/(封面条目 song_cov* 也在 covers/)。 */ -static void save_cover_raw(const char *path, size_t len) + * §NEW 缓存分目录:与歌曲分池,cover_current.jpg 落 covers/(封面条目 song_cov* 也在 covers/)。 + * REPORT §封面混用:is_song=0(歌单封面,无曲目 id)时**只写 cover_current.jpg 镜像、不更新 + * g_cover_path**——g_cover_path 是上报 launcher 通知中心媒体卡片的「当前播放曲」封面指针; + * 歌单页浏览加载歌单封面不应覆盖正在播放曲目的封面(用户真机:通知中心显示另一歌单封面)。 */ +static void save_cover_raw(const char *path, size_t len, int is_song) { if (!path || len == 0) return; const char *covdir = g_st.ctx.cache.covers_dir[0] ? g_st.ctx.cache.covers_dir @@ -301,17 +304,20 @@ static void save_cover_raw(const char *path, size_t len) } fclose(in); } - ncm_strlcpy(g_cover_path, dst, sizeof g_cover_path); + if (is_song) ncm_strlcpy(g_cover_path, dst, sizeof g_cover_path); } } #define COVER_RETRY_MAX 8 /* 连续失败放弃次数(UI 重进页/切歌会重新提交目标) */ /* 内存瘦身:封面解码后按显示上限降采样驻留。网易云全尺寸封面(如 1448×1448)解码成 - * RGBA = 8MB,而应用显示只需 ≤480px(播放页黑胶方图+歌词页模糊铺底均 ≤ 屏幕 480 宽)—— - * 缩到 480×480 = 0.9MB,省 ~7MB anon(设备 RAM 仅 56MB,OOM 真机被杀,最高优先级)。 - * 若已 ≤上限(小缩略图)则原样保留。返回本函数处理后的图(可能替换/释放旧图)。 */ -#define NCM_COVER_DECODE_MAX 480 + * RGBA = 8MB,而应用显示(歌词页 cover-fit 铺满 480×720 全屏,方图实际渲染到 720×720;封面页 + * 方图仅 304×304)——请求 param=640y640(见 ncm_app_internal.h)即 ≤640 边长,故此上限须 ≥ 640 + * 否则解码后又被降采样回 480、仍不够铺满 480 宽(480 图铺 480+ 显示照旧放大)。640×640 RGBA + * ≈ 1.6MB 单张驻留(设备 RAM 仅 56MB,OOM 真机被杀;比 308 瘦身 8.28MB 仍省 6.7MB,单张 1.6MB + * 在 32bit OOM 预算内——换封面仍先 free 旧再解码)。 + * 若已 ≤上限(小缩略图/无 param 全尺寸在 640 内)则原样保留。返回本函数处理后的图(可能替换/释放旧图)。 */ +#define NCM_COVER_DECODE_MAX 640 static meow_img *cover_cap(meow_img *im) { if (!im) return NULL; @@ -362,7 +368,7 @@ static void *cover_worker(void *arg) img = download_cover(url, w, &blen, &drc, &dstatus, &dkind); if (img) { pthread_mutex_lock(&g_cm); - save_cover_raw(ncm_cache_writer_tmp_path(w), blen); + save_cover_raw(ncm_cache_writer_tmp_path(w), blen, song[0] ? 1 : 0); pthread_mutex_unlock(&g_cm); if (ncm_cache_commit(w) != 0) { ncm_cache_abort(w); } } else { @@ -452,6 +458,21 @@ static void *cover_worker(void *arg) if (g_cover_img) meow_img_free(g_cover_img); g_cover_img = img; ncm_strlcpy(g_cover_url, url, sizeof g_cover_url); + /* 上报给 launcher 的封面路径 = 本目标专辑的缓存条目(per-URL 唯一)。 + * 旧码只在 save_cover_raw(下载路径)更新 g_cover_path;缓存命中路径从不更新 → + * cover_current_path() 恒空 → MSG_META artwork 恒空 → 通知中心媒体卡拿不到封面路径 + * → 回退显示歌名。此处统一在「目标达成换入」时把 g_cover_path 指向已落盘的缓存条目 + * (下载 commit 后 / 缓存命中时均真实存在);commit 失败则保留 save_cover_raw 已写的 + * cover_current.jpg 镜像(仍在)。路径随专辑变化 → launcher 侧「按 artwork 文本缓存」 + * 的现有实现能随切歌重载,不残留旧封面。 */ + if (song[0] && g_st.ctx.cache.dir[0]) { + char cpath[512]; + if (ncm_cache_path(&g_st.ctx.cache, key, ".jpg", cpath, sizeof cpath) == NCM_OK) { + struct stat cs; + if (stat(cpath, &cs) == 0 && cs.st_size > 0) + ncm_strlcpy(g_cover_path, cpath, sizeof g_cover_path); + } + } g_gen++; g_tgt_url[0] = 0; /* 目标达成即清:worker 转怠速(修忙等死循环,§18) */ } else { @@ -464,7 +485,8 @@ static void *cover_worker(void *arg) /* UI 线程:提交目标封面 URL(无网络/无 URL 则忽略)。song_id:曲目 id(专辑封面死链时走 * song/detail 兜底重取,见 REPORT §NEW 任务1);NULL/空=歌单封面(无兜底)。 - * 先 cover_norm_url 规范化(追加 ?param=300y300 缩略后缀,REPORT §NEW 任务2),使 + * 先 cover_norm_url 规范化(追加 ?param=640y640 缩略后缀,REPORT §NEW 任务2;640 适配歌词页 + * cover-fit 铺满 480×720,见 ncm_app_internal.h),使 * g_tgt_url/g_cover_url/缓存键/下载 URL 走同一参数版本(小图省内存、命中高)。 */ void cover_request(const char *url, const char *song_id) { diff --git a/patch_v2/ncm_app/app/logic/lyric.c b/patch_v2/ncm_app/app/logic/lyric.c index 0f92ce6..a676df0 100644 --- a/patch_v2/ncm_app/app/logic/lyric.c +++ b/patch_v2/ncm_app/app/logic/lyric.c @@ -50,6 +50,41 @@ static void copy_lrc_text(char *dst, const char *src, size_t n, size_t max) dst[oi] = 0; } +/* 解析单个 [mm:ss.ff] 时间标签为毫秒。返回 1=合法时间标签(*out_ms、*next=']' 之后); + * 0=非时间标签(*next=']' 之后;NULL=无闭合 ']',调用方按残缺处理)。 + * 小数位自适应:1 位=0.1s(×100)、2 位=0.01s(×10,标准 LRC 厘秒)、3 位=0.001s(×1, + * 网易云 LRC 毫秒)——此前固定 ×10 把网易云 3 位毫秒当 2 位厘秒解析,时间戳放大 ~10 倍, + * 歌词行按绝对时间激活滞后 ~4~10s(REPORT「英文歌歌词滚动慢」)。 */ +static int parse_lrc_tag(const char *p, long *out_ms, const char **next) +{ + const char *q = p + 1; /* 跳过 '[' */ + int m = 0, md = 0, s = 0, sd = 0; + while (*q >= '0' && *q <= '9') { m = m * 10 + (*q - '0'); md++; q++; } + if (md == 0 || *q != ':') goto notime; + q++; + while (*q >= '0' && *q <= '9') { s = s * 10 + (*q - '0'); sd++; q++; } + if (sd == 0) goto notime; + long frac = 0; int fd = 0; long scale = 10; + if (*q == '.') { + q++; + while (*q >= '0' && *q <= '9') { frac = frac * 10 + (*q - '0'); fd++; q++; } + if (fd == 1) scale = 100; + else if (fd == 2) scale = 10; + else if (fd >= 3) { scale = 1; while (fd > 3) { frac /= 10; fd--; } } /* >3 位截断到 ms */ + else scale = 0; /* 孤立 '.' 无数字:忽略小数 */ + } + if (*q != ']') goto notime; + *out_ms = (long)m * 60000L + (long)s * 1000L + frac * scale; + *next = q + 1; + return 1; +notime: + { + const char *r = strchr(p, ']'); + *next = r ? r + 1 : NULL; + return 0; + } +} + static void lrc_parse(const char *lrc, int is_trans) { if (!lrc) return; @@ -62,19 +97,16 @@ static void lrc_parse(const char *lrc, int is_trans) int have = 0; long first_ms = 0; while (*p == '[') { - int m = 0, s = 0, cs = 0; - if (sscanf(p, "[%d:%d.%d]", &m, &s, &cs) >= 2) { - long ms = (long)m * 60000L + (long)s * 1000L + (long)cs * 10L; + long ms; + const char *after; + if (parse_lrc_tag(p, &ms, &after)) { if (!have) { first_ms = ms; have = 1; } /* 跳到本标签结尾 */ - const char *q = strchr(p, ']'); - if (!q) break; - p = q + 1; + p = after; } else { /* 非时间标签([ti:..] 等)——跳过到行尾 */ - const char *q = strchr(p, ']'); - if (!q) return; - p = q + 1; + if (!after) return; /* 无闭合 ']':残缺文件 */ + p = after; /* 若行内还有时间标签继续解析(极少),否则整行的正文以第一个文本为准 */ break; } diff --git a/patch_v2/ncm_app/app/ncm_app_internal.h b/patch_v2/ncm_app/app/ncm_app_internal.h index 693a685..144f131 100644 --- a/patch_v2/ncm_app/app/ncm_app_internal.h +++ b/patch_v2/ncm_app/app/ncm_app_internal.h @@ -32,11 +32,19 @@ #define ACT_SEARCH 5 /* 搜索页(顶栏搜索按钮进入) */ /* ---- 封面 CDN 尺寸参数(REPORT §NEW 任务2:网易云图片 CDN 对 URL 追加 ?param=WxH 返等比 - * 缩略 JPEG——省内存(300×300≈20KB vs 全尺寸 1.34MB)+解 webp 三分型③+命中率高;已含 param= 的 - * URL 尊重原样)。共享头声明:cover.c 与 bench/cover_chain_sim.c(URL 期望哈希须与实测 URL 同规则) - * 同源,避免两处字符串漂移。 */ + * 缩略 JPEG——省内存 +解 webp 三分型③+命中率高;已含 param= 的 URL 尊重原样)。 + * 共享头声明:cover.c 与 bench/cover_chain_sim.c(URL 期望哈希须与实测 URL 同规则) + * 同源,避免两处字符串漂移。 + * 值选择(封面分辨率适配歌词页铺满,REPORT:歌词页 cover-fit 铺满 480×720 全屏,方图实际渲染到 + * 720×720;旧默认 300y300 → 300 放大到 720 = 2.4× 最近邻放大 → 锐锯齿): + * 640y640 → 歌词页 640→720 = 1.125× 轻微放大(铺满基本无锯齿),封面页方图 640→304 裁方降采样 + * 依旧锐利;解码 RGBA ≈ 640×640×4 ≈ 1.6MB 单张驻留(比 300 的 0.36MB +1.2MB anon, + * 仍远低于 308 瘦身前 8.28MB;32bit OOM 已由 meow_cachegov 治理 page cache,封面 anon + * 单张 1.6MB 在预算内——平衡清晰度与内存纪律)。 + * 注意 cover.c 的 NCM_COVER_DECODE_MAX 必须 ≥ 本参数边长,否则解码后仍被降采样回 480(不够铺 + * 满 480 宽 → 480 图铺 480+ 显示仍放大)。 */ #define NCM_COVER_USE_PARAM 1 -#define NCM_COVER_PARAM "300y300" +#define NCM_COVER_PARAM "640y640" /* ---- 布局 ---- */ #define APP_W 480 @@ -120,6 +128,9 @@ enum { NCM_CTL_NEXT = 4, NCM_CTL_SEEK = 5, NCM_CTL_TOGGLE = 6, /* 播放/暂停切换(物理键 164 恒发此;按当前态翻转) */ + NCM_CTL_FOCUS = 7, /* launcher 通知中心媒体卡片点卡片体 → 请求 App 回到播放页 + (任务3 §re-entry:launcher 侧点卡片体发本 cmd;App 置标志, + UI 线程消费后导航 ACT_PLAYER,进程已后台保活则直接聚焦) */ }; /* 跨线程播放运行时状态(main 只读展示;play 线程写,IPC 线程读)。 */ @@ -142,6 +153,7 @@ typedef struct ncm_play_run { int underruns; /* 本曲 read_cb 追上下载的等待次数 */ uint64_t underrun_ms; /* 本曲 read_cb 累计等待 ms */ int stalled; /* 1=下载停滞(单次等待>1s,播放线程已对 audiod pause;UI 缓冲菊花) */ + int refaults; /* 本曲 DONTNEED 丢页重读 TF 次数(§2.3 取证) */ /* 播放曲快照(REPORT §17):run_song 起播时把歌曲对象整体拷入(mu 保护), * 之后播放/歌词/IPC/UI 全读快照而非窗口——窗口滑出当前播放曲也不影响播放。 */ ncm_song_t play_song; @@ -481,6 +493,9 @@ int audio_out_open(int sample_rate, int channels, int bits); /* 0=成功;bi int audio_out_write(const void *samples, int frames); /* <0=错误/结束;样本位宽=open 的 bits */ int audio_out_pause(int paused); /* 1=暂停(停 PCM 时钟不丢流) 0=恢复(流死自动重开);非 AUDIOD 后端恒 0 */ void audio_out_close(void); +int audio_out_sync_volume(void); /* 任务1:读 settings.conf 当前系统音量并下发到 audiod + (MSG_AUDIO_VOLUME→写 DAC+持久化)。0=下发成功/-1=失败。 + audio_out_open SINK_AUDIOD 成功后已自动调;独立导出供宿主验证与复调。 */ void audio_out_set_target(int ms); /* 预留:缓冲目标 */ int audio_out_buffered_ms(void); @@ -515,6 +530,13 @@ int songmeta_save(const char *cache_dir, const char *song_id, const ncm_song_t /* ipc_media:媒体会话上报/收指令 */ int ipc_media_start(void); /* 起 IPC 线程;0=成功 */ void ipc_media_stop(void); +int ipc_media_nav_exit(void); /* 任务2:发 MSG_NAV{EXIT} 给 launcher 请求退后台(进程+媒体会话保留; + launcher M4 据 background_audio+PLAYING 判保活)。0=发出/-1=失败。 */ +int ipc_media_focus_consume(void); /* 任务3:UI 线程消费「回播放页」标志(0=无;1=已消费一次) */ +int ipc_act_report(int act); /* 任务4 keep_awake:向 launcher 上报当前顶层 activity(MSG_ACT_FOCUS)。 + act=ACT_* 值,映射 "login/home/playlist/player/settings/search"(对齐 + app.json awake. 键);launcher fg_keep_awake 依声明判常亮。 + 0=发出/-1=无 socket 或写失败。 */ /* ---- 宿主测试面(app/test/test_ncm_app.c 驱动真实页面)---- */ void app_host_set_mock(int mode); /* 0=无 1=home 2=playlist 3=player 4=settings 5=login */ diff --git a/patch_v2/ncm_app/app/play/audio_out.c b/patch_v2/ncm_app/app/play/audio_out.c index aa0931c..6ec6184 100644 --- a/patch_v2/ncm_app/app/play/audio_out.c +++ b/patch_v2/ncm_app/app/play/audio_out.c @@ -22,13 +22,15 @@ * (无声排查的永久资产,勿省)。 */ #include "../ncm_app_internal.h" -#include "meow_ipc.h" /* AUDIO_FMT_S16 */ +#include "meow_ipc.h" /* AUDIO_FMT_S16 / MSG_AUDIO_VOLUME / MEOW_IPC_HDR_LEN */ #include #include #include #include #include #include +#include +#include #ifdef __linux__ #include @@ -143,6 +145,111 @@ static int file_open(int rate, int ch, int bits) return 0; } +/* ============================================================ */ +/* 任务1:开播前把「系统当前音量」同步下发到 audiod */ +/* ============================================================ */ +/* 背景(用户实测 2026-09-02):未开始播放时音量不是系统设定值;按一下音量键才跳到设定值。 + * 根因= ncm 启动并打开 audiod/DAC 时,没有把系统当前音量下发到 audiod(meow_audiod 只在 + * MSG_AUDIO_VOLUME 收到时才写 DAC,流开/重配上不主动应用 settings 音量),导致 DAC 停在 + * 默认/上次残留音量;用户按音量键时 launcher/watchdog 那条音量链才更新并下发到 audiod。 + * 修法= SINK_AUDIOD open 成功后读 settings.conf 的 audio.volume,经 MSG_AUDIO_VOLUME 下发 + * (audiod 写 DAC numid=1/2 + 持久化 settings,同源同键)。语意:CS43198 寄存器=衰减量 + * (0 最响/255 静音),audiod dac_vol_set 负责反转与真静音(写 255),本处只传 pct。 + * 宿主验证:fake audiod socket 收帧断言 pct==系统设定(bench/vol_sync_sim.c)。 */ + +#define NCM_SETTINGS_CONF "/usr/data/meow/settings.conf" /* 与 audiod SETTINGS_CONF 同源 */ +#define NCM_AUDIO_SOCK_DEF "/usr/data/meow_audio.sock" /* 与 audiod / SDK 客户端同缺省 */ +#define NCM_VOL_DEFAULT 60 /* 与 audiod VOL_DEFAULT/SDK 缺省一致 */ +#define NCM_VOL_MAX 100 + +static const char *settings_conf_path(void) +{ + const char *e = getenv("MEOW_SETTINGS_FILE"); /* 宿主单测指向临时文件;设备缺省系统 conf */ + return (e && e[0]) ? e : NCM_SETTINGS_CONF; +} + +/* 读 settings.conf 的 audio.volume(扁平 "key = value";兼容无空格/多空格)。 + * 与 launcher meow_settings / SDK meow_ui_vol_read 同款解析。缺省 60;夹到 [0,100]。 */ +static int settings_read_volume(void) +{ + static const char key[] = "audio.volume"; + const int kl = (int)sizeof key - 1; + FILE *f = fopen(settings_conf_path(), "r"); + int vol = NCM_VOL_DEFAULT; + if (!f) return vol; + char line[160]; + while (fgets(line, sizeof line, f)) { + char *eq = strchr(line, '='); + if (!eq) continue; /* 非 key=value 行跳过 */ + char *k = line; + while (*k == ' ' || *k == '\t') k++; /* key 左去空白 */ + int n = (int)(eq - k); + while (n > 0 && (k[n - 1] == ' ' || k[n - 1] == '\t')) n--; /* key 右去空白 */ + if (n != kl || strncmp(k, key, (size_t)kl) != 0) continue; /* 精确匹配 */ + char *v = eq + 1; + while (*v == ' ' || *v == '\t') v++; /* value 左去空白 */ + int x = atoi(v); + vol = (x < 0) ? 0 : (x > NCM_VOL_MAX ? NCM_VOL_MAX : x); + break; + } + fclose(f); + return vol; +} + +/* 把 pct(0-100) 下发到 audiod。独立连接(SDK 客户端连接私有不可复用);MSG_AUDIO_VOLUME + * 不要求开流,audiod 侧写 DAC+持久化后 ACK(meow_audiod.c MSG_AUDIO_VOLUME)。失败仅日志, + * 不影响播放本身(音量是次要正确性,非播放成败)。 + * + * §2026-09-02 连接风暴根因:audiod_sync_volume 每次 audio_out_open 都新建一条 AF_UNIX + * 短连接(socket→connect→write→close)。边下边播/切歌/seek 反复 open/close 时,audiod 端表现为 + * 「同一 fd=N 被反复 accept」(真机 1489 次 "io: client connected (fd=4)")。音量值实际只在 + * 用户按音量键时变化,且 audiod 收到已持久化(settings_set_volume),无需每次 open 重发—— + * 修法:每进程首次 open 同步一次;此后仅当 settings 音量**变化**才重发(同一连接数坍缩为 + * O(1),风暴消除)。失败不记 status(下次 open 仍会尝试,幂等)。 */ +static void audiod_sync_volume(int pct) +{ + const char *sp = getenv("MEOW_AUDIO_SOCK"); + if (!sp || !sp[0]) sp = NCM_AUDIO_SOCK_DEF; + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) return; + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, sp, sizeof a.sun_path - 1); + if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return; } + + uint8_t frame[MEOW_IPC_HDR_LEN + sizeof(int32_t)]; + uint32_t magic = MEOW_IPC_MAGIC, len = sizeof(int32_t), req = 1; + uint16_t ver = MEOW_IPC_VERSION, type = MSG_AUDIO_VOLUME; + memcpy(frame + 0, &magic, 4); + memcpy(frame + 4, &ver, 2); + memcpy(frame + 6, &type, 2); + memcpy(frame + 8, &req, 4); + memcpy(frame + 12, &len, 4); + int32_t v32 = (int32_t)pct; + memcpy(frame + 16, &v32, sizeof v32); + if (write(fd, frame, sizeof frame) != (ssize_t)sizeof frame) + meow_log(MEOW_LOG_WARN, "audio: sync volume write failed pct=%d", pct); + close(fd); +} + +/* 每进程同步一次 + 值变才重发:杜绝每次 audio_out_open 新建短连接(fd=N accept 风暴根因)。 + * g_vol_synced=1 即已同步过;g_vol_last=上次下发的 pct,二者相同则零连接直接返回。 */ +static int g_vol_synced = 0; +static int g_vol_last = -1; + +int audio_out_sync_volume(void) +{ + int pct = settings_read_volume(); + if (g_vol_synced && pct == g_vol_last) return 0; /* 已同步且未变:零新建连接 */ + g_vol_synced = 1; + g_vol_last = pct; + audiod_sync_volume(pct); + meow_log(MEOW_LOG_INFO, "audio: sync volume -> audiod pct=%d (settings %s)", + pct, settings_conf_path()); + return 0; +} + int audio_out_open(int sample_rate, int channels, int bits) { g_rate = sample_rate; g_ch = channels; g_bits = bits; @@ -202,6 +309,9 @@ int audio_out_open(int sample_rate, int channels, int bits) else meow_log(MEOW_LOG_INFO, "audio: audiod open ok sid=%d req=%dHz/%dch/%dbit", sid, sample_rate, channels, bits); + /* 任务1:开播/打开音频会话时把系统当前音量同步下发到 audiod(DAC 停在默认/上次残留音量的根治)。 + * 幂等(同值重写无害);失败不阻断播放。 */ + audio_out_sync_volume(); return 0; } diff --git a/patch_v2/ncm_app/app/play/ipc_media.c b/patch_v2/ncm_app/app/play/ipc_media.c index d5b4156..3246c77 100644 --- a/patch_v2/ncm_app/app/play/ipc_media.c +++ b/patch_v2/ncm_app/app/play/ipc_media.c @@ -21,6 +21,7 @@ static pthread_t g_thr; static int g_thr_on; static int g_quit; +static volatile int g_req_focus_player = 0; /* 任务3:launcher 请求「回到播放页」(UI 线程消费) */ static const char *sock_path(void) { @@ -151,6 +152,11 @@ static void apply_playctl(const m_playctl_t *c) * (停止/缓冲)时 pause() 内部按态过滤,天然 no-op 安全。 */ player_manager_pause(player_manager_state() == NCM_PC_PAUSED ? 0 : 1); break; + case NCM_CTL_FOCUS: + /* 任务3:通知中心媒体卡片点「卡片体」→ launcher 发本 cmd(需要 launcher 接线,见任务书): + * 请求 App 回到播放页。只置标志(跨线程),UI 线程 app_on_tick 消费后导航(page nav 非线程安全)。 */ + g_req_focus_player = 1; + break; default: break; } } @@ -201,6 +207,78 @@ static void *ipc_thread(void *arg) return NULL; } +/* 任务2:home 根页左滑「回桌面后台」——不再进程退出,改发 MSG_NAV{EXIT} 给 launcher。 + * launcher M4 据 app.json background_audio + 媒体会话 PLAYING(meow_media_app_playing)判 + * 保活(进程+音频流保留转后台,UI 走 §10.5 SDK present 自抑,非 SIGSTOP);否则才真终止。 + * 本函数**不**置 g_app_quit、**不**停线程——进程保留,媒体会话继续上报 PLAYING(任务2 ③)。 + * 独立连接发一帧(launcher ipc_dispatch MSG_NAV{EXIT} → meow_nav_request_exit,app_launcher.c:571)。 + * 返回 0=发出 / -1=无 socket 或写失败。 */ +int ipc_media_nav_exit(void) +{ + int fd = connect_path(sock_path()); + if (fd < 0) { + meow_log(MEOW_LOG_WARN, "ipc: nav_exit no launcher socket"); + return -1; + } + m_nav_t n; + memset(&n, 0, sizeof n); + ncm_strlcpy(n.app_id, "dev.player.ncm", sizeof n.app_id); + n.nav_type = MEOW_NAV_EXIT; + n.depth = 0; + int rc = send_frame(fd, MSG_NAV, 9, &n, sizeof n); + close(fd); + meow_log(MEOW_LOG_INFO, "ipc: nav_exit sent rc=%d (request launcher background)", rc); + return rc; +} + +/* 任务4 keep_awake:ACT_* 值 → launcher app.json awake/route 键用的活动名。 + * 命中的只有这些(ncm_app_internal.h ACT_* 枚举);未知/空栈(0) 返回 NULL(不起作用)。 */ +static const char *ncm_act_name(int act) +{ + switch (act) { + case ACT_LOGIN: return "login"; + case ACT_HOME: return "home"; + case ACT_PLAYLIST: return "playlist"; + case ACT_PLAYER: return "player"; + case ACT_SETTINGS: return "settings"; + case ACT_SEARCH: return "search"; + default: return NULL; + } +} + +/* 任务4 keep_awake:把当前顶层 activity 上报 launcher(MSG_ACT_FOCUS,独立连接发一帧,无需 HELLO)。 + * launcher util.c ipc_dispatch MSG_ACT_FOCUS → meow_fg_act_set(app,act);fg_keep_awake() 依 + * app.json awake.=1 判定该 activity 是否常亮(超时不自动息屏)。无 socket/launcher 不跑 → 优雅跳过。 + * 返回 0=发出 / -1=无 socket 或写失败。 */ +int ipc_act_report(int act) +{ + const char *name = ncm_act_name(act); + if (!name) return -1; + int fd = connect_path(sock_path()); + if (fd < 0) { + meow_log(MEOW_LOG_WARN, "ipc: act_focus(%s) no launcher socket", name); + return -1; + } + m_act_focus_t f; + memset(&f, 0, sizeof f); + ncm_strlcpy(f.app_id, "dev.player.ncm", sizeof f.app_id); + ncm_strlcpy(f.activity, name, sizeof f.activity); + int rc = send_frame(fd, MSG_ACT_FOCUS, 10, &f, sizeof f); + close(fd); + meow_log(MEOW_LOG_INFO, "ipc: act_focus act=%s rc=%d", name, rc); + return rc; +} + +/* 任务3:UI 线程消费「回到播放页」标志。返回 1=有未消费的聚焦请求(消费一次)。 */ +int ipc_media_focus_consume(void) +{ + if (g_req_focus_player) { + g_req_focus_player = 0; + return 1; + } + return 0; +} + int ipc_media_start(void) { if (g_thr_on) return 0; diff --git a/patch_v2/ncm_app/app/play/mem_watch.c b/patch_v2/ncm_app/app/play/mem_watch.c new file mode 100644 index 0000000..11bbb4a --- /dev/null +++ b/patch_v2/ncm_app/app/play/mem_watch.c @@ -0,0 +1,201 @@ +/* play/mem_watch.c — 边播边监控**取证**实现(接口见 mem_watch.h;agent 32bit 大文件 OOM 取证)。 + * + * 归属演进(2026-09-02 定案,ARCHITECTURE_PRINCIPLES ③混合):**降载决策上收通用层**—— + * A(DONTNEED 治理)+ C(/proc/meminfo Cached 监控)分别归 SDK meow_cachegov / meow_svc_perf; + * 本模块退化为「RssAnon 取证」:低频读 /proc/self/status(优先 RssAnon,缺失回退 VmRSS), + * 记录匿名内存峰值,**仅供日志**。不再驱动下载/预取降载(那由 meow_cachegov_download_paused + * / prefetch_paused 查询)。**绝不打断解码/播放**。 + * + * 阈值滞后(仅用于取证 level 标注): + * kb >= CRITICAL → level=2;kb >= HIGH 且当前<2 → level=1;kb <= LOW → level=0;其它保持。 + * 本模块自含:只依赖 pthread + meow_log(SDK 日志),不依赖 core/app 其它 TU。 + */ +#include "mem_watch.h" +#include "../sdk/include/meow_ui_log.h" + +#include +#include +#include +#include +#include + +/* 默认阈值(设备缺省;宿主单测用 env/force 桩覆盖) */ +#define MEM_WATCH_PERIOD_MS 500 +#define MEM_WATCH_HIGH_KB (30 * 1024) +#define MEM_WATCH_LOW_KB (20 * 1024) +#define MEM_WATCH_CRITICAL_KB (40 * 1024) + +struct mw_cfg { + int period_ms; + long high_kb; + long low_kb; + long critical_kb; +}; + +static struct mw_cfg g_cfg = { + .period_ms = MEM_WATCH_PERIOD_MS, + .high_kb = MEM_WATCH_HIGH_KB, + .low_kb = MEM_WATCH_LOW_KB, + .critical_kb = MEM_WATCH_CRITICAL_KB, +}; + +static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER; +static pthread_t g_thr; +static int g_thr_on; +static volatile int g_quit; +static long g_rss_anon_kb; /* 最近一次采样(KB;-1=不可读) */ +static long g_peak_song_kb; /* 本曲匿名峰值(song_begin 清零) */ +static int g_level; /* NCM_MEM_LVL_*(仅取证日志) */ +static long g_forced_kb = -1; /* 宿主桩 ≥0 时替代真读 */ + +/* env 覆盖阈值:非法值回落默认。 */ +static int env_int(const char *k, int def) +{ + const char *e = getenv(k); + if (!e || !e[0]) return def; + int v = atoi(e); + return v > 0 ? v : def; +} +static long env_long(const char *k, long def) +{ + const char *e = getenv(k); + if (!e || !e[0]) return def; + long v = atol(e); + return v > 0 ? v : def; +} + +/* 读匿名 RSS(KB)。优先 RssAnon;缺则回退 VmRSS;都无 → -1。 */ +static long read_rss_kb(void) +{ + pthread_mutex_lock(&g_mu); + long forced = g_forced_kb; + pthread_mutex_unlock(&g_mu); + if (forced >= 0) return forced; + + FILE *fp = fopen("/proc/self/status", "r"); + if (!fp) return -1; + char line[256]; + long anon = -1, rss = -1; + while (fgets(line, sizeof line, fp)) + if (!strncmp(line, "RssAnon:", 8)) anon = atol(line + 8); + else if (rss < 0 && !strncmp(line, "VmRSS:", 6)) rss = atol(line + 6); + fclose(fp); + return anon >= 0 ? anon : rss; +} + +static const char *level_str(int lvl) +{ + switch (lvl) { + case NCM_MEM_LVL_HIGH: return "high"; + case NCM_MEM_LVL_CRITICAL: return "critical"; + default: return "normal"; + } +} + +static void *watch_thread(void *arg) +{ + (void)arg; + while (!g_quit) { + long kb = read_rss_kb(); + int lvl; + pthread_mutex_lock(&g_mu); + g_rss_anon_kb = kb; + if (kb > g_peak_song_kb) g_peak_song_kb = kb; + long hi = g_cfg.high_kb, lo = g_cfg.low_kb, cr = g_cfg.critical_kb; + /* 滞后三档(仅取证 level 标注) */ + if (kb >= cr) lvl = NCM_MEM_LVL_CRITICAL; + else if (kb >= hi) lvl = (g_level == NCM_MEM_LVL_CRITICAL) ? NCM_MEM_LVL_CRITICAL : NCM_MEM_LVL_HIGH; + else if (kb <= lo) lvl = NCM_MEM_LVL_NORMAL; + else lvl = g_level; /* 滞后带:保持 */ + if (lvl != g_level) { + meow_log(MEOW_LOG_INFO, + "[mem] rss_anon=%ldKB peak=%ldKB level=%s (H=%ldK/L=%ldK/C=%ldK)", + kb, g_peak_song_kb, level_str(lvl), hi, lo, cr); + } + g_level = lvl; + pthread_mutex_unlock(&g_mu); + usleep((useconds_t)g_cfg.period_ms * 1000u); + } + return NULL; +} + +int mem_watch_start(void) +{ + pthread_mutex_lock(&g_mu); + if (g_thr_on) { pthread_mutex_unlock(&g_mu); return 0; } + /* 开关(默认开;NCM_MEM_WATCH_ENABLED=0/off 关闭监控,行为退化为旧版零侵入) */ + const char *en = getenv("NCM_MEM_WATCH_ENABLED"); + if (en && (!en[0] || !strcmp(en, "0") || !strcmp(en, "off") || !strcmp(en, "false"))) { + pthread_mutex_unlock(&g_mu); + meow_log(MEOW_LOG_INFO, "[mem] watch disabled (NCM_MEM_WATCH_ENABLED=%s)", en); + return 0; + } + /* 阈值 env 覆盖(宿主单测) */ + g_cfg.period_ms = env_int("NCM_MEM_WATCH_PERIOD_MS", MEM_WATCH_PERIOD_MS); + g_cfg.high_kb = env_long("NCM_MEM_WATCH_HIGH_KB", MEM_WATCH_HIGH_KB); + g_cfg.low_kb = env_long("NCM_MEM_WATCH_LOW_KB", MEM_WATCH_LOW_KB); + g_cfg.critical_kb = env_long("NCM_MEM_WATCH_CRITICAL_KB", MEM_WATCH_CRITICAL_KB); + g_quit = 0; + pthread_attr_t a; + pthread_attr_init(&a); + pthread_attr_setstacksize(&a, (size_t)(128 * 1024)); /* 同 ncm_thread_create 128KB(内存瘦身,OOM 修复) */ + int rc = pthread_create(&g_thr, &a, watch_thread, NULL); + pthread_attr_destroy(&a); + if (rc != 0) { pthread_mutex_unlock(&g_mu); return -1; } + g_thr_on = 1; + pthread_mutex_unlock(&g_mu); + meow_log(MEOW_LOG_INFO, + "[mem] watch started period=%dms H=%ldK L=%ldK C=%ldK (RssAnon 优先)", + g_cfg.period_ms, g_cfg.high_kb, g_cfg.low_kb, g_cfg.critical_kb); + return 0; +} + +void mem_watch_stop(void) +{ + g_quit = 1; + pthread_mutex_lock(&g_mu); + if (g_thr_on) { g_thr_on = 0; pthread_detach(g_thr); } + pthread_mutex_unlock(&g_mu); +} + +int mem_watch_level(void) +{ + pthread_mutex_lock(&g_mu); + int v = g_level; + pthread_mutex_unlock(&g_mu); + return v; +} + +long mem_watch_rss_anon_kb(void) +{ + pthread_mutex_lock(&g_mu); + long v = g_rss_anon_kb; + pthread_mutex_unlock(&g_mu); + return v; +} + +void mem_watch_song_begin(void) +{ + long kb = read_rss_kb(); /* 起播即采样当前内存,本曲峰值从当前起算 */ + pthread_mutex_lock(&g_mu); + g_peak_song_kb = kb; + pthread_mutex_unlock(&g_mu); + meow_log(MEOW_LOG_INFO, "[mem] song begin rss_anon=%ldKB", kb); +} + +/* 播完/切歌释放钩子之后调用:落本曲峰值 + 重置计数。 */ +void mem_watch_song_end(void) +{ + pthread_mutex_lock(&g_mu); + long peak = g_peak_song_kb; + g_peak_song_kb = g_rss_anon_kb; + pthread_mutex_unlock(&g_mu); + meow_log(MEOW_LOG_INFO, "[mem] song end peak_anon=%ldKB (released)", peak); +} + +void mem_watch_set_forced_kb(long kb) +{ + pthread_mutex_lock(&g_mu); + g_forced_kb = kb; + pthread_mutex_unlock(&g_mu); +} diff --git a/patch_v2/ncm_app/app/play/mem_watch.h b/patch_v2/ncm_app/app/play/mem_watch.h new file mode 100644 index 0000000..b839a23 --- /dev/null +++ b/patch_v2/ncm_app/app/play/mem_watch.h @@ -0,0 +1,55 @@ +/* play/mem_watch.h — 边播边监控**取证**(agent 32bit 大文件 OOM 取证;OOM_PAGECACHE_GOVERNANCE)。 + * + * 归属演进(2026-09-02 定案,ARCHITECTURE_PRINCIPLES ③混合):**降载决策上收通用层**—— + * A(DONTNEED 治理)+ C(/proc/meminfo Cached 监控)分别归 SDK meow_cachegov / 系统服务 + * meow_svc_perf;本模块退化为「RssAnon 取样取证」:一个轻量线程(ncm_thread_create 同款 + * 128KB 栈)低频(默认 500ms)读 /proc/self/status 的 RssAnon(缺失回退 VmRSS),记录匿名 + * 内存峰值(RssAnon 贴近「应用自有不可回收内存」,是 OOM kill 高危口径),**仅供日志/取证**, + * 不再驱动下载/预取降载(那由 meow_cachegov_download_paused/prefetch_paused 查询)。 + * + * 调度器结合释放钩子:mem_watch_song_begin/end 在 run_song 的起播/收尾(song_src_dispose + + * ncm_dec_close + audio_out_close,即「彻底释放」钩子)处调用,重置本曲峰值计数并落日志 + * (峰值 RSS 即 RssAnon/RssFile 分离取证锚点;Cached/Dirty 侧取证归 svc_perf/meow_cachegov)。 + * + * 阈值/开关可通过环境变量覆盖(宿主单测确定性驱动):NCM_MEM_WATCH_ENABLED / + * NCM_MEM_WATCH_PERIOD_MS / NCM_MEM_WATCH_HIGH_KB / NCM_MEM_WATCH_LOW_KB / NCM_MEM_WATCH_CRITICAL_KB。 + * 宿主测试桩:mem_watch_set_forced_kb() 注入读值(>=0 时监控线程不再读真 proc,仅测取证逻辑)。 + */ +#ifndef NCM_MEM_WATCH_H +#define NCM_MEM_WATCH_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* 内存压力档(仅取证日志用;不再驱动降载) */ +enum { + NCM_MEM_LVL_NORMAL = 0, /* < HIGH */ + NCM_MEM_LVL_HIGH = 1, /* ≥ HIGH */ + NCM_MEM_LVL_CRITICAL = 2, /* ≥ CRITICAL */ +}; + +/* 启动监控线程(幂等;ncm_thread_create 128KB 栈)。返回 0=ok / -1=线程建失败。 */ +int mem_watch_start(void); +/* 停止(置退出标志;线程 detach,不 join——同 ipc_media 模式)。 */ +void mem_watch_stop(void); + +/* 当前压力档(0/1/2;仅取证日志)。 */ +int mem_watch_level(void); +/* 最近一次采样的匿名 RSS(KB);-1=不可读。 */ +long mem_watch_rss_anon_kb(void); + +/* ---- 调度器:与播放释放钩子联动 ---- */ +/* 起播一首曲时调用:本曲峰值计数清零,开始记录本曲内存画像。 */ +void mem_watch_song_begin(void); +/* 播完/切歌、即「彻底释放」钩子(decoder 关+下载线程 join+源关+.part 收尾)执行后调用: + * 落本曲峰值日志 + 重置计数,给下一首让出统计(内存本身已由释放钩子归还)。 */ +void mem_watch_song_end(void); + +/* ---- 宿主测试桩(设备永不调用;≥0 时监控线程用注入值替代真 /proc 读)---- */ +void mem_watch_set_forced_kb(long kb); + +#ifdef __cplusplus +} +#endif +#endif /* NCM_MEM_WATCH_H */ diff --git a/patch_v2/ncm_app/app/play/player_manager.c b/patch_v2/ncm_app/app/play/player_manager.c index 238bb7a..d19441a 100644 --- a/patch_v2/ncm_app/app/play/player_manager.c +++ b/patch_v2/ncm_app/app/play/player_manager.c @@ -32,6 +32,7 @@ #include #include "decoder.h" #include "stream_src.h" +#include "mem_watch.h" static pthread_t g_thr; static int g_thr_on; @@ -43,6 +44,10 @@ static char g_path[512]; /* 本地音频路径(缓存,mp3/flac static int64_t g_file_bytes; static int g_level; /* 音质档(来自设置) */ static int g_cache_gb; +/* 任务3(§re-entry):pending_replay=1 表示「切音质/缓存后点了正在播的同曲」——此时重播须换档 + * (P12 语义,seq++),不得被「同曲不重播」护栏吞掉。set_quality 置位;player_manager_play 的 + * 同曲护栏据它分流:pending=1 走重播(换档),pending=0 只聚焦不重播(点正在播的曲)。 */ +static int g_pending_replay; /* 被抢占回调(SDK 读线程分发;audiod 后开流赢):走 g_play 命令通道暂停(内部持锁), * 不直接动锁外状态。恢复时 audio_out_pause(0) 会检测流死并自动重开(见 audio_out.c)。 */ @@ -90,12 +95,25 @@ void player_manager_set_quality(int level, int cache_gb) pthread_mutex_lock(&g_play.mu); g_play.err = 0; ncm_cache_set_limit_gb(&g_st.ctx.cache, cache_gb); + g_pending_replay = 1; /* 任务3:改档后点正在播的同曲 → 强制换档重播(§25 P12) */ pthread_mutex_unlock(&g_play.mu); } void player_manager_play(int song_idx) { pthread_mutex_lock(&g_play.mu); + /* 任务3:点「正在播的曲」(同 idx 且 PLAYING/PAUSED,非切档重播)→ 只聚焦不重播: + * 不 seq++、不置 BUFFER——旧曲继续出声/停原状态,UI 由调用方 nav 到播放页。 + * 注意:set_quality 已置 g_pending_replay=1(切音质后重播同曲须换档,P12),此护栏放行。 + * 非 PLAYING/PAUSED(STOPPED/另一曲)不受影响,照旧起播。 */ + if (song_idx == g_play.cur_song && !g_pending_replay && + (g_play.state == NCM_PC_PLAYING || g_play.state == NCM_PC_PAUSED)) { + pthread_mutex_unlock(&g_play.mu); + meow_log(MEOW_LOG_INFO, "play: re-enter current idx=%d state=%d -> no restart", + song_idx, g_play.state); + return; + } + g_pending_replay = 0; /* 消费掉(新播放意图落地,后续同曲 tap 恢复「只聚焦」) */ g_play.cur_song = song_idx; g_play.cmd = 0; g_play.state = NCM_PC_BUFFER; @@ -108,6 +126,7 @@ void player_manager_play(int song_idx) g_play.underruns = 0; /* §21:边下边播统计随新播放意图清零 */ g_play.underrun_ms = 0; g_play.stalled = 0; + g_play.refaults = 0; /* §2.3:refault 取证随新播放意图清零 */ pthread_mutex_unlock(&g_play.mu); } @@ -135,6 +154,7 @@ void player_manager_next(int dir) int cnt = appstate_queue_count(); /* §23:按队列来源(窗口全量 或 搜索条数) */ if (c >= cnt) c = cnt - 1; if (c != g_play.cur_song) { + g_pending_replay = 0; /* 新播放意图落地:清「切档重播」待办 */ g_play.cur_song = c; g_play.cmd = 0; g_play.state = NCM_PC_BUFFER; @@ -147,6 +167,7 @@ void player_manager_next(int dir) g_play.underruns = 0; g_play.underrun_ms = 0; g_play.stalled = 0; + g_play.refaults = 0; } pthread_mutex_unlock(&g_play.mu); } @@ -154,6 +175,7 @@ void player_manager_next(int dir) void player_manager_stop(void) { pthread_mutex_lock(&g_play.mu); + g_pending_replay = 0; /* 停止:清「切档重播」待办 */ g_play.cur_song = -1; g_play.state = NCM_PC_STOPPED; g_play.pos_ms = 0; @@ -189,8 +211,19 @@ typedef struct { int *st_ptr; /* 指向 dl_thread 的 status 局部变量(同线程,无竞态) */ int need_fresh; /* 416/其它:服务器拒绝续传 → 放 .part、交调用方整首重下 */ uint64_t skip; /* 200 忽略 Range:body 前 skip 字节与 .part 前缀重复,丢弃 */ + /* page-cache 治理(A:meow_cachegov)登记路径:播放实际读的文件(流式=.tmp/.part,命中=final)。 */ + char gov_path[512]; + /* 曲终 L0 用:commit 后的正式路径(流式 commit rename 后 = final;命中=gov_path 同)。 */ + char gov_final_path[512]; } song_src_t; +/* page-cache 治理(A):SDK meow_cachegov 的 read_pos 回调——返回当前解码逻辑读指针 + * (stream_src 内部维护,经 stream_src_read_pos 读取;回调上下文=stream_src*)。 */ +static uint64_t gov_read_pos(void *ctx) +{ + return stream_src_read_pos((stream_src_t *)ctx); +} + /* http 体回调(下载线程上下文;ncm_http 每次投递 ≤4KB):write(2) 直写落盘 + * 锁内推进 written + broadcast 唤醒读端 + 进度反馈。 * 阶段3 续传(§5.2):首 body 字节按 response status 切换写姿态—— @@ -201,6 +234,11 @@ static int stream_dl_cb(void *ctx, const unsigned char *data, size_t len) { song_src_t *ss = (song_src_t *)ctx; if (stream_src_aborted(ss->src)) return 1; /* 切歌/停止:中止 http */ + /* page-cache 治理(§2.3/§3):下载 cb 查 meow_cachegov_download_paused()(A 回退硬暂停 + * OR svc_perf C Cached 降载)→ 暂停下载(限速),优先解码播放;不打断播放线程 + * (读端追上下载走既有 underrun/停滞联动)。每 50ms 查一次中止,切歌可立即脱身。 */ + while (meow_cachegov_download_paused() && !stream_src_aborted(ss->src)) + usleep(50 * 1000); if (ss->first_body) { ss->first_body = 0; int st = ss->st_ptr ? *ss->st_ptr : 0; @@ -295,6 +333,15 @@ static void song_src_dispose(song_src_t *ss) if (ss->src) { stream_src_close(ss->src); ss->src = NULL; } } +/* page-cache 治理(A)曲终清理:注销 L1 登记条目 + 下载完整(commit 成功)则对正式文件做 + * L0 整文件 DONTNEED(仅曲终安全窗口;未 commit/中断保留 .part 不丢)。 */ +static void song_gov_cleanup(song_src_t *ss) +{ + if (ss->gov_path[0]) meow_cachegov_unregister(ss->gov_path); + if (ss->dl_rc == NCM_OK && ss->gov_final_path[0]) + meow_cachegov_drop_finished(ss->gov_final_path); +} + /* 缓存键(用户定案 NCM_CACHE_DESIGN §2.5):歌曲ID + 音质档位 → "song__."。 * 同歌同档命中不重复下载;换档=新条目,旧档留给 LRU 自然淘汰。 * §20:键后缀用 ncm_api_level_key(sky 与 sky_c51 可区分)。 */ @@ -587,6 +634,7 @@ static void run_song(int idx) g_play.play_song = s; g_play.play_song_valid = 1; pthread_mutex_unlock(&g_play.mu); + mem_watch_song_begin(); /* 边播边监控:本曲峰值从当前起算(下载/解码/播放全程记录) */ song_src_t ss; int64_t file_bytes = 0; @@ -602,11 +650,22 @@ static void run_song(int idx) } if (!still_intent(idx, my_seq)) { /* 预缓冲完成但已切歌/同曲重播:勿开解码/勿动状态 */ song_src_dispose(&ss); + song_gov_cleanup(&ss); /* 未登记(register 在其后)时为 no-op,统一清理 */ + mem_watch_song_end(); /* 释放钩子:join 下载线程+关源+缓冲 free+.part 收尾 */ return; } ncm_strlcpy(g_path, path, sizeof g_path); g_file_bytes = file_bytes; + /* page-cache 治理(A):登记本曲缓存文件(播放实际读的路径)+ read_pos 回调,供 L1 丢已读 + * 前缀 / refault 回退。cache 命中(ss.w==NULL)path=正式文件;流式 path=.tmp/.part + * (cur file 即正被 pread 的文件,DONTNEED 只作用于它;正式路径待 commit 后 L0 用)。 */ + ncm_strlcpy(ss.gov_path, path, sizeof ss.gov_path); + if (ss.w) ncm_strlcpy(ss.gov_final_path, ncm_cache_writer_final_path(ss.w), sizeof ss.gov_final_path); + else ncm_strlcpy(ss.gov_final_path, path, sizeof ss.gov_final_path); + meow_cachegov_register(ss.gov_path, (uint64_t)(file_bytes > 0 ? file_bytes : 0), + gov_read_pos, ss.src); + int64_t dur = s.duration_ms > 0 ? s.duration_ms : 0; /* 解码分发(§19/§21):stream_src 上 magic 探测 fLaC→dr_flac / 其它→minimp3(open_cb @@ -616,6 +675,8 @@ static void run_song(int idx) int64_t total_frames = 0; if (ncm_dec_open_src(&dec, ss.src, &rate, &ch, &bits, &total_frames) != 0) { song_src_dispose(&ss); + song_gov_cleanup(&ss); + mem_watch_song_end(); /* 释放钩子 */ if (!still_intent(idx, my_seq)) return; /* open 期间(flac 元数据等待)已切歌/重播 */ set_err(NCM_ERR_IO, "decode open failed"); return; } @@ -623,7 +684,8 @@ static void run_song(int idx) /* 音频输出:按实际流参数开(flac ≤16bit→S16,>16bit→S32;audiod 侧格式归一+原生率跟随) */ if (audio_out_open(rate, ch, bits) != 0) { - ncm_dec_close(dec); song_src_dispose(&ss); + ncm_dec_close(dec); song_src_dispose(&ss); song_gov_cleanup(&ss); + mem_watch_song_end(); /* 释放钩子 */ set_err(NCM_ERR_IO, "audio open failed"); return; } @@ -645,6 +707,7 @@ static void run_song(int idx) int paused_edge = 0; /* 暂停沿:进暂停已向 audiod 发 pause */ int stalled_edge = 0; /* §21 停滞沿:已对 audiod pause(1),待数据恢复 pause(0) */ int underruns_seen = 0; + int refaults_seen = 0; /* §2.3:本曲 DONTNEED 丢页重读 TF 计数(取证) */ for (;;) { if (g_app_quit) break; @@ -751,6 +814,18 @@ static void run_song(int idx) idx, u, (unsigned long long)wm); } } + /* page-cache 治理(A):refault 取证落 g_play + 节拍执行 L1 丢弃/refault 回退 */ + { + int rf = stream_src_refaults(ss.src); + if (rf != refaults_seen) { + refaults_seen = rf; + pthread_mutex_lock(&g_play.mu); + g_play.refaults = rf; + pthread_mutex_unlock(&g_play.mu); + meow_log(MEOW_LOG_INFO, "play: refault idx=%d count=%d", idx, rf); + } + meow_cachegov_tick(); + } /* 更新进度(~200ms 节流)*/ int64_t pos = rate > 0 ? frames_done * 1000 / rate : 0; pthread_mutex_lock(&g_play.mu); @@ -763,6 +838,9 @@ static void run_song(int idx) ncm_dec_close(dec); /* 下载完成由播放线程 commit(rename) / 中断 abort(阶段 2 语义,§21) */ song_src_dispose(&ss); + song_gov_cleanup(&ss); /* page-cache 治理(A)曲终:注销 L1 条目 + commit 成功则 L0 整文件 DONTNEED */ + mem_watch_song_end(); /* 调度器释放钩子:上一首缓冲完全清空(decoder 关 + 下载线程 + * join + 源关 + 缓冲 free + .part 收尾)——给下一首让内存 */ /* 若应继续播下一首且非因命令打断,由外层处理(外层见 cur==idx 且需自然结束) */ } @@ -835,7 +913,9 @@ static void *lyric_worker(void *arg) } if (!id[0]) continue; if (strcmp(id, last_id) == 0) continue; /* 同一曲已拉/在拉 */ - + if (meow_cachegov_prefetch_paused()) continue; /* page-cache 治理(§2.3/§3):暂停歌词预取 + * (A 回退硬暂停 OR svc_perf C Cached 降载), + * 不置 last_id → 降载恢复后下轮再拉。 */ ncm_strlcpy(last_id, id, sizeof last_id); lyric_clear(); pthread_mutex_lock(&g_lyrmu); @@ -898,6 +978,7 @@ int player_manager_start(void) g_cache_gb = g_st.cache_limit_gb; pthread_mutex_init(&g_play.mu, NULL); meow_audio_on_preempt(on_audio_preempt, NULL); /* 被抢占 → 暂停(SDK 读线程分发) */ + mem_watch_start(); /* 边播边监控(防 OOM):起监控线程读 RSS,驱下载降载 */ if (ncm_thread_create(&g_thr, player_thread, NULL) != 0) return -1; g_thr_on = 1; return 0; @@ -908,6 +989,7 @@ void player_manager_stop_thread(void) g_want_run = 0; g_app_quit = 1; audio_out_close(); /* 打断阻塞写 */ + mem_watch_stop(); /* 停监控线程 */ if (g_thr_on) { pthread_join(g_thr, NULL); g_thr_on = 0; diff --git a/patch_v2/ncm_app/app/play/stream_src.c b/patch_v2/ncm_app/app/play/stream_src.c index 27806dd..e2614ae 100644 --- a/patch_v2/ncm_app/app/play/stream_src.c +++ b/patch_v2/ncm_app/app/play/stream_src.c @@ -22,6 +22,7 @@ #define REBUFFER_AFTER_MS 500 /* 单次调用累计等待超此值后启用再缓冲迟滞 */ #define REBUFFER_MIN (64 * 1024) /* 迟滞放行门槛(≈1.6s@320kbps) */ #define STALL_MS 1000 /* 单次调用等待超此值 → 置 stalled(播放线程 pause 联动) */ +#define STREAM_SRC_REFAULT_MS 50 /* pread 慢于此阈且数据已在盘上 → 计一次 refault(§2.3) */ struct stream_src { int fd; /* 只读 fd(pread;写端另有自己的写 fd) */ @@ -36,8 +37,16 @@ struct stream_src { uint64_t wait_ms_total; int underruns; int stalled; + int refaults; /* 数据在盘上但 pread 慢(>50ms)= DONTNEED 丢页重读 TF(§2.3) */ }; +/* SDK meow_cachegov 提供的 refault 上报(strong 时覆盖弱符号;未链 SDK 时为空指针,调用需判空)。 */ +#if defined(__GNUC__) +extern void meow_cachegov_note_refault(void) __attribute__((weak)); +#else +extern void meow_cachegov_note_refault(void); +#endif + static int64_t mono_ms(void) { struct timespec ts; @@ -182,9 +191,18 @@ static size_t src_read(void *buf, size_t size, void *ud) if (avail > 0 && !gated) { size_t n = (size - done) < (size_t)avail ? (size - done) : (size_t)avail; uint64_t pos = s->read_pos; + int had_data = (pos < s->written); /* 数据已在盘上(放锁前采样;勿在放锁后读 written) */ pthread_mutex_unlock(&s->mu); /* pread 期间放锁,不阻塞写端 broadcast */ + int64_t t0 = mono_ms(); ssize_t r = pread(s->fd, (char *)buf + done, n, (off_t)pos); + int64_t pread_ms = mono_ms() - t0; pthread_mutex_lock(&s->mu); + if (r > 0 && had_data && pread_ms > STREAM_SRC_REFAULT_MS) { + /* 数据在盘上但 pread 慢 = DONTNEED 丢页后重读 TF(underrun 确认是 refault, + * 非网络等待)。上报 SDK meow_cachegov(弱符号,未链时为空指针需判空)。 */ + s->refaults++; + if (meow_cachegov_note_refault) meow_cachegov_note_refault(); + } if (r <= 0) { /* 盘 IO 错:按中断收尾(真 EOF 语义) */ if (!s->failed) s->failed = -3; /* NCM_ERR_IO(本层不依赖 core 头) */ s->final = s->written ? s->written : 1; @@ -290,6 +308,24 @@ uint64_t stream_src_written(stream_src_t *s) return w; } +int stream_src_refaults(stream_src_t *s) +{ + if (!s) return 0; + pthread_mutex_lock(&s->mu); + int r = s->refaults; + pthread_mutex_unlock(&s->mu); + return r; +} + +uint64_t stream_src_read_pos(stream_src_t *s) +{ + if (!s) return 0; + pthread_mutex_lock(&s->mu); + uint64_t p = s->read_pos; + pthread_mutex_unlock(&s->mu); + return p; +} + uint64_t stream_src_final(stream_src_t *s) { if (!s) return 1; diff --git a/patch_v2/ncm_app/app/play/stream_src.h b/patch_v2/ncm_app/app/play/stream_src.h index 64d01bb..60e64f4 100644 --- a/patch_v2/ncm_app/app/play/stream_src.h +++ b/patch_v2/ncm_app/app/play/stream_src.h @@ -72,6 +72,15 @@ void stream_src_stats(stream_src_t *s, int *underruns, uint64_t *wait_ms); * 取走即清零。 */ int stream_src_take_stalled(stream_src_t *s); +/* ---- page-cache 治理(OOM_PAGECACHE_GOVERNANCE §2.3:underrun/refault 检测)---- + * refault = pread 处「数据已在盘上(written>read_pos)但读出耗时 >50ms」——DONTNEED 丢页后 + * 重读 TF 的信号,区别于网络 underrun(avail==0 等待)。SDK meow_cachegov 据此做 A 回退。 + * 播放线程读 refaults 落 g_play 取证;回调由 SDK(meow_cachegov_note_refault)提供, + * 未链 SDK 时弱符号为空(host sim 兼容)。 */ +int stream_src_refaults(stream_src_t *s); +/* 当前解码逻辑读指针(stream_src_seek_cb 改写)。 */ +uint64_t stream_src_read_pos(stream_src_t *s); + uint64_t stream_src_written(stream_src_t *s); uint64_t stream_src_final(stream_src_t *s); /* 0=下载中;>0=最终大小(完成=全长,中断=截断点) */ int stream_src_failed(stream_src_t *s); /* 0=无错误;<0=下载/IO 错误码 */ diff --git a/patch_v2/ncm_app/app/test/test_ncm_app.c b/patch_v2/ncm_app/app/test/test_ncm_app.c index 304c8bd..7480070 100644 --- a/patch_v2/ncm_app/app/test/test_ncm_app.c +++ b/patch_v2/ncm_app/app/test/test_ncm_app.c @@ -266,10 +266,11 @@ int main(int argc, char **argv) player_touch_move(340, 200); /* 右移 100px */ player_on_swipe(MEOW_SWIPE_RIGHT, 100); /* SDK 中部右滑 → SWIPE_RIGHT */ int p2 = player_page(); - /* 起点在进度条上不锁存(防拖进度条误判翻页);(240,504)=进度条行 */ - player_touch_down(240, 504); - player_touch_move(140, 504); - player_on_tap(140, 504); /* 拖进度条抬手 → 应仍为封面页(seek 而非翻页) */ + /* 起点在进度条上不锁存(防拖进度条误判翻页);(240,484)=进度条行(§22 时间行下移后 + * g_bar.y=484,时间行在 500——拖时间行会误判翻页,故取进度条行本身) */ + player_touch_down(240, 484); + player_touch_move(140, 484); + player_on_tap(140, 484); /* 拖进度条抬手 → 应仍为封面页(seek 而非翻页) */ int latched_change = player_page(); int ok = (p0 == 0 && p1 == 1 && p2 == 0 && latched_change == 0); printf("gesture: p0=%d p1(左滑)=%d p2(右滑)=%d 省电锁存=%d -> %s\n", diff --git a/patch_v2/ncm_app/app/ui/home.c b/patch_v2/ncm_app/app/ui/home.c index e4af442..f41c50c 100644 --- a/patch_v2/ncm_app/app/ui/home.c +++ b/patch_v2/ncm_app/app/ui/home.c @@ -120,6 +120,27 @@ static void home_draw(void *ctx) meow_ui_text(pc.x + asz + 28, pc.y + 6, g_st.nickname[0] ? g_st.nickname : "我的歌单", th->font_body, th->onSurface); meow_ui_text(pc.x + asz + 28, pc.y + 6 + th->font_body + 4, idtxt, th->font_body, th->onSurfaceVariant); + /* §22 会员等级小字:account_vip() 枚举(用户语义 0=无 / 1=畅听会员 / 2=黑胶 VIP / + * 3=超级会员);昵称右侧画小红徽章(red_ctr 底 + 红字),无会员则不画。 */ + { + int vip = account_vip(); + if (vip != NCM_VIP_NONE) { + const char *vip_lab = (vip == NCM_VIP_CVIP) ? "畅听会员" + : (vip == NCM_VIP_SVIP) ? "黑胶 VIP" + : (vip == NCM_VIP_SUPER) ? "超级会员" : ""; + const char *nm = g_st.nickname[0] ? g_st.nickname : "我的歌单"; + int nmw = meow_ui_text_width(nm, th->font_body); + int bs = th->font_body - 2; /* 徽章字号略小 */ + if (bs < 16) bs = 16; + int bw = meow_ui_text_width(vip_lab, bs) + 12; + int bx = pc.x + asz + 28 + nmw + 8; + int by = pc.y + 6 + (th->font_body - (bs + 4)) / 2; + if (bx + bw <= pc.x + pc.w - 24) { /* 不越卡片右缘(避开“>”) */ + ncm_fill_round(bx, by, bw, bs + 4, 6, pa->red_ctr); + meow_ui_text_center(bx + bw / 2, by + 2, vip_lab, bs, pa->red); + } + } + } meow_ui_text_right(pc.x + pc.w - 16, pc.y + (pc.h - 28) / 2, ">", 28, th->onSurfaceVariant); /* 双页签(g_tabs=meow_tabbar widget 建)——红下划线/红选中字叠层。 */ diff --git a/patch_v2/ncm_app/app/ui/player.c b/patch_v2/ncm_app/app/ui/player.c index d0764f0..4397fc9 100644 --- a/patch_v2/ncm_app/app/ui/player.c +++ b/patch_v2/ncm_app/app/ui/player.c @@ -8,8 +8,9 @@ * - 封面页:方形大图+圆角(cover.c LRU,无旋转动画)+ 进度条(可点 seek)+ MD3 矢量控制钮 * (prev/play-pause/next,ncm_icons.h 32x32 单通道 alpha,参照 sb_icons.h 范式) * + 元信息 + 实际音质档 + 页面指示点;缓冲/错误态沿用(「缓冲中…N%」/中文错误条+重试)。 - * - 歌词页:LRC 滚动(当前行高亮居中,上下行渐隐)+ 点行跳转;默认=封面模糊置底, - * 省电=封面背景+黑色半透明叠加层(设置 ncm.player_bg_mode: default/power_save)。 + * - 歌词页:LRC 滚动(活动行纯白偏上锚定,上下行逐级灰阶)+ 点行跳转;默认=封面模糊置底 + * (低透明非纯黑中性叠加层,需求1 加深),省电=封面背景+黑色半透明叠加层 + * (设置 ncm.player_bg_mode: default/power_save)。 * - 日夜间切换:主题配色只映射到半透明叠加层(歌词页 scrim)与 accent(红), * 不动沉浸底(恒 NCM_PLAYER_BG)。 * @@ -39,6 +40,35 @@ #define PLR_EDGE_W 24 /* 左缘返回热区(与 SDK EDGE_W 一致,防抢返回) */ #define PLR_SWIPE_MS 65 /* 中部横滑判页阈值(与 SDK SWIPE_THRESH 一致) */ +/* 歌词页布局(§22 重做批):左边距 / 子行高 / 可见行数上限 / 折行段缓冲。 + * 超宽歌词折行显示(中西文兼容)+ 活动行高亮(纯白)偏上锚定 + 上下行逐级灰阶, + * 顶部元信息与歌词正文同左边距。 */ +#define LYR_PAD 24 /* 歌词页左边距(顶部元信息与歌词正文一致) */ +#define LYR_SUB_LH 44 /* 一个歌词条目(含折行)的子行高 */ +#define LYR_NAME_SZ 28 /* 顶部歌名字号 */ +#define LYR_ART_SZ 20 /* 顶部歌手字号 */ +#define LYR_CUR_SZ 28 /* 当前歌词行字号 */ +#define LYR_OTH_SZ 24 /* 上下行歌词字号 */ +#define LYR_TL_SZ 20 /* 当前行翻译字号 */ +#define LYR_MAX_ROWS 9 /* 歌词页可见视觉行数上限(含折行,≤9) */ +#define LYR_SEG_SZ 256 /* 折行段缓冲 */ +/* 需求3 活动行锚定:块中心相对屏中线偏上 LYR_ABOVE_OFF px;上方至少 LYR_ABOVE_MIN 行已过歌词。 */ +#define LYR_ABOVE_OFF 50 +#define LYR_ABOVE_MIN 2 +/* 需求1 叠加层加深(非纯黑,保留封面微透出 → 歌词清晰、封面隐约): + * default=近黑中性,alpha 132→176、色值略暗;power_save=纯黑 alpha 195→200。 */ +#define LYR_SCRIM_D 0x10101A /* 默认层暗色 scrim */ +#define LYR_SCRIM_L 0x201A16 /* 默认层亮色 scrim(暖黑) */ +#define LYR_SCRIM_PS 0x000000 /* 省电层 scrim(纯黑) */ +#define LYR_SCRIM_A 176 +#define LYR_SCRIM_PS_A 200 + +/* 需求2 歌词行灰阶:按距活动行行距 0..5 分级(0=活动行纯白,越远越灰;最远可见 0x555555)。 + * 锚点对照用户需求:±1 行 0xBBBBBB、中深 0x808080、最远 0x555555,逐级单调变灰。 */ +static const uint32_t lyr_gray_tbl[6] = { + 0xFFFFFF, 0xBBBBBB, 0x929292, 0x808080, 0x6A6A6A, 0x555555 +}; + typedef struct { meow_view *view; int active; } player_ctrl; static player_ctrl g_plc; @@ -63,8 +93,14 @@ static int g_ctl_y, g_prev_x, g_play_x, g_next_x, g_r_side, g_r_play, g_dots_y; /* ---- 歌词页几何 ---- */ static int g_lyr_cx; /* 歌词垂直中心(屏中线) */ -static int g_lyr_lh; /* 行高 */ -static int g_lyr_meta_y; /* 顶部元信息 y */ +static int g_lyr_anchor; /* 活动行块中心 y(屏中线偏上,需求3) */ +static int g_lyr_meta_y; /* 顶部元信息 y(避开状态栏) */ +/* 歌词布局暂存(每次 plr_draw_lyrics 重建;UI 线程串行,静态复用) */ +static char s_lyr_seg[LYR_MAX_ROWS][LYR_SEG_SZ]; /* 折行段复用缓冲(一次画一个条目) */ +static int s_lyr_ent[LYR_MAX_ROWS]; /* 可见条目 → 歌词行下标 */ +static int s_lyr_rw[LYR_MAX_ROWS]; /* 条目折行段数 */ +static int s_lyr_sz[LYR_MAX_ROWS]; /* 条目标题字号 */ +static int s_lyr_y[LYR_MAX_ROWS]; /* 条目块顶 y */ #define CTL_RR (g_r_side + 8) /* 侧钮命中容差(圆外一点仍可点) */ @@ -90,6 +126,108 @@ static void fmt_ms(int64_t ms, char *out, size_t sz) snprintf(out, sz, "%d:%02d", sec / 60, sec % 60); } +/* 折行:把 txt 按「词/字符」折成 ≤max_rows 段,每段 ≤maxw px。中文按字符、英文按词断行; + * 宽测量用 meow_ui_text_width(宿主/生产同口径)。段写入 seg[][LYR_SEG_SZ],返回段数(≥1)。 */ +static int lyr_wrap_lines(const char *txt, int size, int maxw, + char seg[][LYR_SEG_SZ], int max_rows) +{ + if (!txt || !txt[0] || max_rows <= 0) { seg[0][0] = 0; return 1; } + int n = 0; + size_t len = strlen(txt), pos = 0; + while (pos < len && n < max_rows) { + size_t start = pos; + int last_ws = -1; /* 本行最后一个空格(相对 start 的字节偏移) */ + int overflow = 0; + while (pos < len) { + unsigned char c = (unsigned char)txt[pos]; + int ch = (c < 0x80) ? 1 : (c < 0xE0) ? 2 : (c < 0xF0) ? 3 : 4; + if (pos + (size_t)ch > len) ch = (int)(len - pos); + size_t cand = (pos + (size_t)ch) - start; + size_t cp = cand < LYR_SEG_SZ - 1 ? cand : LYR_SEG_SZ - 1; + memcpy(seg[n], txt + start, cp); seg[n][cp] = 0; + if (meow_ui_text_width(seg[n], size) > maxw && cand > 0) { overflow = 1; break; } + if (txt[pos] == ' ' || txt[pos] == '\t') last_ws = (int)(pos - start); + pos += (size_t)ch; + } + if (overflow) { + size_t linelen = (last_ws > 0) ? (size_t)last_ws : (pos - start); + if (linelen == 0) { /* 单字符超宽(罕见):至少放一个字符防死循环 */ + unsigned char c = (unsigned char)txt[start]; + int ch = (c < 0x80) ? 1 : (c < 0xE0) ? 2 : (c < 0xF0) ? 3 : 4; + linelen = (size_t)ch; + } + size_t cp = linelen < LYR_SEG_SZ - 1 ? linelen : LYR_SEG_SZ - 1; + memcpy(seg[n], txt + start, cp); seg[n][cp] = 0; + pos = start + linelen; + if (last_ws > 0) { + size_t sp = start + (size_t)last_ws + 1; + if (sp > pos) pos = sp; /* 跳过断点后的一个空格 */ + } + n++; + } else { + size_t cp = (len - start) < LYR_SEG_SZ - 1 ? (len - start) : LYR_SEG_SZ - 1; + memcpy(seg[n], txt + start, cp); seg[n][cp] = 0; + n++; + break; + } + } + if (n == 0) { seg[0][0] = 0; n = 1; } + return n; +} + +/* 歌词可见条目布局:活动行块中心锚定在 g_lyr_anchor(屏中线偏上,需求3 中间偏上), + * 活动行上方保底 ≥LYR_ABOVE_MIN 行已过歌词,下方填满,总视觉行数 ≤ LYR_MAX_ROWS(≤9)。 + * 结果写 s_lyr_ent/rw/sz/y(s_lyr_y 自上向下排好;当前行块中心 = g_lyr_anchor)。 */ +static int lyr_layout(int n, int cur, int maxw) +{ + /* 清静态暂存(防帧间残留:上帧条目数 > 本周,绘制循环读到陈旧 s_lyr_* 越界复用) */ + for (int k = 0; k < LYR_MAX_ROWS; k++) { s_lyr_rw[k] = 0; s_lyr_ent[k] = -1; s_lyr_y[k] = 0; } + if (cur < 0 || cur >= n) return 0; + + int rc = lyr_wrap_lines(lyric_line_text(cur), LYR_CUR_SZ, maxw, s_lyr_seg, LYR_MAX_ROWS); + if (rc > LYR_MAX_ROWS) rc = LYR_MAX_ROWS; + + int cap = LYR_MAX_ROWS - rc; /* 当前行之外可用的视觉行数 */ + int above_want = (cap * 2) / 5; /* 偏上锚定:约 40% 行给上方 */ + if (above_want > cur) above_want = cur; /* 上方行数上限 = 已过行数 */ + if (above_want < LYR_ABOVE_MIN) above_want = (cur >= LYR_ABOVE_MIN) ? LYR_ABOVE_MIN : cur; + if (above_want > cap) above_want = cap; /* 总行数 ≤ LYR_MAX_ROWS */ + + /* 1) 上方(cur-1 向上)前插到头部,保持自顶向下 */ + int a_cnt = 0, usedA = 0; + for (int i = cur - 1; i >= 0 && usedA < above_want; i--) { + int rr = lyr_wrap_lines(lyric_line_text(i), LYR_OTH_SZ, maxw, s_lyr_seg, LYR_MAX_ROWS); + if (usedA + rr > above_want) break; + for (int k = a_cnt; k > 0; k--) { + s_lyr_ent[k] = s_lyr_ent[k - 1]; s_lyr_rw[k] = s_lyr_rw[k - 1]; + s_lyr_sz[k] = s_lyr_sz[k - 1]; + } + s_lyr_ent[0] = i; s_lyr_rw[0] = rr; s_lyr_sz[0] = LYR_OTH_SZ; a_cnt++; usedA += rr; + } + /* 2) 当前行(下标 a_cnt) */ + s_lyr_ent[a_cnt] = cur; s_lyr_rw[a_cnt] = rc; s_lyr_sz[a_cnt] = LYR_CUR_SZ; + /* 3) 下方(cur+1 向下):余量 = cap - actual 上方行数 */ + int below_want = cap - usedA; + if (below_want > (n - 1 - cur)) below_want = n - 1 - cur; /* 下方行数上限 */ + int b_cnt = 0, usedB = 0; + for (int i = cur + 1; i < n && usedB < below_want; i++) { + int rr = lyr_wrap_lines(lyric_line_text(i), LYR_OTH_SZ, maxw, s_lyr_seg, LYR_MAX_ROWS); + if (usedB + rr > below_want) break; + int k = a_cnt + 1 + b_cnt; + s_lyr_ent[k] = i; s_lyr_rw[k] = rr; s_lyr_sz[k] = LYR_OTH_SZ; b_cnt++; usedB += rr; + } + int cnt = a_cnt + 1 + b_cnt; + if (cnt == 0) return 0; + + /* 4) y 位置:活动行块中心 = g_lyr_anchor;上方向上加、下方向下加(块间无缝隙) */ + s_lyr_y[a_cnt] = g_lyr_anchor - rc * LYR_SUB_LH / 2; + for (int k = a_cnt + 1; k < cnt; k++) + s_lyr_y[k] = s_lyr_y[k - 1] + s_lyr_rw[k - 1] * LYR_SUB_LH; + for (int k = a_cnt - 1; k >= 0; k--) + s_lyr_y[k] = s_lyr_y[k + 1] - s_lyr_rw[k] * LYR_SUB_LH; + return cnt; +} + /* 颜色插值(t=0..255 混向 b)。播放页歌词渐隐/叠加用(无 GPU,逐通道整数混合)。 */ static uint32_t mix_c(uint32_t a, uint32_t b, int t) { @@ -177,19 +315,22 @@ static void player_compute_geom(void) g_title_y = g_cover_r.y + g_cover_r.h + 32; /* 400 */ g_artist_y = g_title_y + 32; /* 432 */ g_qual_y = g_artist_y + 26; /* 458 */ - g_times_y = g_qual_y + 26; /* 484 */ - g_bar = NR(48, g_times_y + 20, W - 96, 4); /* 504 (h=4) */ - g_ctl_y = g_bar.y + 64; /* 568 */ + /* §22 时间行:放大字号并下移到进度条下方,不与进度条相交。 + * 布局:进度条(484,h=4) → 时间行(500, font 24) → 控制钮(564)。 */ + g_bar = NR(48, g_qual_y + 26, W - 96, 4); /* 484 (h=4) */ + g_times_y = g_bar.y + g_bar.h + 12; /* 500 */ + g_ctl_y = g_times_y + 64; /* 564 */ g_prev_x = W / 2 - 92; g_play_x = W / 2; g_next_x = W / 2 + 92; g_r_side = 24; g_r_play = 34; - g_dots_y = g_ctl_y + 62; /* 630 */ + g_dots_y = g_ctl_y + 62; /* 626 */ /* 动态带 = 封面下方(元信息/进度/控制/指示点),与封面静态带不重叠。 */ g_dyn_r = NR(0, g_cover_r.y + g_cover_r.h, W, H - (g_cover_r.y + g_cover_r.h)); - g_lyr_cx = H / 2; /* 360 */ - g_lyr_lh = 30; - g_lyr_meta_y = 22; + /* 歌词页几何:中心线 / 活动行块中心(屏中线偏上,需求3)/ 顶部元信息 y。 */ + g_lyr_cx = H / 2; /* 360 */ + g_lyr_anchor = g_lyr_cx - LYR_ABOVE_OFF; /* 310 活动行偏上锚定 */ + g_lyr_meta_y = ncm_statusbar() + 20; } /* ============================================================ */ @@ -280,8 +421,9 @@ static void plr_draw_cover_dyn(void *ctx) } meow_ui_text_center(W / 2, g_times_y - 8, bbuf, 20, PLR_TEXT_DIM); } else { - meow_ui_text(48, g_times_y, lp, 20, PLR_TEXT_DIM); - meow_ui_text_right(W - 48, g_times_y, rp, 20, PLR_TEXT_DIM); + /* §22 时间字号放大(20→24)且下移到进度条下方,不再与进度条相交 */ + meow_ui_text(48, g_times_y, lp, 24, PLR_TEXT_DIM); + meow_ui_text_right(W - 48, g_times_y, rp, 24, PLR_TEXT_DIM); } meow_ui_fill_rect(g_bar.x, g_bar.y, g_bar.w, g_bar.h, PLR_TRACK_BG); meow_ui_fill_rect(g_bar.x, g_bar.y, (int)((int64_t)g_bar.w * frac / 1000), g_bar.h, pa->red); @@ -297,73 +439,75 @@ static void plr_draw_cover_dyn(void *ctx) draw_page_dots(g_dots_y); } -/* 歌词页(整屏带状物):封面模糊置底 + scrim + 顶部元信息 + 滚动歌词(当前行高亮居中 + - * 上下行渐隐)+ 指示点。整屏在 进入/切歌/封面变化/换背景模式/换行 时重画;pos tick 不画。 */ +/* 歌词页(整屏带状物):封面模糊置底 + 加深 scrim + 顶部元信息 + 滚动歌词(活动行纯白偏上锚定 + + * 上下行逐级灰阶)+ 指示点。整屏在 进入/切歌/封面变化/换背景模式/换行 时重画;pos tick 不画。 */ static void plr_draw_lyrics(void *ctx) { (void)ctx; if (meow_ui_activity_active() != ACT_PLAYER || g_ppage != 1) return; - const meow_theme *th = meow_theme_get(); - const ncm_pal_t *pa = ncm_palette(g_dark); int W = g_W, H = g_H; - /* 底:封面等比居中放大(contain:等比缩放到屏内最大、居中;四周主题底),无封面 → 沉浸底。 - * 不拉伸铺满(方图硬拉 480×720 变形丑,2026-09-02 用户报)。 - * ⚠️ cover_get() 持锁返回,勿在绘前 return(防 g_cm 泄漏 → 下次 paint 自锁死,§18)。 */ + /* 底:封面铺满全屏(cover-fit,四周无黑边;无 GPU 高斯模糊,用低透明中性叠层近似「模糊底」), + * 无封面 → 主题中性底(非纯黑)。⚠️ cover_get() 持锁返回,勿在绘前 return(防 g_cm 泄漏, + * 下次 paint 自锁死,§18)。§22:default=封面+半透明中性叠层;power_save=封面+黑色更重叠层。 */ meow_img *cov = cover_get(); - meow_ui_fill_rect(0, 0, W, H, NCM_PLAYER_BG); + meow_ui_fill_rect(0, 0, W, H, 0x1A1A20); /* 无封面兜底主题底(中性深灰,非纯黑) */ if (cov) { int cw = meow_img_w(cov), ch = meow_img_h(cov); if (cw > 0 && ch > 0) { - int dw = W, dh = (int)((long)ch * W / cw); - if (dh > H) { dh = H; dw = (int)((long)cw * H / ch); } - meow_ui_img_scaled((W - dw) / 2, (H - dh) / 2, dw, dh, cov); + long dw = W, dh = (long)ch * W / cw; /* cover-fit:铺满宽度,高按等比超出裁剪 */ + if (dh < H) { dh = H; dw = (long)cw * H / ch; } + meow_ui_img_scaled((W - (int)dw) / 2, (H - (int)dh) / 2, (int)dw, (int)dh, cov); } } cover_put(); - /* 半透明叠加层:default=主题 scrim(日/夜跟随主题);power_save=黑色更重(§22)。 */ + /* 半透明叠加层:default=主题中性(近黑但不纯黑,封面色彩微透出 → 模糊底观感); + * power_save=黑色更重(§22 保留原「封面背景+黑叠加」语义)。需求1 加深:alpha/色值加暗, + * 但仍保留「封面隐约」而非纯黑整体吞掉。 */ if (g_st.player_bg_mode == NCM_BG_POWER_SAVE) - meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 195); + meow_ui_fill_rect_alpha(0, 0, W, H, LYR_SCRIM_PS, LYR_SCRIM_PS_A); else - meow_ui_fill_rect_alpha(0, 0, W, H, th->scrim, 150); + meow_ui_fill_rect_alpha(0, 0, W, H, g_dark ? LYR_SCRIM_D : LYR_SCRIM_L, LYR_SCRIM_A); - /* 顶部元信息(title · artist,Apple Music 式顶部小字) */ + /* 顶部元信息(两行居左):第一行歌名(大字)、第二行歌手名(小字,多歌手保留已拼串)。 + * 与歌词正文同左边距 LYR_PAD;下移避开状态栏(g_lyr_meta_y = statusbar + 20)。 */ ncm_song_t sm; if (cur_song_meta(&sm) == 0) { - char mb[300]; - snprintf(mb, sizeof mb, "%s / %s", sm.name, sm.art_name); - meow_ui_text_center(W / 2, g_lyr_meta_y, ncm_text_fit(mb, W - 48, 18), 18, PLR_TEXT); + meow_ui_text(LYR_PAD, g_lyr_meta_y, + ncm_text_fit(sm.name, W - 2 * LYR_PAD, LYR_NAME_SZ), LYR_NAME_SZ, PLR_TEXT); + meow_ui_text(LYR_PAD, g_lyr_meta_y + LYR_NAME_SZ + 6, + ncm_text_fit(sm.art_name, W - 2 * LYR_PAD, LYR_ART_SZ), LYR_ART_SZ, PLR_TEXT_DIM); } - /* 歌词:当前行居中高亮;上下行渐隐(距中心越远越接近底色)。 */ + /* 歌词:活动行纯白居中(含折行,块中心偏上锚定);上下行按行距逐级灰阶;≤LYR_MAX_ROWS。 */ int n = lyric_count(); int64_t ps = player_manager_pos_ms(); int cur = (n > 0) ? lyric_line_at(ps) : -1; - int cy = g_lyr_cx; - int lo = g_lyr_meta_y + 44, hi = g_dots_y - 6; /* 歌词窗口(避顶部元信息/底部指示点) */ - for (int i = cur - 7; i <= cur + 6; i++) { - if (i < 0 || i >= n) continue; - int yy = cy + (i - cur) * g_lyr_lh; - if (yy < lo || yy + g_lyr_lh > hi) continue; + int maxw = W - 2 * LYR_PAD; + int cnt = lyr_layout(n, cur, maxw); + /* 渐隐目标色 = 歌词页背底层(与 default/power_save 叠层一致),供翻译行融入背景 */ + uint32_t bgcol = (g_st.player_bg_mode == NCM_BG_POWER_SAVE) ? LYR_SCRIM_PS + : (g_dark ? LYR_SCRIM_D : LYR_SCRIM_L); + for (int k = 0; k < cnt; k++) { + int i = s_lyr_ent[k], rows = s_lyr_rw[k], sz = s_lyr_sz[k], y = s_lyr_y[k]; int is_cur = (i == cur); - int dist = (i - cur) < 0 ? (cur - i) : (i - cur); - int fade = dist * 42; /* 0..255,越远越淡 */ - if (fade > 214) fade = 214; /* 远行仍隐约可见,不全隐 */ - uint32_t base = is_cur ? pa->red_lit : PLR_TEXT; - uint32_t bgcol = (g_st.player_bg_mode == NCM_BG_POWER_SAVE) ? 0x000000 : th->scrim; - uint32_t col = mix_c(base, bgcol, fade); - int size = is_cur ? 24 : 20; - meow_ui_text_center(W / 2, yy, ncm_text_fit(lyric_line_text(i), W - 24, size), size, col); + int dist = is_cur ? 0 : ((i - cur) < 0 ? (cur - i) : (i - cur)); /* 距活动行行距 */ + uint32_t col = lyr_gray_tbl[dist > 5 ? 5 : dist]; /* 0=纯白,越远越灰 */ + int rr = lyr_wrap_lines(lyric_line_text(i), sz, maxw, s_lyr_seg, rows); + if (rr > rows) rr = rows; + for (int r = 0; r < rr; r++) + meow_ui_text(LYR_PAD, y + r * LYR_SUB_LH, s_lyr_seg[r], sz, col); if (is_cur) { const char *tl = lyric_line_tl(i); if (tl && tl[0]) - meow_ui_text_center(W / 2, yy + 28, ncm_text_fit(tl, W - 24, 18), 18, - mix_c(pa->red_lit, bgcol, 120)); + meow_ui_text(LYR_PAD, y + rows * LYR_SUB_LH + 2, + ncm_text_fit(tl, W - 2 * LYR_PAD, LYR_TL_SZ), LYR_TL_SZ, + mix_c(0xFFFFFF, bgcol, 120)); } } if (n == 0) { - meow_ui_text_center(W / 2, cy - 10, - lyric_state() == 2 ? "暂无歌词" : "歌词加载中…", 20, PLR_TEXT_DIM); + meow_ui_text(LYR_PAD, g_lyr_cx - 10, + lyric_state() == 2 ? "暂无歌词" : "歌词加载中…", 20, PLR_TEXT_DIM); } draw_page_dots(g_dots_y); @@ -466,21 +610,27 @@ static void player_reset_touch(void) g_touch_on = 0; g_drag_latched = 0; g_drag_dx = 0; } -/* 歌词页:点行 seek(把 tap y 归到最近一行的下标)。 */ +/* 歌词页:点行 seek(用与绘制一致的可见条目布局把 tap y 命中最临近行; + * 活动行已偏上锚定,非屏中线,故按 s_lyr_y/rw 逐块扫描而非 cx 偏移)。 */ static int lyrics_line_tap(int y) { int n = lyric_count(); if (n <= 0) return -1; + /* 仅当 tap 落在歌词窗口内才响应(顶部元信息/底部指示点区不误触)。 + * 元信息块 = 歌名(28)+6+歌手(20)+边距 → g_lyr_meta_y+66。 */ + if (y < g_lyr_meta_y + 66 || y > g_dots_y - 6) return -1; int64_t ps = player_manager_pos_ms(); int cur = lyric_line_at(ps); - int off = y - g_lyr_cx; - int dioff = (off >= 0) ? (off + g_lyr_lh / 2) / g_lyr_lh : (off - g_lyr_lh / 2) / g_lyr_lh; - int i = cur + dioff; - if (i < 0) i = 0; - if (i >= n) i = n - 1; - /* 仅当 tap 落在歌词窗口内才响应(顶部元信息/底部指示点区不误触)。 */ - if (y < g_lyr_meta_y + 44 || y > g_dots_y - 6) return -1; - return i; + int maxw = g_W - 2 * LYR_PAD; + int cnt = lyr_layout(n, cur, maxw); + if (cnt <= 0) return -1; + for (int k = 0; k < cnt; k++) { + int yb = s_lyr_y[k]; + int yh = s_lyr_rw[k] * LYR_SUB_LH; + if (y >= yb && y < yb + yh) return s_lyr_ent[k]; + } + /* 落在可见块之外 → 就近取整(顶部元信息/底部指示点已在上面排除) */ + return (y < s_lyr_y[0]) ? s_lyr_ent[0] : s_lyr_ent[cnt - 1]; } static int in_circle(int cx, int cy, int r, int x, int y) diff --git a/patch_v2/ncm_app/app/ui/playlist.c b/patch_v2/ncm_app/app/ui/playlist.c index 8ebf1dc..eff7516 100644 --- a/patch_v2/ncm_app/app/ui/playlist.c +++ b/patch_v2/ncm_app/app/ui/playlist.c @@ -268,6 +268,17 @@ static void *playlist_worker(void *arg) /* 首屏:快照式加载(detail 全量 trackIds + 物化首段)。ctx 校验代次/目标,过期自动丢弃。 */ if (tidx < 0) rc = NCM_ERR_ARG; else rc = appstate_load_playlist_songs_ctx(&tpl, tsearch, tgen, tidx, 0, NCM_SONG_BATCH); + /* §22 整张歌单逐渐物化:首屏成功后把「能装进滑动窗口」的歌单一次性补填入窗 + * (≤NCM_SONG_WINDOW 的歌单整张物化,末叶行不再恒「.../加载中」——真机反馈 + * 「曲目稍多时行一直加载中,点开播放这首才回填」);超窗口的歌单仍按视口滑动补载 + * (内存纪律:窗口 ≤256 首)。窗口基址=0 覆盖 [0,min(track,WINDOW)),不滑窗不挤占视口。 */ + if (rc == NCM_OK) { + int full = (g_st.track_ids_count < NCM_SONG_WINDOW) ? g_st.track_ids_count : NCM_SONG_WINDOW; + if (full > NCM_SONG_BATCH) { + int r2 = appstate_song_ensure_range(0, full); + if (r2 != NCM_OK && rc == NCM_OK) rc = r2; /* 补填失败非致命:已物化槽保留 */ + } + } /* REPORT §30(真机四连症状①):首屏结果打日志——tidx/track_ids_count/物化数/rc, * 供「162 首歌单所有条目占位 加载中」定位(首屏 song_detail 失败/部分失败=非致命 * g_ens_err=rc,窗口空 → 行占位;g_st.song_count 为诊断竞态读,仅日志用途)。 */ diff --git a/patch_v2/ncm_app/app/ui/settings.c b/patch_v2/ncm_app/app/ui/settings.c index a5ca44b..fc46d05 100644 --- a/patch_v2/ncm_app/app/ui/settings.c +++ b/patch_v2/ncm_app/app/ui/settings.c @@ -1,7 +1,7 @@ /* ui/settings.c — 设置页(ACT_SETTINGS)。 * * 行项:音质(§20 八档:标准/极高 HQ/无损 SQ/Hi-Res/高清臻音/臻音全景声/超清母带/沉浸环绕声, - * 默认极高 HQ;每档标注规格+会员门槛 CVIP/SVIP;超权限档允许选定,播放时钳顶+逐级降级, + * 默认极高 HQ;每档标注规格+会员门槛(畅听会员/黑胶 VIP/超级会员);超权限档允许选定,播放时钳顶+逐级降级, * 见 player_manager.c song_local_file)+ 缓存上限(默认 10GB,上限 100GB,支持预设与 * 「自定义」数字输入)+ 清理缓存(显示当前占用)+ 退出登录 + 关于/版本。 * 设置持久化到 MeowData/dev.player.ncm/data(meow_config_*,与 core @@ -80,9 +80,12 @@ static void settings_draw(void *ctx) rows[4].title = "退出登录"; rows[4].red = 1; rows[5].title = "关于网易云"; rows[5].red = 0; { - /* 当前档 + 会员门槛标注;超权限(未解锁)时提示将降级(不硬拒,§20) */ + /* 当前档 + 会员门槛标注;超权限(未解锁)时提示将降级(不硬拒,§20)。 + * 门槛串按用户语义对齐:1=畅听会员 / 2=黑胶 VIP / 3=超级会员(门槛恒 ≤2,SUPER 兜底)。 */ int gate = ncm_api_level_gate(g_st.level); - const char *gate_s = (gate == NCM_VIP_SVIP) ? "SVIP" : (gate == NCM_VIP_CVIP) ? "CVIP" : ""; + const char *gate_s = (gate == NCM_VIP_SVIP) ? "黑胶 VIP" + : (gate == NCM_VIP_CVIP) ? "畅听会员" + : (gate == NCM_VIP_SUPER) ? "超级会员" : ""; int locked = (account_vip() < gate); if (gate_s[0]) snprintf(rows[0].sub, sizeof rows[0].sub, "当前: %s (%s) / 需 %s%s", @@ -112,7 +115,7 @@ static void settings_draw(void *ctx) /* 品牌红页脚小字。 */ int ny = y + SETTINGS_ROWS * (g_row_h + 8) + 4; - meow_ui_text(x, ny, "无损/Hi-Res 需 CVIP ・ 臻音/母带需 SVIP,不支持时自动降级", 20, th->onSurfaceVariant); + meow_ui_text(x, ny, "无损/Hi-Res 需畅听会员 ・ 臻音/母带需黑胶 VIP,不支持时自动降级", 20, th->onSurfaceVariant); meow_ui_text(x, ny + 24, "网易云音乐 / MeowOS 移植版", 20, pa->red); } @@ -170,9 +173,11 @@ int settings_on_tap(int x, int y) int lv = g_ui_levels[i]; int gate = ncm_api_level_gate(lv); char lbl[96]; - /* 每档标注 规格+会员门槛;未解锁档标注「需 CVIP/SVIP」(sheet 无置灰能力, + /* 每档标注 规格+会员门槛;未解锁档标注「需 畅听会员/黑胶 VIP」(sheet 无置灰能力, * 以文字标注代替——允许选定,实际播放逐级降级到可用档) */ - const char *gate_s = (gate == NCM_VIP_SVIP) ? "SVIP" : (gate == NCM_VIP_CVIP) ? "CVIP" : NULL; + const char *gate_s = (gate == NCM_VIP_SVIP) ? "黑胶 VIP" + : (gate == NCM_VIP_CVIP) ? "畅听会员" + : (gate == NCM_VIP_SUPER) ? "超级会员" : NULL; if (gate_s) snprintf(lbl, sizeof lbl, "%s (%s) / %s%s", ncm_level_name(lv), ncm_level_spec(lv), gate_s, (vip < gate) ? "/未解锁" : ""); diff --git a/patch_v2/ncm_app/bench/cover_chain_sim.c b/patch_v2/ncm_app/bench/cover_chain_sim.c index d378caf..bbaed59 100644 --- a/patch_v2/ncm_app/bench/cover_chain_sim.c +++ b/patch_v2/ncm_app/bench/cover_chain_sim.c @@ -413,6 +413,24 @@ int main(void) CHECK(cur_cover_hash() == 0, "reset -> cover cleared"); g_fail_next = 0; + /* TX: 缓存命中路径——重请求已下载过的 URL(硬盘还有其 song_cov* 条目,无网络)。 + * 旧码: 缓存命中分支从不更新 g_cover_path → cover_current_path() 恒 NULL → MSG_META + * artwork 恒空 → 通知中心媒体卡无封面路径 → 回退显示歌名(真机症状)。 + * 修复后: 换入时 g_cover_path 指向已落盘的缓存条目 → artwork 非空且可加载出该封面。 */ + printf("== TX: cache-hit path -> cover_current_path() non-empty (launcher artwork) ==\n"); + int bx = g_http_calls; + cover_request(URL_A, NULL); + CHECK(wait_cover_hash(url_hash(URL_A), 3000), "cache-hit: cover A shown (no download)"); + CHECK(g_http_calls == bx, "cache-hit: 0 http calls (from cache, calls=%d)", g_http_calls - bx); + const char *cpx = cover_current_path(); + CHECK(cpx && cpx[0], "cache-hit: cover_current_path() non-empty (before fix: NULL)"); + if (cpx && cpx[0]) { + meow_img *im = meow_img_load_file(cpx); + CHECK(im && im->hash == url_hash(URL_A), "cache-hit: reported path loads cover A"); + if (im) meow_img_free(im); + } + cover_ack(); + printf(g_fail ? "== COVER CHAIN SIM: FAIL ==\n" : "== COVER CHAIN SIM: ALL PASS ==\n"); return g_fail; } diff --git a/patch_v2/ncm_app/bench/http_truncate_sim.c b/patch_v2/ncm_app/bench/http_truncate_sim.c new file mode 100644 index 0000000..16da875 --- /dev/null +++ b/patch_v2/ncm_app/bench/http_truncate_sim.c @@ -0,0 +1,291 @@ +/* http_truncate_sim.c — 截断守卫宿主测试(0 真实网络) + * + * 背景(agent-355 挖出):core/ncm_http.c 的 http_run() 在 body 读取循环里, + * 对端于 Content-Length 未收满时提前关闭(mbedtls_ssl_read 返回 0 / + * MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY),loop 后仅判 overflow,否则一律 NCM_OK, + * 于是残缺 body 被调用方当完整成功 → ncm_cache_commit 把半截 .part/.tmp 转正。 + * + * 本 sim mock 整个 mbedtls 传输层(仅符号桩,不链真实 libmbedtls),脚本化喂给 + * ncm_http.c 一个可分块的对端响应流,验证 http_run(经公开入口 + * ncm_http_download_status)对以下语义的判定: + * 定长(Content-Length) 未收满即关闭 → 截断 → 非 NCM_OK(NCM_ERR_IO) + * 定长(Content-Length) 收满 → BS_DONE → NCM_OK + * Content-Length: 0(空 body) → BS_DONE → NCM_OK + * 无 Content-Length、靠对端关闭结束(BS_CLOSE) → 合法 → NCM_OK + * chunked 完整(0 分块末端) → BS_DONE → NCM_OK + * chunked 中途关闭 → 截断 → 非 NCM_OK(NCM_ERR_IO) + * + * 编译(在 ncm_app 下): + * gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -Ivendor/mbedtls/include \ + * -c bench/http_truncate_sim.c -o /tmp/t.o + * gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -Ivendor/mbedtls/include \ + * -ffunction-sections -c core/ncm_http.c -o /tmp/t_http.o + * gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -Ivendor/mbedtls/include \ + * -ffunction-sections -c core/ncm_common.c -o /tmp/t_common.o + * gcc -Wl,--gc-sections -o http_truncate_sim /tmp/t.o /tmp/t_http.o /tmp/t_common.o + */ +#include +#include +#include +#include + +#include "ncm_http.h" +#include "ncm_common.h" + +#include "mbedtls/net_sockets.h" +#include "mbedtls/ssl.h" +#include "mbedtls/x509_crt.h" + +/* ============================================================ + * 传输层桩:脚本化"对端读流"。 + * 每次 ncm_http_download_status 调用前用 tr_script(...) 装填脚本, + * 脚本用尽后 mbedtls_ssl_read 返回 g_close_rc(0=有序关闭;或 + * MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY 模拟 TLS close_notify)。 + * ============================================================ */ +typedef struct { const unsigned char *buf; size_t len; } tr_chunk_t; + +static const tr_chunk_t *g_script; +static int g_script_n; +static int g_script_i; +static int g_close_rc; /* 脚本耗尽后的 ssl_read 返回值 */ + +static void tr_script(const tr_chunk_t *chunks, int n, int close_rc) { + g_script = chunks; g_script_n = n; g_script_i = 0; g_close_rc = close_rc; +} + +int mbedtls_ssl_read(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len) { + (void)ssl; + if (g_script_i < g_script_n) { + const tr_chunk_t *c = &g_script[g_script_i]; + size_t n = c->len; + if (n > len) n = len; /* 脚本块恒 <= 4096(tmp),一般整块发出 */ + memcpy(buf, c->buf, n); + g_script_i++; + return (int)n; + } + return g_close_rc; +} +int mbedtls_ssl_write(mbedtls_ssl_context *ssl, const unsigned char *buf, size_t len) { + (void)ssl; (void)buf; return (int)len; /* 请求头/体全部写成功 */ +} + +/* 其余 mbedtls 桩:连接/握手/配置全部成功,不触碰任何真实网络。 */ +void mbedtls_net_init(mbedtls_net_context *ctx) { (void)ctx; } +int mbedtls_net_connect(mbedtls_net_context *ctx, const char *host, const char *port, int proto) { + (void)ctx; (void)host; (void)port; (void)proto; return 0; +} +int mbedtls_net_set_nonblock(mbedtls_net_context *ctx) { (void)ctx; return 0; } +int mbedtls_net_poll(mbedtls_net_context *ctx, uint32_t rw, uint32_t timeout) { + (void)ctx; (void)rw; (void)timeout; return 1; /* 就绪 */ +} +void mbedtls_net_free(mbedtls_net_context *ctx) { (void)ctx; } +int mbedtls_net_recv(void *ctx, unsigned char *buf, size_t len) { + (void)ctx; (void)buf; (void)len; return MBEDTLS_ERR_SSL_WANT_READ; /* 实际读由 mbedtls_ssl_read 桩接管 */ +} +int mbedtls_net_send(void *ctx, const unsigned char *buf, size_t len) { + (void)ctx; (void)buf; return (int)len; +} + +void mbedtls_ssl_init(mbedtls_ssl_context *ssl) { (void)ssl; } +void mbedtls_ssl_config_init(mbedtls_ssl_config *conf) { (void)conf; } +int mbedtls_ssl_config_defaults(mbedtls_ssl_config *conf, int endpoint, int transport, int preset) { + (void)conf; (void)endpoint; (void)transport; (void)preset; return 0; +} +void mbedtls_ssl_conf_authmode(mbedtls_ssl_config *conf, int authmode) { (void)conf; (void)authmode; } +void mbedtls_ssl_conf_ca_chain(mbedtls_ssl_config *conf, mbedtls_x509_crt *ca_chain, mbedtls_x509_crl *ca_crl) { + (void)conf; (void)ca_chain; (void)ca_crl; +} +void mbedtls_ssl_conf_rng(mbedtls_ssl_config *conf, + int (*f_rng)(void *, unsigned char *, size_t), void *p_rng) { + (void)conf; (void)f_rng; (void)p_rng; +} +int mbedtls_ssl_setup(mbedtls_ssl_context *ssl, const mbedtls_ssl_config *conf) { + (void)ssl; (void)conf; return 0; +} +int mbedtls_ssl_set_hostname(mbedtls_ssl_context *ssl, const char *hostname) { + (void)ssl; (void)hostname; return 0; +} +void mbedtls_ssl_set_bio(mbedtls_ssl_context *ssl, void *p_bio, + mbedtls_ssl_send_t *f_send, mbedtls_ssl_recv_t *f_recv, + mbedtls_ssl_recv_timeout_t *f_recv_timeout) { + (void)ssl; (void)p_bio; (void)f_send; (void)f_recv; (void)f_recv_timeout; +} +int mbedtls_ssl_handshake(mbedtls_ssl_context *ssl) { (void)ssl; return 0; } +void mbedtls_ssl_free(mbedtls_ssl_context *ssl) { (void)ssl; } +void mbedtls_ssl_config_free(mbedtls_ssl_config *conf) { (void)conf; } + +void mbedtls_x509_crt_init(mbedtls_x509_crt *crt) { (void)crt; } +int mbedtls_x509_crt_parse_file(mbedtls_x509_crt *crt, const char *path) { + (void)crt; (void)path; return 0; /* CA 加载成功(validate=0 时不走此径,桩兜底) */ +} +void mbedtls_x509_crt_free(mbedtls_x509_crt *crt) { (void)crt; } + +/* ============================================================ + * 下载回调 sink:累加接收到的 body 字节。 + * ============================================================ */ +typedef struct { unsigned char data[1 << 16]; size_t len; } sink_cb_t; + +static int on_body(void *ctx, const unsigned char *data, size_t len) { + sink_cb_t *s = (sink_cb_t *)ctx; + if (s->len + len > sizeof s->data) return -1; /* 上限(不触发) */ + memcpy(s->data + s->len, data, len); + s->len += len; + return 0; +} + +/* 运行一个用例:装填脚本 → 发起 GET 下载 → 记录 rc / body_len / content_length */ +typedef struct { int rc; size_t body_len; size_t content_length; } case_result_t; + +static case_result_t run_case(const tr_chunk_t *script, int script_n, int close_rc) { + ncm_http_req_t req; + memset(&req, 0, sizeof req); + req.url = "https://ex.test/song.mp3"; + req.method = "GET"; + req.verify_ca = 0; /* 跳过 CA 校验,聚焦 body 终态判定 */ + req.timeout_ms = 3000; + + sink_cb_t sink; memset(&sink, 0, sizeof sink); + ncm_http_resp_t resp; memset(&resp, 0, sizeof resp); + + tr_script(script, script_n, close_rc); + int rc = ncm_http_download_status(&req, on_body, &sink, 0, &resp, NULL); + + case_result_t r; + r.rc = rc; + r.body_len = sink.len; + r.content_length = resp.content_length; + return r; +} + +static int g_fail = 0; +static void check(const char *name, int ok, const char *detail) { + if (!ok) g_fail++; + printf(" %s %s\n", ok ? "PASS" : "FAIL", name); + if (detail) printf(" %s\n", detail); +} + +int main(void) { + char dbg[160]; + + /* ---- 场景 1:定长(CL=100) 对端提前关闭(只发 50 字节)→ 截断 ---- */ + { + static const unsigned char hdrs[] = + "HTTP/1.1 200 OK\r\nContent-Length: 100\r\n\r\n"; + static unsigned char chunk[512]; + size_t hlen = sizeof hdrs - 1; + memcpy(chunk, hdrs, hlen); + for (size_t i = 0; i < 50; i++) chunk[hlen + i] = 'A'; + tr_chunk_t script[1]; + script[0].buf = chunk; script[0].len = hlen + 50; + case_result_t r = run_case(script, 1, 0 /* 对端关闭 */); + snprintf(dbg, sizeof dbg, "rc=%d body=%zu content_length=%zu", + r.rc, r.body_len, r.content_length); + check("truncate_fixed(CL=100, 50B, close) -> NCM_ERR_IO", + (r.rc != NCM_OK) && (r.rc == NCM_ERR_IO) && (r.body_len == 50), dbg); + } + + /* ---- 场景 2:定长(CL=100) 完整收满 → BS_DONE → NCM_OK ---- */ + { + static const unsigned char hdrs[] = + "HTTP/1.1 200 OK\r\nContent-Length: 100\r\n\r\n"; + static unsigned char chunk[512]; + size_t hlen = sizeof hdrs - 1; + memcpy(chunk, hdrs, hlen); + for (size_t i = 0; i < 100; i++) chunk[hlen + i] = 'B'; + tr_chunk_t script[1]; + script[0].buf = chunk; script[0].len = hlen + 100; + case_result_t r = run_case(script, 1, 0); + snprintf(dbg, sizeof dbg, "rc=%d body=%zu", r.rc, r.body_len); + check("normal_fixed(CL=100, 100B) -> NCM_OK", + (r.rc == NCM_OK) && (r.body_len == 100), dbg); + } + + /* ---- 场景 3:定长(CL=100) 两次读到收满(多块)→ NCM_OK ---- */ + { + static const unsigned char hdrs[] = + "HTTP/1.1 200 OK\r\nContent-Length: 100\r\n\r\n"; + static unsigned char part1[512], part2[64]; + size_t hlen = sizeof hdrs - 1; + memcpy(part1, hdrs, hlen); + for (size_t i = 0; i < 60; i++) part1[hlen + i] = 'C'; + for (size_t i = 0; i < 40; i++) part2[i] = 'D'; + tr_chunk_t script[2]; + script[0].buf = part1; script[0].len = hlen + 60; + script[1].buf = part2; script[1].len = 40; + case_result_t r = run_case(script, 2, 0); + snprintf(dbg, sizeof dbg, "rc=%d body=%zu", r.rc, r.body_len); + check("normal_fixed_multi(CL=100, 60+40B) -> NCM_OK", + (r.rc == NCM_OK) && (r.body_len == 100), dbg); + } + + /* ---- 场景 4:Content-Length: 0(空 body)→ BS_DONE → NCM_OK ---- */ + { + static const unsigned char chunk[] = + "HTTP/1.1 204 No Content\r\nContent-Length: 0\r\n\r\n"; + static const tr_chunk_t script[] = { { chunk, sizeof chunk - 1 } }; + case_result_t r = run_case(script, 1, 0); + snprintf(dbg, sizeof dbg, "rc=%d body=%zu", r.rc, r.body_len); + check("cl_zero(CL=0) -> NCM_OK empty", + (r.rc == NCM_OK) && (r.body_len == 0), dbg); + } + + /* ---- 场景 5:无 Content-Length、靠对端关闭结束(BS_CLOSE) → 合法 → NCM_OK ---- */ + { + static const unsigned char hdrs[] = + "HTTP/1.1 200 OK\r\n\r\n"; /* 无 CL、无 chunked */ + static unsigned char chunk[512]; + size_t hlen = sizeof hdrs - 1; + memcpy(chunk, hdrs, hlen); + for (size_t i = 0; i < 100; i++) chunk[hlen + i] = 'E'; + tr_chunk_t script[1]; + script[0].buf = chunk; script[0].len = hlen + 100; + case_result_t r = run_case(script, 1, 0); + snprintf(dbg, sizeof dbg, "rc=%d body=%zu", r.rc, r.body_len); + check("close_delimited(no CL, 100B, close) -> NCM_OK", + (r.rc == NCM_OK) && (r.body_len == 100), dbg); + } + + /* ---- 场景 6:定长(CL=100) 提前关闭,且用 TLS close_notify 模拟 → 截断 ---- */ + { + static const unsigned char hdrs[] = + "HTTP/1.1 200 OK\r\nContent-Length: 100\r\n\r\n"; + static unsigned char chunk[512]; + size_t hlen = sizeof hdrs - 1; + memcpy(chunk, hdrs, hlen); + for (size_t i = 0; i < 50; i++) chunk[hlen + i] = 'F'; + tr_chunk_t script[1]; + script[0].buf = chunk; script[0].len = hlen + 50; + case_result_t r = run_case(script, 1, MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY); + snprintf(dbg, sizeof dbg, "rc=%d body=%zu", r.rc, r.body_len); + check("truncate_close_notify(CL=100, 50B) -> NCM_ERR_IO", + (r.rc != NCM_OK) && (r.rc == NCM_ERR_IO) && (r.body_len == 50), dbg); + } + + /* ---- 场景 7:chunked 完整(0 分块末端)→ BS_DONE → NCM_OK ---- */ + { + static const unsigned char chunk[] = + "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n" + "A\r\n0123456789\r\n" /* 16 进制 0xA = 10 字节 */ + "0\r\n\r\n"; + static const tr_chunk_t script[] = { { chunk, sizeof chunk - 1 } }; + case_result_t r = run_case(script, 1, 0); + snprintf(dbg, sizeof dbg, "rc=%d body=%zu", r.rc, r.body_len); + check("chunked_full(10B + 0-chunk) -> NCM_OK", + (r.rc == NCM_OK) && (r.body_len == 10), dbg); + } + + /* ---- 场景 8:chunked 中途关闭(数据未收完)→ 截断 → 非 NCM_OK ---- */ + { + static const unsigned char chunk[] = + "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n" + "5\r\nhel"; /* 宣称 5 字节,只发了 3,未到 0 分块 */ + static const tr_chunk_t script[] = { { chunk, sizeof chunk - 1 } }; + case_result_t r = run_case(script, 1, 0); + snprintf(dbg, sizeof dbg, "rc=%d body=%zu", r.rc, r.body_len); + check("chunked_truncated(3/5B, close) -> NCM_ERR_IO", + (r.rc != NCM_OK) && (r.rc == NCM_ERR_IO), dbg); + } + + printf("\n== HTTP TRUNCATE SIM: %s ==\n", g_fail ? "FAIL" : "ALL PASS"); + return g_fail ? 1 : 0; +} diff --git a/patch_v2/ncm_app/bench/mem_release_sim.c b/patch_v2/ncm_app/bench/mem_release_sim.c new file mode 100644 index 0000000..7811fdd --- /dev/null +++ b/patch_v2/ncm_app/bench/mem_release_sim.c @@ -0,0 +1,120 @@ +/* bench/mem_release_sim.c — 宿主「彻底释放」路径验证(agent 32bit OOM 专项)。 + * + * 目的:验证「32bit(>16bit)→S32 解码 播完/切歌 是否真正释放」——ncm_dec_open(整文件)→ + * ncm_dec_read 全量(dr_flac S32 满幅路径)→ ncm_dec_close(dr_flac_close + stream_src_close) + * 循环 N 次,断言进程匿名 RSS(RssAnon,OOM 高危口径)**不单调累积**(播完回落基线), + * 且解码中峰值有界(32bit 解码缓冲存在但有限,非 40MB 级)。 + * + * 依赖:仅 app/play 解码层(decoder.c + stream_src.c + dr_flac_impl + minimp3_impl), + * 无 core/SDK/app.c——本 sim 是解码链路的最小区块,直接量「解码器 buff 播完 free」。 + * 编译(ncm_app 目录下): + * gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd \ + * -Ivendor/jsmn -Ivendor/minimp3 -Ivendor/dr_flac -Ivendor/mbedtls/include \ + * -c bench/mem_release_sim.c -o /tmp/mr.o + * gcc -O2 -Wall -Wextra ... -c app/play/stream_src.c -o /tmp/mr_ss.o + * gcc -O2 -Wall -Wextra ... -c app/play/decoder.c -o /tmp/mr_dec.o + * gcc -O2 -c -Ivendor/minimp3 app/play/minimp3_impl.c -o /tmp/mr_mp3.o + * gcc -O2 -c -Ivendor/dr_flac app/play/dr_flac_impl.c -o /tmp/mr_flac.o + * gcc -O2 -o mem_release_sim /tmp/mr.o /tmp/mr_ss.o /tmp/mr_dec.o /tmp/mr_mp3.o /tmp/mr_flac.o -lpthread -lm + * 运行:./mem_release_sim [iters] (iters 默认 5) + * 断言(PASS 才 exit 0): + * - RssAnon[最后] - RssAnon[基准] < 256KB(播完回落,无累积); + * - 解码中峰值有界(peak - 基准 < 8MB,32bit 解码缓冲不是 40MB 级); + * - 总帧数 > 0 且 bits==32(确实走了 S32 满幅路径——本 sim 只受 24bit 输入触发)。 + */ +#include "../app/play/decoder.h" +#include +#include +#include +#include + +#define READ_CHUNK_FRAMES 4096 +#define ITER_DEFAULT 5 +#define SETTLE_TOLERANCE_KB (256) /* 播完回落允许抖动(<256KB 视为无累积) */ +#define PEAK_BOUND_KB (8 * 1024) /* 解码中峰值必须 <8MB(区别于 40MB 级异常) */ + +/* 静默 meow_log(本 sim 不链 core;decoder/stream_src 不调它,仅占位防漏链) */ +void meow_log(int lvl, const char *fmt, ...) { (void)lvl; (void)fmt; } + +static long rss_anon_kb(void) +{ + FILE *f = fopen("/proc/self/status", "r"); + if (!f) return -1; + char line[256]; + long v = -1; + while (fgets(line, sizeof line, f)) + if (!strncmp(line, "RssAnon:", 8)) { v = atol(line + 8); break; } + fclose(f); + return v; +} + +int main(int argc, char **argv) +{ + if (argc < 2) { + printf("usage: mem_release_sim [iters]\n"); + return 2; + } + const char *path = argv[1]; + int iters = (argc > 2) ? atoi(argv[2]) : ITER_DEFAULT; + if (iters < 2) iters = 2; + + long base = rss_anon_kb(); + if (base < 0) { printf("FAIL cannot read RssAnon\n"); return 1; } + printf("mem_release[0] baseline RssAnon=%ldKB\n", base); + + int first_dec_ok = 0; + long prev_after = 0, last_after = 0; /* 最近两次播完 close 后的 RssAnon */ + for (int i = 0; i < iters; i++) { + ncm_decoder_t *dec = NULL; + int rate = 0, ch = 0, bits = 0; + int64_t tf = 0; + if (ncm_dec_open(&dec, path, &rate, &ch, &bits, &tf) != 0) { + printf("FAIL iter%d ncm_dec_open (%s)\n", i, path); + return 1; + } + if (i == 0) { + printf("mem_release: fmt=%s %dHz/%dch/%dbit total_frames=%lld (S32 path=%d)\n", + ncm_dec_format(dec), rate, ch, bits, (long long)tf, bits > 16); + first_dec_ok = 1; + } + + /* 全量读(S32 满幅路径),测解码中匿名峰值 */ + static int32_t buf[READ_CHUNK_FRAMES * 2]; + long peak = 0; + int64_t frames = 0, got = 0; + do { + got = ncm_dec_read(dec, buf, READ_CHUNK_FRAMES); + frames += got; + long r = rss_anon_kb(); + if (r > peak) peak = r; + } while (got > 0); + + printf("mem_release[%d] after decode: frames=%lld peak_RssAnon=%ldKB\n", + i + 1, (long long)frames, peak); + + ncm_dec_close(dec); /* drflac_close + stream_src_close(整文件静态源) */ + long r = rss_anon_kb(); + prev_after = last_after; + last_after = r; + printf("mem_release[%d] after close RssAnon=%ldKB (delta=%ldKB)\n", + i + 1, r, r - base); + + /* 解码中峰值有界:32bit 解码缓冲不是 40MB 级 */ + if (peak - base > PEAK_BOUND_KB) { + printf("FAIL iter%d decode peak %ldKB - base %ldKB = %ldKB > %dKB (异常高)\n", + i, peak, base, peak - base, PEAK_BOUND_KB); + return 1; + } + } + + /* 累积断言:最后两次播完后的 RssAnon 相对基线的增量都 < 容忍(无单调上升) */ + long d1 = last_after - base; + long d2 = prev_after - base; + int no_accum = (d1 < SETTLE_TOLERANCE_KB) && (d2 < SETTLE_TOLERANCE_KB); + printf("mem_release: settle delta last=%ldKB prev=%ldKB -> %s\n", + d1, d2, no_accum ? "PASS(无累积)" : "FAIL(有残留)"); + + int ok = first_dec_ok && no_accum; + printf("mem_release: %s\n", ok ? "PASS" : "FAIL"); + return ok ? 0 : 1; +} diff --git a/patch_v2/ncm_app/bench/mem_watch_sim.c b/patch_v2/ncm_app/bench/mem_watch_sim.c new file mode 100644 index 0000000..c582799 --- /dev/null +++ b/patch_v2/ncm_app/bench/mem_watch_sim.c @@ -0,0 +1,95 @@ +/* bench/mem_watch_sim.c — 宿主「边播监控取证」阈值/峰值验证(agent 32bit OOM 取证)。 + * + * 归属演进(2026-09-02 定案,ARCHITECTURE_PRINCIPLES ③混合):**降载决策上收通用层**—— + * A + C 分别归 SDK meow_cachegov / meow_svc_perf;本 sim 只验证 mem_watch 的**取证**语义: + * 三档 level 标注滞回(normal→high→critical→滞回→normal)+ song_end 重置峰值 + song_begin 起算。 + * (旧版「dl_paused/prefetch_paused 降载断言」已随降载上收移除——那由 meow_cachegov_sim + * 与 svc_perf test_cachegov_monitor 覆盖。) + * + * 依赖:仅 app/play/mem_watch.c(+ meow_log 桩)。 + * 编译(ncm_app 目录下): + * gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \ + * -c bench/mem_watch_sim.c -o /tmp/mw.o + * gcc -O2 -Wall -Wextra -I. -I../sdk -c app/play/mem_watch.c -o /tmp/mw_mod.o + * gcc -O2 -o mem_watch_sim /tmp/mw.o /tmp/mw_mod.o -lpthread -lm + * 运行:./mem_watch_sim (exit 0=PASS / 1=FAIL) + */ +#include "../app/play/mem_watch.h" +#include +#include +#include +#include +#include + +/* meow_log 桩:打印(便于看阈值过渡;sim 仅验证 mem_watch 逻辑,非真实日志系统) */ +void meow_log(int lvl, const char *fmt, ...) +{ + (void)lvl; + char b[256]; va_list ap; va_start(ap, fmt); + vsnprintf(b, sizeof b, fmt, ap); va_end(ap); + printf(" [mem.log] %s\n", b); +} + +static int fails; +#define CHECK(cond, msg) do { \ + if (!(cond)) { printf("FAIL %s\n", msg); fails++; } \ + else { printf("ok %s\n", msg); } } while (0) + +/* 等监控线程至少走过 2 个周期(feed 后确保已采样) */ +static void settle_ms(int m) { usleep((useconds_t)m * 1000u); } + +int main(void) +{ + /* 用 env 设定短周期(50ms)与干净阈值,加速确定性判定 */ + setenv("NCM_MEM_WATCH_PERIOD_MS", "50", 1); + setenv("NCM_MEM_WATCH_HIGH_KB", "30720", 1); /* 30MB */ + setenv("NCM_MEM_WATCH_LOW_KB", "20480", 1); /* 20MB */ + setenv("NCM_MEM_WATCH_CRITICAL_KB", "40960", 1); /* 40MB */ + + if (mem_watch_start() != 0) { printf("FAIL mem_watch_start\n"); return 1; } + printf("started (period=50ms H=30M L=20M C=40M)\n"); + + /* 1) 正常区 */ + mem_watch_set_forced_kb(10000); + settle_ms(150); + CHECK(mem_watch_level() == NCM_MEM_LVL_NORMAL, "level==NORMAL @10MB"); + + /* 2) 升到 HIGH */ + mem_watch_set_forced_kb(35000); + settle_ms(150); + CHECK(mem_watch_level() == NCM_MEM_LVL_HIGH, "level==HIGH @35MB"); + + /* 3) 升到 CRITICAL */ + mem_watch_set_forced_kb(45000); + settle_ms(150); + CHECK(mem_watch_level() == NCM_MEM_LVL_CRITICAL, "level==CRITICAL @45MB"); + + /* 4) 滞回:落在 LOW..HIGH 之间 -> 保持 CRITICAL(防抖动) */ + mem_watch_set_forced_kb(25000); + settle_ms(150); + CHECK(mem_watch_level() == NCM_MEM_LVL_CRITICAL, "level stays CRITICAL @25MB (hysteresis)"); + + /* 5) 回到正常:低于 LOW -> NORMAL */ + mem_watch_set_forced_kb(15000); + settle_ms(150); + CHECK(mem_watch_level() == NCM_MEM_LVL_NORMAL, "level==NORMAL @15MB"); + + /* 6) 调度器释放钩子:song_end 重置峰值 + song_begin 起算 + rss_anon 反映注入值 */ + mem_watch_set_forced_kb(20000); + settle_ms(100); + mem_watch_song_begin(); + mem_watch_set_forced_kb(37000); /* 本曲峰值冲到 37MB */ + settle_ms(100); + long anon = mem_watch_rss_anon_kb(); + CHECK(anon == 37000, "rss_anon_kb reflects forced value"); + mem_watch_song_end(); /* 释放钩子:落峰值+重置 */ + settle_ms(100); + mem_watch_song_begin(); + mem_watch_set_forced_kb(12000); /* 新曲:峰值应从新起算 */ + settle_ms(100); + CHECK(mem_watch_level() == NCM_MEM_LVL_NORMAL, "after song_end+begin, level resets to NORMAL"); + + mem_watch_stop(); + printf("mem_watch_sim: %s\n", fails ? "FAIL" : "PASS"); + return fails ? 1 : 0; +} diff --git a/patch_v2/ncm_app/bench/nav_exit_sim.c b/patch_v2/ncm_app/bench/nav_exit_sim.c new file mode 100644 index 0000000..2c9f349 --- /dev/null +++ b/patch_v2/ncm_app/bench/nav_exit_sim.c @@ -0,0 +1,130 @@ +/* bench/nav_exit_sim.c — 任务2:home 根页左滑「回桌面后台」宿主验证。 + * + * 场景(0 真实请求/0 真实 launcher):fake launcher IPC socket(MEOW_IPC_SOCK)。调 + * ipc_media_nav_exit(),断言: + * ① fake launcher 收到一条 MSG_NAV 帧,nav_type==MEOW_NAV_EXIT、app_id=="dev.player.ncm"; + * ② 本函数**不**置 g_app_quit(进程保留,媒体会话继续上报 PLAYING,任务2 ③ 保活前提)。 + * 这验证「从网易云主页面侧滑返回不再退出进程,而请求 launcher 退后台(有歌在播则 M4 保活)」。 + * + * 编译(宿主,ncm_app 目录下): + * gcc -O1 -DJSMN_HEADER -I. -Icore -Iapp -I../sdk -I../meow_launcherd \ + * -c bench/nav_exit_sim.c -o /tmp/ne.o + * gcc -O1 -DJSMN_HEADER -I. -Icore -Iapp -I../sdk -I../meow_launcherd \ + * -c app/play/ipc_media.c -o /tmp/ne_ipc.o + * gcc -O1 -o nav_exit_sim /tmp/ne.o /tmp/ne_ipc.o -lpthread + * 运行:./nav_exit_sim (0 网络;PASS/FAIL 退出码) + */ +#include "../app/ncm_app_internal.h" +#include "meow_ipc.h" +#include +#include +#include +#include +#include +#include +#include + +ncm_app_state_t g_st; +ncm_play_run_t g_play = { .mu = PTHREAD_MUTEX_INITIALIZER, .state = NCM_PC_STOPPED, .cur_song = -1 }; +volatile int g_app_quit = 0; +volatile int g_net_ready = 1; +int g_host_mock = 0; + +size_t ncm_strlcpy(char *dst, const char *src, size_t sz) +{ + size_t n = 0; + if (sz) { while (src[n] && n < sz - 1) { dst[n] = src[n]; n++; } dst[n] = 0; } + while (src[n]) n++; + return n; +} +void meow_log(meow_log_level lvl, const char *fmt, ...) { (void)lvl; (void)fmt; } + +/* ipc_media.c 的其余依赖(本 sim 不驱动播放/封面/歌词,仅服务链接与消息类型) */ +uint64_t meow_ui_time_ms(void) { return 0; } +int player_manager_playing_song(void) { return g_play.cur_song; } +int player_manager_current_song(ncm_song_t *out) { (void)out; return -1; } +void player_manager_pause(int on) { (void)on; } +void player_manager_next(int dir) { (void)dir; } +void player_manager_seek(int64_t ms) { (void)ms; } +int player_manager_state(void) { return g_play.state; } +const char *cover_current_path(void) { return NULL; } +int lyric_count(void) { return 0; } + +/* ---- fake launcher ---- */ +static const char *g_sock = "/tmp/nav_exit_launcher.sock"; +static uint16_t g_got_type = 0; +static int g_got_nav_type = -1; +static char g_got_app[MEOW_APP_ID_MAX]; +static int g_got = 0; + +static void *server(void *arg) +{ + (void)arg; + unlink(g_sock); + int lfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (lfd < 0) return NULL; + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, g_sock, sizeof a.sun_path - 1); + if (bind(lfd, (struct sockaddr *)&a, sizeof a) != 0) { close(lfd); return NULL; } + listen(lfd, 1); + int fd = accept(lfd, NULL, NULL); + if (fd < 0) { close(lfd); return NULL; } + uint8_t rbuf[MEOW_IPC_MAX]; + ssize_t n = read(fd, rbuf, sizeof rbuf); + if (n >= (ssize_t)MEOW_IPC_HDR_LEN) { + uint32_t magic, len; + uint16_t ver, type; + memcpy(&magic, rbuf, 4); memcpy(&ver, rbuf + 4, 2); memcpy(&type, rbuf + 6, 2); + memcpy(&len, rbuf + 12, 4); + if (magic == MEOW_IPC_MAGIC && ver == MEOW_IPC_VERSION && len <= (uint32_t)(n - MEOW_IPC_HDR_LEN)) { + g_got_type = type; + if (type == MSG_NAV && len >= sizeof(m_nav_t)) { + m_nav_t nv; + memcpy(&nv, rbuf + MEOW_IPC_HDR_LEN, sizeof nv); + g_got_nav_type = (int)nv.nav_type; + nv.app_id[MEOW_APP_ID_MAX - 1] = 0; + snprintf(g_got_app, sizeof g_got_app, "%s", nv.app_id); + } + g_got = 1; + } + } + close(fd); close(lfd); + return NULL; +} + +int main(void) +{ + int fail = 0; + setenv("MEOW_IPC_SOCK", g_sock, 1); + + pthread_t th; + if (pthread_create(&th, NULL, server, NULL) != 0) { printf("FAIL server thread\n"); return 1; } + usleep(50000); + + int rc = ipc_media_nav_exit(); + pthread_join(th, NULL); + + if (rc != 0) { printf("FAIL ipc_media_nav_exit rc=%d\n", rc); fail = 1; } + if (!g_got) { printf("FAIL fake launcher received no frame\n"); fail = 1; } + else if (g_got_type != MSG_NAV) { + printf("FAIL expected MSG_NAV (0x%04x) got 0x%04x\n", MSG_NAV, g_got_type); + fail = 1; + } else if (g_got_nav_type != MEOW_NAV_EXIT) { + printf("FAIL expected MEOW_NAV_EXIT got %d\n", g_got_nav_type); + fail = 1; + } else if (strcmp(g_got_app, "dev.player.ncm") != 0) { + printf("FAIL expected app_id dev.player.ncm got \"%s\"\n", g_got_app); + fail = 1; + } else { + printf("PASS nav_exit: type=0x%04x nav_type=EXIT app=%s\n", g_got_type, g_got_app); + } + if (g_app_quit != 0) { printf("FAIL g_app_quit set (process must stay alive for bg keepalive)\n"); fail = 1; } + else printf("PASS nav_exit did NOT set g_app_quit (process/alive, media keeps reporting PLAYING)\n"); + + unlink(g_sock); + if (fail) { printf("nav_exit_sim FAIL\n"); return 1; } + printf("nav_exit_sim PASS\n"); + return 0; +} diff --git a/patch_v2/ncm_app/bench/player_flow_sim.c b/patch_v2/ncm_app/bench/player_flow_sim.c index 64e7ebd..b68559c 100644 --- a/patch_v2/ncm_app/bench/player_flow_sim.c +++ b/patch_v2/ncm_app/bench/player_flow_sim.c @@ -151,6 +151,7 @@ int ncm_cache_open(const ncm_cache_t *c, const char *id, const char *ext, ncm_ca *w = (ncm_cache_writer_t *)calloc(1, 64); return NCM_OK; } const char *ncm_cache_writer_tmp_path(const ncm_cache_writer_t *w) { (void)w; return g_tmp_path; } +const char *ncm_cache_writer_final_path(const ncm_cache_writer_t *w) { (void)w; return g_tmp_path; } /* 链接占位:drop_finished 对 .tmp 无害 */ int ncm_cache_write(ncm_cache_writer_t *w, const unsigned char *d, size_t l) { (void)w; (void)d; (void)l; return NCM_OK; } int ncm_cache_commit(ncm_cache_writer_t *w) { free(w); g_cache_ok = 1; g_commits++; return NCM_OK; } int ncm_cache_abort(ncm_cache_writer_t *w) @@ -617,6 +618,32 @@ int main(void) g_eof = 1; wait_state(NCM_PC_STOPPED, 3000); + printf("== P14: 点「正在播的同曲」-> 只聚焦不重播(任务3 re-entry,非 P12 切档)==\n"); + usleep(300 * 1000); + g_eof = 0; g_reads_left = -1; g_url_rc = NCM_OK; g_fee = 0; + g_part_size = 0; g_http_status = 200; g_416_first = 0; g_dl_delay_ms = 0; g_dl_fail = 0; + g_vip = NCM_VIP_NONE; g_downloads = 0; g_url_calls = 0; g_last_key[0] = 0; g_last_ext[0] = 0; + player_manager_stop(); /* 清 P13 可能残留(STOPPED 或已自动顺延) */ + player_manager_set_quality(NCM_LEVEL_STANDARD, 10); + g_cache_ok = 1; /* standard 缓存命中 */ + player_manager_play(0); + CHECK(wait_state(NCM_PC_PLAYING, 3000), "song0 playing at standard (cache hit)"); + int opens_before = g_opens; + int writes_before = g_writes; + /* 任务3:歌单里点「正在播的同曲」(**未** set_quality → pending_replay=0)→ 只聚焦不重播: + * 同步返回后状态仍是 PLAYING(若是重播此刻必为 BUFFER),线程窗口内不得新增 audio_out_open。 */ + player_manager_play(0); + CHECK(player_manager_state() == NCM_PC_PLAYING, + "re-enter same idx -> state stays PLAYING immediately (no restart to BUFFER)"); + usleep(300 * 1000); /* 若真发生重播,线程会重跑 run_song 再开 audio */ + CHECK(g_opens == opens_before, + "re-enter same idx -> no new audio_out_open (no restart), opens=%d/%d", g_opens, opens_before); + CHECK(g_writes > writes_before, + "re-enter same idx -> same stream kept playing (writes %d->%d)", writes_before, g_writes); + player_manager_stop(); + wait_state(NCM_PC_STOPPED, 2000); /* 干净收尾(任务3 复验后置 STOPPED,勿自动顺延) */ + g_cache_ok = 0; g_cache_real[0] = 0; + usleep(200 * 1000); player_manager_stop_thread(); printf(g_fail ? "== PLAYER FLOW SIM: FAIL ==\n" : "== PLAYER FLOW SIM: ALL PASS ==\n"); diff --git a/patch_v2/ncm_app/bench/search_sim b/patch_v2/ncm_app/bench/search_sim new file mode 100755 index 0000000..35ea23c Binary files /dev/null and b/patch_v2/ncm_app/bench/search_sim differ diff --git a/patch_v2/ncm_app/bench/vol_sync_sim.c b/patch_v2/ncm_app/bench/vol_sync_sim.c new file mode 100644 index 0000000..b482e8a --- /dev/null +++ b/patch_v2/ncm_app/bench/vol_sync_sim.c @@ -0,0 +1,171 @@ +/* bench/vol_sync_sim.c — 任务1:开播前「系统当前音量 → audiod」下发宿主验证。 + * + * 场景(0 真实请求/0 真实 audiod):fake audiod socket(AF_UNIX SOCK_STREAM)+ 临时 settings.conf + * (audio.volume = ,读改写单键同源同键)。调 audio_out_sync_volume(),断言 fake audiod 收到 + * 一条 MSG_AUDIO_VOLUME 帧,且 pct == settings 的 audio.volume。这验证「打开音频会话时把系统 + * 音量同步下发到 audiod」的根因修复(未播放时 DAC 停在默认/残留音量 → 动音量键才追设定值)。 + * + * 另验证:音量语义只传 pct(0-100)——反转/真静音(255) 由 audiod dac_vol_set 负责,本层不碰。 + * + * 编译(宿主,ncm_app 目录下): + * gcc -O1 -DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd \ + * -c bench/vol_sync_sim.c -o /tmp/vs.o + * gcc -O1 -DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd \ + * -c app/play/audio_out.c -o /tmp/vs_ao.o + * gcc -O1 -o vol_sync_sim /tmp/vs.o /tmp/vs_ao.o -lpthread + * 运行:./vol_sync_sim (0 网络;PASS/FAIL 退出码) + */ +#include "../app/ncm_app_internal.h" +#include "meow_ipc.h" +#include +#include +#include +#include +#include +#include +#include +#include + +ncm_app_state_t g_st; +ncm_play_run_t g_play; +volatile int g_app_quit = 0; +volatile int g_net_ready = 1; +int g_host_mock = 0; +size_t ncm_strlcpy(char *dst, const char *src, size_t sz) +{ + size_t n = 0; + if (sz) { while (src[n] && n < sz - 1) { dst[n] = src[n]; n++; } dst[n] = 0; } + while (src[n]) n++; + return n; +} +void meow_log(meow_log_level lvl, const char *fmt, ...) { (void)lvl; (void)fmt; } + +/* audio_out.c 引用但本 sim 不经 SDK 调用的符号:只链不跑就满足链接 */ +int meow_audio_open(int rate, int ch, int fmt) { (void)rate;(void)ch;(void)fmt; return -1; } +int meow_audio_write(const void *pcm, int frames) { (void)pcm; return frames; } +int meow_audio_pause(int sid, int paused) { (void)sid;(void)paused; return 0; } +int meow_audio_close(int sid) { (void)sid; return 0; } +int meow_audio_query(meow_audio_state *out) { (void)out; return -1; } + +/* ---- fake audiod ---- */ +static const char *g_sock = "/tmp/vol_sync_audiod.sock"; +static uint16_t g_got_type = 0; +static int32_t g_got_pct = -1; +static int g_got = 0; +static volatile int g_nconn = 0; /* 收到多少条连接 */ +static volatile int g_nframe = 0; /* 收到多少条 VOLUME 帧 */ +static volatile int g_stop = 0; + +/* fake audiod:接受多条连接(上限 32,超过即停——失败才触发,正常不该超),每条只读一帧 + * VOLUME 然后等对端 EOF(ncm 侧 close)。统计连接数与帧数,供「重复 sync 不新建连接」断言。 */ +static void *server(void *arg) +{ + (void)arg; + unlink(g_sock); + int lfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (lfd < 0) return NULL; + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, g_sock, sizeof a.sun_path - 1); + if (bind(lfd, (struct sockaddr *)&a, sizeof a) != 0) { close(lfd); return NULL; } + listen(lfd, 16); + int lfl = fcntl(lfd, F_GETFL, 0); + if (lfl >= 0) fcntl(lfd, F_SETFL, lfl | O_NONBLOCK); /* 非阻塞:g_stop 能及时退出 */ + while (!g_stop && g_nconn < 32) { + int fd = accept(lfd, NULL, NULL); + if (fd < 0) { usleep(20000); continue; } /* EAGAIN:无连接,短睡重试 */ + g_nconn++; + /* 读满一帧(20B)或 EOF:stream socket read 可分段返回,须循环凑满再判。 */ + uint8_t frame[MEOW_IPC_HDR_LEN + sizeof(int32_t)]; + ssize_t n = 0; + while (n < (ssize_t)sizeof frame) { + ssize_t r = read(fd, frame + n, sizeof frame - (size_t)n); + if (r <= 0) break; /* EOF / 错 */ + n += r; + } + if (n >= (ssize_t)(MEOW_IPC_HDR_LEN + sizeof(int32_t))) { + uint32_t magic, len; + uint16_t ver, type; + memcpy(&magic, frame, 4); memcpy(&ver, frame + 4, 2); memcpy(&type, frame + 6, 2); + memcpy(&len, frame + 12, 4); + if (magic == MEOW_IPC_MAGIC && ver == MEOW_IPC_VERSION && type == MSG_AUDIO_VOLUME) { + g_got_type = type; + memcpy(&g_got_pct, frame + 16, sizeof g_got_pct); + g_got = 1; + g_nframe++; + } + } + close(fd); + } + close(lfd); + return NULL; +} + +int main(void) +{ + int fail = 0; + /* 临时 settings.conf:audio.volume = 24(读改写单键同源键),另带 max 干扰键 */ + const char *cfg = "/tmp/vol_sync_settings.conf"; + FILE *f = fopen(cfg, "w"); + if (!f) { printf("FAIL cannot write settings\n"); return 1; } + fprintf(f, "# meow-settings 1\naudio.volume = 24\naudio.volume_max = 50\ndisplay.brightness = 80\n"); + fclose(f); + setenv("MEOW_SETTINGS_FILE", cfg, 1); + setenv("MEOW_AUDIO_SOCK", g_sock, 1); + + pthread_t th; + if (pthread_create(&th, NULL, server, NULL) != 0) { printf("FAIL server thread\n"); return 1; } + usleep(50000); /* 等 fake audiod listen */ + + /* 第一次 sync(首次 open):应下发一帧 VOLUME,pct==settings */ + audio_out_sync_volume(); + usleep(50000); /* 等服务线程 accept+读帧(主/服务线程异步) */ + if (!g_got) { printf("FAIL fake audiod received no frame\n"); fail = 1; } + else if (g_got_type != MSG_AUDIO_VOLUME) { + printf("FAIL expected MSG_AUDIO_VOLUME (0x%04x) got 0x%04x\n", MSG_AUDIO_VOLUME, g_got_type); + fail = 1; + } else if (g_got_pct != 24) { + printf("FAIL expected pct=24 (settings audio.volume) got %d\n", (int)g_got_pct); + fail = 1; + } else { + printf("PASS audiod sync volume: type=0x%04x pct=%d == settings.audio.volume\n", + g_got_type, (int)g_got_pct); + } + + /* 连接风暴回归(§2026-09-02 fd=N accept 根因):音量未变时反复 sync 不得新建连接。 + * 模拟边下边播/切歌期频繁 audio_out_open → audio_out_sync_volume(同值,不重发)。 */ + for (int i = 0; i < 50; i++) audio_out_sync_volume(); /* 同值 50 次:应零新增连接 */ + usleep(50000); + if (g_nconn != 1) { + printf("FAIL unchanged-volume re-sync created %d connections (expect 1)\n", (int)g_nconn); + fail = 1; + } else { + printf("PASS unchanged-volume re-sync: 50 calls -> 1 connection (storm eliminated)\n"); + } + + /* 值变才重发:settings 改成 50 → 下一次 sync 应新建第 2 条连接 */ + FILE *g = fopen(cfg, "w"); + if (!g) { printf("FAIL cannot rewrite settings\n"); fail = 1; } + else { + fprintf(g, "# meow-settings 1\naudio.volume = 50\naudio.volume_max = 50\ndisplay.brightness = 80\n"); + fclose(g); + } + audio_out_sync_volume(); + usleep(50000); + if (g_nconn != 2 || g_got_pct != 50) { + printf("FAIL changed-volume re-sync: conns=%d (expect 2) last_pct=%d (expect 50)\n", + (int)g_nconn, (int)g_got_pct); + fail = 1; + } else { + printf("PASS changed-volume re-sync: settings=50 -> 2nd connection pct=50\n"); + } + + g_stop = 1; + pthread_join(th, NULL); + unlink(g_sock); + unlink(cfg); + if (fail) { printf("vol_sync_sim FAIL\n"); return 1; } + printf("vol_sync_sim PASS\n"); + return 0; +} diff --git a/patch_v2/ncm_app/build_ncm.sh b/patch_v2/ncm_app/build_ncm.sh index 6022a3c..df050d1 100755 --- a/patch_v2/ncm_app/build_ncm.sh +++ b/patch_v2/ncm_app/build_ncm.sh @@ -21,7 +21,8 @@ APP="app/app.c app/ui/login.c app/ui/home.c app/ui/playlist.c app/ui/player.c \ app/ui/settings.c app/ui/search.c app/ui/ncm_colors.c app/ui/chrome.c \ app/logic/appstate.c app/logic/lyric.c app/logic/songmeta.c \ app/logic/cover.c app/logic/account.c app/play/player_manager.c app/play/audio_out.c \ - app/play/ipc_media.c app/play/decoder.c app/play/stream_src.c app/play/minimp3_impl.c" + app/play/ipc_media.c app/play/decoder.c app/play/stream_src.c app/play/minimp3_impl.c \ + app/play/mem_watch.c" echo "==> app+core (strict)" for f in $APP $CORE; do diff --git a/patch_v2/ncm_app/build_ncm_host.sh b/patch_v2/ncm_app/build_ncm_host.sh index ce3a16b..d1c9c50 100755 --- a/patch_v2/ncm_app/build_ncm_host.sh +++ b/patch_v2/ncm_app/build_ncm_host.sh @@ -20,7 +20,7 @@ echo "==> host SDK objects" SDK_SRC="meow_ui_gfx meow_ui_sys meow_ui_theme meow_ui_vol meow_ui_log meow_ui_widget \ meow_ui_input meow_ui_activity meow_ui_dialog meow_ui_ime meow_ui_draw meow_ui_view \ meow_ui_img meow_ui_config meow_ui_nav meow_ui_media meow_notify_ipc meow_pinyin \ - meow_ui_audio meow_ui_anim meow_ui_box meow_ui_scroll meow_ui_str" + meow_ui_audio meow_ui_anim meow_ui_box meow_ui_scroll meow_ui_str meow_cachegov" for f in $SDK_SRC; do $CC_HOST -O2 -c -DMEOW_UI_HOST_TEST -I"$SDK_DIR" -I"$ROOT/patch_v2/meow_launcherd" \ "$SDK_DIR/$f.c" -o "$OUT/host/sdk_$f.o" || { echo "sdk $f FAILED"; exit 1; } diff --git a/patch_v2/ncm_app/build_ncm_sims.sh b/patch_v2/ncm_app/build_ncm_sims.sh index d831380..d0c3a22 100755 --- a/patch_v2/ncm_app/build_ncm_sims.sh +++ b/patch_v2/ncm_app/build_ncm_sims.sh @@ -49,9 +49,21 @@ gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \ -Icore -Ivendor/jsmn -Ivendor/mbedtls/include -Ivendor/minimp3 -Ivendor/dr_flac -Ivendor/mbedtls/include -c app/play/player_manager.c -o /tmp/pf_pm.o gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \ -Icore -c app/play/stream_src.c -o /tmp/pf_ss.o -gcc -O2 -Wl,--gc-sections -o player_flow_sim /tmp/pf.o /tmp/pf_pm.o /tmp/pf_ss.o -lpthread -lm +gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \ + -Icore -c app/play/mem_watch.c -o /tmp/pf_mw.o # player_manager 引用(边播边监控取证,agent 32bit OOM) +gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd -Icore \ + -c ../sdk/meow_cachegov.c -o /tmp/pf_cg.o # player_manager 引 meow_cachegov_*(page-cache 治理 A) +gcc -O2 -Wl,--gc-sections -o player_flow_sim /tmp/pf.o /tmp/pf_pm.o /tmp/pf_ss.o /tmp/pf_mw.o /tmp/pf_cg.o -lpthread -lm run_sim player_flow_sim +echo "==> build mem_watch_sim(边播边监控阈值/滞回/调度器,宿主桩驱动;agent 32bit OOM。" +echo " mem_release_sim 无累积断言在 bench/ 头注释手动构建:需预生成 24bit flac 素材)" +gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \ + -c bench/mem_watch_sim.c -o /tmp/mw.o +gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -I../sdk -c app/play/mem_watch.c -o /tmp/mw_mod.o +gcc -O2 -o mem_watch_sim /tmp/mw.o /tmp/mw_mod.o -lpthread -lm +run_sim mem_watch_sim + echo "==> build window_sim" gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd -Icore -Ivendor/jsmn -Ivendor/mbedtls/include \ -c bench/window_sim.c -o /tmp/ws.o @@ -113,4 +125,14 @@ gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Iapp -Icore -I../sdk -I../meow_launcher gcc -O2 -o account_cookie_sim /tmp/acs.o /tmp/acs_account.o -lpthread -lm run_sim account_cookie_sim +echo "==> build http_truncate_sim (ncm_http 截断守卫宿主测试;mock mbedtls 传输层,0 真实网络)" +gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -Ivendor/mbedtls/include \ + -c bench/http_truncate_sim.c -o /tmp/t.o +gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -Ivendor/mbedtls/include \ + -ffunction-sections -c core/ncm_http.c -o /tmp/t_http.o +gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -Ivendor/mbedtls/include \ + -ffunction-sections -c core/ncm_common.c -o /tmp/t_common.o +gcc -Wl,--gc-sections -o http_truncate_sim /tmp/t.o /tmp/t_http.o /tmp/t_common.o +run_sim http_truncate_sim + echo "===== all sims done =====" diff --git a/patch_v2/ncm_app/cookie_refresh_sim b/patch_v2/ncm_app/cookie_refresh_sim new file mode 100755 index 0000000..5de879b Binary files /dev/null and b/patch_v2/ncm_app/cookie_refresh_sim differ diff --git a/patch_v2/ncm_app/core/ncm_api.c b/patch_v2/ncm_app/core/ncm_api.c index ac8a495..460ad00 100644 --- a/patch_v2/ncm_app/core/ncm_api.c +++ b/patch_v2/ncm_app/core/ncm_api.c @@ -196,9 +196,54 @@ static int obj_bool(const ncm_json_t *j, int tok, const char *key) { return (strcmp(s, "true") == 0); } +/* UTF-8 整序列安全追加:把 src 按「多字节字符整序列」边界拷入 dst[*pos..],放不下整字符则 + * 停止(不切半个字)。sz 为 dst 总大小(含 NUL 位)。用于 art_name 多歌手拼接的字节截断。 */ +static void append_artist_name(char *dst, size_t *pos, size_t sz, const char *src) +{ + size_t cap = sz - 1; /* 最多写 sz-1 字节,留 NUL */ + size_t i = 0; + while (src[i] && *pos < cap) { + unsigned char c = (unsigned char)src[i]; + size_t k = 1; + if (c < 0x80) k = 1; + else if ((c & 0xE0) == 0xC0) k = 2; + else if ((c & 0xF0) == 0xE0) k = 3; + else if ((c & 0xF8) == 0xF0) k = 4; + else { dst[(*pos)++] = src[i++]; continue; } /* 非法起始字节:单字节原样收 */ + if (*pos + k > cap) break; /* 整字符放不下:丢弃(保住 UTF-8 完整性) */ + memcpy(dst + *pos, src + i, k); + *pos += k; i += k; + } + dst[*pos] = 0; +} + +/* 歌曲 ar[](歌手数组)多歌手名用 '/' 拼接进 art_name(≤ out_sz,UTF-8 整序列截断)。 + * 单歌手行为不变(无 '/');空歌手条目跳过(避免连写分隔符「//」)。 */ +static void fill_artist(const ncm_json_t *j, int e, char *out, size_t out_sz) +{ + out[0] = 0; + int ar = ncm_json_obj_get(j, e, "ar"); + if (ar < 0 || ncm_json_type(j, ar) != JSMN_ARRAY) return; + int nart = ncm_json_arr_len(j, ar); + size_t pos = 0; + for (int k = 0; k < nart; k++) { + int a = ncm_json_arr_get(j, ar, k); + if (a < 0) break; + char nm[128]; /* 单歌手名缓冲(obj_str 已截断到 127 字节) */ + obj_str(j, a, "name", nm, sizeof nm); + if (!nm[0]) continue; /* 空条目:跳过,不加分隔符 */ + if (pos > 0) { /* 第 2+ 歌手:先补 '/'(需 1 字节 + NUL) */ + if (pos >= out_sz - 1) break; /* 无空间放分隔符 */ + out[pos++] = '/'; + out[pos] = 0; + } + append_artist_name(out, &pos, out_sz, nm); + } +} + /* 解析一处「歌曲对象数组」(song_detail / cloudsearch / track_all 离线解析 共用)。 - * 元素字段:id/name/ar[0].name/al.name/al.picUrl/dt/fee。id 可为数字或字符串(均以文本拷贝, - * 兼容 >2^31 的歌曲 id)。返回 NCM_OK,*out_n=写入数。 */ + * 元素字段:id/name/ar[].name(多歌手 '/' 拼接) /al.name/al.picUrl/dt/fee。id 可为数字或 + * 字符串(均以文本拷贝,兼容 >2^31 的歌曲 id)。返回 NCM_OK,*out_n=写入数。 */ static int parse_songs_array(const ncm_json_t *j, int arr, ncm_song_t *out, int max, int *out_n) { *out_n = 0; @@ -213,11 +258,7 @@ static int parse_songs_array(const ncm_json_t *j, int arr, ncm_song_t *out, int obj_str(j, e, "name", out[i].name, sizeof out[i].name); out[i].duration_ms = obj_int(j, e, "dt"); out[i].fee = (int)obj_int(j, e, "fee"); - int ar = ncm_json_obj_get(j, e, "ar"); - if (ar >= 0 && ncm_json_type(j, ar) == JSMN_ARRAY) { - int a0 = ncm_json_arr_get(j, ar, 0); - if (a0 >= 0) obj_str(j, a0, "name", out[i].art_name, sizeof out[i].art_name); - } + fill_artist(j, e, out[i].art_name, sizeof out[i].art_name); int al = ncm_json_obj_get(j, e, "al"); if (al >= 0) { obj_str(j, al, "name", out[i].album_name, sizeof out[i].album_name); @@ -371,13 +412,15 @@ static int level_from_str(const char *s) { } /* 账户权限允许的最高档(§20.3,与 app/logic/account.c account_level_cap 同口径): - * NONE→EXHIGH / CVIP→HIRES / SVIP→SURROUND。core 侧权威实现,供降级链自钳顶。 */ + * NONE→EXHIGH / CVIP→HIRES / SVIP|SUPER→SURROUND。core 侧权威实现,供降级链自钳顶。 + * SUPER(超级会员)为最高档,与 SVIP 同享全景声顶档。 */ static int level_cap_for_vip(int vip) { switch (vip) { - case NCM_VIP_SVIP: return NCM_LEVEL_SURROUND; - case NCM_VIP_CVIP: return NCM_LEVEL_HIRES; - default: return NCM_LEVEL_EXHIGH; + case NCM_VIP_SVIP: + case NCM_VIP_SUPER: return NCM_LEVEL_SURROUND; + case NCM_VIP_CVIP: return NCM_LEVEL_HIRES; + default: return NCM_LEVEL_EXHIGH; } } diff --git a/patch_v2/ncm_app/core/ncm_api.h b/patch_v2/ncm_app/core/ncm_api.h index b3ecaf5..1978989 100644 --- a/patch_v2/ncm_app/core/ncm_api.h +++ b/patch_v2/ncm_app/core/ncm_api.h @@ -40,13 +40,19 @@ enum { const char *ncm_api_level_str(int level); /* API level 串(SKY/SURROUND 同为 "sky") */ const char *ncm_api_level_key(int level); /* 缓存键唯一后缀(SKY="sky"/SURROUND="sky_c51") */ -/* 会员等级(vip/info 判定,BIZ_NOTES §1):SVIP(黑胶)权限 ⊇ CVIP(畅听/音乐包)。 */ +/* 会员等级(vip/info 判定,BIZ_NOTES §1):用户语义 0=非会员 / 1=畅听会员(VIP) / + * 2=黑胶会员(CVIP) / 3=超级会员(SVIP)。枚举名保留旧名(CVIP/SVIP)避免大改,新增 + * NCM_VIP_SUPER=3 为最高档;权限 SUPER ⊇ SVIP(黑胶) ⊇ CVIP(畅听/音乐包)。 + * 注:ncm_api_parse_vip_info 仍只产出 NONE/CVIP/SVIP(关联音乐包=CVIP、黑胶=SVIP, + * 无独立“超级”字段);3 档目前经 account.json 持久化进入/显示(account_load 钳位已放行)。 */ enum { - NCM_VIP_NONE = 0, - NCM_VIP_CVIP = 1, - NCM_VIP_SVIP = 2, + NCM_VIP_NONE = 0, + NCM_VIP_CVIP = 1, /* 畅听会员/音乐包 */ + NCM_VIP_SVIP = 2, /* 黑胶会员 */ + NCM_VIP_SUPER = 3, /* 超级会员(最高档) */ }; -/* 档位所需会员门槛:standard/higher/exhigh=NONE;lossless/hires=CVIP;其余=SVIP。 */ +/* 档位所需会员门槛:standard/higher/exhigh=NONE;lossless/hires=CVIP;其余=SVIP。 + * 门槛最高只到 SVIP(2)(超级会员=3 为 ⊇ 黑胶,天然通过所有门槛,无需单独门禁)。 */ int ncm_api_level_gate(int level); typedef struct ncm_playlist { @@ -65,7 +71,7 @@ typedef struct ncm_playlist { typedef struct ncm_song { char id[24]; char name[128]; - char art_name[96]; /* 首个歌手名 */ + char art_name[96]; /* 歌手名:多歌手用 '/' 拼接(UTF-8 整序列截断) */ char album_name[96]; char al_pic[256]; /* 专辑封面 picUrl(song_detail/cloudsearch 解析;播放页黑胶封面) */ int32_t duration_ms; diff --git a/patch_v2/ncm_app/core/ncm_cache.c b/patch_v2/ncm_app/core/ncm_cache.c index d011700..f105707 100644 --- a/patch_v2/ncm_app/core/ncm_cache.c +++ b/patch_v2/ncm_app/core/ncm_cache.c @@ -232,6 +232,11 @@ const char *ncm_cache_writer_tmp_path(const ncm_cache_writer_t *w) { return w ? w->tmp_path : ""; } +/* 写者 final(正式)路径(commit 后 rename 目标;曲终 drop_finished L0 DONTNEED 用)。 */ +const char *ncm_cache_writer_final_path(const ncm_cache_writer_t *w) { + return w ? w->final_path : ""; +} + /* 半成品判定(阶段3,§5.1):.tmp(下载中)/ .part(中断残体)不属正式缓存条目, * 不进 LRU 扫描/计数——否则正被下载播放的文件会被缓存上限误删(写者管理域)。 */ static int is_partial_name(const char *name) diff --git a/patch_v2/ncm_app/core/ncm_cache.h b/patch_v2/ncm_app/core/ncm_cache.h index 7bdde7d..85c2b25 100644 --- a/patch_v2/ncm_app/core/ncm_cache.h +++ b/patch_v2/ncm_app/core/ncm_cache.h @@ -61,6 +61,8 @@ int ncm_cache_commit(ncm_cache_writer_t *w); /* win; 返回 0=成功 */ int ncm_cache_abort(ncm_cache_writer_t *w); /* 写者 .tmp 路径(只读):边下边播解码侧用它开只读 fd 建 stream_src。 */ const char *ncm_cache_writer_tmp_path(const ncm_cache_writer_t *w); +/* 写者 final(正式)路径(只读):commit 后目标文件路径,用于曲终 drop_finished(L0 DONTNEED)。 */ +const char *ncm_cache_writer_final_path(const ncm_cache_writer_t *w); /* ---- 阶段3:.part 断点续传 + 在播文件防淘汰(NCM_STREAM_PLAY §5) ---- * 命名约定:中断的 .tmp 保留为 final+".part"(残体),供下次播放「Range 续传」; diff --git a/patch_v2/ncm_app/core/ncm_http.c b/patch_v2/ncm_app/core/ncm_http.c index af6cbba..524da1d 100644 --- a/patch_v2/ncm_app/core/ncm_http.c +++ b/patch_v2/ncm_app/core/ncm_http.c @@ -491,6 +491,13 @@ static int http_run(const ncm_http_req_t *req, sink_t *sink, ncm_http_resp_t *re } } if (sink->overflow) { ret = NCM_ERR_FULL; goto done; } + /* 截断守卫:body 未到达合法终态却已跳出读取循环(对端提前关闭 / PEER_CLOSE_NOTIFY)。 + * 合法终态只有两种:BS_DONE(定长收满 / chunked 到 0 分块 / Content-Length:0,body_st_init 即置) + * 与 BS_CLOSE(无 Content-Length、依 RFC 靠对端关闭定界——关闭即合法结束)。 + * body_st_init 里 BS_CLOSE 仅在 content_length==SIZE_MAX 且非 chunked 时进入;若 Content-Length + * 存在而未收满(此时必为 BS_LEN/BS_CHUNK_*),或 chunked 未到 0 分块就关闭,即为截断: + * 此时返回错误,调用方(song_src_dispose)走 abort_part 保留 .part,不得 commit 残缺文件。 */ + if (bst.st != BS_DONE && bst.st != BS_CLOSE) { ret = NCM_ERR_IO; goto done; } ret = NCM_OK; done: mbedtls_net_free(&net); diff --git a/patch_v2/ncm_app/degrade_sim_tmp b/patch_v2/ncm_app/degrade_sim_tmp new file mode 100755 index 0000000..c435506 Binary files /dev/null and b/patch_v2/ncm_app/degrade_sim_tmp differ diff --git a/patch_v2/ncm_app/http_truncate_sim b/patch_v2/ncm_app/http_truncate_sim new file mode 100755 index 0000000..0d31339 Binary files /dev/null and b/patch_v2/ncm_app/http_truncate_sim differ diff --git a/patch_v2/ncm_app/mem_release_sim b/patch_v2/ncm_app/mem_release_sim new file mode 100755 index 0000000..4e39f97 Binary files /dev/null and b/patch_v2/ncm_app/mem_release_sim differ diff --git a/patch_v2/ncm_app/mem_sim b/patch_v2/ncm_app/mem_sim new file mode 100755 index 0000000..d18311a Binary files /dev/null and b/patch_v2/ncm_app/mem_sim differ diff --git a/patch_v2/ncm_app/mem_watch_sim b/patch_v2/ncm_app/mem_watch_sim new file mode 100755 index 0000000..be73aef Binary files /dev/null and b/patch_v2/ncm_app/mem_watch_sim differ diff --git a/patch_v2/ncm_app/nav_exit_sim b/patch_v2/ncm_app/nav_exit_sim new file mode 100755 index 0000000..50077be Binary files /dev/null and b/patch_v2/ncm_app/nav_exit_sim differ diff --git a/patch_v2/ncm_app/ncm b/patch_v2/ncm_app/ncm index 05136c7..3f0d808 100755 Binary files a/patch_v2/ncm_app/ncm and b/patch_v2/ncm_app/ncm differ diff --git a/patch_v2/ncm_app/ncm_host b/patch_v2/ncm_app/ncm_host index ab7e430..ef3b1be 100755 Binary files a/patch_v2/ncm_app/ncm_host and b/patch_v2/ncm_app/ncm_host differ diff --git a/patch_v2/ncm_app/playlist_race_sim b/patch_v2/ncm_app/playlist_race_sim new file mode 100755 index 0000000..585fff7 Binary files /dev/null and b/patch_v2/ncm_app/playlist_race_sim differ diff --git a/patch_v2/ncm_app/search_sim b/patch_v2/ncm_app/search_sim new file mode 100755 index 0000000..803976d Binary files /dev/null and b/patch_v2/ncm_app/search_sim differ diff --git a/patch_v2/ncm_app/stream_sim b/patch_v2/ncm_app/stream_sim new file mode 100755 index 0000000..1d1c89d Binary files /dev/null and b/patch_v2/ncm_app/stream_sim differ diff --git a/patch_v2/ncm_app/test/test_ncm_core_host b/patch_v2/ncm_app/test/test_ncm_core_host index 9ee10c9..bd0d4d7 100755 Binary files a/patch_v2/ncm_app/test/test_ncm_core_host and b/patch_v2/ncm_app/test/test_ncm_core_host differ diff --git a/patch_v2/ncm_app/toggle_sim b/patch_v2/ncm_app/toggle_sim new file mode 100755 index 0000000..e294f48 Binary files /dev/null and b/patch_v2/ncm_app/toggle_sim differ diff --git a/patch_v2/ncm_app/vol_sync_sim b/patch_v2/ncm_app/vol_sync_sim new file mode 100755 index 0000000..beb2279 Binary files /dev/null and b/patch_v2/ncm_app/vol_sync_sim differ diff --git a/patch_v2/reader_app/.gitignore b/patch_v2/reader_app/.gitignore index 72d1518..483ad59 100644 --- a/patch_v2/reader_app/.gitignore +++ b/patch_v2/reader_app/.gitignore @@ -1,6 +1,7 @@ # os.meow.reader 构建产物(不入仓) /vendor/ /reader +/os.meow.reader.mpk /test/test_reader_core_host /test/loaders_smoke_host /test/rdft_smoke_mipsel diff --git a/patch_v2/reader_app/NOTICE b/patch_v2/reader_app/NOTICE index 88833ab..a7c6b8d 100644 --- a/patch_v2/reader_app/NOTICE +++ b/patch_v2/reader_app/NOTICE @@ -1,10 +1,30 @@ os.meow.reader (patch_v2/reader_app) — Third-Party NOTICES ========================================================== -This application statically links and/or bundles the following -third-party components. Their copyrights and licenses are reproduced -below. The build script (build_reader.sh) fetches the exact pinned -versions, so the binaries can be rebuilt / re-linked from source. +This application (os.meow.reader) statically links and/or bundles the +following third-party components. Their copyrights and licenses are +reproduced below. The build script (build_reader.sh) fetches the exact +pinned versions, so the binaries can be rebuilt / re-linked from source. + +License ownership +----------------- +The reader application's own source code is licensed under the Apache +License 2.0 (part of the MeowOS project; see the top-level repository +LICENSE). The third-party components listed below retain their own +licenses — in particular libmobi is LGPL-3.0 and stays under LGPL-3.0 +even though it is statically linked into the binary. Under LGPL-3.0 +section 1 the Apache-2.0 application is not thereby required to be +re-licensed under the GPL. + +Source availability +------------------- +The reader source (this application), build_reader.sh, and the top-level +MeowOS repository are publicly pullable at: + https://git.yeij.top/AskaEth/MeowOS-For-R3P2.git +(admin push: ssh://git@git.yeij.top:2222/AskaEth/MeowOS-For-R3P2.git) +The libmobi source tree at the locked commit remains available from the +upstream project and is re-fetched by build_reader.sh; a relink how-to +is in README_LGPL_RELINK.md in the reader_app directory. -------------------------------------------------------------------- 1. libmobi — Copyright (c) 2014-2024 Bartek Fabiszewski @@ -26,6 +46,11 @@ versions, so the binaries can be rebuilt / re-linked from source. upstream repo at the commit above); the build script retrieves exactly that commit. + Full license texts (shipped with the package in resources/licenses/): + - GNU GPL version 3: resources/licenses/GPL-3.0.txt + - GNU LGPL version 3: resources/licenses/LGPL-3.0.txt + Both full texts are also reproduced below. + Full license text (GNU LGPL v3): -------------------------------------------------------------------- GNU LESSER GENERAL PUBLIC LICENSE diff --git a/patch_v2/reader_app/README_LGPL_RELINK.md b/patch_v2/reader_app/README_LGPL_RELINK.md new file mode 100644 index 0000000..8065547 --- /dev/null +++ b/patch_v2/reader_app/README_LGPL_RELINK.md @@ -0,0 +1,85 @@ +# README_LGPL_RELINK — 如何替换 libmobi 并重新链接阅读器(LGPL-3.0 relink) + +`os.meow.reader` 静态链接 **libmobi**(LGPL-3.0)。LGPL-3.0 §4(d) 要求以「允许用户在替换 libmobi 后重链」的方式提供源码(§4(d)(0))。本文件给出完整重链步骤:获取工具链 → 替换 libmobi 源码 → 重链 → 装回设备。**本操作可自行修改 libmobi 并重编,不触犯任何许可证限制;修改 libmobi 部分的衍生成果仍受 LGPL-3.0 约束(但不会被强制整体 GPL)。** + +## 0) 相关文件与源码位置 + +| 内容 | 位置 | +|---|---| +| 阅读器自有源码 | 主仓:`https://git.yeij.top/AskaEth/MeowOS-For-R3P2.git`(`patch_v2/reader_app/`,Apache-2.0) | +| libmobi 源码(锁定 commit) | `https://github.com/bfabiszewski/libmobi`,commit `906274205c11944b628da1c553b255acb1af7c55`(2024-10-29);`build_reader.sh` 的 `LM_URL` 即指向该 SHA | +| 重链构建脚本 | `patch_v2/reader_app/build_reader.sh`(可复现构建 + qemu-mipsel 冒烟) | +| 合规材料(随 .mpk 分发) | `patch_v2/reader_app/resources/licenses/`(`NOTICE` + `GPL-3.0.txt` + `LGPL-3.0.txt`) | + +> 主仓公开性:`git ls-remote https://git.yeij.top/AskaEth/MeowOS-For-R3P2.git` 可匿名读到 HEAD,即公开可克隆(见 NOTICE「Source availability」)。若看不到,说明镜像/网络受限,请从维护者获取。 + +## 1) 获取 mipsel 交叉工具链(两种方式) + +阅读器是 **mipsel / musl 静态软浮点 (sf)** 目标。构建用的是本地 `.tools/`(gitignored,不入仓),接收者需自行准备等价工具链: + +- **方式 A(推荐,zig 单文件)**:下载 zig(`zig cc` 支持 `-target mipsel-linux-musl`,静态软浮点)。本项目用 `zig-mipsel-cc` 封装,等价于: + ```sh + # 示例:zig-0.12+;把 zig 解压后设 ZIG 路径 + # zig cc -target mipsel-linux-musl -static ... + ``` +- **方式 B(musl 交叉编译套件)**:安装 `mipsel-linux-muslsf-cross`(如 musl.cc 发布的 `mipsel-linux-muslsf-cross`),得到: + ``` + $ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf # sysroot/lib等 + ``` + `$ROOT = $HOME/Project/R3ProII`(build_reader.sh 内 `ROOT` 变量,可改)。 + +若本地已有 `.tools/`,重链无需再装。 + +## 2) 替换 libmobi 源码 + +```sh +cd "$HOME/Project/R3ProII/patch_v2/reader_app" +# 拉取你想要的 libmobi(或用 build_reader.sh 自动拉锁定 commit) +# 用锁定 commit 拉取: +curl -fsSL -o /tmp/lm.tgz \ + "https://codeload.github.com/bfabiszewski/libmobi/tar.gz/906274205c11944b628da1c553b255acb1af7c55" +rm -rf vendor/libmobi && mkdir -p /tmp/lm_m && tar xzf /tmp/lm.tgz -C /tmp/lm_m --strip-components=1 +mkdir -p vendor/libmobi && cp -r /tmp/lm_m/src vendor/libmobi/src && cp /tmp/lm_m/COPYING vendor/libmobi/COPYING +# 然后在此处对 vendor/libmobi/src/*.c 做你的修改 +``` + +## 3) 强制重编 libmobi 目标文件 + 重链 + +`build_reader.sh` 对 `vendor/libmobi/src/*.c` 有「已存在 .o 跳过」缓存(`vendor/build/lm_*.o`),替换源码后**必须清掉**旧目标文件再重跑,否则不会重编 libmobi: + +```sh +rm -f vendor/build/lm_*.o +./build_reader.sh # 全量:编 libmobi/miniz/stb/freetype + 链接 reader + qemu-mipsel 冒烟 +``` + +- 产物:`./reader`(mipsel 静态 ELF)。 +- 需要 `qemu-mipsel`(跑冒烟单测);宿主 core/loader 单测用 `gcc`。 +- 若只想重链不跑测试,可手动执行 `build_reader.sh` 第 7 步的链接命令(见脚本注释),但建议跑全套以保正确。 + +## 4) 装回设备(两种方式) + +- **方式 A(宽松,最直接;启动路径不验签)**:launcher 重扫 `MeowAPPs/` 后直接运行二进制,故把重链后的 `reader` 直接放到应用目录即可,**不触发安装期签名门**: + ```sh + scp ./reader root@<设备IP>:/usr/data/mnt/sd_0/MeowAPPs/os.meow.reader/reader + ssh root@<设备IP> 'chmod 755 /usr/data/mnt/sd_0/MeowAPPs/os.meow.reader/reader' + # 若在跑,重开阅读器;MeowAPPs 热更即生效 + ``` +- **方式 B(走官方 .mpk + 签名 + nyai 安装)**:`os.meow.*` 包无签会被 `nyai`/launcher 拒装,故需用 `.keys/signing.key` 签名(仅维护者持有私钥)。接收者的本地重链一般走方式 A。 + ```sh + ./pack_reader_mpk.sh # 产 os.meow.reader.mpk(含 resources/licenses/) + ../meow_sign/meow_sign sign os.meow.reader.mpk "$HOME/Project/R3ProII/.keys/signing.key" + scp os.meow.reader.mpk root@<设备IP>:/data/ + ssh root@<设备IP> 'nyai install /data/os.meow.reader.mpk' + ``` + +## 5) 修改自由度说明(LGPL 敏感点) + +- 安装期签名校验只针对**未签名/无有效签**的 `.mpk` 安装;**启动路径**是 launcher 直接运行 `MeowAPPs//` 下的二进制(`README.md:259` 热更语义),对重链后的二进制**不做运行时验签**,改造自由度未被结构性封死。 +- 重链产物请**保留 `resources/licenses/` 随包**(含 GPL-3.0 + LGPL-3.0 全文 + NOTICE),并保留 libmobi 版权声明。 +- 你对 libmobi 的修改形成衍生作品:其自身仍按 LGPL-3.0 提供;把修改后的 libmobi 重新分发时应随附相应源码(按 LGPL §4 履行,本仓库的重链脚本 + 锁定的 `LM_URL` 即可复现)。 + +## 6) 校验 + +- `file ./reader` 应显示 `ELF 32-bit LSB executable, MIPS, ... statically linked, ... soft-float`。 +- `tar tzvf os.meow.reader.mpk` 应含 `resources/licenses/{NOTICE,GPL-3.0.txt,LGPL-3.0.txt}`。 +- 真机打开一本 MOBI 确认可读。 diff --git a/patch_v2/reader_app/app.c b/patch_v2/reader_app/app.c index 2fe3a78..286a73e 100644 --- a/patch_v2/reader_app/app.c +++ b/patch_v2/reader_app/app.c @@ -34,6 +34,15 @@ static void app_on_tick(void) if (reader_is_active()) { reader_on_tick(now); return; } } +static void app_on_touch_down(int x, int y) +{ + if (reader_is_active()) { reader_on_touch_down(x, y); return; } +} +static void app_on_touch_move(int x, int y) +{ + if (reader_is_active()) { reader_on_touch_move(x, y); return; } +} + int main(void) { meow_log(MEOW_LOG_INFO, "start"); @@ -48,7 +57,7 @@ int main(void) meow_view_page_push(ACT_SHELF); meow_ui_events ev = { app_on_key, app_on_tap, app_on_swipe, app_on_tick, - NULL, NULL, NULL }; + NULL, app_on_touch_down, app_on_touch_move }; meow_ui_run(&ev); meow_view_free(g_reader_view); diff --git a/patch_v2/reader_app/app/book.c b/patch_v2/reader_app/app/book.c index 165f08e..6e5af99 100644 --- a/patch_v2/reader_app/app/book.c +++ b/patch_v2/reader_app/app/book.c @@ -24,7 +24,8 @@ struct rd_book { char path[400]; char font[400]; char base[256]; - const char *encname; + const char *encname; /* TXT 有效编码可读名(含覆盖后);非 TXT = "" */ + rd_enc_t enc; /* TXT 有效编码;非 TXT = RD_ENC_UTF8 */ rdft *ft; rd_src *src; rd_fmt_t fmt; @@ -44,21 +45,22 @@ struct rd_book { int seg_cache_idx; }; -static const char *fmt_encname(rd_fmt_t f, const char *path) +/* 编码枚举 → 可读名(R3:Big5 正确解码后名实相符)。 */ +static const char *enc_to_name(rd_enc_t enc) { - if (f == RD_FMT_TXT) { - rdtxt_info info; - if (rdtxt_probe(path, &info) == 0) { - switch (info.enc) { - case RD_ENC_UTF8: return "UTF-8"; - case RD_ENC_UTF16LE: return "UTF-16LE"; - case RD_ENC_UTF16BE: return "UTF-16BE"; - case RD_ENC_GB18030: return "GB18030"; - case RD_ENC_BIG5: return "Big5(按GB18030)"; - case RD_ENC_ASCII: return "ASCII"; - } - } + switch (enc) { + case RD_ENC_UTF8: return "UTF-8"; + case RD_ENC_UTF16LE: return "UTF-16LE"; + case RD_ENC_UTF16BE: return "UTF-16BE"; + case RD_ENC_GB18030: return "GB18030"; + case RD_ENC_BIG5: return "Big5"; + case RD_ENC_ASCII: return "ASCII"; + default: return ""; } +} +static const char *fmt_encname(rd_fmt_t f, rd_enc_t enc) +{ + if (f == RD_FMT_TXT) return enc_to_name(enc); return ""; } @@ -169,7 +171,10 @@ int rd_book_open(rd_book **b, const char *path, const char *font_path, if (rd_src_open(&x->src, path) != 0) goto close; x->fmt = rd_src_fmt(x->src); - x->encname = fmt_encname(x->fmt, path); + x->enc = RD_ENC_UTF8; + rd_enc_t tmp_enc = RD_ENC_UTF8; + if (rd_src_txt_enc(x->src, &tmp_enc) == 0) x->enc = tmp_enc; + x->encname = fmt_encname(x->fmt, x->enc); x->is_img = (x->fmt == RD_FMT_CBZ); if (!x->is_img) { @@ -210,6 +215,7 @@ int rd_book_font(rd_book *b) { return b ? b->fs : 0; } int rd_book_ls(rd_book *b) { return b ? b->ls : 0; } int rd_book_is_image(rd_book *b) { return b ? b->is_img : 0; } const char *rd_book_encname(const rd_book *b) { return b ? (b->encname ? b->encname : "") : ""; } +rd_enc_t rd_book_enc_get(const rd_book *b) { return b ? b->enc : RD_ENC_UTF8; } void rd_book_geom(rd_book *b, int *fs, int *ls, int *pw, int *ph, int *mx, int *my, int *top) { @@ -225,6 +231,28 @@ void rd_book_geom(rd_book *b, int *fs, int *ls, int *pw, int *ph, int *mx, int * const char *rd_book_basename(const rd_book *b) { return b ? b->base : ""; } const char *rd_book_path(const rd_book *b) { return b ? b->path : ""; } +/* R3:TXT 编码切换——重开 src(带覆盖编码)、重建页索引、回锚当前页。非 TXT/CBZ 返回 -1。 + * 事务式:先开新 src 成功才换(失败保留旧 src,书仍可用,返回负值)。 */ +int rd_book_set_enc(rd_book *b, rd_enc_t enc) +{ + if (!b || b->is_img || b->fmt != RD_FMT_TXT) return -1; + if (enc == b->enc) return b->cur; /* 同编码,无需重建 */ + int seg; int32_t ch; + rd_book_anchor(b, &seg, &ch); + + rd_src *ns = NULL; + if (rd_src_open_with_enc(&ns, b->path, &enc) != 0) return -2; /* 旧 src 未动 */ + rd_src_free(b->src); b->src = ns; /* 换新 */ + b->enc = enc; + b->encname = fmt_encname(b->fmt, b->enc); + + /* 重建索引(复用现值 fs/ls/content_*),再回锚 */ + free(b->segs); free(b->pages); b->segs = NULL; b->pages = NULL; + b->nsegs = b->npages = 0; + if (build_index(b) != 0) return -3; /* 新 src 有效但索引空 → 渲染返回 -1,安全降级 */ + return rd_book_jump_to_anchor(b, seg, ch, b->fs, b->ls); +} + void rd_book_anchor(rd_book *b, int *seg, int32_t *ch) { if (!b || b->npages <= 0) { if (seg) *seg = 0; if (ch) *ch = 0; return; } @@ -243,6 +271,15 @@ int rd_book_turn(rd_book *b, int delta) return np; } +int rd_book_set_page(rd_book *b, int page) +{ + if (!b || b->npages <= 0) return 0; + if (page < 0) page = 0; + if (page >= b->npages) page = b->npages - 1; + b->cur = page; + return page; +} + /* ---- 文本页渲染(FreeType 字形 → 全屏 565) ---- */ static int render_text_page(rd_book *b, int page_no, uint16_t *out, int pitch, uint16_t bg565, uint16_t fg565) { diff --git a/patch_v2/reader_app/app/book.h b/patch_v2/reader_app/app/book.h index 3ef37f7..4059dba 100644 --- a/patch_v2/reader_app/app/book.h +++ b/patch_v2/reader_app/app/book.h @@ -35,6 +35,7 @@ void rd_book_close(rd_book *b); int rd_book_pages(rd_book *b); int rd_book_current(rd_book *b); /* 当前页 */ int rd_book_turn(rd_book *b, int delta); /* +1/-1;clamp 到 [0,npages-1] */ +int rd_book_set_page(rd_book *b, int page); /* 跳到绝对页;clamp;返回实际页(段刮擦用) */ int rd_book_is_image(rd_book *b); /* 1=CBZ 图片页书 */ /* 目录:0=无;i 项标题写到 title,目标段/字符偏移填 *seg 与 *ch。 */ @@ -56,8 +57,13 @@ int rd_book_render_text(rd_book *b, int page_no, rd_book_line_fn fn, void *ctx) /* 书名/信息(章节名:TXT 无章节=文件名;enc 可读名)。调用方 strcpy 到自己的缓冲。 */ const char *rd_book_basename(const rd_book *b); /* 文件名(路径尾分量) */ const char *rd_book_encname(const rd_book *b); +rd_enc_t rd_book_enc_get(const rd_book *b); /* TXT 有效编码(非 TXT = RD_ENC_UTF8) */ const char *rd_book_path(const rd_book *b); /* 原始路径 */ +/* R3:TXT 编码切换:重开 src(覆盖编码)+ 重建索引 + 回锚。返回目标页;非 TXT/CBZ 或失败负值。 + * 用于「编码」菜单(Big5 繁体书手动选定)。 */ +int rd_book_set_enc(rd_book *b, rd_enc_t enc); + /* 字体/几何 */ int rd_book_font(rd_book *b); /* 当前字号 */ int rd_book_ls(rd_book *b); /* 行距% */ diff --git a/patch_v2/reader_app/app/progress.c b/patch_v2/reader_app/app/progress.c index 4d0800c..fe99e61 100644 --- a/patch_v2/reader_app/app/progress.c +++ b/patch_v2/reader_app/app/progress.c @@ -68,18 +68,19 @@ int rdprog_load(const char *book_path, rdprog *p) FILE *f = fopen(path, "rb"); if (!f) return 0; char line[256]; - int seg = 0, fs = 0, ls = 0; int32_t ch = 0; uint64_t ts = 0; int v = 0, found = 0; + int seg = 0, fs = 0, ls = 0, enc = -1; int32_t ch = 0; uint64_t ts = 0; int v = 0, found = 0; while (fgets(line, sizeof line, f)) { if (sscanf(line, "v=%d", &v) == 1) { if (v == 1) found = 1; } else if (sscanf(line, "seg=%d", &seg) == 1) { /* no-op */ } else if (sscanf(line, "ch=%d", &ch) == 1) { /* no-op */ } else if (sscanf(line, "fs=%d", &fs) == 1) { /* no-op */ } else if (sscanf(line, "ls=%d", &ls) == 1) { /* no-op */ } + else if (sscanf(line, "enc=%d", &enc) == 1) { /* no-op(旧档无此键→enc=-1 自动) */ } else if (sscanf(line, "ts=%llu", (unsigned long long *)&ts) == 1) { /* no-op */ } } fclose(f); if (!found) return 0; - p->valid = 1; p->seg = seg; p->ch = ch; p->fs = fs; p->ls = ls; p->ts = ts; + p->valid = 1; p->seg = seg; p->ch = ch; p->fs = fs; p->ls = ls; p->enc = enc; p->ts = ts; return 1; } @@ -96,6 +97,7 @@ int rdprog_save(const char *book_path, const rdprog *p) fprintf(f, "ch=%d\n", (int)p->ch); fprintf(f, "fs=%d\n", p->fs); fprintf(f, "ls=%d\n", p->ls); + fprintf(f, "enc=%d\n", p->enc); fprintf(f, "ts=%llu\n", (unsigned long long)p->ts); fclose(f); return 0; diff --git a/patch_v2/reader_app/app/progress.h b/patch_v2/reader_app/app/progress.h index cb88baf..d456d3c 100644 --- a/patch_v2/reader_app/app/progress.h +++ b/patch_v2/reader_app/app/progress.h @@ -21,6 +21,7 @@ typedef struct rdprog { int32_t ch; int fs; int ls; + int enc; /* R3:显式文本编码(rd_enc_t);-1=自动(未手动选) */ uint64_t ts; /* 最近阅读 unix 秒 */ } rdprog; diff --git a/patch_v2/reader_app/app/reader.c b/patch_v2/reader_app/app/reader.c index e356a5a..e664d7d 100644 --- a/patch_v2/reader_app/app/reader.c +++ b/patch_v2/reader_app/app/reader.c @@ -17,7 +17,7 @@ #include #define RD_MENU_H 64 -#define RD_MENU_BTN 6 +#define RD_MENU_BTN 7 static rd_book *g_book; static int g_reader_active; @@ -34,12 +34,47 @@ static int g_dir_top, g_dir_row_h; /* 目录列表几何(rdsrc 目录项 static char g_font_path[400]; static int g_notice_ms; +/* ---- R3 翻页动画(轻量水平滑入) ---- */ +static meow_anim *g_anim; /* 活动翻页动画;NULL=无 */ +static uint16_t *g_anim_old; /* 动画「滑出」页位图(切页前快照) */ +static int g_anim_dir; /* 1=下一页(旧左移/新右入);-1=上一页(旧右移/新左入) */ + +/* ---- R3 段刮擦(右缘拖动快速定位 + 预览) ---- */ +#define RD_SCRUB_W 26 /* 右缘刮擦条宽度 */ +static int g_scrub; /* 1=刮擦中 */ +static int g_scrub_page; /* 刮擦目标页(实时预览用) */ + +/* ---- R3 编码切换(TXT;Big5 繁体书手动选定) ---- */ +static int g_enc_idx; /* g_enc_cycle[] 下标(当前有效编码) */ + static uint32_t g_page_bg, g_page_fg; /* 正文页底/字色(24bit) */ static char g_chapter[128]; /* 顶栏章节名(TXT 无章节=文件名) */ #define RD_READER_DEFAULT_FS 20 #define RD_READER_DEFAULT_LS 130 +/* R3:编码循环(显式编码;Auto 由打开时探测决定,不在此列)。 + * -1 哨兵不用;TXT 初始 = 打开探测结果(GB18030 兜底)。 */ +static const rd_enc_t g_enc_cycle[] = { RD_ENC_GB18030, RD_ENC_BIG5, RD_ENC_UTF8, + RD_ENC_UTF16LE, RD_ENC_UTF16BE }; +#define RD_ENC_CYCLE_N ((int)(sizeof g_enc_cycle / sizeof g_enc_cycle[0])) +static const char *enc_cycle_name(int idx) +{ + switch (g_enc_cycle[idx]) { + case RD_ENC_GB18030: return "GB18030"; + case RD_ENC_BIG5: return "Big5"; + case RD_ENC_UTF8: return "UTF-8"; + case RD_ENC_UTF16LE: return "UTF-16LE"; + case RD_ENC_UTF16BE: return "UTF-16BE"; + default: return "?"; + } +} +static int enc_to_cycle_idx(rd_enc_t e) +{ + for (int i = 0; i < RD_ENC_CYCLE_N; i++) if (g_enc_cycle[i] == e) return i; + return -1; +} + /* 前向声明(reader_reflow* 依赖 reader_refill/redraw,定义在后) */ static void reader_redraw(void); static int reader_refill_page(void); @@ -96,6 +131,7 @@ static void chapter_name(void) static void reader_reflow(void) { if (!g_book) return; + if (g_anim) { meow_anim_cancel(g_anim); g_anim = NULL; } /* 重排/雷字号时终止翻页动画 */ int seg = 0; int32_t ch = 0; rd_book_anchor(g_book, &seg, &ch); /* 记录当前锚,重排后回锚 */ rd_book_jump_to_anchor(g_book, seg, ch, g_fs, g_ls); @@ -110,7 +146,9 @@ static void reader_reflow_persist(void) int seg = 0; int32_t ch = 0; rd_book_anchor(g_book, &seg, &ch); rdprog p; - p.valid = 1; p.seg = seg; p.ch = ch; p.fs = g_fs; p.ls = g_ls; p.ts = (uint64_t)time(NULL); + p.valid = 1; p.seg = seg; p.ch = ch; p.fs = g_fs; p.ls = g_ls; + p.enc = (g_book && !rd_book_is_image(g_book)) ? (int)g_enc_cycle[g_enc_idx] : -1; + p.ts = (uint64_t)time(NULL); rdprog_save(rd_book_path(g_book), &p); } @@ -121,6 +159,67 @@ static void reader_draw_line_atlas(int x, int y, const char *utf8, int len, void meow_ui_text(x, y, utf8, (g_fs <= 20) ? 20 : 28, g_page_fg); } +/* 正文内容区几何:y0=top(顶栏下沿),y1=ph-my(下边距)。 */ +static int rect_y0(void) { return g_top; } +static int rect_y1(void) { return g_ph - g_my; } + +/* 把一页位图的行区间 [y0,y1) 以水平偏移 dx 拷到 back_buf(裁剪到屏幕)。动画/局部重绘共用。 */ +static void blit_rows(uint16_t *src, int dx, int y0, int y1) +{ + unsigned char *bb = meow_ui_back_buf(); + int pitch = meow_ui_back_buf_pitch(); + int bpp = meow_ui_back_buf_bpp(); + if (!bb || pitch <= 0 || bpp != 16) return; + int sx0 = dx < 0 ? -dx : 0; + int dx0 = dx > 0 ? dx : 0; + int w = g_pw - (dx < 0 ? -dx : dx); + if (w <= 0) return; + for (int y = y0; y < y1; y++) { + if (y < 0 || y >= g_ph) continue; + memcpy(bb + (size_t)y * pitch + (size_t)dx0 * 2, + src + (size_t)y * g_pw + sx0, (size_t)w * 2); + } +} +/* 子矩形直拷(同屏幕坐标):用于右缘刮擦条区恢复/局部重绘。 */ +static void blit_sub(uint16_t *src, int sx, int sy, int w, int h) +{ + unsigned char *bb = meow_ui_back_buf(); + int pitch = meow_ui_back_buf_pitch(); + int bpp = meow_ui_back_buf_bpp(); + if (!bb || pitch <= 0 || bpp != 16) return; + for (int y = sy; y < sy + h; y++) { + if (y < 0 || y >= g_ph) continue; + memcpy(bb + (size_t)y * pitch + (size_t)sx * 2, src + (size_t)y * g_pw + sx, (size_t)w * 2); + } +} + +/* R3 右缘刮擦指示条:轨道 + 拇指(当前/目标位置)+ 刮擦中预览框。render_cb 调用。 */ +static void reader_draw_scrub(const meow_theme *th) +{ + int np = g_book ? rd_book_pages(g_book) : 0; + if (np <= 0) return; + int y0 = rect_y0(), y1 = rect_y1(), h = y1 - y0; + if (h <= 0) return; + int page = g_scrub ? g_scrub_page : rd_book_current(g_book); + if (page < 0) page = 0; + if (page >= np) page = np - 1; + int x = g_pw - 10; + meow_ui_fill_rect(x - 1, y0, 3, h, th->dim); /* 轨道 */ + int denom = np > 1 ? (np - 1) : 1; + int ty = y0 + (int)((long long)h * (np > 1 ? page : 0) / denom); + meow_ui_fill_rect(x - 3, ty - 4, 7, 8, th->primary); /* 拇指 */ + if (g_scrub) { + char pv[24]; snprintf(pv, sizeof pv, "%d/%d", page + 1, np); + int bw = 58, bh = 22; + int px = x - bw - 6; + int py = ty - bh / 2; + if (py < y0) py = y0; + if (py + bh > y1) py = y1 - bh; + meow_ui_fill_rect(px, py, bw, bh, th->surfaceContainer); + meow_ui_text_center(px + bw / 2, py + (bh - 20) / 2, pv, 16, th->fg); + } +} + static void reader_canvas_render(meow_widget *w, const meow_theme *th, void *ctx) { (void)w; (void)ctx; @@ -129,36 +228,39 @@ static void reader_canvas_render(meow_widget *w, const meow_theme *th, void *ctx int page = rd_book_current(g_book); if ((g_has_font || rd_book_is_image(g_book)) && g_page_buf[RD_PAGE_SLOT_CUR]) { /* 翻页 blit:行拷贝当前页位图 → back_buf(局部脏区由 flush_region 上屏) */ - unsigned char *bb = meow_ui_back_buf(); - int pitch = meow_ui_back_buf_pitch(); - int bpp = meow_ui_back_buf_bpp(); - uint16_t *src = g_page_buf[RD_PAGE_SLOT_CUR]; - if (bb && pitch > 0 && bpp == 16) { - for (int y = 0; y < g_ph; y++) - memcpy(bb + (size_t)y * pitch, src + (size_t)y * g_pw, (size_t)g_pw * 2); - } + blit_rows(g_page_buf[RD_PAGE_SLOT_CUR], 0, 0, g_ph); } else { rd_book_render_text(g_book, page, reader_draw_line_atlas, NULL); } } - /* 顶栏:进度% + 章节名(避开系统状态栏) */ + /* 顶栏:章节名 + 页 n/N + 进度% + (TXT)编码 */ int sbh = meow_ui_statusbar_height(); - int pages = rd_book_pages(g_book); - int page = rd_book_current(g_book); - char pct[16]; + int pages = g_book ? rd_book_pages(g_book) : 0; + int page = g_book ? rd_book_current(g_book) : 0; + char right[40]; int p = pages > 0 ? (page * 100 / pages) : 0; - snprintf(pct, sizeof pct, "%d%%", p); + if (pages > 0) snprintf(right, sizeof right, "%d/%d %d%%", page + 1, pages, p); + else snprintf(right, sizeof right, "%d%%", p); meow_ui_fill_rect(0, sbh, g_pw, (g_top - 4 - sbh), g_page_bg); meow_ui_text(16, sbh + 4, g_chapter[0] ? g_chapter : "阅读", 20, th->fg); - meow_ui_text_right(g_pw - 16, sbh + 4, pct, 20, th->fg); + meow_ui_text_right(g_pw - 16, sbh + 4, right, 20, th->fg); meow_ui_draw_rect(0, g_top - 5, g_pw, 1, th->outline); + /* R3 刮擦指示条(非目录覆盖时显示;菜单为底部条,与右缘条并存即可) */ + if (g_book && !g_dir_open) reader_draw_scrub(th); + if (g_menu_open) { int y = g_ph - RD_MENU_H; meow_ui_fill_rect_alpha(0, y, g_pw, RD_MENU_H, 0x000000, 160); - /* 6 按钮:返回 | 目录 | 字− | 字+ | 距− | 距+ */ + /* 当前编码提示(菜单上方淡条;仅文本类书有意义) */ + if (g_book && !rd_book_is_image(g_book)) { + char en[64]; snprintf(en, sizeof en, "编码:%s", enc_cycle_name(g_enc_idx)); + meow_ui_fill_rect_alpha(0, y - 26, g_pw, 24, 0x000000, 130); + meow_ui_text_right(g_pw - 16, y - 22, en, 16, th->dim); + } + /* 7 按钮:返回 | 目录 | 字− | 字+ | 距− | 距+ | 编码 */ int bw = g_pw / RD_MENU_BTN; - const char *labels[RD_MENU_BTN] = { "返回", "目录", "字-", "字+", "距-", "距+" }; + const char *labels[RD_MENU_BTN] = { "返回", "目录", "字-", "字+", "距-", "距+", "编码" }; for (int i = 0; i < RD_MENU_BTN; i++) { int bx = i * bw; meow_ui_fill_rect(bx + 4, y + 12, bw - 8, RD_MENU_H - 24, th->surfaceContainer); @@ -203,6 +305,7 @@ static void reader_redraw(void) static void reader_free_page_bufs(void) { for (int i = 0; i < RD_PAGE_CACHE; i++) { free(g_page_buf[i]); g_page_buf[i] = NULL; } + if (g_anim_old) { free(g_anim_old); g_anim_old = NULL; } } /* 渲染当前页 + 前后各一页到 3 槽缓存(对齐设计「当前±前±后」;动画档为 无/滑动/淡入 R1 默认无)。 */ @@ -225,6 +328,104 @@ static int reader_refill_page(void) return 0; /* 兜底路径:render_text 直绘 */ } +/* ---------- R3 翻页动画(重载页位图,轻量水平滑动,仅内容区分帧) ---------- */ +static void reader_anim_update(meow_anim *a, float v, void *ctx) +{ + (void)a; (void)ctx; + if (!g_book || !g_anim_old || !g_page_buf[RD_PAGE_SLOT_CUR]) return; + int off = (int)(v * g_pw); + int y0 = rect_y0(), y1 = rect_y1(); + meow_ui_fill_rect(0, y0, g_pw, y1 - y0, g_page_bg); /* 内容区清底色 */ + int old_x, new_x; + if (g_anim_dir == 1) { old_x = -off; new_x = g_pw - off; } /* 下一页:旧左移/新右入 */ + else { old_x = off; new_x = off - g_pw; } /* 上一页:旧右移/新左入 */ + blit_rows(g_anim_old, old_x, y0, y1); + blit_rows(g_page_buf[RD_PAGE_SLOT_CUR], new_x, y0, y1); + meow_ui_flush_region(0, y0, g_pw, y1 - y0); +} +static void reader_anim_done(meow_anim *a, int canceled, void *ctx) +{ + (void)a; (void)ctx; + g_anim = NULL; + if (!canceled) { reader_redraw(); reader_reflow_persist(); } /* 顶栏 n/N+% 更新 + 进度保存 */ +} +static void reader_turn_animated(int dir) +{ + if (!g_book) return; + if (g_anim) { meow_anim_cancel(g_anim); g_anim = NULL; } /* 取消进行中动画,重开 */ + /* 无页位图(atlas 兜底):瞬切 */ + if (!(g_has_font || rd_book_is_image(g_book)) || !g_page_buf[RD_PAGE_SLOT_CUR]) { + rd_book_turn(g_book, dir); + reader_refill_page(); reader_redraw(); reader_reflow_persist(); + return; + } + if (!g_anim_old) g_anim_old = (uint16_t *)malloc((size_t)g_pw * g_ph * 2); + if (!g_anim_old) { rd_book_turn(g_book, dir); reader_refill_page(); reader_redraw(); return; } + memcpy(g_anim_old, g_page_buf[RD_PAGE_SLOT_CUR], (size_t)g_pw * g_ph * 2); + rd_book_turn(g_book, dir); + reader_refill_page(); + g_anim_dir = dir; + meow_anim_desc d = { 0.0f, 1.0f, 140, MEOW_EASE_OUT, reader_anim_update, reader_anim_done, NULL }; + g_anim = meow_anim_start(&d); + if (!g_anim) reader_redraw(); /* 池满等:瞬时落定 */ +} + +/* ---------- R3 段刮擦(右缘拖动快速定位 + 预览) ---------- */ +static int scrub_page_for_y(int y) +{ + int y0 = rect_y0(), y1 = rect_y1(), h = y1 - y0; + int np = g_book ? rd_book_pages(g_book) : 0; + if (h <= 0 || np <= 0) return g_book ? rd_book_current(g_book) : 0; + int cy = y - y0; if (cy < 0) cy = 0; if (cy > h) cy = h; + return (int)((long long)cy * (np - 1) / h); +} +static void reader_scrub_jump(int page) +{ + if (!g_book) return; + int np = rd_book_pages(g_book); + if (np <= 0) return; + rd_book_set_page(g_book, page); + reader_refill_page(); + reader_redraw(); /* 整页落定:正文 + 顶栏 + 刮擦条 */ + reader_reflow_persist(); +} +/* 拖动中局部重绘(不动正文页,仅顶栏 n/N + 右缘条/预览)。内容页保持(预览语义)。 */ +static void reader_scrub_preview(int page) +{ + g_scrub_page = page; + if (!g_book) return; + const meow_theme *th = meow_theme_get(); + int sbh = meow_ui_statusbar_height(); + int pages = rd_book_pages(g_book); + int y0 = rect_y0(), y1 = rect_y1(); + char right[40]; + int p = pages > 0 ? (g_scrub_page * 100 / pages) : 0; + if (pages > 0) snprintf(right, sizeof right, "%d/%d %d%%", g_scrub_page + 1, pages, p); + else snprintf(right, sizeof right, "%d%%", p); + /* 顶栏重画 */ + meow_ui_fill_rect(0, sbh, g_pw, (g_top - 4 - sbh), g_page_bg); + meow_ui_text(16, sbh + 4, g_chapter[0] ? g_chapter : "阅读", 20, th->fg); + meow_ui_text_right(g_pw - 16, sbh + 4, right, 20, th->fg); + meow_ui_draw_rect(0, g_top - 5, g_pw, 1, th->outline); + /* 右缘条区清残留(恢复页面内容)再画条 + 预览框;无页位图(字库兜底)则跳过恢复 */ + if (g_page_buf[RD_PAGE_SLOT_CUR]) blit_sub(g_page_buf[RD_PAGE_SLOT_CUR], g_pw - 120, y0, 120, y1 - y0); + reader_draw_scrub(th); + meow_ui_flush_region(0, sbh, g_pw, g_top - sbh); + meow_ui_flush_region(g_pw - 120, y0, 120, y1 - y0); +} +static void reader_scrub_begin(int x, int y) +{ + (void)x; + if (!g_book || g_menu_open || g_dir_open) return; + if (g_pw - x > RD_SCRUB_W) return; + if (y < rect_y0() || y >= rect_y1()) return; + int np = rd_book_pages(g_book); + if (np <= 1) return; + g_scrub = 1; + if (g_anim) { meow_anim_cancel(g_anim); g_anim = NULL; } + reader_scrub_preview(scrub_page_for_y(y)); +} + /* ---------- 打开 ---------- */ void reader_open(const char *path) { @@ -263,8 +464,27 @@ void reader_open(const char *path) for (int i = 0; i < RD_PAGE_CACHE; i++) g_page_buf[i] = (uint16_t *)malloc((size_t)g_pw * (size_t)g_ph * 2); if (!g_page_buf[RD_PAGE_SLOT_CUR]) { reader_free_page_bufs(); g_has_font = 0; } + else g_anim_old = (uint16_t *)malloc((size_t)g_pw * (size_t)g_ph * 2); /* 动画快照位图 */ } + /* 复位动画/刮擦状态(跨书) */ + if (g_anim) { meow_anim_cancel(g_anim); g_anim = NULL; } + g_scrub = 0; g_menu_open = 0; g_dir_open = 0; + + /* 编码:初始 = 打开探测结果;进度里的显式编码(Big5 用户选定)优先重开 */ + if (g_book && !rd_book_is_image(g_book)) { + int idx = enc_to_cycle_idx(rd_book_enc_get(g_book)); + g_enc_idx = idx >= 0 ? idx : 0; + if (hasprog && prog.enc >= 0) { + int pi = enc_to_cycle_idx((rd_enc_t)prog.enc); + if (pi >= 0) { + if (g_enc_cycle[pi] != rd_book_enc_get(g_book)) { + if (rd_book_set_enc(g_book, g_enc_cycle[pi]) == 0) { g_enc_idx = pi; chapter_name(); } + } else g_enc_idx = pi; + } + } + } else g_enc_idx = 0; + /* 续读:进度锚(段,字符偏移)→ 跳页 */ if (hasprog && g_book) { rd_book_jump_to_anchor(g_book, prog.seg, prog.ch, g_fs, g_ls); @@ -272,7 +492,6 @@ void reader_open(const char *path) reader_refill_page(); - g_menu_open = 0; g_dir_open = 0; if (g_reader_active) reader_redraw(); } @@ -294,10 +513,11 @@ static void reader_on_leave(meow_view *v, void *ctx) static void reader_on_destroy(meow_view *v, void *ctx) { (void)v; (void)ctx; + if (g_anim) { meow_anim_cancel(g_anim); g_anim = NULL; } if (g_book) rd_book_close(g_book); g_book = NULL; reader_free_page_bufs(); - g_menu_open = 0; g_dir_open = 0; g_reader_active = 0; + g_menu_open = 0; g_dir_open = 0; g_scrub = 0; g_reader_active = 0; } meow_view *reader_view_create(void) @@ -314,7 +534,13 @@ int reader_is_active(void) { return g_reader_active; } void reader_on_tap(int x, int y) { + (void)x; if (!g_book) { return; } + if (g_scrub) { /* 刮擦抬手 → 跳到目标页收尾 */ + g_scrub = 0; + reader_scrub_jump(g_scrub_page); + return; + } if (g_dir_open) { /* 目录覆盖层:点条目跳转,点空白关闭 */ int n = rd_book_toc_n(g_book); @@ -359,6 +585,18 @@ void reader_on_tap(int x, int y) case 3: if (g_fs < 40) { g_fs += 2; reader_reflow(); } break; case 4: if (g_ls > 100) { g_ls -= 5; reader_reflow(); } break; case 5: if (g_ls < 200) { g_ls += 5; reader_reflow(); } break; + case 6: /* 编码切换(TXT/Big5 繁体书) */ + if (g_book && !rd_book_is_image(g_book)) { + int ni = (g_enc_idx + 1) % RD_ENC_CYCLE_N; + if (rd_book_set_enc(g_book, g_enc_cycle[ni]) == 0) { + g_enc_idx = ni; + chapter_name(); + reader_refill_page(); + reader_redraw(); + reader_reflow_persist(); + } + } + break; } if (y >= y0) return; } @@ -367,20 +605,27 @@ void reader_on_tap(int x, int y) return; } /* 三栏:左 1/3 上一页、右 1/3 下一页、中央唤菜单 */ - if (x < g_pw / 3) { rd_book_turn(g_book, -1); reader_refill_page(); reader_redraw(); } - else if (x > g_pw * 2 / 3) { rd_book_turn(g_book, 1); reader_refill_page(); reader_redraw(); } - else { g_menu_open = 1; reader_redraw(); } + if (x < g_pw / 3) { reader_turn_animated(-1); } + else if (x > g_pw * 2 / 3) { reader_turn_animated(1); } + else { if (g_anim) { meow_anim_cancel(g_anim); g_anim = NULL; } g_menu_open = 1; reader_redraw(); } } void reader_on_swipe(int dir, int dx, int dy) { (void)dx; (void)dy; if (!g_book) return; + if (g_scrub) g_scrub = 0; /* 刮擦中被滑走 → 退出刮擦 */ if (g_menu_open) { g_menu_open = 0; reader_redraw(); return; } - if (dir == MEOW_SWIPE_LEFT) { rd_book_turn(g_book, 1); } - else if (dir == MEOW_SWIPE_RIGHT) { rd_book_turn(g_book, -1); } - else return; - reader_refill_page(); reader_redraw(); + if (dir == MEOW_SWIPE_LEFT) reader_turn_animated(1); + else if (dir == MEOW_SWIPE_RIGHT) reader_turn_animated(-1); +} + +/* ---- R3 段刮擦触控(app.c on_touch_down/on_touch_move 路由) ---- */ +void reader_on_touch_down(int x, int y) { reader_scrub_begin(x, y); } +void reader_on_touch_move(int x, int y) +{ + (void)x; + if (g_scrub && g_book) reader_scrub_preview(scrub_page_for_y(y)); } void reader_on_tick(uint64_t now) diff --git a/patch_v2/reader_app/build_reader.sh b/patch_v2/reader_app/build_reader.sh index 70ef41e..aee28ec 100755 --- a/patch_v2/reader_app/build_reader.sh +++ b/patch_v2/reader_app/build_reader.sh @@ -25,7 +25,10 @@ mkdir -p "$OBJ" # ---------- 1) vendor 拉取/对齐 ---------- FT_VER="2.14.3"; MINIZ_URL="https://codeload.github.com/richgel999/miniz/tar.gz/refs/heads/master" -LM_URL="https://codeload.github.com/bfabiszewski/libmobi/tar.gz/refs/heads/master" +# libmobi 版本锁定:见 NOTICE「Locked version (commit)」。LGPL-3.0 §4(d) 要求「已分发二进制 +# 对应的 libmobi 源码」与公开源码一致可复现,故不用 refs/heads/master(移动分支),改锁到精确 +# commit SHA(2024-10-29 的 9062742...,与上级代理 agent-332 复验/NOTICE 声明一致,已验证可拉取)。 +LM_URL="https://codeload.github.com/bfabiszewski/libmobi/tar.gz/906274205c11944b628da1c553b255acb1af7c55" mkdir -p "$VENDOR" fetch_freetype() { if [ -n "$FREETYPE_SRC" ] && [ -f "$FREETYPE_SRC/CMakeLists.txt" ]; then cp -r "$FREETYPE_SRC" vendor/freetype-src; @@ -45,6 +48,8 @@ fetch_miniz() { [ -f "$VENDOR/miniz/miniz.c" ] || { echo "miniz 不可用"; exit 1; } } fetch_libmobi() { + # 若 vendor/libmobi 来自旧版(LM_URL 改 pin 前抓的 master),请先删 vendor/libmobi 再重跑, + # 以确保取到下方锁定的 commit。此处仅在其缺失时拉取。 if [ ! -f "$VENDOR/libmobi/src/mobi.h" ]; then ( cd /tmp && curl -fsSL -o lm.tgz "$LM_URL" && mkdir -p lm_m && tar xzf lm.tgz -C lm_m --strip-components=1 \ && mkdir -p "$VENDOR/libmobi" && cp -r lm_m/src "$VENDOR/libmobi/src" && cp lm_m/COPYING "$VENDOR/libmobi/COPYING" ) diff --git a/patch_v2/reader_app/core/big5_table.h b/patch_v2/reader_app/core/big5_table.h new file mode 100644 index 0000000..938524c --- /dev/null +++ b/patch_v2/reader_app/core/big5_table.h @@ -0,0 +1,3169 @@ +/* big5_table.h — Big5 双字节 → Unicode BMP 码位表(生成物,勿手改; + * 由 tools/gen_big5_table.py 用 python3 `big5` codec 生成)。 + * 布局:big5_2byte[lead-0x81][trail_col];列号 idx(b)= b-0x40-(b>=0x80); + * 值 0 = 非法 Big5 双字节(如 trail 0x80..0xA0);解码器输出 U+FFFD。*/ +#ifndef RD_BIG5_TABLE_H +#define RD_BIG5_TABLE_H + +#include + +#define RD_B5_LEADS 126 +#define RD_B5_TRAILS 190 +#define RD_B5_LEAD0 0x81 +#define RD_B5_TRAIL0 0x40 + +/* big5_2byte[126][190]:rdbig5_lookup(lead, trail) 返回 BMP 码位或 0。 */ +static const uint16_t big5_2byte[126][190] = { + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x3000,0xFF0C,0x3001,0x3002,0xFF0E,0x2022,0xFF1B,0xFF1A, + 0xFF1F,0xFF01,0xFE30,0x2026,0x2025,0xFE50,0xFF64,0xFE52, + 0x00B7,0xFE54,0xFE55,0xFE56,0xFE57,0xFF5C,0x2013,0xFE31, + 0x2014,0xFE33,0x2574,0xFE34,0xFE4F,0xFF08,0xFF09,0xFE35, + 0xFE36,0xFF5B,0xFF5D,0xFE37,0xFE38,0x3014,0x3015,0xFE39, + 0xFE3A,0x3010,0x3011,0xFE3B,0xFE3C,0x300A,0x300B,0xFE3D, + 0xFE3E,0x3008,0x3009,0xFE3F,0xFE40,0x300C,0x300D,0xFE41, + 0xFE42,0x300E,0x300F,0xFE43,0xFE44,0xFE59,0xFE5A,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0xFE5B,0xFE5C,0xFE5D,0xFE5E,0x2018,0x2019,0x201C,0x201D, + 0x301D,0x301E,0x2035,0x2032,0xFF03,0xFF06,0xFF0A,0x203B, + 0x00A7,0x3003,0x25CB,0x25CF,0x25B3,0x25B2,0x25CE,0x2606, + 0x2605,0x25C7,0x25C6,0x25A1,0x25A0,0x25BD,0x25BC,0x32A3, + 0x2105,0x203E,0xFFE3,0xFF3F,0x02CD,0xFE49,0xFE4A,0xFE4D, + 0xFE4E,0xFE4B,0xFE4C,0xFE5F,0xFE60,0xFE61,0xFF0B,0xFF0D, + 0x00D7,0x00F7,0x00B1,0x221A,0xFF1C,0xFF1E,0xFF1D,0x2266, + 0x2267,0x2260,0x221E,0x2252,0x2261,0xFE62,0xFE63,0xFE64, + 0xFE65,0xFE66,0x223C,0x2229,0x222A,0x22A5,0x2220,0x221F, + 0x22BF,0x33D2,0x33D1,0x222B,0x222E,0x2235,0x2234,0x2640, + 0x2642,0x2641,0x2609,0x2191,0x2193,0x2190,0x2192,0x2196, + 0x2197,0x2199,0x2198,0x2225,0x2223,0xFF0F, + }, + {0xFF3C,0xFF0F,0xFF3C,0xFF04,0x00A5,0x3012,0x00A2,0x00A3, + 0xFF05,0xFF20,0x2103,0x2109,0xFE69,0xFE6A,0xFE6B,0x33D5, + 0x339C,0x339D,0x339E,0x33CE,0x33A1,0x338E,0x338F,0x33C4, + 0x00B0,0x5159,0x515B,0x515E,0x515D,0x5161,0x5163,0x55E7, + 0x74E9,0x7CCE,0x2581,0x2582,0x2583,0x2584,0x2585,0x2586, + 0x2587,0x2588,0x258F,0x258E,0x258D,0x258C,0x258B,0x258A, + 0x2589,0x253C,0x2534,0x252C,0x2524,0x251C,0x2594,0x2500, + 0x2502,0x2595,0x250C,0x2510,0x2514,0x2518,0x256D,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x256E,0x2570,0x256F,0x2550,0x255E,0x256A,0x2561,0x25E2, + 0x25E3,0x25E5,0x25E4,0x2571,0x2572,0x2573,0xFF10,0xFF11, + 0xFF12,0xFF13,0xFF14,0xFF15,0xFF16,0xFF17,0xFF18,0xFF19, + 0x2160,0x2161,0x2162,0x2163,0x2164,0x2165,0x2166,0x2167, + 0x2168,0x2169,0x3021,0x3022,0x3023,0x3024,0x3025,0x3026, + 0x3027,0x3028,0x3029,0x5341,0x5344,0x5345,0xFF21,0xFF22, + 0xFF23,0xFF24,0xFF25,0xFF26,0xFF27,0xFF28,0xFF29,0xFF2A, + 0xFF2B,0xFF2C,0xFF2D,0xFF2E,0xFF2F,0xFF30,0xFF31,0xFF32, + 0xFF33,0xFF34,0xFF35,0xFF36,0xFF37,0xFF38,0xFF39,0xFF3A, + 0xFF41,0xFF42,0xFF43,0xFF44,0xFF45,0xFF46,0xFF47,0xFF48, + 0xFF49,0xFF4A,0xFF4B,0xFF4C,0xFF4D,0xFF4E,0xFF4F,0xFF50, + 0xFF51,0xFF52,0xFF53,0xFF54,0xFF55,0xFF56, + }, + {0xFF57,0xFF58,0xFF59,0xFF5A,0x0391,0x0392,0x0393,0x0394, + 0x0395,0x0396,0x0397,0x0398,0x0399,0x039A,0x039B,0x039C, + 0x039D,0x039E,0x039F,0x03A0,0x03A1,0x03A3,0x03A4,0x03A5, + 0x03A6,0x03A7,0x03A8,0x03A9,0x03B1,0x03B2,0x03B3,0x03B4, + 0x03B5,0x03B6,0x03B7,0x03B8,0x03B9,0x03BA,0x03BB,0x03BC, + 0x03BD,0x03BE,0x03BF,0x03C0,0x03C1,0x03C3,0x03C4,0x03C5, + 0x03C6,0x03C7,0x03C8,0x03C9,0x3105,0x3106,0x3107,0x3108, + 0x3109,0x310A,0x310B,0x310C,0x310D,0x310E,0x310F,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x3110,0x3111,0x3112,0x3113,0x3114,0x3115,0x3116,0x3117, + 0x3118,0x3119,0x311A,0x311B,0x311C,0x311D,0x311E,0x311F, + 0x3120,0x3121,0x3122,0x3123,0x3124,0x3125,0x3126,0x3127, + 0x3128,0x3129,0x02D9,0x02C9,0x02CA,0x02C7,0x02CB,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x4E00,0x4E59,0x4E01,0x4E03,0x4E43,0x4E5D,0x4E86,0x4E8C, + 0x4EBA,0x513F,0x5165,0x516B,0x51E0,0x5200,0x5201,0x529B, + 0x5315,0x5341,0x535C,0x53C8,0x4E09,0x4E0B,0x4E08,0x4E0A, + 0x4E2B,0x4E38,0x51E1,0x4E45,0x4E48,0x4E5F,0x4E5E,0x4E8E, + 0x4EA1,0x5140,0x5203,0x52FA,0x5343,0x53C9,0x53E3,0x571F, + 0x58EB,0x5915,0x5927,0x5973,0x5B50,0x5B51,0x5B53,0x5BF8, + 0x5C0F,0x5C22,0x5C38,0x5C71,0x5DDD,0x5DE5,0x5DF1,0x5DF2, + 0x5DF3,0x5DFE,0x5E72,0x5EFE,0x5F0B,0x5F13,0x624D,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x4E11,0x4E10,0x4E0D,0x4E2D,0x4E30,0x4E39,0x4E4B,0x5C39, + 0x4E88,0x4E91,0x4E95,0x4E92,0x4E94,0x4EA2,0x4EC1,0x4EC0, + 0x4EC3,0x4EC6,0x4EC7,0x4ECD,0x4ECA,0x4ECB,0x4EC4,0x5143, + 0x5141,0x5167,0x516D,0x516E,0x516C,0x5197,0x51F6,0x5206, + 0x5207,0x5208,0x52FB,0x52FE,0x52FF,0x5316,0x5339,0x5348, + 0x5347,0x5345,0x535E,0x5384,0x53CB,0x53CA,0x53CD,0x58EC, + 0x5929,0x592B,0x592A,0x592D,0x5B54,0x5C11,0x5C24,0x5C3A, + 0x5C6F,0x5DF4,0x5E7B,0x5EFF,0x5F14,0x5F15,0x5FC3,0x6208, + 0x6236,0x624B,0x624E,0x652F,0x6587,0x6597,0x65A4,0x65B9, + 0x65E5,0x66F0,0x6708,0x6728,0x6B20,0x6B62,0x6B79,0x6BCB, + 0x6BD4,0x6BDB,0x6C0F,0x6C34,0x706B,0x722A,0x7236,0x723B, + 0x7247,0x7259,0x725B,0x72AC,0x738B,0x4E19, + }, + {0x4E16,0x4E15,0x4E14,0x4E18,0x4E3B,0x4E4D,0x4E4F,0x4E4E, + 0x4EE5,0x4ED8,0x4ED4,0x4ED5,0x4ED6,0x4ED7,0x4EE3,0x4EE4, + 0x4ED9,0x4EDE,0x5145,0x5144,0x5189,0x518A,0x51AC,0x51F9, + 0x51FA,0x51F8,0x520A,0x52A0,0x529F,0x5305,0x5306,0x5317, + 0x531D,0x4EDF,0x534A,0x5349,0x5361,0x5360,0x536F,0x536E, + 0x53BB,0x53EF,0x53E4,0x53F3,0x53EC,0x53EE,0x53E9,0x53E8, + 0x53FC,0x53F8,0x53F5,0x53EB,0x53E6,0x53EA,0x53F2,0x53F1, + 0x53F0,0x53E5,0x53ED,0x53FB,0x56DB,0x56DA,0x5916,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x592E,0x5931,0x5974,0x5976,0x5B55,0x5B83,0x5C3C,0x5DE8, + 0x5DE7,0x5DE6,0x5E02,0x5E03,0x5E73,0x5E7C,0x5F01,0x5F18, + 0x5F17,0x5FC5,0x620A,0x6253,0x6254,0x6252,0x6251,0x65A5, + 0x65E6,0x672E,0x672C,0x672A,0x672B,0x672D,0x6B63,0x6BCD, + 0x6C11,0x6C10,0x6C38,0x6C41,0x6C40,0x6C3E,0x72AF,0x7384, + 0x7389,0x74DC,0x74E6,0x7518,0x751F,0x7528,0x7529,0x7530, + 0x7531,0x7532,0x7533,0x758B,0x767D,0x76AE,0x76BF,0x76EE, + 0x77DB,0x77E2,0x77F3,0x793A,0x79BE,0x7A74,0x7ACB,0x4E1E, + 0x4E1F,0x4E52,0x4E53,0x4E69,0x4E99,0x4EA4,0x4EA6,0x4EA5, + 0x4EFF,0x4F09,0x4F19,0x4F0A,0x4F15,0x4F0D,0x4F10,0x4F11, + 0x4F0F,0x4EF2,0x4EF6,0x4EFB,0x4EF0,0x4EF3,0x4EFD,0x4F01, + 0x4F0B,0x5149,0x5147,0x5146,0x5148,0x5168, + }, + {0x5171,0x518D,0x51B0,0x5217,0x5211,0x5212,0x520E,0x5216, + 0x52A3,0x5308,0x5321,0x5320,0x5370,0x5371,0x5409,0x540F, + 0x540C,0x540A,0x5410,0x5401,0x540B,0x5404,0x5411,0x540D, + 0x5408,0x5403,0x540E,0x5406,0x5412,0x56E0,0x56DE,0x56DD, + 0x5733,0x5730,0x5728,0x572D,0x572C,0x572F,0x5729,0x5919, + 0x591A,0x5937,0x5938,0x5984,0x5978,0x5983,0x597D,0x5979, + 0x5982,0x5981,0x5B57,0x5B58,0x5B87,0x5B88,0x5B85,0x5B89, + 0x5BFA,0x5C16,0x5C79,0x5DDE,0x5E06,0x5E76,0x5E74,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x5F0F,0x5F1B,0x5FD9,0x5FD6,0x620E,0x620C,0x620D,0x6210, + 0x6263,0x625B,0x6258,0x6536,0x65E9,0x65E8,0x65EC,0x65ED, + 0x66F2,0x66F3,0x6709,0x673D,0x6734,0x6731,0x6735,0x6B21, + 0x6B64,0x6B7B,0x6C16,0x6C5D,0x6C57,0x6C59,0x6C5F,0x6C60, + 0x6C50,0x6C55,0x6C61,0x6C5B,0x6C4D,0x6C4E,0x7070,0x725F, + 0x725D,0x767E,0x7AF9,0x7C73,0x7CF8,0x7F36,0x7F8A,0x7FBD, + 0x8001,0x8003,0x800C,0x8012,0x8033,0x807F,0x8089,0x808B, + 0x808C,0x81E3,0x81EA,0x81F3,0x81FC,0x820C,0x821B,0x821F, + 0x826E,0x8272,0x827E,0x866B,0x8840,0x884C,0x8863,0x897F, + 0x9621,0x4E32,0x4EA8,0x4F4D,0x4F4F,0x4F47,0x4F57,0x4F5E, + 0x4F34,0x4F5B,0x4F55,0x4F30,0x4F50,0x4F51,0x4F3D,0x4F3A, + 0x4F38,0x4F43,0x4F54,0x4F3C,0x4F46,0x4F63, + }, + {0x4F5C,0x4F60,0x4F2F,0x4F4E,0x4F36,0x4F59,0x4F5D,0x4F48, + 0x4F5A,0x514C,0x514B,0x514D,0x5175,0x51B6,0x51B7,0x5225, + 0x5224,0x5229,0x522A,0x5228,0x52AB,0x52A9,0x52AA,0x52AC, + 0x5323,0x5373,0x5375,0x541D,0x542D,0x541E,0x543E,0x5426, + 0x544E,0x5427,0x5446,0x5443,0x5433,0x5448,0x5442,0x541B, + 0x5429,0x544A,0x5439,0x543B,0x5438,0x542E,0x5435,0x5436, + 0x5420,0x543C,0x5440,0x5431,0x542B,0x541F,0x542C,0x56EA, + 0x56F0,0x56E4,0x56EB,0x574A,0x5751,0x5740,0x574D,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x5747,0x574E,0x573E,0x5750,0x574F,0x573B,0x58EF,0x593E, + 0x599D,0x5992,0x59A8,0x599E,0x59A3,0x5999,0x5996,0x598D, + 0x59A4,0x5993,0x598A,0x59A5,0x5B5D,0x5B5C,0x5B5A,0x5B5B, + 0x5B8C,0x5B8B,0x5B8F,0x5C2C,0x5C40,0x5C41,0x5C3F,0x5C3E, + 0x5C90,0x5C91,0x5C94,0x5C8C,0x5DEB,0x5E0C,0x5E8F,0x5E87, + 0x5E8A,0x5EF7,0x5F04,0x5F1F,0x5F64,0x5F62,0x5F77,0x5F79, + 0x5FD8,0x5FCC,0x5FD7,0x5FCD,0x5FF1,0x5FEB,0x5FF8,0x5FEA, + 0x6212,0x6211,0x6284,0x6297,0x6296,0x6280,0x6276,0x6289, + 0x626D,0x628A,0x627C,0x627E,0x6279,0x6273,0x6292,0x626F, + 0x6298,0x626E,0x6295,0x6293,0x6291,0x6286,0x6539,0x653B, + 0x6538,0x65F1,0x66F4,0x675F,0x674E,0x674F,0x6750,0x6751, + 0x675C,0x6756,0x675E,0x6749,0x6746,0x6760, + }, + {0x6753,0x6757,0x6B65,0x6BCF,0x6C42,0x6C5E,0x6C99,0x6C81, + 0x6C88,0x6C89,0x6C85,0x6C9B,0x6C6A,0x6C7A,0x6C90,0x6C70, + 0x6C8C,0x6C68,0x6C96,0x6C92,0x6C7D,0x6C83,0x6C72,0x6C7E, + 0x6C74,0x6C86,0x6C76,0x6C8D,0x6C94,0x6C98,0x6C82,0x7076, + 0x707C,0x707D,0x7078,0x7262,0x7261,0x7260,0x72C4,0x72C2, + 0x7396,0x752C,0x752B,0x7537,0x7538,0x7682,0x76EF,0x77E3, + 0x79C1,0x79C0,0x79BF,0x7A76,0x7CFB,0x7F55,0x8096,0x8093, + 0x809D,0x8098,0x809B,0x809A,0x80B2,0x826F,0x8292,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x828B,0x828D,0x898B,0x89D2,0x8A00,0x8C37,0x8C46,0x8C55, + 0x8C9D,0x8D64,0x8D70,0x8DB3,0x8EAB,0x8ECA,0x8F9B,0x8FB0, + 0x8FC2,0x8FC6,0x8FC5,0x8FC4,0x5DE1,0x9091,0x90A2,0x90AA, + 0x90A6,0x90A3,0x9149,0x91C6,0x91CC,0x9632,0x962E,0x9631, + 0x962A,0x962C,0x4E26,0x4E56,0x4E73,0x4E8B,0x4E9B,0x4E9E, + 0x4EAB,0x4EAC,0x4F6F,0x4F9D,0x4F8D,0x4F73,0x4F7F,0x4F6C, + 0x4F9B,0x4F8B,0x4F86,0x4F83,0x4F70,0x4F75,0x4F88,0x4F69, + 0x4F7B,0x4F96,0x4F7E,0x4F8F,0x4F91,0x4F7A,0x5154,0x5152, + 0x5155,0x5169,0x5177,0x5176,0x5178,0x51BD,0x51FD,0x523B, + 0x5238,0x5237,0x523A,0x5230,0x522E,0x5236,0x5241,0x52BE, + 0x52BB,0x5352,0x5354,0x5353,0x5351,0x5366,0x5377,0x5378, + 0x5379,0x53D6,0x53D4,0x53D7,0x5473,0x5475, + }, + {0x5496,0x5478,0x5495,0x5480,0x547B,0x5477,0x5484,0x5492, + 0x5486,0x547C,0x5490,0x5471,0x5476,0x548C,0x549A,0x5462, + 0x5468,0x548B,0x547D,0x548E,0x56FA,0x5783,0x5777,0x576A, + 0x5769,0x5761,0x5766,0x5764,0x577C,0x591C,0x5949,0x5947, + 0x5948,0x5944,0x5954,0x59BE,0x59BB,0x59D4,0x59B9,0x59AE, + 0x59D1,0x59C6,0x59D0,0x59CD,0x59CB,0x59D3,0x59CA,0x59AF, + 0x59B3,0x59D2,0x59C5,0x5B5F,0x5B64,0x5B63,0x5B97,0x5B9A, + 0x5B98,0x5B9C,0x5B99,0x5B9B,0x5C1A,0x5C48,0x5C45,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x5C46,0x5CB7,0x5CA1,0x5CB8,0x5CA9,0x5CAB,0x5CB1,0x5CB3, + 0x5E18,0x5E1A,0x5E16,0x5E15,0x5E1B,0x5E11,0x5E78,0x5E9A, + 0x5E97,0x5E9C,0x5E95,0x5E96,0x5EF6,0x5F26,0x5F27,0x5F29, + 0x5F80,0x5F81,0x5F7F,0x5F7C,0x5FDD,0x5FE0,0x5FFD,0x5FF5, + 0x5FFF,0x600F,0x6014,0x602F,0x6035,0x6016,0x602A,0x6015, + 0x6021,0x6027,0x6029,0x602B,0x601B,0x6216,0x6215,0x623F, + 0x623E,0x6240,0x627F,0x62C9,0x62CC,0x62C4,0x62BF,0x62C2, + 0x62B9,0x62D2,0x62DB,0x62AB,0x62D3,0x62D4,0x62CB,0x62C8, + 0x62A8,0x62BD,0x62BC,0x62D0,0x62D9,0x62C7,0x62CD,0x62B5, + 0x62DA,0x62B1,0x62D8,0x62D6,0x62D7,0x62C6,0x62AC,0x62CE, + 0x653E,0x65A7,0x65BC,0x65FA,0x6614,0x6613,0x660C,0x6606, + 0x6602,0x660E,0x6600,0x660F,0x6615,0x660A, + }, + {0x6607,0x670D,0x670B,0x676D,0x678B,0x6795,0x6771,0x679C, + 0x6773,0x6777,0x6787,0x679D,0x6797,0x676F,0x6770,0x677F, + 0x6789,0x677E,0x6790,0x6775,0x679A,0x6793,0x677C,0x676A, + 0x6772,0x6B23,0x6B66,0x6B67,0x6B7F,0x6C13,0x6C1B,0x6CE3, + 0x6CE8,0x6CF3,0x6CB1,0x6CCC,0x6CE5,0x6CB3,0x6CBD,0x6CBE, + 0x6CBC,0x6CE2,0x6CAB,0x6CD5,0x6CD3,0x6CB8,0x6CC4,0x6CB9, + 0x6CC1,0x6CAE,0x6CD7,0x6CC5,0x6CF1,0x6CBF,0x6CBB,0x6CE1, + 0x6CDB,0x6CCA,0x6CAC,0x6CEF,0x6CDC,0x6CD6,0x6CE0,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x7095,0x708E,0x7092,0x708A,0x7099,0x722C,0x722D,0x7238, + 0x7248,0x7267,0x7269,0x72C0,0x72CE,0x72D9,0x72D7,0x72D0, + 0x73A9,0x73A8,0x739F,0x73AB,0x73A5,0x753D,0x759D,0x7599, + 0x759A,0x7684,0x76C2,0x76F2,0x76F4,0x77E5,0x77FD,0x793E, + 0x7940,0x7941,0x79C9,0x79C8,0x7A7A,0x7A79,0x7AFA,0x7CFE, + 0x7F54,0x7F8C,0x7F8B,0x8005,0x80BA,0x80A5,0x80A2,0x80B1, + 0x80A1,0x80AB,0x80A9,0x80B4,0x80AA,0x80AF,0x81E5,0x81FE, + 0x820D,0x82B3,0x829D,0x8299,0x82AD,0x82BD,0x829F,0x82B9, + 0x82B1,0x82AC,0x82A5,0x82AF,0x82B8,0x82A3,0x82B0,0x82BE, + 0x82B7,0x864E,0x8671,0x521D,0x8868,0x8ECB,0x8FCE,0x8FD4, + 0x8FD1,0x90B5,0x90B8,0x90B1,0x90B6,0x91C7,0x91D1,0x9577, + 0x9580,0x961C,0x9640,0x963F,0x963B,0x9644, + }, + {0x9642,0x96B9,0x96E8,0x9752,0x975E,0x4E9F,0x4EAD,0x4EAE, + 0x4FE1,0x4FB5,0x4FAF,0x4FBF,0x4FE0,0x4FD1,0x4FCF,0x4FDD, + 0x4FC3,0x4FB6,0x4FD8,0x4FDF,0x4FCA,0x4FD7,0x4FAE,0x4FD0, + 0x4FC4,0x4FC2,0x4FDA,0x4FCE,0x4FDE,0x4FB7,0x5157,0x5192, + 0x5191,0x51A0,0x524E,0x5243,0x524A,0x524D,0x524C,0x524B, + 0x5247,0x52C7,0x52C9,0x52C3,0x52C1,0x530D,0x5357,0x537B, + 0x539A,0x53DB,0x54AC,0x54C0,0x54A8,0x54CE,0x54C9,0x54B8, + 0x54A6,0x54B3,0x54C7,0x54C2,0x54BD,0x54AA,0x54C1,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x54C4,0x54C8,0x54AF,0x54AB,0x54B1,0x54BB,0x54A9,0x54A7, + 0x54BF,0x56FF,0x5782,0x578B,0x57A0,0x57A3,0x57A2,0x57CE, + 0x57AE,0x5793,0x5955,0x5951,0x594F,0x594E,0x5950,0x59DC, + 0x59D8,0x59FF,0x59E3,0x59E8,0x5A03,0x59E5,0x59EA,0x59DA, + 0x59E6,0x5A01,0x59FB,0x5B69,0x5BA3,0x5BA6,0x5BA4,0x5BA2, + 0x5BA5,0x5C01,0x5C4E,0x5C4F,0x5C4D,0x5C4B,0x5CD9,0x5CD2, + 0x5DF7,0x5E1D,0x5E25,0x5E1F,0x5E7D,0x5EA0,0x5EA6,0x5EFA, + 0x5F08,0x5F2D,0x5F65,0x5F88,0x5F85,0x5F8A,0x5F8B,0x5F87, + 0x5F8C,0x5F89,0x6012,0x601D,0x6020,0x6025,0x600E,0x6028, + 0x604D,0x6070,0x6068,0x6062,0x6046,0x6043,0x606C,0x606B, + 0x606A,0x6064,0x6241,0x62DC,0x6316,0x6309,0x62FC,0x62ED, + 0x6301,0x62EE,0x62FD,0x6307,0x62F1,0x62F7, + }, + {0x62EF,0x62EC,0x62FE,0x62F4,0x6311,0x6302,0x653F,0x6545, + 0x65AB,0x65BD,0x65E2,0x6625,0x662D,0x6620,0x6627,0x662F, + 0x661F,0x6628,0x6631,0x6624,0x66F7,0x67FF,0x67D3,0x67F1, + 0x67D4,0x67D0,0x67EC,0x67B6,0x67AF,0x67F5,0x67E9,0x67EF, + 0x67C4,0x67D1,0x67B4,0x67DA,0x67E5,0x67B8,0x67CF,0x67DE, + 0x67F3,0x67B0,0x67D9,0x67E2,0x67DD,0x67D2,0x6B6A,0x6B83, + 0x6B86,0x6BB5,0x6BD2,0x6BD7,0x6C1F,0x6CC9,0x6D0B,0x6D32, + 0x6D2A,0x6D41,0x6D25,0x6D0C,0x6D31,0x6D1E,0x6D17,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x6D3B,0x6D3D,0x6D3E,0x6D36,0x6D1B,0x6CF5,0x6D39,0x6D27, + 0x6D38,0x6D29,0x6D2E,0x6D35,0x6D0E,0x6D2B,0x70AB,0x70BA, + 0x70B3,0x70AC,0x70AF,0x70AD,0x70B8,0x70AE,0x70A4,0x7230, + 0x7272,0x726F,0x7274,0x72E9,0x72E0,0x72E1,0x73B7,0x73CA, + 0x73BB,0x73B2,0x73CD,0x73C0,0x73B3,0x751A,0x752D,0x754F, + 0x754C,0x754E,0x754B,0x75AB,0x75A4,0x75A5,0x75A2,0x75A3, + 0x7678,0x7686,0x7687,0x7688,0x76C8,0x76C6,0x76C3,0x76C5, + 0x7701,0x76F9,0x76F8,0x7709,0x770B,0x76FE,0x76FC,0x7707, + 0x77DC,0x7802,0x7814,0x780C,0x780D,0x7946,0x7949,0x7948, + 0x7947,0x79B9,0x79BA,0x79D1,0x79D2,0x79CB,0x7A7F,0x7A81, + 0x7AFF,0x7AFD,0x7C7D,0x7D02,0x7D05,0x7D00,0x7D09,0x7D07, + 0x7D04,0x7D06,0x7F38,0x7F8E,0x7FBF,0x8004, + }, + {0x8010,0x800D,0x8011,0x8036,0x80D6,0x80E5,0x80DA,0x80C3, + 0x80C4,0x80CC,0x80E1,0x80DB,0x80CE,0x80DE,0x80E4,0x80DD, + 0x81F4,0x8222,0x82E7,0x8303,0x8305,0x82E3,0x82DB,0x82E6, + 0x8304,0x82E5,0x8302,0x8309,0x82D2,0x82D7,0x82F1,0x8301, + 0x82DC,0x82D4,0x82D1,0x82DE,0x82D3,0x82DF,0x82EF,0x8306, + 0x8650,0x8679,0x867B,0x867A,0x884D,0x886B,0x8981,0x89D4, + 0x8A08,0x8A02,0x8A03,0x8C9E,0x8CA0,0x8D74,0x8D73,0x8DB4, + 0x8ECD,0x8ECC,0x8FF0,0x8FE6,0x8FE2,0x8FEA,0x8FE5,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x8FED,0x8FEB,0x8FE4,0x8FE8,0x90CA,0x90CE,0x90C1,0x90C3, + 0x914B,0x914A,0x91CD,0x9582,0x9650,0x964B,0x964C,0x964D, + 0x9762,0x9769,0x97CB,0x97ED,0x97F3,0x9801,0x98A8,0x98DB, + 0x98DF,0x9996,0x9999,0x4E58,0x4EB3,0x500C,0x500D,0x5023, + 0x4FEF,0x5026,0x5025,0x4FF8,0x5029,0x5016,0x5006,0x503C, + 0x501F,0x501A,0x5012,0x5011,0x4FFA,0x5000,0x5014,0x5028, + 0x4FF1,0x5021,0x500B,0x5019,0x5018,0x4FF3,0x4FEE,0x502D, + 0x502A,0x4FFE,0x502B,0x5009,0x517C,0x51A4,0x51A5,0x51A2, + 0x51CD,0x51CC,0x51C6,0x51CB,0x5256,0x525C,0x5254,0x525B, + 0x525D,0x532A,0x537F,0x539F,0x539D,0x53DF,0x54E8,0x5510, + 0x5501,0x5537,0x54FC,0x54E5,0x54F2,0x5506,0x54FA,0x5514, + 0x54E9,0x54ED,0x54E1,0x5509,0x54EE,0x54EA, + }, + {0x54E6,0x5527,0x5507,0x54FD,0x550F,0x5703,0x5704,0x57C2, + 0x57D4,0x57CB,0x57C3,0x5809,0x590F,0x5957,0x5958,0x595A, + 0x5A11,0x5A18,0x5A1C,0x5A1F,0x5A1B,0x5A13,0x59EC,0x5A20, + 0x5A23,0x5A29,0x5A25,0x5A0C,0x5A09,0x5B6B,0x5C58,0x5BB0, + 0x5BB3,0x5BB6,0x5BB4,0x5BAE,0x5BB5,0x5BB9,0x5BB8,0x5C04, + 0x5C51,0x5C55,0x5C50,0x5CED,0x5CFD,0x5CFB,0x5CEA,0x5CE8, + 0x5CF0,0x5CF6,0x5D01,0x5CF4,0x5DEE,0x5E2D,0x5E2B,0x5EAB, + 0x5EAD,0x5EA7,0x5F31,0x5F92,0x5F91,0x5F90,0x6059,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x6063,0x6065,0x6050,0x6055,0x606D,0x6069,0x606F,0x6084, + 0x609F,0x609A,0x608D,0x6094,0x608C,0x6085,0x6096,0x6247, + 0x62F3,0x6308,0x62FF,0x634E,0x633E,0x632F,0x6355,0x6342, + 0x6346,0x634F,0x6349,0x633A,0x6350,0x633D,0x632A,0x632B, + 0x6328,0x634D,0x634C,0x6548,0x6549,0x6599,0x65C1,0x65C5, + 0x6642,0x6649,0x664F,0x6643,0x6652,0x664C,0x6645,0x6641, + 0x66F8,0x6714,0x6715,0x6717,0x6821,0x6838,0x6848,0x6846, + 0x6853,0x6839,0x6842,0x6854,0x6829,0x68B3,0x6817,0x684C, + 0x6851,0x683D,0x67F4,0x6850,0x6840,0x683C,0x6843,0x682A, + 0x6845,0x6813,0x6818,0x6841,0x6B8A,0x6B89,0x6BB7,0x6C23, + 0x6C27,0x6C28,0x6C26,0x6C24,0x6CF0,0x6D6A,0x6D95,0x6D88, + 0x6D87,0x6D66,0x6D78,0x6D77,0x6D59,0x6D93, + }, + {0x6D6C,0x6D89,0x6D6E,0x6D5A,0x6D74,0x6D69,0x6D8C,0x6D8A, + 0x6D79,0x6D85,0x6D65,0x6D94,0x70CA,0x70D8,0x70E4,0x70D9, + 0x70C8,0x70CF,0x7239,0x7279,0x72FC,0x72F9,0x72FD,0x72F8, + 0x72F7,0x7386,0x73ED,0x7409,0x73EE,0x73E0,0x73EA,0x73DE, + 0x7554,0x755D,0x755C,0x755A,0x7559,0x75BE,0x75C5,0x75C7, + 0x75B2,0x75B3,0x75BD,0x75BC,0x75B9,0x75C2,0x75B8,0x768B, + 0x76B0,0x76CA,0x76CD,0x76CE,0x7729,0x771F,0x7720,0x7728, + 0x77E9,0x7830,0x7827,0x7838,0x781D,0x7834,0x7837,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x7825,0x782D,0x7820,0x781F,0x7832,0x7955,0x7950,0x7960, + 0x795F,0x7956,0x795E,0x795D,0x7957,0x795A,0x79E4,0x79E3, + 0x79E7,0x79DF,0x79E6,0x79E9,0x79D8,0x7A84,0x7A88,0x7AD9, + 0x7B06,0x7B11,0x7C89,0x7D21,0x7D17,0x7D0B,0x7D0A,0x7D20, + 0x7D22,0x7D14,0x7D10,0x7D15,0x7D1A,0x7D1C,0x7D0D,0x7D19, + 0x7D1B,0x7F3A,0x7F5F,0x7F94,0x7FC5,0x7FC1,0x8006,0x8018, + 0x8015,0x8019,0x8017,0x803D,0x803F,0x80F1,0x8102,0x80F0, + 0x8105,0x80ED,0x80F4,0x8106,0x80F8,0x80F3,0x8108,0x80FD, + 0x810A,0x80FC,0x80EF,0x81ED,0x81EC,0x8200,0x8210,0x822A, + 0x822B,0x8228,0x822C,0x82BB,0x832B,0x8352,0x8354,0x834A, + 0x8338,0x8350,0x8349,0x8335,0x8334,0x834F,0x8332,0x8339, + 0x8336,0x8317,0x8340,0x8331,0x8328,0x8343, + }, + {0x8654,0x868A,0x86AA,0x8693,0x86A4,0x86A9,0x868C,0x86A3, + 0x869C,0x8870,0x8877,0x8881,0x8882,0x887D,0x8879,0x8A18, + 0x8A10,0x8A0E,0x8A0C,0x8A15,0x8A0A,0x8A17,0x8A13,0x8A16, + 0x8A0F,0x8A11,0x8C48,0x8C7A,0x8C79,0x8CA1,0x8CA2,0x8D77, + 0x8EAC,0x8ED2,0x8ED4,0x8ECF,0x8FB1,0x9001,0x9006,0x8FF7, + 0x9000,0x8FFA,0x8FF4,0x9003,0x8FFD,0x9005,0x8FF8,0x9095, + 0x90E1,0x90DD,0x90E2,0x9152,0x914D,0x914C,0x91D8,0x91DD, + 0x91D7,0x91DC,0x91D9,0x9583,0x9662,0x9663,0x9661,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x965B,0x965D,0x9664,0x9658,0x965E,0x96BB,0x98E2,0x99AC, + 0x9AA8,0x9AD8,0x9B25,0x9B32,0x9B3C,0x4E7E,0x507A,0x507D, + 0x505C,0x5047,0x5043,0x504C,0x505A,0x5049,0x5065,0x5076, + 0x504E,0x5055,0x5075,0x5074,0x5077,0x504F,0x500F,0x506F, + 0x506D,0x515C,0x5195,0x51F0,0x526A,0x526F,0x52D2,0x52D9, + 0x52D8,0x52D5,0x5310,0x530F,0x5319,0x533F,0x5340,0x533E, + 0x53C3,0x66FC,0x5546,0x556A,0x5566,0x5544,0x555E,0x5561, + 0x5543,0x554A,0x5531,0x5556,0x554F,0x5555,0x552F,0x5564, + 0x5538,0x552E,0x555C,0x552C,0x5563,0x5533,0x5541,0x5557, + 0x5708,0x570B,0x5709,0x57DF,0x5805,0x580A,0x5806,0x57E0, + 0x57E4,0x57FA,0x5802,0x5835,0x57F7,0x57F9,0x5920,0x5962, + 0x5A36,0x5A41,0x5A49,0x5A66,0x5A6A,0x5A40, + }, + {0x5A3C,0x5A62,0x5A5A,0x5A46,0x5A4A,0x5B70,0x5BC7,0x5BC5, + 0x5BC4,0x5BC2,0x5BBF,0x5BC6,0x5C09,0x5C08,0x5C07,0x5C60, + 0x5C5C,0x5C5D,0x5D07,0x5D06,0x5D0E,0x5D1B,0x5D16,0x5D22, + 0x5D11,0x5D29,0x5D14,0x5D19,0x5D24,0x5D27,0x5D17,0x5DE2, + 0x5E38,0x5E36,0x5E33,0x5E37,0x5EB7,0x5EB8,0x5EB6,0x5EB5, + 0x5EBE,0x5F35,0x5F37,0x5F57,0x5F6C,0x5F69,0x5F6B,0x5F97, + 0x5F99,0x5F9E,0x5F98,0x5FA1,0x5FA0,0x5F9C,0x607F,0x60A3, + 0x6089,0x60A0,0x60A8,0x60CB,0x60B4,0x60E6,0x60BD,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x60C5,0x60BB,0x60B5,0x60DC,0x60BC,0x60D8,0x60D5,0x60C6, + 0x60DF,0x60B8,0x60DA,0x60C7,0x621A,0x621B,0x6248,0x63A0, + 0x63A7,0x6372,0x6396,0x63A2,0x63A5,0x6377,0x6367,0x6398, + 0x63AA,0x6371,0x63A9,0x6389,0x6383,0x639B,0x636B,0x63A8, + 0x6384,0x6388,0x6399,0x63A1,0x63AC,0x6392,0x638F,0x6380, + 0x637B,0x6369,0x6368,0x637A,0x655D,0x6556,0x6551,0x6559, + 0x6557,0x555F,0x654F,0x6558,0x6555,0x6554,0x659C,0x659B, + 0x65AC,0x65CF,0x65CB,0x65CC,0x65CE,0x665D,0x665A,0x6664, + 0x6668,0x6666,0x665E,0x66F9,0x52D7,0x671B,0x6881,0x68AF, + 0x68A2,0x6893,0x68B5,0x687F,0x6876,0x68B1,0x68A7,0x6897, + 0x68B0,0x6883,0x68C4,0x68AD,0x6886,0x6885,0x6894,0x689D, + 0x68A8,0x689F,0x68A1,0x6882,0x6B32,0x6BBA, + }, + {0x6BEB,0x6BEC,0x6C2B,0x6D8E,0x6DBC,0x6DF3,0x6DD9,0x6DB2, + 0x6DE1,0x6DCC,0x6DE4,0x6DFB,0x6DFA,0x6E05,0x6DC7,0x6DCB, + 0x6DAF,0x6DD1,0x6DAE,0x6DDE,0x6DF9,0x6DB8,0x6DF7,0x6DF5, + 0x6DC5,0x6DD2,0x6E1A,0x6DB5,0x6DDA,0x6DEB,0x6DD8,0x6DEA, + 0x6DF1,0x6DEE,0x6DE8,0x6DC6,0x6DC4,0x6DAA,0x6DEC,0x6DBF, + 0x6DE6,0x70F9,0x7109,0x710A,0x70FD,0x70EF,0x723D,0x727D, + 0x7281,0x731C,0x731B,0x7316,0x7313,0x7319,0x7387,0x7405, + 0x740A,0x7403,0x7406,0x73FE,0x740D,0x74E0,0x74F6,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x74F7,0x751C,0x7522,0x7565,0x7566,0x7562,0x7570,0x758F, + 0x75D4,0x75D5,0x75B5,0x75CA,0x75CD,0x768E,0x76D4,0x76D2, + 0x76DB,0x7737,0x773E,0x773C,0x7736,0x7738,0x773A,0x786B, + 0x7843,0x784E,0x7965,0x7968,0x796D,0x79FB,0x7A92,0x7A95, + 0x7B20,0x7B28,0x7B1B,0x7B2C,0x7B26,0x7B19,0x7B1E,0x7B2E, + 0x7C92,0x7C97,0x7C95,0x7D46,0x7D43,0x7D71,0x7D2E,0x7D39, + 0x7D3C,0x7D40,0x7D30,0x7D33,0x7D44,0x7D2F,0x7D42,0x7D32, + 0x7D31,0x7F3D,0x7F9E,0x7F9A,0x7FCC,0x7FCE,0x7FD2,0x801C, + 0x804A,0x8046,0x812F,0x8116,0x8123,0x812B,0x8129,0x8130, + 0x8124,0x8202,0x8235,0x8237,0x8236,0x8239,0x838E,0x839E, + 0x8398,0x8378,0x83A2,0x8396,0x83BD,0x83AB,0x8392,0x838A, + 0x8393,0x8389,0x83A0,0x8377,0x837B,0x837C, + }, + {0x8386,0x83A7,0x8655,0x5F6A,0x86C7,0x86C0,0x86B6,0x86C4, + 0x86B5,0x86C6,0x86CB,0x86B1,0x86AF,0x86C9,0x8853,0x889E, + 0x8888,0x88AB,0x8892,0x8896,0x888D,0x888B,0x8993,0x898F, + 0x8A2A,0x8A1D,0x8A23,0x8A25,0x8A31,0x8A2D,0x8A1F,0x8A1B, + 0x8A22,0x8C49,0x8C5A,0x8CA9,0x8CAC,0x8CAB,0x8CA8,0x8CAA, + 0x8CA7,0x8D67,0x8D66,0x8DBE,0x8DBA,0x8EDB,0x8EDF,0x9019, + 0x900D,0x901A,0x9017,0x9023,0x901F,0x901D,0x9010,0x9015, + 0x901E,0x9020,0x900F,0x9022,0x9016,0x901B,0x9014,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x90E8,0x90ED,0x90FD,0x9157,0x91CE,0x91F5,0x91E6,0x91E3, + 0x91E7,0x91ED,0x91E9,0x9589,0x966A,0x9675,0x9673,0x9678, + 0x9670,0x9674,0x9676,0x9677,0x966C,0x96C0,0x96EA,0x96E9, + 0x7AE0,0x7ADF,0x9802,0x9803,0x9B5A,0x9CE5,0x9E75,0x9E7F, + 0x9EA5,0x9EBB,0x50A2,0x508D,0x5085,0x5099,0x5091,0x5080, + 0x5096,0x5098,0x509A,0x6700,0x51F1,0x5272,0x5274,0x5275, + 0x5269,0x52DE,0x52DD,0x52DB,0x535A,0x53A5,0x557B,0x5580, + 0x55A7,0x557C,0x558A,0x559D,0x5598,0x5582,0x559C,0x55AA, + 0x5594,0x5587,0x558B,0x5583,0x55B3,0x55AE,0x559F,0x553E, + 0x55B2,0x559A,0x55BB,0x55AC,0x55B1,0x557E,0x5589,0x55AB, + 0x5599,0x570D,0x582F,0x582A,0x5834,0x5824,0x5830,0x5831, + 0x5821,0x581D,0x5820,0x58F9,0x58FA,0x5960, + }, + {0x5A77,0x5A9A,0x5A7F,0x5A92,0x5A9B,0x5AA7,0x5B73,0x5B71, + 0x5BD2,0x5BCC,0x5BD3,0x5BD0,0x5C0A,0x5C0B,0x5C31,0x5D4C, + 0x5D50,0x5D34,0x5D47,0x5DFD,0x5E45,0x5E3D,0x5E40,0x5E43, + 0x5E7E,0x5ECA,0x5EC1,0x5EC2,0x5EC4,0x5F3C,0x5F6D,0x5FA9, + 0x5FAA,0x5FA8,0x60D1,0x60E1,0x60B2,0x60B6,0x60E0,0x611C, + 0x6123,0x60FA,0x6115,0x60F0,0x60FB,0x60F4,0x6168,0x60F1, + 0x610E,0x60F6,0x6109,0x6100,0x6112,0x621F,0x6249,0x63A3, + 0x638C,0x63CF,0x63C0,0x63E9,0x63C9,0x63C6,0x63CD,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x63D2,0x63E3,0x63D0,0x63E1,0x63D6,0x63ED,0x63EE,0x6376, + 0x63F4,0x63EA,0x63DB,0x6452,0x63DA,0x63F9,0x655E,0x6566, + 0x6562,0x6563,0x6591,0x6590,0x65AF,0x666E,0x6670,0x6674, + 0x6676,0x666F,0x6691,0x667A,0x667E,0x6677,0x66FE,0x66FF, + 0x671F,0x671D,0x68FA,0x68D5,0x68E0,0x68D8,0x68D7,0x6905, + 0x68DF,0x68F5,0x68EE,0x68E7,0x68F9,0x68D2,0x68F2,0x68E3, + 0x68CB,0x68CD,0x690D,0x6912,0x690E,0x68C9,0x68DA,0x696E, + 0x68FB,0x6B3E,0x6B3A,0x6B3D,0x6B98,0x6B96,0x6BBC,0x6BEF, + 0x6C2E,0x6C2F,0x6C2C,0x6E2F,0x6E38,0x6E54,0x6E21,0x6E32, + 0x6E67,0x6E4A,0x6E20,0x6E25,0x6E23,0x6E1B,0x6E5B,0x6E58, + 0x6E24,0x6E56,0x6E6E,0x6E2D,0x6E26,0x6E6F,0x6E34,0x6E4D, + 0x6E3A,0x6E2C,0x6E43,0x6E1D,0x6E3E,0x6ECB, + }, + {0x6E89,0x6E19,0x6E4E,0x6E63,0x6E44,0x6E72,0x6E69,0x6E5F, + 0x7119,0x711A,0x7126,0x7130,0x7121,0x7136,0x716E,0x711C, + 0x724C,0x7284,0x7280,0x7336,0x7325,0x7334,0x7329,0x743A, + 0x742A,0x7433,0x7422,0x7425,0x7435,0x7436,0x7434,0x742F, + 0x741B,0x7426,0x7428,0x7525,0x7526,0x756B,0x756A,0x75E2, + 0x75DB,0x75E3,0x75D9,0x75D8,0x75DE,0x75E0,0x767B,0x767C, + 0x7696,0x7693,0x76B4,0x76DC,0x774F,0x77ED,0x785D,0x786C, + 0x786F,0x7A0D,0x7A08,0x7A0B,0x7A05,0x7A00,0x7A98,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x7A97,0x7A96,0x7AE5,0x7AE3,0x7B49,0x7B56,0x7B46,0x7B50, + 0x7B52,0x7B54,0x7B4D,0x7B4B,0x7B4F,0x7B51,0x7C9F,0x7CA5, + 0x7D5E,0x7D50,0x7D68,0x7D55,0x7D2B,0x7D6E,0x7D72,0x7D61, + 0x7D66,0x7D62,0x7D70,0x7D73,0x5584,0x7FD4,0x7FD5,0x800B, + 0x8052,0x8085,0x8155,0x8154,0x814B,0x8151,0x814E,0x8139, + 0x8146,0x813E,0x814C,0x8153,0x8174,0x8212,0x821C,0x83E9, + 0x8403,0x83F8,0x840D,0x83E0,0x83C5,0x840B,0x83C1,0x83EF, + 0x83F1,0x83F4,0x8457,0x840A,0x83F0,0x840C,0x83CC,0x83FD, + 0x83F2,0x83CA,0x8438,0x840E,0x8404,0x83DC,0x8407,0x83D4, + 0x83DF,0x865B,0x86DF,0x86D9,0x86ED,0x86D4,0x86DB,0x86E4, + 0x86D0,0x86DE,0x8857,0x88C1,0x88C2,0x88B1,0x8983,0x8996, + 0x8A3B,0x8A60,0x8A55,0x8A5E,0x8A3C,0x8A41, + }, + {0x8A54,0x8A5B,0x8A50,0x8A46,0x8A34,0x8A3A,0x8A36,0x8A56, + 0x8C61,0x8C82,0x8CAF,0x8CBC,0x8CB3,0x8CBD,0x8CC1,0x8CBB, + 0x8CC0,0x8CB4,0x8CB7,0x8CB6,0x8CBF,0x8CB8,0x8D8A,0x8D85, + 0x8D81,0x8DCE,0x8DDD,0x8DCB,0x8DDA,0x8DD1,0x8DCC,0x8DDB, + 0x8DC6,0x8EFB,0x8EF8,0x8EFC,0x8F9C,0x902E,0x9035,0x9031, + 0x9038,0x9032,0x9036,0x9102,0x90F5,0x9109,0x90FE,0x9163, + 0x9165,0x91CF,0x9214,0x9215,0x9223,0x9209,0x921E,0x920D, + 0x9210,0x9207,0x9211,0x9594,0x958F,0x958B,0x9591,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x9593,0x9592,0x958E,0x968A,0x968E,0x968B,0x967D,0x9685, + 0x9686,0x968D,0x9672,0x9684,0x96C1,0x96C5,0x96C4,0x96C6, + 0x96C7,0x96EF,0x96F2,0x97CC,0x9805,0x9806,0x9808,0x98E7, + 0x98EA,0x98EF,0x98E9,0x98F2,0x98ED,0x99AE,0x99AD,0x9EC3, + 0x9ECD,0x9ED1,0x4E82,0x50AD,0x50B5,0x50B2,0x50B3,0x50C5, + 0x50BE,0x50AC,0x50B7,0x50BB,0x50AF,0x50C7,0x527F,0x5277, + 0x527D,0x52DF,0x52E6,0x52E4,0x52E2,0x52E3,0x532F,0x55DF, + 0x55E8,0x55D3,0x55E6,0x55CE,0x55DC,0x55C7,0x55D1,0x55E3, + 0x55E4,0x55EF,0x55DA,0x55E1,0x55C5,0x55C6,0x55E5,0x55C9, + 0x5712,0x5713,0x585E,0x5851,0x5858,0x5857,0x585A,0x5854, + 0x586B,0x584C,0x586D,0x584A,0x5862,0x5852,0x584B,0x5967, + 0x5AC1,0x5AC9,0x5ACC,0x5ABE,0x5ABD,0x5ABC, + }, + {0x5AB3,0x5AC2,0x5AB2,0x5D69,0x5D6F,0x5E4C,0x5E79,0x5EC9, + 0x5EC8,0x5F12,0x5F59,0x5FAC,0x5FAE,0x611A,0x610F,0x6148, + 0x611F,0x60F3,0x611B,0x60F9,0x6101,0x6108,0x614E,0x614C, + 0x6144,0x614D,0x613E,0x6134,0x6127,0x610D,0x6106,0x6137, + 0x6221,0x6222,0x6413,0x643E,0x641E,0x642A,0x642D,0x643D, + 0x642C,0x640F,0x641C,0x6414,0x640D,0x6436,0x6416,0x6417, + 0x6406,0x656C,0x659F,0x65B0,0x6697,0x6689,0x6687,0x6688, + 0x6696,0x6684,0x6698,0x668D,0x6703,0x6994,0x696D,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x695A,0x6977,0x6960,0x6954,0x6975,0x6930,0x6982,0x694A, + 0x6968,0x696B,0x695E,0x6953,0x6979,0x6986,0x695D,0x6963, + 0x695B,0x6B47,0x6B72,0x6BC0,0x6BBF,0x6BD3,0x6BFD,0x6EA2, + 0x6EAF,0x6ED3,0x6EB6,0x6EC2,0x6E90,0x6E9D,0x6EC7,0x6EC5, + 0x6EA5,0x6E98,0x6EBC,0x6EBA,0x6EAB,0x6ED1,0x6E96,0x6E9C, + 0x6EC4,0x6ED4,0x6EAA,0x6EA7,0x6EB4,0x714E,0x7159,0x7169, + 0x7164,0x7149,0x7167,0x715C,0x716C,0x7166,0x714C,0x7165, + 0x715E,0x7146,0x7168,0x7156,0x723A,0x7252,0x7337,0x7345, + 0x733F,0x733E,0x746F,0x745A,0x7455,0x745F,0x745E,0x7441, + 0x743F,0x7459,0x745B,0x745C,0x7576,0x7578,0x7600,0x75F0, + 0x7601,0x75F2,0x75F1,0x75FA,0x75FF,0x75F4,0x75F3,0x76DE, + 0x76DF,0x775B,0x776B,0x7766,0x775E,0x7763, + }, + {0x7779,0x776A,0x776C,0x775C,0x7765,0x7768,0x7762,0x77EE, + 0x788E,0x78B0,0x7897,0x7898,0x788C,0x7889,0x787C,0x7891, + 0x7893,0x787F,0x797A,0x797F,0x7981,0x842C,0x79BD,0x7A1C, + 0x7A1A,0x7A20,0x7A14,0x7A1F,0x7A1E,0x7A9F,0x7AA0,0x7B77, + 0x7BC0,0x7B60,0x7B6E,0x7B67,0x7CB1,0x7CB3,0x7CB5,0x7D93, + 0x7D79,0x7D91,0x7D81,0x7D8F,0x7D5B,0x7F6E,0x7F69,0x7F6A, + 0x7F72,0x7FA9,0x7FA8,0x7FA4,0x8056,0x8058,0x8086,0x8084, + 0x8171,0x8170,0x8178,0x8165,0x816E,0x8173,0x816B,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x8179,0x817A,0x8166,0x8205,0x8247,0x8482,0x8477,0x843D, + 0x8431,0x8475,0x8466,0x846B,0x8449,0x846C,0x845B,0x843C, + 0x8435,0x8461,0x8463,0x8469,0x846D,0x8446,0x865E,0x865C, + 0x865F,0x86F9,0x8713,0x8708,0x8707,0x8700,0x86FE,0x86FB, + 0x8702,0x8703,0x8706,0x870A,0x8859,0x88DF,0x88D4,0x88D9, + 0x88DC,0x88D8,0x88DD,0x88E1,0x88CA,0x88D5,0x88D2,0x899C, + 0x89E3,0x8A6B,0x8A72,0x8A73,0x8A66,0x8A69,0x8A70,0x8A87, + 0x8A7C,0x8A63,0x8AA0,0x8A71,0x8A85,0x8A6D,0x8A62,0x8A6E, + 0x8A6C,0x8A79,0x8A7B,0x8A3E,0x8A68,0x8C62,0x8C8A,0x8C89, + 0x8CCA,0x8CC7,0x8CC8,0x8CC4,0x8CB2,0x8CC3,0x8CC2,0x8CC5, + 0x8DE1,0x8DDF,0x8DE8,0x8DEF,0x8DF3,0x8DFA,0x8DEA,0x8DE4, + 0x8DE6,0x8EB2,0x8F03,0x8F09,0x8EFE,0x8F0A, + }, + {0x8F9F,0x8FB2,0x904B,0x904A,0x9053,0x9042,0x9054,0x903C, + 0x9055,0x9050,0x9047,0x904F,0x904E,0x904D,0x9051,0x903E, + 0x9041,0x9112,0x9117,0x916C,0x916A,0x9169,0x91C9,0x9237, + 0x9257,0x9238,0x923D,0x9240,0x923E,0x925B,0x924B,0x9264, + 0x9251,0x9234,0x9249,0x924D,0x9245,0x9239,0x923F,0x925A, + 0x9598,0x9698,0x9694,0x9695,0x96CD,0x96CB,0x96C9,0x96CA, + 0x96F7,0x96FB,0x96F9,0x96F6,0x9756,0x9774,0x9776,0x9810, + 0x9811,0x9813,0x980A,0x9812,0x980C,0x98FC,0x98F4,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x98FD,0x98FE,0x99B3,0x99B1,0x99B4,0x9AE1,0x9CE9,0x9E82, + 0x9F0E,0x9F13,0x9F20,0x50E7,0x50EE,0x50E5,0x50D6,0x50ED, + 0x50DA,0x50D5,0x50CF,0x50D1,0x50F1,0x50CE,0x50E9,0x5162, + 0x51F3,0x5283,0x5282,0x5331,0x53AD,0x55FE,0x5600,0x561B, + 0x5617,0x55FD,0x5614,0x5606,0x5609,0x560D,0x560E,0x55F7, + 0x5616,0x561F,0x5608,0x5610,0x55F6,0x5718,0x5716,0x5875, + 0x587E,0x5883,0x5893,0x588A,0x5879,0x5885,0x587D,0x58FD, + 0x5925,0x5922,0x5924,0x596A,0x5969,0x5AE1,0x5AE6,0x5AE9, + 0x5AD7,0x5AD6,0x5AD8,0x5AE3,0x5B75,0x5BDE,0x5BE7,0x5BE1, + 0x5BE5,0x5BE6,0x5BE8,0x5BE2,0x5BE4,0x5BDF,0x5C0D,0x5C62, + 0x5D84,0x5D87,0x5E5B,0x5E63,0x5E55,0x5E57,0x5E54,0x5ED3, + 0x5ED6,0x5F0A,0x5F46,0x5F70,0x5FB9,0x6147, + }, + {0x613F,0x614B,0x6177,0x6162,0x6163,0x615F,0x615A,0x6158, + 0x6175,0x622A,0x6487,0x6458,0x6454,0x64A4,0x6478,0x645F, + 0x647A,0x6451,0x6467,0x6434,0x646D,0x647B,0x6572,0x65A1, + 0x65D7,0x65D6,0x66A2,0x66A8,0x669D,0x699C,0x69A8,0x6995, + 0x69C1,0x69AE,0x69D3,0x69CB,0x699B,0x69B7,0x69BB,0x69AB, + 0x69B4,0x69D0,0x69CD,0x69AD,0x69CC,0x69A6,0x69C3,0x69A3, + 0x6B49,0x6B4C,0x6C33,0x6F33,0x6F14,0x6EFE,0x6F13,0x6EF4, + 0x6F29,0x6F3E,0x6F20,0x6F2C,0x6F0F,0x6F02,0x6F22,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x6EFF,0x6EEF,0x6F06,0x6F31,0x6F38,0x6F32,0x6F23,0x6F15, + 0x6F2B,0x6F2F,0x6F88,0x6F2A,0x6EEC,0x6F01,0x6EF2,0x6ECC, + 0x6EF7,0x7194,0x7199,0x717D,0x718A,0x7184,0x7192,0x723E, + 0x7292,0x7296,0x7344,0x7350,0x7464,0x7463,0x746A,0x7470, + 0x746D,0x7504,0x7591,0x7627,0x760D,0x760B,0x7609,0x7613, + 0x76E1,0x76E3,0x7784,0x777D,0x777F,0x7761,0x78C1,0x789F, + 0x78A7,0x78B3,0x78A9,0x78A3,0x798E,0x798F,0x798D,0x7A2E, + 0x7A31,0x7AAA,0x7AA9,0x7AED,0x7AEF,0x7BA1,0x7B95,0x7B8B, + 0x7B75,0x7B97,0x7B9D,0x7B94,0x7B8F,0x7BB8,0x7B87,0x7B84, + 0x7CB9,0x7CBD,0x7CBE,0x7DBB,0x7DB0,0x7D9C,0x7DBD,0x7DBE, + 0x7DA0,0x7DCA,0x7DB4,0x7DB2,0x7DB1,0x7DBA,0x7DA2,0x7DBF, + 0x7DB5,0x7DB8,0x7DAD,0x7DD2,0x7DC7,0x7DAC, + }, + {0x7F70,0x7FE0,0x7FE1,0x7FDF,0x805E,0x805A,0x8087,0x8150, + 0x8180,0x818F,0x8188,0x818A,0x817F,0x8182,0x81E7,0x81FA, + 0x8207,0x8214,0x821E,0x824B,0x84C9,0x84BF,0x84C6,0x84C4, + 0x8499,0x849E,0x84B2,0x849C,0x84CB,0x84B8,0x84C0,0x84D3, + 0x8490,0x84BC,0x84D1,0x84CA,0x873F,0x871C,0x873B,0x8722, + 0x8725,0x8734,0x8718,0x8755,0x8737,0x8729,0x88F3,0x8902, + 0x88F4,0x88F9,0x88F8,0x88FD,0x88E8,0x891A,0x88EF,0x8AA6, + 0x8A8C,0x8A9E,0x8AA3,0x8A8D,0x8AA1,0x8A93,0x8AA4,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x8AAA,0x8AA5,0x8AA8,0x8A98,0x8A91,0x8A9A,0x8AA7,0x8C6A, + 0x8C8D,0x8C8C,0x8CD3,0x8CD1,0x8CD2,0x8D6B,0x8D99,0x8D95, + 0x8DFC,0x8F14,0x8F12,0x8F15,0x8F13,0x8FA3,0x9060,0x9058, + 0x905C,0x9063,0x9059,0x905E,0x9062,0x905D,0x905B,0x9119, + 0x9118,0x911E,0x9175,0x9178,0x9177,0x9174,0x9278,0x9280, + 0x9285,0x9298,0x9296,0x927B,0x9293,0x929C,0x92A8,0x927C, + 0x9291,0x95A1,0x95A8,0x95A9,0x95A3,0x95A5,0x95A4,0x9699, + 0x969C,0x969B,0x96CC,0x96D2,0x9700,0x977C,0x9785,0x97F6, + 0x9817,0x9818,0x98AF,0x98B1,0x9903,0x9905,0x990C,0x9909, + 0x99C1,0x9AAF,0x9AB0,0x9AE6,0x9B41,0x9B42,0x9CF4,0x9CF6, + 0x9CF3,0x9EBC,0x9F3B,0x9F4A,0x5104,0x5100,0x50FB,0x50F5, + 0x50F9,0x5102,0x5108,0x5109,0x5105,0x51DC, + }, + {0x5287,0x5288,0x5289,0x528D,0x528A,0x52F0,0x53B2,0x562E, + 0x563B,0x5639,0x5632,0x563F,0x5634,0x5629,0x5653,0x564E, + 0x5657,0x5674,0x5636,0x562F,0x5630,0x5880,0x589F,0x589E, + 0x58B3,0x589C,0x58AE,0x58A9,0x58A6,0x596D,0x5B09,0x5AFB, + 0x5B0B,0x5AF5,0x5B0C,0x5B08,0x5BEE,0x5BEC,0x5BE9,0x5BEB, + 0x5C64,0x5C65,0x5D9D,0x5D94,0x5E62,0x5E5F,0x5E61,0x5EE2, + 0x5EDA,0x5EDF,0x5EDD,0x5EE3,0x5EE0,0x5F48,0x5F71,0x5FB7, + 0x5FB5,0x6176,0x6167,0x616E,0x615D,0x6155,0x6182,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x617C,0x6170,0x616B,0x617E,0x61A7,0x6190,0x61AB,0x618E, + 0x61AC,0x619A,0x61A4,0x6194,0x61AE,0x622E,0x6469,0x646F, + 0x6479,0x649E,0x64B2,0x6488,0x6490,0x64B0,0x64A5,0x6493, + 0x6495,0x64A9,0x6492,0x64AE,0x64AD,0x64AB,0x649A,0x64AC, + 0x6499,0x64A2,0x64B3,0x6575,0x6577,0x6578,0x66AE,0x66AB, + 0x66B4,0x66B1,0x6A23,0x6A1F,0x69E8,0x6A01,0x6A1E,0x6A19, + 0x69FD,0x6A21,0x6A13,0x6A0A,0x69F3,0x6A02,0x6A05,0x69ED, + 0x6A11,0x6B50,0x6B4E,0x6BA4,0x6BC5,0x6BC6,0x6F3F,0x6F7C, + 0x6F84,0x6F51,0x6F66,0x6F54,0x6F86,0x6F6D,0x6F5B,0x6F78, + 0x6F6E,0x6F8E,0x6F7A,0x6F70,0x6F64,0x6F97,0x6F58,0x6ED5, + 0x6F6F,0x6F60,0x6F5F,0x719F,0x71AC,0x71B1,0x71A8,0x7256, + 0x729B,0x734E,0x7357,0x7469,0x748B,0x7483, + }, + {0x747E,0x7480,0x757F,0x7620,0x7629,0x761F,0x7624,0x7626, + 0x7621,0x7622,0x769A,0x76BA,0x76E4,0x778E,0x7787,0x778C, + 0x7791,0x778B,0x78CB,0x78C5,0x78BA,0x78CA,0x78BE,0x78D5, + 0x78BC,0x78D0,0x7A3F,0x7A3C,0x7A40,0x7A3D,0x7A37,0x7A3B, + 0x7AAF,0x7AAE,0x7BAD,0x7BB1,0x7BC4,0x7BB4,0x7BC6,0x7BC7, + 0x7BC1,0x7BA0,0x7BCC,0x7CCA,0x7DE0,0x7DF4,0x7DEF,0x7DFB, + 0x7DD8,0x7DEC,0x7DDD,0x7DE8,0x7DE3,0x7DDA,0x7DDE,0x7DE9, + 0x7D9E,0x7DD9,0x7DF2,0x7DF9,0x7F75,0x7F77,0x7FAF,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x7FE9,0x8026,0x819B,0x819C,0x819D,0x81A0,0x819A,0x8198, + 0x8517,0x853D,0x851A,0x84EE,0x852C,0x852D,0x8513,0x8511, + 0x8523,0x8521,0x8514,0x84EC,0x8525,0x84FF,0x8506,0x8782, + 0x8774,0x8776,0x8760,0x8766,0x8778,0x8768,0x8759,0x8757, + 0x874C,0x8753,0x885B,0x885D,0x8910,0x8907,0x8912,0x8913, + 0x8915,0x890A,0x8ABC,0x8AD2,0x8AC7,0x8AC4,0x8A95,0x8ACB, + 0x8AF8,0x8AB2,0x8AC9,0x8AC2,0x8ABF,0x8AB0,0x8AD6,0x8ACD, + 0x8AB6,0x8AB9,0x8ADB,0x8C4C,0x8C4E,0x8C6C,0x8CE0,0x8CDE, + 0x8CE6,0x8CE4,0x8CEC,0x8CED,0x8CE2,0x8CE3,0x8CDC,0x8CEA, + 0x8CE1,0x8D6D,0x8D9F,0x8DA3,0x8E2B,0x8E10,0x8E1D,0x8E22, + 0x8E0F,0x8E29,0x8E1F,0x8E21,0x8E1E,0x8EBA,0x8F1D,0x8F1B, + 0x8F1F,0x8F29,0x8F26,0x8F2A,0x8F1C,0x8F1E, + }, + {0x8F25,0x9069,0x906E,0x9068,0x906D,0x9077,0x9130,0x912D, + 0x9127,0x9131,0x9187,0x9189,0x918B,0x9183,0x92C5,0x92BB, + 0x92B7,0x92EA,0x92AC,0x92E4,0x92C1,0x92B3,0x92BC,0x92D2, + 0x92C7,0x92F0,0x92B2,0x95AD,0x95B1,0x9704,0x9706,0x9707, + 0x9709,0x9760,0x978D,0x978B,0x978F,0x9821,0x982B,0x981C, + 0x98B3,0x990A,0x9913,0x9912,0x9918,0x99DD,0x99D0,0x99DF, + 0x99DB,0x99D1,0x99D5,0x99D2,0x99D9,0x9AB7,0x9AEE,0x9AEF, + 0x9B27,0x9B45,0x9B44,0x9B77,0x9B6F,0x9D06,0x9D09,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x9D03,0x9EA9,0x9EBE,0x9ECE,0x58A8,0x9F52,0x5112,0x5118, + 0x5114,0x5110,0x5115,0x5180,0x51AA,0x51DD,0x5291,0x5293, + 0x52F3,0x5659,0x566B,0x5679,0x5669,0x5664,0x5678,0x566A, + 0x5668,0x5665,0x5671,0x566F,0x566C,0x5662,0x5676,0x58C1, + 0x58BE,0x58C7,0x58C5,0x596E,0x5B1D,0x5B34,0x5B78,0x5BF0, + 0x5C0E,0x5F4A,0x61B2,0x6191,0x61A9,0x618A,0x61CD,0x61B6, + 0x61BE,0x61CA,0x61C8,0x6230,0x64C5,0x64C1,0x64CB,0x64BB, + 0x64BC,0x64DA,0x64C4,0x64C7,0x64C2,0x64CD,0x64BF,0x64D2, + 0x64D4,0x64BE,0x6574,0x66C6,0x66C9,0x66B9,0x66C4,0x66C7, + 0x66B8,0x6A3D,0x6A38,0x6A3A,0x6A59,0x6A6B,0x6A58,0x6A39, + 0x6A44,0x6A62,0x6A61,0x6A4B,0x6A47,0x6A35,0x6A5F,0x6A48, + 0x6B59,0x6B77,0x6C05,0x6FC2,0x6FB1,0x6FA1, + }, + {0x6FC3,0x6FA4,0x6FC1,0x6FA7,0x6FB3,0x6FC0,0x6FB9,0x6FB6, + 0x6FA6,0x6FA0,0x6FB4,0x71BE,0x71C9,0x71D0,0x71D2,0x71C8, + 0x71D5,0x71B9,0x71CE,0x71D9,0x71DC,0x71C3,0x71C4,0x7368, + 0x749C,0x74A3,0x7498,0x749F,0x749E,0x74E2,0x750C,0x750D, + 0x7634,0x7638,0x763A,0x76E7,0x76E5,0x77A0,0x779E,0x779F, + 0x77A5,0x78E8,0x78DA,0x78EC,0x78E7,0x79A6,0x7A4D,0x7A4E, + 0x7A46,0x7A4C,0x7A4B,0x7ABA,0x7BD9,0x7C11,0x7BC9,0x7BE4, + 0x7BDB,0x7BE1,0x7BE9,0x7BE6,0x7CD5,0x7CD6,0x7E0A,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x7E11,0x7E08,0x7E1B,0x7E23,0x7E1E,0x7E1D,0x7E09,0x7E10, + 0x7F79,0x7FB2,0x7FF0,0x7FF1,0x7FEE,0x8028,0x81B3,0x81A9, + 0x81A8,0x81FB,0x8208,0x8258,0x8259,0x854A,0x8559,0x8548, + 0x8568,0x8569,0x8543,0x8549,0x856D,0x856A,0x855E,0x8783, + 0x879F,0x879E,0x87A2,0x878D,0x8861,0x892A,0x8932,0x8925, + 0x892B,0x8921,0x89AA,0x89A6,0x8AE6,0x8AFA,0x8AEB,0x8AF1, + 0x8B00,0x8ADC,0x8AE7,0x8AEE,0x8AFE,0x8B01,0x8B02,0x8AF7, + 0x8AED,0x8AF3,0x8AF6,0x8AFC,0x8C6B,0x8C6D,0x8C93,0x8CF4, + 0x8E44,0x8E31,0x8E34,0x8E42,0x8E39,0x8E35,0x8F3B,0x8F2F, + 0x8F38,0x8F33,0x8FA8,0x8FA6,0x9075,0x9074,0x9078,0x9072, + 0x907C,0x907A,0x9134,0x9192,0x9320,0x9336,0x92F8,0x9333, + 0x932F,0x9322,0x92FC,0x932B,0x9304,0x931A, + }, + {0x9310,0x9326,0x9321,0x9315,0x932E,0x9319,0x95BB,0x96A7, + 0x96A8,0x96AA,0x96D5,0x970E,0x9711,0x9716,0x970D,0x9713, + 0x970F,0x975B,0x975C,0x9766,0x9798,0x9830,0x9838,0x983B, + 0x9837,0x982D,0x9839,0x9824,0x9910,0x9928,0x991E,0x991B, + 0x9921,0x991A,0x99ED,0x99E2,0x99F1,0x9AB8,0x9ABC,0x9AFB, + 0x9AED,0x9B28,0x9B91,0x9D15,0x9D23,0x9D26,0x9D28,0x9D12, + 0x9D1B,0x9ED8,0x9ED4,0x9F8D,0x9F9C,0x512A,0x511F,0x5121, + 0x5132,0x52F5,0x568E,0x5680,0x5690,0x5685,0x5687,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x568F,0x58D5,0x58D3,0x58D1,0x58CE,0x5B30,0x5B2A,0x5B24, + 0x5B7A,0x5C37,0x5C68,0x5DBC,0x5DBA,0x5DBD,0x5DB8,0x5E6B, + 0x5F4C,0x5FBD,0x61C9,0x61C2,0x61C7,0x61E6,0x61CB,0x6232, + 0x6234,0x64CE,0x64CA,0x64D8,0x64E0,0x64F0,0x64E6,0x64EC, + 0x64F1,0x64E2,0x64ED,0x6582,0x6583,0x66D9,0x66D6,0x6A80, + 0x6A94,0x6A84,0x6AA2,0x6A9C,0x6ADB,0x6AA3,0x6A7E,0x6A97, + 0x6A90,0x6AA0,0x6B5C,0x6BAE,0x6BDA,0x6C08,0x6FD8,0x6FF1, + 0x6FDF,0x6FE0,0x6FDB,0x6FE4,0x6FEB,0x6FEF,0x6F80,0x6FEC, + 0x6FE1,0x6FE9,0x6FD5,0x6FEE,0x6FF0,0x71E7,0x71DF,0x71EE, + 0x71E6,0x71E5,0x71ED,0x71EC,0x71F4,0x71E0,0x7235,0x7246, + 0x7370,0x7372,0x74A9,0x74B0,0x74A6,0x74A8,0x7646,0x7642, + 0x764C,0x76EA,0x77B3,0x77AA,0x77B0,0x77AC, + }, + {0x77A7,0x77AD,0x77EF,0x78F7,0x78FA,0x78F4,0x78EF,0x7901, + 0x79A7,0x79AA,0x7A57,0x7ABF,0x7C07,0x7C0D,0x7BFE,0x7BF7, + 0x7C0C,0x7BE0,0x7CE0,0x7CDC,0x7CDE,0x7CE2,0x7CDF,0x7CD9, + 0x7CDD,0x7E2E,0x7E3E,0x7E46,0x7E37,0x7E32,0x7E43,0x7E2B, + 0x7E3D,0x7E31,0x7E45,0x7E41,0x7E34,0x7E39,0x7E48,0x7E35, + 0x7E3F,0x7E2F,0x7F44,0x7FF3,0x7FFC,0x8071,0x8072,0x8070, + 0x806F,0x8073,0x81C6,0x81C3,0x81BA,0x81C2,0x81C0,0x81BF, + 0x81BD,0x81C9,0x81BE,0x81E8,0x8209,0x8271,0x85AA,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x8584,0x857E,0x859C,0x8591,0x8594,0x85AF,0x859B,0x8587, + 0x85A8,0x858A,0x8667,0x87C0,0x87D1,0x87B3,0x87D2,0x87C6, + 0x87AB,0x87BB,0x87BA,0x87C8,0x87CB,0x893B,0x8936,0x8944, + 0x8938,0x893D,0x89AC,0x8B0E,0x8B17,0x8B19,0x8B1B,0x8B0A, + 0x8B20,0x8B1D,0x8B04,0x8B10,0x8C41,0x8C3F,0x8C73,0x8CFA, + 0x8CFD,0x8CFC,0x8CF8,0x8CFB,0x8DA8,0x8E49,0x8E4B,0x8E48, + 0x8E4A,0x8F44,0x8F3E,0x8F42,0x8F45,0x8F3F,0x907F,0x907D, + 0x9084,0x9081,0x9082,0x9080,0x9139,0x91A3,0x919E,0x919C, + 0x934D,0x9382,0x9328,0x9375,0x934A,0x9365,0x934B,0x9318, + 0x937E,0x936C,0x935B,0x9370,0x935A,0x9354,0x95CA,0x95CB, + 0x95CC,0x95C8,0x95C6,0x96B1,0x96B8,0x96D6,0x971C,0x971E, + 0x97A0,0x97D3,0x9846,0x98B6,0x9935,0x9A01, + }, + {0x99FF,0x9BAE,0x9BAB,0x9BAA,0x9BAD,0x9D3B,0x9D3F,0x9E8B, + 0x9ECF,0x9EDE,0x9EDC,0x9EDD,0x9EDB,0x9F3E,0x9F4B,0x53E2, + 0x5695,0x56AE,0x58D9,0x58D8,0x5B38,0x5F5D,0x61E3,0x6233, + 0x64F4,0x64F2,0x64FE,0x6506,0x64FA,0x64FB,0x64F7,0x65B7, + 0x66DC,0x6726,0x6AB3,0x6AAC,0x6AC3,0x6ABB,0x6AB8,0x6AC2, + 0x6AAE,0x6AAF,0x6B5F,0x6B78,0x6BAF,0x7009,0x700B,0x6FFE, + 0x7006,0x6FFA,0x7011,0x700F,0x71FB,0x71FC,0x71FE,0x71F8, + 0x7377,0x7375,0x74A7,0x74BF,0x7515,0x7656,0x7658,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x7652,0x77BD,0x77BF,0x77BB,0x77BC,0x790E,0x79AE,0x7A61, + 0x7A62,0x7A60,0x7AC4,0x7AC5,0x7C2B,0x7C27,0x7C2A,0x7C1E, + 0x7C23,0x7C21,0x7CE7,0x7E54,0x7E55,0x7E5E,0x7E5A,0x7E61, + 0x7E52,0x7E59,0x7F48,0x7FF9,0x7FFB,0x8077,0x8076,0x81CD, + 0x81CF,0x820A,0x85CF,0x85A9,0x85CD,0x85D0,0x85C9,0x85B0, + 0x85BA,0x85B9,0x85A6,0x87EF,0x87EC,0x87F2,0x87E0,0x8986, + 0x89B2,0x89F4,0x8B28,0x8B39,0x8B2C,0x8B2B,0x8C50,0x8D05, + 0x8E59,0x8E63,0x8E66,0x8E64,0x8E5F,0x8E55,0x8EC0,0x8F49, + 0x8F4D,0x9087,0x9083,0x9088,0x91AB,0x91AC,0x91D0,0x9394, + 0x938A,0x9396,0x93A2,0x93B3,0x93AE,0x93AC,0x93B0,0x9398, + 0x939A,0x9397,0x95D4,0x95D6,0x95D0,0x95D5,0x96E2,0x96DC, + 0x96D9,0x96DB,0x96DE,0x9724,0x97A3,0x97A6, + }, + {0x97AD,0x97F9,0x984D,0x984F,0x984C,0x984E,0x9853,0x98BA, + 0x993E,0x993F,0x993D,0x992E,0x99A5,0x9A0E,0x9AC1,0x9B03, + 0x9B06,0x9B4F,0x9B4E,0x9B4D,0x9BCA,0x9BC9,0x9BFD,0x9BC8, + 0x9BC0,0x9D51,0x9D5D,0x9D60,0x9EE0,0x9F15,0x9F2C,0x5133, + 0x56A5,0x58DE,0x58DF,0x58E2,0x5BF5,0x9F90,0x5EEC,0x61F2, + 0x61F7,0x61F6,0x61F5,0x6500,0x650F,0x66E0,0x66DD,0x6AE5, + 0x6ADD,0x6ADA,0x6AD3,0x701B,0x701F,0x7028,0x701A,0x701D, + 0x7015,0x7018,0x7206,0x720D,0x7258,0x72A2,0x7378,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x737A,0x74BD,0x74CA,0x74E3,0x7587,0x7586,0x765F,0x7661, + 0x77C7,0x7919,0x79B1,0x7A6B,0x7A69,0x7C3E,0x7C3F,0x7C38, + 0x7C3D,0x7C37,0x7C40,0x7E6B,0x7E6D,0x7E79,0x7E69,0x7E6A, + 0x7F85,0x7E73,0x7FB6,0x7FB9,0x7FB8,0x81D8,0x85E9,0x85DD, + 0x85EA,0x85D5,0x85E4,0x85E5,0x85F7,0x87FB,0x8805,0x880D, + 0x87F9,0x87FE,0x8960,0x895F,0x8956,0x895E,0x8B41,0x8B5C, + 0x8B58,0x8B49,0x8B5A,0x8B4E,0x8B4F,0x8B46,0x8B59,0x8D08, + 0x8D0A,0x8E7C,0x8E72,0x8E87,0x8E76,0x8E6C,0x8E7A,0x8E74, + 0x8F54,0x8F4E,0x8FAD,0x908A,0x908B,0x91B1,0x91AE,0x93E1, + 0x93D1,0x93DF,0x93C3,0x93C8,0x93DC,0x93DD,0x93D6,0x93E2, + 0x93CD,0x93D8,0x93E4,0x93D7,0x93E8,0x95DC,0x96B4,0x96E3, + 0x972A,0x9727,0x9761,0x97DC,0x97FB,0x985E, + }, + {0x9858,0x985B,0x98BC,0x9945,0x9949,0x9A16,0x9A19,0x9B0D, + 0x9BE8,0x9BE7,0x9BD6,0x9BDB,0x9D89,0x9D61,0x9D72,0x9D6A, + 0x9D6C,0x9E92,0x9E97,0x9E93,0x9EB4,0x52F8,0x56A8,0x56B7, + 0x56B6,0x56B4,0x56BC,0x58E4,0x5B40,0x5B43,0x5B7D,0x5BF6, + 0x5DC9,0x61F8,0x61FA,0x6518,0x6514,0x6519,0x66E6,0x6727, + 0x6AEC,0x703E,0x7030,0x7032,0x7210,0x737B,0x74CF,0x7662, + 0x7665,0x7926,0x792A,0x792C,0x792B,0x7AC7,0x7AF6,0x7C4C, + 0x7C43,0x7C4D,0x7CEF,0x7CF0,0x8FAE,0x7E7D,0x7E7C,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x7E82,0x7F4C,0x8000,0x81DA,0x8266,0x85FB,0x85F9,0x8611, + 0x85FA,0x8606,0x860B,0x8607,0x860A,0x8814,0x8815,0x8964, + 0x89BA,0x89F8,0x8B70,0x8B6C,0x8B66,0x8B6F,0x8B5F,0x8B6B, + 0x8D0F,0x8D0D,0x8E89,0x8E81,0x8E85,0x8E82,0x91B4,0x91CB, + 0x9418,0x9403,0x93FD,0x95E1,0x9730,0x98C4,0x9952,0x9951, + 0x99A8,0x9A2B,0x9A30,0x9A37,0x9A35,0x9C13,0x9C0D,0x9E79, + 0x9EB5,0x9EE8,0x9F2F,0x9F5F,0x9F63,0x9F61,0x5137,0x5138, + 0x56C1,0x56C0,0x56C2,0x5914,0x5C6C,0x5DCD,0x61FC,0x61FE, + 0x651D,0x651C,0x6595,0x66E9,0x6AFB,0x6B04,0x6AFA,0x6BB2, + 0x704C,0x721B,0x72A7,0x74D6,0x74D4,0x7669,0x77D3,0x7C50, + 0x7E8F,0x7E8C,0x7FBC,0x8617,0x862D,0x861A,0x8823,0x8822, + 0x8821,0x881F,0x896A,0x896C,0x89BD,0x8B74, + }, + {0x8B77,0x8B7D,0x8D13,0x8E8A,0x8E8D,0x8E8B,0x8F5F,0x8FAF, + 0x91BA,0x942E,0x9433,0x9435,0x943A,0x9438,0x9432,0x942B, + 0x95E2,0x9738,0x9739,0x9732,0x97FF,0x9867,0x9865,0x9957, + 0x9A45,0x9A43,0x9A40,0x9A3E,0x9ACF,0x9B54,0x9B51,0x9C2D, + 0x9C25,0x9DAF,0x9DB4,0x9DC2,0x9DB8,0x9E9D,0x9EEF,0x9F19, + 0x9F5C,0x9F66,0x9F67,0x513C,0x513B,0x56C8,0x56CA,0x56C9, + 0x5B7F,0x5DD4,0x5DD2,0x5F4E,0x61FF,0x6524,0x6B0A,0x6B61, + 0x7051,0x7058,0x7380,0x74E4,0x758A,0x766E,0x766C,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x79B3,0x7C60,0x7C5F,0x807E,0x807D,0x81DF,0x8972,0x896F, + 0x89FC,0x8B80,0x8D16,0x8D17,0x8E91,0x8E93,0x8F61,0x9148, + 0x9444,0x9451,0x9452,0x973D,0x973E,0x97C3,0x97C1,0x986B, + 0x9955,0x9A55,0x9A4D,0x9AD2,0x9B1A,0x9C49,0x9C31,0x9C3E, + 0x9C3B,0x9DD3,0x9DD7,0x9F34,0x9F6C,0x9F6A,0x9F94,0x56CC, + 0x5DD6,0x6200,0x6523,0x652B,0x652A,0x66EC,0x6B10,0x74DA, + 0x7ACA,0x7C64,0x7C63,0x7C65,0x7E93,0x7E96,0x7E94,0x81E2, + 0x8638,0x863F,0x8831,0x8B8A,0x9090,0x908F,0x9463,0x9460, + 0x9464,0x9768,0x986F,0x995C,0x9A5A,0x9A5B,0x9A57,0x9AD3, + 0x9AD4,0x9AD1,0x9C54,0x9C57,0x9C56,0x9DE5,0x9E9F,0x9EF4, + 0x56D1,0x58E9,0x652C,0x705E,0x7671,0x7672,0x77D7,0x7F50, + 0x7F88,0x8836,0x8839,0x8862,0x8B93,0x8B92, + }, + {0x8B96,0x8277,0x8D1B,0x91C0,0x946A,0x9742,0x9748,0x9744, + 0x97C6,0x9870,0x9A5F,0x9B22,0x9B58,0x9C5F,0x9DF9,0x9DFA, + 0x9E7C,0x9E7D,0x9F07,0x9F77,0x9F72,0x5EF3,0x6B16,0x7063, + 0x7C6C,0x7C6E,0x883B,0x89C0,0x8EA1,0x91C1,0x9472,0x9470, + 0x9871,0x995E,0x9AD6,0x9B23,0x9ECC,0x7064,0x77DA,0x8B9A, + 0x9477,0x97C9,0x9A62,0x9A65,0x7E9C,0x8B9C,0x8EAA,0x91C5, + 0x947D,0x947E,0x947C,0x9C77,0x9C78,0x9EF7,0x8C54,0x947F, + 0x9E1A,0x7228,0x9A6A,0x9B31,0x9E1B,0x9E1E,0x7C72,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x30FE,0x309D,0x309E,0x3005,0x3041,0x3042,0x3043,0x3044, + 0x3045,0x3046,0x3047,0x3048,0x3049,0x304A,0x304B,0x304C, + 0x304D,0x304E,0x304F,0x3050,0x3051,0x3052,0x3053,0x3054, + 0x3055,0x3056,0x3057,0x3058,0x3059,0x305A,0x305B,0x305C, + 0x305D,0x305E,0x305F,0x3060,0x3061,0x3062,0x3063,0x3064, + 0x3065,0x3066,0x3067,0x3068,0x3069,0x306A,0x306B,0x306C, + 0x306D,0x306E,0x306F,0x3070,0x3071,0x3072,0x3073,0x3074, + 0x3075,0x3076,0x3077,0x3078,0x3079,0x307A,0x307B,0x307C, + 0x307D,0x307E,0x307F,0x3080,0x3081,0x3082,0x3083,0x3084, + 0x3085,0x3086,0x3087,0x3088,0x3089,0x308A,0x308B,0x308C, + 0x308D,0x308E,0x308F,0x3090,0x3091,0x3092,0x3093,0x30A1, + 0x30A2,0x30A3,0x30A4,0x30A5,0x30A6,0x30A7, + }, + {0x30A8,0x30A9,0x30AA,0x30AB,0x30AC,0x30AD,0x30AE,0x30AF, + 0x30B0,0x30B1,0x30B2,0x30B3,0x30B4,0x30B5,0x30B6,0x30B7, + 0x30B8,0x30B9,0x30BA,0x30BB,0x30BC,0x30BD,0x30BE,0x30BF, + 0x30C0,0x30C1,0x30C2,0x30C3,0x30C4,0x30C5,0x30C6,0x30C7, + 0x30C8,0x30C9,0x30CA,0x30CB,0x30CC,0x30CD,0x30CE,0x30CF, + 0x30D0,0x30D1,0x30D2,0x30D3,0x30D4,0x30D5,0x30D6,0x30D7, + 0x30D8,0x30D9,0x30DA,0x30DB,0x30DC,0x30DD,0x30DE,0x30DF, + 0x30E0,0x30E1,0x30E2,0x30E3,0x30E4,0x30E5,0x30E6,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x30E7,0x30E8,0x30E9,0x30EA,0x30EB,0x30EC,0x30ED,0x30EE, + 0x30EF,0x30F0,0x30F1,0x30F2,0x30F3,0x30F4,0x30F5,0x30F6, + 0x0414,0x0415,0x0401,0x0416,0x0417,0x0418,0x0419,0x041A, + 0x041B,0x041C,0x0423,0x0424,0x0425,0x0426,0x0427,0x0428, + 0x0429,0x042A,0x042B,0x042C,0x042D,0x042E,0x042F,0x0430, + 0x0431,0x0432,0x0433,0x0434,0x0435,0x0451,0x0436,0x0437, + 0x0438,0x0439,0x043A,0x043B,0x043C,0x043D,0x043E,0x043F, + 0x0440,0x0441,0x0442,0x0443,0x0444,0x0445,0x0446,0x0447, + 0x0448,0x0449,0x044A,0x044B,0x044C,0x044D,0x044E,0x044F, + 0x2460,0x2461,0x2462,0x2463,0x2464,0x2465,0x2466,0x2467, + 0x2468,0x2469,0x2474,0x2475,0x2476,0x2477,0x2478,0x2479, + 0x247A,0x247B,0x247C,0x247D,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x4E42,0x4E5C,0x51F5,0x531A,0x5382,0x4E07,0x4E0C,0x4E47, + 0x4E8D,0x56D7,0xFA0C,0x5C6E,0x5F73,0x4E0F,0x5187,0x4E0E, + 0x4E2E,0x4E93,0x4EC2,0x4EC9,0x4EC8,0x5198,0x52FC,0x536C, + 0x53B9,0x5720,0x5903,0x592C,0x5C10,0x5DFF,0x65E1,0x6BB3, + 0x6BCC,0x6C14,0x723F,0x4E31,0x4E3C,0x4EE8,0x4EDC,0x4EE9, + 0x4EE1,0x4EDD,0x4EDA,0x520C,0x531C,0x534C,0x5722,0x5723, + 0x5917,0x592F,0x5B81,0x5B84,0x5C12,0x5C3B,0x5C74,0x5C73, + 0x5E04,0x5E80,0x5E82,0x5FC9,0x6209,0x6250,0x6C15,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x6C36,0x6C43,0x6C3F,0x6C3B,0x72AE,0x72B0,0x738A,0x79B8, + 0x808A,0x961E,0x4F0E,0x4F18,0x4F2C,0x4EF5,0x4F14,0x4EF1, + 0x4F00,0x4EF7,0x4F08,0x4F1D,0x4F02,0x4F05,0x4F22,0x4F13, + 0x4F04,0x4EF4,0x4F12,0x51B1,0x5213,0x5209,0x5210,0x52A6, + 0x5322,0x531F,0x534D,0x538A,0x5407,0x56E1,0x56DF,0x572E, + 0x572A,0x5734,0x593C,0x5980,0x597C,0x5985,0x597B,0x597E, + 0x5977,0x597F,0x5B56,0x5C15,0x5C25,0x5C7C,0x5C7A,0x5C7B, + 0x5C7E,0x5DDF,0x5E75,0x5E84,0x5F02,0x5F1A,0x5F74,0x5FD5, + 0x5FD4,0x5FCF,0x625C,0x625E,0x6264,0x6261,0x6266,0x6262, + 0x6259,0x6260,0x625A,0x6265,0x65EF,0x65EE,0x673E,0x6739, + 0x6738,0x673B,0x673A,0x673F,0x673C,0x6733,0x6C18,0x6C46, + 0x6C52,0x6C5C,0x6C4F,0x6C4A,0x6C54,0x6C4B, + }, + {0x6C4C,0x7071,0x725E,0x72B4,0x72B5,0x738E,0x752A,0x767F, + 0x7A75,0x7F51,0x8278,0x827C,0x8280,0x827D,0x827F,0x864D, + 0x897E,0x9099,0x9097,0x9098,0x909B,0x9094,0x9622,0x9624, + 0x9620,0x9623,0x4F56,0x4F3B,0x4F62,0x4F49,0x4F53,0x4F64, + 0x4F3E,0x4F67,0x4F52,0x4F5F,0x4F41,0x4F58,0x4F2D,0x4F33, + 0x4F3F,0x4F61,0x518F,0x51B9,0x521C,0x521E,0x5221,0x52AD, + 0x52AE,0x5309,0x5363,0x5372,0x538E,0x538F,0x5430,0x5437, + 0x542A,0x5454,0x5445,0x5419,0x541C,0x5425,0x5418,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x543D,0x544F,0x5441,0x5428,0x5424,0x5447,0x56EE,0x56E7, + 0x56E5,0x5741,0x5745,0x574C,0x5749,0x574B,0x5752,0x5906, + 0x5940,0x59A6,0x5998,0x59A0,0x5997,0x598E,0x59A2,0x5990, + 0x598F,0x59A7,0x59A1,0x5B8E,0x5B92,0x5C28,0x5C2A,0x5C8D, + 0x5C8F,0x5C88,0x5C8B,0x5C89,0x5C92,0x5C8A,0x5C86,0x5C93, + 0x5C95,0x5DE0,0x5E0A,0x5E0E,0x5E8B,0x5E89,0x5E8C,0x5E88, + 0x5E8D,0x5F05,0x5F1D,0x5F78,0x5F76,0x5FD2,0x5FD1,0x5FD0, + 0x5FED,0x5FE8,0x5FEE,0x5FF3,0x5FE1,0x5FE4,0x5FE3,0x5FFA, + 0x5FEF,0x5FF7,0x5FFB,0x6000,0x5FF4,0x623A,0x6283,0x628C, + 0x628E,0x628F,0x6294,0x6287,0x6271,0x627B,0x627A,0x6270, + 0x6281,0x6288,0x6277,0x627D,0x6272,0x6274,0x6537,0x65F0, + 0x65F4,0x65F3,0x65F2,0x65F5,0x6745,0x6747, + }, + {0x6759,0x6755,0x674C,0x6748,0x675D,0x674D,0x675A,0x674B, + 0x6BD0,0x6C19,0x6C1A,0x6C78,0x6C67,0x6C6B,0x6C84,0x6C8B, + 0x6C8F,0x6C71,0x6C6F,0x6C69,0x6C9A,0x6C6D,0x6C87,0x6C95, + 0x6C9C,0x6C66,0x6C73,0x6C65,0x6C7B,0x6C8E,0x7074,0x707A, + 0x7263,0x72BF,0x72BD,0x72C3,0x72C6,0x72C1,0x72BA,0x72C5, + 0x7395,0x7397,0x7393,0x7394,0x7392,0x753A,0x7539,0x7594, + 0x7595,0x7681,0x793D,0x8034,0x8095,0x8099,0x8090,0x8092, + 0x809C,0x8290,0x828F,0x8285,0x828E,0x8291,0x8293,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x828A,0x8283,0x8284,0x8C78,0x8FC9,0x8FBF,0x909F,0x90A1, + 0x90A5,0x909E,0x90A7,0x90A0,0x9630,0x9628,0x962F,0x962D, + 0x4E33,0x4F98,0x4F7C,0x4F85,0x4F7D,0x4F80,0x4F87,0x4F76, + 0x4F74,0x4F89,0x4F84,0x4F77,0x4F4C,0x4F97,0x4F6A,0x4F9A, + 0x4F79,0x4F81,0x4F78,0x4F90,0x4F9C,0x4F94,0x4F9E,0x4F92, + 0x4F82,0x4F95,0x4F6B,0x4F6E,0x519E,0x51BC,0x51BE,0x5235, + 0x5232,0x5233,0x5246,0x5231,0x52BC,0x530A,0x530B,0x533C, + 0x5392,0x5394,0x5487,0x547F,0x5481,0x5491,0x5482,0x5488, + 0x546B,0x547A,0x547E,0x5465,0x546C,0x5474,0x5466,0x548D, + 0x546F,0x5461,0x5460,0x5498,0x5463,0x5467,0x5464,0x56F7, + 0x56F9,0x576F,0x5772,0x576D,0x576B,0x5771,0x5770,0x5776, + 0x5780,0x5775,0x577B,0x5773,0x5774,0x5762, + }, + {0x5768,0x577D,0x590C,0x5945,0x59B5,0x59BA,0x59CF,0x59CE, + 0x59B2,0x59CC,0x59C1,0x59B6,0x59BC,0x59C3,0x59D6,0x59B1, + 0x59BD,0x59C0,0x59C8,0x59B4,0x59C7,0x5B62,0x5B65,0x5B93, + 0x5B95,0x5C44,0x5C47,0x5CAE,0x5CA4,0x5CA0,0x5CB5,0x5CAF, + 0x5CA8,0x5CAC,0x5C9F,0x5CA3,0x5CAD,0x5CA2,0x5CAA,0x5CA7, + 0x5C9D,0x5CA5,0x5CB6,0x5CB0,0x5CA6,0x5E17,0x5E14,0x5E19, + 0x5F28,0x5F22,0x5F23,0x5F24,0x5F54,0x5F82,0x5F7E,0x5F7D, + 0x5FDE,0x5FE5,0x602D,0x6026,0x6019,0x6032,0x600B,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x6034,0x600A,0x6017,0x6033,0x601A,0x601E,0x602C,0x6022, + 0x600D,0x6010,0x602E,0x6013,0x6011,0x600C,0x6009,0x601C, + 0x6214,0x623D,0x62AD,0x62B4,0x62D1,0x62BE,0x62AA,0x62B6, + 0x62CA,0x62AE,0x62B3,0x62AF,0x62BB,0x62A9,0x62B0,0x62B8, + 0x653D,0x65A8,0x65BB,0x6609,0x65FC,0x6604,0x6612,0x6608, + 0x65FB,0x6603,0x660B,0x660D,0x6605,0x65FD,0x6611,0x6610, + 0x66F6,0x670A,0x6785,0x676C,0x678E,0x6792,0x6776,0x677B, + 0x6798,0x6786,0x6784,0x6774,0x678D,0x678C,0x677A,0x679F, + 0x6791,0x6799,0x6783,0x677D,0x6781,0x6778,0x6779,0x6794, + 0x6B25,0x6B80,0x6B7E,0x6BDE,0x6C1D,0x6C93,0x6CEC,0x6CEB, + 0x6CEE,0x6CD9,0x6CB6,0x6CD4,0x6CAD,0x6CE7,0x6CB7,0x6CD0, + 0x6CC2,0x6CBA,0x6CC3,0x6CC6,0x6CED,0x6CF2, + }, + {0x6CD2,0x6CDD,0x6CB4,0x6C8A,0x6C9D,0x6C80,0x6CDE,0x6CC0, + 0x6D30,0x6CCD,0x6CC7,0x6CB0,0x6CF9,0x6CCF,0x6CE9,0x6CD1, + 0x7094,0x7098,0x7085,0x7093,0x7086,0x7084,0x7091,0x7096, + 0x7082,0x709A,0x7083,0x726A,0x72D6,0x72CB,0x72D8,0x72C9, + 0x72DC,0x72D2,0x72D4,0x72DA,0x72CC,0x72D1,0x73A4,0x73A1, + 0x73AD,0x73A6,0x73A2,0x73A0,0x73AC,0x739D,0x74DD,0x74E8, + 0x753F,0x7540,0x753E,0x758C,0x7598,0x76AF,0x76F3,0x76F1, + 0x76F0,0x76F5,0x77F8,0x77FC,0x77F9,0x77FB,0x77FA,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x77F7,0x7942,0x793F,0x79C5,0x7A78,0x7A7B,0x7AFB,0x7C75, + 0x7CFD,0x8035,0x808F,0x80AE,0x80A3,0x80B8,0x80B5,0x80AD, + 0x8220,0x82A0,0x82C0,0x82AB,0x829A,0x8298,0x829B,0x82B5, + 0x82A7,0x82AE,0x82BC,0x829E,0x82BA,0x82B4,0x82A8,0x82A1, + 0x82A9,0x82C2,0x82A4,0x82C3,0x82B6,0x82A2,0x8670,0x866F, + 0x866D,0x866E,0x8C56,0x8FD2,0x8FCB,0x8FD3,0x8FCD,0x8FD6, + 0x8FD5,0x8FD7,0x90B2,0x90B4,0x90AF,0x90B3,0x90B0,0x9639, + 0x963D,0x963C,0x963A,0x9643,0x4FCD,0x4FC5,0x4FD3,0x4FB2, + 0x4FC9,0x4FCB,0x4FC1,0x4FD4,0x4FDC,0x4FD9,0x4FBB,0x4FB3, + 0x4FDB,0x4FC7,0x4FD6,0x4FBA,0x4FC0,0x4FB9,0x4FEC,0x5244, + 0x5249,0x52C0,0x52C2,0x533D,0x537C,0x5397,0x5396,0x5399, + 0x5398,0x54BA,0x54A1,0x54AD,0x54A5,0x54CF, + }, + {0x54C3,0x830D,0x54B7,0x54AE,0x54D6,0x54B6,0x54C5,0x54C6, + 0x54A0,0x5470,0x54BC,0x54A2,0x54BE,0x5472,0x54DE,0x54B0, + 0x57B5,0x579E,0x579F,0x57A4,0x578C,0x5797,0x579D,0x579B, + 0x5794,0x5798,0x578F,0x5799,0x57A5,0x579A,0x5795,0x58F4, + 0x590D,0x5953,0x59E1,0x59DE,0x59EE,0x5A00,0x59F1,0x59DD, + 0x59FA,0x59FD,0x59FC,0x59F6,0x59E4,0x59F2,0x59F7,0x59DB, + 0x59E9,0x59F3,0x59F5,0x59E0,0x59FE,0x59F4,0x59ED,0x5BA8, + 0x5C4C,0x5CD0,0x5CD8,0x5CCC,0x5CD7,0x5CCB,0x5CDB,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x5CDE,0x5CDA,0x5CC9,0x5CC7,0x5CCA,0x5CD6,0x5CD3,0x5CD4, + 0x5CCF,0x5CC8,0x5CC6,0x5CCE,0x5CDF,0x5CF8,0x5DF9,0x5E21, + 0x5E22,0x5E23,0x5E20,0x5E24,0x5EB0,0x5EA4,0x5EA2,0x5E9B, + 0x5EA3,0x5EA5,0x5F07,0x5F2E,0x5F56,0x5F86,0x6037,0x6039, + 0x6054,0x6072,0x605E,0x6045,0x6053,0x6047,0x6049,0x605B, + 0x604C,0x6040,0x6042,0x605F,0x6024,0x6044,0x6058,0x6066, + 0x606E,0x6242,0x6243,0x62CF,0x630D,0x630B,0x62F5,0x630E, + 0x6303,0x62EB,0x62F9,0x630F,0x630C,0x62F8,0x62F6,0x6300, + 0x6313,0x6314,0x62FA,0x6315,0x62FB,0x62F0,0x6541,0x6543, + 0x65AA,0x65BF,0x6636,0x6621,0x6632,0x6635,0x661C,0x6626, + 0x6622,0x6633,0x662B,0x663A,0x661D,0x6634,0x6639,0x662E, + 0x670F,0x6710,0x67C1,0x67F2,0x67C8,0x67BA, + }, + {0x67DC,0x67BB,0x67F8,0x67D8,0x67C0,0x67B7,0x67C5,0x67EB, + 0x67E4,0x67DF,0x67B5,0x67CD,0x67B3,0x67F7,0x67F6,0x67EE, + 0x67E3,0x67C2,0x67B9,0x67CE,0x67E7,0x67F0,0x67B2,0x67FC, + 0x67C6,0x67ED,0x67CC,0x67AE,0x67E6,0x67DB,0x67FA,0x67C9, + 0x67CA,0x67C3,0x67EA,0x67CB,0x6B28,0x6B82,0x6B84,0x6BB6, + 0x6BD6,0x6BD8,0x6BE0,0x6C20,0x6C21,0x6D28,0x6D34,0x6D2D, + 0x6D1F,0x6D3C,0x6D3F,0x6D12,0x6D0A,0x6CDA,0x6D33,0x6D04, + 0x6D19,0x6D3A,0x6D1A,0x6D11,0x6D00,0x6D1D,0x6D42,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x6D01,0x6D18,0x6D37,0x6D03,0x6D0F,0x6D40,0x6D07,0x6D20, + 0x6D2C,0x6D08,0x6D22,0x6D09,0x6D10,0x70B7,0x709F,0x70BE, + 0x70B1,0x70B0,0x70A1,0x70B4,0x70B5,0x70A9,0x7241,0x7249, + 0x724A,0x726C,0x7270,0x7273,0x726E,0x72CA,0x72E4,0x72E8, + 0x72EB,0x72DF,0x72EA,0x72E6,0x72E3,0x7385,0x73CC,0x73C2, + 0x73C8,0x73C5,0x73B9,0x73B6,0x73B5,0x73B4,0x73EB,0x73BF, + 0x73C7,0x73BE,0x73C3,0x73C6,0x73B8,0x73CB,0x74EC,0x74EE, + 0x752E,0x7547,0x7548,0x75A7,0x75AA,0x7679,0x76C4,0x7708, + 0x7703,0x7704,0x7705,0x770A,0x76F7,0x76FB,0x76FA,0x77E7, + 0x77E8,0x7806,0x7811,0x7812,0x7805,0x7810,0x780F,0x780E, + 0x7809,0x7803,0x7813,0x794A,0x794C,0x794B,0x7945,0x7944, + 0x79D5,0x79CD,0x79CF,0x79D6,0x79CE,0x7A80, + }, + {0x7A7E,0x7AD1,0x7B00,0x7B01,0x7C7A,0x7C78,0x7C79,0x7C7F, + 0x7C80,0x7C81,0x7D03,0x7D08,0x7D01,0x7F58,0x7F91,0x7F8D, + 0x7FBE,0x8007,0x800E,0x800F,0x8014,0x8037,0x80D8,0x80C7, + 0x80E0,0x80D1,0x80C8,0x80C2,0x80D0,0x80C5,0x80E3,0x80D9, + 0x80DC,0x80CA,0x80D5,0x80C9,0x80CF,0x80D7,0x80E6,0x80CD, + 0x81FF,0x8221,0x8294,0x82D9,0x82FE,0x82F9,0x8307,0x82E8, + 0x8300,0x82D5,0x833A,0x82EB,0x82D6,0x82F4,0x82EC,0x82E1, + 0x82F2,0x82F5,0x830C,0x82FB,0x82F6,0x82F0,0x82EA,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x82E4,0x82E0,0x82FA,0x82F3,0x82ED,0x8677,0x8674,0x867C, + 0x8673,0x8841,0x884E,0x8867,0x886A,0x8869,0x89D3,0x8A04, + 0x8A07,0x8D72,0x8FE3,0x8FE1,0x8FEE,0x8FE0,0x90F1,0x90BD, + 0x90BF,0x90D5,0x90C5,0x90BE,0x90C7,0x90CB,0x90C8,0x91D4, + 0x91D3,0x9654,0x964F,0x9651,0x9653,0x964A,0x964E,0x501E, + 0x5005,0x5007,0x5013,0x5022,0x5030,0x501B,0x4FF5,0x4FF4, + 0x5033,0x5037,0x502C,0x4FF6,0x4FF7,0x5017,0x501C,0x5020, + 0x5027,0x5035,0x502F,0x5031,0x500E,0x515A,0x5194,0x5193, + 0x51CA,0x51C4,0x51C5,0x51C8,0x51CE,0x5261,0x525A,0x5252, + 0x525E,0x525F,0x5255,0x5262,0x52CD,0x530E,0x539E,0x5526, + 0x54E2,0x5517,0x5512,0x54E7,0x54F3,0x54E4,0x551A,0x54FF, + 0x5504,0x5508,0x54EB,0x5511,0x5505,0x54F1, + }, + {0x550A,0x54FB,0x54F7,0x54F8,0x54E0,0x550E,0x5503,0x550B, + 0x5701,0x5702,0x57CC,0x5832,0x57D5,0x57D2,0x57BA,0x57C6, + 0x57BD,0x57BC,0x57B8,0x57B6,0x57BF,0x57C7,0x57D0,0x57B9, + 0x57C1,0x590E,0x594A,0x5A19,0x5A16,0x5A2D,0x5A2E,0x5A15, + 0x5A0F,0x5A17,0x5A0A,0x5A1E,0x5A33,0x5B6C,0x5BA7,0x5BAD, + 0x5BAC,0x5C03,0x5C56,0x5C54,0x5CEC,0x5CFF,0x5CEE,0x5CF1, + 0x5CF7,0x5D00,0x5CF9,0x5E29,0x5E28,0x5EA8,0x5EAE,0x5EAA, + 0x5EAC,0x5F33,0x5F30,0x5F67,0x605D,0x605A,0x6067,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x6041,0x60A2,0x6088,0x6080,0x6092,0x6081,0x609D,0x6083, + 0x6095,0x609B,0x6097,0x6087,0x609C,0x608E,0x6219,0x6246, + 0x62F2,0x6310,0x6356,0x632C,0x6344,0x6345,0x6336,0x6343, + 0x63E4,0x6339,0x634B,0x634A,0x633C,0x6329,0x6341,0x6334, + 0x6358,0x6354,0x6359,0x632D,0x6347,0x6333,0x635A,0x6351, + 0x6338,0x6357,0x6340,0x6348,0x654A,0x6546,0x65C6,0x65C3, + 0x65C4,0x65C2,0x664A,0x665F,0x6647,0x6651,0x6712,0x6713, + 0x681F,0x681A,0x6849,0x6832,0x6833,0x683B,0x684B,0x684F, + 0x6816,0x6831,0x681C,0x6835,0x682B,0x682D,0x682F,0x684E, + 0x6844,0x6834,0x681D,0x6812,0x6814,0x6826,0x6828,0x682E, + 0x684D,0x683A,0x6825,0x6820,0x6B2C,0x6B2F,0x6B2D,0x6B31, + 0x6B34,0x6B6D,0x8082,0x6B88,0x6BE6,0x6BE4, + }, + {0x6BE8,0x6BE3,0x6BE2,0x6BE7,0x6C25,0x6D7A,0x6D63,0x6D64, + 0x6D76,0x6D0D,0x6D61,0x6D92,0x6D58,0x6D62,0x6D6D,0x6D6F, + 0x6D91,0x6D8D,0x6DEF,0x6D7F,0x6D86,0x6D5E,0x6D67,0x6D60, + 0x6D97,0x6D70,0x6D7C,0x6D5F,0x6D82,0x6D98,0x6D2F,0x6D68, + 0x6D8B,0x6D7E,0x6D80,0x6D84,0x6D16,0x6D83,0x6D7B,0x6D7D, + 0x6D75,0x6D90,0x70DC,0x70D3,0x70D1,0x70DD,0x70CB,0x7F39, + 0x70E2,0x70D7,0x70D2,0x70DE,0x70E0,0x70D4,0x70CD,0x70C5, + 0x70C6,0x70C7,0x70DA,0x70CE,0x70E1,0x7242,0x7278,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x7277,0x7276,0x7300,0x72FA,0x72F4,0x72FE,0x72F6,0x72F3, + 0x72FB,0x7301,0x73D3,0x73D9,0x73E5,0x73D6,0x73BC,0x73E7, + 0x73E3,0x73E9,0x73DC,0x73D2,0x73DB,0x73D4,0x73DD,0x73DA, + 0x73D7,0x73D8,0x73E8,0x74DE,0x74DF,0x74F4,0x74F5,0x7521, + 0x755B,0x755F,0x75B0,0x75C1,0x75BB,0x75C4,0x75C0,0x75BF, + 0x75B6,0x75BA,0x768A,0x76C9,0x771D,0x771B,0x7710,0x7713, + 0x7712,0x7723,0x7711,0x7715,0x7719,0x771A,0x7722,0x7727, + 0x7823,0x782C,0x7822,0x7835,0x782F,0x7828,0x782E,0x782B, + 0x7821,0x7829,0x7833,0x782A,0x7831,0x7954,0x795B,0x794F, + 0x795C,0x7953,0x7952,0x7951,0x79EB,0x79EC,0x79E0,0x79EE, + 0x79ED,0x79EA,0x79DC,0x79DE,0x79DD,0x7A86,0x7A89,0x7A85, + 0x7A8B,0x7A8C,0x7A8A,0x7A87,0x7AD8,0x7B10, + }, + {0x7B04,0x7B13,0x7B05,0x7B0F,0x7B08,0x7B0A,0x7B0E,0x7B09, + 0x7B12,0x7C84,0x7C91,0x7C8A,0x7C8C,0x7C88,0x7C8D,0x7C85, + 0x7D1E,0x7D1D,0x7D11,0x7D0E,0x7D18,0x7D16,0x7D13,0x7D1F, + 0x7D12,0x7D0F,0x7D0C,0x7F5C,0x7F61,0x7F5E,0x7F60,0x7F5D, + 0x7F5B,0x7F96,0x7F92,0x7FC3,0x7FC2,0x7FC0,0x8016,0x803E, + 0x8039,0x80FA,0x80F2,0x80F9,0x80F5,0x8101,0x80FB,0x8100, + 0x8201,0x822F,0x8225,0x8333,0x832D,0x8344,0x8319,0x8351, + 0x8325,0x8356,0x833F,0x8341,0x8326,0x831C,0x8322,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x8342,0x834E,0x831B,0x832A,0x8308,0x833C,0x834D,0x8316, + 0x8324,0x8320,0x8337,0x832F,0x8329,0x8347,0x8345,0x834C, + 0x8353,0x831E,0x832C,0x834B,0x8327,0x8348,0x8653,0x8652, + 0x86A2,0x86A8,0x8696,0x868D,0x8691,0x869E,0x8687,0x8697, + 0x8686,0x868B,0x869A,0x8685,0x86A5,0x8699,0x86A1,0x86A7, + 0x8695,0x8698,0x868E,0x869D,0x8690,0x8694,0x8843,0x8844, + 0x886D,0x8875,0x8876,0x8872,0x8880,0x8871,0x887F,0x886F, + 0x8883,0x887E,0x8874,0x887C,0x8A12,0x8C47,0x8C57,0x8C7B, + 0x8CA4,0x8CA3,0x8D76,0x8D78,0x8DB5,0x8DB7,0x8DB6,0x8ED1, + 0x8ED3,0x8FFE,0x8FF5,0x9002,0x8FFF,0x8FFB,0x9004,0x8FFC, + 0x8FF6,0x90D6,0x90E0,0x90D9,0x90DA,0x90E3,0x90DF,0x90E5, + 0x90D8,0x90DB,0x90D7,0x90DC,0x90E4,0x9150, + }, + {0x914E,0x914F,0x91D5,0x91E2,0x91DA,0x965C,0x965F,0x96BC, + 0x98E3,0x9ADF,0x9B2F,0x4E7F,0x5070,0x506A,0x5061,0x505E, + 0x5060,0x5053,0x504B,0x505D,0x5072,0x5048,0x504D,0x5041, + 0x505B,0x504A,0x5062,0x5015,0x5045,0x505F,0x5069,0x506B, + 0x5063,0x5064,0x5046,0x5040,0x506E,0x5073,0x5057,0x5051, + 0x51D0,0x526B,0x526D,0x526C,0x526E,0x52D6,0x52D3,0x532D, + 0x539C,0x5575,0x5576,0x553C,0x554D,0x5550,0x5534,0x552A, + 0x5551,0x5562,0x5536,0x5535,0x5530,0x5552,0x5545,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x550C,0x5532,0x5565,0x554E,0x5539,0x5548,0x552D,0x553B, + 0x5540,0x554B,0x570A,0x5707,0x57FB,0x5814,0x57E2,0x57F6, + 0x57DC,0x57F4,0x5800,0x57ED,0x57FD,0x5808,0x57F8,0x580B, + 0x57F3,0x57CF,0x5807,0x57EE,0x57E3,0x57F2,0x57E5,0x57EC, + 0x57E1,0x580E,0x57FC,0x5810,0x57E7,0x5801,0x580C,0x57F1, + 0x57E9,0x57F0,0x580D,0x5804,0x595C,0x5A60,0x5A58,0x5A55, + 0x5A67,0x5A5E,0x5A38,0x5A35,0x5A6D,0x5A50,0x5A5F,0x5A65, + 0x5A6C,0x5A53,0x5A64,0x5A57,0x5A43,0x5A5D,0x5A52,0x5A44, + 0x5A5B,0x5A48,0x5A8E,0x5A3E,0x5A4D,0x5A39,0x5A4C,0x5A70, + 0x5A69,0x5A47,0x5A51,0x5A56,0x5A42,0x5A5C,0x5B72,0x5B6E, + 0x5BC1,0x5BC0,0x5C59,0x5D1E,0x5D0B,0x5D1D,0x5D1A,0x5D20, + 0x5D0C,0x5D28,0x5D0D,0x5D26,0x5D25,0x5D0F, + }, + {0x5D30,0x5D12,0x5D23,0x5D1F,0x5D2E,0x5E3E,0x5E34,0x5EB1, + 0x5EB4,0x5EB9,0x5EB2,0x5EB3,0x5F36,0x5F38,0x5F9B,0x5F96, + 0x5F9F,0x608A,0x6090,0x6086,0x60BE,0x60B0,0x60BA,0x60D3, + 0x60D4,0x60CF,0x60E4,0x60D9,0x60DD,0x60C8,0x60B1,0x60DB, + 0x60B7,0x60CA,0x60BF,0x60C3,0x60CD,0x60C0,0x6332,0x6365, + 0x638A,0x6382,0x637D,0x63BD,0x639E,0x63AD,0x639D,0x6397, + 0x63AB,0x638E,0x636F,0x6387,0x6390,0x636E,0x63AF,0x6375, + 0x639C,0x636D,0x63AE,0x637C,0x63A4,0x633B,0x639F,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x6378,0x6385,0x6381,0x6391,0x638D,0x6370,0x6553,0x65CD, + 0x6665,0x6661,0x665B,0x6659,0x665C,0x6662,0x6718,0x6879, + 0x6887,0x6890,0x689C,0x686D,0x686E,0x68AE,0x68AB,0x6956, + 0x686F,0x68A3,0x68AC,0x68A9,0x6875,0x6874,0x68B2,0x688F, + 0x6877,0x6892,0x687C,0x686B,0x6872,0x68AA,0x6880,0x6871, + 0x687E,0x689B,0x6896,0x688B,0x68A0,0x6889,0x68A4,0x6878, + 0x687B,0x6891,0x688C,0x688A,0x687D,0x6B36,0x6B33,0x6B37, + 0x6B38,0x6B91,0x6B8F,0x6B8D,0x6B8E,0x6B8C,0x6C2A,0x6DC0, + 0x6DAB,0x6DB4,0x6DB3,0x6E74,0x6DAC,0x6DE9,0x6DE2,0x6DB7, + 0x6DF6,0x6DD4,0x6E00,0x6DC8,0x6DE0,0x6DDF,0x6DD6,0x6DBE, + 0x6DE5,0x6DDC,0x6DDD,0x6DDB,0x6DF4,0x6DCA,0x6DBD,0x6DED, + 0x6DF0,0x6DBA,0x6DD5,0x6DC2,0x6DCF,0x6DC9, + }, + {0x6DD0,0x6DF2,0x6DD3,0x6DFD,0x6DD7,0x6DCD,0x6DE3,0x6DBB, + 0x70FA,0x710D,0x70F7,0x7117,0x70F4,0x710C,0x70F0,0x7104, + 0x70F3,0x7110,0x70FC,0x70FF,0x7106,0x7113,0x7100,0x70F8, + 0x70F6,0x710B,0x7102,0x710E,0x727E,0x727B,0x727C,0x727F, + 0x731D,0x7317,0x7307,0x7311,0x7318,0x730A,0x7308,0x72FF, + 0x730F,0x731E,0x7388,0x73F6,0x73F8,0x73F5,0x7404,0x7401, + 0x73FD,0x7407,0x7400,0x73FA,0x73FC,0x73FF,0x740C,0x740B, + 0x73F4,0x7408,0x7564,0x7563,0x75CE,0x75D2,0x75CF,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x75CB,0x75CC,0x75D1,0x75D0,0x768F,0x7689,0x76D3,0x7739, + 0x772F,0x772D,0x7731,0x7732,0x7734,0x7733,0x773D,0x7725, + 0x773B,0x7735,0x7848,0x7852,0x7849,0x784D,0x784A,0x784C, + 0x7826,0x7845,0x7850,0x7964,0x7967,0x7969,0x796A,0x7963, + 0x796B,0x7961,0x79BB,0x79FA,0x79F8,0x79F6,0x79F7,0x7A8F, + 0x7A94,0x7A90,0x7B35,0x7B47,0x7B34,0x7B25,0x7B30,0x7B22, + 0x7B24,0x7B33,0x7B18,0x7B2A,0x7B1D,0x7B31,0x7B2B,0x7B2D, + 0x7B2F,0x7B32,0x7B38,0x7B1A,0x7B23,0x7C94,0x7C98,0x7C96, + 0x7CA3,0x7D35,0x7D3D,0x7D38,0x7D36,0x7D3A,0x7D45,0x7D2C, + 0x7D29,0x7D41,0x7D47,0x7D3E,0x7D3F,0x7D4A,0x7D3B,0x7D28, + 0x7F63,0x7F95,0x7F9C,0x7F9D,0x7F9B,0x7FCA,0x7FCB,0x7FCD, + 0x7FD0,0x7FD1,0x7FC7,0x7FCF,0x7FC9,0x801F, + }, + {0x801E,0x801B,0x8047,0x8043,0x8048,0x8118,0x8125,0x8119, + 0x811B,0x812D,0x811F,0x812C,0x811E,0x8121,0x8115,0x8127, + 0x811D,0x8122,0x8211,0x8238,0x8233,0x823A,0x8234,0x8232, + 0x8274,0x8390,0x83A3,0x83A8,0x838D,0x837A,0x8373,0x83A4, + 0x8374,0x838F,0x8381,0x8395,0x8399,0x8375,0x8394,0x83A9, + 0x837D,0x8383,0x838C,0x839D,0x839B,0x83AA,0x838B,0x837E, + 0x83A5,0x83AF,0x8388,0x8397,0x83B0,0x837F,0x83A6,0x8387, + 0x83AE,0x8376,0x839A,0x8659,0x8656,0x86BF,0x86B7,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x86C2,0x86C1,0x86C5,0x86BA,0x86B0,0x86C8,0x86B9,0x86B3, + 0x86B8,0x86CC,0x86B4,0x86BB,0x86BC,0x86C3,0x86BD,0x86BE, + 0x8852,0x8889,0x8895,0x88A8,0x88A2,0x88AA,0x889A,0x8891, + 0x88A1,0x889F,0x8898,0x88A7,0x8899,0x889B,0x8897,0x88A4, + 0x88AC,0x888C,0x8893,0x888E,0x8982,0x89D6,0x89D9,0x89D5, + 0x8A30,0x8A27,0x8A2C,0x8A1E,0x8C39,0x8C3B,0x8C5C,0x8C5D, + 0x8C7D,0x8CA5,0x8D7D,0x8D7B,0x8D79,0x8DBC,0x8DC2,0x8DB9, + 0x8DBF,0x8DC1,0x8ED8,0x8EDE,0x8EDD,0x8EDC,0x8ED7,0x8EE0, + 0x8EE1,0x9024,0x900B,0x9011,0x901C,0x900C,0x9021,0x90EF, + 0x90EA,0x90F0,0x90F4,0x90F2,0x90F3,0x90D4,0x90EB,0x90EC, + 0x90E9,0x9156,0x9158,0x915A,0x9153,0x9155,0x91EC,0x91F4, + 0x91F1,0x91F3,0x91F8,0x91E4,0x91F9,0x91EA, + }, + {0x91EB,0x91F7,0x91E8,0x91EE,0x957A,0x9586,0x9588,0x967C, + 0x966D,0x966B,0x9671,0x966F,0x96BF,0x976A,0x9804,0x98E5, + 0x9997,0x509B,0x5095,0x5094,0x509E,0x508B,0x50A3,0x5083, + 0x508C,0x508E,0x509D,0x5068,0x509C,0x5092,0x5082,0x5087, + 0x515F,0x51D4,0x5312,0x5311,0x53A4,0x53A7,0x5591,0x55A8, + 0x55A5,0x55AD,0x5577,0x5645,0x55A2,0x5593,0x5588,0x558F, + 0x55B5,0x5581,0x55A3,0x5592,0x55A4,0x557D,0x558C,0x55A6, + 0x557F,0x5595,0x55A1,0x558E,0x570C,0x5829,0x5837,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x5819,0x581E,0x5827,0x5823,0x5828,0x57F5,0x5848,0x5825, + 0x581C,0x581B,0x5833,0x583F,0x5836,0x582E,0x5839,0x5838, + 0x582D,0x582C,0x583B,0x5961,0x5AAF,0x5A94,0x5A9F,0x5A7A, + 0x5AA2,0x5A9E,0x5A78,0x5AA6,0x5A7C,0x5AA5,0x5AAC,0x5A95, + 0x5AAE,0x5A37,0x5A84,0x5A8A,0x5A97,0x5A83,0x5A8B,0x5AA9, + 0x5A7B,0x5A7D,0x5A8C,0x5A9C,0x5A8F,0x5A93,0x5A9D,0x5BEA, + 0x5BCD,0x5BCB,0x5BD4,0x5BD1,0x5BCA,0x5BCE,0x5C0C,0x5C30, + 0x5D37,0x5D43,0x5D6B,0x5D41,0x5D4B,0x5D3F,0x5D35,0x5D51, + 0x5D4E,0x5D55,0x5D33,0x5D3A,0x5D52,0x5D3D,0x5D31,0x5D59, + 0x5D42,0x5D39,0x5D49,0x5D38,0x5D3C,0x5D32,0x5D36,0x5D40, + 0x5D45,0x5E44,0x5E41,0x5F58,0x5FA6,0x5FA5,0x5FAB,0x60C9, + 0x60B9,0x60CC,0x60E2,0x60CE,0x60C4,0x6114, + }, + {0x60F2,0x610A,0x6116,0x6105,0x60F5,0x6113,0x60F8,0x60FC, + 0x60FE,0x60C1,0x6103,0x6118,0x611D,0x6110,0x60FF,0x6104, + 0x610B,0x624A,0x6394,0x63B1,0x63B0,0x63CE,0x63E5,0x63E8, + 0x63EF,0x63C3,0x649D,0x63F3,0x63CA,0x63E0,0x63F6,0x63D5, + 0x63F2,0x63F5,0x6461,0x63DF,0x63BE,0x63DD,0x63DC,0x63C4, + 0x63D8,0x63D3,0x63C2,0x63C7,0x63CC,0x63CB,0x63C8,0x63F0, + 0x63D7,0x63D9,0x6532,0x6567,0x656A,0x6564,0x655C,0x6568, + 0x6565,0x658C,0x659D,0x659E,0x65AE,0x65D0,0x65D2,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x667C,0x666C,0x667B,0x6680,0x6671,0x6679,0x666A,0x6672, + 0x6701,0x690C,0x68D3,0x6904,0x68DC,0x692A,0x68EC,0x68EA, + 0x68F1,0x690F,0x68D6,0x68F7,0x68EB,0x68E4,0x68F6,0x6913, + 0x6910,0x68F3,0x68E1,0x6907,0x68CC,0x6908,0x6970,0x68B4, + 0x6911,0x68EF,0x68C6,0x6914,0x68F8,0x68D0,0x68FD,0x68FC, + 0x68E8,0x690B,0x690A,0x6917,0x68CE,0x68C8,0x68DD,0x68DE, + 0x68E6,0x68F4,0x68D1,0x6906,0x68D4,0x68E9,0x6915,0x6925, + 0x68C7,0x6B39,0x6B3B,0x6B3F,0x6B3C,0x6B94,0x6B97,0x6B99, + 0x6B95,0x6BBD,0x6BF0,0x6BF2,0x6BF3,0x6C30,0x6DFC,0x6E46, + 0x6E47,0x6E1F,0x6E49,0x6E88,0x6E3C,0x6E3D,0x6E45,0x6E62, + 0x6E2B,0x6E3F,0x6E41,0x6E5D,0x6E73,0x6E1C,0x6E33,0x6E4B, + 0x6E40,0x6E51,0x6E3B,0x6E03,0x6E2E,0x6E5E, + }, + {0x6E68,0x6E5C,0x6E61,0x6E31,0x6E28,0x6E60,0x6E71,0x6E6B, + 0x6E39,0x6E22,0x6E30,0x6E53,0x6E65,0x6E27,0x6E78,0x6E64, + 0x6E77,0x6E55,0x6E79,0x6E52,0x6E66,0x6E35,0x6E36,0x6E5A, + 0x7120,0x711E,0x712F,0x70FB,0x712E,0x7131,0x7123,0x7125, + 0x7122,0x7132,0x711F,0x7128,0x713A,0x711B,0x724B,0x725A, + 0x7288,0x7289,0x7286,0x7285,0x728B,0x7312,0x730B,0x7330, + 0x7322,0x7331,0x7333,0x7327,0x7332,0x732D,0x7326,0x7323, + 0x7335,0x730C,0x742E,0x742C,0x7430,0x742B,0x7416,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x741A,0x7421,0x742D,0x7431,0x7424,0x7423,0x741D,0x7429, + 0x7420,0x7432,0x74FB,0x752F,0x756F,0x756C,0x75E7,0x75DA, + 0x75E1,0x75E6,0x75DD,0x75DF,0x75E4,0x75D7,0x7695,0x7692, + 0x76DA,0x7746,0x7747,0x7744,0x774D,0x7745,0x774A,0x774E, + 0x774B,0x774C,0x77DE,0x77EC,0x7860,0x7864,0x7865,0x785C, + 0x786D,0x7871,0x786A,0x786E,0x7870,0x7869,0x7868,0x785E, + 0x7862,0x7974,0x7973,0x7972,0x7970,0x7A02,0x7A0A,0x7A03, + 0x7A0C,0x7A04,0x7A99,0x7AE6,0x7AE4,0x7B4A,0x7B3B,0x7B44, + 0x7B48,0x7B4C,0x7B4E,0x7B40,0x7B58,0x7B45,0x7CA2,0x7C9E, + 0x7CA8,0x7CA1,0x7D58,0x7D6F,0x7D63,0x7D53,0x7D56,0x7D67, + 0x7D6A,0x7D4F,0x7D6D,0x7D5C,0x7D6B,0x7D52,0x7D54,0x7D69, + 0x7D51,0x7D5F,0x7D4E,0x7F3E,0x7F3F,0x7F65, + }, + {0x7F66,0x7FA2,0x7FA0,0x7FA1,0x7FD7,0x8051,0x804F,0x8050, + 0x80FE,0x80D4,0x8143,0x814A,0x8152,0x814F,0x8147,0x813D, + 0x814D,0x813A,0x81E6,0x81EE,0x81F7,0x81F8,0x81F9,0x8204, + 0x823C,0x823D,0x823F,0x8275,0x833B,0x83CF,0x83F9,0x8423, + 0x83C0,0x83E8,0x8412,0x83E7,0x83E4,0x83FC,0x83F6,0x8410, + 0x83C6,0x83C8,0x83EB,0x83E3,0x83BF,0x8401,0x83DD,0x83E5, + 0x83D8,0x83FF,0x83E1,0x83CB,0x83CE,0x83D6,0x83F5,0x83C9, + 0x8409,0x840F,0x83DE,0x8411,0x8406,0x83C2,0x83F3,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x83D5,0x83FA,0x83C7,0x83D1,0x83EA,0x8413,0x83C3,0x83EC, + 0x83EE,0x83C4,0x83FB,0x83D7,0x83E2,0x841B,0x83DB,0x83FE, + 0x86D8,0x86E2,0x86E6,0x86D3,0x86E3,0x86DA,0x86EA,0x86DD, + 0x86EB,0x86DC,0x86EC,0x86E9,0x86D7,0x86E8,0x86D1,0x8848, + 0x8856,0x8855,0x88BA,0x88D7,0x88B9,0x88B8,0x88C0,0x88BE, + 0x88B6,0x88BC,0x88B7,0x88BD,0x88B2,0x8901,0x88C9,0x8995, + 0x8998,0x8997,0x89DD,0x89DA,0x89DB,0x8A4E,0x8A4D,0x8A39, + 0x8A59,0x8A40,0x8A57,0x8A58,0x8A44,0x8A45,0x8A52,0x8A48, + 0x8A51,0x8A4A,0x8A4C,0x8A4F,0x8C5F,0x8C81,0x8C80,0x8CBA, + 0x8CBE,0x8CB0,0x8CB9,0x8CB5,0x8D84,0x8D80,0x8D89,0x8DD8, + 0x8DD3,0x8DCD,0x8DC7,0x8DD6,0x8DDC,0x8DCF,0x8DD5,0x8DD9, + 0x8DC8,0x8DD7,0x8DC5,0x8EEF,0x8EF7,0x8EFA, + }, + {0x8EF9,0x8EE6,0x8EEE,0x8EE5,0x8EF5,0x8EE7,0x8EE8,0x8EF6, + 0x8EEB,0x8EF1,0x8EEC,0x8EF4,0x8EE9,0x902D,0x9034,0x902F, + 0x9106,0x912C,0x9104,0x90FF,0x90FC,0x9108,0x90F9,0x90FB, + 0x9101,0x9100,0x9107,0x9105,0x9103,0x9161,0x9164,0x915F, + 0x9162,0x9160,0x9201,0x920A,0x9225,0x9203,0x921A,0x9226, + 0x920F,0x920C,0x9200,0x9212,0x91FF,0x91FD,0x9206,0x9204, + 0x9227,0x9202,0x921C,0x9224,0x9219,0x9217,0x9205,0x9216, + 0x957B,0x958D,0x958C,0x9590,0x9687,0x967E,0x9688,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x9689,0x9683,0x9680,0x96C2,0x96C8,0x96C3,0x96F1,0x96F0, + 0x976C,0x9770,0x976E,0x9807,0x98A9,0x98EB,0x9CE6,0x9EF9, + 0x4E83,0x4E84,0x4EB6,0x50BD,0x50BF,0x50C6,0x50AE,0x50C4, + 0x50CA,0x50B4,0x50C8,0x50C2,0x50B0,0x50C1,0x50BA,0x50B1, + 0x50CB,0x50C9,0x50B6,0x50B8,0x51D7,0x527A,0x5278,0x527B, + 0x527C,0x55C3,0x55DB,0x55CC,0x55D0,0x55CB,0x55CA,0x55DD, + 0x55C0,0x55D4,0x55C4,0x55E9,0x55BF,0x55D2,0x558D,0x55CF, + 0x55D5,0x55E2,0x55D6,0x55C8,0x55F2,0x55CD,0x55D9,0x55C2, + 0x5714,0x5853,0x5868,0x5864,0x584F,0x584D,0x5849,0x586F, + 0x5855,0x584E,0x585D,0x5859,0x5865,0x585B,0x583D,0x5863, + 0x5871,0x58FC,0x5AC7,0x5AC4,0x5ACB,0x5ABA,0x5AB8,0x5AB1, + 0x5AB5,0x5AB0,0x5ABF,0x5AC8,0x5ABB,0x5AC6, + }, + {0x5AB7,0x5AC0,0x5ACA,0x5AB4,0x5AB6,0x5ACD,0x5AB9,0x5A90, + 0x5BD6,0x5BD8,0x5BD9,0x5C1F,0x5C33,0x5D71,0x5D63,0x5D4A, + 0x5D65,0x5D72,0x5D6C,0x5D5E,0x5D68,0x5D67,0x5D62,0x5DF0, + 0x5E4F,0x5E4E,0x5E4A,0x5E4D,0x5E4B,0x5EC5,0x5ECC,0x5EC6, + 0x5ECB,0x5EC7,0x5F40,0x5FAF,0x5FAD,0x60F7,0x6149,0x614A, + 0x612B,0x6145,0x6136,0x6132,0x612E,0x6146,0x612F,0x614F, + 0x6129,0x6140,0x6220,0x9168,0x6223,0x6225,0x6224,0x63C5, + 0x63F1,0x63EB,0x6410,0x6412,0x6409,0x6420,0x6424,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x6433,0x6443,0x641F,0x6415,0x6418,0x6439,0x6437,0x6422, + 0x6423,0x640C,0x6426,0x6430,0x6428,0x6441,0x6435,0x642F, + 0x640A,0x641A,0x6440,0x6425,0x6427,0x640B,0x63E7,0x641B, + 0x642E,0x6421,0x640E,0x656F,0x6592,0x65D3,0x6686,0x668C, + 0x6695,0x6690,0x668B,0x668A,0x6699,0x6694,0x6678,0x6720, + 0x6966,0x695F,0x6938,0x694E,0x6962,0x6971,0x693F,0x6945, + 0x696A,0x6939,0x6942,0x6957,0x6959,0x697A,0x6948,0x6949, + 0x6935,0x696C,0x6933,0x693D,0x6965,0x68F0,0x6978,0x6934, + 0x6969,0x6940,0x696F,0x6944,0x6976,0x6958,0x6941,0x6974, + 0x694C,0x693B,0x694B,0x6937,0x695C,0x694F,0x6951,0x6932, + 0x6952,0x692F,0x697B,0x693C,0x6B46,0x6B45,0x6B43,0x6B42, + 0x6B48,0x6B41,0x6B9B,0xFA0D,0x6BFB,0x6BFC, + }, + {0x6BF9,0x6BF7,0x6BF8,0x6E9B,0x6ED6,0x6EC8,0x6E8F,0x6EC0, + 0x6E9F,0x6E93,0x6E94,0x6EA0,0x6EB1,0x6EB9,0x6EC6,0x6ED2, + 0x6EBD,0x6EC1,0x6E9E,0x6EC9,0x6EB7,0x6EB0,0x6ECD,0x6EA6, + 0x6ECF,0x6EB2,0x6EBE,0x6EC3,0x6EDC,0x6ED8,0x6E99,0x6E92, + 0x6E8E,0x6E8D,0x6EA4,0x6EA1,0x6EBF,0x6EB3,0x6ED0,0x6ECA, + 0x6E97,0x6EAE,0x6EA3,0x7147,0x7154,0x7152,0x7163,0x7160, + 0x7141,0x715D,0x7162,0x7172,0x7178,0x716A,0x7161,0x7142, + 0x7158,0x7143,0x714B,0x7170,0x715F,0x7150,0x7153,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x7144,0x714D,0x715A,0x724F,0x728D,0x728C,0x7291,0x7290, + 0x728E,0x733C,0x7342,0x733B,0x733A,0x7340,0x734A,0x7349, + 0x7444,0x744A,0x744B,0x7452,0x7451,0x7457,0x7440,0x744F, + 0x7450,0x744E,0x7442,0x7446,0x744D,0x7454,0x74E1,0x74FF, + 0x74FE,0x74FD,0x751D,0x7579,0x7577,0x6983,0x75EF,0x760F, + 0x7603,0x75F7,0x75FE,0x75FC,0x75F9,0x75F8,0x7610,0x75FB, + 0x75F6,0x75ED,0x75F5,0x75FD,0x7699,0x76B5,0x76DD,0x7755, + 0x775F,0x7760,0x7752,0x7756,0x775A,0x7769,0x7767,0x7754, + 0x7759,0x776D,0x77E0,0x7887,0x789A,0x7894,0x788F,0x7884, + 0x7895,0x7885,0x7886,0x78A1,0x7883,0x7879,0x7899,0x7880, + 0x7896,0x787B,0x797C,0x7982,0x797D,0x7979,0x7A11,0x7A18, + 0x7A19,0x7A12,0x7A17,0x7A15,0x7A22,0x7A13, + }, + {0x7A1B,0x7A10,0x7AA3,0x7AA2,0x7A9E,0x7AEB,0x7B66,0x7B64, + 0x7B6D,0x7B74,0x7B69,0x7B72,0x7B65,0x7B73,0x7B71,0x7B70, + 0x7B61,0x7B78,0x7B76,0x7B63,0x7CB2,0x7CB4,0x7CAF,0x7D88, + 0x7D86,0x7D80,0x7D8D,0x7D7F,0x7D85,0x7D7A,0x7D8E,0x7D7B, + 0x7D83,0x7D7C,0x7D8C,0x7D94,0x7D84,0x7D7D,0x7D92,0x7F6D, + 0x7F6B,0x7F67,0x7F68,0x7F6C,0x7FA6,0x7FA5,0x7FA7,0x7FDB, + 0x7FDC,0x8021,0x8164,0x8160,0x8177,0x815C,0x8169,0x815B, + 0x8162,0x8172,0x6721,0x815E,0x8176,0x8167,0x816F,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x8144,0x8161,0x821D,0x8249,0x8244,0x8240,0x8242,0x8245, + 0x84F1,0x843F,0x8456,0x8476,0x8479,0x848F,0x848D,0x8465, + 0x8451,0x8440,0x8486,0x8467,0x8430,0x844D,0x847D,0x845A, + 0x8459,0x8474,0x8473,0x845D,0x8507,0x845E,0x8437,0x843A, + 0x8434,0x847A,0x8443,0x8478,0x8432,0x8445,0x8429,0x83D9, + 0x844B,0x842F,0x8442,0x842D,0x845F,0x8470,0x8439,0x844E, + 0x844C,0x8452,0x846F,0x84C5,0x848E,0x843B,0x8447,0x8436, + 0x8433,0x8468,0x847E,0x8444,0x842B,0x8460,0x8454,0x846E, + 0x8450,0x870B,0x8704,0x86F7,0x870C,0x86FA,0x86D6,0x86F5, + 0x874D,0x86F8,0x870E,0x8709,0x8701,0x86F6,0x870D,0x8705, + 0x88D6,0x88CB,0x88CD,0x88CE,0x88DE,0x88DB,0x88DA,0x88CC, + 0x88D0,0x8985,0x899B,0x89DF,0x89E5,0x89E4, + }, + {0x89E1,0x89E0,0x89E2,0x89DC,0x89E6,0x8A76,0x8A86,0x8A7F, + 0x8A61,0x8A3F,0x8A77,0x8A82,0x8A84,0x8A75,0x8A83,0x8A81, + 0x8A74,0x8A7A,0x8C3C,0x8C4B,0x8C4A,0x8C65,0x8C64,0x8C66, + 0x8C86,0x8C84,0x8C85,0x8CCC,0x8D68,0x8D69,0x8D91,0x8D8C, + 0x8D8E,0x8D8F,0x8D8D,0x8D93,0x8D94,0x8D90,0x8D92,0x8DF0, + 0x8DE0,0x8DEC,0x8DF1,0x8DEE,0x8DD0,0x8DE9,0x8DE3,0x8DE2, + 0x8DE7,0x8DF2,0x8DEB,0x8DF4,0x8F06,0x8EFF,0x8F01,0x8F00, + 0x8F05,0x8F07,0x8F08,0x8F02,0x8F0B,0x9052,0x903F,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x9044,0x9049,0x903D,0x9110,0x910D,0x910F,0x9111,0x9116, + 0x9114,0x910B,0x910E,0x916E,0x916F,0x9248,0x9252,0x9230, + 0x923A,0x9266,0x9233,0x9265,0x925E,0x9283,0x922E,0x924A, + 0x9246,0x926D,0x926C,0x924F,0x9260,0x9267,0x926F,0x9236, + 0x9261,0x9270,0x9231,0x9254,0x9263,0x9250,0x9272,0x924E, + 0x9253,0x924C,0x9256,0x9232,0x959F,0x959C,0x959E,0x959B, + 0x9692,0x9693,0x9691,0x9697,0x96CE,0x96FA,0x96FD,0x96F8, + 0x96F5,0x9773,0x9777,0x9778,0x9772,0x980F,0x980D,0x980E, + 0x98AC,0x98F6,0x98F9,0x99AF,0x99B2,0x99B0,0x99B5,0x9AAD, + 0x9AAB,0x9B5B,0x9CEA,0x9CED,0x9CE7,0x9E80,0x9EFD,0x50E6, + 0x50D4,0x50D7,0x50E8,0x50F3,0x50DB,0x50EA,0x50DD,0x50E4, + 0x50D3,0x50EC,0x50F0,0x50EF,0x50E3,0x50E0, + }, + {0x51D8,0x5280,0x5281,0x52E9,0x52EB,0x5330,0x53AC,0x5627, + 0x5615,0x560C,0x5612,0x55FC,0x560F,0x561C,0x5601,0x5613, + 0x5602,0x55FA,0x561D,0x5604,0x55FF,0x55F9,0x5889,0x587C, + 0x5890,0x5898,0x5886,0x5881,0x587F,0x5874,0x588B,0x587A, + 0x5887,0x5891,0x588E,0x5876,0x5882,0x5888,0x587B,0x5894, + 0x588F,0x58FE,0x596B,0x5ADC,0x5AEE,0x5AE5,0x5AD5,0x5AEA, + 0x5ADA,0x5AED,0x5AEB,0x5AF3,0x5AE2,0x5AE0,0x5ADB,0x5AEC, + 0x5ADE,0x5ADD,0x5AD9,0x5AE8,0x5ADF,0x5B77,0x5BE0,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x5BE3,0x5C63,0x5D82,0x5D80,0x5D7D,0x5D86,0x5D7A,0x5D81, + 0x5D77,0x5D8A,0x5D89,0x5D88,0x5D7E,0x5D7C,0x5D8D,0x5D79, + 0x5D7F,0x5E58,0x5E59,0x5E53,0x5ED8,0x5ED1,0x5ED7,0x5ECE, + 0x5EDC,0x5ED5,0x5ED9,0x5ED2,0x5ED4,0x5F44,0x5F43,0x5F6F, + 0x5FB6,0x612C,0x6128,0x6141,0x615E,0x6171,0x6173,0x6152, + 0x6153,0x6172,0x616C,0x6180,0x6174,0x6154,0x617A,0x615B, + 0x6165,0x613B,0x616A,0x6161,0x6156,0x6229,0x6227,0x622B, + 0x642B,0x644D,0x645B,0x645D,0x6474,0x6476,0x6472,0x6473, + 0x647D,0x6475,0x6466,0x64A6,0x644E,0x6482,0x645E,0x645C, + 0x644B,0x6453,0x6460,0x6450,0x647F,0x643F,0x646C,0x646B, + 0x6459,0x6465,0x6477,0x6573,0x65A0,0x66A1,0x66A0,0x669F, + 0x6705,0x6704,0x6722,0x69B1,0x69B6,0x69C9, + }, + {0x69A0,0x69CE,0x6996,0x69B0,0x69AC,0x69BC,0x6991,0x6999, + 0x698E,0x69A7,0x698D,0x69A9,0x69BE,0x69AF,0x69BF,0x69C4, + 0x69BD,0x69A4,0x69D4,0x69B9,0x69CA,0x699A,0x69CF,0x69B3, + 0x6993,0x69AA,0x69A1,0x699E,0x69D9,0x6997,0x6990,0x69C2, + 0x69B5,0x69A5,0x69C6,0x6B4A,0x6B4D,0x6B4B,0x6B9E,0x6B9F, + 0x6BA0,0x6BC3,0x6BC4,0x6BFE,0x6ECE,0x6EF5,0x6EF1,0x6F03, + 0x6F25,0x6EF8,0x6F37,0x6EFB,0x6F2E,0x6F09,0x6F4E,0x6F19, + 0x6F1A,0x6F27,0x6F18,0x6F3B,0x6F12,0x6EED,0x6F0A,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x6F36,0x6F73,0x6EF9,0x6EEE,0x6F2D,0x6F40,0x6F30,0x6F3C, + 0x6F35,0x6EEB,0x6F07,0x6F0E,0x6F43,0x6F05,0x6EFD,0x6EF6, + 0x6F39,0x6F1C,0x6EFC,0x6F3A,0x6F1F,0x6F0D,0x6F1E,0x6F08, + 0x6F21,0x7187,0x7190,0x7189,0x7180,0x7185,0x7182,0x718F, + 0x717B,0x7186,0x7181,0x7197,0x7244,0x7253,0x7297,0x7295, + 0x7293,0x7343,0x734D,0x7351,0x734C,0x7462,0x7473,0x7471, + 0x7475,0x7472,0x7467,0x746E,0x7500,0x7502,0x7503,0x757D, + 0x7590,0x7616,0x7608,0x760C,0x7615,0x7611,0x760A,0x7614, + 0x76B8,0x7781,0x777C,0x7785,0x7782,0x776E,0x7780,0x776F, + 0x777E,0x7783,0x78B2,0x78AA,0x78B4,0x78AD,0x78A8,0x787E, + 0x78AB,0x789E,0x78A5,0x78A0,0x78AC,0x78A2,0x78A4,0x7998, + 0x798A,0x798B,0x7996,0x7995,0x7994,0x7993, + }, + {0x7997,0x7988,0x7992,0x7990,0x7A2B,0x7A4A,0x7A30,0x7A2F, + 0x7A28,0x7A26,0x7AA8,0x7AAB,0x7AAC,0x7AEE,0x7B88,0x7B9C, + 0x7B8A,0x7B91,0x7B90,0x7B96,0x7B8D,0x7B8C,0x7B9B,0x7B8E, + 0x7B85,0x7B98,0x5284,0x7B99,0x7BA4,0x7B82,0x7CBB,0x7CBF, + 0x7CBC,0x7CBA,0x7DA7,0x7DB7,0x7DC2,0x7DA3,0x7DAA,0x7DC1, + 0x7DC0,0x7DC5,0x7D9D,0x7DCE,0x7DC4,0x7DC6,0x7DCB,0x7DCC, + 0x7DAF,0x7DB9,0x7D96,0x7DBC,0x7D9F,0x7DA6,0x7DAE,0x7DA9, + 0x7DA1,0x7DC9,0x7F73,0x7FE2,0x7FE3,0x7FE5,0x7FDE,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x8024,0x805D,0x805C,0x8189,0x8186,0x8183,0x8187,0x818D, + 0x818C,0x818B,0x8215,0x8497,0x84A4,0x84A1,0x849F,0x84BA, + 0x84CE,0x84C2,0x84AC,0x84AE,0x84AB,0x84B9,0x84B4,0x84C1, + 0x84CD,0x84AA,0x849A,0x84B1,0x84D0,0x849D,0x84A7,0x84BB, + 0x84A2,0x8494,0x84C7,0x84CC,0x849B,0x84A9,0x84AF,0x84A8, + 0x84D6,0x8498,0x84B6,0x84CF,0x84A0,0x84D7,0x84D4,0x84D2, + 0x84DB,0x84B0,0x8491,0x8661,0x8733,0x8723,0x8728,0x876B, + 0x8740,0x872E,0x871E,0x8721,0x8719,0x871B,0x8743,0x872C, + 0x8741,0x873E,0x8746,0x8720,0x8732,0x872A,0x872D,0x873C, + 0x8712,0x873A,0x8731,0x8735,0x8742,0x8726,0x8727,0x8738, + 0x8724,0x871A,0x8730,0x8711,0x88F7,0x88E7,0x88F1,0x88F2, + 0x88FA,0x88FE,0x88EE,0x88FC,0x88F6,0x88FB, + }, + {0x88F0,0x88EC,0x88EB,0x899D,0x89A1,0x899F,0x899E,0x89E9, + 0x89EB,0x89E8,0x8AAB,0x8A99,0x8A8B,0x8A92,0x8A8F,0x8A96, + 0x8C3D,0x8C68,0x8C69,0x8CD5,0x8CCF,0x8CD7,0x8D96,0x8E09, + 0x8E02,0x8DFF,0x8E0D,0x8DFD,0x8E0A,0x8E03,0x8E07,0x8E06, + 0x8E05,0x8DFE,0x8E00,0x8E04,0x8F10,0x8F11,0x8F0E,0x8F0D, + 0x9123,0x911C,0x9120,0x9122,0x911F,0x911D,0x911A,0x9124, + 0x9121,0x911B,0x917A,0x9172,0x9179,0x9173,0x92A5,0x92A4, + 0x9276,0x929B,0x927A,0x92A0,0x9294,0x92AA,0x928D,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x92A6,0x929A,0x92AB,0x9279,0x9297,0x927F,0x92A3,0x92EE, + 0x928E,0x9282,0x9295,0x92A2,0x927D,0x9288,0x92A1,0x928A, + 0x9286,0x928C,0x9299,0x92A7,0x927E,0x9287,0x92A9,0x929D, + 0x928B,0x922D,0x969E,0x96A1,0x96FF,0x9758,0x977D,0x977A, + 0x977E,0x9783,0x9780,0x9782,0x977B,0x9784,0x9781,0x977F, + 0x97CE,0x97CD,0x9816,0x98AD,0x98AE,0x9902,0x9900,0x9907, + 0x999D,0x999C,0x99C3,0x99B9,0x99BB,0x99BA,0x99C2,0x99BD, + 0x99C7,0x9AB1,0x9AE3,0x9AE7,0x9B3E,0x9B3F,0x9B60,0x9B61, + 0x9B5F,0x9CF1,0x9CF2,0x9CF5,0x9EA7,0x50FF,0x5103,0x5130, + 0x50F8,0x5106,0x5107,0x50F6,0x50FE,0x510B,0x510C,0x50FD, + 0x510A,0x528B,0x528C,0x52F1,0x52EF,0x5648,0x5642,0x564C, + 0x5635,0x5641,0x564A,0x5649,0x5646,0x5658, + }, + {0x565A,0x5640,0x5633,0x563D,0x562C,0x563E,0x5638,0x562A, + 0x563A,0x571A,0x58AB,0x589D,0x58B1,0x58A0,0x58A3,0x58AF, + 0x58AC,0x58A5,0x58A1,0x58FF,0x5AFF,0x5AF4,0x5AFD,0x5AF7, + 0x5AF6,0x5B03,0x5AF8,0x5B02,0x5AF9,0x5B01,0x5B07,0x5B05, + 0x5B0F,0x5C67,0x5D99,0x5D97,0x5D9F,0x5D92,0x5DA2,0x5D93, + 0x5D95,0x5DA0,0x5D9C,0x5DA1,0x5D9A,0x5D9E,0x5E69,0x5E5D, + 0x5E60,0x5E5C,0x7DF3,0x5EDB,0x5EDE,0x5EE1,0x5F49,0x5FB2, + 0x618B,0x6183,0x6179,0x61B1,0x61B0,0x61A2,0x6189,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x619B,0x6193,0x61AF,0x61AD,0x619F,0x6192,0x61AA,0x61A1, + 0x618D,0x6166,0x61B3,0x622D,0x646E,0x6470,0x6496,0x64A0, + 0x6485,0x6497,0x649C,0x648F,0x648B,0x648A,0x648C,0x64A3, + 0x649F,0x6468,0x64B1,0x6498,0x6576,0x657A,0x6579,0x657B, + 0x65B2,0x65B3,0x66B5,0x66B0,0x66A9,0x66B2,0x66B7,0x66AA, + 0x66AF,0x6A00,0x6A06,0x6A17,0x69E5,0x69F8,0x6A15,0x69F1, + 0x69E4,0x6A20,0x69FF,0x69EC,0x69E2,0x6A1B,0x6A1D,0x69FE, + 0x6A27,0x69F2,0x69EE,0x6A14,0x69F7,0x69E7,0x6A40,0x6A08, + 0x69E6,0x69FB,0x6A0D,0x69FC,0x69EB,0x6A09,0x6A04,0x6A18, + 0x6A25,0x6A0F,0x69F6,0x6A26,0x6A07,0x69F4,0x6A16,0x6B51, + 0x6BA5,0x6BA3,0x6BA2,0x6BA6,0x6C01,0x6C00,0x6BFF,0x6C02, + 0x6F41,0x6F26,0x6F7E,0x6F87,0x6FC6,0x6F92, + }, + {0x6F8D,0x6F89,0x6F8C,0x6F62,0x6F4F,0x6F85,0x6F5A,0x6F96, + 0x6F76,0x6F6C,0x6F82,0x6F55,0x6F72,0x6F52,0x6F50,0x6F57, + 0x6F94,0x6F93,0x6F5D,0x6F00,0x6F61,0x6F6B,0x6F7D,0x6F67, + 0x6F90,0x6F53,0x6F8B,0x6F69,0x6F7F,0x6F95,0x6F63,0x6F77, + 0x6F6A,0x6F7B,0x71B2,0x71AF,0x719B,0x71B0,0x71A0,0x719A, + 0x71A9,0x71B5,0x719D,0x71A5,0x719E,0x71A4,0x71A1,0x71AA, + 0x719C,0x71A7,0x71B3,0x7298,0x729A,0x7358,0x7352,0x735E, + 0x735F,0x7360,0x735D,0x735B,0x7361,0x735A,0x7359,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x7362,0x7487,0x7489,0x748A,0x7486,0x7481,0x747D,0x7485, + 0x7488,0x747C,0x7479,0x7508,0x7507,0x757E,0x7625,0x761E, + 0x7619,0x761D,0x761C,0x7623,0x761A,0x7628,0x761B,0x769C, + 0x769D,0x769E,0x769B,0x778D,0x778F,0x7789,0x7788,0x78CD, + 0x78BB,0x78CF,0x78CC,0x78D1,0x78CE,0x78D4,0x78C8,0x78C3, + 0x78C4,0x78C9,0x799A,0x79A1,0x79A0,0x799C,0x79A2,0x799B, + 0x6B76,0x7A39,0x7AB2,0x7AB4,0x7AB3,0x7BB7,0x7BCB,0x7BBE, + 0x7BAC,0x7BCE,0x7BAF,0x7BB9,0x7BCA,0x7BB5,0x7CC5,0x7CC8, + 0x7CCC,0x7CCB,0x7DF7,0x7DDB,0x7DEA,0x7DE7,0x7DD7,0x7DE1, + 0x7E03,0x7DFA,0x7DE6,0x7DF6,0x7DF1,0x7DF0,0x7DEE,0x7DDF, + 0x7F76,0x7FAC,0x7FB0,0x7FAD,0x7FED,0x7FEB,0x7FEA,0x7FEC, + 0x7FE6,0x7FE8,0x8064,0x8067,0x81A3,0x819F, + }, + {0x819E,0x8195,0x81A2,0x8199,0x8197,0x8216,0x824F,0x8253, + 0x8252,0x8250,0x824E,0x8251,0x8524,0x853B,0x850F,0x8500, + 0x8529,0x850E,0x8509,0x850D,0x851F,0x850A,0x8527,0x851C, + 0x84FB,0x852B,0x84FA,0x8508,0x850C,0x84F4,0x852A,0x84F2, + 0x8515,0x84F7,0x84EB,0x84F3,0x84FC,0x8512,0x84EA,0x84E9, + 0x8516,0x84FE,0x8528,0x851D,0x852E,0x8502,0x84FD,0x851E, + 0x84F6,0x8531,0x8526,0x84E7,0x84E8,0x84F0,0x84EF,0x84F9, + 0x8518,0x8520,0x8530,0x850B,0x8519,0x852F,0x8662,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x8756,0x8763,0x8764,0x8777,0x87E1,0x8773,0x8758,0x8754, + 0x875B,0x8752,0x8761,0x875A,0x8751,0x875E,0x876D,0x876A, + 0x8750,0x874E,0x875F,0x875D,0x876F,0x876C,0x877A,0x876E, + 0x875C,0x8765,0x874F,0x877B,0x8775,0x8762,0x8767,0x8769, + 0x885A,0x8905,0x890C,0x8914,0x890B,0x8917,0x8918,0x8919, + 0x8906,0x8916,0x8911,0x890E,0x8909,0x89A2,0x89A4,0x89A3, + 0x89ED,0x89F0,0x89EC,0x8ACF,0x8AC6,0x8AB8,0x8AD3,0x8AD1, + 0x8AD4,0x8AD5,0x8ABB,0x8AD7,0x8ABE,0x8AC0,0x8AC5,0x8AD8, + 0x8AC3,0x8ABA,0x8ABD,0x8AD9,0x8C3E,0x8C4D,0x8C8F,0x8CE5, + 0x8CDF,0x8CD9,0x8CE8,0x8CDA,0x8CDD,0x8CE7,0x8DA0,0x8D9C, + 0x8DA1,0x8D9B,0x8E20,0x8E23,0x8E25,0x8E24,0x8E2E,0x8E15, + 0x8E1B,0x8E16,0x8E11,0x8E19,0x8E26,0x8E27, + }, + {0x8E14,0x8E12,0x8E18,0x8E13,0x8E1C,0x8E17,0x8E1A,0x8F2C, + 0x8F24,0x8F18,0x8F1A,0x8F20,0x8F23,0x8F16,0x8F17,0x9073, + 0x9070,0x906F,0x9067,0x906B,0x912F,0x912B,0x9129,0x912A, + 0x9132,0x9126,0x912E,0x9185,0x9186,0x918A,0x9181,0x9182, + 0x9184,0x9180,0x92D0,0x92C3,0x92C4,0x92C0,0x92D9,0x92B6, + 0x92CF,0x92F1,0x92DF,0x92D8,0x92E9,0x92D7,0x92DD,0x92CC, + 0x92EF,0x92C2,0x92E8,0x92CA,0x92C8,0x92CE,0x92E6,0x92CD, + 0x92D5,0x92C9,0x92E0,0x92DE,0x92E7,0x92D1,0x92D3,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x92B5,0x92E1,0x92C6,0x92B4,0x957C,0x95AC,0x95AB,0x95AE, + 0x95B0,0x96A4,0x96A2,0x96D3,0x9705,0x9708,0x9702,0x975A, + 0x978A,0x978E,0x9788,0x97D0,0x97CF,0x981E,0x981D,0x9826, + 0x9829,0x9828,0x9820,0x981B,0x9827,0x98B2,0x9908,0x98FA, + 0x9911,0x9914,0x9916,0x9917,0x9915,0x99DC,0x99CD,0x99CF, + 0x99D3,0x99D4,0x99CE,0x99C9,0x99D6,0x99D8,0x99CB,0x99D7, + 0x99CC,0x9AB3,0x9AEC,0x9AEB,0x9AF3,0x9AF2,0x9AF1,0x9B46, + 0x9B43,0x9B67,0x9B74,0x9B71,0x9B66,0x9B76,0x9B75,0x9B70, + 0x9B68,0x9B64,0x9B6C,0x9CFC,0x9CFA,0x9CFD,0x9CFF,0x9CF7, + 0x9D07,0x9D00,0x9CF9,0x9CFB,0x9D08,0x9D05,0x9D04,0x9E83, + 0x9ED3,0x9F0F,0x9F10,0x511C,0x5113,0x5117,0x511A,0x5111, + 0x51DE,0x5334,0x53E1,0x5670,0x5660,0x566E, + }, + {0x5673,0x5666,0x5663,0x566D,0x5672,0x565E,0x5677,0x571C, + 0x571B,0x58C8,0x58BD,0x58C9,0x58BF,0x58BA,0x58C2,0x58BC, + 0x58C6,0x5B17,0x5B19,0x5B1B,0x5B21,0x5B14,0x5B13,0x5B10, + 0x5B16,0x5B28,0x5B1A,0x5B20,0x5B1E,0x5BEF,0x5DAC,0x5DB1, + 0x5DA9,0x5DA7,0x5DB5,0x5DB0,0x5DAE,0x5DAA,0x5DA8,0x5DB2, + 0x5DAD,0x5DAF,0x5DB4,0x5E67,0x5E68,0x5E66,0x5E6F,0x5EE9, + 0x5EE7,0x5EE6,0x5EE8,0x5EE5,0x5F4B,0x5FBC,0x619D,0x61A8, + 0x6196,0x61C5,0x61B4,0x61C6,0x61C1,0x61CC,0x61BA,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x61BF,0x61B8,0x618C,0x64D7,0x64D6,0x64D0,0x64CF,0x64C9, + 0x64BD,0x6489,0x64C3,0x64DB,0x64F3,0x64D9,0x6533,0x657F, + 0x657C,0x65A2,0x66C8,0x66BE,0x66C0,0x66CA,0x66CB,0x66CF, + 0x66BD,0x66BB,0x66BA,0x66CC,0x6723,0x6A34,0x6A66,0x6A49, + 0x6A67,0x6A32,0x6A68,0x6A3E,0x6A5D,0x6A6D,0x6A76,0x6A5B, + 0x6A51,0x6A28,0x6A5A,0x6A3B,0x6A3F,0x6A41,0x6A6A,0x6A64, + 0x6A50,0x6A4F,0x6A54,0x6A6F,0x6A69,0x6A60,0x6A3C,0x6A5E, + 0x6A56,0x6A55,0x6A4D,0x6A4E,0x6A46,0x6B55,0x6B54,0x6B56, + 0x6BA7,0x6BAA,0x6BAB,0x6BC8,0x6BC7,0x6C04,0x6C03,0x6C06, + 0x6FAD,0x6FCB,0x6FA3,0x6FC7,0x6FBC,0x6FCE,0x6FC8,0x6F5E, + 0x6FC4,0x6FBD,0x6F9E,0x6FCA,0x6FA8,0x7004,0x6FA5,0x6FAE, + 0x6FBA,0x6FAC,0x6FAA,0x6FCF,0x6FBF,0x6FB8, + }, + {0x6FA2,0x6FC9,0x6FAB,0x6FCD,0x6FAF,0x6FB2,0x6FB0,0x71C5, + 0x71C2,0x71BF,0x71B8,0x71D6,0x71C0,0x71C1,0x71CB,0x71D4, + 0x71CA,0x71C7,0x71CF,0x71BD,0x71D8,0x71BC,0x71C6,0x71DA, + 0x71DB,0x729D,0x729E,0x7369,0x7366,0x7367,0x736C,0x7365, + 0x736B,0x736A,0x747F,0x749A,0x74A0,0x7494,0x7492,0x7495, + 0x74A1,0x750B,0x7580,0x762F,0x762D,0x7631,0x763D,0x7633, + 0x763C,0x7635,0x7632,0x7630,0x76BB,0x76E6,0x779A,0x779D, + 0x77A1,0x779C,0x779B,0x77A2,0x77A3,0x7795,0x7799,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x7797,0x78DD,0x78E9,0x78E5,0x78EA,0x78DE,0x78E3,0x78DB, + 0x78E1,0x78E2,0x78ED,0x78DF,0x78E0,0x79A4,0x7A44,0x7A48, + 0x7A47,0x7AB6,0x7AB8,0x7AB5,0x7AB1,0x7AB7,0x7BDE,0x7BE3, + 0x7BE7,0x7BDD,0x7BD5,0x7BE5,0x7BDA,0x7BE8,0x7BF9,0x7BD4, + 0x7BEA,0x7BE2,0x7BDC,0x7BEB,0x7BD8,0x7BDF,0x7CD2,0x7CD4, + 0x7CD7,0x7CD0,0x7CD1,0x7E12,0x7E21,0x7E17,0x7E0C,0x7E1F, + 0x7E20,0x7E13,0x7E0E,0x7E1C,0x7E15,0x7E1A,0x7E22,0x7E0B, + 0x7E0F,0x7E16,0x7E0D,0x7E14,0x7E25,0x7E24,0x7F43,0x7F7B, + 0x7F7C,0x7F7A,0x7FB1,0x7FEF,0x802A,0x8029,0x806C,0x81B1, + 0x81A6,0x81AE,0x81B9,0x81B5,0x81AB,0x81B0,0x81AC,0x81B4, + 0x81B2,0x81B7,0x81A7,0x81F2,0x8255,0x8256,0x8257,0x8556, + 0x8545,0x856B,0x854D,0x8553,0x8561,0x8558, + }, + {0x8540,0x8546,0x8564,0x8541,0x8562,0x8544,0x8551,0x8547, + 0x8563,0x853E,0x855B,0x8571,0x854E,0x856E,0x8575,0x8555, + 0x8567,0x8560,0x858C,0x8566,0x855D,0x8554,0x8565,0x856C, + 0x8663,0x8665,0x8664,0x879B,0x878F,0x8797,0x8793,0x8792, + 0x8788,0x8781,0x8796,0x8798,0x8779,0x8787,0x87A3,0x8785, + 0x8790,0x8791,0x879D,0x8784,0x8794,0x879C,0x879A,0x8789, + 0x891E,0x8926,0x8930,0x892D,0x892E,0x8927,0x8931,0x8922, + 0x8929,0x8923,0x892F,0x892C,0x891F,0x89F1,0x8AE0,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x8AE2,0x8AF2,0x8AF4,0x8AF5,0x8ADD,0x8B14,0x8AE4,0x8ADF, + 0x8AF0,0x8AC8,0x8ADE,0x8AE1,0x8AE8,0x8AFF,0x8AEF,0x8AFB, + 0x8C91,0x8C92,0x8C90,0x8CF5,0x8CEE,0x8CF1,0x8CF0,0x8CF3, + 0x8D6C,0x8D6E,0x8DA5,0x8DA7,0x8E33,0x8E3E,0x8E38,0x8E40, + 0x8E45,0x8E36,0x8E3C,0x8E3D,0x8E41,0x8E30,0x8E3F,0x8EBD, + 0x8F36,0x8F2E,0x8F35,0x8F32,0x8F39,0x8F37,0x8F34,0x9076, + 0x9079,0x907B,0x9086,0x90FA,0x9133,0x9135,0x9136,0x9193, + 0x9190,0x9191,0x918D,0x918F,0x9327,0x931E,0x9308,0x931F, + 0x9306,0x930F,0x937A,0x9338,0x933C,0x931B,0x9323,0x9312, + 0x9301,0x9346,0x932D,0x930E,0x930D,0x92CB,0x931D,0x92FA, + 0x9325,0x9313,0x92F9,0x92F7,0x9334,0x9302,0x9324,0x92FF, + 0x9329,0x9339,0x9335,0x932A,0x9314,0x930C, + }, + {0x930B,0x92FE,0x9309,0x9300,0x92FB,0x9316,0x95BC,0x95CD, + 0x95BE,0x95B9,0x95BA,0x95B6,0x95BF,0x95B5,0x95BD,0x96A9, + 0x96D4,0x970B,0x9712,0x9710,0x9799,0x9797,0x9794,0x97F0, + 0x97F8,0x9835,0x982F,0x9832,0x9924,0x991F,0x9927,0x9929, + 0x999E,0x99EE,0x99EC,0x99E5,0x99E4,0x99F0,0x99E3,0x99EA, + 0x99E9,0x99E7,0x9AB9,0x9ABF,0x9AB4,0x9ABB,0x9AF6,0x9AFA, + 0x9AF9,0x9AF7,0x9B33,0x9B80,0x9B85,0x9B87,0x9B7C,0x9B7E, + 0x9B7B,0x9B82,0x9B93,0x9B92,0x9B90,0x9B7A,0x9B95,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x9B7D,0x9B88,0x9D25,0x9D17,0x9D20,0x9D1E,0x9D14,0x9D29, + 0x9D1D,0x9D18,0x9D22,0x9D10,0x9D19,0x9D1F,0x9E88,0x9E86, + 0x9E87,0x9EAE,0x9EAD,0x9ED5,0x9ED6,0x9EFA,0x9F12,0x9F3D, + 0x5126,0x5125,0x5122,0x5124,0x5120,0x5129,0x52F4,0x5693, + 0x568C,0x568D,0x5686,0x5684,0x5683,0x567E,0x5682,0x567F, + 0x5681,0x58D6,0x58D4,0x58CF,0x58D2,0x5B2D,0x5B25,0x5B32, + 0x5B23,0x5B2C,0x5B27,0x5B26,0x5B2F,0x5B2E,0x5B7B,0x5BF1, + 0x5BF2,0x5DB7,0x5E6C,0x5E6A,0x5FBE,0x5FBB,0x61C3,0x61B5, + 0x61BC,0x61E7,0x61E0,0x61E5,0x61E4,0x61E8,0x61DE,0x64EF, + 0x64E9,0x64E3,0x64EB,0x64E4,0x64E8,0x6581,0x6580,0x65B6, + 0x65DA,0x66D2,0x6A8D,0x6A96,0x6A81,0x6AA5,0x6A89,0x6A9F, + 0x6A9B,0x6AA1,0x6A9E,0x6A87,0x6A93,0x6A8E, + }, + {0x6A95,0x6A83,0x6AA8,0x6AA4,0x6A91,0x6A7F,0x6AA6,0x6A9A, + 0x6A85,0x6A8C,0x6A92,0x6B5B,0x6BAD,0x6C09,0x6FCC,0x6FA9, + 0x6FF4,0x6FD4,0x6FE3,0x6FDC,0x6FED,0x6FE7,0x6FE6,0x6FDE, + 0x6FF2,0x6FDD,0x6FE2,0x6FE8,0x71E1,0x71F1,0x71E8,0x71F2, + 0x71E4,0x71F0,0x71E2,0x7373,0x736E,0x736F,0x7497,0x74B2, + 0x74AB,0x7490,0x74AA,0x74AD,0x74B1,0x74A5,0x74AF,0x7510, + 0x7511,0x7512,0x750F,0x7584,0x7643,0x7648,0x7649,0x7647, + 0x76A4,0x76E9,0x77B5,0x77AB,0x77B2,0x77B7,0x77B6,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x77B4,0x77B1,0x77A8,0x77F0,0x78F3,0x78FD,0x7902,0x78FB, + 0x78FC,0x78F2,0x7905,0x78F9,0x78FE,0x7904,0x79AB,0x79A8, + 0x7A5C,0x7A5B,0x7A56,0x7A58,0x7A54,0x7A5A,0x7ABE,0x7AC0, + 0x7AC1,0x7C05,0x7C0F,0x7BF2,0x7C00,0x7BFF,0x7BFB,0x7C0E, + 0x7BF4,0x7C0B,0x7BF3,0x7C02,0x7C09,0x7C03,0x7C01,0x7BF8, + 0x7BFD,0x7C06,0x7BF0,0x7BF1,0x7C10,0x7C0A,0x7CE8,0x7E2D, + 0x7E3C,0x7E42,0x7E33,0x9848,0x7E38,0x7E2A,0x7E49,0x7E40, + 0x7E47,0x7E29,0x7E4C,0x7E30,0x7E3B,0x7E36,0x7E44,0x7E3A, + 0x7F45,0x7F7F,0x7F7E,0x7F7D,0x7FF4,0x7FF2,0x802C,0x81BB, + 0x81C4,0x81CC,0x81CA,0x81C5,0x81C7,0x81BC,0x81E9,0x825B, + 0x825A,0x825C,0x8583,0x8580,0x858F,0x85A7,0x8595,0x85A0, + 0x858B,0x85A3,0x857B,0x85A4,0x859A,0x859E, + }, + {0x8577,0x857C,0x8589,0x85A1,0x857A,0x8578,0x8557,0x858E, + 0x8596,0x8586,0x858D,0x8599,0x859D,0x8581,0x85A2,0x8582, + 0x8588,0x8585,0x8579,0x8576,0x8598,0x8590,0x859F,0x8668, + 0x87BE,0x87AA,0x87AD,0x87C5,0x87B0,0x87AC,0x87B9,0x87B5, + 0x87BC,0x87AE,0x87C9,0x87C3,0x87C2,0x87CC,0x87B7,0x87AF, + 0x87C4,0x87CA,0x87B4,0x87B6,0x87BF,0x87B8,0x87BD,0x87DE, + 0x87B2,0x8935,0x8933,0x893C,0x893E,0x8941,0x8952,0x8937, + 0x8942,0x89AD,0x89AF,0x89AE,0x89F2,0x89F3,0x8B1E,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x8B18,0x8B16,0x8B11,0x8B05,0x8B0B,0x8B22,0x8B0F,0x8B12, + 0x8B15,0x8B07,0x8B0D,0x8B08,0x8B06,0x8B1C,0x8B13,0x8B1A, + 0x8C4F,0x8C70,0x8C72,0x8C71,0x8C6F,0x8C95,0x8C94,0x8CF9, + 0x8D6F,0x8E4E,0x8E4D,0x8E53,0x8E50,0x8E4C,0x8E47,0x8F43, + 0x8F40,0x9085,0x907E,0x9138,0x919A,0x91A2,0x919B,0x9199, + 0x919F,0x91A1,0x919D,0x91A0,0x93A1,0x9383,0x93AF,0x9364, + 0x9356,0x9347,0x937C,0x9358,0x935C,0x9376,0x9349,0x9350, + 0x9351,0x9360,0x936D,0x938F,0x934C,0x936A,0x9379,0x9357, + 0x9355,0x9352,0x934F,0x9371,0x9377,0x937B,0x9361,0x935E, + 0x9363,0x9367,0x9380,0x934E,0x9359,0x95C7,0x95C0,0x95C9, + 0x95C3,0x95C5,0x95B7,0x96AE,0x96B0,0x96AC,0x9720,0x971F, + 0x9718,0x971D,0x9719,0x979A,0x97A1,0x979C, + }, + {0x979E,0x979D,0x97D5,0x97D4,0x97F1,0x9841,0x9844,0x984A, + 0x9849,0x9845,0x9843,0x9925,0x992B,0x992C,0x992A,0x9933, + 0x9932,0x992F,0x992D,0x9931,0x9930,0x9998,0x99A3,0x99A1, + 0x9A02,0x99FA,0x99F4,0x99F7,0x99F9,0x99F8,0x99F6,0x99FB, + 0x99FD,0x99FE,0x99FC,0x9A03,0x9ABE,0x9AFE,0x9AFD,0x9B01, + 0x9AFC,0x9B48,0x9B9A,0x9BA8,0x9B9E,0x9B9B,0x9BA6,0x9BA1, + 0x9BA5,0x9BA4,0x9B86,0x9BA2,0x9BA0,0x9BAF,0x9D33,0x9D41, + 0x9D67,0x9D36,0x9D2E,0x9D2F,0x9D31,0x9D38,0x9D30,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x9D45,0x9D42,0x9D43,0x9D3E,0x9D37,0x9D40,0x9D3D,0x7FF5, + 0x9D2D,0x9E8A,0x9E89,0x9E8D,0x9EB0,0x9EC8,0x9EDA,0x9EFB, + 0x9EFF,0x9F24,0x9F23,0x9F22,0x9F54,0x9FA0,0x5131,0x512D, + 0x512E,0x5698,0x569C,0x5697,0x569A,0x569D,0x5699,0x5970, + 0x5B3C,0x5C69,0x5C6A,0x5DC0,0x5E6D,0x5E6E,0x61D8,0x61DF, + 0x61ED,0x61EE,0x61F1,0x61EA,0x61F0,0x61EB,0x61D6,0x61E9, + 0x64FF,0x6504,0x64FD,0x64F8,0x6501,0x6503,0x64FC,0x6594, + 0x65DB,0x66DA,0x66DB,0x66D8,0x6AC5,0x6AB9,0x6ABD,0x6AE1, + 0x6AC6,0x6ABA,0x6AB6,0x6AB7,0x6AC7,0x6AB4,0x6AAD,0x6B5E, + 0x6BC9,0x6C0B,0x7007,0x700C,0x700D,0x7001,0x7005,0x7014, + 0x700E,0x6FFF,0x7000,0x6FFB,0x7026,0x6FFC,0x6FF7,0x700A, + 0x7201,0x71FF,0x71F9,0x7203,0x71FD,0x7376, + }, + {0x74B8,0x74C0,0x74B5,0x74C1,0x74BE,0x74B6,0x74BB,0x74C2, + 0x7514,0x7513,0x765C,0x7664,0x7659,0x7650,0x7653,0x7657, + 0x765A,0x76A6,0x76BD,0x76EC,0x77C2,0x77BA,0x78FF,0x790C, + 0x7913,0x7914,0x7909,0x7910,0x7912,0x7911,0x79AD,0x79AC, + 0x7A5F,0x7C1C,0x7C29,0x7C19,0x7C20,0x7C1F,0x7C2D,0x7C1D, + 0x7C26,0x7C28,0x7C22,0x7C25,0x7C30,0x7E5C,0x7E50,0x7E56, + 0x7E63,0x7E58,0x7E62,0x7E5F,0x7E51,0x7E60,0x7E57,0x7E53, + 0x7FB5,0x7FB3,0x7FF7,0x7FF8,0x8075,0x81D1,0x81D2,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x81D0,0x825F,0x825E,0x85B4,0x85C6,0x85C0,0x85C3,0x85C2, + 0x85B3,0x85B5,0x85BD,0x85C7,0x85C4,0x85BF,0x85CB,0x85CE, + 0x85C8,0x85C5,0x85B1,0x85B6,0x85D2,0x8624,0x85B8,0x85B7, + 0x85BE,0x8669,0x87E7,0x87E6,0x87E2,0x87DB,0x87EB,0x87EA, + 0x87E5,0x87DF,0x87F3,0x87E4,0x87D4,0x87DC,0x87D3,0x87ED, + 0x87D8,0x87E3,0x87A4,0x87D7,0x87D9,0x8801,0x87F4,0x87E8, + 0x87DD,0x8953,0x894B,0x894F,0x894C,0x8946,0x8950,0x8951, + 0x8949,0x8B2A,0x8B27,0x8B23,0x8B33,0x8B30,0x8B35,0x8B47, + 0x8B2F,0x8B3C,0x8B3E,0x8B31,0x8B25,0x8B37,0x8B26,0x8B36, + 0x8B2E,0x8B24,0x8B3B,0x8B3D,0x8B3A,0x8C42,0x8C75,0x8C99, + 0x8C98,0x8C97,0x8CFE,0x8D04,0x8D02,0x8D00,0x8E5C,0x8E62, + 0x8E60,0x8E57,0x8E56,0x8E5E,0x8E65,0x8E67, + }, + {0x8E5B,0x8E5A,0x8E61,0x8E5D,0x8E69,0x8E54,0x8F46,0x8F47, + 0x8F48,0x8F4B,0x9128,0x913A,0x913B,0x913E,0x91A8,0x91A5, + 0x91A7,0x91AF,0x91AA,0x93B5,0x938C,0x9392,0x93B7,0x939B, + 0x939D,0x9389,0x93A7,0x938E,0x93AA,0x939E,0x93A6,0x9395, + 0x9388,0x9399,0x939F,0x938D,0x93B1,0x9391,0x93B2,0x93A4, + 0x93A8,0x93B4,0x93A3,0x93A5,0x95D2,0x95D3,0x95D1,0x96B3, + 0x96D7,0x96DA,0x5DC2,0x96DF,0x96D8,0x96DD,0x9723,0x9722, + 0x9725,0x97AC,0x97AE,0x97A8,0x97AB,0x97A4,0x97AA,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x97A2,0x97A5,0x97D7,0x97D9,0x97D6,0x97D8,0x97FA,0x9850, + 0x9851,0x9852,0x98B8,0x9941,0x993C,0x993A,0x9A0F,0x9A0B, + 0x9A09,0x9A0D,0x9A04,0x9A11,0x9A0A,0x9A05,0x9A07,0x9A06, + 0x9AC0,0x9ADC,0x9B08,0x9B04,0x9B05,0x9B29,0x9B35,0x9B4A, + 0x9B4C,0x9B4B,0x9BC7,0x9BC6,0x9BC3,0x9BBF,0x9BC1,0x9BB5, + 0x9BB8,0x9BD3,0x9BB6,0x9BC4,0x9BB9,0x9BBD,0x9D5C,0x9D53, + 0x9D4F,0x9D4A,0x9D5B,0x9D4B,0x9D59,0x9D56,0x9D4C,0x9D57, + 0x9D52,0x9D54,0x9D5F,0x9D58,0x9D5A,0x9E8E,0x9E8C,0x9EDF, + 0x9F01,0x9F00,0x9F16,0x9F25,0x9F2B,0x9F2A,0x9F29,0x9F28, + 0x9F4C,0x9F55,0x5134,0x5135,0x5296,0x52F7,0x53B4,0x56AB, + 0x56AD,0x56A6,0x56A7,0x56AA,0x56AC,0x58DA,0x58DD,0x58DB, + 0x5912,0x5B3D,0x5B3E,0x5B3F,0x5DC3,0x5E70, + }, + {0x5FBF,0x61FB,0x6507,0x6510,0x650D,0x6509,0x650C,0x650E, + 0x6584,0x65DE,0x65DD,0x66DE,0x6AE7,0x6AE0,0x6ACC,0x6AD1, + 0x6AD9,0x6ACB,0x6ADF,0x6ADC,0x6AD0,0x6AEB,0x6ACF,0x6ACD, + 0x6ADE,0x6B60,0x6BB0,0x6C0C,0x7019,0x7027,0x7020,0x7016, + 0x702B,0x7021,0x7022,0x7023,0x7029,0x7017,0x7024,0x701C, + 0x702A,0x720C,0x720A,0x7207,0x7202,0x7205,0x72A5,0x72A6, + 0x72A4,0x72A3,0x72A1,0x74CB,0x74C5,0x74B7,0x74C3,0x7516, + 0x7660,0x77C9,0x77CA,0x77C4,0x77F1,0x791D,0x791B,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x7921,0x791C,0x7917,0x791E,0x79B0,0x7A67,0x7A68,0x7C33, + 0x7C3C,0x7C39,0x7C2C,0x7C3B,0x7CEC,0x7CEA,0x7E76,0x7E75, + 0x7E78,0x7E70,0x7E77,0x7E6F,0x7E7A,0x7E72,0x7E74,0x7E68, + 0x7F4B,0x7F4A,0x7F83,0x7F86,0x7FB7,0x7FFD,0x7FFE,0x8078, + 0x81D7,0x81D5,0x8264,0x8261,0x8263,0x85EB,0x85F1,0x85ED, + 0x85D9,0x85E1,0x85E8,0x85DA,0x85D7,0x85EC,0x85F2,0x85F8, + 0x85D8,0x85DF,0x85E3,0x85DC,0x85D1,0x85F0,0x85E6,0x85EF, + 0x85DE,0x85E2,0x8800,0x87FA,0x8803,0x87F6,0x87F7,0x8809, + 0x880C,0x880B,0x8806,0x87FC,0x8808,0x87FF,0x880A,0x8802, + 0x8962,0x895A,0x895B,0x8957,0x8961,0x895C,0x8958,0x895D, + 0x8959,0x8988,0x89B7,0x89B6,0x89F6,0x8B50,0x8B48,0x8B4A, + 0x8B40,0x8B53,0x8B56,0x8B54,0x8B4B,0x8B55, + }, + {0x8B51,0x8B42,0x8B52,0x8B57,0x8C43,0x8C77,0x8C76,0x8C9A, + 0x8D06,0x8D07,0x8D09,0x8DAC,0x8DAA,0x8DAD,0x8DAB,0x8E6D, + 0x8E78,0x8E73,0x8E6A,0x8E6F,0x8E7B,0x8EC2,0x8F52,0x8F51, + 0x8F4F,0x8F50,0x8F53,0x8FB4,0x9140,0x913F,0x91B0,0x91AD, + 0x93DE,0x93C7,0x93CF,0x93C2,0x93DA,0x93D0,0x93F9,0x93EC, + 0x93CC,0x93D9,0x93A9,0x93E6,0x93CA,0x93D4,0x93EE,0x93E3, + 0x93D5,0x93C4,0x93CE,0x93C0,0x93D2,0x93E7,0x957D,0x95DA, + 0x95DB,0x96E1,0x9729,0x972B,0x972C,0x9728,0x9726,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x97B3,0x97B7,0x97B6,0x97DD,0x97DE,0x97DF,0x985C,0x9859, + 0x985D,0x9857,0x98BF,0x98BD,0x98BB,0x98BE,0x9948,0x9947, + 0x9943,0x99A6,0x99A7,0x9A1A,0x9A15,0x9A25,0x9A1D,0x9A24, + 0x9A1B,0x9A22,0x9A20,0x9A27,0x9A23,0x9A1E,0x9A1C,0x9A14, + 0x9AC2,0x9B0B,0x9B0A,0x9B0E,0x9B0C,0x9B37,0x9BEA,0x9BEB, + 0x9BE0,0x9BDE,0x9BE4,0x9BE6,0x9BE2,0x9BF0,0x9BD4,0x9BD7, + 0x9BEC,0x9BDC,0x9BD9,0x9BE5,0x9BD5,0x9BE1,0x9BDA,0x9D77, + 0x9D81,0x9D8A,0x9D84,0x9D88,0x9D71,0x9D80,0x9D78,0x9D86, + 0x9D8B,0x9D8C,0x9D7D,0x9D6B,0x9D74,0x9D75,0x9D70,0x9D69, + 0x9D85,0x9D73,0x9D7B,0x9D82,0x9D6F,0x9D79,0x9D7F,0x9D87, + 0x9D68,0x9E94,0x9E91,0x9EC0,0x9EFC,0x9F2D,0x9F40,0x9F41, + 0x9F4D,0x9F56,0x9F57,0x9F58,0x5337,0x56B2, + }, + {0x56B5,0x56B3,0x58E3,0x5B45,0x5DC6,0x5DC7,0x5EEE,0x5EEF, + 0x5FC0,0x5FC1,0x61F9,0x6517,0x6516,0x6515,0x6513,0x65DF, + 0x66E8,0x66E3,0x66E4,0x6AF3,0x6AF0,0x6AEA,0x6AE8,0x6AF9, + 0x6AF1,0x6AEE,0x6AEF,0x703C,0x7035,0x702F,0x7037,0x7034, + 0x7031,0x7042,0x7038,0x703F,0x703A,0x7039,0x7040,0x703B, + 0x7033,0x7041,0x7213,0x7214,0x72A8,0x737D,0x737C,0x74BA, + 0x76AB,0x76AA,0x76BE,0x76ED,0x77CC,0x77CE,0x77CF,0x77CD, + 0x77F2,0x7925,0x7923,0x7927,0x7928,0x7924,0x7929,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x79B2,0x7A6E,0x7A6C,0x7A6D,0x7AF7,0x7C49,0x7C48,0x7C4A, + 0x7C47,0x7C45,0x7CEE,0x7E7B,0x7E7E,0x7E81,0x7E80,0x7FBA, + 0x7FFF,0x8079,0x81DB,0x81D9,0x820B,0x8268,0x8269,0x8622, + 0x85FF,0x8601,0x85FE,0x861B,0x8600,0x85F6,0x8604,0x8609, + 0x8605,0x860C,0x85FD,0x8819,0x8810,0x8811,0x8817,0x8813, + 0x8816,0x8963,0x8966,0x89B9,0x89F7,0x8B60,0x8B6A,0x8B5D, + 0x8B68,0x8B63,0x8B65,0x8B67,0x8B6D,0x8DAE,0x8E86,0x8E88, + 0x8E84,0x8F59,0x8F56,0x8F57,0x8F55,0x8F58,0x8F5A,0x908D, + 0x9143,0x9141,0x91B7,0x91B5,0x91B2,0x91B3,0x940B,0x9413, + 0x93FB,0x9420,0x940F,0x9414,0x93FE,0x9415,0x9410,0x9428, + 0x9419,0x940D,0x93F5,0x9400,0x93F7,0x9407,0x940E,0x9416, + 0x9412,0x93FA,0x9409,0x93F8,0x940A,0x93FF, + }, + {0x93FC,0x940C,0x93F6,0x9411,0x9406,0x95DE,0x95E0,0x95DF, + 0x972E,0x972F,0x97B9,0x97BB,0x97FD,0x97FE,0x9860,0x9862, + 0x9863,0x985F,0x98C1,0x98C2,0x9950,0x994E,0x9959,0x994C, + 0x994B,0x9953,0x9A32,0x9A34,0x9A31,0x9A2C,0x9A2A,0x9A36, + 0x9A29,0x9A2E,0x9A38,0x9A2D,0x9AC7,0x9ACA,0x9AC6,0x9B10, + 0x9B12,0x9B11,0x9C0B,0x9C08,0x9BF7,0x9C05,0x9C12,0x9BF8, + 0x9C40,0x9C07,0x9C0E,0x9C06,0x9C17,0x9C14,0x9C09,0x9D9F, + 0x9D99,0x9DA4,0x9D9D,0x9D92,0x9D98,0x9D90,0x9D9B,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x9DA0,0x9D94,0x9D9C,0x9DAA,0x9D97,0x9DA1,0x9D9A,0x9DA2, + 0x9DA8,0x9D9E,0x9DA3,0x9DBF,0x9DA9,0x9D96,0x9DA6,0x9DA7, + 0x9E99,0x9E9B,0x9E9A,0x9EE5,0x9EE4,0x9EE7,0x9EE6,0x9F30, + 0x9F2E,0x9F5B,0x9F60,0x9F5E,0x9F5D,0x9F59,0x9F91,0x513A, + 0x5139,0x5298,0x5297,0x56C3,0x56BD,0x56BE,0x5B48,0x5B47, + 0x5DCB,0x5DCF,0x5EF1,0x61FD,0x651B,0x6B02,0x6AFC,0x6B03, + 0x6AF8,0x6B00,0x7043,0x7044,0x704A,0x7048,0x7049,0x7045, + 0x7046,0x721D,0x721A,0x7219,0x737E,0x7517,0x766A,0x77D0, + 0x792D,0x7931,0x792F,0x7C54,0x7C53,0x7CF2,0x7E8A,0x7E87, + 0x7E88,0x7E8B,0x7E86,0x7E8D,0x7F4D,0x7FBB,0x8030,0x81DD, + 0x8618,0x862A,0x8626,0x861F,0x8623,0x861C,0x8619,0x8627, + 0x862E,0x8621,0x8620,0x8629,0x861E,0x8625, + }, + {0x8829,0x881D,0x881B,0x8820,0x8824,0x881C,0x882B,0x884A, + 0x896D,0x8969,0x896E,0x896B,0x89FA,0x8B79,0x8B78,0x8B45, + 0x8B7A,0x8B7B,0x8D10,0x8D14,0x8DAF,0x8E8E,0x8E8C,0x8F5E, + 0x8F5B,0x8F5D,0x9146,0x9144,0x9145,0x91B9,0x943F,0x943B, + 0x9436,0x9429,0x943D,0x943C,0x9430,0x9439,0x942A,0x9437, + 0x942C,0x9440,0x9431,0x95E5,0x95E4,0x95E3,0x9735,0x973A, + 0x97BF,0x97E1,0x9864,0x98C9,0x98C6,0x98C0,0x9958,0x9956, + 0x9A39,0x9A3D,0x9A46,0x9A44,0x9A42,0x9A41,0x9A3A,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x9A3F,0x9ACD,0x9B15,0x9B17,0x9B18,0x9B16,0x9B3A,0x9B52, + 0x9C2B,0x9C1D,0x9C1C,0x9C2C,0x9C23,0x9C28,0x9C29,0x9C24, + 0x9C21,0x9DB7,0x9DB6,0x9DBC,0x9DC1,0x9DC7,0x9DCA,0x9DCF, + 0x9DBE,0x9DC5,0x9DC3,0x9DBB,0x9DB5,0x9DCE,0x9DB9,0x9DBA, + 0x9DAC,0x9DC8,0x9DB1,0x9DAD,0x9DCC,0x9DB3,0x9DCD,0x9DB2, + 0x9E7A,0x9E9C,0x9EEB,0x9EEE,0x9EED,0x9F1B,0x9F18,0x9F1A, + 0x9F31,0x9F4E,0x9F65,0x9F64,0x9F92,0x4EB9,0x56C6,0x56C5, + 0x56CB,0x5971,0x5B4B,0x5B4C,0x5DD5,0x5DD1,0x5EF2,0x6521, + 0x6520,0x6526,0x6522,0x6B0B,0x6B08,0x6B09,0x6C0D,0x7055, + 0x7056,0x7057,0x7052,0x721E,0x721F,0x72A9,0x737F,0x74D8, + 0x74D5,0x74D9,0x74D7,0x766D,0x76AD,0x7935,0x79B4,0x7A70, + 0x7A71,0x7C57,0x7C5C,0x7C59,0x7C5B,0x7C5A, + }, + {0x7CF4,0x7CF1,0x7E91,0x7F4F,0x7F87,0x81DE,0x826B,0x8634, + 0x8635,0x8633,0x862C,0x8632,0x8636,0x882C,0x8828,0x8826, + 0x882A,0x8825,0x8971,0x89BF,0x89BE,0x89FB,0x8B7E,0x8B84, + 0x8B82,0x8B86,0x8B85,0x8B7F,0x8D15,0x8E95,0x8E94,0x8E9A, + 0x8E92,0x8E90,0x8E96,0x8E97,0x8F60,0x8F62,0x9147,0x944C, + 0x9450,0x944A,0x944B,0x944F,0x9447,0x9445,0x9448,0x9449, + 0x9446,0x973F,0x97E3,0x986A,0x9869,0x98CB,0x9954,0x995B, + 0x9A4E,0x9A53,0x9A54,0x9A4C,0x9A4F,0x9A48,0x9A4A,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x9A49,0x9A52,0x9A50,0x9AD0,0x9B19,0x9B2B,0x9B3B,0x9B56, + 0x9B55,0x9C46,0x9C48,0x9C3F,0x9C44,0x9C39,0x9C33,0x9C41, + 0x9C3C,0x9C37,0x9C34,0x9C32,0x9C3D,0x9C36,0x9DDB,0x9DD2, + 0x9DDE,0x9DDA,0x9DCB,0x9DD0,0x9DDC,0x9DD1,0x9DDF,0x9DE9, + 0x9DD9,0x9DD8,0x9DD6,0x9DF5,0x9DD5,0x9DDD,0x9EB6,0x9EF0, + 0x9F35,0x9F33,0x9F32,0x9F42,0x9F6B,0x9F95,0x9FA2,0x513D, + 0x5299,0x58E8,0x58E7,0x5972,0x5B4D,0x5DD8,0x882F,0x5F4F, + 0x6201,0x6203,0x6204,0x6529,0x6525,0x6596,0x66EB,0x6B11, + 0x6B12,0x6B0F,0x6BCA,0x705B,0x705A,0x7222,0x7382,0x7381, + 0x7383,0x7670,0x77D4,0x7C67,0x7C66,0x7E95,0x826C,0x863A, + 0x8640,0x8639,0x863C,0x8631,0x863B,0x863E,0x8830,0x8832, + 0x882E,0x8833,0x8976,0x8974,0x8973,0x89FE, + }, + {0x8B8C,0x8B8E,0x8B8B,0x8B88,0x8C45,0x8D19,0x8E98,0x8F64, + 0x8F63,0x91BC,0x9462,0x9455,0x945D,0x9457,0x945E,0x97C4, + 0x97C5,0x9800,0x9A56,0x9A59,0x9B1E,0x9B1F,0x9B20,0x9C52, + 0x9C58,0x9C50,0x9C4A,0x9C4D,0x9C4B,0x9C55,0x9C59,0x9C4C, + 0x9C4E,0x9DFB,0x9DF7,0x9DEF,0x9DE3,0x9DEB,0x9DF8,0x9DE4, + 0x9DF6,0x9DE1,0x9DEE,0x9DE6,0x9DF2,0x9DF0,0x9DE2,0x9DEC, + 0x9DF4,0x9DF3,0x9DE8,0x9DED,0x9EC2,0x9ED0,0x9EF2,0x9EF3, + 0x9F06,0x9F1C,0x9F38,0x9F37,0x9F36,0x9F43,0x9F4F,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x9F71,0x9F70,0x9F6E,0x9F6F,0x56D3,0x56CD,0x5B4E,0x5C6D, + 0x652D,0x66ED,0x66EE,0x6B13,0x705F,0x7061,0x705D,0x7060, + 0x7223,0x74DB,0x74E5,0x77D5,0x7938,0x79B7,0x79B6,0x7C6A, + 0x7E97,0x7F89,0x826D,0x8643,0x8838,0x8837,0x8835,0x884B, + 0x8B94,0x8B95,0x8E9E,0x8E9F,0x8EA0,0x8E9D,0x91BE,0x91BD, + 0x91C2,0x946B,0x9468,0x9469,0x96E5,0x9746,0x9743,0x9747, + 0x97C7,0x97E5,0x9A5E,0x9AD5,0x9B59,0x9C63,0x9C67,0x9C66, + 0x9C62,0x9C5E,0x9C60,0x9E02,0x9DFE,0x9E07,0x9E03,0x9E06, + 0x9E05,0x9E00,0x9E01,0x9E09,0x9DFF,0x9DFD,0x9E04,0x9EA0, + 0x9F1E,0x9F46,0x9F74,0x9F75,0x9F76,0x56D4,0x652E,0x65B8, + 0x6B18,0x6B19,0x6B17,0x6B1A,0x7062,0x7226,0x72AA,0x77D8, + 0x77D9,0x7939,0x7C69,0x7C6B,0x7CF6,0x7E9A, + }, + {0x7E98,0x7E9B,0x7E99,0x81E0,0x81E1,0x8646,0x8647,0x8648, + 0x8979,0x897A,0x897C,0x897B,0x89FF,0x8B98,0x8B99,0x8EA5, + 0x8EA4,0x8EA3,0x946E,0x946D,0x946F,0x9471,0x9473,0x9749, + 0x9872,0x995F,0x9C68,0x9C6E,0x9C6D,0x9E0B,0x9E0D,0x9E10, + 0x9E0F,0x9E12,0x9E11,0x9EA1,0x9EF5,0x9F09,0x9F47,0x9F78, + 0x9F7B,0x9F7A,0x9F79,0x571E,0x7066,0x7C6F,0x883C,0x8DB2, + 0x8EA6,0x91C3,0x9474,0x9478,0x9476,0x9475,0x9A60,0x9C74, + 0x9C73,0x9C71,0x9C75,0x9E14,0x9E13,0x9EF6,0x9F0A,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x9FA4,0x7068,0x7065,0x7CF7,0x866A,0x883E,0x883D,0x883F, + 0x8B9E,0x8C9C,0x8EA9,0x8EC9,0x974B,0x9873,0x9874,0x98CC, + 0x9961,0x99AB,0x9A64,0x9A66,0x9A67,0x9B24,0x9E15,0x9E17, + 0x9F48,0x6207,0x6B1E,0x7227,0x864C,0x8EA8,0x9482,0x9480, + 0x9481,0x9A69,0x9A68,0x9B2E,0x9E19,0x7229,0x864B,0x8B9F, + 0x9483,0x9C79,0x9EB7,0x7675,0x9A6B,0x9C7A,0x9E1D,0x7069, + 0x706A,0x9EA4,0x9F7E,0x9F49,0x9F98,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + }, + {0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + } +}; + +#endif /* RD_BIG5_TABLE_H */ diff --git a/patch_v2/reader_app/core/rdenc.c b/patch_v2/reader_app/core/rdenc.c index 6ab554d..afb9696 100644 --- a/patch_v2/reader_app/core/rdenc.c +++ b/patch_v2/reader_app/core/rdenc.c @@ -1,6 +1,7 @@ -/* rdenc.c — 文本编码检测 + GB18030→UTF-8 + UTF-8 工具(实现见 rdenc.h)。 */ +/* rdenc.c — 文本编码检测 + GB18030/Big5→UTF-8 + UTF-8 工具(实现见 rdenc.h)。 */ #include "rdenc.h" #include "gb18030_table.h" +#include "big5_table.h" #include #include @@ -39,6 +40,14 @@ static inline int gb_trail_ok(unsigned b) { return (b >= 0x40 && b <= 0x7E) || (b >= 0x80 && b <= 0xFE); } +static inline int gb_trail_q(unsigned b) +{ + return gb_trail_ok(b); +} +static inline int big5_trail_q(unsigned b) +{ + return (b >= 0x40 && b <= 0x7E) || (b >= 0xA1 && b <= 0xFE); +} static inline int gb_col(unsigned b) { /* 列 = b - 0x40 - (b >= 0x80);b 已在合法范围(不含 0x7F) */ @@ -128,16 +137,45 @@ int rd_gb18030_to_utf8(const uint8_t *in, size_t inlen, uint8_t *out, size_t out return RD_ENC_OK; } -/* ================= 编码检测 ================= */ -static inline int gb_trail_q(unsigned b) +/* ================= Big5 双字节 → UTF-8 ================= */ +int rd_big5_to_utf8(const uint8_t *in, size_t inlen, uint8_t *out, size_t outcap, size_t *outlen) { - return (b >= 0x40 && b <= 0x7E) || (b >= 0x80 && b <= 0xFE); -} -static inline int big5_trail_q(unsigned b) -{ - return (b >= 0x40 && b <= 0x7E) || (b >= 0xA1 && b <= 0xFE); + if (!in || !outlen) return RD_ENC_ERR_INVAL; + if (!out && outcap) return RD_ENC_ERR_INVAL; + size_t o = 0; + size_t i = 0; + while (i < inlen) { + uint8_t b = in[i]; + if (b < 0x80) { + if (o + 1 > outcap) { *outlen = o + 1; return RD_ENC_ERR_BUFSZ; } + out[o++] = b; i++; continue; + } + if (b >= 0x81 && b <= 0xFE && i + 1 < inlen) { + uint8_t t = in[i + 1]; + if (big5_trail_q(t)) { + uint32_t cp = big5_2byte[b - RD_B5_LEAD0][gb_col(t)]; + uint8_t tmp[4]; + int n; + if (cp == 0) { cp = 0xFFFD; } + n = enc_utf8(cp, tmp); + if (o + (size_t)n > outcap) { *outlen = o + (size_t)n; return RD_ENC_ERR_BUFSZ; } + memcpy(out + o, tmp, (size_t)n); o += (size_t)n; + i += 2; continue; + } + } + /* 非法首字节 / 尾字节不成对 → U+FFFD */ + { + uint8_t tmp[4]; int n = enc_utf8(0xFFFD, tmp); + if (o + (size_t)n > outcap) { *outlen = o + (size_t)n; return RD_ENC_ERR_BUFSZ; } + memcpy(out + o, tmp, (size_t)n); o += (size_t)n; + i++; continue; + } + } + *outlen = o; + return RD_ENC_OK; } +/* ================= 编码检测 ================= */ rd_enc_t rd_enc_detect(const uint8_t *buf, size_t len, rd_enc_stats *st) { if (st) { st->gb_pairs = st->big5_pairs = st->stray = 0; } @@ -156,33 +194,27 @@ rd_enc_t rd_enc_detect(const uint8_t *buf, size_t len, rd_enc_stats *st) return ascii ? RD_ENC_ASCII : RD_ENC_UTF8; } - /* 3) 双字节区段统计(GB18030 兼容 GBK;Big5 尾字节不同)。 - * GB 有效 = 码表 gb_2byte 查到非 0(GBK/GB18030 已映射码位); - * Big5 专属 = 结构上 Big5 合法、但对 GB 码表无效(Big5 独有码位)。 - * 二者比较更可靠(结构上每个 GBK 双字节对也是合法 Big5 结构,无法区分)。 */ - long gb = 0, big5 = 0, stray = 0; + /* 3) 双字节区段统计(GB18030 兼容 GBK)。 + * GB 有效 = 码表 gb_2byte 查到非 0(GBK/GB18030 已映射码位);stray = 无法成对字节。 + * R3 注:Big5 双字节区间是 GB18030 双字节区间的**严格子集**(Big5 尾字节 + * 0x40..0x7E|0xA1..0xFE ⊂ GB 尾字节 0x40..0x7E|0x80..0xFE;常见 GBK 简体字尾字节大量 + * 落在 0xA1..0xFE 与 Big5 重叠),故字节结构**无法可靠区分 Big5 与 GB18030**——盲判必然 + * 误伤(误把 GBK 判成 Big5 或反之)。自动检测统一兜底 GB18030(简体最常见、误伤代价 + * 最小);Big5 书由 UI 提供「编码切换」显式选中(对应正确解码走 rd_big5_to_utf8)。 */ + long gb = 0, stray = 0; for (size_t i = 0; i < len; ) { uint8_t b = buf[i]; if (b < 0x80) { i++; continue; } if (b >= 0x81 && b <= 0xFE && i + 1 < len) { uint8_t t = buf[i + 1]; - int gb_ok = gb_trail_q(t) && - gb_2byte[b - RD_GB_LEAD0][gb_col(t)] != 0; - int b5_ok = big5_trail_q(t); - if (gb_ok) gb++; - if (b5_ok && !gb_ok) big5++; /* Big5 专属:GB 码表无效 */ + if (gb_trail_q(t) && gb_2byte[b - RD_GB_LEAD0][gb_col(t)] != 0) gb++; i += 2; continue; } stray++; i++; } - if (st) { st->gb_pairs = gb; st->big5_pairs = big5; st->stray = stray; } + if (st) { st->gb_pairs = gb; st->big5_pairs = 0; st->stray = stray; } - /* 阈值(经验值):双字节对太少 → 乱码/不确定 → 兜底 GB18030(最常见中文编码)。 - * 注(READER_ENGINE_NOTES §3.5 风险):Big5 与 GB18030 双字节区大量重叠,码表有效性 - * 无法可靠区分(简体 GB2312 文本 trail 0xA1-0xFE 恰是 Big5 trail 区)。R1 双字节一律 - * 按 GB18030 兜底;Big5 经此检测也按 GB18030 试(v1 不转),真实区分需用户手动切编码 - * (列 R2)。 */ - if (gb + big5 < (long)(len / 5)) return RD_ENC_GB18030; + if (gb < (long)(len / 5)) return RD_ENC_GB18030; /* 双字节对太少 → 不确定 → GB18030 */ return RD_ENC_GB18030; } @@ -231,7 +263,8 @@ int rd_to_utf8(rd_enc_t enc, const uint8_t *in, size_t inlen, uint8_t *out, size memcpy(out, in + off, inlen - off); *outlen = inlen - off; return RD_ENC_OK; } - /* GB18030 / Big5(v1 按 GB18030 试) */ + if (enc == RD_ENC_BIG5) return rd_big5_to_utf8(in, inlen, out, outcap, outlen); + /* GB18030 兜底 */ return rd_gb18030_to_utf8(in, inlen, out, outcap, outlen); } diff --git a/patch_v2/reader_app/core/rdenc.h b/patch_v2/reader_app/core/rdenc.h index 53dffa3..ef41105 100644 --- a/patch_v2/reader_app/core/rdenc.h +++ b/patch_v2/reader_app/core/rdenc.h @@ -1,14 +1,19 @@ -/* rdenc.h — 阅读器文本编码检测 + GB18030→UTF-8 解码 + UTF-8 工具(纯 C,宿主可测)。 +/* rdenc.h — 阅读器文本编码检测 + GB18030/Big5→UTF-8 解码 + UTF-8 工具(纯 C,宿主可测)。 * * 对齐 devdocs/plan/READER_ENGINE_NOTES.md §3(zLibrary 轻量化:BOM + 严格 UTF-8 + 双字节区段统计, * 不抄 N-gram 马尔可夫): * - BOM 优先:FE FF=UTF-16BE、FF FE=UTF-16LE、EF BB BF=UTF-8; * - 无 BOM → 严格 UTF-8 校验(合法即 UTF-8;ASCII 属 UTF-8 子集); - * - 非 UTF-8 → GB18030(GBK 超集) / Big5 双字节区段统计(阈值经验值,见 READER_ENGINE_NOTES §3.3); - * - **R1 只做 GB18030→UTF-8 双字节映射**(uint16[126][190] 码表),4 字节扩展段 → U+FFFD; - * Big5 检测出来也按 GB18030 试(v1 不转 Big5,正文会乱但可读,注明)。 + * - 非 UTF-8 → GB18030(GBK 超集) 双字节映射(uint16[126][190] 码表);4 字节扩展段 → U+FFFD。 * - * 无 SDK 依赖;`#include "gb18030_table.h"`(生成物,见 tools/gen_gb18030_table.py)。 + * **Big5(R3)**:Big5 双字节区间是 GB18030 双字节区间的**严格子集**(Big5 尾字节 + * 0x40..0x7E|0xA1..0xFE ⊂ GB 尾字节 0x40..0x7E|0x80..0xFE,且常用 GBK 简体字尾字节 + * 大量落在 0xA1..0xFE 与 Big5 重叠),故**字节结构无法自动区分 Big5 与 GB18030**—— + * 盲判必然误伤。R3 方案:解码器支持 RD_ENC_BIG5(big5_2byte 码表),由 UI 提供编码 + * 切换(Auto→GB18030/Big5/UTF-8/UTF-16LE/BE),用户对繁体乱码书手动选 Big5 即正确 + * 渲染;自动检测默认 GB18030(简体最常见,误伤代价最小)。详见 READER_ENGINE_NOTES §3.5。 + * + * 无 SDK 依赖;`#include "gb18030_table.h"` 与 `"big5_table.h"`(生成物,见 tools/)。 */ #ifndef RDENC_H #define RDENC_H @@ -26,7 +31,7 @@ typedef enum { RD_ENC_UTF16LE, RD_ENC_UTF16BE, RD_ENC_GB18030, /* GBK 超集(解码器按 2 字节码表) */ - RD_ENC_BIG5, /* v1 检测但按 GB18030 解码 */ + RD_ENC_BIG5, /* Big5 → UTF-8(R3:big5_2byte 码表;需显式选中,不自动检测) */ } rd_enc_t; /* 检测统计(诊断 + 阈值用;值可为 0)。 */ @@ -49,6 +54,9 @@ int rd_utf8_validate(const uint8_t *buf, size_t len); #define RD_ENC_ERR_BUFSZ -2 /* out 不足;*outlen 已填所需 */ int rd_gb18030_to_utf8(const uint8_t *in, size_t inlen, uint8_t *out, size_t outcap, size_t *outlen); +/* Big5 双字节 → UTF-8(与 GB 版同构;Big5 非法双字节 → U+FFFD)。成功 0;输出不足 RD_ENC_ERR_BUFSZ。 */ +int rd_big5_to_utf8(const uint8_t *in, size_t inlen, uint8_t *out, size_t outcap, size_t *outlen); + /* UTF-8 → 码点数组(布局用;malloc 分配 *cps,调用方 free)。成功 0。 *n = 码点数。 * 非法序列按一个 U+FFFD 收(宽松);len 为输入字节长。 */ #define RD_UTF8_ERR_INVAL -1 @@ -59,7 +67,7 @@ int rd_utf8_to_cp(const uint8_t *utf8, size_t len, uint32_t **cps, size_t *n); int rd_cp_to_utf8(uint32_t cp, uint8_t out[4]); /* 通用:任意检测编码 → UTF-8(rdenc 已知的各编码)。UTF-16LE/BE 走简易 BOM 对齐 + 码点转换; - * Big5 v1 按 GB18030 双字节表转(会乱但可读,注明)。返回码同 rd_gb18030_to_utf8。 */ + * Big5 走 rd_big5_to_utf8。返回码同 rd_gb18030_to_utf8。 */ int rd_to_utf8(rd_enc_t enc, const uint8_t *in, size_t inlen, uint8_t *out, size_t outcap, size_t *outlen); #ifdef __cplusplus diff --git a/patch_v2/reader_app/core/rdsrc.c b/patch_v2/reader_app/core/rdsrc.c index d127346..cc49572 100644 --- a/patch_v2/reader_app/core/rdsrc.c +++ b/patch_v2/reader_app/core/rdsrc.c @@ -40,6 +40,8 @@ static int txt_open(rd_src *s) if (!m) return -6; s->impl = m; if (rdtxt_probe(s->path, &m->info) != 0) { txt_free(s); return -1; } + /* R3:显式编码覆盖(Big5 书)——探测只取 file_size/path,编码改用覆盖值 */ + if (s->enc_override_set) m->info.enc = s->enc_override; /* 建段表:一次扫描(off,len) */ rdtxt_reader *r = NULL; if (rdtxt_reader_open(s->path, &m->info, &r) != 0) { txt_free(s); return -2; } @@ -105,7 +107,7 @@ static int is_ext(const char *path, const char *ext) return strcasecmp(path + pl - el, ext) == 0; /* ext 含点(如 ".cbz") */ } -int rd_src_open(rd_src **out, const char *path) +static int src_open_common(rd_src **out, const char *path, const rd_enc_t *enc) { if (!out || !path) return -1; /* 读前 64 字节做 magic 探测 */ @@ -127,6 +129,7 @@ int rd_src_open(rd_src **out, const char *path) if (strlen(path) >= sizeof s->path) { free(s); return -2; } strcpy(s->path, path); s->fmt = pick->fmt; + if (enc) { s->enc_override = *enc; s->enc_override_set = 1; } int rc = pick->open(s); if (rc != 0) { s->vt = NULL; s->impl = NULL; rd_src_free(s); return rc; } s->vt = pick->vt; @@ -134,6 +137,16 @@ int rd_src_open(rd_src **out, const char *path) return 0; } +int rd_src_open(rd_src **out, const char *path) +{ + return src_open_common(out, path, NULL); +} + +int rd_src_open_with_enc(rd_src **out, const char *path, const rd_enc_t *enc) +{ + return src_open_common(out, path, enc); +} + void rd_src_free(rd_src *s) { if (!s) return; @@ -141,6 +154,14 @@ void rd_src_free(rd_src *s) free(s); } rd_fmt_t rd_src_fmt(const rd_src *s) { return s ? s->fmt : RD_FMT_TXT; } +int rd_src_txt_enc(const rd_src *s, rd_enc_t *out) +{ + if (!s || s->fmt != RD_FMT_TXT || !out) return -1; + txt_impl *m = (txt_impl *)s->impl; + if (!m) return -1; + *out = m->info.enc; + return 0; +} const char *rd_src_title(const rd_src *s) { return (s && s->title[0]) ? s->title : (s ? s->path : ""); } const char *rd_src_author(const rd_src *s) { return (s && s->author[0]) ? s->author : "佚名"; } const char *rd_src_cover(const rd_src *s) { return (s && s->cover[0]) ? s->cover : NULL; } diff --git a/patch_v2/reader_app/core/rdsrc.h b/patch_v2/reader_app/core/rdsrc.h index 1920160..83b3c21 100644 --- a/patch_v2/reader_app/core/rdsrc.h +++ b/patch_v2/reader_app/core/rdsrc.h @@ -13,6 +13,7 @@ #ifndef RDSRC_H #define RDSRC_H +#include "rdenc.h" #include #include @@ -38,9 +39,15 @@ typedef struct rd_toc_entry { /* 打开(按扩展名/magic 探测格式)。0=成功;负值=不支持/打不开。 */ int rd_src_open(rd_src **out, const char *path); + +/* 同 rd_src_open,但用 *enc 显式覆盖文本编码(TXT;ENC 为 NULL=自动探测)。 + * 非 TXT 格式忽略覆盖。R3 编码切换用(Big5 书显式选中)。 */ +int rd_src_open_with_enc(rd_src **out, const char *path, const rd_enc_t *enc); void rd_src_free(rd_src *s); rd_fmt_t rd_src_fmt(const rd_src *s); +/* TXT 有效文本编码(含覆盖);*out 填 rd_enc_t。非 TXT 返回 -1(XML/结构化固定 UTF-8 内部码)。 */ +int rd_src_txt_enc(const rd_src *s, rd_enc_t *out); const char *rd_src_title(const rd_src *s); const char *rd_src_author(const rd_src *s); const char *rd_src_cover(const rd_src *s); /* 封面 ref;无则 NULL */ diff --git a/patch_v2/reader_app/core/rdsrc_int.h b/patch_v2/reader_app/core/rdsrc_int.h index fd9453f..5af0758 100644 --- a/patch_v2/reader_app/core/rdsrc_int.h +++ b/patch_v2/reader_app/core/rdsrc_int.h @@ -4,6 +4,7 @@ #define RDSRC_INT_H #include "rdsrc.h" +#include "rdenc.h" #include #include @@ -29,6 +30,10 @@ struct rd_src { char author[160]; char cover[320]; void *impl; + /* R3:文本编码显式覆盖(rd_src_open_with_enc)。enc_override_set=1 时 TXT loader 用该编码; + * 非 TXT 格式(XML/结构化)固定 UTF-8 内部编码,忽略覆盖。 */ + rd_enc_t enc_override; + int enc_override_set; }; /* 注册一个格式(供 rdsrc.c 调度)。 */ diff --git a/patch_v2/reader_app/pack_reader_mpk.sh b/patch_v2/reader_app/pack_reader_mpk.sh new file mode 100755 index 0000000..97e1f3d --- /dev/null +++ b/patch_v2/reader_app/pack_reader_mpk.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# pack_reader_mpk.sh — 把 os.meow.reader 打成 .mpk(tar.gz),并把 LGPL 合规材料并入包内。 +# .mpk = app.json + reader 二进制 + icon.png + resources/licenses/{NOTICE,GPL-3.0.txt,LGPL-3.0.txt} +# (不含 data/cache;符合 APPS.md §3 与 mips-repo/mpk/README.md 的 .mpk 规范) +# +# 合规目的:让「接收者随包拿到」NOTICE + GPL-3.0 全文 + LGPL-3.0 全文, +# 满足 LGPL-3.0 §4(a)(显著通知)与 §4(b)(随 Combined Work 附 GPL + LGPL 两份文本)。 +# +# 用法: +# ./build_reader.sh # 先编出 reader 二进制(本脚本不重编) +# ./pack_reader_mpk.sh # → ./os.meow.reader.mpk(未签名) +# 签名(可选,正式分发:install 期验签,os.meow.* 无签拒装): +# ../meow_sign/meow_sign sign os.meow.reader.mpk "$HOME/Project/R3ProII/.keys/signing.key" +# +# 只做打包/sign 提示,不改阅读器功能逻辑。 +set -e +cd "$(dirname "$0")" +PKG="os.meow.reader" +MPK="$PKG.mpk" + +[ -f "reader" ] || { echo "找不到 ./reader —— 请先运行 ./build_reader.sh 编出二进制"; exit 1; } +[ -f "app.json" ] || { echo "找不到 ./app.json"; exit 1; } +[ -f "resources/licenses/NOTICE" ] || { echo "找不到 resources/licenses/NOTICE(合规材料缺失)"; exit 1; } + +STAGE="$(mktemp -d)" +trap 'rm -rf "$STAGE"' EXIT + +# 组装 MeowAPPs// 布局(.mpk 内相对路径,顶层不含包名目录) +cp app.json "$STAGE/" +cp icon.png "$STAGE/" 2>/dev/null || true +cp reader "$STAGE/" +mkdir -p "$STAGE/resources/licenses" +cp resources/licenses/NOTICE resources/licenses/GPL-3.0.txt resources/licenses/LGPL-3.0.txt \ + "$STAGE/resources/licenses/" + +# busybox tar + gzip(与 launcher/nyai 同管道语义);-cz 直接出 . +tar czf "$MPK" -C "$STAGE" app.json reader icon.png resources + +echo "== packed $MPK ==" +tar tzvf "$MPK" +echo "md5: $(md5sum "$MPK" | cut -d' ' -f1)" +echo +echo "含合规材料:resources/licenses/NOTICE + GPL-3.0.txt + LGPL-3.0.txt" +echo "未签名。正式分发前 run:" +echo " ../meow_sign/meow_sign sign $MPK \"\$HOME/Project/R3ProII/.keys/signing.key\"" diff --git a/patch_v2/reader_app/reader_internal.h b/patch_v2/reader_app/reader_internal.h index cb52cad..a746b38 100644 --- a/patch_v2/reader_app/reader_internal.h +++ b/patch_v2/reader_app/reader_internal.h @@ -24,6 +24,8 @@ int reader_is_active(void); void reader_on_tap(int x, int y); void reader_on_swipe(int dir, int dx, int dy); void reader_on_tick(uint64_t now); +void reader_on_touch_down(int x, int y); /* R3 段刮擦:右缘拖动快速定位 */ +void reader_on_touch_move(int x, int y); /* 打开一本书(书架调;成功进入阅读页)。path=TXT 完整路径。 */ void reader_open(const char *path); diff --git a/patch_v2/reader_app/resources/licenses/GPL-3.0.txt b/patch_v2/reader_app/resources/licenses/GPL-3.0.txt new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/patch_v2/reader_app/resources/licenses/GPL-3.0.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/patch_v2/reader_app/resources/licenses/LGPL-3.0.txt b/patch_v2/reader_app/resources/licenses/LGPL-3.0.txt new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/patch_v2/reader_app/resources/licenses/LGPL-3.0.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/patch_v2/reader_app/resources/licenses/NOTICE b/patch_v2/reader_app/resources/licenses/NOTICE new file mode 100644 index 0000000..a7c6b8d --- /dev/null +++ b/patch_v2/reader_app/resources/licenses/NOTICE @@ -0,0 +1,234 @@ +os.meow.reader (patch_v2/reader_app) — Third-Party NOTICES +========================================================== + +This application (os.meow.reader) statically links and/or bundles the +following third-party components. Their copyrights and licenses are +reproduced below. The build script (build_reader.sh) fetches the exact +pinned versions, so the binaries can be rebuilt / re-linked from source. + +License ownership +----------------- +The reader application's own source code is licensed under the Apache +License 2.0 (part of the MeowOS project; see the top-level repository +LICENSE). The third-party components listed below retain their own +licenses — in particular libmobi is LGPL-3.0 and stays under LGPL-3.0 +even though it is statically linked into the binary. Under LGPL-3.0 +section 1 the Apache-2.0 application is not thereby required to be +re-licensed under the GPL. + +Source availability +------------------- +The reader source (this application), build_reader.sh, and the top-level +MeowOS repository are publicly pullable at: + https://git.yeij.top/AskaEth/MeowOS-For-R3P2.git +(admin push: ssh://git@git.yeij.top:2222/AskaEth/MeowOS-For-R3P2.git) +The libmobi source tree at the locked commit remains available from the +upstream project and is re-fetched by build_reader.sh; a relink how-to +is in README_LGPL_RELINK.md in the reader_app directory. + +-------------------------------------------------------------------- +1. libmobi — Copyright (c) 2014-2024 Bartek Fabiszewski + project: https://github.com/bfabiszewski/libmobi + license: GNU LESSER GENERAL PUBLIC LICENSE, Version 3 + (or, at your option, any later version) + + Locked version (commit): + 906274205c11944b628da1c553b255acb1af7c55 (2024-10-29) + Upstream source and relink instructions: + https://github.com/bfabiszewski/libmobi + + libmobi is linked statically into the reader binary (LGPL-3.0 + §4 relink requirement). This NOTICE, the version lock above, and + the publicly reproducible build (build_reader.sh) that fetches and + recompiles libmobi satisfy the obligation to provide the + corresponding source and the ability to re-link. The libmobi + sources are NOT committed to this repository (they live in the + upstream repo at the commit above); the build script retrieves + exactly that commit. + + Full license texts (shipped with the package in resources/licenses/): + - GNU GPL version 3: resources/licenses/GPL-3.0.txt + - GNU LGPL version 3: resources/licenses/LGPL-3.0.txt + Both full texts are also reproduced below. + + Full license text (GNU LGPL v3): +-------------------------------------------------------------------- + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + +-------------------------------------------------------------------- +2. miniz — Copyright (c) 2013-2014 RAD Game Tools and Valve Software + Copyright (c) 2010-2014 Rich Geldreich and Tenacious + Software LLC + license: zlib license (MIT-style, permissive) + project: https://github.com/richgel999/miniz + Used for ZIP container reading (EPUB/CBZ) and inflate. + +-------------------------------------------------------------------- +3. stb_image — Public domain / MIT (Sean Barrett et al., v2.30) + project: https://github.com/nothings/stb + Used for image decode + downsample (CBZ pages, covers). + diff --git a/patch_v2/reader_app/test/test_reader_core.c b/patch_v2/reader_app/test/test_reader_core.c index 95b2b18..2a53b10 100644 --- a/patch_v2/reader_app/test/test_reader_core.c +++ b/patch_v2/reader_app/test/test_reader_core.c @@ -266,10 +266,66 @@ static void t10(void) remove(path); } +/* t11 — Big5→UTF-8 解码金标准(R3:big5_2byte 码表;对照 python `big5` codec)。 */ +static void t11(void) +{ + /* "你好世界" Big5 = a741 a66e a540 acc9 → UTF-8 你好世界 */ + const uint8_t in_nihao[] = { 0xA7, 0x41, 0xA6, 0x6E, 0xA5, 0x40, 0xAC, 0xC9 }; + const uint8_t ex_nihao[] = "\xE4\xBD\xA0\xE5\xA5\xBD\xE4\xB8\x96\xE7\x95\x8C"; + uint8_t out[256]; size_t olen = 0; + int rc = rd_big5_to_utf8(in_nihao, sizeof in_nihao, out, sizeof out, &olen); + CHECK(rc == RD_ENC_OK, "big5 decode ok"); + CHECK(olen == sizeof ex_nihao - 1 && memcmp(out, ex_nihao, olen) == 0, "big5 golden 你好世界"); + /* 经通用 rd_to_utf8(RD_ENC_BIG5) 路径 */ + memset(out, 0, sizeof out); olen = 0; + CHECK(rd_to_utf8(RD_ENC_BIG5, in_nihao, sizeof in_nihao, out, sizeof out, &olen) == RD_ENC_OK && + olen == sizeof ex_nihao - 1 && memcmp(out, ex_nihao, olen) == 0, "rd_to_utf8 big5"); + + /* 繁体长句(python 生成金标准) */ + const uint8_t in_tr[] = { 0xB3,0x6F,0xAC,0x4F,0xC1,0x63,0xC5,0xE9,0xA4,0xA4,0xA4,0xE5,0xB4,0xFA, + 0xB8,0xD5,0xA1,0x41,0xA5,0xCE,0xA9,0xF3,0xBD,0x73,0xBD,0x58,0xC0,0xCB, + 0xB4,0xFA,0xA1,0x43 }; + const uint8_t ex_tr[] = "\xE9\x80\x99\xE6\x98\xAF\xE7\xB9\x81\xE9\xAB\x94\xE4\xB8\xAD\xE6\x96\x87" + "\xE6\xB8\xAC\xE8\xA9\xA6\xEF\xBC\x8C\xE7\x94\xA8\xE6\x96\xBC\xE7\xB7\xA8" + "\xE7\xA2\xBC\xE6\xAA\xA2\xE6\xB8\xAC\xE3\x80\x82"; + memset(out, 0, sizeof out); olen = 0; + CHECK(rd_big5_to_utf8(in_tr, sizeof in_tr, out, sizeof out, &olen) == RD_ENC_OK, "big5 trad decode"); + CHECK(olen == sizeof ex_tr - 1 && memcmp(out, ex_tr, olen) == 0, "big5 golden trad"); + + /* 非法 Big5 双字节(trail 0x80..0xA0 非 Big5 尾节区)→ U+FFFD,不崩溃 */ + const uint8_t in_bad[] = { 0xA7, 0x80, 'X' }; /* a7 + trail 0x80(Big5 非法)+ 孤立 0x80 */ + memset(out, 0, sizeof out); olen = 0; + CHECK(rd_big5_to_utf8(in_bad, sizeof in_bad, out, sizeof out, &olen) == RD_ENC_OK, "big5 bad decode"); + const uint8_t ex_bad[] = { 0xEF, 0xBF, 0xBD, 0xEF, 0xBF, 0xBD, 'X' }; /* FFFD(0xA7) FFFD(0x80) X */ + CHECK(olen == sizeof ex_bad && memcmp(out, ex_bad, olen) == 0, "big5 bad → FFFD"); +} + +/* t12 — Big5 文件经 rdtxt 段读(info.enc 显式 Big5,模拟 UI 编码切换)。 */ +static void t12(void) +{ + const char *path = "/tmp/rdtxt_big5.txt"; + FILE *f = fopen(path, "wb"); + const uint8_t b5[] = { 0xA7,0x41,0xA6,0x6E, 0x0A, 0xA5,0x40,0xAC,0xC9, 0x0A }; /* 你好\n世界\n */ + fwrite(b5, 1, sizeof b5, f); + fclose(f); + rdtxt_info info; + CHECK(rdtxt_probe(path, &info) == 0, "big5 probe"); + info.enc = RD_ENC_BIG5; /* 手动指定 Big5(auto 会判 GB18030) */ + rdtxt_reader *r = NULL; + CHECK(rdtxt_reader_open(path, &info, &r) == 0, "big5 open"); + uint8_t buf[RDTXT_SEG_UTF8_MAX + 8]; + size_t ul = 0, off = 0, flen = 0, total = 0; + while (rdtxt_reader_next(r, buf, sizeof buf, &ul, &off, &flen) == 0) total += ul; + rdtxt_reader_free(r); + /* 整文件为一段(< 48K 分段阈值):"你好\n世界\n" = 6+1+6+1 = 14 字节 */ + CHECK(total == 14, "big5 segments sum"); + remove(path); +} + int main(void) { t1(); t2(); t3(); t4(); t5(); - t6(); t7(); t8(); t9(); t10(); + t6(); t7(); t8(); t9(); t10(); t11(); t12(); printf("test_reader_core: %d pass, %d fail\n", g_pass, g_fail); return g_fail ? 1 : 0; } diff --git a/patch_v2/reader_app/tools/gen_big5_table.py b/patch_v2/reader_app/tools/gen_big5_table.py new file mode 100644 index 0000000..a21ef25 --- /dev/null +++ b/patch_v2/reader_app/tools/gen_big5_table.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +"""gen_big5_table.py — 生成 Big5 双字节 → Unicode BMP 码位表(源码生成物,入仓)。 + +输出:core/big5_table.h 的 `static const uint16_t big5_2byte[126][190]`。 + +为什么这样生成(对齐 devdocs/plan/READER_ENGINE_NOTES.md §3.4 的 GB 表做法): + - 用 Python 内置 `big5` codec 枚举 2 字节码位(CP950/Big5 全映射)。 + - 表大小 = 126 引导字节(0x81..0xFE)× 190 尾字节 = 23,940 项 × 2B = 47,880 B。 + - 0 值 = 该 (lead,trail) 不是合法 Big5 双字节(含 trail 0x80..0xA0 区——Big5 尾字节 + 只取 0x40..0x7E | 0xA1..0xFE);解码器查表得码位,命中 0 → U+FFFD。 + - 列布局与 GB 表完全一致(idx(b) = b-0x40-(b>=0x80),跳过 0x7F),故解码器可复用同一 + trail 列索引函数。 + - 说明(README/编码检测局限):Big5 的双字节区间是 GB18030 双字节区间的**严格子集**, + 字节结构无法区分二者;本表用于 Big5 **显式选中时正确解码**,不用于自动检测(见 rdenc.h)。 + +用法:`python3 tools/gen_big5_table.py > core/big5_table.h` +(可用 env BIG5_TABLE_H 覆写输出路径。) +""" +import sys + +LEADS = list(range(0x81, 0xFF)) # 0x81..0xFE → 126 个 +TRAILS = list(range(0x40, 0x7F)) + list(range(0x80, 0xFF)) # 0x40..0x7E,0x80..0xFE → 190 个 + +INDEX_OF = {b: i for i, b in enumerate(TRAILS)} # 尾字节 → 列号 + +def build(): + g = [0] * (len(LEADS) * len(TRAILS)) + for k, lead in enumerate(LEADS): + for j, tr in enumerate(TRAILS): + try: + s = bytes([lead, tr]).decode('big5') + if len(s) == 1 and ord(s) <= 0xFFFF: + g[k * len(TRAILS) + j] = ord(s) + except UnicodeDecodeError: + pass + return g + +def emit(g): + rows, cols = len(LEADS), len(TRAILS) + out = sys.stdout + out.write("/* big5_table.h — Big5 双字节 → Unicode BMP 码位表(生成物,勿手改;\n") + out.write(" * 由 tools/gen_big5_table.py 用 python3 `big5` codec 生成)。\n") + out.write(" * 布局:big5_2byte[lead-0x81][trail_col];列号 idx(b)= b-0x40-(b>=0x80);\n") + out.write(" * 值 0 = 非法 Big5 双字节(如 trail 0x80..0xA0);解码器输出 U+FFFD。*/\n") + out.write("#ifndef RD_BIG5_TABLE_H\n#define RD_BIG5_TABLE_H\n\n") + out.write("#include \n\n") + out.write("#define RD_B5_LEADS %d\n" % rows) + out.write("#define RD_B5_TRAILS %d\n" % cols) + out.write("#define RD_B5_LEAD0 0x81\n") + out.write("#define RD_B5_TRAIL0 0x40\n\n") + out.write("/* big5_2byte[126][190]:rdbig5_lookup(lead, trail) 返回 BMP 码位或 0。 */\n") + out.write("static const uint16_t big5_2byte[%d][%d] = {\n" % (rows, cols)) + for k in range(rows): + out.write(" {") + for j in range(cols): + v = g[k * cols + j] + if j % 8 == 0: + out.write("\n " if j else "") + out.write("0x%04X,%s" % (v, "" if j == cols - 1 else "")) + out.write("\n }%s\n" % ("," if k != rows - 1 else "")) + out.write("};\n\n#endif /* RD_BIG5_TABLE_H */\n") + +if __name__ == '__main__': + emit(build()) diff --git a/patch_v2/sdk/build_sdk.sh b/patch_v2/sdk/build_sdk.sh index dc518fc..8415543 100755 --- a/patch_v2/sdk/build_sdk.sh +++ b/patch_v2/sdk/build_sdk.sh @@ -40,7 +40,7 @@ FT_INCS="-I$FT_BUILD -I$FT_BUILD/include -I$FT_SRC/include" rm -f meow_ui.a *.o # -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移) -OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_pinyin meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view meow_ui_nav meow_ui_media meow_ui_audio stb_image meow_ui_box meow_ui_anim meow_ui_scroll meow_ui_str meow_ui_ft meow_net_ipc" +OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_pinyin meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view meow_ui_nav meow_ui_media meow_ui_audio stb_image meow_ui_box meow_ui_anim meow_ui_scroll meow_lazygrid meow_select meow_ui_str meow_ui_ft meow_net_ipc meow_cachegov" CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd" for f in $OBJS; do echo "== cc $f ==" diff --git a/patch_v2/sdk/include/meow_cachegov.h b/patch_v2/sdk/include/meow_cachegov.h new file mode 100644 index 0000000..54991d1 --- /dev/null +++ b/patch_v2/sdk/include/meow_cachegov.h @@ -0,0 +1,82 @@ +/* + * meow_cachegov.h — 磁盘缓存 page-cache 治理 SDK 库(A 的主导/治本侧)。 + * + * 归属(devdocs/plan/ARCHITECTURE_PRINCIPLES.md ③混合,2026-09-02 用户定案): + * - A(DONTNEED 热路径,需要 read_pos) → 随 SDK 库(本文件,进程内直读 read_pos,无 IPC)。 + * - C(设备级 /proc/meminfo Cached 监控降载)→ 归系统服务 meow_svc_perf;本模块只读其共享状态 + * 文件(MEOW_CACHEGOV_C_FILE)作为「降载」信号,供 App 查询。 + * - App(如 ncm)侧只调本库接口:登记文件 + read_pos 回调 + tick + drop_finished + 查询降载, + * 不写自己的治理算法。 + * + * 权威方案:devdocs/plan/OOM_PAGECACHE_GOVERNANCE.md。红线:**L2(丢 read_pos 前方)禁止**, + * 本库绝不 drop `[read_pos, ...)` 任何未解码区。 + * + * 分级(§2.2): + * L0(默认,安全): 曲终/commit 后对整文件 posix_fadvise(DONTNEED)——播放器已退出解码循环。 + * L1(可选,主杠杆): 播放中丢已读前缀 `[0, read_pos-keep_hot)`,保 keep_hot 窗口供倒带; + * 仅 drop_mode>=1 且 A 未回退到 ≥2 时执行。 + * L2(禁止,红线): 绝不丢 read_pos 前方/当前未解码页。 + * + * underrun 回退(§2.3,SDK 内置):App 在 pread 慢(数据在盘上但 >threshold ms)处上报 + * meow_cachegov_note_refault();本库滚窗 10s,超门限逐级回退(放大 keep_hot → 降 L0 → 关 A)。 + * + * 接口按 OOM 文档 §4.2 + §4.4;值语义与 mem_watch 的「滞回三档」风格对齐(自含:pthread + meow_log)。 + */ +#ifndef MEOW_CACHEGOV_H +#define MEOW_CACHEGOV_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct meow_cachegov_cfg { + int enabled; /* 总开关:0=A 完全不丢页(register/tick/drop_finished 全部 no-op) */ + int cached_threshold_kb; /* 保留:C 侧阈值归 svc_perf(本库恒 0,只读不控,防误用) */ + int dirty_warn_kb; /* 保留:同上(svc_perf 用;本库只透传日志) */ + size_t keep_hot_bytes; /* L1 保留窗口:read_pos 之后保多少不丢(回退时自动放大) */ + size_t min_hot_ahead; /* L2 红线:read_pos 前方至少保多少——仅防御注释,**绝不参与 drop** */ + int drop_mode; /* 0=仅曲终(L0);1=曲终+播放中丢已读前缀(L0+L1,配 seek 回退) */ + int refault_backoff; /* 10s 窗口内 refault 触发回退的门限(次数) */ +} meow_cachegov_cfg_t; + +/* 登记本次治理的缓存文件(正式缓存文件路径 + 大小 + read_pos 回调 + 回调 ctx)。 + * 返回 0=ok / 负=错。重复 path 幂等(替换条目)。 */ +int meow_cachegov_register(const char *path, uint64_t size, + uint64_t (*read_pos_fn)(void *ctx), void *ctx); + +/* 注销该 path 的治理条目(曲终/切歌后清理)。返回 0=找到并删 / 负=不在册。 */ +int meow_cachegov_unregister(const char *path); + +/* 周期调用(播放循环节拍 / 监控线程均可): + * ① 按回退档执行 L1 丢弃(丢已读前缀 [0, read_pos-keep_hot)); + * ② 评估 refault 滚窗做逐级回退。 */ +int meow_cachegov_tick(void); + +/* 曲终/commit 后:对刚播完文件做整文件 L0 DONTNEED + 注销该条目。 + * 回退档>=3(A 整体关闭)时不丢页(L0 也被关,见 §2.3 步骤 3)。 */ +int meow_cachegov_drop_finished(const char *path); + +/* 下载/预取方查询(1=应暂停)。数据源 = A 侧回退硬暂停 OR svc_perf C(Cached 超阈值)降载。 */ +int meow_cachegov_download_paused(void); +int meow_cachegov_prefetch_paused(void); + +/* 设置/读取治理配置(运行时覆盖编译期默认)。 */ +int meow_cachegov_set_cfg(const meow_cachegov_cfg_t *cfg); +int meow_cachegov_get_cfg(meow_cachegov_cfg_t *cfg); + +/* ---- refault 上报 + A 回退状态(underrun 检测,§2.3)---- */ +/* App 在 pread 慢(数据已在盘上、读出 >threshold ms)处上报一次 refault。线程安全。 */ +void meow_cachegov_note_refault(void); +/* A 当前回退档:0=正常 1=keep_hot 放大 2=降 L0(L1 关) 3=A 关。 */ +int meow_cachegov_backoff_level(void); + +/* ---- 宿主测试桩(设备永不调用;-1=真实时钟,>=0 时用注入值替代 monotonic 时钟)---- */ +void meow_cachegov_test_set_time_ms(int64_t t); + +#ifdef __cplusplus +} +#endif +#endif /* MEOW_CACHEGOV_H */ diff --git a/patch_v2/sdk/include/meow_lazygrid.h b/patch_v2/sdk/include/meow_lazygrid.h new file mode 100644 index 0000000..98859a5 --- /dev/null +++ b/patch_v2/sdk/include/meow_lazygrid.h @@ -0,0 +1,106 @@ +/* + * meow_lazygrid.h — SDK 2 维虚拟化宫格(§12.4:meow_lazygrid,M7.4 主界面前置)。 + * + * 定位:给「卡片/图标宫格」提供**二维虚拟化**——应用入口宫格、图标墙、设置快捷卡等。 + * 只实例化**可见行**(视口内),行高/列几何 + gap;滚动复用 §12.3 meow_scroll(fling/snap), + * 与 meow_listview 同款惯性;命中 meow_lazygrid_hit/tap_at 把滚动+网格坐标映射回 item idx。 + * + * 设计(仿 meow_listview 的 vsource 思想 + meow_scroll 滚动 + meow_box 列几何): + * - 数据源:count_fn() 返回总项数(任意长),bind_fn(index, item) 仅当 item「进入视口」 + * (绑定到固定池槽)时调一次;回滚命中同槽不重绑(同 listview 池语义)。 + * - 固定 item 池:池容量 = 视口行数 + 2(× cols),静态有界(≤MEOW_LAZYGRID_POOL_MAX)。 + * - 列几何:cols + cell_w + cell_h + gap;行高 row_h = cell_h + gap;按 scroll offset 推可见行。 + * - 渲染:默认 renderer 画「圆角卡片(icon+名+状态)」;App 也可经 set_item_draw 完全自绘 + * (主界面卡片宫格用:icon+名+后台标注)。支持选中高亮/禁用变淡/空态占位。 + * - 滚动:接 meow_scroll(meow_lazygrid_scroll 返回会话;track/fling/snap 与 listview 同款)。 + * + * 内存纪律(M7 §5.2):静态有界 item 池(冷路径 create/set_vsource 分配,热路径 draw/hit/scroll + * 零 malloc),与 listview 池同源;不常驻 count 个 item 对象。 + * + * 非 meow_widget:lazygrid 是**自包含控件**(不注册进 z 序/绘制注册表),由宿主(launcher session) + * 在 draw 阶段调 meow_lazygrid_draw、命中阶段调 meow_lazygrid_hit/tap_at、滚动喂 meow_scroll—— + * 与 meow_scroll/meow_box 同为「正交模块」,不侵入 struct meow_widget(全追加式)。 + */ +#ifndef MEOW_UI_LAZYGRID_H +#define MEOW_UI_LAZYGRID_H + +#include "meow_ui_types.h" /* meow_img 前向 + 基础类型(子头不引伞头,避免相对路径依赖) */ +#include "meow_ui_theme.h" /* meow_theme(draw 取语义色) */ +#include "meow_ui_scroll.h" /* meow_scroll(滚动集成;子头引依赖子头,同 widgets.h→theme.h) */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct meow_lazygrid meow_lazygrid; + +/* ---- cell / item ---- */ +/* 单格数据(bind 回调填充;SDK 固定池持有;App 跨 bind 用 user 存业务态/图/index)。 */ +typedef struct meow_lazygrid_item { + const char *title; /* 主文本(名;必填,NULL 视为空格) */ + const char *subtitle; /* 副文本(状态/后台标注),可空 */ + const meow_img *icon; /* 可选:格内图标(NULL=无) */ + int enabled; /* 1=可交互;0=变淡不响应 */ + void *user; /* App 私有(跨 bind 保存业务态;SDK 不碰) */ +} meow_lazygrid_item; + +/* 数据源:count 返回总项数(>=0,任意长);bind 在 item 进入视口时调一次填充 item。 + * 二者均不常驻 item 对象;count 每帧实时查(支持动态长度/搜索过滤)。 */ +typedef int (*meow_lazygrid_count_fn)(void *ctx); +typedef void (*meow_lazygrid_bind_fn)(int index, meow_lazygrid_item *item, void *ctx); + +/* 完全自绘回调(覆盖默认 renderer):index=项号;x,y,w,h=格矩形(已裁剪到宫格视口); + * state=MEOW_LAZYGRID_ST_* 状态位(选中/禁用组合)。App 用 §3 绘制 API 自绘整格。 */ +typedef void (*meow_lazygrid_draw_fn)(int index, int x, int y, int w, int h, + int state, void *ctx); +/* state 位(draw 回调收到)。 */ +#define MEOW_LAZYGRID_ST_SELECTED (1u<<0) +#define MEOW_LAZYGRID_ST_DISABLED (1u<<1) + +/* 命中/点击回调(idx=项号;App 在 tap_at 命中后触发动作)。 */ +typedef void (*meow_lazygrid_tap_fn)(int idx, void *ctx); + +#define MEOW_LAZYGRID_POOL_MAX 96 /* 池容量上限(固定;保证静态有界) */ + +/* ---- 生命周期(冷路径 create/free;热路径 draw/hit/scroll 零 malloc)---- + * cols=列数(>=1,越界 clamp);cell_w/cell_h=格宽高;gap=格间距(水平与垂直同用)。 */ +meow_lazygrid *meow_lazygrid_create(int x, int y, int w, int h, int cols, + int cell_w, int cell_h, int gap); +void meow_lazygrid_free(meow_lazygrid *g); + +/* ---- 几何配置 ---- */ +void meow_lazygrid_set_rect(meow_lazygrid *g, int x, int y, int w, int h); /* 视口矩形(绝对坐标) */ +void meow_lazygrid_set_cols(meow_lazygrid *g, int cols); +void meow_lazygrid_set_cell(meow_lazygrid *g, int cell_w, int cell_h, int gap); + +/* ---- 数据源 + 渲染 ---- */ +int meow_lazygrid_set_vsource(meow_lazygrid *g, meow_lazygrid_count_fn count, + meow_lazygrid_bind_fn bind, void *ctx); +int meow_lazygrid_set_item_draw(meow_lazygrid *g, meow_lazygrid_draw_fn draw, + void *ctx); /* NULL=用默认卡片 renderer */ +int meow_lazygrid_set_empty_text(meow_lazygrid *g, const char *text); + +/* ---- 滚动(像素级;可视行数按 offset 实时推) ---- */ +void meow_lazygrid_set_scroll(meow_lazygrid *g, int off); +void meow_lazygrid_scroll_by(meow_lazygrid *g, int dy); +int meow_lazygrid_scroll(meow_lazygrid *g); +int meow_lazygrid_max_scroll(meow_lazygrid *g); /* max(0, 内容高 - 视口高) */ +int meow_lazygrid_row_h(const meow_lazygrid *g); /* 行高(=cell_h+gap) */ +/* 便捷:包一个 meow_scroll 会话(set/max/get/redraw 指回本宫格;redraw 只刷视口区)。 + * App 在 on_touch_move 喂 meow_scroll_track、松手 meow_scroll_fling。返回 NULL=池满。 */ +meow_scroll *meow_lazygrid_scroll_attach(meow_lazygrid *g); + +/* ---- 选中 / 命中 ---- */ +void meow_lazygrid_set_on_tap(meow_lazygrid *g, meow_lazygrid_tap_fn cb, void *ctx); +void meow_lazygrid_set_sel(meow_lazygrid *g, int idx); /* 越界 clamp;<0 清除;标脏 */ +int meow_lazygrid_sel(const meow_lazygrid *g); +int meow_lazygrid_hit(meow_lazygrid *g, int x, int y); /* 命中项 idx(滚动+网格坐标映射);-1=未命中/禁用 */ +int meow_lazygrid_tap_at(meow_lazygrid *g, int x, int y); /* 命中:置选中 + 回调 + 返回 idx/-1 */ + +/* ---- 绘制 ---- */ +void meow_lazygrid_draw(meow_lazygrid *g, const meow_theme *th); + +#ifdef __cplusplus +} +#endif +#endif /* MEOW_UI_LAZYGRID_H */ diff --git a/patch_v2/sdk/include/meow_select.h b/patch_v2/sdk/include/meow_select.h new file mode 100644 index 0000000..1aba5bb --- /dev/null +++ b/patch_v2/sdk/include/meow_select.h @@ -0,0 +1,47 @@ +/* + * meow_select.h — SDK 枚举/预设选择器(§5.8.6 模态组件,M7.2 设置页缺口 #4)。 + * + * 定位:给「枚举 / 预设选择器」一个开箱即用的**下拉模态**——设置页采样率/码率/主题/语言等 + * 枚举、均衡器预设等。封装 = BottomSheet(标题 + 选项列表)+ meow_listview(SINGLE) 单选列表: + * 选项数组 + 当前选中(高亮)+ 点选项 = 选中 + 关闭 + on_change 回调;点面板外/左缘返回 = 取消。 + * + * 设计(§3 缺口 #4):选项用 meow_listview(SINGLE) 渲染(单选高亮、可惯性滚动、虚拟化长列表), + * 外层是模态 BottomSheet(复用 meow_dialog 的遮罩/快照恢复,scrim 只叠一层/关闭无残影)。 + * 阻塞式(内层 meow_input_pop 轮询,与 meow_dialog 同构);打开前排空残留触摸。 + * + * 内存纪律:选项数组定长(≤MEOW_SELECT_MAX_ITEMS,栈/数组持有,非 malloc 长列表); + * 列表在 open 时创建(冷路径)、close 时释放;open 循环零 malloc。 + */ +#ifndef MEOW_UI_SELECT_H +#define MEOW_UI_SELECT_H + +#include "meow_ui_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct meow_select meow_select; + +/* 选中回调(on_change):idx=被选中的选项下标(0..n-1)。 */ +typedef void (*meow_select_fn)(int idx, void *ctx); + +#define MEOW_SELECT_MAX_ITEMS 32 /* 选项上限(静态数组;越界 add 忽略并返回 -1) */ + +/* ---- 组件式(可复用 + on_change 回调)---- */ +meow_select *meow_select_create(const char *title, int cur); /* cur=初始选中项;越界 clamp */ +int meow_select_add_item(meow_select *s, const char *label); /* 返回项下标;满/无效= -1 */ +void meow_select_set_on_change(meow_select *s, meow_select_fn cb, void *ctx); +/* 打开模态(阻塞到确认/取消):选中 → 置 result=idx + 调 on_change(idx)+ 返回 idx; + * 取消/点面板外/左缘返回 → 返回 -1(不调 on_change)。 */ +int meow_select_open(meow_select *s); +void meow_select_free(meow_select *s); + +/* ---- 一次性(无回调,返回值即选择)---- + * opts=选项数组(const char* 数组);n=个数;cur=初始选中。返回选中下标/-1=取消。 */ +int meow_select_show(const char *title, const char *const *opts, int n, int cur); + +#ifdef __cplusplus +} +#endif +#endif /* MEOW_UI_SELECT_H */ diff --git a/patch_v2/sdk/include/meow_ui_widgets.h b/patch_v2/sdk/include/meow_ui_widgets.h index 93136b1..3ebf369 100644 --- a/patch_v2/sdk/include/meow_ui_widgets.h +++ b/patch_v2/sdk/include/meow_ui_widgets.h @@ -386,6 +386,25 @@ int meow_listview_set_item_draw(meow_widget *lv, meow_listview_draw_fn draw, /* 空态占位:count==0 时视口内居中画 empty_text(NULL 不画)。返回 0=成功。 */ int meow_listview_set_empty_text(meow_widget *lv, const char *text); +/* ============================================================ */ +/* §5.5 左滑删除(M7.3 通知中心缺口 #9):列表行横向手势 + 删除/回弹动画 */ +/* ------------------------------------------------------------ + * 行自绘 set_item_draw 配合(SDK 在自绘行前把该行横向偏移 swipe_dx 传给 x,并画右露删除区)。 + * 手势由 App 在 on_touch_move 喂三级:begin(touch-down)/ move(touch-move)/ end(release)。 + * 判定:横向小位移|dx|>|dy| 且超容差=swipe(move 返回 1=本列表消费,App 勿滚动);纵向 + * |dy|>|dx| 且超容差=滚动手势优先(返回 0 并取消本行 swipe,App 照常 scroll_by);点按 + * 无位移(end 时未 started)=行点击(返回 0,App 照常 tap_at)。不修改 tap_at/滚动语义。 + * 动画:超阈值 → 删除动画(行滑出 meow_anim,结束后回调 on_delete(idx));未超 → 回弹。 + * 颜色:删除区用主题 error/onError;不与行点击/滚动冲突(见判定表)。 */ +typedef void (*meow_listview_swipe_fn)(int idx, void *ctx); +void meow_listview_set_swipe_delete(meow_widget *lv, int enabled, + meow_listview_swipe_fn on_delete, void *ctx); +int meow_listview_swipe_begin(meow_widget *lv, int x, int y); /* touch-down:1=命中可滑行(喂后续 move) */ +int meow_listview_swipe_move(meow_widget *lv, int x, int y); /* touch-move:1=消费(横向);0=让位(取消/纵向) */ +int meow_listview_swipe_end(meow_widget *lv, int x, int y); /* release:1=消费(删/回弹动画);0=未滑动(tap) */ +int meow_listview_swipe_active(meow_widget *lv); /* 1=有行处于滑动/动画中 */ +int meow_listview_swipe_offset(meow_widget *lv); /* 当前滑动行的 dx(<0=左移;自绘行画用) */ + #ifdef __cplusplus } #endif diff --git a/patch_v2/sdk/meow_cachegov.c b/patch_v2/sdk/meow_cachegov.c new file mode 100644 index 0000000..f538df3 --- /dev/null +++ b/patch_v2/sdk/meow_cachegov.c @@ -0,0 +1,302 @@ +/* sdk/meow_cachegov.c — 磁盘缓存 page-cache 治理 SDK 库(A:DONTNEED 主导侧)。 + * + * 权威方案:devdocs/plan/OOM_PAGECACHE_GOVERNANCE.md。归属(ARCHITECTURE_PRINCIPLES ③混合): + * A 热路径(register/tick/drop_finished/refault)随 SDK 库;C(/proc/meminfo Cached 监控) + * 归 meow_svc_perf,本库只读其共享状态文件 MEOW_CACHEGOV_C_FILE 作为降载信号。 + * + * 红线(务必保留本注释):**L2 禁止**——绝不丢 `[read_pos, ...)` 任何未解码区(含 read_pos 前方 + * 刚写入尚未解码的页)。本库所有 drop 的上界只能是 read_pos-keep_hot(L1)或整文件曲终(L0)。 + * + * 分层:只依赖 pthread + meow_log + POSIX(posix_fadvise),不依赖 core/App 其它 TU。 + * 自含:refault 滚窗 + 三级回退(放大 keep_hot → 降 L0 → 关 A)。 + */ +#include "include/meow_cachegov.h" + +#include +#include +#include +#include +#include +#include +#include + +/* meow_ui.h:SDK 公共伞头(含 meow_log/meow_ui_log.h + 公共类型)。 */ +#include "meow_ui.h" +/* 共享协议头:C 侧状态文件路径(meow_svc_perf 写入 / 各 App 读取,同源防漂移)。 */ +#include "meow_ipc.h" + +#define MEOW_CACHEGOV_MAXREG 8 /* 同时治理的缓存文件上限(ncm 单点播放,远小于此) */ +#define MEOW_CACHEGOV_REFAULTRING 64 /* refault 滚窗环形缓冲 */ +#define MEOW_CACHEGOV_WINDOW_MS 10000 /* refault 滚窗 10s */ +#define MEOW_CACHEGOV_HOTESCALATE 1 /* 回退档>=1 时 keep_hot 放大倍数(翻倍) */ +#define MEOW_CACHEGOV_DROP_THROTTLE (64u * 1024u) /* L1 每次至少推进 64KB 才再 drop(省 open/fadvise) */ + +typedef struct cg_entry { + char path[512]; + uint64_t size; /* 标称大小(流式=nominal;命中=实际) */ + uint64_t (*read_pos_fn)(void *ctx); + void *ctx; + uint64_t dropped_to; /* 已 DONTNEED 的已读前缀上界(只推进,不回退) */ + int used; +} cg_entry_t; + +static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER; +static struct meow_cachegov_cfg g_cfg = { + .enabled = 1, + .cached_threshold_kb = 0, /* C 归属 svc_perf,本库只读 */ + .dirty_warn_kb = 0, /* 同上 */ + .keep_hot_bytes = (256u * 1024u), /* 保 256KB 解码窗口供倒带 */ + .min_hot_ahead = (0), /* L2 红线防御值;绝不参与 drop */ + .drop_mode = 0, /* 默认仅曲终(L0);L1 需显式开(带 seek 回退) */ + .refault_backoff = 3, /* 10s 内 3 次 refault → 回退 */ +}; +static cg_entry_t g_reg[MEOW_CACHEGOV_MAXREG]; +static int g_backoff = 0; /* 0=正常 1=keep_hot放大 2=降L0 3=A关 */ +static int64_t g_last_step_ms = -1000000000000LL; /* 上次升/降档时刻(初始很远 → 首次可动作) */ +static int64_t g_refault_ring[MEOW_CACHEGOV_REFAULTRING]; +static int g_refault_head = 0; +static int g_refault_n = 0; + +/* ---- 时钟(宿主测试桩可注入) ---- */ +static int64_t g_test_now_ms = -1; +static int64_t now_ms(void) +{ + if (g_test_now_ms >= 0) return g_test_now_ms; + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} +void meow_cachegov_test_set_time_ms(int64_t t) { g_test_now_ms = t; } + +/* ---- C 侧状态文件(svc_perf 写 / 本库读) ---- */ +static const char *c_file(void) +{ + const char *e = getenv("MEOW_CACHEGOV_C_FILE"); + return (e && e[0]) ? e : MEOW_CACHEGOV_C_FILE; +} + +/* C 状态(svc_perf 写)最近一次解析缓存:下载 cb 每 4KB 块会查一次,避免每块都读文件(200ms TTL)。 */ +static int g_cached_c_dl = 0, g_cached_c_pf = 0; +static int64_t g_cached_c_at = 0; + +/* 读 C 状态文件(缺省 0=不降载)。解析失败/文件不存在 → 全部 0。 */ +static void read_c_state(int *dl_pause, int *pf_pause, long *cached_kb, long *dirty_kb) +{ + if (dl_pause) *dl_pause = 0; + if (pf_pause) *pf_pause = 0; + if (cached_kb) *cached_kb = 0; + if (dirty_kb) *dirty_kb = 0; + FILE *f = fopen(c_file(), "r"); + if (!f) return; + char line[160]; + while (fgets(line, sizeof line, f)) { + if (!strncmp(line, "cached_kb=", 10)) { if (cached_kb) *cached_kb = atol(line + 10); } + else if (!strncmp(line, "dirty_kb=", 9)) { if (dirty_kb) *dirty_kb = atol(line + 9); } + else if (!strncmp(line, "dl_pause=", 9)) { if (dl_pause) *dl_pause = atol(line + 9) ? 1 : 0; } + else if (!strncmp(line, "prefetch_pause=", 15)) { if (pf_pause) *pf_pause = atol(line + 15) ? 1 : 0; } + } + fclose(f); +} + +/* ---- refault 滚窗 ---- */ +void meow_cachegov_note_refault(void) +{ + int64_t t = now_ms(); + pthread_mutex_lock(&g_mu); + g_refault_ring[g_refault_head] = t; + g_refault_head = (g_refault_head + 1) % MEOW_CACHEGOV_REFAULTRING; + if (g_refault_n < MEOW_CACHEGOV_REFAULTRING) g_refault_n++; + pthread_mutex_unlock(&g_mu); +} + +static int refaults_last_window(void) +{ + int64_t now = now_ms(); + int cnt = 0, start = (g_refault_head - g_refault_n + MEOW_CACHEGOV_REFAULTRING) % + MEOW_CACHEGOV_REFAULTRING; + for (int i = 0; i < g_refault_n; i++) { + int64_t t = g_refault_ring[(start + i) % MEOW_CACHEGOV_REFAULTRING]; + if (t >= now - MEOW_CACHEGOV_WINDOW_MS) cnt++; + } + return cnt; +} + +/* 曲终/cur_finished 后:整文件 L0 DONTNEED。 */ +int meow_cachegov_drop_finished(const char *path) +{ + if (!path) return -1; + pthread_mutex_lock(&g_mu); + for (int i = 0; i < MEOW_CACHEGOV_MAXREG; i++) { + if (g_reg[i].used && strcmp(g_reg[i].path, path) == 0) { + g_reg[i].used = 0; + break; + } + } + int lbf = g_backoff, en = g_cfg.enabled; + pthread_mutex_unlock(&g_mu); + + if (!en || lbf >= 3) { /* A 已整体关闭:连曲终 L0 也不丢(§2.3 步骤 3) */ + meow_log(MEOW_LOG_INFO, "[cachegov] drop_finished skip %s (A off, backoff=%d)", path, lbf); + return 0; + } + int fd = open(path, O_RDONLY); + if (fd < 0) return -1; + /* len=0 → 到 EOF(整文件)。曲终播放器已不再读该文件,重播=冷缓存首读(等价未命中)。 */ + posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED); + close(fd); + meow_log(MEOW_LOG_INFO, "[cachegov] drop_finished L0 %s", path); + return 0; +} + +/* L1:丢已读前缀 [dropped_to, read_pos-keep_hot)。返回 1=有丢。 */ +static int entry_drop_l1(cg_entry_t *e, size_t keep) +{ + if (!e->read_pos_fn) return 0; + uint64_t pos = e->read_pos_fn(e->ctx); + if (pos <= keep) return 0; /* keep_hot 覆盖整个已读前缀,无需丢 */ + uint64_t drop_end = pos - (uint64_t)keep; /* L1 上界:read_pos 前方绝不丢(L2 红线) */ + if (drop_end <= e->dropped_to) return 0; /* 无推进(seek 回退 / 前进不足) */ + if (drop_end - e->dropped_to < MEOW_CACHEGOV_DROP_THROTTLE && e->dropped_to != 0) + return 0; /* 节流:不足 64KB 不重复 open */ + int fd = open(e->path, O_RDONLY); + if (fd < 0) return 0; + off_t off = (off_t)e->dropped_to; + off_t len = (off_t)(drop_end - e->dropped_to); + ssize_t r = posix_fadvise(fd, off, len, POSIX_FADV_DONTNEED); + close(fd); + if (r != 0) return 0; + e->dropped_to = drop_end; + meow_log(MEOW_LOG_INFO, "[cachegov] L1 drop %s off=%lld len=%lld (pos=%lld keep=%zu)", + e->path, (long long)off, (long long)len, (long long)pos, keep); + return 1; +} + +int meow_cachegov_tick(void) +{ + pthread_mutex_lock(&g_mu); + int64_t now = now_ms(); + /* ① 回退评估(滚窗 10s):refault 超门限 → 升一档;滚窗干净 ≥10s → 降一档。 + * 每升降一档需间隔 ≥10s(逐级回退,不因窗内持续 refault 一次跳过多档,防误回退)。 */ + int cnt = refaults_last_window(); + if (cnt >= g_cfg.refault_backoff && g_cfg.refault_backoff > 0 && g_backoff < 3 && + (now - g_last_step_ms) >= MEOW_CACHEGOV_WINDOW_MS) { + g_backoff = g_backoff + 1; + g_last_step_ms = now; + meow_log(MEOW_LOG_INFO, "[cachegov] backoff -> %d (refault=%d/%lds)", + g_backoff, cnt, MEOW_CACHEGOV_WINDOW_MS / 1000); + } else if (cnt == 0 && g_backoff > 0 && + (now - g_last_step_ms) >= MEOW_CACHEGOV_WINDOW_MS) { + g_backoff = g_backoff - 1; + g_last_step_ms = now; + meow_log(MEOW_LOG_INFO, "[cachegov] backoff -> %d (clean window)", g_backoff); + } + + size_t keep = g_cfg.keep_hot_bytes; + if (g_backoff >= MEOW_CACHEGOV_HOTESCALATE) keep = g_cfg.keep_hot_bytes * 2; /* 档1:放大 keep_hot */ + int l1_on = g_cfg.enabled && g_cfg.drop_mode >= 1 && g_backoff < 2; /* 档2:L1 关 */ + int en = g_cfg.enabled; + + /* ② L1 丢弃 */ + int dropped = 0; + if (en && l1_on) { + for (int i = 0; i < MEOW_CACHEGOV_MAXREG; i++) + if (g_reg[i].used) dropped += entry_drop_l1(&g_reg[i], keep); + } + pthread_mutex_unlock(&g_mu); + return dropped; +} + +/* ---- 注册 / 注销 ---- */ +int meow_cachegov_register(const char *path, uint64_t size, + uint64_t (*read_pos_fn)(void *ctx), void *ctx) +{ + if (!path) return -1; + pthread_mutex_lock(&g_mu); + int slot = -1; + for (int i = 0; i < MEOW_CACHEGOV_MAXREG; i++) { + if (g_reg[i].used && strcmp(g_reg[i].path, path) == 0) { slot = i; break; } + if (!g_reg[i].used && slot < 0) slot = i; + } + if (slot < 0) { pthread_mutex_unlock(&g_mu); return -1; } + snprintf(g_reg[slot].path, sizeof g_reg[slot].path, "%s", path); + g_reg[slot].size = size; + g_reg[slot].read_pos_fn = read_pos_fn; + g_reg[slot].ctx = ctx; + g_reg[slot].dropped_to = 0; + g_reg[slot].used = 1; + pthread_mutex_unlock(&g_mu); + meow_log(MEOW_LOG_INFO, "[cachegov] register %s size=%llu", path, (unsigned long long)size); + return 0; +} + +int meow_cachegov_unregister(const char *path) +{ + if (!path) return -1; + pthread_mutex_lock(&g_mu); + int rc = -1; + for (int i = 0; i < MEOW_CACHEGOV_MAXREG; i++) + if (g_reg[i].used && strcmp(g_reg[i].path, path) == 0) { g_reg[i].used = 0; rc = 0; break; } + pthread_mutex_unlock(&g_mu); + return rc; +} + +/* ---- 降载查询(A 回退 OR svc_perf C) ---- */ +static void c_state(int *dl_pause, int *pf_pause) +{ + int64_t now = now_ms(); + pthread_mutex_lock(&g_mu); + if (g_cached_c_at == 0 || (now - g_cached_c_at) > 200) { + read_c_state(&g_cached_c_dl, &g_cached_c_pf, NULL, NULL); + g_cached_c_at = now; + } + if (dl_pause) *dl_pause = g_cached_c_dl; + if (pf_pause) *pf_pause = g_cached_c_pf; + pthread_mutex_unlock(&g_mu); +} + +int meow_cachegov_download_paused(void) +{ + pthread_mutex_lock(&g_mu); + int a = (g_backoff >= 2); + pthread_mutex_unlock(&g_mu); + int c = 0; + c_state(&c, NULL); + return (a || c) ? 1 : 0; +} + +int meow_cachegov_prefetch_paused(void) +{ + pthread_mutex_lock(&g_mu); + int a = (g_backoff >= 3); + pthread_mutex_unlock(&g_mu); + int c = 0; + c_state(NULL, &c); + return (a || c) ? 1 : 0; +} + +/* ---- 配置 ---- */ +int meow_cachegov_set_cfg(const struct meow_cachegov_cfg *cfg) +{ + if (!cfg) return -1; + pthread_mutex_lock(&g_mu); + g_cfg = *cfg; + pthread_mutex_unlock(&g_mu); + return 0; +} + +int meow_cachegov_get_cfg(struct meow_cachegov_cfg *cfg) +{ + if (!cfg) return -1; + pthread_mutex_lock(&g_mu); + *cfg = g_cfg; + pthread_mutex_unlock(&g_mu); + return 0; +} + +int meow_cachegov_backoff_level(void) +{ + pthread_mutex_lock(&g_mu); + int v = g_backoff; + pthread_mutex_unlock(&g_mu); + return v; +} diff --git a/patch_v2/sdk/meow_lazygrid.c b/patch_v2/sdk/meow_lazygrid.c new file mode 100644 index 0000000..f965078 --- /dev/null +++ b/patch_v2/sdk/meow_lazygrid.c @@ -0,0 +1,350 @@ +/* + * meow_lazygrid.c — §12.4 2 维虚拟化宫格(M7.4 主界面前置)。 + * + * 自包含控件(非 meow_widget,不注册 z 序/绘制注册表):宿主在 draw 阶段调 meow_lazygrid_draw、 + * 命中阶段调 meow_lazygrid_hit/tap_at、滚动喂 meow_scroll(meow_lazygrid_scroll 返回会话)。 + * 只实例化**可见行**:固定 item 池(视口行数 + 2 × cols,静态有界 ≤MEOW_LAZYGRID_POOL_MAX), + * bind 仅在 item 进入视口时调一次(回滚同槽不重绑,同 listview 池语义)。 + * + * 内存纪律:池在 create/set_vsource/set_cell(冷路径)分配;draw/hit/set_scroll 热路径零 malloc。 + * 颜色全取 meow_theme_get() 语义色(日/夜/style 自动),不硬编码。 + */ +#include "meow_ui.h" +#include "include/meow_lazygrid.h" +#include "meow_ui_internal.h" /* meow_gfx_clip_push/pop + meow_gfx_fill_round */ +#include "include/meow_ui_scroll.h" +#include +#include + +struct meow_lazygrid { + int x, y, w, h; /* 视口矩形(绝对坐标) */ + int cols; /* 列数 */ + int cell_w, cell_h, gap; /* 格宽/格高/间距 */ + int row_h; /* 行高 = cell_h + gap(缓存) */ + + int scroll; /* 滚动偏移(像素) */ + int sel; /* 选中项(-1=无) */ + + meow_lazygrid_count_fn count_fn; void *vctx; + meow_lazygrid_bind_fn bind; + meow_lazygrid_draw_fn draw_cb; void *draw_ctx; + meow_lazygrid_tap_fn tap_cb; void *tap_ctx; + + /* 固定 item 池(冷路径分配;slot 按 item index mod pool_n 映射) */ + meow_lazygrid_item *pool; + int *pool_idx; /* 每槽绑定的 item index(-1=空闲) */ + int pool_n; + + char empty_text[64]; /* 空态占位(首字符 0=不画) */ +}; + +/* 行高(= cell_h + gap)。 */ +int meow_lazygrid_row_h(const meow_lazygrid *g) +{ return g ? g->row_h : 0; } + +/* 总行数。 */ +static int lg_rows(const meow_lazygrid *g) +{ + int n = g->count_fn ? g->count_fn(g->vctx) : 0; + if (n < 0) n = 0; + if (n == 0) return 0; + return (n + g->cols - 1) / g->cols; +} +/* 总项数。 */ +static int lg_count(const meow_lazygrid *g) +{ return g->count_fn ? g->count_fn(g->vctx) : 0; } + +/* 内容总高(行高 × 行数 − 末行后无 gap)。 */ +static int lg_content_h(const meow_lazygrid *g) +{ + int rows = lg_rows(g); + if (rows <= 0) return 0; + return rows * g->cell_h + (rows - 1) * g->gap; +} + +/* ---- 生命周期 ---- */ +meow_lazygrid *meow_lazygrid_create(int x, int y, int w, int h, int cols, + int cell_w, int cell_h, int gap) +{ + if (cols < 1) cols = 1; + meow_lazygrid *g = (meow_lazygrid *)calloc(1, sizeof *g); + if (!g) return NULL; + g->x = x; g->y = y; g->w = w; g->h = h; g->cols = cols; + g->cell_w = cell_w > 0 ? cell_w : 1; + g->cell_h = cell_h > 0 ? cell_h : 1; + g->gap = gap > 0 ? gap : 0; + g->row_h = g->cell_h + g->gap; + g->sel = -1; + return g; +} + +void meow_lazygrid_free(meow_lazygrid *g) +{ + if (!g) return; + free(g->pool); free(g->pool_idx); + free(g); +} + +void meow_lazygrid_set_rect(meow_lazygrid *g, int x, int y, int w, int h) +{ + if (!g) return; + g->x = x; g->y = y; g->w = w; g->h = h; +} +void meow_lazygrid_set_cols(meow_lazygrid *g, int cols) +{ + if (!g) return; + if (cols < 1) cols = 1; + if (g->cols == cols) return; + g->cols = cols; +} +void meow_lazygrid_set_cell(meow_lazygrid *g, int cell_w, int cell_h, int gap) +{ + if (!g) return; + g->cell_w = cell_w > 0 ? cell_w : 1; + g->cell_h = cell_h > 0 ? cell_h : 1; + g->gap = gap > 0 ? gap : 0; + g->row_h = g->cell_h + g->gap; /* 行高变化 → 可见行数变 → 池容量随之调整 */ +} + +/* ---- item 池(冷路径;有界 ≤MEOW_LAZYGRID_POOL_MAX)---- */ +static int lg_pool_cap(const meow_lazygrid *g) +{ + int rh = g->row_h; if (rh < 8) rh = 8; + int rows = (g->h / rh) + 1; if (rows < 1) rows = 1; + int cap = (rows + 2) * g->cols; + if (cap < 8) cap = 8; + if (cap > MEOW_LAZYGRID_POOL_MAX) cap = MEOW_LAZYGRID_POOL_MAX; + return cap; +} +/* 确保池容量 ≥ cap(有界扩张;重分配清空绑定 → 下一帧按需重绑)。返回 0=成功;-1=分配失败。 */ +static int lg_ensure_pool(meow_lazygrid *g) +{ + if (!g->count_fn || !g->bind) return 0; + int cap = lg_pool_cap(g); + if (g->pool && g->pool_n >= cap) return 0; + meow_lazygrid_item *np = (meow_lazygrid_item *)calloc((size_t)cap, sizeof *np); + int *ni = (int *)calloc((size_t)cap, sizeof *ni); + if (!np || !ni) { free(np); free(ni); return -1; } + for (int i = 0; i < cap; i++) ni[i] = -1; + free(g->pool); free(g->pool_idx); + g->pool = np; g->pool_idx = ni; g->pool_n = cap; + return 0; +} +/* 取 index 对应的池槽(按 item index mod pool_n 映射;槽绑定不匹配 → 防御性重绑)。 */ +static meow_lazygrid_item *lg_slot(meow_lazygrid *g, int index) +{ + if (!g->pool || g->pool_n <= 0) return NULL; + int s = (int)((unsigned)index % (unsigned)g->pool_n); + if (g->pool_idx[s] != index) { + memset(&g->pool[s], 0, sizeof g->pool[s]); + g->bind(index, &g->pool[s], g->vctx); + g->pool_idx[s] = index; + } + return &g->pool[s]; +} + +/* ---- 数据源 + 渲染 ---- */ +int meow_lazygrid_set_vsource(meow_lazygrid *g, meow_lazygrid_count_fn count, + meow_lazygrid_bind_fn bind, void *ctx) +{ + if (!g || !count || !bind) return -1; + g->count_fn = count; g->bind = bind; g->vctx = ctx; + if (lg_ensure_pool(g) != 0) { /* 分配固定 item 池(有界,冷路径) */ + g->count_fn = NULL; g->bind = NULL; /* 池分配失败:回退为空 */ + return -1; + } + return 0; +} +int meow_lazygrid_set_item_draw(meow_lazygrid *g, meow_lazygrid_draw_fn draw, + void *ctx) +{ + if (!g) return -1; + g->draw_cb = draw; g->draw_ctx = ctx; /* NULL=用默认卡片 renderer */ + return 0; +} +int meow_lazygrid_set_empty_text(meow_lazygrid *g, const char *text) +{ + if (!g) return -1; + if (text) { + strncpy(g->empty_text, text, sizeof g->empty_text - 1); + g->empty_text[sizeof g->empty_text - 1] = '\0'; + } else { + g->empty_text[0] = '\0'; + } + return 0; +} + +/* ---- 滚动 ---- */ +int meow_lazygrid_max_scroll(meow_lazygrid *g) +{ + if (!g) return 0; + int ch = lg_content_h(g); + int ms = ch - g->h; + return ms > 0 ? ms : 0; +} +void meow_lazygrid_set_scroll(meow_lazygrid *g, int off) +{ + if (!g) return; + int ms = meow_lazygrid_max_scroll(g); + if (off < 0) off = 0; + if (off > ms) off = ms; + g->scroll = off; +} +void meow_lazygrid_scroll_by(meow_lazygrid *g, int dy) +{ if (g) meow_lazygrid_set_scroll(g, g->scroll + dy); } +int meow_lazygrid_scroll(meow_lazygrid *g) { return g ? g->scroll : 0; } + +/* ---- meow_scroll 集成(callbacks 指回本宫格)---- */ +static void lg_s_set(void *c, int off) { meow_lazygrid_set_scroll((meow_lazygrid *)c, off); } +static int lg_s_max(void *c) { return meow_lazygrid_max_scroll((meow_lazygrid *)c); } +static int lg_s_get(void *c) { return meow_lazygrid_scroll((meow_lazygrid *)c); } +static void lg_s_redraw(void *c) +{ + meow_lazygrid *g = (meow_lazygrid *)c; + if (g && g->w > 0 && g->h > 0) { + meow_lazygrid_draw(g, meow_theme_get()); + meow_ui_flush_region(g->x, g->y, g->w, g->h); /* 只刷视口区 */ + } +} +meow_scroll *meow_lazygrid_scroll_attach(meow_lazygrid *g) +{ + if (!g) return NULL; + return meow_scroll_create(lg_s_set, lg_s_max, lg_s_get, lg_s_redraw, g); +} + +/* ---- 选中 ---- */ +void meow_lazygrid_set_sel(meow_lazygrid *g, int idx) +{ + if (!g) return; + int n = lg_count(g); + if (idx < 0) { g->sel = -1; return; } + if (n > 0 && idx >= n) idx = n - 1; + g->sel = idx; +} +int meow_lazygrid_sel(const meow_lazygrid *g) { return g ? g->sel : -1; } +void meow_lazygrid_set_on_tap(meow_lazygrid *g, meow_lazygrid_tap_fn cb, void *ctx) +{ + if (!g) return; + g->tap_cb = cb; g->tap_ctx = ctx; +} +int meow_lazygrid_tap_at(meow_lazygrid *g, int x, int y) +{ + int idx = meow_lazygrid_hit(g, x, y); + if (idx < 0) return -1; + meow_lazygrid_set_sel(g, idx); + if (g->tap_cb) g->tap_cb(idx, g->tap_ctx); + return idx; +} + +/* ---- 命中(滚动 + 网格坐标 → item idx)---- + * 点在格内(跨过 gap 区域返回 -1);禁用格不响应(同 listview enabled=0 语义)。 */ +int meow_lazygrid_hit(meow_lazygrid *g, int x, int y) +{ + if (!g) return -1; + if (x < g->x || x >= g->x + g->w || y < g->y || y >= g->y + g->h) return -1; + /* 列格(水平):col = px / (cell_w+gap),落点在 gap 内 → 未命中。 */ + int px = x - g->x; + int cstep = g->cell_w + g->gap; + int col = px / cstep; + if (col < 0 || col >= g->cols) return -1; + if ((px - col * cstep) >= g->cell_w) return -1; + /* 行格(垂直,含滚动):row = (py+scroll)/row_h;落点在 gap 内 → 未命中。 */ + int py = y - g->y + g->scroll; + int rstep = g->row_h; + int row = py / rstep; + if (row < 0) return -1; + if ((py - row * rstep) >= g->cell_h) return -1; + int idx = row * g->cols + col; + int n = lg_count(g); + if (idx < 0 || idx >= n) return -1; + /* 禁用格不响应(顺手 bind 临时 enabled 查:与 listview hit 同法)。 */ + if (g->bind) { + meow_lazygrid_item it; + memset(&it, 0, sizeof it); + g->bind(idx, &it, g->vctx); + if (!it.enabled) return -1; + } + return idx; +} + +/* ---- 绘制(只画可见行;clip 钳到宫格视口)---- */ +static void lg_draw_cell(meow_lazygrid *g, const meow_theme *th, int index, + int cell_x, int cell_y, meow_lazygrid_item *it) +{ + int sel = (index == g->sel); + int txtcol = sel ? th->onPrimaryContainer : th->onSurface; + int subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant; + uint32_t bg = sel ? th->primaryContainer : th->surfaceContainerLow; + meow_gfx_fill_round(cell_x, cell_y, g->cell_w, g->cell_h, th->radius_card, bg); + + int pad = 8; + int thh = meow_ui_text_height(th->font_body); + int chh = g->cell_h, cw = g->cell_w; + /* 图标(可选):居中于格顶 ~50%。 */ + if (it->icon) { + int isz = cw * 6 / 10; if (isz > chh * 5 / 10) isz = chh * 5 / 10; + if (isz < 16) isz = 16; + int icx = cell_x + cw / 2, icy = cell_y + pad + isz / 2; + meow_ui_img_scaled(icx - isz / 2, icy - isz / 2, isz, isz, (meow_img *)it->icon); + } + /* 标题 + 副标题(主界面:名 + 状态)。底部对齐。 */ + int has_sub = (it->subtitle && it->subtitle[0]); + int ty = cell_y + chh - pad - thh - (has_sub ? (thh + 2) : 0); + if (it->title) meow_ui_text_center(cell_x + cw / 2, ty, it->title, th->font_body, txtcol); + if (has_sub) meow_ui_text_center(cell_x + cw / 2, ty + thh + 2, it->subtitle, th->font_body, subcol); + + /* 选中描边(轮廓);禁用整体变淡。 */ + if (sel) meow_ui_draw_rect_round(cell_x, cell_y, cw, chh, th->radius_card, th->primary); + if (!it->enabled) + meow_ui_fill_rect_alpha(cell_x, cell_y, cw, chh, th->onSurface, 90); +} + +void meow_lazygrid_draw(meow_lazygrid *g, const meow_theme *th) +{ + if (!g || !th) return; + meow_gfx_clip_push(g->x, g->y, g->w, g->h); /* 格画不出宫格视口(防泄漏) */ + + int n = lg_count(g); + int rows = lg_rows(g); + lg_ensure_pool(g); /* 防御:视口/列几何变化时确保池容量 */ + + /* 空态占位:count==0 且设 empty_text → 视口内居中。 */ + if (n == 0 && g->empty_text[0]) { + int thh = meow_ui_text_height(th->font_body); + meow_ui_text_center(g->x + g->w / 2, g->y + (g->h - thh) / 2, g->empty_text, + th->font_body, th->onSurfaceVariant); + meow_gfx_clip_pop(); + return; + } + + int off = g->scroll; + int cstep = g->cell_w + g->gap; + int rstep = g->row_h; + if (rstep <= 0) rstep = 1; + int first = off / rstep; + int last = (off + g->h) / rstep + 1; if (last > rows) last = rows; + if (first < 0) first = 0; + if (last > rows) last = rows; + for (int r = first; r < last; r++) { + int ry = g->y + (r * rstep - off); /* 本行格顶视口坐标 */ + for (int c = 0; c < g->cols; c++) { + int index = r * g->cols + c; + if (index >= n) break; /* 末行余下空格 */ + int cx = g->x + c * cstep; + /* 行完全滚出视口(裁剪由 clip 兜底,这里跳过整行) */ + if (ry >= g->y + g->h) break; + if (ry + g->cell_h <= g->y) continue; + + if (g->draw_cb) { + int st = (index == g->sel) ? MEOW_LAZYGRID_ST_SELECTED : 0; + g->draw_cb(index, cx, ry, g->cell_w, g->cell_h, st, g->draw_ctx); + } else { + meow_lazygrid_item *it = lg_slot(g, index); + if (!it) continue; + lg_draw_cell(g, th, index, cx, ry, it); + } + } + } + + meow_gfx_clip_pop(); +} diff --git a/patch_v2/sdk/meow_select.c b/patch_v2/sdk/meow_select.c new file mode 100644 index 0000000..2a26d1f --- /dev/null +++ b/patch_v2/sdk/meow_select.c @@ -0,0 +1,157 @@ +/* + * meow_select.c — §5.8.6 枚举/预设选择器(M7.2 设置页缺口 #4)。 + * + * 封装 = BottomSheet(标题 + 选项列表)+ meow_listview(SINGLE) 单选列表。选项用 meow_listview + * 渲染(单选高亮 primaryContainer、可滚动 meow_scroll 惯性);外层模态复用 meow_ui_modal_* + * + meow_ui_modal_capture/restore(与 meow_dialog 同源:scrim 只叠一层/关闭无残影)。 + * 阻塞式(meow_input_pop 轮询);打开前排空残留触摸;点面板外/左缘返回 = 取消;点选项 = 选中 + + * 关闭 + on_change 回调。选项数组定长(≤MEOW_SELECT_MAX_ITEMS)。 + */ +#include "meow_ui.h" +#include "include/meow_select.h" +#include "meow_ui_internal.h" /* meow_input_pop/drain + meow_ui_modal_capture/restore */ +#include +#include +#include + +#define MEOW_SELECT_ROW 48 /* 选项行高(px) */ + +struct meow_select { + char title[96]; + char items[MEOW_SELECT_MAX_ITEMS][64]; + int nitems; + int cur; /* 初始/当前选中项 */ + int result; /* 选中下标 / -1=取消 */ + int done; + int h; /* 面板高度(0=自动) */ + meow_select_fn cb; + void *ctx; +}; + +/* meow_listview src2 数据源:填 title=options[idx](右尾件无;enabled=1)。 */ +static int select_src2(int idx, meow_list_item *out, void *ctx) +{ + meow_select *s = (meow_select *)ctx; + if (idx < 0 || idx >= s->nitems) return 0; + memset(out, 0, sizeof *out); + out->title = s->items[idx]; + out->right = MEOW_LIST_RIGHT_NONE; + out->enabled = 1; + return 1; +} + +meow_select *meow_select_create(const char *title, int cur) +{ + meow_select *s = (meow_select *)calloc(1, sizeof *s); + if (!s) return NULL; + if (title) { strncpy(s->title, title, sizeof s->title - 1); s->title[sizeof s->title - 1] = '\0'; } + s->cur = cur; s->result = -1; s->done = 0; + return s; +} +int meow_select_add_item(meow_select *s, const char *label) +{ + if (!s || s->nitems >= MEOW_SELECT_MAX_ITEMS) return -1; + if (label) { strncpy(s->items[s->nitems], label, 63); s->items[s->nitems][63] = '\0'; } + int idx = s->nitems; + s->nitems++; + return idx; +} +void meow_select_set_on_change(meow_select *s, meow_select_fn cb, void *ctx) +{ if (s) { s->cb = cb; s->ctx = ctx; } } + +int meow_select_open(meow_select *s) +{ + if (!s || s->nitems <= 0) return -1; + const meow_theme *th = meow_theme_get(); + meow_input_start(); + meow_input_drain(); /* 排空残留触摸(goodix 长尾) */ + + s->done = 0; s->result = -1; + int W = meow_ui_width(), H = meow_ui_height(); + int sb = meow_ui_statusbar_height(); if (sb < 0) sb = 0; + int pad = 24; + int title_h = meow_ui_text_height(th->font_title); + int rowh = MEOW_SELECT_ROW; + int avail = H - sb - 20; + int min_h = pad + title_h + 12 + rowh + 20; + int want = (s->h > 0) ? s->h : min_h; + int est = pad + title_h + 12 + s->nitems * rowh + 20; + if (est > want) want = est; + if (want > avail) want = avail; + if (want < min_h) want = (min_h < avail) ? min_h : avail; + int ph = want, py = H - ph; /* 底部面板(顶部圆角) */ + int list_y = py + pad + title_h + 12; + int list_h = ph - (list_y - py) - pad; /* 底部余量 */ + if (list_h < rowh) list_h = rowh; + + /* 选项列表(meow_listview[SINGLE]:单选高亮 + 可滚动)。open 时创建,close 释放(冷路径)。 */ + meow_widget *lv = meow_listview_create(0, list_y, W, list_h, s->nitems, + select_src2, s, MEOW_LIST_SEL_SINGLE); + if (!lv) return -1; + meow_listview_set_item_h(lv, rowh); + /* 定位初始选中项在视口内。 */ + int target = s->cur * rowh - (list_h - rowh) / 2; + int maxs = meow_listview_max_scroll(lv); + if (target < 0) target = 0; + if (target > maxs) target = maxs; + meow_listview_set_scroll(lv, target); + meow_listview_set_sel(lv, s->cur); + meow_scroll *sc = meow_scroll_wrap(lv, 1); /* 惯性滚动(fling/snap) */ + + /* §6.4 模态作用域 + 背景快照(关闭即还原,无残影/不叠黑)。 */ + meow_ui_modal *m = meow_ui_modal_begin(); + meow_ui_modal_capture(); + /* 首次绘制:遮罩 + 底部面板(顶部圆角)+ 标题 + 选项列表。 */ + meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150); + meow_gfx_fill_round(0, py, W, ph, th->radius_card, th->surfaceContainerHighest); + meow_ui_text(pad, py + pad, s->title, th->font_title, th->onSurface); + meow_widget_draw(lv); + meow_ui_flush(); + + int dy_start = 0, down = 0; + while (!s->done) { + meow_input_ev e; + while (meow_input_pop(&e)) { + meow_ui_activity_ping(); /* 模态期间交互也重置息屏计时 */ + if (e.type == MEOW_EV_TOUCH_DOWN) { down = 1; dy_start = e.b; } + else if (e.type == MEOW_EV_TOUCH_MOVE) { + if (down && sc) { + meow_scroll_track(sc, e.b - dy_start, meow_ui_time_ms()); + dy_start = e.b; + } + } else if (e.type == MEOW_EV_TAP) { + int x = e.a, y = e.b; + if (y < py) { s->done = 1; s->result = -1; break; } /* 点面板外 = 取消 */ + int idx = meow_listview_tap_at(lv, x, y); /* 单选高亮 = 选中 */ + if (idx >= 0) { + s->result = idx; s->cur = idx; + if (s->cb) s->cb(idx, s->ctx); /* 关闭即回调 */ + s->done = 1; + } + /* 面板内但未命中项:保持打开(等下一次点击)。 */ + } else if (e.type == MEOW_EV_SWIPE) { + if (e.a == MEOW_SWIPE_LEFT) { s->done = 1; s->result = -1; } /* 返回 = 取消 */ + else if (sc) meow_scroll_fling(sc); + } + } + meow_anim_tick(); /* 推进 fling */ + if (!s->done) usleep(16000); + } + meow_ui_modal_end(m); + meow_ui_modal_restore(); + if (sc) meow_scroll_free(sc); + meow_widget_free(lv); + return s->result; +} +void meow_select_free(meow_select *s) { if (s) free(s); } + +int meow_select_show(const char *title, const char *const *opts, int n, int cur) +{ + if (!opts || n <= 0) return -1; + meow_select *s = meow_select_create(title, cur); + if (!s) return -1; + for (int i = 0; i < n && i < MEOW_SELECT_MAX_ITEMS; i++) meow_select_add_item(s, opts[i]); + int r = meow_select_open(s); + meow_select_free(s); + return r; +} diff --git a/patch_v2/sdk/meow_ui.h b/patch_v2/sdk/meow_ui.h index 1c7c1a0..f85e1c3 100644 --- a/patch_v2/sdk/meow_ui.h +++ b/patch_v2/sdk/meow_ui.h @@ -37,6 +37,8 @@ * include/meow_ui_box.h 线性布局引擎(§12.1:meow_box,固定/weight/wrap/对齐/嵌套) * include/meow_ui_anim.h 动画基座+UI 节拍器(§12.2:tween/easing/池+脏区联动) * include/meow_ui_scroll.h 滚动物理(§12.3:速度跟踪/fling/边界回弹/snap) + * include/meow_lazygrid.h 2 维虚拟化宫格(§12.4:meow_lazygrid,M7.4 主界面前置) + * include/meow_select.h 枚举/预设选择器(M7.2 设置页缺口 #4:BottomSheet+单选列表) * include/meow_ui_str.h 字符串资源/i18n(§13:meow_str/meow_strf/meow_str_set_locale) * * 其余模块头(非伞头、App 按需显式 include,均保持不动): @@ -70,7 +72,10 @@ #include "include/meow_ui_box.h" #include "include/meow_ui_anim.h" #include "include/meow_ui_scroll.h" +#include "include/meow_lazygrid.h" +#include "include/meow_select.h" #include "include/meow_ui_str.h" #include "include/meow_ui_net.h" /* 期4 网络自愿通道:meow_net_policy_check / meow_net_report */ +#include "include/meow_cachegov.h" /* 磁盘缓存 page-cache 治理(A:DONTNEED L0/L1 + refault 回退;C 归 svc_perf) */ #endif /* MEOW_UI_H */ diff --git a/patch_v2/sdk/meow_ui_dialog.c b/patch_v2/sdk/meow_ui_dialog.c index 5987bf9..ea70595 100644 --- a/patch_v2/sdk/meow_ui_dialog.c +++ b/patch_v2/sdk/meow_ui_dialog.c @@ -27,7 +27,7 @@ static unsigned char *g_modal_cap = NULL; static int g_modal_cap_line = 0, g_modal_cap_h = 0, g_modal_cap_bpp = 0; -static void modal_capture(void) +void meow_ui_modal_capture(void) { unsigned char *bb = meow_ui_back_buf(); int line = meow_ui_back_buf_pitch(); @@ -43,7 +43,7 @@ static void modal_capture(void) if (g_modal_cap) memcpy(g_modal_cap, bb, n); } -static void modal_restore(void) +void meow_ui_modal_restore(void) { unsigned char *bb = meow_ui_back_buf(); if (!g_modal_cap || !bb) return; @@ -235,7 +235,7 @@ void meow_dialog_show(meow_dialog *d) /* §6.4:模态作用域——本层打开期间下层(view/widget)完全收不到触摸;点卡片外由背板消费 * (d->done=1 result=0 取消),不透传。嵌套弹层 depth 递增。 */ meow_ui_modal *m = meow_ui_modal_begin(); - modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原(scrim 只叠一层/无残影); + meow_ui_modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原(scrim 只叠一层/无残影); * 嵌套模态在各自打开时拍「当前已渲染帧」,故两层弹开不叠黑。 */ dlg_paint(d, th); meow_ui_flush(); @@ -291,7 +291,7 @@ void meow_dialog_show(meow_dialog *d) usleep(16000); } meow_ui_modal_end(m); /* §6.4:关闭模态作用域(下层恢复接收触摸) */ - modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */ + meow_ui_modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */ } int meow_dialog_result(meow_dialog *d) { return d ? d->result : 0; } @@ -355,7 +355,7 @@ int meow_sheet_open(meow_sheet *s) int ph = sheet_h(s, th); int py = H - ph, pad = 24; meow_ui_modal *m = meow_ui_modal_begin(); /* §6.4:模态作用域(下层收不到触摸) */ - modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原(scrim 只叠一层/无残影) */ + meow_ui_modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原(scrim 只叠一层/无残影) */ /* 首次绘制:遮罩 + 底部面板(顶部圆角) */ meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150); meow_gfx_fill_round(0, py, W, ph, th->radius_card, th->surfaceContainerHighest); @@ -389,7 +389,7 @@ int meow_sheet_open(meow_sheet *s) usleep(16000); } meow_ui_modal_end(m); /* §6.4:关闭模态作用域 */ - modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */ + meow_ui_modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */ return s->result; } void meow_sheet_free(meow_sheet *s) { if (s) free(s); } diff --git a/patch_v2/sdk/meow_ui_gfx.c b/patch_v2/sdk/meow_ui_gfx.c index d805725..697ab0b 100644 --- a/patch_v2/sdk/meow_ui_gfx.c +++ b/patch_v2/sdk/meow_ui_gfx.c @@ -979,6 +979,9 @@ static int statusbar_touched(void) * App 进程(非系统 UI 渲染者)此时不上屏;200ms 节流缓存存在性(别每帧 stat)。 * 计数 g_overlay_suppressed 供宿主单测断言(meow_gfx_overlay_suppressed_count)。 */ static unsigned long long g_overlay_suppressed = 0; +/* §后台化保活-残帧:记录「上一次 present 是否处于叠加自抑态」,用于在抑制→不抑制的恢复边沿 + * 强制整帧重画(盖掉 launcher 在后台化期间画进 buffer0 的桌面/主界面帧)。 */ +static int g_was_overlay_suppressed = 0; static int overlay_present_suppressed(void) { @@ -1009,7 +1012,18 @@ static void present(void) * (保 PCM 供流,ncm 播放线程同进程),改由 App 进程在此自抑上屏——绘制照常进 back_buf, * 只是不 present 到 fb,防 App 重绘穿透 launcher 叠加层。系统 UI 进程(launcher 渲染面板) * 不抑制。抑制期脏区保留累积(超上限自然升级整帧),标记消失后下一次 present 一次上屏。 */ - if (overlay_present_suppressed()) { g_overlay_suppressed++; return; } + if (overlay_present_suppressed()) { g_overlay_suppressed++; g_was_overlay_suppressed = 1; return; } + /* §后台化保活-残帧:抑制→不抑制的**恢复边沿**强制整帧重画。App 被 launcher 转后台保活 + * (/tmp/meow_notif_active 存在)期间 present 被自抑,back_buf 仍保留 App 自己的完整帧; + * 但 launcher 已把桌面/主界面整帧画进 buffer0 盖在 App 残帧上。恢复前台时若只 present + * 抑制期累积的小脏区(时钟/状态栏),launcher 的桌面帧会在 App 未重画区域透出(真机 + * 「桌面叠在 ncm UI 下面/旧播放界面层叠」。标记由有→无即「回到前台」,强制 dirty_full + * 让下一次 present 把 App 完整 back_buf 一次上屏,盖掉桌面帧。仅真实 App 进程(launcher + * 系统 UI 恒不抑制,本边沿不触发)。 */ + if (g_was_overlay_suppressed) { + g_was_overlay_suppressed = 0; + dirty_full(); + } /* 帧率门控(A-1/A-2,从 launcher 迁入):present 距上次 ≥ period(1000/eff_rate) 才呈现; * 未满周期则保留脏区(不 dirty_reset),下一轮再试。refresh_always_max=1 时跳过门控(恒满速)。 */ unsigned long long now = now_ms(); diff --git a/patch_v2/sdk/meow_ui_internal.h b/patch_v2/sdk/meow_ui_internal.h index 04468fd..c38375e 100644 --- a/patch_v2/sdk/meow_ui_internal.h +++ b/patch_v2/sdk/meow_ui_internal.h @@ -131,6 +131,13 @@ void meow_gfx_mark_full(void); * 菜单内容盖到 App 内容上)——叠加前用本函数撤销整帧脏,再按需标局部脏(agent-146)。 */ void meow_gfx_dirty_reset(void); +/* ---- 模态背景快照/恢复(meow_ui_dialog.c 提供;dialog/sheet/select 共用)---- + * open 前调用 capture 把整帧背景拍下(modal_capture),close 后调用 restore 回填 + 整帧上屏 + * (modal_restore)→ scrim 每次只叠一层、关闭即还原无残影(M2.71-bug3)。共享同一份快照缓冲, + * 各模态组件(meow_select 等)无需重复实现。 */ +void meow_ui_modal_capture(void); +void meow_ui_modal_restore(void); + /* 返回离屏 back_buf 指针(launcher 系统组件与 SDK 共用同一 back_buf 用)。 */ unsigned char *meow_ui_back_buf(void); /* back_buf 的每行字节数(pitch)与位深(16/32)。M2.71-bug3:模态/IME 快照恢复用。 */ diff --git a/patch_v2/sdk/meow_ui_widget.c b/patch_v2/sdk/meow_ui_widget.c index 86bdc35..5d14e92 100644 --- a/patch_v2/sdk/meow_ui_widget.c +++ b/patch_v2/sdk/meow_ui_widget.c @@ -116,6 +116,21 @@ struct meow_widget { int show_icon; /* 1=画左图标/缩略图(默认 1) */ int show_right;/* 1=画右尾件(默认 1) */ char empty_text[64]; /* 空态占位文案(首字符 0=不画) */ + + /* §5.5 左滑删除(列表行横向手势:滑动偏移/删除动画/回弹;calloc 归零,安全)。 + * 手势由 App 在 on_touch_move 喂(swipe_begin/move/end);横向小位移判 swipe、纵向滚动优先; + * 行自绘 set_item_draw 配合(M7.3 通知行)。swipe_anim 由 meow_anim 静态池驱动。 */ + int swipe_enabled; /* 1=左滑删除可用(on_delete 回调有效) */ + int swipe_active; /* 1=当前行处于滑动态(swipe_idx 有效) */ + int swipe_idx; /* 正被滑动的行号(-1=无) */ + int swipe_dx; /* 当前行横向偏移(<=0=左移;滑动/动画中) */ + int swipe_drag; /* 1=手指拖动中(限行内) */ + int swipe_start_x, swipe_start_y; /* 手势起点(判定方向用) */ + int swipe_started; /* 1=已裁定为横向 swipe(纵向滚动手势被抑制) */ + int swipe_anim_delete;/* 1=删除动画(目标 -w);0=回弹(目标 0) */ + meow_listview_swipe_fn swipe_cb; /* 删除回调(idx) */ + void *swipe_ctx; + meow_anim *swipe_anim; /* 滑动/回弹动画(非 NULL=进行中) */ }; /* ---- §5.8 batch1 内部辅助:opts 取值(opts_used==0 时按控件内建默认)---- @@ -1161,7 +1176,7 @@ meow_widget *meow_listview_create(int x, int y, int w, int h, int count, t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; t->count = count > 0 ? count : 0; t->src2 = src; t->ctx = ctx; - t->sel = -1; t->item_h = 56; t->scroll = 0; + t->sel = -1; t->item_h = 56; t->scroll = 0; t->swipe_idx = -1; t->sel_mode = (mode >= MEOW_LIST_SEL_NONE && mode <= MEOW_LIST_SEL_MULTI) ? mode : MEOW_LIST_SEL_NONE; t->lines = 2; t->show_icon = 1; t->show_right = 1; /* 默认双行富 item + 图标 + 右尾件(src2 布局不变) */ if (t->sel_mode == MEOW_LIST_SEL_MULTI && t->count > 0) { @@ -1393,6 +1408,127 @@ int meow_listview_set_empty_text(meow_widget *w, const char *text) return 0; } +/* ============================================================ */ +/* §5.5 左滑删除(M7.3 缺口 #9):手势判定 + 删除/回弹动画 */ +/* ============================================================ */ + +#define MEOW_SWIPE_SLOP 10 /* 手势判定容差(px):超过才裁定方向 */ +/* 删除阈值:|dx| ≥ 行宽*2/5。 */ +static int swipe_thresh(const meow_widget *w) { return w->w * 2 / 5; } + +/* 动画更新:设行偏移 + 重绘列表(只画可见行;widget clip 已裁剪)。 */ +static void swipe_anim_update(meow_anim *a, float v, void *ctx) +{ + (void)a; + meow_widget *lv = (meow_widget *)ctx; + if (!lv) return; + lv->swipe_dx = (int)v; + meow_widget_draw(lv); +} +static void swipe_anim_done(meow_anim *a, int canceled, void *ctx) +{ + (void)a; + meow_widget *lv = (meow_widget *)ctx; + if (!lv) return; + int del = lv->swipe_anim_delete; + int idx = lv->swipe_idx; + lv->swipe_anim = NULL; + lv->swipe_anim_delete = 0; + lv->swipe_active = 0; lv->swipe_idx = -1; lv->swipe_dx = 0; + if (canceled) return; /* 取消:回正常位(无回调) */ + if (del && lv->swipe_cb) /* 删除:滑出完成 → 回调(App 移除行 + 刷新 count) */ + lv->swipe_cb(idx, lv->swipe_ctx); +} + +void meow_listview_set_swipe_delete(meow_widget *lv, int enabled, + meow_listview_swipe_fn on_delete, void *ctx) +{ + if (!lv || lv->type != W_LISTVIEW) return; + if (lv->swipe_anim) { meow_anim_cancel(lv->swipe_anim); lv->swipe_anim = NULL; } + lv->swipe_enabled = enabled ? 1 : 0; + lv->swipe_cb = on_delete; lv->swipe_ctx = ctx; + lv->swipe_active = 0; lv->swipe_idx = -1; lv->swipe_dx = 0; lv->swipe_drag = 0; lv->swipe_started = 0; +} + +int meow_listview_swipe_begin(meow_widget *lv, int x, int y) +{ + if (!lv || lv->type != W_LISTVIEW || !lv->swipe_enabled) return 0; + int idx = meow_listview_hit(lv, x, y); + if (idx < 0) return 0; + if (lv->swipe_anim) return 1; /* 已有动画:吞掉,不干扰当前行 */ + lv->swipe_drag = 1; lv->swipe_started = 0; + lv->swipe_idx = idx; lv->swipe_dx = 0; + lv->swipe_start_x = x; lv->swipe_start_y = y; + return 1; +} + +int meow_listview_swipe_move(meow_widget *lv, int x, int y) +{ + if (!lv || lv->type != W_LISTVIEW || !lv->swipe_drag) return 0; + int dx = x - lv->swipe_start_x, dy = y - lv->swipe_start_y; + int adx = dx < 0 ? -dx : dx, ady = dy < 0 ? -dy : dy; + if (!lv->swipe_started) { + if (ady > adx && ady > MEOW_SWIPE_SLOP) { /* 纵向优先:取消 swipe,让位滚动 */ + lv->swipe_drag = 0; lv->swipe_active = 0; lv->swipe_idx = -1; lv->swipe_dx = 0; + return 0; + } + if (adx > MEOW_SWIPE_SLOP && adx > ady) { /* 横向小位移:裁定为 swipe */ + lv->swipe_started = 1; lv->swipe_active = 1; + } else { + return 0; /* 太小未裁定:暂不消费 */ + } + } else { + if (ady > adx && ady > MEOW_SWIPE_SLOP) { /* 中途纵向接管:取消,让位滚动 */ + lv->swipe_drag = 0; lv->swipe_active = 0; lv->swipe_idx = -1; lv->swipe_dx = 0; + return 0; + } + } + /* 只左滑(dx<0)露右删除区;右滑 clamp 到 0;触底 clamp 到 -w。 */ + int off = dx < 0 ? dx : 0; + if (off < -lv->w) off = -lv->w; + if (off == lv->swipe_dx) return 1; + lv->swipe_dx = off; + meow_gfx_mark_dirty(lv->x, lv->y, lv->w, lv->h); + return 1; +} + +int meow_listview_swipe_end(meow_widget *lv, int x, int y) +{ + (void)x; (void)y; + if (!lv || lv->type != W_LISTVIEW) return 0; + if (!lv->swipe_drag) return 0; /* 非 swipe 行(tap/滚动)→ App 自理 */ + lv->swipe_drag = 0; + if (!lv->swipe_started) return 0; /* 无位移 = 点击 → App 走 tap_at */ + int dx = lv->swipe_dx; + int del = (-dx >= swipe_thresh(lv)); /* 超阈值 → 删除;否则回弹 */ + int item = listv_item_h(lv); + int ry = lv->y + ((unsigned)lv->swipe_idx * (unsigned)item - lv->scroll); + if (lv->swipe_anim) { meow_anim_cancel(lv->swipe_anim); lv->swipe_anim = NULL; } + lv->swipe_anim_delete = del; + meow_anim_desc d; memset(&d, 0, sizeof d); + d.from = (float)dx; + d.to = (float)(del ? -lv->w : 0); + d.duration_ms = del ? 160u : 140u; + d.ease = del ? MEOW_EASE_OUT : MEOW_EASE_OVERSHOOT; + d.update = swipe_anim_update; d.done = swipe_anim_done; d.ctx = lv; + meow_anim *an = meow_anim_start(&d); + if (!an) { /* 池满:直接终态(防卡死) */ + if (del) { int i = lv->swipe_idx; lv->swipe_idx = -1; lv->swipe_dx = 0; + if (lv->swipe_cb) lv->swipe_cb(i, lv->swipe_ctx); } + lv->swipe_active = 0; lv->swipe_dx = 0; + meow_gfx_mark_dirty(lv->x, lv->y, lv->w, lv->h); + return 1; + } + lv->swipe_anim = an; + meow_anim_set_region(an, lv->x, ry < lv->y ? lv->y : ry, lv->w, item); /* 动画帧只刷该行带 */ + return 1; +} + +int meow_listview_swipe_active(meow_widget *lv) +{ return (lv && lv->type == W_LISTVIEW && (lv->swipe_active || lv->swipe_anim)) ? 1 : 0; } +int meow_listview_swipe_offset(meow_widget *lv) +{ return (lv && lv->type == W_LISTVIEW) ? lv->swipe_dx : 0; } + static int hit(meow_widget *w, int x, int y) { if (!w) return 0; @@ -1669,7 +1805,17 @@ void meow_widget_draw(meow_widget *w) meow_input_touch_x() >= vx0 && meow_input_touch_x() < vx0 + w->w && meow_input_touch_y() >= ry && meow_input_touch_y() < ry + item) st |= MEOW_LISTVIEW_ST_PRESSED; - w->vs_draw(i, vx0, ry, w->w, item, st, w->vs_ctx); + int rx0 = vx0; /* 左滑行:行 x 左移 + 右露删除区 */ + if (w->swipe_active && w->swipe_idx == i && w->swipe_dx < 0) { + meow_ui_fill_rect(vx0 + w->w + w->swipe_dx, ry, -w->swipe_dx, item, th->error); + int thh2 = meow_ui_text_height(th->font_body); + int dly = ry + (item - thh2) / 2; + if (dly >= vy0 && dly + thh2 <= vy1) + meow_ui_text_center(vx0 + w->w + w->swipe_dx + (-w->swipe_dx) / 2, dly, + meow_str("common.delete"), th->font_body, th->onError); + rx0 = vx0 + w->swipe_dx; + } + w->vs_draw(i, rx0, ry, w->w, item, st, w->vs_ctx); } } else { /* ② 默认 renderer:固定 item 池(进入视口才 bind) */ for (int i = first; i < last; i++) { @@ -1684,23 +1830,37 @@ void meow_widget_draw(meow_widget *w) int press = meow_input_touch_down() && meow_input_touch_x() >= vx0 && meow_input_touch_x() < vx0 + w->w && meow_input_touch_y() >= ry && meow_input_touch_y() < ry + item; + /* 左滑删除(M7.3):本行被滑动 → 右露删除区 + 行 x 左移。非滑动行 shift=0 → 输出不变。 */ + int shift = 0; + if (w->swipe_active && w->swipe_idx == i && w->swipe_dx < 0) { + meow_ui_fill_rect(vx0 + w->w + w->swipe_dx, ry, -w->swipe_dx, item, th->error); + int thh2 = meow_ui_text_height(th->font_body); + int dly = ry + (item - thh2) / 2; + if (dly >= vy0 && dly + thh2 <= vy1) + meow_ui_text_center(vx0 + w->w + w->swipe_dx + (-w->swipe_dx) / 2, dly, + meow_str("common.delete"), th->font_body, th->onError); + shift = w->swipe_dx; + } + int rlx = vx0 + shift; /* 行内容左移后的 x */ + int rtx = text_x + shift; /* 文本 x(随行左移) */ + int rop = op_start + shift; /* 操作区分界 x */ if (it->section) { /* 段头占位行(非粘性):surfaceContainerHigh 底 + primary 段名 */ - meow_ui_fill_rect(vx0, cl, w->w, cr - cl, th->surfaceContainerHigh); + meow_ui_fill_rect(rlx, cl, w->w, cr - cl, th->surfaceContainerHigh); int sty = ry + (item - thh) / 2; if (it->title && sty >= vy0 && sty + thh <= vy1) - listv_text_trunc(text_x, sty, it->title, text_maxw, th->font_body, th->primary); + listv_text_trunc(rtx, sty, it->title, text_maxw, th->font_body, th->primary); int sy = ry + item - 1; if (sy >= cl && sy < cr) - meow_ui_fill_rect(vx0, sy, w->w, 1, th->outlineVariant); + meow_ui_fill_rect(rlx, sy, w->w, 1, th->outlineVariant); if (!it->enabled) - meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->onSurface, 90); + meow_ui_fill_rect_alpha(rlx, cl, w->w, cr - cl, th->onSurface, 90); continue; } /* 选中/按压背景(primaryContainer / 半透明主题色按压层) */ - if (sel) meow_ui_fill_rect(vx0, cl, w->w, cr - cl, th->primaryContainer); - else if (press) meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->primary, 26); + if (sel) meow_ui_fill_rect(rlx, cl, w->w, cr - cl, th->primaryContainer); + else if (press) meow_ui_fill_rect_alpha(rlx, cl, w->w, cr - cl, th->primary, 26); uint32_t textcol = sel ? th->onPrimaryContainer : th->onSurface; uint32_t subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant; @@ -1711,38 +1871,38 @@ void meow_widget_draw(meow_widget *w) : th->onSurfaceVariant); int icy = ry + (item - isz) / 2; if (icy + isz >= vy0 && icy < vy1) - listv_draw_icon(th, vx0 + pad, icy, it->icon_kind, it->icon, iccol); + listv_draw_icon(th, rlx + pad, icy, it->icon_kind, it->icon, iccol); } /* 标题 + 副标题(双行居中;单行(lines==1 或无副标题)单行居中);越界不画 */ if (w->lines == 2 && it->subtitle && it->subtitle[0]) { int ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2; if (it->title && ty >= vy0 && ty + thh <= vy1) - listv_text_trunc(text_x, ty, it->title, text_maxw, th->font_body, textcol); + listv_text_trunc(rtx, ty, it->title, text_maxw, th->font_body, textcol); int sy = ty + thh + MEOW_LISTV_SUB_GAP; if (sy >= vy0 && sy + thh <= vy1) - listv_text_trunc(text_x, sy, it->subtitle, text_maxw, th->font_body, subcol); + listv_text_trunc(rtx, sy, it->subtitle, text_maxw, th->font_body, subcol); } else { int ty = ry + (item - thh) / 2; if (it->title && ty >= vy0 && ty + thh <= vy1) - listv_text_trunc(text_x, ty, it->title, text_maxw, th->font_body, textcol); + listv_text_trunc(rtx, ty, it->title, text_maxw, th->font_body, textcol); } /* 右尾件(show_right=1:箭头/开关/复选/⋯) */ if (w->show_right && it->right != MEOW_LIST_RIGHT_NONE) { - int ax = w->x + w->w - pad - 24; /* 右缘动作槽(约 24px) */ - if (ax >= op_start) + int ax = rlx + w->w - pad - 24; /* 右缘动作槽(约 24px) */ + if (ax >= rop) listv_draw_right(th, ax, ry, item, it->right, it->on, subcol); } /* 分隔线(仅落在可见带内才画) */ int sy = ry + item - 1; if (sy >= cl && sy < cr) - meow_ui_fill_rect(vx0, sy, w->w, 1, th->outlineVariant); + meow_ui_fill_rect(rlx, sy, w->w, 1, th->outlineVariant); /* 禁用行整体变淡(§5.5/§5.8.3 enabled=0 → 变暗) */ if (!it->enabled) - meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->onSurface, 90); + meow_ui_fill_rect_alpha(rlx, cl, w->w, cr - cl, th->onSurface, 90); } } } else { @@ -1760,7 +1920,18 @@ void meow_widget_draw(meow_widget *w) int sel = (i == w->sel); uint32_t textcol = sel ? th->onPrimaryContainer : th->onSurface; uint32_t subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant; - if (sel) meow_ui_fill_rect(vx0, cl, w->w, cr - cl, th->primaryContainer); + /* 左滑删除(M7.3):本行被滑动 → 右露删除区 + 行 x 左移。非滑动行 shift=0 → 输出不变。 */ + int shift = 0, rlx = vx0, rtx = text_x, rop = op_start; + if (w->swipe_active && w->swipe_idx == i && w->swipe_dx < 0) { + meow_ui_fill_rect(vx0 + w->w + w->swipe_dx, ry, -w->swipe_dx, item, th->error); + int thh2 = meow_ui_text_height(th->font_body); + int dly = ry + (item - thh2) / 2; + if (dly >= vy0 && dly + thh2 <= vy1) + meow_ui_text_center(vx0 + w->w + w->swipe_dx + (-w->swipe_dx) / 2, dly, + meow_str("common.delete"), th->font_body, th->onError); + shift = w->swipe_dx; rlx = vx0 + shift; rtx = text_x + shift; rop = op_start + shift; + } + if (sel) meow_ui_fill_rect(rlx, cl, w->w, cr - cl, th->primaryContainer); /* 图标(左) */ uint32_t iccol = sel ? th->onPrimaryContainer @@ -1768,38 +1939,38 @@ void meow_widget_draw(meow_widget *w) : th->onSurfaceVariant); int icy = ry + (item - isz) / 2; if (icy + isz >= vy0 && icy < vy1) /* 图标不越视口边界才画(防泄漏) */ - listv_draw_icon(th, vx0 + pad, icy, it.icon_kind, it.icon, iccol); + listv_draw_icon(th, rlx + pad, icy, it.icon_kind, it.icon, iccol); /* 标题 + 副标题(两行居中;无副标题=单行居中;越界不画防泄漏) */ int ty; if (it.subtitle && it.subtitle[0]) { ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2; if (it.title && ty >= vy0 && ty + thh <= vy1) - listv_text_trunc(text_x, ty, it.title, text_maxw, th->font_body, textcol); + listv_text_trunc(rtx, ty, it.title, text_maxw, th->font_body, textcol); int sy = ty + thh + MEOW_LISTV_SUB_GAP; if (sy >= vy0 && sy + thh <= vy1) - listv_text_trunc(text_x, sy, it.subtitle, text_maxw, th->font_body, subcol); + listv_text_trunc(rtx, sy, it.subtitle, text_maxw, th->font_body, subcol); } else { ty = ry + (item - thh) / 2; if (it.title && ty >= vy0 && ty + thh <= vy1) - listv_text_trunc(text_x, ty, it.title, text_maxw, th->font_body, textcol); + listv_text_trunc(rtx, ty, it.title, text_maxw, th->font_body, textcol); } /* 右侧操作区 */ if (it.right != MEOW_LIST_RIGHT_NONE) { - int ax = w->x + w->w - pad - 24; /* 右缘动作槽(约 24px) */ - if (ax >= op_start) + int ax = rlx + w->w - pad - 24; /* 右缘动作槽(约 24px) */ + if (ax >= rop) listv_draw_right(th, ax, ry, item, it.right, it.on, subcol); } /* 分隔线(仅落在可见带内才画) */ int sy = ry + item - 1; if (sy >= cl && sy < cr) - meow_ui_fill_rect(vx0, sy, w->w, 1, th->outlineVariant); + meow_ui_fill_rect(rlx, sy, w->w, 1, th->outlineVariant); /* 禁用行整体变淡(§5.5/§5.8.3 enabled=0 → 变暗) */ if (!it.enabled) - meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->onSurface, 90); + meow_ui_fill_rect_alpha(rlx, cl, w->w, cr - cl, th->onSurface, 90); } } if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); diff --git a/patch_v2/sdk/test_ime_fix b/patch_v2/sdk/test_ime_fix index 6d6f73b..c7c3b2c 100755 Binary files a/patch_v2/sdk/test_ime_fix and b/patch_v2/sdk/test_ime_fix differ diff --git a/patch_v2/sdk/test_meow_cachegov b/patch_v2/sdk/test_meow_cachegov new file mode 100755 index 0000000..a9f7587 Binary files /dev/null and b/patch_v2/sdk/test_meow_cachegov differ diff --git a/patch_v2/sdk/test_meow_cachegov.c b/patch_v2/sdk/test_meow_cachegov.c new file mode 100644 index 0000000..e516bc3 --- /dev/null +++ b/patch_v2/sdk/test_meow_cachegov.c @@ -0,0 +1,189 @@ +/* test_meow_cachegov.c — 宿主测试:SDK meow_cachegov(A:DONTNEED L0/L1 + refault 回退)。 + * + * 背景(OOM_PAGECACHE_GOVERNANCE §2/§4,③混合):A 的 DONTNEED/read_pos 热路径随 SDK 库; + * 本测试用宿主桩: + * - 真实 posix_fadvise(文件数据完整性校验:drop 后文件内容不变、drop_finished 可再读); + * - meow_log 桩捕获 "[cachegov] L1 drop off=.. len=.." 断言丢弃范围([0, read_pos-keep_hot)); + * - meow_cachegov_test_set_time_ms() 注入时钟驱动 refault 滚窗回退(放大 keep_hot→降L0→关A); + * - MEOW_CACHEGOV_C_FILE 指向临时文件写入 fake C 状态,断言 download_paused/prefetch_paused。 + * + * 编译(patch_v2/sdk 目录下): + * gcc -Os -Wall -Wextra -I. -Iinclude -I../meow_launcherd -c meow_cachegov.c -o /tmp/cg.o + * gcc -Os -Wall -Wextra -I. -Iinclude -I../meow_launcherd test_meow_cachegov.c /tmp/cg.o -lpthread -o test_meow_cachegov + * 运行:./test_meow_cachegov (exit 0=PASS / 1=FAIL) + */ +#define _GNU_SOURCE +#include "meow_cachegov.h" + +#include +#include +#include +#include +#include +#include +#include + +/* ---- meow_log 桩:捕获最近一条 [cachegov] 行,供断言 ---- */ +static char g_last_log[512]; +void meow_log(int lvl, const char *fmt, ...) +{ + (void)lvl; + va_list ap; va_start(ap, fmt); + vsnprintf(g_last_log, sizeof g_last_log, fmt, ap); + va_end(ap); + printf(" [cachegov.log] %s\n", g_last_log); +} +static int log_has(const char *needle) { return strstr(g_last_log, needle) != NULL; } + +static int g_pass = 0, g_fail = 0; +static void check(const char *name, int cond) +{ + if (cond) { g_pass++; printf("PASS %s\n", name); } + else { g_fail++; printf("FAIL %s\n", name); } +} + +/* ---- read_pos 桩:由测试驱动当前读指针 ---- */ +static uint64_t g_pos; +static uint64_t fake_read_pos(void *ctx) { (void)ctx; return g_pos; } + +/* 建一个 size 字节的临时文件并填充确定性内容。 */ +static int make_file(const char *path, size_t size) +{ + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) return -1; + for (size_t off = 0; off < size; off += 4096) { + char tmp[4096]; + memset(tmp, (int)(off / 4096) & 0xFF, sizeof tmp); + size_t n = (size - off) < sizeof tmp ? (size - off) : sizeof tmp; + if (write(fd, tmp, n) != (ssize_t)n) { close(fd); return -1; } + } + close(fd); + return 0; +} + +/* 校验文件内容仍为确定性模式(drop 后数据完整)。 */ +static int verify_file(const char *path, size_t size) +{ + int fd = open(path, O_RDONLY); + if (fd < 0) return 0; + char tmp[4096]; + size_t off = 0; + int ok = 1; + while (off < size) { + size_t n = (size - off) < sizeof tmp ? (size - off) : sizeof tmp; + ssize_t r = read(fd, tmp, n); + if (r != (ssize_t)n) { ok = 0; break; } + char expect[4096]; + memset(expect, (int)(off / 4096) & 0xFF, n); + if (memcmp(tmp, expect, n) != 0) { ok = 0; break; } + off += (size_t)n; + } + close(fd); + return ok; +} + +int main(void) +{ + char path[320], cfile[360]; + snprintf(path, sizeof path, "/tmp/meow_cg_%ld.bin", (long)getpid()); + snprintf(cfile, sizeof cfile, "/tmp/meow_cg_%ld.state", (long)getpid()); + setenv("MEOW_CACHEGOV_C_FILE", cfile, 1); + unlink(path); unlink(cfile); + + const size_t size = 1024u * 1024u; /* 1MB 测试文件 */ + check("make_file", make_file(path, size) == 0); + + /* 1) cfg:默认 + set/get 往返 */ + struct meow_cachegov_cfg c = {0}; + meow_cachegov_get_cfg(&c); + check("cfg default enabled", c.enabled == 1); + check("cfg default keep_hot=256KB", c.keep_hot_bytes == 256u * 1024u); + struct meow_cachegov_cfg c2 = c; + c2.keep_hot_bytes = 128u * 1024u; + c2.drop_mode = 1; + c2.refault_backoff = 2; + check("set_cfg", meow_cachegov_set_cfg(&c2) == 0); + meow_cachegov_get_cfg(&c); + check("get_cfg roundtrip", c.keep_hot_bytes == 128u * 1024u && c.drop_mode == 1 && c.refault_backoff == 2); + + /* 2) L1 drop:read_pos=512KB、keep_hot=128KB → drop [0, 384KB) off=0 len=384KB */ + check("register", meow_cachegov_register(path, size, fake_read_pos, NULL) == 0); + g_pos = 512u * 1024u; + meow_cachegov_tick(); + check("L1 drop off=0 len=384KB", log_has("L1 drop") && strstr(g_last_log, "off=0 len=393216")); + check("L1 range bounded by read_pos-keep_hot (no L2)", + !strstr(g_last_log, "off=393216") && strstr(g_last_log, "len=393216")); + + /* 3) L1 推进:read_pos=800KB → drop [384KB, 800-128KB=672KB) off=384KB len=288KB */ + g_pos = 800u * 1024u; + meow_cachegov_tick(); + check("L1 drop off=384KB len=288KB", log_has("L1 drop") && strstr(g_last_log, "off=393216") && strstr(g_last_log, "len=294912")); + + /* 4) refault 回退档1(放大 keep_hot 翻倍):3 次触发 2 次 → 0→1(每升降一档需间隔 ≥10s */ + meow_cachegov_test_set_time_ms(0); + meow_cachegov_note_refault(); meow_cachegov_note_refault(); + meow_cachegov_tick(); + check("backoff -> 1 (refault*2)", meow_cachegov_backoff_level() == 1); + check("file intact after backoff1", verify_file(path, size)); + /* 档1 且 keep_hot=128KB*2=256KB:read_pos=1000KB → drop 上界=744KB(已丢到 672KB) */ + g_pos = 1000u * 1024u; + meow_cachegov_tick(); + check("backoff1 L1 keep_hot enlarged (no L2)", meow_cachegov_backoff_level() == 1); + + /* 5) 再 2 次(间隔 10s)→ 档2(L1 关,只 L0)→ download_paused=1 */ + meow_cachegov_test_set_time_ms(11000); + meow_cachegov_note_refault(); meow_cachegov_note_refault(); + meow_cachegov_tick(); + check("backoff -> 2 (L1 off)", meow_cachegov_backoff_level() == 2); + g_pos = 900u * 1024u; + meow_cachegov_tick(); /* L1 off:应无新的 L1 drop 日志 */ + check("level2 no L1 drop (only L0)", !log_has("L1 drop")); + check("download_paused @backoff>=2", meow_cachegov_download_paused() == 1); + + /* 6) 再 2 次(间隔 10s)→ 档3(A 全关)→ prefetch_paused=1 + drop_finished 不再丢页 */ + meow_cachegov_test_set_time_ms(22000); + meow_cachegov_note_refault(); meow_cachegov_note_refault(); + meow_cachegov_tick(); + check("backoff -> 3 (A off)", meow_cachegov_backoff_level() == 3); + check("prefetch_paused @backoff>=3", meow_cachegov_prefetch_paused() == 1); + meow_cachegov_test_set_time_ms(23000); + meow_cachegov_drop_finished(path); + check("drop_finished skip @A-off", log_has("drop_finished skip")); + check("file intact after A-off", verify_file(path, size)); + + /* 7) C 侧降载:write fake C state dl_pause=1;滚窗干净逐级降回 0 后 download_paused 仍 =1 */ + { + FILE *f = fopen(cfile, "w"); + check("write C state", f != NULL); + if (f) { fputs("cached_kb=35000\ndirty_kb=1000\ndl_pause=1\nprefetch_pause=0\n", f); fclose(f); } + } + meow_cachegov_test_set_time_ms(33000); + meow_cachegov_tick(); /* 3→2 */ + meow_cachegov_test_set_time_ms(44000); + meow_cachegov_tick(); /* 2→1 */ + meow_cachegov_test_set_time_ms(55000); + meow_cachegov_tick(); /* 1→0 */ + check("backoff de-escalated to 0", meow_cachegov_backoff_level() == 0); + check("download_paused via C file", meow_cachegov_download_paused() == 1); + + /* 8) L0 drop_finished(A 正常态):整文件 DONTNEED + 数据完整 + 注销条目 */ + unlink(cfile); + meow_cachegov_register(path, size, fake_read_pos, NULL); + meow_cachegov_test_set_time_ms(66000); + meow_cachegov_drop_finished(path); + check("drop_finished L0 log", log_has("drop_finished L0")); + check("file intact after L0", verify_file(path, size)); + check("unregister removes entry", meow_cachegov_unregister(path) != 0); /* 已注销 → 找不到 */ + + /* 9) disable 时 drop/tick 均 no-op:数据仍完整 */ + c2.enabled = 0; meow_cachegov_set_cfg(&c2); + meow_cachegov_register(path, size, fake_read_pos, NULL); + g_pos = 512u * 1024u; + meow_cachegov_tick(); + meow_cachegov_drop_finished(path); + check("file intact when disabled", verify_file(path, size)); + + unlink(path); unlink(cfile); + printf("----\n%d passed, %d failed\n", g_pass, g_fail); + return g_fail ? 1 : 0; +} diff --git a/patch_v2/sdk/test_meow_ui_anim b/patch_v2/sdk/test_meow_ui_anim new file mode 100755 index 0000000..b6d4dc7 Binary files /dev/null and b/patch_v2/sdk/test_meow_ui_anim differ diff --git a/patch_v2/sdk/test_meow_ui_box b/patch_v2/sdk/test_meow_ui_box new file mode 100755 index 0000000..aed8416 Binary files /dev/null and b/patch_v2/sdk/test_meow_ui_box differ diff --git a/patch_v2/sdk/test_meow_ui_config b/patch_v2/sdk/test_meow_ui_config new file mode 100755 index 0000000..f851825 Binary files /dev/null and b/patch_v2/sdk/test_meow_ui_config differ diff --git a/patch_v2/sdk/test_meow_ui_dispatch b/patch_v2/sdk/test_meow_ui_dispatch new file mode 100755 index 0000000..8266c71 Binary files /dev/null and b/patch_v2/sdk/test_meow_ui_dispatch differ diff --git a/patch_v2/sdk/test_meow_ui_lazygrid b/patch_v2/sdk/test_meow_ui_lazygrid new file mode 100755 index 0000000..7f41543 Binary files /dev/null and b/patch_v2/sdk/test_meow_ui_lazygrid differ diff --git a/patch_v2/sdk/test_meow_ui_lazygrid.c b/patch_v2/sdk/test_meow_ui_lazygrid.c new file mode 100644 index 0000000..fb1766f --- /dev/null +++ b/patch_v2/sdk/test_meow_ui_lazygrid.c @@ -0,0 +1,277 @@ +/* test_meow_ui_lazygrid.c — 宿主验证 §12.4 2 维虚拟化宫格(meow_lazygrid): + * T1 虚拟化正确性:count/cols/max_scroll;首帧绑定可见行 item;scroll 下滚仅补新入视口的 item; + * 回滚不重绑;全量扫过 = count(每 index 恰 bind 一次);命中把滚动+网格坐标映射回 idx 正确。 + * T2 默认 renderer:圆角卡片底 / 标题/副标题 / 选中高亮(primaryContainer)/ 禁用变淡。 + * T3 完全自绘 set_item_draw:每可见格调一次 + 自绘内容像素。 + * T4 空态占位(count=0 + empty_text 居中)。 + * T5 meow_scroll 集成(meow_lazygrid_scroll_attach:track/fling/snap 到行边界)。 + * + * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global): + * gcc -o test_meow_ui_lazygrid test_meow_ui_lazygrid.c test_host_stub.c \ + * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ + * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \ + * meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \ + * meow_lazygrid.c meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm + * 运行:./test_meow_ui_lazygrid + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp:真实像素断言 */ +#include +#include +#include +#include + +static int g_pass = 0, g_fail = 0; +#define CHECK(cond, ...) do { \ + if (cond) { g_pass++; } \ + else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \ +} while (0) + +/* ---- back_buf 像素读取(真实渲染断言) ---- */ +static void rd_px(int x, int y, int *r, int *g, int *b) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); + if (!bb || x < 0 || y < 0) { *r = *g = *b = 0; return; } + if (bpp == 16) { + uint16_t px = *(uint16_t *)(bb + (size_t)y * line + (size_t)x * 2); + *r = (((px >> 11) & 0x1F) << 3) | ((px >> 11) & 0x1F) >> 2; + *g = (((px >> 5) & 0x3F) << 2) | ((px >> 5) & 0x3F) >> 4; + *b = ((px & 0x1F) << 3) | ((px & 0x1F) >> 2); + } else { + uint32_t px = *(uint32_t *)(bb + (size_t)y * line + (size_t)x * 4); + *r = (px >> 16) & 0xFF; *g = (px >> 8) & 0xFF; *b = px & 0xFF; + } +} +static int near2(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 14; } +static int px_near(int x, int y, int r, int g, int b) +{ + int rr, gg, bb; rd_px(x, y, &rr, &gg, &bb); + return near2(rr, r) && near2(gg, g) && near2(bb, b); +} +static int region_has(int x, int y, int w, int h, int r, int g, int b) +{ + int cnt = 0; + for (int dy = 0; dy < h; dy++) + for (int dx = 0; dx < w; dx++) + if (px_near(x + dx, y + dy, r, g, b)) cnt++; + return cnt; +} +#define UNPK(c, rr, gg, bb) do { (rr) = ((c) >> 16) & 0xFF; (gg) = ((c) >> 8) & 0xFF; (bb) = (c) & 0xFF; } while (0) + +/* ---- 网格几何(测试统一) ---- */ +#define GX 20 +#define GY 50 +#define GW 440 +#define GH 200 +#define COLS 3 +#define CW 100 +#define CH 80 +#define GAP 10 +#define ROWH (CH + GAP) /* 90 */ +#define N 20 /* 默认总项数 */ + +static int g_count_override; /* <0 = 用默认 N;>=0 = 覆盖(空态/小列表) */ +static int g_bind_total; /* bind 回调计数(含 hit 的临时 bind) */ +static int g_draw_total; /* draw_cb(自绘)计数 */ +static int g_disabled_at; /* 该 index 返回 enabled=0(-1=无) */ +static int g_tap_cb_idx; /* tap_at 回调记录的选中项 */ + +static void lg_tap_cb(int idx, void *ctx) { (void)ctx; g_tap_cb_idx = idx; } + +static int lg_count(void *ctx) { (void)ctx; return g_count_override >= 0 ? g_count_override : N; } +static void lg_bind(int idx, meow_lazygrid_item *it, void *ctx) +{ + (void)ctx; + g_bind_total++; + memset(it, 0, sizeof *it); + static char t[32], s[32]; + snprintf(t, sizeof t, "cell %d", idx); + snprintf(s, sizeof s, "st %d", idx); + it->title = t; it->subtitle = s; + it->enabled = 1; + if (g_disabled_at == idx) it->enabled = 0; +} + +/* ---- T3 完全自绘:每格画实心块 + 角标记 ---- */ +static void mydraw(int idx, int x, int y, int w, int h, int st, void *ctx) +{ + (void)idx; (void)ctx; + g_draw_total++; + meow_ui_fill_rect(x, y, w, h, 0x334455); /* 自定义格底 */ + meow_ui_fill_rect(x + 4, y + 4, 8, 8, 0xFF0000); /* 角标记 */ + if (st & MEOW_LAZYGRID_ST_SELECTED) meow_ui_fill_rect(x + w - 12, y + 4, 8, 8, 0x00FF00); +} + +int main(void) +{ + meow_ui_opts o = { 480, 720 }; + if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; } + const meow_theme *th = meow_theme_get(); + int scl_r, scl_g, scl_b, on_r, on_g, on_b, pc_r, pc_g, pc_b, osv_r, osv_g, osv_b, opc_r, opc_g, opc_b; + UNPK(th->surfaceContainerLow, scl_r, scl_g, scl_b); + UNPK(th->onSurface, on_r, on_g, on_b); + UNPK(th->primaryContainer, pc_r, pc_g, pc_b); + UNPK(th->onSurfaceVariant, osv_r, osv_g, osv_b); + UNPK(th->onPrimaryContainer, opc_r, opc_g, opc_b); + g_count_override = -1; g_bind_total = 0; g_disabled_at = -1; + + /* ============ T1 虚拟化正确性 ============ */ + { + meow_lazygrid *g = meow_lazygrid_create(GX, GY, GW, GH, COLS, CW, CH, GAP); + CHECK(g != NULL, "create"); + CHECK(meow_lazygrid_set_vsource(g, lg_count, lg_bind, NULL) == 0, "vsource"); + CHECK(meow_lazygrid_max_scroll(g) == 7 * CH + 6 * GAP - GH, "max_scroll %d (expect %d)", + meow_lazygrid_max_scroll(g), 7 * CH + 6 * GAP - GH); /* 620-200=420 */ + CHECK(meow_lazygrid_scroll(g) == 0, "scroll0"); + meow_ui_clear(th->bg); + meow_lazygrid_draw(g, th); + + int B0 = g_bind_total; + int vis_rows = GH / ROWH + 1; /* 3 */ + int expect0 = vis_rows * COLS; /* 9(row0..2 × 3 格) */ + CHECK(B0 == expect0, "first draw binds %d got %d", expect0, B0); + + /* 下滚 1 行(90px):新入视口 = 末 1 行 3 格 → bind +3(其余同槽不重绑) */ + meow_lazygrid_set_scroll(g, ROWH); + meow_lazygrid_draw(g, th); + int B1 = g_bind_total; + CHECK(B1 == B0 + COLS, "scroll+1row binds +%d got %d (was %d)", COLS, B1 - B0, B0); + + /* 回滚到顶:槽仍在 → 不重绑(此段不调 hit,避免临时 bind 干扰计数) */ + meow_lazygrid_set_scroll(g, 0); + meow_lazygrid_draw(g, th); + CHECK(g_bind_total == B1, "scroll-back no rebind got %d (was %d)", g_bind_total, B1); + + /* 全量扫过:每个 index 进入视口时 bind 恰一次 → 总 == N(有界,非 per-frame × count) */ + int prev = 0; + while (prev <= N) { + int off = prev * ROWH; + meow_lazygrid_set_scroll(g, off); + meow_lazygrid_draw(g, th); + if (off >= meow_lazygrid_max_scroll(g)) break; + prev++; + } + CHECK(g_bind_total == N, "full sweep binds each index once got %d (expect %d)", g_bind_total, N); + + /* 命中:索引 = 滚动 + 网格坐标映射(此后的 hit 走临时 bind,不影响上面的总数断言) */ + meow_lazygrid_set_scroll(g, 0); + CHECK(meow_lazygrid_hit(g, GX + 10, GY + 10) == 0, "hit cell0 got %d", meow_lazygrid_hit(g, GX + 10, GY + 10)); + meow_lazygrid_set_scroll(g, ROWH); + /* scroll=90:GY+10(视口顶)→ 内容 y=100 → row1([90,170])col0 → idx=3 */ + CHECK(meow_lazygrid_hit(g, GX + 10, GY + 10) == COLS, + "hit maps scroll+grid got %d (expect 3)", meow_lazygrid_hit(g, GX + 10, GY + 10)); + /* gap 区不命中 */ + CHECK(meow_lazygrid_hit(g, GX + CW + GAP / 2, GY + 10) == -1, "horizontal gap miss got %d", + meow_lazygrid_hit(g, GX + CW + GAP / 2, GY + 10)); + /* 越界列(row0 左侧无格/第 4 列)不命中 */ + meow_lazygrid_set_scroll(g, 0); + CHECK(meow_lazygrid_hit(g, GX + COLS * (CW + GAP) + 10, GY + 10) == -1, "col overflow miss got %d", + meow_lazygrid_hit(g, GX + COLS * (CW + GAP) + 10, GY + 10)); + CHECK(meow_lazygrid_hit(g, GX + 10, GY + ROWH) == COLS, "hit row1 col0 idx=3 got %d", + meow_lazygrid_hit(g, GX + 10, GY + ROWH)); + + /* tap_at:命中 → 置选中 + 回调 */ + g_tap_cb_idx = -1; + meow_lazygrid_set_on_tap(g, lg_tap_cb, NULL); + int tap_idx = meow_lazygrid_tap_at(g, GX + 50, GY + 40); + CHECK(tap_idx == 0, "tap_at idx0 got %d", tap_idx); + CHECK(meow_lazygrid_sel(g) == 0, "sel set to 0"); + CHECK(g_tap_cb_idx == 0, "tap cb idx0 got %d", g_tap_cb_idx); + /* 点 row1,col1(idx=4) → 该格命中 */ + int idx4 = meow_lazygrid_hit(g, GX + 1 * (CW + GAP) + 10, GY + ROWH + 10); + CHECK(idx4 == 4, "hit row1col1 idx=%d (expect 4)", idx4); + + meow_lazygrid_free(g); + } + + /* ============ T2 默认 renderer:卡片底 / 标题 / 选中高亮 / 禁用 ============ */ + { + meow_lazygrid *g = meow_lazygrid_create(GX, GY, GW, GH, COLS, CW, CH, GAP); + meow_lazygrid_set_vsource(g, lg_count, lg_bind, NULL); + g_count_override = 3; g_disabled_at = 2; /* 3 格:cell2 禁用 */ + meow_ui_clear(th->bg); + meow_lazygrid_set_sel(g, 0); + meow_lazygrid_draw(g, th); + /* 选中 cell0 → primaryContainer 底([GX,GY,100,80]) */ + CHECK(region_has(GX + 4, GY + 4, 90, 60, pc_r, pc_g, pc_b) > 40, "selected cell primaryContainer bg"); + /* 未选中 cell1 → surfaceContainerLow 底 */ + int x1 = GX + 1 * (CW + GAP); + CHECK(region_has(x1 + 4, GY + 4, 90, 60, scl_r, scl_g, scl_b) > 40, "cell1 surfaceContainerLow bg"); + /* 标题画在 cell0 底部(选中 → onPrimaryContainer;副标题在下方 onPrimaryContainer 同色带) */ + int thh = meow_ui_text_height(th->font_body); + int title_y = GY + CH - 8 - thh - (thh + 2); /* = lg_draw_cell 的 ty */ + int c0cx = GX + CW / 2; + CHECK(region_has(c0cx - 30, title_y, 60, thh, opc_r, opc_g, opc_b) > 2, "cell0 title drawn (onPrimaryContainer)"); + /* 禁用 cell2 变淡:左上角(图标/文本区之外)不再是纯 surfaceContainerLow(被 onSurface α 叠暗) */ + int x2 = GX + 2 * (CW + GAP); + CHECK(!px_near(x2 + 4, GY + 4, scl_r, scl_g, scl_b), "disabled cell overlaid (bg dimmed)"); + meow_lazygrid_free(g); + } + + /* ============ T3 完全自绘(set_item_draw) ============ */ + { + meow_lazygrid *g = meow_lazygrid_create(GX, GY, GW, GH, COLS, CW, CH, GAP); + meow_lazygrid_set_vsource(g, lg_count, lg_bind, NULL); + g_count_override = 3; g_disabled_at = -1; g_draw_total = 0; + CHECK(meow_lazygrid_set_item_draw(g, mydraw, NULL) == 0, "set_item_draw"); + meow_ui_clear(th->bg); + meow_lazygrid_draw(g, th); + CHECK(g_draw_total == 3, "draw_cb per visible cell got %d (expect 3)", g_draw_total); + CHECK(region_has(GX + 4, GY + 4, 8, 8, 0xFF, 0x00, 0x00) > 0, "custom cell content drawn"); + meow_lazygrid_free(g); + } + + /* ============ T4 空态 ============ */ + { + meow_lazygrid *g = meow_lazygrid_create(GX, GY, GW, GH, COLS, CW, CH, GAP); + meow_lazygrid_set_vsource(g, lg_count, lg_bind, NULL); + g_count_override = 0; + meow_lazygrid_set_empty_text(g, "No data"); + const meow_theme *t2 = meow_theme_get(); + meow_ui_clear(th->bg); + meow_lazygrid_draw(g, t2); + int thh = meow_ui_text_height(t2->font_body); + int ety = GY + (GH - thh) / 2; + CHECK(region_has(GX + GW / 2 - 60, ety, 120, thh, osv_r, osv_g, osv_b) > 2, "empty state text drawn"); + meow_lazygrid_free(g); + } + + /* ============ T5 meow_scroll 集成(fling/snap) ============ */ + { + g_count_override = -1; g_disabled_at = -1; + meow_lazygrid *g = meow_lazygrid_create(GX, GY, GW, GH, COLS, CW, CH, GAP); + meow_lazygrid_set_vsource(g, lg_count, lg_bind, NULL); + meow_scroll *s = meow_lazygrid_scroll_attach(g); + CHECK(s != NULL, "scroll attach"); + meow_scroll_set_snap(s, ROWH); + uint64_t t = meow_ui_time_ms(); + for (int i = 0; i < 12; i++) meow_scroll_track(s, 30, t + (uint64_t)i * 16); + int start = meow_scroll_offset(s); + CHECK(start > 0, "drag advances offset got %d", start); + meow_scroll_fling(s); + CHECK(meow_scroll_active(s) == 1, "fling active"); + int max_off = start, guard = 0; + while (meow_scroll_active(s) && guard++ < 300) { + meow_anim_tick(); + int cur = meow_scroll_offset(s); + if (max_off < cur) max_off = cur; + usleep(10000); + } + meow_anim_tick(); + int end = meow_scroll_offset(s); + CHECK(end >= start, "fling advances %d vs %d", end, start); + /* snap 到行边界;若撞底(max_scroll=420 非 row_h 的整数倍)则停在边界(clamp 优先) */ + CHECK((end % ROWH) == 0 || end == meow_lazygrid_max_scroll(g), + "fling ends snapped-or-boundary got %d (%%%d, max %d)", end, end % ROWH, meow_lazygrid_max_scroll(g)); + CHECK(end <= meow_lazygrid_max_scroll(g), "fling in range %d (max %d)", end, meow_lazygrid_max_scroll(g)); + CHECK(meow_lazygrid_scroll(g) == end, "grid scroll mirrors offset"); + meow_scroll_free(s); + meow_lazygrid_free(g); + } + + meow_ui_shutdown(); + printf("lazygrid: %d pass / %d fail\n", g_pass, g_fail); + return g_fail ? 1 : 0; +} diff --git a/patch_v2/sdk/test_meow_ui_listvirt b/patch_v2/sdk/test_meow_ui_listvirt new file mode 100755 index 0000000..15a939d Binary files /dev/null and b/patch_v2/sdk/test_meow_ui_listvirt differ diff --git a/patch_v2/sdk/test_meow_ui_scroll b/patch_v2/sdk/test_meow_ui_scroll new file mode 100755 index 0000000..904d3b3 Binary files /dev/null and b/patch_v2/sdk/test_meow_ui_scroll differ diff --git a/patch_v2/sdk/test_meow_ui_select b/patch_v2/sdk/test_meow_ui_select new file mode 100755 index 0000000..131c3ec Binary files /dev/null and b/patch_v2/sdk/test_meow_ui_select differ diff --git a/patch_v2/sdk/test_meow_ui_select.c b/patch_v2/sdk/test_meow_ui_select.c new file mode 100644 index 0000000..3d6f0fd --- /dev/null +++ b/patch_v2/sdk/test_meow_ui_select.c @@ -0,0 +1,178 @@ +/* test_meow_ui_select.c — 宿主验证枚举/预设选择器(meow_select): + * T1 打开→点选项→单选高亮(primaryContainer)+ 选中 + 关闭即回调(on_change(idx))+ 返回值。 + * T2 取消:点面板外(遮罩)→ 返回 -1,不调 on_change。 + * T3 meow_select_show 一次性:返回选中下标。 + * + * 阻塞式模态用后台线程驱动(同 test_ime_fix 范式),主线程经 meow_input_inject 喂触摸。 + * 编译(宿主 gcc): + * gcc -o test_meow_ui_select test_meow_ui_select.c test_host_stub.c \ + * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ + * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \ + * meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \ + * meow_select.c meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm + * 运行:./test_meow_ui_select + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" /* meow_input_inject + back_buf 读 */ +#include +#include +#include +#include +#include + +static int g_pass = 0, g_fail = 0; +#define CHECK(cond, ...) do { \ + if (cond) { g_pass++; } \ + else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \ +} while (0) + +static void rd_px(int x, int y, int *r, int *g, int *b) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); + if (!bb || x < 0 || y < 0) { *r = *g = *b = 0; return; } + if (bpp == 16) { + uint16_t px = *(uint16_t *)(bb + (size_t)y * line + (size_t)x * 2); + *r = (((px >> 11) & 0x1F) << 3) | ((px >> 11) & 0x1F) >> 2; + *g = (((px >> 5) & 0x3F) << 2) | ((px >> 5) & 0x3F) >> 4; + *b = ((px & 0x1F) << 3) | ((px & 0x1F) >> 2); + } else { + uint32_t px = *(uint32_t *)(bb + (size_t)y * line + (size_t)x * 4); + *r = (px >> 16) & 0xFF; *g = (px >> 8) & 0xFF; *b = px & 0xFF; + } +} +static int near2(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 14; } +#define UNPK(c, rr, gg, bb) do { (rr) = ((c) >> 16) & 0xFF; (gg) = ((c) >> 8) & 0xFF; (bb) = (c) & 0xFF; } while (0) + +/* ---- 驱动:后台线程跑阻塞 meow_select_open ---- */ +typedef struct { + meow_select *s; + int ret; +} sel_arg; +static void *sel_driver(void *p) +{ + sel_arg *a = (sel_arg *)p; + a->ret = meow_select_open(a->s); + return NULL; +} + +/* ---- on_change 回调记录 ---- */ +static int g_cb = -1; +static void on_change(int idx, void *ctx) { (void)ctx; g_cb = idx; } + +/* 计算面板/选项列表几何(与 meow_select_open 同式;用于注入命中坐标 + 像素采样) */ +static int sel_list_y(int nitems) +{ + const meow_theme *th = meow_theme_get(); + int W = meow_ui_width(), H = meow_ui_height(); + int sb = meow_ui_statusbar_height(); if (sb < 0) sb = 0; + int pad = 24, rowh = 48; + int title_h = meow_ui_text_height(th->font_title); + int avail = H - sb - 20; + int min_h = pad + title_h + 12 + rowh + 20; + int want = min_h; + int est = pad + title_h + 12 + nitems * rowh + 20; + if (est > want) want = est; + if (want > avail) want = avail; + if (want < min_h) want = (min_h < avail) ? min_h : avail; + int py = H - want; + return py + pad + title_h + 12; +} +static int sel_sheet_top(int nitems) +{ + const meow_theme *th = meow_theme_get(); + int H = meow_ui_height(); + int sb = meow_ui_statusbar_height(); if (sb < 0) sb = 0; + int pad = 24, rowh = 48; + int title_h = meow_ui_text_height(th->font_title); + int avail = H - sb - 20; + int min_h = pad + title_h + 12 + rowh + 20; + int want = min_h; + int est = pad + title_h + 12 + nitems * rowh + 20; + if (est > want) want = est; + if (want > avail) want = avail; + if (want < min_h) want = (min_h < avail) ? min_h : avail; + return H - want; +} +#define SEL_ROW 48 + +/* ============ T1 选中 + 回调 + 单选高亮 ============ */ +static void test_select_highlight(void) +{ + meow_select *s = meow_select_create("Choose", 2); + meow_select_add_item(s, "Alpha"); + meow_select_add_item(s, "Beta"); + meow_select_add_item(s, "Gamma"); + meow_select_set_on_change(s, on_change, NULL); + int list_y = sel_list_y(3); + sel_arg a = { s, -999 }; + g_cb = -1; + pthread_t th; pthread_create(&th, NULL, sel_driver, &a); + /* 等 open 的 drain + 首帧绘制完成(高亮已画进 back_buf) */ + usleep(150000); + /* 单选高亮:cur=2(第 3 行)选中背景 = primaryContainer */ + const meow_theme *thm = meow_theme_get(); + int pc_r, pc_g, pc_b; UNPK(thm->primaryContainer, pc_r, pc_g, pc_b); + int hx = 6, hy = list_y + 2 * SEL_ROW + SEL_ROW / 2; + int r, g, b; rd_px(hx, hy, &r, &g, &b); + CHECK(near2(r, pc_r) && near2(g, pc_g) && near2(b, pc_b), + "T1 highlight row2 primaryContainer got %02X%02X%02X", r, g, b); + /* 点第 1 行(idx 0)→ 选中 + 关闭 + 回调 */ + meow_input_inject(MEOW_EV_TAP, 100, list_y + 0 * SEL_ROW + SEL_ROW / 2); + pthread_join(th, NULL); + CHECK(a.ret == 0, "T1 open return idx0 got %d", a.ret); + CHECK(g_cb == 0, "T1 on_change(0) got %d", g_cb); + meow_select_free(s); +} + +/* ============ T2 取消(点面板外) ============ */ +static void test_select_cancel(void) +{ + meow_select *s = meow_select_create("Choose", 0); + meow_select_add_item(s, "Alpha"); + meow_select_add_item(s, "Beta"); + meow_select_set_on_change(s, on_change, NULL); + sel_arg a = { s, -999 }; + g_cb = -1; + pthread_t th; pthread_create(&th, NULL, sel_driver, &a); + usleep(150000); + int top = sel_sheet_top(2); + meow_input_inject(MEOW_EV_TAP, 240, (top > 30) ? 10 : 10); /* 面板外(遮罩) */ + pthread_join(th, NULL); + CHECK(a.ret == -1, "T2 cancel returns -1 got %d", a.ret); + CHECK(g_cb == -1, "T2 on_change NOT fired on cancel got %d", g_cb); + meow_select_free(s); +} + +/* ============ T3 meow_select_show 一次性 ============ */ +static void *show_driver(void *p) +{ + (void)p; + static const char *opts[] = { "A", "B", "C", "D" }; + static int r = -999; + r = meow_select_show("Preset", opts, 4, 1); + return (void *)(long)r; +} +static void test_select_show(void) +{ + g_cb = -1; + pthread_t th; pthread_create(&th, NULL, show_driver, NULL); + usleep(150000); + int list_y = sel_list_y(4); + meow_input_inject(MEOW_EV_TAP, 100, list_y + 2 * SEL_ROW + SEL_ROW / 2); /* 选第 3 项 idx2 */ + long r = 0; pthread_join(th, (void **)&r); + CHECK(r == 2, "T3 show returns idx2 got %ld", r); +} + +int main(void) +{ + meow_ui_opts o = { 480, 720 }; + if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; } + test_select_highlight(); + test_select_cancel(); + test_select_show(); + meow_ui_shutdown(); + printf("select: %d pass / %d fail\n", g_pass, g_fail); + return g_fail ? 1 : 0; +} diff --git a/patch_v2/sdk/test_meow_ui_str b/patch_v2/sdk/test_meow_ui_str new file mode 100755 index 0000000..867606f Binary files /dev/null and b/patch_v2/sdk/test_meow_ui_str differ diff --git a/patch_v2/sdk/test_meow_ui_swipe b/patch_v2/sdk/test_meow_ui_swipe new file mode 100755 index 0000000..29d3f9c Binary files /dev/null and b/patch_v2/sdk/test_meow_ui_swipe differ diff --git a/patch_v2/sdk/test_meow_ui_swipe.c b/patch_v2/sdk/test_meow_ui_swipe.c new file mode 100644 index 0000000..d40787b --- /dev/null +++ b/patch_v2/sdk/test_meow_ui_swipe.c @@ -0,0 +1,210 @@ +/* test_meow_ui_swipe.c — 宿主验证列表行左滑删除(§5.5,M7.3 缺口 #9): + * T1 滑动手势判定与冲突消解:begin 命中可滑行返回 1;横向小位移 = swipe(move 返回 1 消费); + * 纵向大位移 = 滚动手势优先(move 返回 0 并取消本行 swipe);非启用行 begin 返回 0。 + * T2 左滑超阈值 → 删除动画(meow_anim 滑出),结束后 on_delete 回调恰一次 + swipe 状态复位。 + * T3 未超阈值 → 回弹,on_delete 不触发,swipe_dx 回到 0。 + * T4 点按(无位移):begin 后 end(未 started)返回 0(App 走 tap_at),不触发 on_delete。 + * T5 右露删除区像素:滑动中重绘列表 → 右露区画主题 error 底 + 行内容左移。 + * + * 编译(宿主 gcc): + * gcc -o test_meow_ui_swipe test_meow_ui_swipe.c test_host_stub.c \ + * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ + * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \ + * meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \ + * meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm + * 运行:./test_meow_ui_swipe + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include +#include +#include +#include + +static int g_pass = 0, g_fail = 0; +#define CHECK(cond, ...) do { \ + if (cond) { g_pass++; } \ + else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \ +} while (0) + +static void rd_px(int x, int y, int *r, int *g, int *b) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); + if (!bb || x < 0 || y < 0) { *r = *g = *b = 0; return; } + if (bpp == 16) { + uint16_t px = *(uint16_t *)(bb + (size_t)y * line + (size_t)x * 2); + *r = (((px >> 11) & 0x1F) << 3) | ((px >> 11) & 0x1F) >> 2; + *g = (((px >> 5) & 0x3F) << 2) | ((px >> 5) & 0x3F) >> 4; + *b = ((px & 0x1F) << 3) | ((px & 0x1F) >> 2); + } else { + uint32_t px = *(uint32_t *)(bb + (size_t)y * line + (size_t)x * 4); + *r = (px >> 16) & 0xFF; *g = (px >> 8) & 0xFF; *b = px & 0xFF; + } +} +static int near2(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 14; } +static int px_near(int x, int y, int r, int g, int b) +{ + int rr, gg, bb; rd_px(x, y, &rr, &gg, &bb); + return near2(rr, r) && near2(gg, g) && near2(bb, b); +} +static int region_has(int x, int y, int w, int h, int r, int g, int b) +{ + int cnt = 0; + for (int dy = 0; dy < h; dy++) + for (int dx = 0; dx < w; dx++) + if (px_near(x + dx, y + dy, r, g, b)) cnt++; + return cnt; +} +#define UNPK(c, rr, gg, bb) do { (rr) = ((c) >> 16) & 0xFF; (gg) = ((c) >> 8) & 0xFF; (bb) = (c) & 0xFF; } while (0) + +#define LVX 20 +#define LVY 50 +#define LVW 440 +#define LVH 300 +#define ITEM 56 +#define CNT 10 + +static int del_count; /* on_delete 回调次数 */ +static int del_idx; /* 最后一次删除 idx */ +static void on_delete(int idx, void *ctx) { (void)ctx; del_count++; del_idx = idx; } + +static int scount(void *ctx) { (void)ctx; return CNT; } +static void sbind(int idx, meow_lv_item *it, void *ctx) +{ + (void)ctx; + memset(it, 0, sizeof *it); + static char t[32]; + snprintf(t, sizeof t, "notif %d", idx); + it->title = t; it->icon_kind = MEOW_LIST_ICON_NONE; it->right = MEOW_LIST_RIGHT_NONE; + it->enabled = 1; +} + +static meow_widget *make_list(void) +{ + meow_widget *lv = meow_listview_create(LVX, LVY, LVW, LVH, 0, NULL, NULL, MEOW_LIST_SEL_NONE); + meow_listview_set_item_h(lv, ITEM); + meow_listview_set_vsource(lv, scount, sbind, NULL); + meow_listview_set_item_renderer(lv, 2, 0, 0); /* 双行、无图标、无尾件 */ + meow_listview_set_swipe_delete(lv, 1, on_delete, NULL); + return lv; +} + +/* 推进 meow_anim 直到 swipe 动画结束(模拟 on_tick 节拍)。 */ +static void pump_swipe(meow_widget *lv) +{ + int guard = 0; + while (meow_listview_swipe_active(lv) && guard++ < 80) { + meow_anim_tick(); + usleep(10000); + } + meow_anim_tick(); +} + +int main(void) +{ + meow_ui_opts o = { 480, 720 }; + if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; } + const meow_theme *th = meow_theme_get(); + int err_r, err_g, err_b; UNPK(th->error, err_r, err_g, err_b); + + int row0y = LVY + ITEM / 2; /* 行 0 中心 y(scroll=0) */ + + /* ============ T1 手势判定 / 冲突消解 ============ */ + { + meow_widget *lv = make_list(); + /* begin 命中可滑行行 → 1 */ + CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "T1 begin on swipe row = 1"); + /* 横向小位移 → 裁定 swipe,move 返回 1(消费) */ + CHECK(meow_listview_swipe_move(lv, 40, row0y) == 1, "T1 move horizontal = 1 (consumed)"); + int dx = meow_listview_swipe_offset(lv); + CHECK(dx == -60, "T1 dx=-60 got %d", dx); + /* 又一小位移(不超容差也不改变方向) */ + CHECK(meow_listview_swipe_move(lv, 30, row0y) == 1, "T1 move horizontal 2 = 1"); + CHECK(meow_listview_swipe_end(lv, 30, row0y) == 1, "T1 end started swipe = 1 (bounce)"); + pump_swipe(lv); + CHECK(meow_listview_swipe_offset(lv) == 0, "T1 bounce back dx=0 got %d", meow_listview_swipe_offset(lv)); + CHECK(del_count == 0, "T1 no delete on bounce"); + meow_widget_free(lv); + } + { + /* 纵向大位移 → 滚动手势优先(move 返回 0 + 取消) */ + meow_widget *lv = make_list(); + CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "T1 begin2 = 1"); + CHECK(meow_listview_swipe_move(lv, 100, row0y + 80) == 0, "T1 vertical move = 0 (yield to scroll)"); + CHECK(meow_listview_swipe_active(lv) == 0, "T1 vertical cancels swipe"); + CHECK(meow_listview_swipe_offset(lv) == 0, "T1 vertical dx=0"); + meow_widget_free(lv); + } + { + /* 非启用行 begin → 0 */ + meow_widget *lv = make_list(); + meow_listview_set_swipe_delete(lv, 0, NULL, NULL); + CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 0, "T1 disabled begin = 0"); + meow_widget_free(lv); + } + + /* ============ T2 超阈值 → 删除动画 + on_delete ============ */ + { + del_count = 0; del_idx = -1; + meow_widget *lv = make_list(); + CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "T2 begin = 1"); + /* 左滑 200px(阈值=LVW*2/5=176)→ 超阈值 */ + CHECK(meow_listview_swipe_move(lv, 100 - 200, row0y) == 1, "T2 move big left = 1"); + CHECK(meow_listview_swipe_end(lv, 100 - 200, row0y) == 1, "T2 end = 1 (delete anim)"); + CHECK(meow_listview_swipe_active(lv) == 1, "T2 delete anim active"); + pump_swipe(lv); + CHECK(del_count == 1, "T2 on_delete fired once got %d", del_count); + CHECK(del_idx == 0, "T2 on_delete idx0 got %d", del_idx); + CHECK(meow_listview_swipe_active(lv) == 0, "T2 state reset inactive"); + CHECK(meow_listview_swipe_offset(lv) == 0, "T2 dx reset 0 got %d", meow_listview_swipe_offset(lv)); + meow_widget_free(lv); + } + + /* ============ T3 未超阈值 → 回弹,on_delete 不触发 ============ */ + { + del_count = 0; + meow_widget *lv = make_list(); + CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "T3 begin = 1"); + CHECK(meow_listview_swipe_move(lv, 55, row0y) == 1, "T3 move left 45px = 1"); + CHECK(meow_listview_swipe_offset(lv) == -45, "T3 dx=-45 got %d", meow_listview_swipe_offset(lv)); + CHECK(meow_listview_swipe_end(lv, 55, row0y) == 1, "T3 end = 1 (bounce anim)"); + CHECK(meow_listview_swipe_active(lv) == 1, "T3 bounce active"); + pump_swipe(lv); + CHECK(del_count == 0, "T3 on_delete NOT fired got %d", del_count); + CHECK(meow_listview_swipe_offset(lv) == 0, "T3 bounce dx=0 got %d", meow_listview_swipe_offset(lv)); + CHECK(meow_listview_swipe_active(lv) == 0, "T3 inactive after bounce"); + meow_widget_free(lv); + } + + /* ============ T4 点按(无位移)= 行点击,end 返回 0 ============ */ + { + del_count = 0; + meow_widget *lv = make_list(); + CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "T4 begin = 1"); + CHECK(meow_listview_swipe_end(lv, 100, row0y) == 0, "T4 tap end = 0 (let App tap_at)"); + CHECK(del_count == 0, "T4 no delete on tap"); + CHECK(meow_listview_swipe_active(lv) == 0, "T4 inactive after tap"); + meow_widget_free(lv); + } + + /* ============ T5 右露删除区像素(默认 renderer) ============ */ + { + meow_widget *lv = make_list(); + CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "T5 begin = 1"); + CHECK(meow_listview_swipe_move(lv, 100 - 200, row0y) == 1, "T5 move big left = 1"); + meow_ui_clear(th->bg); + meow_widget_draw(lv); + /* 右露区 [LVX+LVW+dx, row0, -dx, ITEM] = [260,50,200,56] → error 底 */ + CHECK(region_has(270, row0y - ITEM / 2 + 4, 180, ITEM - 8, err_r, err_g, err_b) > 50, + "T5 delete reveal error bg got %d", region_has(270, row0y - ITEM / 2 + 4, 180, ITEM - 8, err_r, err_g, err_b)); + meow_listview_swipe_end(lv, 100 - 200, row0y); + pump_swipe(lv); + meow_widget_free(lv); + } + + meow_ui_shutdown(); + printf("swipe: %d pass / %d fail\n", g_pass, g_fail); + return g_fail ? 1 : 0; +} diff --git a/patch_v2/sdk/test_meow_ui_view_event b/patch_v2/sdk/test_meow_ui_view_event new file mode 100755 index 0000000..adf951b Binary files /dev/null and b/patch_v2/sdk/test_meow_ui_view_event differ diff --git a/patch_v2/sdk/test_meow_ui_vol b/patch_v2/sdk/test_meow_ui_vol index 2b2b3b7..7cd1bc0 100755 Binary files a/patch_v2/sdk/test_meow_ui_vol and b/patch_v2/sdk/test_meow_ui_vol differ diff --git a/patch_v2/sdk/test_meow_ui_widgets b/patch_v2/sdk/test_meow_ui_widgets index ba5e99a..b0e6bfa 100755 Binary files a/patch_v2/sdk/test_meow_ui_widgets and b/patch_v2/sdk/test_meow_ui_widgets differ diff --git a/patch_v2/sdk/test_net_ipc b/patch_v2/sdk/test_net_ipc new file mode 100755 index 0000000..bfdbaaa Binary files /dev/null and b/patch_v2/sdk/test_net_ipc differ diff --git a/patch_v2/sdk/test_notif_gesture b/patch_v2/sdk/test_notif_gesture index d8de637..95b4637 100755 Binary files a/patch_v2/sdk/test_notif_gesture and b/patch_v2/sdk/test_notif_gesture differ diff --git a/patch_v2/sdk/test_notif_open b/patch_v2/sdk/test_notif_open index 76292e8..5151584 100755 Binary files a/patch_v2/sdk/test_notif_open and b/patch_v2/sdk/test_notif_open differ diff --git a/patch_v2/sdk/test_notify_post b/patch_v2/sdk/test_notify_post new file mode 100755 index 0000000..dd60837 Binary files /dev/null and b/patch_v2/sdk/test_notify_post differ diff --git a/patch_v2/sdk/test_overlay_suppress b/patch_v2/sdk/test_overlay_suppress new file mode 100755 index 0000000..582c0e6 Binary files /dev/null and b/patch_v2/sdk/test_overlay_suppress differ diff --git a/patch_v2/sdk/test_pinyin b/patch_v2/sdk/test_pinyin index ce9fedc..0258c4c 100755 Binary files a/patch_v2/sdk/test_pinyin and b/patch_v2/sdk/test_pinyin differ diff --git a/patch_v2/sdk/test_sb_collapse b/patch_v2/sdk/test_sb_collapse index 385d32b..f805398 100755 Binary files a/patch_v2/sdk/test_sb_collapse and b/patch_v2/sdk/test_sb_collapse differ diff --git a/patch_v2/sdk/test_sdk_input_keys b/patch_v2/sdk/test_sdk_input_keys index d38b64b..49907e9 100755 Binary files a/patch_v2/sdk/test_sdk_input_keys and b/patch_v2/sdk/test_sdk_input_keys differ diff --git a/patch_v2/sdk/test_ui_audio_host b/patch_v2/sdk/test_ui_audio_host index 3890d78..39a9881 100755 Binary files a/patch_v2/sdk/test_ui_audio_host and b/patch_v2/sdk/test_ui_audio_host differ diff --git a/scripts/build/fw_content.conf b/scripts/build/fw_content.conf index 2a107ed..7a0ead8 100644 --- a/scripts/build/fw_content.conf +++ b/scripts/build/fw_content.conf @@ -28,7 +28,7 @@ # ---- 用户态/服务守护二进制(usr/bin,全部 chmod 755 静态 MIPS)---- patch_v2/bootmenu/meow_bootmenu|/usr/bin/meow_bootmenu|4441822d87e76bfce2ce584604ac3830|引导选单 M2.85(sysui让位 agent-216) -patch_v2/meow_launcherd/meow_launcherd|/usr/bin/meow_launcherd|94a33b7b13b93a2972adc70ccc029de2|主屏 launcher M6.9(M6.8 全量+327 M7.0b 试点:电源菜单+NowPlaying 控件化+s_ui_theme 薄壳读 SDK+两页 i18n 抽串) +patch_v2/meow_launcherd/meow_launcherd|/usr/bin/meow_launcherd|5f01ecdcbe1b3a32ca4d907f05e2dbb7|主屏 launcher M6.19: agent-373 五修复收编(keys.c 亮度恢复修 PIN 亮屏亮度跳 fix④),重链新SDK 51caee4a(含 meow_ui_gfx 恢复边沿整帧重画 fix①②③ ncm 后台化叠层/穿透) patch_v2/meow_watchdog/meow_watchdog|/usr/bin/meow_watchdog|553840cdc79d8d7bed766a5e3981ac57|应用看门狗 M2.97(音量反转 + DS1039 平方锥度 + 插孔消抖) patch_v2/meow_svcd/meow_svcd|/usr/bin/meow_svcd|605646c4cc93f2baf29071ea6bc870ea|服务管理器 M2.77(+崩溃上报 meow_crash agent-187) mips-repo/bins/meow_mdns/bin/meow_mdns|/usr/bin/meow_mdns|d6b0151bda26768f19952dae1342610d|mDNS 应答器 M2.78(启动竞态+换IP重检 agent-194) @@ -36,8 +36,8 @@ patch_v2/meow_sysui/meow_sysui|/usr/bin/meow_sysui|173013cc8f0899b3001d0b894ac0d patch_v2/meowpkg/nyai|/usr/bin/nyai|a7d24553ebf8e47cd251236840b5259c|nyai M2.97(M2.89 导出 exec 相对化 + 271 app.json depends 安装提示 agent-240/271) patch_v2/meow_svc_power/meow_svc_power|/usr/bin/meow_svc_power|39ae3aa255695a97f4281124c0621b52|电源专项服务(充电态权威源 mp2731-charger/status,修 battery/status 撒谎恒 Discharging) patch_v2/meow_verify/meow_verify|/usr/bin/meow_verify|6c2e8ed24aebe5caf41e7aed391312d5|II期验签 2B(多公钥+v2签名块 agent-221) -patch_v2/meow_crash/meow_crash|/usr/bin/meow_crash|62781084445e82351bcadbdadbe4c7fc|系统级崩溃捕获 (agent-187) -patch_v2/meow_svc_perf/meow_svc_perf|/usr/bin/meow_svc_perf|7ea196de9d59ab8cbd1e26edb7762772|性能专项服务 M2.75(net_json 补 network 闭合括号,修 /api/system JSON 解析失败) +patch_v2/meow_crash/meow_crash|/usr/bin/meow_crash|f02c4f45099157dc388b3d10b443aa57|系统级崩溃捕获 (agent-187) + log升级: 清理前缀扩 perf_/hang_ 承接1GB纪律 (337) +patch_v2/meow_svc_perf/meow_svc_perf|/usr/bin/meow_svc_perf|566f0615698efe32c1f90a9c62f09148|性能专项服务 M2.75 + log升级(337:卡死快照+perf基线) + OOM治理C(348:Cached/Dirty监控降载, /tmp/meow_cachegov_c.state单点) patch_v2/meow_audiod/meow_audiod|/usr/bin/meow_audiod|b122c9b81880b00f705bca30e42f3d58|独占音频守护 M6.5(M6.0 全量+315 孤儿会话两段式回收:客户端崩溃/OOM 杀后 STATE 回干净无流基线) patch_v2/meow_audiod/meow_audio|/usr/bin/meow_audio|891ab26475ef9e9931bf9147d265dce4|audiod 用户侧 CLI M2.97(+monitor 实时电平表/文本电平条;status 补 rms/peak) patch_v2/meow_powermgr/meow_powermgr|/usr/bin/meow_powermgr|1ef2f8afc07acc7aa09dee9f7149aba8|功耗管理专项服务期2 M6.1(期1+history/top/events+mp2731 current_now 实测充电功率+WiFi 未关联档/BT rfkill 细化+系数路径 /data/meow/powermgr/;IPC 0xD0-D7)
实体远端(域名)本端状态寿命