diff --git a/examples/hello/build_hello.sh b/examples/hello/build_hello.sh index 98edcb1..640219f 100755 --- a/examples/hello/build_hello.sh +++ b/examples/hello/build_hello.sh @@ -1,16 +1,14 @@ #!/bin/bash -# Build the hello app (SDK B1, 视图框架批次③目录嵌套+视图深度化): 链 meow_ui.a(已内含 meow_font.o), -# 额外链系统 libz.a(meow_font 的 inflate 依赖)+ -lpthread。静态 MIPS32r2 (musl sf)。 -# 批次②:hello.c 拆为多文件(app.c + ui/home.c + ui/full.c + ui/demo.c + ui/common/chrome.c + -# logic/notify.c + logic/state.c)。 -# 批次③:ui/home、ui/demo 子目录化(ui/home/home.c + ui/demo/demo.c);demo 列表区块抽成 -# CONTAINER 子视图 ui/demo/list/list.c。仅改列文件,编译期不改 SDK(patch_v2/sdk/ 零触及)。 -# mipsel -Wall -Wextra 0 warning。 +# build_hello.sh — 构建 hello(SDK 示例 App,视图框架批②③目录嵌套+视图深度化): +# 链 meow_ui.a(已内含 meow_font.o)+ 系统 libz.a(meow_font 的 inflate 依赖)+ -lpthread。 +# 静态 MIPS32r2 (musl sf)。 +# +# 交叉编译器 / sysroot 同 sdk/build_sdk.sh:默认 MeowOS 开发机路径, +# 可用环境变量 MEOW_CC / MEOW_SYSROOT 覆盖。mipsel -Wall -Wextra 0 warning。 set -e cd "$(dirname "$0")" -ROOT="$HOME/Project/R3ProII" -CC="$ROOT/.tools/zig-mipsel-cc" -SYSROOT="$ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf" +CC="${MEOW_CC:-$HOME/Project/R3ProII/.tools/zig-mipsel-cc}" +SYSROOT="${MEOW_SYSROOT:-$HOME/Project/R3ProII/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf}" "$CC" -Os -static -Wall -Wextra -I../sdk -I. \ app.c ui/home/home.c ui/full.c ui/demo/demo.c ui/demo/list/list.c ui/common/chrome.c \ logic/notify.c logic/state.c \ diff --git a/examples/hello/hello b/examples/hello/hello deleted file mode 100755 index 95cf810..0000000 Binary files a/examples/hello/hello and /dev/null differ diff --git a/examples/hello/render_test.png b/examples/hello/render_test.png deleted file mode 100644 index e6f1caa..0000000 Binary files a/examples/hello/render_test.png and /dev/null differ diff --git a/sdk/build_sdk.sh b/sdk/build_sdk.sh index 8c67ac6..a940537 100755 --- a/sdk/build_sdk.sh +++ b/sdk/build_sdk.sh @@ -1,31 +1,29 @@ #!/bin/bash -# build_sdk.sh — 构建 meow_ui.a(mipsel 静态库,含 meow_font.o 内联打包, -# 应用只需链 meow_ui.a + libz.a + -lpthread)。 +# build_sdk.sh — 构建 meow_ui.a(mipsel 静态库,内含 meow_font.o,应用只需链 meow_ui.a + libz.a + -lpthread)。 +# +# 本公开 SDK 仓**自包含**:公共头(meow_ui.h 伞头 + include/ 子头 + meow_ipc.h / meow_font.h / +# meow_notif.h)与字体运行时源码(meow_font.c)都在 sdk/ 内,不再依赖主仓 meow_launcherd/。 +# +# 交叉编译器:默认 MeowOS 开发机路径 $HOME/Project/R3ProII/.tools/zig-mipsel-cc; +# 可用环境变量 MEOW_CC 覆盖(或用你自己装好的 zig 交叉 cc,--target=mipsel-linux-musleabi soft-float)。 set -e -ROOT="$HOME/Project/R3ProII" cd "$(dirname "$0")" -CC="$ROOT/.tools/zig-mipsel-cc" +CC="${MEOW_CC:-$HOME/Project/R3ProII/.tools/zig-mipsel-cc}" AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架构无关) rm -f meow_ui.a *.o -# -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移) -OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_pinyin meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view meow_ui_nav meow_ui_media meow_ui_audio stb_image" -CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd" +# meow_font.c = 预渲染位图字体运行时(自包含:只含 meow_font.h + 标准头;最小 zlib ABI 自声明, +# 链接时用 sysroot 的 libz.a),随 SDK 一并 vendored。 +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" +CFLAGS="-Os -static -Iinclude -I." for f in $OBJS; do echo "== cc $f ==" "$CC" $CFLAGS -c "$f.c" -o "$f.o" done -# meow_font.o(复用 launcher 字库,打进 meow_ui.a → 应用只链一个库) -TMP="$(mktemp -d)" -( cd "$TMP" && "$AR" x "$ROOT/patch_v2/meow_launcherd/meow_font.a" && ls *.o ) -cp "$TMP"/*.o meow_font.o 2>/dev/null || { echo "meow_font extract failed"; rm -rf "$TMP"; exit 1; } -rm -rf "$TMP" - rm -f meow_ui.a for f in $OBJS; do "$AR" rcs meow_ui.a "$f.o"; done -"$AR" rcs meow_ui.a meow_font.o echo "== meow_ui.a ==" ls -la meow_ui.a "$AR" t meow_ui.a diff --git a/sdk/include/meow_font.h b/sdk/include/meow_font.h new file mode 100644 index 0000000..431cf32 --- /dev/null +++ b/sdk/include/meow_font.h @@ -0,0 +1,174 @@ +#ifndef MEOW_FONT_H +#define MEOW_FONT_H +/* + * meow_font.h — MeowOS 预渲染位图字体运行时(独立、可复用,不绑定启动器)。 + * + * 数据来源:由 gen/gen_font.py 生成的 font.png(灰度 PNG,bit depth 4|8,含各字号面) + * + font.idx(索引:头部 + 每面元数据 + 每字符 16B 记录)。 + * + * 设备端加载:模块内建最小 PNG 解码(仅支持灰度非隔行)经 zlib inflate + 反滤波还原像素, + * 无需 libpng;交叉链接 musl 静态 libz.a。像素面按 depth=4|8 打包存储,取像素时按 nibble 解包。 + * + * 绘制模型:drawtext(fb, x, y, utf8, color, size);(x,y)=行首「升线(top)」锚点; + * baseline = y + ascent;每字形 blit 于 (x + pen + dx, y + dy);pen 累加 advance。 + * color 为 565 位 RGB(R 在 bit15..11,G bit10..5,B bit4..0),与 launcher 一致。 + * 无字回退:缺码位画空心方块(tofu)并前进空格宽度。 + * + * 字号:字库含若干原生面(如 28、20)。drawtext 请求 size 时先找精确匹配面; + * 若无则就近取一面做最近邻缩放(最优仍用原生字号)。 + */ +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* 设置 framebuffer 几何:宽/高(像素)与每行字节数(如 480*2)。绘制前调用一次。 */ +void meow_font_set_fb(unsigned fb_w, unsigned fb_h, unsigned pitch_bytes); + +/* 加载字库(全部字号)。成功返回 0,失败返回负值。可重复调用(先卸载旧的)。 */ +int meow_font_load(const char *png_path, const char *idx_path); + +/* 释放字库。 */ +void meow_font_unload(void); + +/* 按档装载大字库(字体档位分层,见 MEOWOS_SPEC_REF §5.2/§5.7.8 与 meow_ui.h §10): + * tier = 0 common≈4k(小/默认)/ 1 gbk≈20k+(大字库)/ 2 full(全,可选)。 + * png/idx 为调用方已按档解析的设备绝对路径(launcher 经 env 注入)。切换时先 unload 旧 + * 再 load 新(释放旧 atlas —— 大字库 GBK 解码约 5-15MB,避免新旧同时驻留内存)。 + * 返回 0 成功,负值失败(失败时旧库已释放,调用方应回退卡默认/固件)。 + * 由 meow_ui_gfx.c 的 meow_font_set_tier() 在档位变化时唤起;meow_font_load() 仍是装载 + * 系统默认(slot0)字库的入口。 */ +int meow_font_reload_tier(int tier, const char *png, const char *idx); + +/* 某字号是否存在(命中原生面返回 1)。 */ +int meow_font_has_size(int size); + +/* UTF-8 字符串渲染宽度(px)。字面不存在返回 0。 */ +int meow_font_width_text(const char *utf8, int size); + +/* 行高(px = ascent + descent)。 */ +int meow_font_line_height(int size); +int meow_font_ascent(int size); + +/* 绘制文本:(x,y)=行首升线顶部;color=RGB565。已按 fb_w/fb_h 裁剪。 */ +void meow_font_drawtext(uint16_t *fb, int x, int y, const char *utf8, + uint16_t color, int size); + +/* 描边绘制:先以 outline_color 将每字形向 4/8 邻域平移 ol 绘制一圈,再以 color 填充。 */ +void meow_font_drawtext_outline(uint16_t *fb, int x, int y, const char *utf8, + uint16_t color, uint16_t outline_color, int ol, int size); + +/* ---- 字符分类器(multi_font 逐字符路由的依据;按 codepoint 归类)---- */ +typedef enum { + MEOW_FCLS_CJK = 0, /* CJK 表意文字 + 扩展 / 兼容表意(U+3400-4DBF/4E00-9FFF/20000-2FA1F/…) */ + MEOW_FCLS_CJK_SYM = 1, /* 全角/中文标点(U+3000-303F、U+FF00-FFEF、U+2014/2018-201D/2026、U+00B7) */ + MEOW_FCLS_NUM = 2, /* 0-9 */ + MEOW_FCLS_LATIN = 3, /* A-Za-z(基本拉丁字母) */ + MEOW_FCLS_LATIN_SYM = 4, /* 半角标点(ASCII 非字母数字:空格/括号/引号/运算符/控制符) */ + MEOW_FCLS_OTHER = 5, /* 其他(假名/韩文/他文字/扩展区等) */ + MEOW_FCLS_COUNT = 6 +} meow_fclass; + +/* 把一个 codepoint 归类为上述之一。 */ +int meow_font_class_of(uint32_t cp); + +/* 校验码位是否在「将用于渲染它的字库」中存在字形(1=有,0=缺字/字库未加载→渲染为豆腐)。 + * multi_font 开启时按分类路由到对应字库,关闭时仅查全局字库(slot 0)。 */ +int meow_font_has_glyph(uint32_t cp); + +/* ---- 多字体(MEOW_FCLS* 逐字符路由)---- + * 槽位模型:slot 0 = meow_font_load() 装载的全局/默认字库(font.file);额外字库经 + * meow_font_load_slot() 装载后,用 meow_font_set_class_slot() 把某字符类指到某一槽。 + * meow_font_drawtext/width_text/line_height/... 在 meow_font_enable_multifont(1) 开启时 + * 逐字符分类 → 用该类别对应槽的字库取字形与度量;缺该类别字库/未加载 → 回退 slot 0。 + * 关闭(默认)时一律走 slot 0,行为与单字体完全一致(零额外开销)。 + * 同 family 去重、类别字库 ≤3 套上限 + 打包面内存预算由 meow_font_config_multifont() 统一处理(launcher 与 SDK 共用)。 */ +#define MEOW_MAX_SLOTS 4 /* slot0(全局) + ≤3 套类别字库。真机 56MB:多字体每套字库都驻留整幅 + * 打包面(common≈4.2MiB / gbk≈16.6MiB),5 套太多会叠加 OOM;故硬上限 + * 收到 4,且 meow_font_config_multifont 另按 meow_font.c 内 + * MEOW_MULTIFONT_MEM_BUDGET 做「总打包面字节」预算(超出→新增类别回退全局)。 */ + +int meow_font_load_slot(int slot, const char *png_path, const char *idx_path); /* 0=成功,负值失败 */ +void meow_font_unload_slot(int slot); +void meow_font_enable_multifont(int on); /* 0=单字体(默认);1=逐字符分类路由 */ +void meow_font_set_class_slot(meow_fclass cls, int slot); /* slot 越界/载入失败 → 该类别回退 slot 0 */ + +/* 一个字符类的候选字库路径(绝对路径,已由调用方按卡根解析;png=NULL/空 = 跟随全局)。 */ +typedef struct { + const char *png; + const char *idx; +} meow_font_class_cfg; + +/* 依据开关 + 各类候选配置多字体:同 png 路径去重(含与全局相同 → 直接用 slot 0), + * 独立字库数(含全局)> MEOW_MAX_SLOTS 时截断并置 *truncated=1(此前已装载的保留)。 + * 返回 0 成功,负值失败;*distinct = 实际装载的不同字库数(含全局)。 */ +int meow_font_config_multifont(int on, + const char *global_png, const char *global_idx, + const meow_font_class_cfg cfg[MEOW_FCLS_COUNT], + int *distinct, int *truncated); + +/* ---- MeowPanel 预留:TF 卡字体库 .zip 导入/导出(TODO,未实现)---- + * 当前仅声明占位,供 MeowPanel 后续接入;不实现、不接 zip 库。 + * 调用方约定(待实现):导入=校验 .zip 内含 font.png+font.idx+font.json 三件套, + * 校验通过后解压到 TF 卡 MeowOS/fonts///(family/weight 取自 font.json 的 + * family/weight 或 zip 名,缺省 weight=regular;冲突时覆盖需先确认);导出=把 + * MeowOS/fonts/// 三件套打包为 。 + * 目录布局与 font.json schema 见 docs/MEOWOS_SPEC_REF.md §1.2/§5.2。 + * 返回码:MEOW_OK=0 成功,负值错误码(沿用本模块失败负值约定)。 */ +int meow_font_import_zip(const char *zip_path); /* TODO:MeowPanel */ +int meow_font_export_zip(const char *family, const char *out_zip); /* TODO:MeowPanel */ + +/* ================================================================== */ +/* M2.58 字体索引 + 应用字库查询(规格 §5.7:索引文件 / 查列表 / 丢失降级默认) */ +/* ================================================================== */ +/* 系统字体列表的持久化快照 = TF 卡 /MeowOS/config/fonts/index(CSV,每套一条: + * "family","weight","path","label","tier",glyph_count + * path = 卡根相对 font.png(如 MeowOS/fonts/misans/regular/font.png) + * label = 显示名(含字重 + 尺寸标记,如 `MiSans Regular(common≈4k)`) + * tier = common|gbk|full(档位,信息用 + 驱动按需加载) + * launcher 扫描 MeowOS/fonts/// 后写该文件(覆盖,含默认行 index0); + * SDK 侧 meow_font_get_available_count()/enumerate() 只从这个文件读,不依赖 launcher 内存数组; + * 无卡/无 index → 仅 1 项板载默认(family=default)。 */ +#define MEOW_FONT_INDEX_REL "MeowOS/config/fonts/index" + +/* 权威列表数据模型(SDK 侧快照,枚举时填充;规格 §5.7.1)。 */ +#ifndef MEOW_FONT_INFO_DEFINED +#define MEOW_FONT_INFO_DEFINED +typedef struct meow_font_info { + char family[64]; /* family slug = MeowOS/fonts// 一级目录名 */ + char name[64]; /* 显示名(index 无单列 → 回退 family) */ + char weight[32]; /* weight slug(regular|bold|…,缺省 regular) */ + char tier[8]; /* 档位 common|gbk|full(info 用) */ + int glyph_count; /* font.json glyph_count(info 用) */ + char label[128]; /* index 显示名(含字重 + 尺寸标记) */ + int size_list[16]; /* 原生字号表(index 无 → 空) */ + int size_count; /* 有效字号数 */ + int bit_depth; /* 4|8(index 无 → 0) */ + char path_png[320]; /* 卡根相对 MeowOS/fonts///font.png */ +} meow_font_info; +#endif + +/* 卡根:env MEOW_CARD_ROOT 优先,否则默认 /usr/data/mnt/sd_0。 */ +const char *meow_font_card_root(void); +/* /MeowOS/config/fonts/index 绝对路径写入 out;0=成功。 */ +int meow_font_index_path(char *out, int olen); +/* 读 index → 填充 out 数组(最多 max 条),返回「总条数」(无 index/不可读/空 → 1=合成默认行)。 + * 注意 out[i].path_png 为「卡根相对」路径,需经卡根解析为绝对路径再使用。 */ +int meow_font_index_load(meow_font_info *out, int max); + +/* 应用可读 TF 卡字体列表(规格 §5.7.2): + * - get_available_count():可用字库数(含默认;无卡 → 1)。 + * - enumerate(idx, out):按序号取,越界返回 0;idx=0=默认行(family=default)。 + * - select_by_id(id):id=0 跟随系统默认(font.file,经 env MEOW_FONT_PNG/IDX); + * id>0 会话内固定用该套;该套字库缺失 → 降级默认(不丢请求)+ WARN 日志 + * (meow_log→app 日志)。成功 0,越界负值。 */ +int meow_font_get_available_count(void); +int meow_font_enumerate(int idx, meow_font_info *out); +int meow_font_select_by_id(int id); + +#ifdef __cplusplus +} +#endif +#endif /* MEOW_FONT_H */ diff --git a/sdk/include/meow_ipc.h b/sdk/include/meow_ipc.h new file mode 100644 index 0000000..6e7a1c6 --- /dev/null +++ b/sdk/include/meow_ipc.h @@ -0,0 +1,604 @@ +/* meow_ipc.h — MeowOS IPC 协议定义(FRAMEWORK_DESIGN §1 / MEOWOS_API.md §1)。 + * + * 客户端(meow_ipc 测试钩子)与守护进程(meow_launcherd)共用一份,杜绝两端漂移。 + * 传输:AF_UNIX SOCK_STREAM,路径默认 /usr/data/meow_ipc.sock(可被 --ipc-sock / + * MEOW_IPC_SOCK 覆盖,供 qemu sim)。 + * 帧格式:定长头(16B) + 变长负载,**全部小端 LE**(mipsel 原生)。 + * + * struct ipc_hdr { LE 字节序 + * uint32_t magic; = 0x4D4F5701 == 'MEOW'|1 + * uint16_t version; = 1 + * uint16_t type; = msg_type + * uint32_t req_id; = 请求id,响应回显 + * uint32_t len; = payload 字节数 + * }; + * + * 本阶段(D · M0+M1+M2+M3+M4)通讯子集:MSG_HELLO/ACK/ERR + MSG_ACT_DECLARE/QUERY/RESULT + + * MSG_ACT_FOCUS/BACK + MSG_NOTIFY/PROGRESS/QUERY/DISMISS* + MSG_NAV(导航栈上报/退出) + * + MSG_META/MSG_PLAYCTL(媒体会话,阶段 D 附录 B) + * + MSG_REGISTER/SERVICE_OK/SERVICE_STOP/MSG_SYS(后台服务注册/停止/熔断事件,M4)。 + * picker(0x0070/0x0071)已落地(二期)。 + */ +#ifndef MEOW_IPC_H +#define MEOW_IPC_H + +#include +#include + +#define MEOW_IPC_MAGIC 0x4D4F5701u +#define MEOW_IPC_VERSION 1u +#define MEOW_IPC_PATH "/usr/data/meow_ipc.sock" +#define MEOW_IPC_SOCK_LEN 108 /* sockaddr_un.sun_path */ +#define MEOW_IPC_MAX 4096u /* 单帧负载上限 */ +#define MEOW_IPC_HDR_LEN 16u + +/* 列表编码上限(count 个 NUL 字符串时,distinct 工作缓冲上限) */ +#define MEOW_IPC_CARGO 1024u /* 守护进程入队拷贝上限(可小于 MEOW_IPC_MAX) */ + +/* ---- 消息类型(FRAMEWORK_DESIGN §1 表)---- */ +enum { + MSG_NONE = 0x0000, + MSG_HELLO = 0x0001, /* ↑ 声明 app_id/版本/能力 */ + MSG_ACK = 0x0002, /* ↓ 握手/应答 */ + MSG_ERR = 0x0003, /* ↓ 错误码 */ + MSG_REGISTER = 0x0010, /* ↑ 注册后台服务(保持连接) */ + MSG_SERVICE_OK = 0x0011, /* ↓ 服务登记成功 */ + MSG_SERVICE_STOP = 0x0012, /* ↑ 请求停止服务 */ + MSG_NOTIFY = 0x0020, /* ↑ 推送/更新通知 */ + MSG_NOTIFY_ACK = 0x0021, /* ↓ 落库结果 */ + MSG_NOTIFY_DISMISS = 0x0022, /* ↑ 撤下通知 */ + MSG_NOTIFY_LIST = 0x0023, /* ↓ 下发通知清单(重同步) */ + MSG_NOTIFY_PROGRESS = 0x0024,/* ↑ 实时更新通知进度(仅改内存活跃槽,不落盘) */ + MSG_NOTIFY_QUERY = 0x0025, /* ↑ 查询内存活跃通知集(MeowPanel 实时化前置) */ + MSG_NOTIFY_DISMISS_ALL = 0x0026,/* ↑ 一键清除(保留 ongoing/media/progress 进行中,同通知中心 UI) */ + MSG_NOTIFY_QUERY_RESULT = 0x0027, /* ↓ 活跃通知集序列化文本(JSON,字段同 TF 归档) */ + MSG_NOTIF_OPEN = 0x0028, /* ↑ App 前台顶部下滑 → 请求 launcher 展开通知中心叠加会话(§10.5) */ + MSG_NOTIF_CLOSE = 0x0029, /* ↑ App 告知结束/退出 → launcher 收尾兜底(可选用途,§10.5) */ + MSG_ACT_DECLARE = 0x0030, /* ↑ (重)声明路由表 */ + MSG_ACT_QUERY = 0x0031, /* ↑ 解析 activity 链路 */ + MSG_ACT_RESULT = 0x0032, /* ↓ 返回链路 */ + MSG_ACT_FOCUS = 0x0033, /* ↑ 上报当前顶层 activity(keep_awake 判定,M2.17) */ + MSG_ACT_BACK = 0x0034, /* ↑ 上报回到根/无 activity(清 keep_awake,M2.17) */ + MSG_NAV = 0x0040, /* ↑ 上报导航/请求退出 */ + MSG_SHOW_ACTIVITY= 0x0041, /* ↓ 令前端应用显示某屏 */ + MSG_PERF_QUERY = 0x0050, /* ↑ 性能/系统聚合查询(专项服务 meow_svc_perf) */ + MSG_PERF_RESULT = 0x0051, /* ↓ 聚合 JSON(perf/system 子集) */ + MSG_POWER_ACTION = 0x0060, /* ↑ 电源/系统动作信号(专项服务 meow_svc_power) */ + MSG_POWER_STATUS = 0x0061, /* ↓ 动作受理/结果 + 状态文本 */ + /* ---- picker:带返回值的跨 App 调用(M3 延伸,AMSD_DESIGN §6.2 二期已落地)---- + * 语义:App A 经 MSG_PICKER_CALL 请求 App B 弹一个 selector 并返回所选值(如文件选择器)。 + * amsd 为路由/决策者:它查 picker 表(app.json `picker.=`)决定 handler App, + * 经 MSG_AMS_START(扩载荷含 picker 上下文)拉起 handler;handler 结果经 MSG_PICKER_RESULT + * 回传,launcher 转发 amsd 记录。属「跨 App 带返回值」调用。负载结构见下方 + * m_picker_call_t / m_picker_result_t。 */ + MSG_PICKER_CALL = 0x0070, /* ↑ 请求跨 App picker 调用(发起方 A → 持有方 B;→launcher→amsd) */ + MSG_PICKER_RESULT = 0x0071, /* ↑ picker 返回值(候选结果回 A;→launcher→amsd) */ + MSG_META = 0x0072, /* ↑ 媒体会话上报元数据/进度/状态(阶段 D 附录 B) */ + MSG_PLAYCTL = 0x0073, /* ↓ 播放控制下发/↑ 控制器转发到最近活跃媒体会话 */ + /* ---- amsd 控制(AMSD_DESIGN §4.2):amsd→launcher,申请拉起/终止某 App ---- + * 语义:amsd 为「启动/终止」决策者 → launcher(执行器)执行。payload 见下。 */ + MSG_AMS_START = 0x00A0, /* → 请求 launcher 拉起某 App(pkg + 可选 activity) */ + MSG_AMS_KILL = 0x00A1, /* → 请求 launcher 终止某 App(SIGTERM 语义) */ + /* ---- audiod 独占音频服务(devdocs/plan/AUDIOD_DESIGN.md §2):0x00C0-0x00CA ---- + * 语义:App 只经 SDK 客户端 API(meow_ui_audio.c)喂 PCM,audiod 独占 hw:0,0。 + * 独占+抢占(后开赢);软混音已否决(MIPS32 软 SRC 伤音质,留混音器接口位)。 */ + MSG_AUDIO_OPEN = 0x00C0, /* ↑ 开流(m_audio_open;client 原生 rate/ch/fmt/flags) */ + MSG_AUDIO_OPEN_ACK = 0x00C1, /* ↓ 授权(stream_id + grant_rate/ch/fmt + err;grant=客户端应产出的格式) */ + MSG_AUDIO_WRITE = 0x00C2, /* ↑ PCM 字节流(按 grant 格式交织;单帧 ≤4096B,多帧连续发,无回执) */ + MSG_AUDIO_PAUSE = 0x00C3, /* ↑ 暂停(u32 stream_id) */ + MSG_AUDIO_RESUME = 0x00C4, /* ↑ 恢复(u32 stream_id) */ + MSG_AUDIO_STOP = 0x00C5, /* ↑ 停止并关流(u32 stream_id) */ + MSG_AUDIO_PREEMPTED= 0x00C6, /* ↓ 被抢占通知(m_audio_preempted;旧流被后开流赶走,App 收 UI 提示) */ + MSG_AUDIO_QUERY = 0x00C7, /* ↑ 状态快照查询(空负载) */ + MSG_AUDIO_STATE = 0x00C8, /* ↓ 状态快照文本(NUL 结尾;当前流/参数/帧数/underrun/路由/音量) */ + MSG_AUDIO_ROUTE = 0x00C9, /* ↑ 手动路由(u32 port:0=auto 2=3.5 3=4.4;调试/面板用) */ + MSG_AUDIO_VOLUME = 0x00CA, /* ↑ 音量(i32 pct:-1=查询 0-100=设置;v1 透传写 DAC numid=1/2) */ + /* ---- meow_powermgr 功耗管理服务(devdocs/plan/POWER_MANAGER_DESIGN.md §6.1):0x00D0-0x00D7 ---- + * 语义:功耗维度的查看/分析/管理(svc_power 管动作、svc_perf 管性能,powermgr 管功耗)。 + * 独立 socket /usr/data/meow_pm.sock(--sock/MEOW_PM_SOCK 覆盖)。鉴权分级同 svc_power: + * QUERY/HISTORY/EVENTS=读级任意调用者;POLICY_SET=动作级(期 3 落地时接 SO_PEERCRED 同源代码)。 */ + MSG_PM_QUERY = 0x00D0, /* ↑ 功耗聚合查询(m_pm_query_t{subset}:PM_SUBSET_* 位;0=全部) */ + MSG_PM_RESULT = 0x00D1, /* ↓ 聚合 JSON(电池深加工/功耗估算/分解/策略,形状对齐 §3/§4.1) */ + /* 0x00D2 MSG_PM_POLICY_GET / 0x00D3 MSG_PM_POLICY_SET —— 期 3(profile/低电阈值读改,SET 动作级鉴权) + * 0x00D4 MSG_PM_HISTORY(u32 hours)/ 0x00D5 MSG_PM_HISTORY_RESULT(统计 JSON)—— 期 2 + * 0x00D6 MSG_PM_EVENTS / 0x00D7 MSG_PM_EVENTS_RESULT(异常耗电事件清单)—— 期 2 */ + MSG_SYS = 0x00F0 /* ↓ 系统事件(守护关闭/服务死亡/播放器状态) */ +}; + +/* ---- 返回码(MEOWOS_API.md §1,与 HTTP 层一致)---- */ +enum { + MEOW_OK = 0, + MEOW_ERR_INVAL = -1, + MEOW_ERR_NOENT = -2, + MEOW_ERR_EXISTS = -3, + MEOW_ERR_PERM = -4, + MEOW_ERR_IO = -5, + MEOW_ERR_NOMEM = -6, + MEOW_ERR_NOTINIT = -7, + MEOW_ERR_BUSY = -8, + MEOW_ERR_TIMEOUT = -9, + MEOW_ERR_NODEV = -10, + MEOW_ERR_INTERNAL = -255 +}; + +/* ---- HELLO flags(附录 A 声明式权限)---- */ +#define MF_BACKGROUND 0x00000001u /* 可后台常驻 */ +#define MF_PLAYBACK 0x00000002u /* 后台音频/播放 */ + +/* ---- MSG_META flags(元数据有效位)---- */ +#define MF_ART 0x00000004u /* 封面/专辑图已提供 */ +#define MF_LYRIC 0x00000008u /* 歌词已提供 */ +#define MF_COVER 0x00000010u /* artwork 为可用路径/槽位 */ + +/* ---- 标识/长度 ---- */ +#define MEOW_APP_ID_MAX 32 +#define MEOW_ACT_MAX 64 +#define MEOW_CHAIN_MAX 160 + +/* ---- 负载结构(全部 LE;mipsel 原生对齐,字段 4 字节对齐)---- */ + +/* MSG_HELLO ↑:应用声明 */ +typedef struct m_hello { + char app_id[MEOW_APP_ID_MAX]; + uint32_t version; + uint32_t flags; + uint32_t caps; /* 预留能力位 */ +} m_hello_t; /* 44 B */ + +/* MSG_ACK ↓:握手/应答 */ +typedef struct m_ack { + uint32_t code; /* 0=MEOW_OK */ + uint32_t status; /* 附加(如已注册 app 数) */ +} m_ack_t; /* 8 B */ + +/* MSG_ERR ↓:错误码 + 短消息 */ +typedef struct m_err { + uint32_t code; + uint16_t msg_len; + char msg[64]; +} m_err_t; + +/* ================= 后台服务注册(M4,FRAMEWORK_DESIGN §9) = */ + +/* MSG_REGISTER ↑:无头后台服务注册。调用方(App 前台进程)须: + * ① app.json 声明 `background=true` 或 HELLO flags 含 MF_BACKGROUND(否则 ERR_PERM); + * ② app.json `service.=mode:restart:max:window_ms` 静态声明该服务(否则 ERR_NOENT); + * ③ 注册后保持 IPC 连接不断(launcher 以连接存活做熔断广播的投递判定)。 + * svc=NUL 结尾服务名(≤15 字符);flags 按位:MF_BACKGROUND(申请后台常驻)。 */ +typedef struct m_register { + char app_id[MEOW_APP_ID_MAX]; /* 32 */ + char svc[16]; /* 服务名(app.json service.) */ + uint32_t flags; /* MF_BACKGROUND 等 */ +} m_register_t; /* 52 B */ + +/* MSG_SERVICE_OK ↓:用 m_ack_t(code=0;status=服务子进程 pid)。 */ + +/* MSG_SERVICE_STOP ↑:请求停止自己已注册的服务(SIGTERM,不触发重启)。 */ +typedef struct m_service_stop { + char app_id[MEOW_APP_ID_MAX]; /* 32 */ + char svc[16]; +} m_service_stop_t; /* 48 B */ + +/* MSG_SYS ↓:系统事件广播(守护关闭/服务死亡/播放器状态)。 + * event:MEOW_SYS_*;app_id/svc 为事件主体(SHUTDOWN 时可空)。 */ +enum { MEOW_SYS_SHUTDOWN = 1, /* 守护进程即将关闭(关机/重启前回收) */ + MEOW_SYS_SVC_DEAD = 2, /* 后台服务熔断(超 max_restart 不再拉起) */ + MEOW_SYS_PLAYER_STATE = 3 }; /* 播放器状态变更(预留) */ +typedef struct m_sys { + uint32_t event; /* MEOW_SYS_* */ + char app_id[MEOW_APP_ID_MAX]; /* 32 */ + char svc[16]; /* SVC_DEAD 时为熔断服务名 */ +} m_sys_t; /* 52 B */ + +/* MSG_ACT_QUERY ↑:解析某 app 的 activity 链路 */ +typedef struct m_act_query { + char app_id[MEOW_APP_ID_MAX]; + char activity[MEOW_ACT_MAX]; +} m_act_query_t; /* 96 B */ + +/* MSG_ACT_FOCUS ↑:上报当前顶层 activity(keep_awake 判定,M2.17) */ +typedef struct m_act_focus { + char app_id[MEOW_APP_ID_MAX]; + char activity[MEOW_ACT_MAX]; +} m_act_focus_t; /* 96 B */ + +/* MSG_ACT_RESULT ↓:返回链路(root..target),depth=链路逻辑深度 */ +typedef struct m_act_result { + uint32_t depth; + uint32_t code; /* MEOW_OK / MEOW_ERR_NOENT(act 未注册) */ + uint16_t chain_len; /* chain 有效字节 */ + uint16_t pad; + char chain[MEOW_CHAIN_MAX]; +} m_act_result_t; /* 172 B */ + +/* MSG_ACT_DECLARE ↑:运行时声明路由表。app_id + count 个 NUL 结尾字符串, + * 每串形如 "="(如 "nowplaying=home;search;nowplaying")。 + * 守护进程并集覆盖静态 app.json 路由。 */ +typedef struct m_act_declare { + char app_id[MEOW_APP_ID_MAX]; + uint16_t count; + uint16_t pad; + char entries[MEOW_IPC_CARGO - MEOW_APP_ID_MAX - 4]; +} m_act_declare_t; + +/* MSG_NAV nav_type 取值(FRAMEWORK_DESIGN §3 导航栈) */ +enum { MEOW_NAV_REPORT = 0, /* 应用内逐屏回退/前进:上报当前栈深 + 顶层 activity */ + MEOW_NAV_EXIT = 1 }; /* 应用在根屏按返回 → 请求启动器终止应用、回主屏 */ + +/* MSG_NAV ↑:上报导航/请求退出(M3 导航栈;launcher 主循环单写者消费)。 + * - REPORT:应用弹/推自己的屏后上报;depth=当前逻辑深度(1..16),current=当前顶层 activity_id; + * - EXIT :应用在根屏再按返回;depth 置 0,current 可空。 + * launcher 据此维护每应用 nav_stack(≤16 深),并在 EXIT 时终止前台应用会话。 */ +typedef struct m_nav { + char app_id[MEOW_APP_ID_MAX]; + uint32_t nav_type; /* MEOW_NAV_REPORT / MEOW_NAV_EXIT */ + uint32_t depth; /* REPORT: 1..16;EXIT: 0 */ + char current[MEOW_ACT_MAX]; /* REPORT: 当前顶层 activity_id;EXIT: 可空 */ +} m_nav_t; + +/* MSG_META ↑:媒体会话实时上报(阶段 D 附录 B;FRAMEWORK_DESIGN 附录 B 的 m_meta 微调落实)。 + * 声明 MF_PLAYBACK 的 App 首次上报 → 注册为媒体源;后续更新(含 1s 级进度)。字段全 LE 定长。 + * - state:0=停止 1=播放 2=暂停 3=缓冲。 + * - flags:MF_ART/MF_LYRIC/MF_COVER(可多;声明哪类元数据有效)。 + * - artwork/lyric:封面/歌词源(本地路径或 MeowOS 存储槽位);空=无(mf 位同时清)。 + * - app_id:上报源头(与 HELLO 的 app_id 一致),launcher 据此归并同一会话。 + * 边界:防越界(128 长度由 meow_ipc 双方同源 + 接收侧截断;payload < MEOW_IPC_CARGO)。 */ +typedef struct m_meta { + char title[128]; + char artist[128]; + char album[128]; + uint32_t pos_ms; /* 当前播放位置 */ + uint32_t dur_ms; /* 总时长 */ + uint32_t state; /* 0=停止 1=播放 2=暂停 3=缓冲 */ + uint32_t flags; /* MF_ART / MF_LYRIC / MF_COVER */ + char artwork[192]; /* 封面: 本地路径或 MeowOS 存储槽位 */ + char lyric[192]; /* 歌词: 路径或文本槽位 */ + char app_id[MEOW_APP_ID_MAX]; +} m_meta_t; /* 128+128+128+16+192+192+32 = 828 B */ + +/* MSG_PLAYCTL ↓(launcher→App)/↑(控制器→launcher 转发到最近活跃媒体会话):播放控制。 + * cmd:1=播放 2=暂停 3=上一首 4=下一首 5=seek 6=播放/暂停切换(toggle,源侧按当前态翻转);其余保留。 + * seek_ms:cmd=5 时目标位置(ms);其余置 0。 + * 语义:通知中心卡片/播放页按钮、物理媒体键 → meow_media_playctl(cmd,seek_ms) 经 + * 「最近活跃媒体会话」的连接 fd 下发;宿主验证经 meow_ipc playctl 发到 launcher 转发。 + * 物理播放/暂停键(164)在 SDK 侧恒映射 cmd=6(不再本地判 state:走自有 IPC 线程的源 + * 不经 SDK meta 上报,SDK 侧 state 可能恒 0);launcher 透传 cmd 不改写。 */ +typedef struct m_playctl { + uint32_t cmd; /* 1=播放 2=暂停 3=上一首 4=下一首 5=seek 6=播放/暂停toggle */ + int64_t seek_ms; /* cmd=5: 目标位置 ms;否则 0 */ +} m_playctl_t; /* 4 (+4 pad) + 8 = 16 B(mipsel int64 8 对齐) */ + +/* MSG_NOTIFY ↑:推送/更新通知(freedesktop 对齐 + MeowOS 扩展;定长 LE)。 + * - summary/body/app_icon/expire_timeout 直通 freedesktop 字段;app_icon 即 SDK `icon` + * (icon name 或 path;映射为空串时 launcher 用该 app 默认图标回退); + * - target_activity/progress 为 MeowOS 专有(存储层映射为 hints 的 + * meow.target_activity / meow.progress); + * - urgency 为 MeowOS 扩展(0-3,缺省 NORMAL;旧调用置 0=旧 pad 位); + * - actions[]/hints{} v1 走定长空串(M2b D-Bus 桥再填);media/ongoing 标记。 + * 宿主:NOTIFICATION_SYSTEM.md §2/§4 + FRAMEWORK_DESIGN §4/附C。 */ +typedef struct m_notify { + char app_id[MEOW_APP_ID_MAX]; /* 来源应用;系统事件="system" */ + char category[24]; /* 通知类型(媒体通知="media") */ + char summary[64]; + char body[96]; + char app_icon[48]; /* SDK icon;空=launcher 按 app_id 回退 app 图标 */ + char target_activity[MEOW_ACT_MAX]; /* MeowOS:点击跳转目标 activity */ + uint32_t replaces_id; /* 0=新建;>0=替换已存在通知 */ + uint32_t expire_timeout; /* ms;0xFFFFFFFF=ONGOING 常驻 */ + int32_t progress; /* MeowOS:0-100;<0=无 */ + uint8_t media; /* 1=媒体通知 */ + uint8_t ongoing; /* 1=常驻通知 */ + uint8_t urgency; /* 0-3(低/普通/高/紧急);越界由 SDK 钳位缺省 NORMAL;旧调用置 0=旧 pad 位 → LOW/静默 */ + uint8_t pad; +} m_notify_t; /* 定长 LE;尾部 4 字节(media/ongoing/urgency/pad)与旧版 pad 等宽 */ + +/* MSG_NOTIFY_PROGRESS ↑:实时更新一条已存在通知的进度(仅改 launcher 内存活跃槽, + * 不落盘——TF 快照只在创建/结束(100% 或 dismiss)写)。 */ +typedef struct m_notify_progress { + uint32_t nid; /* launcher 分配的通知 id(MSG_NOTIFY_ACK.status) */ + int32_t progress; /* 0-100;<0=清除进度条 */ +} m_notify_progress_t; /* 8 B */ + +/* MSG_NOTIFY_QUERY_RESULT ↓:活跃通知集序列化文本(JSON 数组;NUL 结尾)。 + * 字段与 TF 归档一致:id/app_id/category/summary/body/urgency/ts/progress/ongoing/media/ + * target_activity(+app_icon)。payload 定长 LE,整个结构 < MEOW_IPC_MAX。 */ +typedef struct m_notify_query_result { + uint32_t code; /* MEOW_OK / MEOW_ERR_* */ + uint16_t text_len; /* text 有效字节(不含 NUL) */ + uint16_t pad; + char text[MEOW_IPC_MAX - 12]; /* 序列化 JSON 数组 */ +} m_notify_query_result_t; /* 4096 B */ + +/* ================= App 前台划出通知中心(FRAMEWORK_DESIGN §10.5) = */ + +/* MSG_NOTIF_OPEN ↑(App→launcher):App 进程内顶部下滑(MEOW_EV_NOTIF_PULL),请求 launcher + * 展开通知中心叠加会话。App 只检测+上报、**不渲染系统 UI**(MEOW_APP_SDK §6.1 ③)。 + * 负载字段全 LE 定长。 */ +typedef struct m_notif_open { + char app_id[MEOW_APP_ID_MAX]; /* 32 */ + uint32_t event_seq; /* App 侧事件序号(防重放/去抖,可选;0=填充默认) */ + uint16_t origin_x; /* 手势按下原点 X(防御性校验:应≈顶部,best-effort 可 0) */ + uint16_t origin_y; /* 手势按下原点 Y(launcher 复核是否在顶部热区) */ + uint16_t hotzone; /* 上报时 SDK 的热区高(28 或 4,见 meow_ui_statusbar_hotzone) */ + uint8_t flags; /* 预留:位0=App 声明 fullscreen */ + uint8_t pad; +} m_notif_open_t; /* 32+4+2+2+2+1+1 = 44 B(LE,4 字节对齐) */ + +/* MSG_NOTIF_CLOSE ↑(App→launcher,可选用途):App 告知结束/退出,launcher 收尾兜底 + * (清 active 标记/SIGCONT 兜底)。非可靠链路:launcher 主要靠 waitpid/kill(pid,0) 自感知。 */ +typedef struct m_notif_close { + char app_id[MEOW_APP_ID_MAX]; /* 32 */ + uint32_t reason; /* 0=App 主动退出;1=App 请求关闭通知中心;2=App 崩溃 */ +} m_notif_close_t; /* 32+4 = 36 B */ + +/* ================= 专项服务 IPC(SYSTEM_FRAMEWORK_SERVICES §2.2/§2.3)= */ + +/* ---- MSG_PERF_QUERY 子集位(0=全部)---- */ +#define PERF_SUBSET_PERF (1u << 0) /* cpu/mem/uptime/temp/storage/tfcard */ +#define PERF_SUBSET_SYSTEM (1u << 1) /* system(version/hostname/kernel/arch) */ + +/* MSG_PERF_QUERY ↑:性能/系统聚合查询(服务端 meow_svc_perf) */ +typedef struct m_perf_query { + uint32_t subset; /* 0=全部;PERF_SUBSET_* 组合 */ +} m_perf_query_t; /* 4 B */ + +/* MSG_PERF_RESULT ↓:聚合 JSON(payload 为 NUL 结尾 JSON 文本) */ +typedef struct m_perf_result { + uint32_t code; /* MEOW_OK / MEOW_ERR_* */ + uint32_t subset; /* 回显请求 subset */ + uint16_t json_len; /* json 有效字节(不含 NUL) */ + uint16_t pad; + char json[MEOW_IPC_MAX - 12]; /* 聚合 JSON(.data 形状,§2.2) */ +} m_perf_result_t; + +/* ---- POWER_* 动作枚举(m_power_action_t.cmd;与 CLI §2.4 同源)---- */ +enum { + POWER_SHUTDOWN = 0, /* 关机 */ + POWER_REBOOT = 1, /* 重启回 MeowOS(强制写 boot_default=0) */ + POWER_UPGRADE = 2, /* 写 mtd5=ota:kernel2 进升级模式 */ + POWER_BACK_HIBY = 3, /* 写 boot_default=1 + reboot(回 HiBy) */ + POWER_SET_DEFAULT_MEOW = 4, /* 只写 boot_default=0,不重启 */ + POWER_SET_DEFAULT_HIBY = 5, /* 只写 boot_default=1,不重启 */ + POWER_STATUS = 6, /* 综合状态查询 */ + POWER_BATTERY = 7, /* 电池查询 */ + POWER_REBOOT_REASON = 8, /* 上次重启原因 */ + POWER_UPGRADE_STATUS = 9, /* 升级标志(mtd5=ota:kernel2?) */ + POWER_SCREEN = 10, /* 屏幕电源:flag 0=off/1=on/2=timeout(秒取 arg) */ + POWER_DAC = 11 /* DAC:flag 0=off/1=on/2=status */ +}; + +/* MSG_POWER_ACTION ↑:电源/系统动作信号(服务端 meow_svc_power) */ +typedef struct m_power_action { + uint32_t cmd; /* POWER_* 枚举 */ + uint32_t flag; /* 随 cmd;POWER_SCREEN/DAC 用 */ + char arg[32]; /* 可选文本参数(如 screen timeout 秒数字符串) */ +} m_power_action_t; /* 40 B */ + +/* MSG_POWER_STATUS ↓:动作受理/结果 + 状态文本(CLI/IPC 展示) */ +typedef struct m_power_status { + uint32_t code; /* MEOW_OK / MEOW_ERR_PERM / MEOW_ERR_INVAL / MEOW_ERR_NODEV */ + uint32_t cmd; /* 回显 cmd */ + uint16_t msg_len; /* msg 有效字节(不含 NUL) */ + uint16_t pad; + char msg[96]; /* 结果/状态文本 */ +} m_power_status_t; + +/* ---- meow_powermgr 功耗管理(POWER_MANAGER_DESIGN §6.1;服务端 meow_powermgr)---- */ + +/* MSG_PM_QUERY 子集位(0=全部) */ +#define PM_SUBSET_BATTERY (1u << 0) /* 电池深加工:state/pct/mv/health/续航估算 */ +#define PM_SUBSET_ESTIMATE (1u << 1) /* 当前功耗估算:est_mw + confidence(estimated/derived) */ +#define PM_SUBSET_BREAKDOWN (1u << 2) /* 功耗分解:各子系统分量(§3.2 经验模型,系数待标定) */ +#define PM_SUBSET_POLICY (1u << 3) /* 策略档位快照(期 3;期 1 恒 null) */ + +/* MSG_PM_QUERY ↑:功耗聚合查询 */ +typedef struct m_pm_query { + uint32_t subset; /* 0=全部;PM_SUBSET_* 组合 */ +} m_pm_query_t; /* 4 B */ + +/* MSG_PM_RESULT ↓:聚合 JSON(payload 为 NUL 结尾 JSON 文本,同 m_perf_result_t 口径) */ +typedef struct m_pm_result { + uint32_t code; /* MEOW_OK / MEOW_ERR_* */ + uint32_t subset; /* 回显请求 subset */ + uint16_t json_len; /* json 有效字节(不含 NUL) */ + uint16_t pad; + char json[MEOW_IPC_MAX - 12]; /* 聚合 JSON(§3/§4.1 形状) */ +} m_pm_result_t; + +/* ---- amsd 控制(AMSD_DESIGN §4.2)---- */ + +/* MSG_AMS_START →:请求 launcher 拉起某 App。app_id=包名(权威);activity 空=裸启动 + * (launcher launch_app),非空=resolve_chain+launch_activity。 + * picker 上下文(二期):picker=1 时本次启动为「picker handler 调用」——launcher 注入 env + * MEOW_PICKER_ACTION/MEOW_PICKER_REQ_ID/MEOW_PICKER_CALLER 到子进程;handler App 据此进入 + * picker 模式,选完后经 MSG_PICKER_RESULT 回传。 */ +typedef struct m_ams_start { + char app_id[MEOW_APP_ID_MAX]; /* 32 */ + char activity[MEOW_ACT_MAX]; /* 64;空=裸启动 */ + uint32_t picker; /* 1=带 picker 上下文(注入 MEOW_PICKER_*) */ + uint32_t picker_req_id; /* picker 请求号(回显到结果) */ + char picker_action[24]; /* picker action(如 "pick_file");picker=0 忽略 */ + char picker_caller[MEOW_APP_ID_MAX]; /* 32:发起方 app_id(""=系统/CLI) */ +} m_ams_start_t; /* 160 B */ + +/* MSG_AMS_KILL →:请求 launcher 终止某 App(SIGTERM 语义,执行器=launcher)。 */ +typedef struct m_ams_kill { + char app_id[MEOW_APP_ID_MAX]; /* 32 */ +} m_ams_kill_t; /* 32 B */ + +/* ---- picker 负载(二期)---- */ + +/* MSG_PICKER_CALL ↑:发起跨 App picker 调用(App A → launcher → amsd 路由;或 amsd 内部)。 + * caller=发起方 app_id(""=系统/CLI);req_id=调用方自填(结果/PICKER_RESULT 回显); + * action=要调用的 picker 类型(如 "pick_file");hint=初始目录等提示(空=缺省)。 */ +typedef struct m_picker_call { + char caller[MEOW_APP_ID_MAX]; /* 32 */ + uint32_t req_id; /* 调用方请求号 */ + char action[24]; /* picker action */ + char hint[128]; /* 提示(初始目录等);空=缺省 */ + uint32_t flags; /* 预留 */ +} m_picker_call_t; /* 192 B */ + +/* MSG_PICKER_RESULT ↑:picker 返回值(handler App → launcher → amsd)。caller/req_id/action + * 回显调用方;code=MEOW_OK/MEOW_ERR_*;value=返回项(路径/文本;空=用户取消)。 */ +typedef struct m_picker_result { + char caller[MEOW_APP_ID_MAX]; /* 32:回显发起方(""=系统/CLI) */ + uint32_t req_id; /* 回显请求号 */ + char action[24]; /* 回显 action */ + uint32_t code; /* MEOW_OK / MEOW_ERR_* */ + char value[256]; /* 返回项(路径/文本);空=cancel */ +} m_picker_result_t; /* 320 B */ + +/* ================= audiod 独占音频服务(AUDIOD_DESIGN.md) = */ + +/* PCM 格式枚举(客户端原生 fmt 与 grant_fmt 共用):1=S16_LE 2=S24_LE 3=S32_LE 4=FLOAT_LE。 + * v1 实装 S16/S32(S16 直通 + S32 升位);S24 按 32-in-32 处理;FLOAT v1 拒绝。 */ +enum { + AUDIO_FMT_S16 = 1, + AUDIO_FMT_S24 = 2, + AUDIO_FMT_S32 = 3, + AUDIO_FMT_FLOAT = 4 +}; + +/* MSG_AUDIO_OPEN ↑:请求开流(App 原生声源参数)。grant_*(ACK)回 audiod 实际授权给客户端的 + * 产出格式(率/声道/位深)——客户端必须按 grant 格式产出 PCM(SDK meow_ui_audio 负责归一: + * mono→stereo 复制 / S16↔S32 定值升降位)。ch>2 或 FLOAT → ERR_INVAL。 */ +typedef struct m_audio_open { + char app_id[MEOW_APP_ID_MAX]; /* 32 */ + uint32_t rate; /* 原生采样率(应用源,如 44100/48000) */ + uint16_t ch; /* 原生声道数(1=mono 2=stereo) */ + uint16_t fmt; /* 原生格式(AUDIO_FMT_*) */ + uint32_t flags; /* 预留(0) */ +} m_audio_open_t; /* 32+4+2+2+4 = 44 B */ + +/* MSG_AUDIO_OPEN_ACK ↓:授权结果。err=MEOW_OK 才有效(stream_id>0 且 grant_* 为实际授权值); + * err<0 时 stream_id=0、grant_* 视为无效(客户端 open 返回负错误码)。 */ +typedef struct m_audio_open_ack { + uint32_t stream_id; /* 授权流 id(>0) */ + uint32_t grant_rate; /* 客户端应产出的采样率(=客户端 rate,无 SRC) */ + uint16_t grant_ch; /* 客户端应产出的声道数(DAC 双声道 → 通常 2) */ + uint16_t grant_fmt; /* 客户端应产出的格式(AUDIO_FMT_*,=硬件最优) */ + int32_t err; /* MEOW_OK / MEOW_ERR_INVAL / MEOW_ERR_NODEV / MEOW_ERR_BUSY 等 */ +} m_audio_open_ack_t; /* 4+4+2+2+4 = 16 B */ + +/* MSG_AUDIO_PAUSE/RESUME/STOP ↑:流控制(按 stream_id 定位)。 */ +typedef struct m_audio_stream { + uint32_t stream_id; +} m_audio_stream_t; /* 4 B */ + +/* MSG_AUDIO_PREEMPTED ↓:被抢占通知(后开流赢;旧流收此消息,App 应暂停 UI/提示)。 + * 旧流仍可读但 PCM 被丢弃(draining),直到客户端 CLOSE/断开。 */ +typedef struct m_audio_preempted { + uint32_t stream_id; /* 被抢占的流 id */ + char by_app[MEOW_APP_ID_MAX]; /* 32:抢占者 app_id */ +} m_audio_preempted_t; /* 4+32 = 36 B */ + +/* MSG_AUDIO_ROUTE ↑:手动切换输出路由(调试/面板用)。port:0=auto(跟随插孔) 2=3.5 3=4.4。 */ +typedef struct m_audio_route { + uint32_t port; /* 0=auto 2=3.5 3=4.4 */ +} m_audio_route_t; /* 4 B */ + +/* MSG_AUDIO_VOLUME ↑:音量查询/设置。pct<0(-1)=查询当前音量;0-100=设置(写 DAC numid=1/2 + * + 持久化 settings.conf `audio.volume`,与 watchdog/launcher 同源同键)。 */ +typedef struct m_audio_volume { + int32_t pct; /* -1=查询;0-100=设置 */ +} m_audio_volume_t; /* 4 B */ + +/* ================= meow_netmgrd 网络管理服务(devdocs/plan/NETMGR_DESIGN.md §3/§10)===== + * 语义:接口统一层(WiFi/BT/USB-eth/rfkill/IP/路由/DNS 只读)查询与控制的系统权威。 + * 独立 socket /usr/data/meow_net.sock(--sock/MEOW_NET_SOCK 覆盖)。进程形态对齐 powermgr: + * svcd 托管 trigger=boot + 单一二进制双形态(/usr/bin/meow_netmgrd 守护 + /usr/bin/meow_net CLI)。 + * 鉴权分级同 svc_power(§10/期1):读级(STATUS/IFACE、WIFI_CTL 内 status/networks、BT_CTL 内 + * status/paired)任意调用者;写级(WIFI_CTL 内 scan/connect/disconnect/on/off/rfkill_set/udhcpc、 + * BT_CTL 内 on/off)仅系统身份(SO_PEERCRED → /usr/bin/meow_* 或 os.meow.* 包)。 + * HibyOS 模式(/tmp/meow_boot_mode=hiby):读级可查,写级拒 + 记日志。 + * 期 1 实装 0x00E0-0x00E7;0x00E8-0x00EF 保留(期2:连接普查/DNS 代理;期3:管控策略/事件推送)。 */ +enum { /* 与主 enum 分列,追加式不改既有行 */ + MSG_NET_STATUS_REQ = 0x00E0, /* ↑ 概览查询(空负载=全部) */ + MSG_NET_STATUS_RESULT = 0x00E1, /* ↓ 概览 JSON */ + MSG_NET_IFACE_REQ = 0x00E2, /* ↑ 接口列表/详情(name 空=全部) */ + MSG_NET_IFACE_RESULT = 0x00E3, /* ↓ 接口 JSON */ + MSG_NET_WIFI_CTL = 0x00E4, /* ↑ 子命令字串(scan/connect/... + rfkill/udhcpc) */ + MSG_NET_WIFI_RESULT = 0x00E5, /* ↓ 结果/文本 */ + MSG_NET_BT_CTL = 0x00E6, /* ↑ 子命令字串(status/on/off/paired) */ + MSG_NET_BT_RESULT = 0x00E7 /* ↓ 结果/文本 */ + /* 0x00E8-0x00EF 保留:0xE8 连接表 / 0xEA 策略读写 / 0xEC DNS 控制 / 0xEE 事件推送(期 2/3) */ +}; + +/* MSG_NET_STATUS_REQ ↑(空负载=全部;预留 subset 位,期 1 恒 0)→ MSG_NET_STATUS_RESULT ↓: + * 概览 JSON(mode/ifaces[]/wifi/bt/rfkill[],见 NETMGR_DESIGN §3 统一模型)。*/ +typedef struct m_net_status_result { + uint32_t code; /* MEOW_OK / MEOW_ERR_* */ + uint16_t json_len; /* json 有效字节(不含 NUL) */ + uint16_t pad; + char json[MEOW_IPC_MAX - 8]; /* 概览 JSON(.data 形状) */ +} m_net_status_result_t; + +#define NET_IFACE_NAME_MAX 32 /* 接口名上限(lo/wlan0/usb-eth) */ + +/* MSG_NET_IFACE_REQ ↑:name 空=全部接口;否则单接口详情。→ MSG_NET_IFACE_RESULT ↓。 */ +typedef struct m_net_iface_req { + char name[NET_IFACE_NAME_MAX]; +} m_net_iface_req_t; /* 32 B */ + +typedef struct m_net_iface_result { + uint32_t code; + uint16_t json_len; + uint16_t pad; + char json[MEOW_IPC_MAX - 8]; +} m_net_iface_result_t; + +/* MSG_NET_WIFI_CTL / MSG_NET_BT_CTL ↑:子命令字串 + 变长 arg(connect 的 ssid/psk)。→ *_RESULT ↓。 + * cmd(WIFI_CTL): status|scan|networks|connect|disconnect|on|off + rfkill/udhcpc 命名空间 + * (rfkill arg="list"|" ";udhcpc arg="renew "|"release ")。 + * cmd(BT_CTL) : status|on|off|paired。 */ +typedef struct m_net_cmd { + char cmd[24]; /* 子命令字串(大写不敏感;接收侧截断) */ + char arg[132]; /* connect: "[,]";rfkill: " ";udhcpc: "renew|release " */ +} m_net_cmd_t; /* 156 B */ + +typedef struct m_net_cmd_result { + uint32_t code; /* MEOW_OK / MEOW_ERR_PERM / MEOW_ERR_INVAL / MEOW_ERR_NODEV */ + uint16_t text_len; /* text 有效字节(不含 NUL) */ + uint16_t pad; + char text[MEOW_IPC_MAX - 8]; +} m_net_cmd_result_t; + +/* ---- 期 2:观测层(连接普查)+ DNS 代理(NETMGR_DESIGN §4/§5)---- + * 0x00E8/0x00EC 实现取代上方「0x00E8-0x00EF 保留」注释中的连接表/DNS 控制两项; + * 0x00EA(策略读写,期3)/0x00EE(事件推送,期2 订阅制预留)仍保留未实装。 */ +enum { /* 与主 enum 分列,追加式不改既有行 */ + MSG_NET_CONNS_REQ = 0x00E8, /* ↑ 连接表查询(entity 空=全部) */ + MSG_NET_CONNS_RESULT = 0x00E9, /* ↓ 连接表文本(定长行;超帧截断标记) */ + MSG_NET_DNS_CTL = 0x00EC, /* ↑ dns status|stats|flush */ + MSG_NET_DNS_RESULT = 0x00ED /* ↓ 结果/统计文本 */ + /* 0x00EA 策略读写(期3)/ 0x00EE 事件推送(预留,期2 订阅制)/ 0xEB、0xEF 余量 */ +}; + +/* MSG_NET_CONNS_REQ ↑:entity 前缀过滤(空=全部)。entity 形如 `app:`/`svc:`/`proc:`。 */ +typedef struct m_net_conns_req { + char entity[48]; +} m_net_conns_req_t; /* 48 B */ + +/* MSG_NET_CONNS_RESULT ↓:连接表文本(每行 `entity|lip:lport|rip:rport|state|age_s|domain`)。 + * 超单帧上限时截断并置 truncated=1(app/top 由客户端按行聚合,容纳全部)。 */ +typedef struct m_net_conns_result { + uint32_t code; /* MEOW_OK / MEOW_ERR_* */ + uint16_t text_len; + uint16_t truncated; /* 1=被截断 */ + char text[MEOW_IPC_MAX - 8]; +} m_net_conns_result_t; + +/* MSG_NET_DNS_CTL ↑:cmd=status|stats|flush。status/stats 读级任意;flush(清缓存)为动作级系统身份。 */ +typedef struct m_net_dns_ctl { + char cmd[16]; +} m_net_dns_ctl_t; /* 16 B */ + +typedef struct m_net_dns_result { + uint32_t code; + uint16_t text_len; + uint16_t pad; + char text[MEOW_IPC_MAX - 8]; +} m_net_dns_result_t; + +#endif /* MEOW_IPC_H */ diff --git a/sdk/include/meow_notif.h b/sdk/include/meow_notif.h new file mode 100644 index 0000000..62de2d4 --- /dev/null +++ b/sdk/include/meow_notif.h @@ -0,0 +1,168 @@ +/* meow_notif.h — MeowOS 通知存储 + 分发(阶段 D · M2a) + * + * 定位:MeowOS 通知中心后端(存储 + 分发),是阶段 B 通知中心 UI(M5)+ 阶段 D + * (M2b D-Bus 桥 / M2c 转储 TF 卡)的前提。设计规范:NOTIFICATION_SYSTEM.md §1-§8 + * + FRAMEWORK_DESIGN §4/附C(freedesktop.org Desktop Notifications)。 + * + * 本批(M2a)只做核心存储骨架: + * - 通知数据结构(对齐 freedesktop 字段 + MeowOS 扩展 target_activity/progress); + * - 内存配额(每 App ≤ notify.categories 个类型;每类型活跃 ≤ notify.category_quota; + * 媒体通知每 App ≤ notify.media_quota,允许多媒体 App 同时); + * - TTL 10s 转储钩子(设计 §8,实际 TF 落盘 M2c 后置,本批预留 notif_dump_cb); + * - 原生 IPC 通道接入点(MSG_NOTIFY→ACK / MSG_NOTIFY_DISMISS→ACK;MSG_NOTIFY_LIST + * 为守护→App 下发,本批提供序列化 helper,UI(M5) 接入)。 + * + * 空洞实现风格:本文件自包含(不依赖 launcher 主模块的 logmsg/now_ms —— 二者现归 util.c,可经 + * meow_launcher_internal.h 引用),配额从 meow_settings 读(B2a;键缺省时用本文件默认值)。 + * 注释风格中文 +「M2.a 说明」。 + */ +#ifndef MEOW_NOTIF_H +#define MEOW_NOTIF_H + +#include /* uint32_t/uint64_t/int32_t */ +#include /* size_t */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* == 返回码(对齐 meow_ipc.h / MEOWOS_API.md §1,通知专用枚举)== */ +enum { + NOTIF_OK = 0, + NOTIF_ERR_INVAL = -1, /* 参数非法 */ + NOTIF_ERR_NOMEM = -6, /* 槽满/内存不足 */ + NOTIF_ERR_NOTINIT = -7, /* 未 init */ + NOTIF_ERR_QUOTA = -12, /* 配额超限(App 类型数 / 类型条数 / 媒体数) */ + NOTIF_ERR_NOENT = -2, /* 通知 id 不存在 */ + NOTIF_ERR_IO = -5 /* 文件/存储 IO 失败(TF 转储,无卡/不可写) */ +}; + +/* == 容量/长度(单设备上限;定长,控制静态内存)== */ +#define MEOW_NOTIF_APP_ID_MAX 32 +#define MEOW_NOTIF_CAT_MAX 24 +#define MEOW_NOTIF_SUMMARY_MAX 64 +#define MEOW_NOTIF_BODY_MAX 96 +#define MEOW_NOTIF_ICON_MAX 48 +#define MEOW_NOTIF_ACTIVITY_MAX 48 +#define MEOW_NOTIF_ACTIONS_MAX 96 /* NUL 分隔动作键列表 */ +#define MEOW_NOTIF_HINTS_MAX 96 /* 原始 "k=v;..." 提示串 */ + +/* == 配额默认(运行时从 meow_settings notify.* 覆盖;键缺省用此)== */ +#define MEOW_NOTIF_CATS_DEF 4 /* 每 App 最多申请的通知类型数(notify.categories) */ +#define MEOW_NOTIF_CAT_QUOTA_DEF 4 /* 每类型活跃(未过期)通知上限(notify.category_quota) */ +#define MEOW_NOTIF_MEDIA_QUOTA_DEF 1 /* 每 App 媒体通知上限(notify.media_quota) */ +#define MEOW_NOTIF_TTL_MS 10000 /* 设计 §8:通知创建 10s 后自动转储到 TF */ + +/* 媒体通知(category)约定名:命中即按 notify.media_quota 约束(单 App 1 条) */ +#define MEOW_NOTIF_MEDIA_CAT "media" + +/* 系统事件作为 App 的 app_id(NOTIFICATION_SYSTEM.md §7:系统也抽象为 App,同样走配额) */ +#define MEOW_NOTIF_SYSTEM_APP "system" + +/* 通知结构(对齐 freedesktop + MeowOS 扩展)。 + * fields 注释:NOTIFICATION_SYSTEM.md §4。 */ +typedef struct meow_notif { + uint32_t id; /* 全局递增通知 id(0=无效/未分配) */ + char app_id[MEOW_NOTIF_APP_ID_MAX]; /* 来源应用;系统事件="system" */ + char category[MEOW_NOTIF_CAT_MAX]; /* 通知类型(媒体通知="media") */ + char summary[MEOW_NOTIF_SUMMARY_MAX]; /* 标题(单行) */ + char body[MEOW_NOTIF_BODY_MAX]; /* 正文(可多行) */ + char app_icon[MEOW_NOTIF_ICON_MAX]; /* 图标 icon name 或 path(SDK icon;空→app 图标回退) */ + char actions[MEOW_NOTIF_ACTIONS_MAX]; /* actions[](NUL 分隔;v1 空,M2b 填) */ + char hints[MEOW_NOTIF_HINTS_MAX]; /* hints{} 原始 "k=v;..." 串(v1 空,M2b 填) */ + uint32_t expire_timeout; /* 超时 ms;0xFFFFFFFF=ONGOING 常驻 */ + uint64_t created_ms; /* 创建时刻(墙钟 epoch ms,供归档/显示) */ + uint64_t dump_at_ms; /* 转储时刻(monotonic ms,TTL=创建+10s;ONGOING 亦到此转储) */ + int32_t progress; /* MeowOS:0-100 进度;<0=无(实时仅改内存,落盘只在创建/结束) */ + char target_activity[MEOW_NOTIF_ACTIVITY_MAX]; /* MeowOS:点击跳转目标 activity */ + uint8_t urgency; /* 紧急度 0-3(低/普通/高/紧急);缺省 NORMAL */ + uint8_t ongoing; /* 1=常驻通知(服务死随清) */ + uint8_t media; /* 1=媒体通知(走 notify.media_quota) */ + uint8_t dismissed; /* 1=已撤下 */ + uint8_t dumped; /* 1=已转储(归档) */ + uint8_t active; /* 1=槽位占用 */ +} meow_notif_t; + +/* 转储回调(M2c 落盘 / 本批默认仅日志):通知归档时调用。 */ +typedef void (*meow_notif_dump_cb)(const meow_notif_t *n); + +/* 图标回退回调:post 的 n->app_icon 为空时调用,按 app_id 填该 app 默认图标(name/path)。 + * 返回 1=已填 out;0/空=无(通知中心 UI 再回退)。NULL=禁用回退(保持空 app_icon)。 */ +typedef int (*meow_notif_icon_cb)(const char *app_id, char *out, size_t outsz); + +/* == 生命周期 == */ +int meow_notif_init(void); /* 幂等;从 meow_settings 读 notify.* 配额(B2a) */ +int meow_notif_shutdown(void); /* 保存/清理(进程退出前调;本批=归档未转储项) */ + +/* == 核心 API == */ +/* 推送/更新通知。n->id 为 0(新建);若 n->id>0 或 replace 语义, + * 按 n 中同 id 更新已存在通知(未找到则新建)。返回 NOTIF_OK/NOTIF_ERR_*; + * *out_id 返回分配/更新的通知 id(可为 NULL)。 */ +int meow_notif_post(const meow_notif_t *n, uint32_t *out_id); + +/* 撤下通知(dismiss)。随之为该通知归档(未转储则立即调 dump_cb + 标记),并释放槽。 */ +int meow_notif_dismiss(uint32_t id); + +/* 活跃(未转储)通知数。app_id 为 NULL/空=全部。 */ +int meow_notif_count(const char *app_id); + +/* 遍历(回调)当前通知。app_id 为 NULL/空=全部。返回遍历到的条数; + * 回调返回非 0 则中止遍历。 */ +int meow_notif_list(const char *app_id, + int (*cb)(const meow_notif_t *n, void *ctx), void *ctx); + +/* 立即转储所有未上卡(未 dumped)通知:逐条调 dump_cb + 标 dumped(不释放; + * 停机前转储用)。返回转储条数。 */ +int meow_notif_dump_now(void); + +/* 主循环周期调(每 10ms 排空 TODO):TTL 到期 → 归档 + (非 ONGOING)释放槽; + * dismissed 且未转储 → 归档 + 释放;ONGOING 转储后仍保留内存(服务死随清)。 */ +void meow_notif_tick(void); + +/* 正常电源事件(关机/重启)前调用:先转储所有未上卡通知,再清板载。 */ +void meow_notif_before_poweroff(void); + +/* 覆盖转储回调(NULL=恢复默认日志;M2c 接 TF 落盘)。 */ +void meow_notif_set_dump_cb(meow_notif_dump_cb cb); + +/* 覆盖图标回退回调(NULL=禁用,空 app_icon 保持空;M5 通知中心 UI 再回退)。 */ +void meow_notif_set_icon_cb(meow_notif_icon_cb cb); + +/* 实时更新一条活跃通知的进度(0-100,<0=清除)。仅改内存活跃槽;progress>=100 视为「结束」, + * 立即归档(落盘快照)并(非 ONGOING)释放槽——对应「里程碑落盘」。返回 NOTIF_OK/NOTIF_ERR_NOENT。 */ +int meow_notif_update_progress(uint32_t id, int progress); + +/* 一键清除(MSG_NOTIFY_DISMISS_ALL / 通知中心 UI 同语义):dismiss 所有「可清除」活跃通知 + * (非 ongoing / 非 media / 无进行中进度 progress<0——红线段一律保留)。每条 dismiss = 归档留档 + * + 释放槽。返回清除条数;<0=错误(NOTIF_ERR_*)。 */ +int meow_notif_clear_active(void); + +/* 把当前活跃通知集序列化为 JSON 数组文本写入 buf(≤buf_len,NUL 结尾)。app_id 空/NULL=全部, + * 非空=仅该 app 的活跃通知。字段同 TF 归档:id/app_id/category/summary/body/urgency/ts/ + * progress/ongoing/media/target_activity(+app_icon);ts=created_ms(epoch ms)。 + * 返回文本字节数(不含 NUL);<0=错误(NOTIF_ERR_*)。 */ +int meow_notif_serialize_json(const char *app_id, char *buf, int buf_len); + +/* == TF 卡转储(通知#6 / M2c,NOTIFICATION_SYSTEM.md §5/§8 + MEOWOS_SPEC_REF §5.3)== + * 把 dump_cb 接到 meow_notif_dump_tf 即把每条通知追加为一行 CSV 到 + * <卡根>/MeowOS/notifications/notifications.log,并按日志总大小滚动截断 + * (保留最新 10MB,不被电源事件清;启动时 meow_notif_init 先清板载+内存表)。 + * 字段按 meow_notif 结构(title<-summary;app_icon<-SDK icon 空→app 图标回退;urgency 0-3; + * progress 当前值,-1=无;均做 CSV 转义/去换行)。无卡/不可写仅打点降级,绝不崩。 */ +int meow_notif_dump_tf(const meow_notif_t *n); /* 0=成功;负=失败(无卡/IO/参数) */ +void meow_notif_clear_tf(void); /* 开机清板载通知(清空文件,保留目录) */ +void meow_notif_set_tf_path(const char *path); /* 覆盖转储日志路径;NULL=恢复默认卡路径(宿主单测用) */ +void meow_notif_set_tf_max(size_t max_bytes); /* 覆盖滚动截断上限;0=恢复默认 10MB(宿主单测用) */ + +/* 调试:打印当前 Active 表/App 表(前缀)。 */ +void meow_notif_dump(const char *log_prefix); + +/* MSG_NOTIFY_LIST(守护→App 下发清单,M5 通知中心/重同步用): + * 把 app_id 的当前通知序列化为 MSG_NOTIFY_LIST 负载写入 buf(≤MEOW_IPC_CARGO), + * 返回负载字节数;<0=无(app 无通知)。 */ +int meow_notif_serialize_list(const char *app_id, uint8_t *buf, int buf_len); + +#ifdef __cplusplus +} +#endif +#endif /* MEOW_NOTIF_H */ diff --git a/sdk/meow_font.c b/sdk/meow_font.c new file mode 100644 index 0000000..410ca41 --- /dev/null +++ b/sdk/meow_font.c @@ -0,0 +1,964 @@ +/* + * meow_font.c — MeowOS 预渲染位图字体运行时(独立库,不绑定启动器)。 + * + * 依赖: + * - libz(musl 静态)仅用于 inflate PNG 像素面。目标系统无 libpng,故模块内自带 + * 最小 PNG 解码(灰度 non-interlaced,bit depth 4|8,filter 0-4)。 + * - zlib 头文件不在 musl sysroot,故在文件底部声明 ABI 兼容的最小 API(z_stream 布局 + * 与 libz.a 一致,已用 qemu-mipsel 验证)。 + * + * 文件格式参见 gen/gen_font.py 注释(version=2,magic 'MFNI')。 + * + * 绘制模型:drawtext(fb,x,y,utf8,color,size),(x,y)=行首升线顶部;baseline=y+ascent; + * 每字形 blit 于 (x+pen+dx, y+dy);pen 累加 advance。color 为 RGB565(R=bit15..11)。 + * + * 多字体(M2.5x):槽位模型。slot 0 = meow_font_load() 装载的全局/默认字库(font.file); + * 额外字库经 meow_font_load_slot() 装载。meow_font_enable_multifont(1) 开启后, + * drawtext/width_text/line_height/... 逐字符 meow_font_class_of() 分类 → 用该类别 + * 对应槽的字库取字形与度量;缺该类别/槽未载 → 回退 slot 0。关闭(默认)一律 slot 0。 + */ +#include "meow_font.h" + +#include +#include +#include +#include /* access():字库缺失判定用 */ + +/* ---------------- 最小 zlib ABI 声明(与 libz.a 匹配,mipsel 32-bit unsigned long=4B) ------- */ +typedef unsigned char mzf_Bytef; +typedef unsigned int mzf_uInt; +typedef unsigned long mzf_uLong; +typedef void *mzf_voidpf; +typedef unsigned int (*mzf_alloc_func)(unsigned int, unsigned int); +typedef void (*mzf_free_func)(unsigned int, unsigned int); + +typedef struct mzf_stream_s { + mzf_Bytef *next_in; + mzf_uInt avail_in; + mzf_uLong total_in; + mzf_Bytef *next_out; + mzf_uInt avail_out; + mzf_uLong total_out; + char *msg; + void *state; + mzf_alloc_func zalloc; + mzf_free_func zfree; + mzf_voidpf opaque; + int data_type; + mzf_uLong adler; + mzf_uLong reserved; +} mzf_stream; + +#define MZF_OK 0 +#define MZF_STREAM_END 1 +#define MZF_NO_FLUSH 0 +#define MZF_FINISH 4 + +/* 函数符号名必须与 libz.a 一致(inflate 等);结构名仅 C 侧,ABI 由布局决定。 */ +extern int inflateInit_(mzf_stream *, const char *, int); +extern int inflate(mzf_stream *, int); +extern int inflateEnd(mzf_stream *); +extern const char *zlibVersion(void); + +/* ---------------- 内部数据结构 ---------------- */ +typedef struct { + uint32_t cp; + uint16_t x, y, w, h, advance; + int8_t dx; + uint8_t dy; +} meow_glyph; + +typedef struct { + int px; /* 原生字号 */ + unsigned plane_x, plane_y; /* 在大画布(plane_w x plane_h)内的面左上角 */ + unsigned plane_w, plane_h; + int ascent, descent; /* descent>=0 */ + unsigned glyph_count; + meow_glyph *glyphs; /* 按 cp 升序 */ +} meow_face; + +/* 一个槽 = 一套已装载的字库(一个 PNG plane + 若干 face)。 */ +typedef struct { + size_t plane_rowbytes; + unsigned depth; /* 4 或 8 */ + unsigned plane_w, plane_h; /* 画布尺寸 */ + unsigned char *plane; /* 打包像素面 */ + int face_count; + meow_face *faces; + int loaded; /* 是否已装载 */ +} meow_font_slot; + +/* framebuffer 几何(全局,作用于所有槽;绘制前 meow_font_set_fb 设置一次)。 */ +static unsigned g_fb_w, g_fb_h, g_fb_pitch; + +static meow_font_slot g_slots[MEOW_MAX_SLOTS]; +static int g_multifont = 0; /* 0=单字体(默认) */ +static int g_class_slot[MEOW_FCLS_COUNT]; /* 每字符类 → 槽;默认全 0(跟随全局) */ + +/* PNG 内部为大端;idx(自定格式)为小端。 */ +static uint32_t be32(const unsigned char *p) { + return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | + ((uint32_t)p[2] << 8) | (uint32_t)p[3]; +} +static uint32_t le32(const unsigned char *p) { + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +/* ---------------- 内存读文件 ---------------- */ +static unsigned char *read_file(const char *path, size_t *out_len) { + FILE *f = fopen(path, "rb"); + if (!f) return NULL; + fseek(f, 0, SEEK_END); + long n = ftell(f); + fseek(f, 0, SEEK_SET); + if (n < 0) { fclose(f); return NULL; } + unsigned char *buf = (unsigned char *)malloc((size_t)n ? (size_t)n : 1); + if (!buf) { fclose(f); return NULL; } + size_t rd = fread(buf, 1, (size_t)n, f); + fclose(f); + if (rd != (size_t)n) { free(buf); return NULL; } + *out_len = (size_t)n; + return buf; +} + +/* ---------------- 最小 PNG 解码(灰度 non-interlaced) ---------------- + * 流式/分块解码,消除「整幅原始扫描行 raw + 整段 idat 拷贝」双份驻留: + * - idat 不再另外 malloc 拷贝,直接以 png 文件缓冲内 IDAT 区间作 zlib 输入; + * - 逐行 inflate 到单行小缓冲 rowbuf(rowbytes+1 字节),随即反滤波写入打包面 out; + * 不再分配整幅 raw(= 扫描行数×(rowbytes+1))——对大字库这是 6-16MB 级的一次性峰值。 + * 大字库 gbk 解码面本身很大(行数×rowbytes),流式后 load 峰值 ≈ 文件缓冲 + idx + 打包面, + * 而非 文件缓冲 + idat + raw + 打包面。*/ +static unsigned char *png_decode_gray(const unsigned char *png, size_t len, + unsigned *out_w, unsigned *out_h, + unsigned *out_depth, size_t *out_rowbytes) { + if (len < 8 || memcmp(png, "\x89PNG\r\n\x1a\n", 8) != 0) + return NULL; + size_t i = 8; + unsigned w = 0, h = 0, depth = 0, color = 0, interlace = 1, compression = 1, filt = 1; + int have_ihdr = 0; + size_t idat_off = 0, idat_len = 0; /* IDAT 在 png 内的连续区间(偏移/长度) */ + int have_idat = 0; + while (i + 8 <= len) { + uint32_t clen = be32(png + i); + const unsigned char *type = png + i + 4; + if (i + 8 + (size_t)clen > len) break; + if (memcmp(type, "IHDR", 4) == 0) { + if (clen < 13) return NULL; + w = be32(png + i + 8); + h = be32(png + i + 12); + depth = png[i + 16]; + color = png[i + 17]; + compression = png[i + 18]; + filt = png[i + 19]; + interlace = png[i + 20]; + have_ihdr = 1; + } else if (memcmp(type, "IDAT", 4) == 0) { + if (!have_idat) { idat_off = i + 8; idat_len = clen; have_idat = 1; } + else { + /* 要求 IDAT 连续(合法 PNG/单段压缩满足;gen_font.py 亦单段) */ + if (idat_off + idat_len != i + 8) return NULL; + idat_len += clen; + } + } else if (memcmp(type, "IEND", 4) == 0) { + break; + } + i += 12 + (size_t)clen; + } + if (!have_ihdr || !have_idat || + color != 0 || compression != 0 || filt != 0 || interlace != 0 || + (depth != 4 && depth != 8) || w == 0 || h == 0) + return NULL; + size_t rowbytes = ((size_t)w * depth + 7) / 8; + size_t scanlen = rowbytes + 1; /* 每扫描行 = 1 filter 字节 + rowbytes 像素 */ + size_t rawlen = scanlen * h; /* 期望的解压总量(校验用,不据此分配) */ + + unsigned char *out = (unsigned char *)malloc(rowbytes * h); + if (!out) return NULL; + unsigned char *rowbuf = (unsigned char *)malloc(scanlen); + if (!rowbuf) { free(out); return NULL; } + + mzf_stream s; + memset(&s, 0, sizeof(s)); + s.next_in = (mzf_Bytef *)(png + idat_off); + s.avail_in = (mzf_uInt)idat_len; + s.zalloc = 0; s.zfree = 0; s.opaque = 0; + if (inflateInit_(&s, zlibVersion(), (int)sizeof(s)) != MZF_OK) { + free(out); free(rowbuf); return NULL; + } + + const unsigned bpp = 1; + size_t produced = 0; + int ok = 1, stream_end = 0; + for (unsigned y = 0; y < h; y++) { + /* 逐行填满 rowbuf(zlib 可能分批产出,内层循环补足到 scanlen) */ + size_t filled = 0; + while (filled < scanlen) { + s.next_out = rowbuf + filled; + s.avail_out = (mzf_uInt)(scanlen - filled); + int ret = inflate(&s, MZF_NO_FLUSH); + size_t got = (scanlen - filled) - s.avail_out; + filled += got; + if (ret == MZF_STREAM_END) { stream_end = 1; break; } + if (ret != MZF_OK || got == 0) { ok = 0; break; } + } + if (!ok || filled != scanlen) { ok = 0; break; } + + /* 反滤波(bpp=1:灰度 depth 4|8)→ out 为每行 rowbytes 的打包像素 */ + unsigned char ft = rowbuf[0]; + const unsigned char *c = rowbuf + 1; + unsigned char *dst = out + (size_t)y * rowbytes; + unsigned char *prev = (y == 0) ? NULL : out + (size_t)(y - 1) * rowbytes; + for (size_t x = 0; x < rowbytes; x++) { + unsigned char left = (x >= bpp) ? dst[x - bpp] : 0; + unsigned char up = prev ? prev[x] : 0; + unsigned char ul = (prev && x >= bpp) ? prev[x - bpp] : 0; + unsigned char v = c[x]; + switch (ft) { + case 0: dst[x] = v; break; + case 1: dst[x] = v + left; break; + case 2: dst[x] = v + up; break; + case 3: dst[x] = v + (((unsigned)left + (unsigned)up) >> 1); break; + case 4: { + int p = (int)left + (int)up - (int)ul; + int pa = p - (int)left; if (pa < 0) pa = -pa; + int pb = p - (int)up; if (pb < 0) pb = -pb; + int pc = p - (int)ul; if (pc < 0) pc = -pc; + unsigned char pred = (pa <= pb && pa <= pc) ? left : (pb <= pc ? up : ul); + dst[x] = v + pred; + break; + } + default: ok = 0; break; + } + } + if (!ok) break; + produced += scanlen; + if (stream_end) break; + } + inflateEnd(&s); + free(rowbuf); + if (!ok || produced != rawlen) { free(out); return NULL; } + + *out_w = w; *out_h = h; *out_depth = depth; *out_rowbytes = rowbytes; + return out; +} + +/* ---------------- idx 解析(写入指定槽) ---------------- */ +static int load_idx(const unsigned char *buf, size_t len, int slot) { + meow_font_slot *st = &g_slots[slot]; + if (len < 16 || memcmp(buf, "MFNI", 4) != 0) return -1; + unsigned depth = buf[6]; + int nfaces = buf[7]; + unsigned png_w = le32(buf + 8), png_h = le32(buf + 12); + (void)png_w; (void)png_h; + if (nfaces < 1 || nfaces > 32) return -1; + if (depth != st->depth) return -2; /* 位深须与 PNG 一致 */ + + size_t face_off = 16; + meow_face *faces = (meow_face *)calloc((size_t)nfaces, sizeof(meow_face)); + if (!faces) return -3; + + for (int i = 0; i < nfaces; i++) { + const unsigned char *fh = buf + face_off + (size_t)i * 20; + meow_face *f = &faces[i]; + f->px = fh[0] | (fh[1] << 8); + f->plane_x = fh[2] | (fh[3] << 8); + f->plane_y = fh[4] | (fh[5] << 8); + f->plane_w = fh[6] | (fh[7] << 8); + f->plane_h = fh[8] | (fh[9] << 8); + int16_t asc = (int16_t)(fh[10] | (fh[11] << 8)); + int16_t dsc = (int16_t)(fh[12] | (fh[13] << 8)); + f->ascent = asc; + f->descent = dsc; + f->glyph_count = fh[14] | (fh[15] << 8); + uint32_t goff = le32(fh + 16); + if (f->glyph_count > 0 && goff + (size_t)f->glyph_count * 16 <= len) { + f->glyphs = (meow_glyph *)malloc((size_t)f->glyph_count * sizeof(meow_glyph)); + if (!f->glyphs) { for (int j = 0; j < i; j++) free(faces[j].glyphs); free(faces); return -4; } + for (unsigned k = 0; k < f->glyph_count; k++) { + const unsigned char *gchr = buf + goff + (size_t)k * 16; + meow_glyph *gr = &f->glyphs[k]; + gr->cp = le32(gchr); + gr->x = gchr[4] | (gchr[5] << 8); + gr->y = gchr[6] | (gchr[7] << 8); + gr->w = gchr[8] | (gchr[9] << 8); + gr->h = gchr[10] | (gchr[11] << 8); + gr->advance = gchr[12] | (gchr[13] << 8); + gr->dx = (int8_t)gchr[14]; + gr->dy = gchr[15]; + } + } else if (f->glyph_count == 0) { + f->glyphs = NULL; + } else { + for (int j = 0; j < i; j++) free(faces[j].glyphs); + free(faces); + return -5; + } + } + st->faces = faces; + st->face_count = nfaces; + return 0; +} + +/* ---------------- 面/字形查找(槽感知) ---------------- */ +static meow_face *face_for_size(int slot, int size) { + meow_font_slot *st = &g_slots[slot]; + if (st->face_count <= 0) return NULL; + meow_face *best = NULL; + int best_delta = -1; + for (int i = 0; i < st->face_count; i++) { + int d = st->faces[i].px - size; + if (d < 0) d = -d; + if (best_delta < 0 || d < best_delta) { best_delta = d; best = &st->faces[i]; } + } + return best; +} + +static meow_glyph *find_glyph(meow_face *f, uint32_t cp) { + unsigned lo = 0, hi = f->glyph_count; + while (lo < hi) { + unsigned mid = (lo + hi) >> 1; + uint32_t c = f->glyphs[mid].cp; + if (c == cp) return &f->glyphs[mid]; + if (c < cp) lo = mid + 1; else hi = mid; + } + return NULL; +} + +/* ---------------- 像素采样(depth 感知,槽感知) ---------------- */ +static inline unsigned sample_px(int slot, unsigned x, unsigned y) { + meow_font_slot *st = &g_slots[slot]; + if (x >= st->plane_w || y >= st->plane_h) return 0; + if (st->depth == 8) return st->plane[(size_t)y * st->plane_w + x]; + unsigned char b = st->plane[((size_t)y * st->plane_w + x) >> 1]; + unsigned nh = (x & 1) ? (b & 0x0F) : (b >> 4); + return nh * 17; +} + +/* ---------------- RGB565 alpha 混合 ---------------- */ +static inline uint16_t blend565(uint16_t bg, uint16_t fg, unsigned a) { + unsigned inv = 255 - a; + unsigned br = (bg >> 11) & 31, bgc = (bg >> 5) & 63, bb = bg & 31; + unsigned fr = (fg >> 11) & 31, fgc = (fg >> 5) & 63, fb = fg & 31; + unsigned r = ((fr * a + br * inv) + 127) / 255; + unsigned gc = ((fgc * a + bgc * inv) + 127) / 255; + unsigned b = ((fb * a + bb * inv) + 127) / 255; + return (uint16_t)((r << 11) | (gc << 5) | b); +} + +/* ---------------- 字形 blit(含缩放、裁剪、alpha 混合;槽感知) ---------------- */ +static void blit_glyph(uint16_t *fb, int slot, meow_face *face, const meow_glyph *gr, + int pen_x, int y, uint16_t color, unsigned scaleq) { + int stride = (int)(g_fb_pitch / 2); /* uint16 元素每行数 */ + /* 目标尺寸 = 源尺寸 * scaleq / 4096 */ + unsigned gw, gh; + int dx, dy; + if (scaleq == 4096) { + gw = gr->w; gh = gr->h; + dx = gr->dx; dy = gr->dy; + } else { + gw = (unsigned)(((uint64_t)gr->w * scaleq) >> 12); + gh = (unsigned)(((uint64_t)gr->h * scaleq) >> 12); + dx = (int)(((int64_t)gr->dx * scaleq) >> 12); + dy = (int)(((int64_t)gr->dy * scaleq) >> 12); + } + if (gw == 0 || gh == 0) return; + int px = pen_x + dx; + int py = y + dy; + unsigned sw = gr->w, sh = gr->h; + for (unsigned oy = 0; oy < gh; oy++) { + int ay = py + (int)oy; + if (ay < 0) continue; + if (ay >= (int)g_fb_h) break; + int sy = (int)((uint64_t)oy * sh / gh); + int base = face->plane_x + gr->x; + for (unsigned ox = 0; ox < gw; ox++) { + int ax = px + (int)ox; + if (ax < 0) continue; + if (ax >= (int)g_fb_w) break; + int sx = (int)((uint64_t)ox * sw / gw); + unsigned a = sample_px(slot, (unsigned)(base + sx), + (unsigned)(face->plane_y + gr->y + sy)); + if (!a) continue; + size_t idx = (size_t)ay * (size_t)stride + (size_t)ax; + fb[idx] = blend565(fb[idx], color, a); + } + } +} + +/* ---------------- UTF-8 解码 ---------------- */ +static int utf8_next(const unsigned char **pp) { + const unsigned char *s = *pp; + unsigned char c = s[0]; + if (c < 0x80) { *pp += 1; return c; } + int n; + uint32_t cp; + if ((c & 0xE0) == 0xC0) { n = 2; cp = c & 0x1F; } + else if ((c & 0xF0) == 0xE0) { n = 3; cp = c & 0x0F; } + else if ((c & 0xF8) == 0xF0) { n = 4; cp = c & 0x07; } + else { *pp += 1; return -1; } + for (int i = 1; i < n; i++) { + if ((s[i] & 0xC0) != 0x80) { *pp += 1; return -1; } + cp = (cp << 6) | (s[i] & 0x3F); + } + *pp += n; + return (int)cp; +} + +static uint32_t advance_for_missing(meow_face *f) { + /* 缺字:用空格或 人 位 advance;找不到就用 fontSize/2 */ + meow_glyph *sp = find_glyph(f, 0x20); + return sp ? sp->advance : (uint32_t)(f->px / 2); +} + +static void draw_tofu(uint16_t *fb, int x, int y, int w, int h, uint16_t color) { + int stride = (int)(g_fb_pitch / 2); + for (int oy = 0; oy < h; oy++) { + int ay = y + oy; + if (ay < 0 || ay >= (int)g_fb_h) break; + for (int ox = 0; ox < w; ox++) { + int ax = x + ox; + if (ax < 0 || ax >= (int)g_fb_w) break; + if (oy == 0 || oy == h - 1 || ox == 0 || ox == w - 1) { + size_t idx = (size_t)ay * (size_t)stride + (size_t)ax; + fb[idx] = blend565(fb[idx], color, 255); + } + } + } +} + +/* ---------------- 字符分类器(multi_font 路由依据) ---------------- */ +int meow_font_class_of(uint32_t cp) { + /* 数字 */ + if (cp >= '0' && cp <= '9') return MEOW_FCLS_NUM; + /* 拉丁字母(基本拉丁) */ + if ((cp >= 'A' && cp <= 'Z') || (cp >= 'a' && cp <= 'z')) return MEOW_FCLS_LATIN; + /* 半角标点/空格/控制符(ASCII 非字母数字) */ + if (cp < 0x80) return MEOW_FCLS_LATIN_SYM; + /* 全角 / 中文标点 */ + if ((cp >= 0x3000 && cp <= 0x303F) || /* CJK 符号与标点(含全角空格 U+3000) */ + (cp >= 0xFF00 && cp <= 0xFFEF) || /* 半/全角形式(全角数字字母符号) */ + cp == 0x2014 || cp == 0x2018 || cp == 0x2019 || /* — ‘’ */ + cp == 0x201C || cp == 0x201D || cp == 0x2026 || /* “” … */ + cp == 0x00B7) /* · */ + return MEOW_FCLS_CJK_SYM; + /* CJK 表意文字 + 扩展 + 兼容表意 */ + if ((cp >= 0x4E00 && cp <= 0x9FFF) || + (cp >= 0x3400 && cp <= 0x4DBF) || + (cp >= 0x20000 && cp <= 0x2A6DF) || + (cp >= 0x2A700 && cp <= 0x2B73F) || + (cp >= 0x2B740 && cp <= 0x2B81F) || + (cp >= 0x2B820 && cp <= 0x2CEAF) || + (cp >= 0x2CEB0 && cp <= 0x2EBEF) || + (cp >= 0x30000 && cp <= 0x3134F) || + (cp >= 0xF900 && cp <= 0xFAFF) || + (cp >= 0x2F800 && cp <= 0x2FA1F)) + return MEOW_FCLS_CJK; + return MEOW_FCLS_OTHER; +} + +/* ---------------- 路由:一个 codepoint 应使用的槽(multi off → slot0) ---------------- */ +static int route_slot(uint32_t cp) { + if (!g_multifont) return 0; + int cls = meow_font_class_of(cp); + if (cls < 0 || cls >= MEOW_FCLS_COUNT) return 0; + int s = g_class_slot[cls]; + if (s >= 0 && s < MEOW_MAX_SLOTS && g_slots[s].loaded) return s; + return 0; /* 缺该类别字库/未装载 → 回退全局 slot0 */ +} + +/* 取某码位在路由落点槽内、对应最近字号的面 + scaleq;无可用字库返回 NULL。 + * 顺带带回落点槽(供 blit 采样;不需要者传 NULL)。 */ +static meow_face *routed_face(uint32_t cp, int size, unsigned *scaleq, int *slot_out) { + int slot = route_slot(cp); + if (slot_out) *slot_out = slot; + meow_font_slot *st = &g_slots[slot]; + if (!st->loaded) return NULL; + meow_face *f = face_for_size(slot, size); + if (!f) return NULL; + if (scaleq) *scaleq = (size == f->px) ? 4096u : (unsigned)(((uint64_t)size << 12) / (uint64_t)f->px); + return f; +} + +/* ---------------- 公共 API ---------------- */ +void meow_font_set_fb(unsigned fb_w, unsigned fb_h, unsigned pitch_bytes) { + g_fb_w = fb_w; g_fb_h = fb_h; g_fb_pitch = pitch_bytes; +} + +void meow_font_unload_slot(int slot) { + if (slot < 0 || slot >= MEOW_MAX_SLOTS) return; + meow_font_slot *st = &g_slots[slot]; + if (st->faces) { + for (int i = 0; i < st->face_count; i++) free(st->faces[i].glyphs); + free(st->faces); + } + free(st->plane); + st->faces = NULL; st->plane = NULL; st->face_count = 0; + st->plane_w = st->plane_h = 0; st->plane_rowbytes = 0; st->depth = 0; + st->loaded = 0; +} + +void meow_font_unload(void) { + for (int i = 0; i < MEOW_MAX_SLOTS; i++) meow_font_unload_slot(i); + g_multifont = 0; + for (int i = 0; i < MEOW_FCLS_COUNT; i++) g_class_slot[i] = 0; +} + +#ifdef MEOW_FONT_ENABLE_DEBUG +/* 宿主调试/校验钩子:取某槽已装载(打包)像素面 + 几何。仅宿主单测用, + * mipsel 构建不含 MEOW_FONT_ENABLE_DEBUG,无此符号、零开销。 */ +const unsigned char *meow_font_debug_plane(int slot, unsigned *w, unsigned *h, + unsigned *depth, size_t *rowbytes) { + meow_font_slot *st = &g_slots[slot]; + if (!st->loaded) return NULL; + *w = st->plane_w; *h = st->plane_h; *depth = st->depth; *rowbytes = st->plane_rowbytes; + return st->plane; +} +#endif + +int meow_font_load_slot(int slot, const char *png_path, const char *idx_path) { + if (slot < 0 || slot >= MEOW_MAX_SLOTS) return -9; + meow_font_unload_slot(slot); + size_t plen = 0, ilen = 0; + unsigned char *png = read_file(png_path, &plen); + if (!png) return -1; + unsigned char *idx = read_file(idx_path, &ilen); + if (!idx) { free(png); return -1; } + + unsigned w, h, depth; + size_t rowbytes; + unsigned char *plane = png_decode_gray(png, plen, &w, &h, &depth, &rowbytes); + free(png); + if (!plane) { free(idx); return -2; } + + meow_font_slot *st = &g_slots[slot]; + st->plane = plane; + st->depth = depth; + st->plane_w = w; + st->plane_h = h; + st->plane_rowbytes = rowbytes; + + if (load_idx(idx, ilen, slot) != 0) { + free(idx); + meow_font_unload_slot(slot); + return -3; + } + free(idx); + st->loaded = 1; + return 0; +} + +int meow_font_load(const char *png_path, const char *idx_path) { + meow_font_unload(); /* 全局字库(重)装:清全部槽 + 复位多字体配置 */ + return meow_font_load_slot(0, png_path, idx_path); +} + +/* ---------------- 按档装载大字库(字体档位分层) ---------------- */ +/* 当前装载档位(0=common 1=gbk 2=full;仅记录,getter 走 meow_ui_gfx.c meow_font_tier) */ +static int g_reload_tier = 0; + +int meow_font_reload_tier(int tier, const char *png_path, const char *idx_path) +{ + if (tier < 0) tier = 0; + if (tier > 2) tier = 2; + if (!png_path || !png_path[0] || !idx_path || !idx_path[0]) return -1; + /* 先 unload 旧(释放旧 atlas——大字库 GBK/Full 解码 5-15MB+,避免新旧同时驻留内存), + * 再 load 新(slot0 = 全局/默认字库)。meow_font_load 内部即 unload+load。 */ + int r = meow_font_load(png_path, idx_path); + if (r == 0) g_reload_tier = tier; + return r; +} + +void meow_font_enable_multifont(int on) { g_multifont = on ? 1 : 0; } + +void meow_font_set_class_slot(meow_fclass cls, int slot) { + if (cls < 0 || cls >= MEOW_FCLS_COUNT) return; + g_class_slot[cls] = (slot >= 0 && slot < MEOW_MAX_SLOTS && g_slots[slot].loaded) ? slot : 0; +} + +int meow_font_has_size(int size) { + int lo = g_multifont ? 0 : 0; + int hi = g_multifont ? MEOW_MAX_SLOTS : 1; + for (int s = lo; s < hi; s++) { + meow_font_slot *st = &g_slots[s]; + if (!st->loaded) continue; + for (int i = 0; i < st->face_count; i++) + if (st->faces[i].px == size) return 1; + } + return 0; +} + +int meow_font_has_glyph(uint32_t cp) { + int slot = route_slot(cp); + meow_font_slot *st = &g_slots[slot]; + if (!st->loaded) return 0; + for (int i = 0; i < st->face_count; i++) + if (find_glyph(&st->faces[i], cp)) return 1; + return 0; +} + +int meow_font_ascent(int size) { + if (g_multifont) { + int best = 0; + for (int s = 0; s < MEOW_MAX_SLOTS; s++) { + meow_face *f = face_for_size(s, size); + if (f && f->ascent > best) best = f->ascent; + } + return best; + } + meow_face *f = face_for_size(0, size); + return f ? f->ascent : 0; +} + +int meow_font_line_height(int size) { + if (g_multifont) { + int best = 0; + for (int s = 0; s < MEOW_MAX_SLOTS; s++) { + meow_face *f = face_for_size(s, size); + if (f) { int h = f->ascent + f->descent; if (h > best) best = h; } + } + return best; + } + meow_face *f = face_for_size(0, size); + return f ? (f->ascent + f->descent) : 0; +} + +int meow_font_width_text(const char *utf8, int size) { + const unsigned char *p = (const unsigned char *)utf8; + int total = 0; + while (*p) { + int cp = utf8_next(&p); + if (cp < 0) continue; + unsigned scaleq; + meow_face *f = routed_face((uint32_t)cp, size, &scaleq, NULL); + if (!f) continue; + meow_glyph *gr = find_glyph(f, (uint32_t)cp); + unsigned adv = gr ? gr->advance : advance_for_missing(f); + if (scaleq == 4096) total += (int)adv; + else total += (int)(((uint64_t)adv * scaleq) >> 12); + } + return total; +} + +void meow_font_drawtext(uint16_t *fb, int x, int y, const char *utf8, + uint16_t color, int size) { + const unsigned char *p = (const unsigned char *)utf8; + int pen = 0; + while (*p) { + int cp = utf8_next(&p); + if (cp < 0) continue; + unsigned scaleq; + int slot; + meow_face *f = routed_face((uint32_t)cp, size, &scaleq, &slot); + if (!f) continue; + meow_glyph *gr = find_glyph(f, (uint32_t)cp); + if (gr && gr->w > 0 && gr->h > 0) { + blit_glyph(fb, slot, f, gr, x + pen, y, color, scaleq); + } else if (!gr) { + /* 缺字:画空心方块 */ + unsigned adv = advance_for_missing(f); + int tw, th; + if (scaleq == 4096) { tw = f->px / 2; th = f->ascent; } + else { tw = (int)(((uint64_t)(f->px / 2) * scaleq) >> 12); th = (int)(((uint64_t)f->ascent * scaleq) >> 12); } + draw_tofu(fb, x + pen + 1, y + 1, tw - 2, th - 2, color); + pen += scaleq == 4096 ? (int)adv : (int)(((uint64_t)adv * scaleq) >> 12); + continue; + } + int adv = gr ? gr->advance : (int)advance_for_missing(f); + pen += scaleq == 4096 ? adv : (int)(((uint64_t)adv * scaleq) >> 12); + } +} + +void meow_font_drawtext_outline(uint16_t *fb, int x, int y, const char *utf8, + uint16_t color, uint16_t outline_color, int ol, int size) { + if (ol <= 0) { meow_font_drawtext(fb, x, y, utf8, color, size); return; } + static const int odx[8] = {-1,1,-1,1,0,0,-1,1}; + static const int ody[8] = {-1,-1,1,1,-1,1,0,0}; + /* 先将每个字形画 8 方向偏移描边,再画原色:逐字符处理以保证 advance 一致。 */ + const unsigned char *p = (const unsigned char *)utf8; + int pen = 0; + while (*p) { + int cp = utf8_next(&p); + if (cp < 0) continue; + unsigned scaleq; + int slot; + meow_face *f = routed_face((uint32_t)cp, size, &scaleq, &slot); + if (!f) continue; + meow_glyph *gr = find_glyph(f, (uint32_t)cp); + if (gr && gr->w > 0 && gr->h > 0) { + for (int k = 0; k < 8; k++) + blit_glyph(fb, slot, f, gr, x + pen + odx[k] * ol, y + ody[k] * ol, outline_color, scaleq); + } + unsigned adv = gr ? gr->advance : advance_for_missing(f); + pen += scaleq == 4096 ? (int)adv : (int)(((uint64_t)adv * scaleq) >> 12); + } + meow_font_drawtext(fb, x, y, utf8, color, size); +} + +/* ---------------- 多字体配置(去重 + ≤3 套上限 + 内存预算,launcher 与 SDK 共用) ---------------- */ +/* 多字体全部槽(含 slot0)「打包像素面」总字节预算:超出时新增类别字库被拒、回退全局并用 + * *truncated=1 通知调用方 WARN。设定依据(真机 56MB,launcher+App 常态化紧张): + * slot0 若为 gbk 大字库,其 plane 已 ≈16.6MiB(>预算)→ 自动拒绝任何额外类别字库, + * 杜绝「大字库 + 多字体」双驻留 OOM;slot0 为 common(≈4.2MiB)时允许 1-3 个额外常用类 + * (+小类拉丁/数字等),封顶后单进程字库总驻留可控制在 ~16MiB 内。 + * 值 16 MiB:56MiB 总内存 − launcher 基线(估 26-28MiB)− 应用运行时 → 字库红利带。 */ +#define MEOW_MULTIFONT_MEM_BUDGET (16u * 1024 * 1024) + +static size_t mfont_total_plane_bytes(void) { + size_t t = 0; + for (int i = 0; i < MEOW_MAX_SLOTS; i++) { + meow_font_slot *st = &g_slots[i]; + if (st->loaded) t += (size_t)st->plane_rowbytes * st->plane_h; + } + return t; +} +int meow_font_config_multifont(int on, + const char *global_png, const char *global_idx, + const meow_font_class_cfg cfg[MEOW_FCLS_COUNT], + int *distinct, int *truncated) { + if (distinct) *distinct = 0; + if (truncated) *truncated = 0; + (void)global_idx; /* 全局字库已由 meow_font_load(slot0) 装载,idx 此处不重读 */ + /* 复位映射(保留已装载槽;未开则纯单字体) */ + g_multifont = 0; + for (int i = 0; i < MEOW_FCLS_COUNT; i++) g_class_slot[i] = 0; + int g0 = g_slots[0].loaded; + if (!on) { if (distinct) *distinct = g0 ? 1 : 0; return 0; } + + /* 已装载的不同字库(png 路径去重)。首项 = 全局(slot0)。 */ + const char *d_png[MEOW_MAX_SLOTS]; + int d_slot[MEOW_MAX_SLOTS]; + int d_n = 0; + d_png[0] = global_png ? global_png : ""; + d_slot[0] = 0; + d_n = 1; + g_multifont = 1; + + for (int c = 0; c < MEOW_FCLS_COUNT; c++) { + const char *png = cfg[c].png; + if (!png || !png[0]) continue; /* 空 = 跟随全局 */ + int found = -1; + if (global_png && !strcmp(png, global_png)) found = 0; + for (int k = 0; k < d_n && found < 0; k++) + if (!strcmp(png, d_png[k])) found = d_slot[k]; /* 同 family 去重 */ + if (found >= 0) { g_class_slot[c] = found; continue; } + + /* 新的一套不同字体:上限 MEOW_MAX_SLOTS(含全局)→ 截断 + WARN */ + if (d_n >= MEOW_MAX_SLOTS) { + if (truncated) *truncated = 1; + g_class_slot[c] = 0; /* 该类别回退全局 */ + continue; + } + int slot = d_n; /* slot0 已被全局占用 */ + const char *idx = cfg[c].idx ? cfg[c].idx : ""; + if (meow_font_load_slot(slot, png, idx) != 0) { /* 装载失败 → 回退全局 */ + g_class_slot[c] = 0; + continue; + } + /* 内存预算:装载成功后,若全部槽 plane 总字节超预算 → 卸载该新槽 + 回退全局 + 截断通知。 + * 防「大字库 slot0 + 多个大字库类别字库」同时驻留(真机 56MB 会 OOM)。 */ + if (mfont_total_plane_bytes() > MEOW_MULTIFONT_MEM_BUDGET) { + meow_font_unload_slot(slot); + if (truncated) *truncated = 1; + g_class_slot[c] = 0; + continue; + } + d_png[d_n] = png; d_slot[d_n] = slot; d_n++; + g_class_slot[c] = slot; + } + if (distinct) *distinct = d_n; + return 0; +} + +/* ================================================================== */ +/* M2.58 字体索引 + 应用字库查询(规格 §5.7:索引文件 / 查列表 / 丢失降级默认) */ +/* ================================================================== */ +/* SDK 可选日志:SDK 链 meow_log 强符号(→app 日志);launcher 无 meow_log(弱符号 → NULL, + * 静默跳过)——避免 meow_font.o 依赖 SDK,launcher 链接不至于为未定义 meow_log 而失败。 */ +extern void meow_log(int lvl, const char *fmt, ...) __attribute__((weak)); +#define MEOW_FONT_INDEX_MAX 32 /* index 条数上限(实际 ≤17,留余量) */ + +#define MEOW_FONT_DEF_PNG "/usr/data/mnt/sd_0/MeowOS/fonts/default/regular/font.png" +#define MEOW_FONT_DEF_IDX "/usr/data/mnt/sd_0/MeowOS/fonts/default/regular/font.idx" +#define MEOW_FONT_SYS_PNG "/usr/share/meowos/font/font.png" +#define MEOW_FONT_SYS_IDX "/usr/share/meowos/font/font.idx" + +const char *meow_font_card_root(void) +{ + const char *e = getenv("MEOW_CARD_ROOT"); + return (e && e[0]) ? e : "/usr/data/mnt/sd_0"; +} + +int meow_font_index_path(char *out, int olen) +{ + if (!out || olen <= 0) return -1; + snprintf(out, (size_t)olen, "%s/%s", meow_font_card_root(), MEOW_FONT_INDEX_REL); + return 0; +} + +/* 合成「板载默认」条目(index0;无卡/无 index 时唯一项)。 */ +static void meow_font_default_entry(meow_font_info *out) +{ + if (!out) return; + memset(out, 0, sizeof *out); + snprintf(out->family, sizeof out->family, "default"); + snprintf(out->name, sizeof out->name, "默认字体"); + snprintf(out->weight, sizeof out->weight, "regular"); + snprintf(out->tier, sizeof out->tier, "common"); + out->glyph_count = 0; + snprintf(out->label, sizeof out->label, "默认字体 Regular(common)"); + snprintf(out->path_png, sizeof out->path_png, "MeowOS/fonts/default/regular/font.png"); +} + +/* CSV 单字段读取(支持双引号包裹 + 内嵌双引号转义)。p 走到下一字段/行尾。 */ +static int csv_field(const char **pp, char *out, int olen) +{ + const char *p = *pp; + if (!out || olen <= 0) return 0; + int o = 0; + if (*p == '"') { + p++; + while (*p && o < olen - 1) { + if (*p == '"') { + if (p[1] == '"') { out[o++] = '"'; p += 2; continue; } + p++; /* 结束引号 */ + break; + } + out[o++] = *p++; + } + if (*p == ',') p++; + else if (*p == '\r' || *p == '\n') { /* 行尾,由外层清理 */ } + } else { + while (*p && *p != ',' && *p != '\r' && *p != '\n' && o < olen - 1) + out[o++] = *p++; + if (*p == ',') p++; + } + out[o] = 0; + *pp = p; + return 1; +} + +/* 有界复制(字符串入 dst≤dlen 且保证 NUL;避免 -Wformat-truncation 之类告警)。 */ +static void fcpy(char *dst, int dlen, const char *src) +{ + if (dlen <= 0) return; + size_t n = src ? strlen(src) : 0; + if (n >= (size_t)dlen) n = (size_t)dlen - 1; + memcpy(dst, src, n); + dst[n] = 0; +} + +/* 解析一行 index(CSV 6 字段)。成功填 info 并返回 1;path 为空 → 0(跳过)。 */ +static int meow_font_parse_index_line(char *line, meow_font_info *info) +{ + if (!line || !info) return 0; + const char *p = line; + char f[6][360]; + for (int i = 0; i < 6; i++) { + if (!csv_field(&p, f[i], (int)sizeof f[i])) return 0; + if (!f[i][0]) return 0; + } + memset(info, 0, sizeof *info); + fcpy(info->family, sizeof info->family, f[0]); + fcpy(info->name, sizeof info->name, f[0]); /* index 无单列 name → 回退 family */ + fcpy(info->weight, sizeof info->weight, f[1]); + fcpy(info->path_png, sizeof info->path_png, f[2]); + fcpy(info->label, sizeof info->label, f[3]); + fcpy(info->tier, sizeof info->tier, f[4]); + info->glyph_count = atoi(f[5]); + return info->path_png[0] ? 1 : 0; +} + +int meow_font_index_load(meow_font_info *out, int max) +{ + if (!out || max <= 0) return 0; + char path[450]; + if (meow_font_index_path(path, sizeof path) != 0) return 0; + FILE *f = fopen(path, "r"); + if (!f) { meow_font_default_entry(&out[0]); return 1; } /* 无卡/无 index → 仅板载默认 */ + int n = 0, total = 0; + char line[1024]; + while (fgets(line, sizeof line, f)) { + char *s = line; + while (*s == ' ' || *s == '\t') s++; + if (!*s || *s == '\n' || *s == '\r' || *s == '#') continue; /* 忽略空行/注释 */ + meow_font_info tmp; + if (!meow_font_parse_index_line(s, &tmp)) continue; + total++; + if (n < max) { out[n] = tmp; n++; } + } + fclose(f); + if (total == 0) { meow_font_default_entry(&out[0]); return 1; } + return total; +} + +int meow_font_get_available_count(void) +{ + meow_font_info buf[1]; + return meow_font_index_load(buf, 1); /* max=1:只统计总数,不要求全填 */ +} + +int meow_font_enumerate(int idx, meow_font_info *out) +{ + if (!out || idx < 0) return 0; + meow_font_info buf[MEOW_FONT_INDEX_MAX]; + int n = meow_font_index_load(buf, MEOW_FONT_INDEX_MAX); + if (idx >= n) return 0; + memcpy(out, &buf[idx], sizeof *out); + return 1; +} + +/* 相对路径按卡根解析为绝对路径(绝对路径原样返回)。 */ +static void font_abs_path(const char *rel, char *out, int olen) +{ + if (!rel || !rel[0]) { if (olen > 0) out[0] = 0; return; } + if (rel[0] == '/') { snprintf(out, (size_t)olen, "%s", rel); return; } + snprintf(out, (size_t)olen, "%s/%s", meow_font_card_root(), rel); +} + +/* 由 png 绝对路径派生同目录 font.idx(.png → .idx)。0=成功。 */ +static int font_idx_from_png(const char *png, char *out, int olen) +{ + if (!png || olen <= 0) return -1; + snprintf(out, (size_t)olen, "%s", png); + size_t n = strlen(out); + if (n > 4 && !strcmp(out + n - 4, ".png")) { + out[n-4]='.'; out[n-3]='i'; out[n-2]='d'; out[n-1]='x'; out[n]=0; + return 0; + } + return -1; +} + +/* 装载系统默认字体(跟随 font.file,经 env MEOW_FONT_PNG/IDX → 卡默认 → 固件)。 + * 与 SDK meow_ui_gfx load_font 的优先级一致;app 经 select_by_id(0) 或任何降级回退时走此路径。 */ +static int meow_font_load_system_default(void) +{ + const char *ep = getenv("MEOW_FONT_PNG"); + const char *ei = getenv("MEOW_FONT_IDX"); + char idxbuf[400]; + if (ep && ep[0]) { + if (!ei || !ei[0]) { + if (font_idx_from_png(ep, idxbuf, sizeof idxbuf) != 0) ei = NULL; + else ei = idxbuf; + } + if (ei && meow_font_load(ep, ei) == 0) return 0; + } + if (meow_font_load(MEOW_FONT_DEF_PNG, MEOW_FONT_DEF_IDX) == 0) return 0; + if (meow_font_load(MEOW_FONT_SYS_PNG, MEOW_FONT_SYS_IDX) == 0) return 0; + return -1; +} + +int meow_font_select_by_id(int id) +{ + if (id == 0) return meow_font_load_system_default(); /* 跟随系统默认(font.file) */ + meow_font_info info; + if (!meow_font_enumerate(id, &info)) return -1; /* 越界 → 负值 */ + if (!info.path_png[0]) return -1; + char png[400], idx[400]; + font_abs_path(info.path_png, png, sizeof png); + if (font_idx_from_png(png, idx, sizeof idx) != 0) return -1; + if (access(png, R_OK) == 0 && meow_font_load(png, idx) == 0) return 0; + /* 字库缺失 → 降级默认(不丢请求)+ WARN(app 日志;meow_log 为 SDK 统一日志) */ + if (meow_log) meow_log(2 /* MEOW_LOG_WARN */, "font %s missing, fallback default", png); + return meow_font_load_system_default(); +} diff --git a/sdk/meow_ui_audio.c b/sdk/meow_ui_audio.c index eb7a008..50c6fbe 100644 --- a/sdk/meow_ui_audio.c +++ b/sdk/meow_ui_audio.c @@ -75,7 +75,7 @@ static const char *sock_path(void) } /* 连接 audiod。**全部有界**:connect 非阻塞 + poll 2s(对端 listen backlog 满/wedge 时 - * 阻塞 connect 会把 App 播放线程永久压进内核——真机长流卡死时 ncm 卡死的同款路径); + * 阻塞 connect 会把 App 播放线程永久压进内核——真机长流卡死的同款路径); * 建连后 SO_SNDTIMEO/RCVTIMEO 2s:audiod 停摆时 write 超时返回错误 → 标记流死, * App 走「重新 open」恢复路径,绝不永久阻塞。 */ #define AUD_CONN_TMO_MS 2000 diff --git a/sdk/meow_ui_gfx.c b/sdk/meow_ui_gfx.c index f459632..900d145 100644 --- a/sdk/meow_ui_gfx.c +++ b/sdk/meow_ui_gfx.c @@ -12,7 +12,7 @@ #include "meow_ui_internal.h" #include "meow_ui_sys.h" #include "sb_icons.h" -#include "../meow_launcherd/meow_font.h" +#include "meow_font.h" /* 公开 SDK 仓内 vendored(include/meow_font.h),不再引主仓 launcher 头 */ #include #include @@ -976,7 +976,7 @@ static void present(void) if (!g_init_ok || !g_back_buf) return; if (!(g_dirty_full || g_ndirty > 0)) return; /* §10.5 叠加让位自抑(仅 App 进程):App 前台播放中唤起通知中心时 launcher 不 SIGSTOP - * (保 PCM 供流,ncm 播放线程同进程),改由 App 进程在此自抑上屏——绘制照常进 back_buf, + * (保 PCM 供流,App 播放线程同进程),改由 App 进程在此自抑上屏——绘制照常进 back_buf, * 只是不 present 到 fb,防 App 重绘穿透 launcher 叠加层。系统 UI 进程(launcher 渲染面板) * 不抑制。抑制期脏区保留累积(超上限自然升级整帧),标记消失后下一次 present 一次上屏。 */ if (overlay_present_suppressed()) { g_overlay_suppressed++; return; } diff --git a/sdk/meow_ui_input.c b/sdk/meow_ui_input.c index e105f5a..56b9dea 100644 --- a/sdk/meow_ui_input.c +++ b/sdk/meow_ui_input.c @@ -137,7 +137,7 @@ static int meow_input_jack_debounce(void) } /* ---- §10.5 叠加让位:通知中心叠加会话期输入排空(播放中 App 不被 SIGSTOP 的配套)---- - * 媒体播放中的 App 前台唤起通知中心时 launcher 不冻结 App(保 PCM 供流,ncm 播放线程同进程), + * 媒体播放中的 App 前台唤起通知中心时 launcher 不冻结 App(保 PCM 供流,App 播放线程同进程), * App 输入线程仍在读同一批 evdev 设备(每 fd 各收一份事件副本,与 launcher 互不剥夺)——若不 * 抑制,面板上的一次点击会被 App 再消费一次(如面板媒体卡片「暂停」与 App 同坐标控件双重触发)。 * 故标记存在期:排空本进程 fd 队列并丢弃、EVIOCGKEY 位图静默对齐(不产生边沿语义,系统键动作 diff --git a/sdk/meow_ui_log.c b/sdk/meow_ui_log.c index 3817ea8..933e4bb 100644 --- a/sdk/meow_ui_log.c +++ b/sdk/meow_ui_log.c @@ -146,9 +146,9 @@ void meow_log_install_exit_handler(void) /* M2.87:SIGUSR1 无害护栏(防线冗余)。watchdog 对「左缘起点+右滑」发 SIGUSR1 当返回事件; * SDK 框架 App(framework=1)自己经 on_swipe 处理左缘返回(meow_ui_input.c),不应被 SIGUSR1 * 终止。但看门狗对 framework 判定有误/竞态/测试注入等**任何**路径漏进 SIGUSR1,默认动作都会 - * 杀死 App(ncm signal=16 崩溃根因之一)。本节安装一个不做任何致命动作的处理器:记日志+忽略 + * 杀死 App(某 App signal=16 崩溃根因之一)。本节安装一个不做任何致命动作的处理器:记日志+忽略 * ——框架 App 的返回/退出语义由 SDK on_swipe 统一决策,SIGUSR1 仅作「绝不杀 App」的保活护栏, - * 不重复触发返回(避免与 on_swipe 双重返回)。App 若自装 SIGUSR1 处理器(hello/ncm 均未装, + * 不重复触发返回(避免与 on_swipe 双重返回)。App 若自装 SIGUSR1 处理器(hello 等框架 App 均未装, * 裸沉浸 App 自绘处理)会覆盖本处理器;`--sim-swipe` 测试路径也随之被吞。 */ static void sigusr1_guard(int sig) { diff --git a/sdk/meow_ui_media.c b/sdk/meow_ui_media.c index 59c54a8..bab4ae4 100644 --- a/sdk/meow_ui_media.c +++ b/sdk/meow_ui_media.c @@ -214,7 +214,7 @@ void meow_ui_media_stop(void) /* 物理媒体键 keycode → MSG_PLAYCTL cmd(1=播放 2=暂停 3=上一首 4=下一首 5=seek 6=播放/暂停切换)。 * 播放/暂停(164)恒发 cmd=6 toggle——SDK 本地不再按「最近上报 state」判 play/pause: - * 走自有 IPC 线程的媒体源(如 ncm 的 ipc_media.c)不经 meow_ui_media_meta 上报, + * 走自有 IPC 线程的媒体源,不经 meow_ui_media_meta 上报, * 本地 state 恒 0 → 恒发「播放」,播放中按键无效(真机 bug)。toggle 由媒体源按自身 * 当前态翻转(权威在源)。上一曲 165/19→3,下一曲 163→4。非媒体键返回 -1。 */ static int media_key_cmd(int code) diff --git a/sdk/test_ime_fix b/sdk/test_ime_fix deleted file mode 100755 index 6d6f73b..0000000 Binary files a/sdk/test_ime_fix and /dev/null differ diff --git a/sdk/test_meow_ui_theme b/sdk/test_meow_ui_theme deleted file mode 100755 index d24f89c..0000000 Binary files a/sdk/test_meow_ui_theme and /dev/null differ diff --git a/sdk/test_meow_ui_vol b/sdk/test_meow_ui_vol deleted file mode 100755 index 2b2b3b7..0000000 Binary files a/sdk/test_meow_ui_vol and /dev/null differ diff --git a/sdk/test_meow_ui_widgets b/sdk/test_meow_ui_widgets deleted file mode 100755 index ba5e99a..0000000 Binary files a/sdk/test_meow_ui_widgets and /dev/null differ diff --git a/sdk/test_notif_gesture b/sdk/test_notif_gesture deleted file mode 100755 index d8de637..0000000 Binary files a/sdk/test_notif_gesture and /dev/null differ diff --git a/sdk/test_notif_open b/sdk/test_notif_open deleted file mode 100755 index 76292e8..0000000 Binary files a/sdk/test_notif_open and /dev/null differ diff --git a/sdk/test_notify_integration b/sdk/test_notify_integration deleted file mode 100755 index cb3e3be..0000000 Binary files a/sdk/test_notify_integration and /dev/null differ diff --git a/sdk/test_notify_ipc b/sdk/test_notify_ipc deleted file mode 100755 index a663107..0000000 Binary files a/sdk/test_notify_ipc and /dev/null differ diff --git a/sdk/test_p1p2p3_repro.c b/sdk/test_p1p2p3_repro.c index 27438eb..31692f5 100644 --- a/sdk/test_p1p2p3_repro.c +++ b/sdk/test_p1p2p3_repro.c @@ -36,7 +36,7 @@ */ #include "meow_ui.h" #include "meow_ui_internal.h" -#include "../hello_app/hello_internal.h" +#include "../examples/hello/hello_internal.h" #include #include #include diff --git a/sdk/test_pinyin b/sdk/test_pinyin deleted file mode 100755 index ce9fedc..0000000 Binary files a/sdk/test_pinyin and /dev/null differ diff --git a/sdk/test_sb_collapse b/sdk/test_sb_collapse deleted file mode 100755 index 385d32b..0000000 Binary files a/sdk/test_sb_collapse and /dev/null differ diff --git a/sdk/test_sdk_input_keys b/sdk/test_sdk_input_keys deleted file mode 100755 index d38b64b..0000000 Binary files a/sdk/test_sdk_input_keys and /dev/null differ diff --git a/sdk/test_ui_audio_host b/sdk/test_ui_audio_host deleted file mode 100755 index 3890d78..0000000 Binary files a/sdk/test_ui_audio_host and /dev/null differ