diff --git a/README.md b/README.md index c94ccc1..a6a3a03 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ MeowOS 应用框架 SDK 的**公开仓库**——面向第三方开发者,让 MeowOS-SDK/ ├── sdk/ # meow_ui 应用框架静态库源码(自包含,可交叉 → sdk/meow_ui.a) │ ├── meow_ui.h # 伞头(#include 聚合 include/ 子头;App 只 include 这个) -│ ├── include/ # 20 个模块子头(含 box/anim/scroll/str 等)+ meow_ipc.h / meow_font.h / meow_notif.h(vendored) +│ ├── include/ # 24 个模块子头(含 box/anim/scroll/str/lazygrid/select/cachegov/net 通道等)+ meow_ipc.h / meow_font.h / meow_notif.h(vendored) │ ├── meow_font.c # 预渲染位图字体运行时(meow_font.a 源码,自包含) │ ├── build_sdk.sh # 交叉编译 meow_ui.a(mipsel 静态库,内含 meow_font.o) │ └── test_*.c # SDK 宿主逻辑测试(test_host_stub.c 打桩,无硬件可跑) @@ -60,6 +60,10 @@ cd ../examples/hello - **字符串资源 / i18n**(§13):`meow_str`/`meow_strf`/`meow_str_set_locale`——内嵌 `zh_cn`/`en_us` 双语表 + TF 卡覆盖表;UI 文案零硬编码,按 locale 取整串。 - **图标库 `sb_icons`**(§5.8):30 个 MD3 Material Symbols 位图动作图标(`MEOW_ICON_*`),`meow_topappbar_set_item_icon` 把 TopAppBar 动作项换成位图图标(修复 ⚙↻⌕ 豆腐)。 - **图片降采样 `meow_img_downscale`**(`include/meow_ui_image.h`):把解码图降到 ≤maxdim 边长省内存(封面/大图用,如 8.28MB→0.78MB)。 +- **2 维虚拟化宫格 `meow_lazygrid`**(§12.4):卡片/图标宫格二维虚拟化——只实例化可见行(固定 item 池 ≤96、视口行数+2×cols),`count+bind` 数据源、长列表不卡;命中 `meow_lazygrid_hit/tap_at` 把滚动+网格坐标映射回 item idx;滚动复用 `meow_scroll`(fling/snap)。 +- **枚举/预设选择器 `meow_select`**(§5.8.6):开箱即用下拉模态——BottomSheet(标题 + `meow_listview` SINGLE 单选列表)+ `meow_ui_modal_capture/restore` 背景快照(关闭无残影);点选项=选中+`on_change` 回调,点面板外/左缘=取消。 +- **磁盘页缓存治理 `meow_cachegov`**(OOM_PAGECACHE_GOVERNANCE):App 登记缓存文件 + read_pos 回调,SDK 做 DONTNEED 治理(L0 曲终整文件丢 / L1 播放中丢已读前缀保 keep_hot 窗口);refault 滚窗逐级回退(放大 keep_hot→降 L0→关 A);`meow_cachegov_download/prefetch_paused` 提供给下载/预取方降载查询。 +- **网络自愿通道 `meow_ui_net`**(netmgrd 期 4):联网前 `meow_net_policy_check(host)` 自查是否被策略放行(netmgrd 不在/超时→乐观放行);`meow_net_report(rx,tx)` 自愿上报本进程收发字节(best-effort,失败静默)。 ## 构建要求 diff --git a/sdk/build_sdk.sh b/sdk/build_sdk.sh index e1bbf5d..2b0bf4c 100755 --- a/sdk/build_sdk.sh +++ b/sdk/build_sdk.sh @@ -14,8 +14,9 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架 rm -f meow_ui.a *.o # meow_font.c = 预渲染位图字体运行时(自包含:只含 meow_font.h + 标准头;最小 zlib ABI 自声明, -# 链接时用 sysroot 的 libz.a),随 SDK 一并 vendored。新模块(box/anim/scroll/str)随主仓增量并入。 -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_font meow_ui_box meow_ui_anim meow_ui_scroll meow_ui_str" +# 链接时用 sysroot 的 libz.a),随 SDK 一并 vendored。新模块(box/anim/scroll/str + cachegov/lazygrid/ +# select/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_font meow_ui_box meow_ui_anim meow_ui_scroll meow_ui_str meow_cachegov meow_lazygrid meow_select meow_net_ipc" CFLAGS="-Os -static -Iinclude -I." for f in $OBJS; do echo "== cc $f ==" diff --git a/sdk/include/meow_cachegov.h b/sdk/include/meow_cachegov.h new file mode 100644 index 0000000..54991d1 --- /dev/null +++ b/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/sdk/include/meow_ipc.h b/sdk/include/meow_ipc.h index 6e7a1c6..ada8255 100644 --- a/sdk/include/meow_ipc.h +++ b/sdk/include/meow_ipc.h @@ -35,6 +35,12 @@ /* 列表编码上限(count 个 NUL 字符串时,distinct 工作缓冲上限) */ #define MEOW_IPC_CARGO 1024u /* 守护进程入队拷贝上限(可小于 MEOW_IPC_MAX) */ +/* ---- page-cache 治理(OOM_PAGECACHE_GOVERNANCE.md,③混合)---- + * meow_svc_perf(C:/proc/meminfo Cached 监控降载)把「是否暂停下载/预取」写入此共享状态文件 + * (一行式 key=value;App/SDK meow_cachegov 只读,系统级单点,不重复起线程)。 + * 路径可被 env MEOW_CACHEGOV_C_FILE 覆盖(宿主测试隔离)。 */ +#define MEOW_CACHEGOV_C_FILE "/tmp/meow_cachegov_c.state" + /* ---- 消息类型(FRAMEWORK_DESIGN §1 表)---- */ enum { MSG_NONE = 0x0000, @@ -601,4 +607,34 @@ typedef struct m_net_dns_result { char text[MEOW_IPC_MAX - 8]; } m_net_dns_result_t; +/* ---- 期 4:SDK 网络自愿通道(NETMGR_DESIGN §6.3/§4;0xEE/0xEF)---- + * 一、meow_net_policy_check(host):框架 App 联网前自查 host 是否被 netmgrd 策略放行—— + * 服务端以调用方 app_id(msg.app_id)→ 实体 app:,评估 policy_net_blocked(entity) + * || dns_query_domain_denied(entity, host),放行=allow 1 / 拒绝=block 0;netmgrd 不在 → SDK 侧放行。 + * 二、meow_net_report(rx,tx):App 自愿上报本进程收发字节数(内核无 per-socket/per-uid 计数, + * §4 诚实声明的「字节级记账走 SDK 自报」);netmgrd 记入 per-实体统计(status JSON entity_bytes)。 + * 读级/上报任意调用者(自愿自报,无副作用;block 判定由服务端策略引擎权威执行)。 + * (0x00EE 曾留作「事件推送 订阅制」,期 4 起改作 SDK 通道;事件推送期 2 从未实装。) */ +enum { /* 与主 enum 分列,追加式不改既有行 */ + MSG_NET_SDK = 0x00EE, /* ↑ op=0 策略检查 / op=1 字节上报 */ + MSG_NET_SDK_RESULT = 0x00EF /* ↓ code + (CHECK)allow + 状态文本 */ +}; +enum { NET_SDK_CHECK = 0, NET_SDK_REPORT = 1 }; + +typedef struct m_net_sdk { + uint32_t op; /* NET_SDK_CHECK / NET_SDK_REPORT */ + char app_id[MEOW_APP_ID_MAX]; /* 调用方 application(SDK 用 env MEOW_LOG_APP;CLI/测试可任意) */ + char host[64]; /* CHECK:待查域名 */ + uint32_t rx; /* REPORT:本进程累计 RX 字节 */ + uint32_t tx; /* REPORT:本进程累计 TX 字节 */ +} m_net_sdk_t; /* 4+32+64+4+4 = 108 B */ + +typedef struct m_net_sdk_result { + uint32_t code; /* MEOW_OK / MEOW_ERR_* */ + int32_t allow; /* CHECK:1=allow 0=block(block 由服务端策略引擎判定) */ + uint16_t text_len; + uint16_t pad; + char text[MEOW_IPC_MAX - 12]; +} m_net_sdk_result_t; + #endif /* MEOW_IPC_H */ diff --git a/sdk/include/meow_lazygrid.h b/sdk/include/meow_lazygrid.h new file mode 100644 index 0000000..98859a5 --- /dev/null +++ b/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/sdk/include/meow_select.h b/sdk/include/meow_select.h new file mode 100644 index 0000000..1aba5bb --- /dev/null +++ b/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/sdk/include/meow_ui_net.h b/sdk/include/meow_ui_net.h new file mode 100644 index 0000000..332d434 --- /dev/null +++ b/sdk/include/meow_ui_net.h @@ -0,0 +1,33 @@ +/* + * meow_ui_net.h — MeowOS SDK 网络自愿通道(NETMGR_DESIGN §6.3/§4;追加式 API,含入 meow_ui.h)。 + * + * 定位:框架 App 联网前的**自愿自治**通道 + **自愿字节上报**,二者都不强制(防君子不防小人)。 + * - meow_net_policy_check(host):联网前自查 host 是否被 netmgrd 管控策略放行。 + * 返回 0=allow(可联网);1=block(策略拒绝,App 应中止本次联网)。 + * netmgrd 不在 / 超时 → 0(乐观放行,绝不因监控不可用而误封业务)。 + * - meow_net_report(rx,tx):App 自愿上报本进程累计收发字节。内核无 per-socket/per-uid + * 字节计数器(NETMGR_DESIGN §4 表),§4 的「字节级精确记账走 SDK 自报通道」由此落地; + * netmgrd 记入 per-实体统计(status JSON `entity_bytes`)。best-effort,失败静默不影响业务。 + * 用法:App 在发起 HTTP/TLS 前调 meow_net_policy_check(host),若返回 1 则按产品语义处理(如提示被限)。 + */ +#ifndef MEOW_UI_NET_H +#define MEOW_UI_NET_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* 检查 host 是否被策略放行。0=allow;1=block;netmgrd 不在/超时 → 0(乐观放行)。 + * host 为域名(如 "music.163.com");判定以当前 App(MEOW_LOG_APP)为实体,服务端策略引擎权威执行。 */ +int meow_net_policy_check(const char *host); + +/* 上报本进程累计收发字节(best-effort,失败静默)。 */ +void meow_net_report(uint32_t rx, uint32_t tx); + +#ifdef __cplusplus +} +#endif + +#endif /* MEOW_UI_NET_H */ diff --git a/sdk/meow_cachegov.c b/sdk/meow_cachegov.c new file mode 100644 index 0000000..f538df3 --- /dev/null +++ b/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/sdk/meow_lazygrid.c b/sdk/meow_lazygrid.c new file mode 100644 index 0000000..f965078 --- /dev/null +++ b/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/sdk/meow_net_ipc.c b/sdk/meow_net_ipc.c new file mode 100644 index 0000000..5247867 --- /dev/null +++ b/sdk/meow_net_ipc.c @@ -0,0 +1,157 @@ +/* + * meow_net_ipc.c — MeowOS SDK 网络自愿通道 → netmgrd IPC 桥(期4;追加式协议 0x00EE/0x00EF)。 + * + * 与 meow_notify_ipc.c 同型:只做「一次性 connect + send + read」,不维护连接/订阅循环。 + * - meow_net_policy_check(host):MSG_NET_SDK op=NET_SDK_CHECK(app_id=MEOW_LOG_APP + host); + * 读到 allow=1 → 返回 0(allow),allow=0 → 返回 1(block);任何失败(无 socket/超时/非法应答) + * → 返回 0(乐观放行,绝不因监控不可用误封业务——NETMGR_DESIGN §6.3 自愿性质)。 + * - meow_net_report(rx,tx):MSG_NET_SDK op=NET_SDK_REPORT;send 后读一次应答头即关(不读文本), + * best-effort,失败静默(返回值 void)。 + * 映射层(SDK 入参 ↔ IPC m_net_sdk_t 字段)集中在本文件;app_id 取 env MEOW_LOG_APP(与 meow_ui_log.c + * 口径一致),socket 路径取 env MEOW_NET_SOCK(宿主/qemu 覆盖)缺省 /usr/data/meow_net.sock。 + * + * 线程安全:app_id pthread_once 缓存;本文件纯传输层不调 meow_log(与 meow_notify_ipc.c 同约定)。 + */ +#include "meow_net_ipc.h" +#include "meow_ipc.h" /* 共享协议:帧头/m_net_sdk_t/m_net_sdk_result_t/MSG_NET_SDK* —— 防漂移 */ + +#include /* snprintf */ +#include /* getenv */ +#include +#include /* EINTR */ +#include +#include +#include +#include +#include + +#define MEOW_NET_IPC_TIMEOUT_MS 500 /* 应答读超时:netmgrd 单线程 poll,>500ms 视为降级 */ + +static pthread_once_t g_once = PTHREAD_ONCE_INIT; +static char g_app_id[MEOW_APP_ID_MAX] = "app"; + +static void ipc_init(void) +{ + const char *s = getenv("MEOW_LOG_APP"); + if (s && s[0]) snprintf(g_app_id, sizeof g_app_id, "%s", s); +} + +const char *meow_net_ipc_app_id(void) +{ + pthread_once(&g_once, ipc_init); + return g_app_id; +} + +/* socket 路径:优先 env MEOW_NET_SOCK,缺省 /usr/data/meow_net.sock。 */ +static const char *ipc_sock_path(void) +{ + const char *s = getenv("MEOW_NET_SOCK"); + return (s && s[0]) ? s : "/usr/data/meow_net.sock"; +} + +/* 连接 socket;失败 -1(调用方降级)。 */ +static int conn_path(const char *path) +{ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) return -1; + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, path, sizeof a.sun_path - 1); + if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; } + return fd; +} +static int write_all(int fd, const uint8_t *p, size_t n) +{ + size_t off = 0; + while (off < n) { + ssize_t w = write(fd, p + off, n - off); + if (w < 0) { if (errno == EINTR) continue; return 0; } + if (w == 0) return 0; + off += (size_t)w; + } + return 1; +} +static int read_all(int fd, uint8_t *p, size_t n) +{ + size_t off = 0; + while (off < n) { + ssize_t r = read(fd, p + off, n - off); + if (r < 0) { if (errno == EINTR) continue; return 0; } + if (r == 0) return 0; + off += (size_t)r; + } + return 1; +} +static int send_frame(int fd, uint16_t type, uint32_t req_id, const void *payload, uint32_t len) +{ + uint8_t hdr[MEOW_IPC_HDR_LEN]; + uint32_t magic = MEOW_IPC_MAGIC, rl = len; + uint16_t ver = MEOW_IPC_VERSION, t = type; + memcpy(hdr + 0, &magic, 4); memcpy(hdr + 4, &ver, 2); memcpy(hdr + 6, &t, 2); + memcpy(hdr + 8, &req_id, 4); memcpy(hdr + 12, &rl, 4); + if (!write_all(fd, hdr, sizeof hdr)) return 0; + if (len && payload && !write_all(fd, payload, len)) return 0; + return 1; +} +static void set_timeout(int fd) +{ + struct timeval tv; + tv.tv_sec = MEOW_NET_IPC_TIMEOUT_MS / 1000; + tv.tv_usec = (MEOW_NET_IPC_TIMEOUT_MS % 1000) * 1000; + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); +} + +int meow_net_policy_check(const char *host) +{ + if (!host || !host[0]) return 0; + m_net_sdk_t q; + memset(&q, 0, sizeof q); + q.op = NET_SDK_CHECK; + snprintf(q.app_id, sizeof q.app_id, "%s", meow_net_ipc_app_id()); + snprintf(q.host, sizeof q.host, "%s", host); + + int fd = conn_path(ipc_sock_path()); + if (fd < 0) return 0; /* java socket:乐观放行 */ + set_timeout(fd); + int allow = 0; /* 默认放行 */ + if (send_frame(fd, MSG_NET_SDK, 0x4E455443u /* "NETC" */, &q, (uint32_t)sizeof q)) { + uint8_t rhdr[MEOW_IPC_HDR_LEN]; + if (read_all(fd, rhdr, sizeof rhdr)) { + uint16_t rtype; uint32_t rlen; + memcpy(&rtype, rhdr + 6, 2); memcpy(&rlen, rhdr + 12, 4); + if (rtype == MSG_NET_SDK_RESULT && rlen >= 10) { + uint8_t pay[12]; + if (read_all(fd, pay, 12)) { /* code(4)+allow(4)+text_len(2)+pad(2) */ + m_net_sdk_result_t res; + memset(&res, 0, sizeof res); + memcpy(&res.code, pay, 4); + memcpy(&res.allow, pay + 4, 4); + memcpy(&res.text_len, pay + 8, 2); + if (res.text_len < sizeof res.text) + read_all(fd, (uint8_t *)res.text, res.text_len); + if (res.code == MEOW_OK) allow = (res.allow != 0) ? 1 : 0; + } + } + } + } + close(fd); + return allow ? 0 : 1; /* allow=1 → 返回 0(放行);allow=0 → 返回 1(block) */ +} + +void meow_net_report(uint32_t rx, uint32_t tx) +{ + m_net_sdk_t q; + memset(&q, 0, sizeof q); + q.op = NET_SDK_REPORT; + snprintf(q.app_id, sizeof q.app_id, "%s", meow_net_ipc_app_id()); + q.rx = rx; q.tx = tx; + int fd = conn_path(ipc_sock_path()); + if (fd < 0) return; /* 无 socket:静默 */ + set_timeout(fd); + if (send_frame(fd, MSG_NET_SDK, 0x4E455452u /* "NETR" */, &q, (uint32_t)sizeof q)) { + uint8_t rhdr[MEOW_IPC_HDR_LEN]; + read_all(fd, rhdr, sizeof rhdr); /* best-effort:读一次应答头即关,不解析 */ + } + close(fd); +} diff --git a/sdk/meow_net_ipc.h b/sdk/meow_net_ipc.h new file mode 100644 index 0000000..98646c7 --- /dev/null +++ b/sdk/meow_net_ipc.h @@ -0,0 +1,28 @@ +/* + * meow_net_ipc.h — MeowOS SDK 网络自愿通道 → netmgrd IPC 桥(内部头,不随 meow_ui.h 发布)。 + * + * 定位(NETMGR_DESIGN §6.3/§4):让框架 App 的 meow_net_policy_check()/meow_net_report() 真正经 + * AF_UNIX IPC 到 netmgrd(/usr/data/meow_net.sock)。协议帧与负载结构复用共享 meow_ipc.h: + * - MSG_NET_SDK(0x00EE) ↑:op=NET_SDK_CHECK(app_id+host)/ NET_SDK_REPORT(app_id+rx+tx); + * - MSG_NET_SDK_RESULT(0x00EF) ↓:code + (CHECK)allow + 状态文本。 + * 约定:只做「一次性 connect + send + read」,不维护订阅循环(同 meow_notify_ipc.c 型); + * 全部失败路径优雅降级(无 socket/写失败/超时/非法应答 → check 返回 0 放行、report 静默)。 + */ +#ifndef MEOW_NET_IPC_H +#define MEOW_NET_IPC_H + +#include +#include "meow_ui.h" /* meow_net_policy_check / meow_net_report */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* 当前 app 的 app_id(env MEOW_LOG_APP,缺省 "app";pthread_once 缓存,线程安全)。 */ +const char *meow_net_ipc_app_id(void); + +#ifdef __cplusplus +} +#endif + +#endif /* MEOW_NET_IPC_H */ diff --git a/sdk/meow_select.c b/sdk/meow_select.c new file mode 100644 index 0000000..2a26d1f --- /dev/null +++ b/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/sdk/meow_ui.h b/sdk/meow_ui.h index 0e9cd19..f85e1c3 100644 --- a/sdk/meow_ui.h +++ b/sdk/meow_ui.h @@ -32,11 +32,13 @@ * include/meow_ui_log.h 应用日志(§9) * include/meow_ui_notify.h 应用通知(§9.1) * include/meow_ui_config.h 应用配置读写(§9.2) - * include/meow_ui_font.h 字体列表(§5.7)+ 字体分层(§10) + * include/meow_ui_font.h 字体列表(§5.7)+ 字体分层(§10)+ 矢量字 meow_ft_*(§10.1) * include/meow_ui_audio.h 独占音频客户端(§11) * 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,6 +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/sdk/meow_ui_dialog.c b/sdk/meow_ui_dialog.c index 5987bf9..ea70595 100644 --- a/sdk/meow_ui_dialog.c +++ b/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/sdk/meow_ui_internal.h b/sdk/meow_ui_internal.h index 57d3dc6..bd12a64 100644 --- a/sdk/meow_ui_internal.h +++ b/sdk/meow_ui_internal.h @@ -137,6 +137,13 @@ unsigned char *meow_ui_back_buf(void); int meow_ui_back_buf_pitch(void); int meow_ui_back_buf_bpp(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); + /* 实心圆角矩形(控件按钮底色用;公共 §3 只有空心 draw_rect_round)。[D3] */ void meow_gfx_fill_round(int x, int y, int w, int h, int r, uint32_t color); /* 圆角矩形半透明填充(M2.71-bug4):禁用变暗等状态层用,圆角外不叠色(防灰方块)。 */