sdk: 同步主仓新增模块 cachegov/lazygrid/select/net 通道

This commit is contained in:
AskaEth committed 2026-09-03 05:09:05 +08:00
1 parent 0af6af7c7e
commit 64cde13c8d
15 files changed
+1326 -10

No files matched your search

+3 -2
View File
@@ -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 =="
+82
View File
@@ -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 <stddef.h>
#include <stdint.h>
#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 */
+36
View File
@@ -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:<pkg>,评估 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 */
+106
View File
@@ -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 */
+47
View File
@@ -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 */
+33
View File
@@ -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 <stdint.h>
#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 */
+302
View File
@@ -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 <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
/* 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;
}
+350
View File
@@ -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 <string.h>
#include <stdlib.h>
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();
}
+157
View File
@@ -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 <stdio.h> /* snprintf */
#include <stdlib.h> /* getenv */
#include <string.h>
#include <errno.h> /* EINTR */
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/time.h>
#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);
}
+28
View File
@@ -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 <stdint.h>
#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 */
+157
View File
@@ -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 <string.h>
#include <stdlib.h>
#include <unistd.h>
#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;
}
+7 -1
View File
@@ -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 */
+6 -6
View File
@@ -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); }
+7
View File
@@ -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):禁用变暗等状态层用,圆角外不叠色(防灰方块)。 */