chore(sdk): 公开仓适配 — 剔除构建产物/宿主测试二进制; vendored meow_font.c/meow_ipc.h/meow_font.h/meow_notif.h 使其自包含; build_sdk.sh/build_hello.sh 改 MEOW_CC/MEOW_SYSROOT 可配置; 去除内部路径与 ncm 引用
This commit is contained in:
1 parent
4f66da66ad
commit
86605d01e3
26 files changed
+1938
-32
No files matched your search
+12
-14
@@ -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
|
||||
@@ -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 <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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>/(family/weight 取自 font.json 的
|
||||
* family/weight 或 zip 名,缺省 weight=regular;冲突时覆盖需先确认);导出=把
|
||||
* MeowOS/fonts/<family>/<weight>/ 三件套打包为 <out_zip>。
|
||||
* 目录布局与 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 卡 <card_root>/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/<family>/<weight>/ 后写该文件(覆盖,含默认行 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/<family>/ 一级目录名 */
|
||||
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/<family>/<weight>/font.png */
|
||||
} meow_font_info;
|
||||
#endif
|
||||
|
||||
/* 卡根:env MEOW_CARD_ROOT 优先,否则默认 /usr/data/mnt/sd_0。 */
|
||||
const char *meow_font_card_root(void);
|
||||
/* <card_root>/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 */
|
||||
@@ -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 <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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.<action>=<chain>`)决定 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.<svc>=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.<svc>) */
|
||||
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 结尾字符串,
|
||||
* 每串形如 "<route_id>=<chain>"(如 "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"|"<type> <on|off>";udhcpc arg="renew <if>"|"release <if>")。
|
||||
* cmd(BT_CTL) : status|on|off|paired。 */
|
||||
typedef struct m_net_cmd {
|
||||
char cmd[24]; /* 子命令字串(大写不敏感;接收侧截断) */
|
||||
char arg[132]; /* connect: "<ssid>[,<psk>]";rfkill: "<type> <on|off>";udhcpc: "renew|release <if>" */
|
||||
} 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:<pkg>`/`svc:<svc>`/`proc:<comm>`。 */
|
||||
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 */
|
||||
@@ -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 <stdint.h> /* uint32_t/uint64_t/int32_t */
|
||||
#include <stddef.h> /* 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 */
|
||||
+964
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h> /* 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();
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
|
||||
+2
-2
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -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; }
|
||||
|
||||
+1
-1
@@ -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 位图静默对齐(不产生边沿语义,系统键动作
|
||||
|
||||
+2
-2
@@ -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)
|
||||
{
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user