三特性: 字体分层(gen_font.py --charset + meow_font_reload_tier + font.load_full + 尺寸标记) + 全屏机制/状态栏收合(meow_ui_sys/input) + 通知#6 TF转储(CSV+10MB+开机清板载+内存表)

This commit is contained in:
MeowOS Dev committed 2026-08-30 11:46:29 +08:00
1 parent 0b4d1d3215
commit a2673d5922
18 files changed
+819 -80

No files matched your search

+2
View File
@@ -326,6 +326,8 @@ patch_v2/meow_launcherd/meow_font.a # 复用(静态链接)
> **app 字体来源(M2.x 已落地)**:app 若**自带字库**(`load_handle`,见 §5.7.3,**未实现**)则用它;否则 SDK `meow_ui_gfx.c load_font()` **优先读 launcher 注入的环境变量** `MEOW_FONT_PNG`/`MEOW_FONT_IDX`(=系统 `font.file` 当前生效字体,绝对路径)/`MEOW_FONT_APP`(family),无 env 则回退卡默认 `MeowOS/fonts/default/font.png` → 固件。因此**app 未自带字库时跟随系统 `font.file`**,不再硬编码 `MeowAPPs/fonts`。 > **app 字体来源(M2.x 已落地)**:app 若**自带字库**(`load_handle`,见 §5.7.3,**未实现**)则用它;否则 SDK `meow_ui_gfx.c load_font()` **优先读 launcher 注入的环境变量** `MEOW_FONT_PNG`/`MEOW_FONT_IDX`(=系统 `font.file` 当前生效字体,绝对路径)/`MEOW_FONT_APP`(family),无 env 则回退卡默认 `MeowOS/fonts/default/font.png` → 固件。因此**app 未自带字库时跟随系统 `font.file`**,不再硬编码 `MeowAPPs/fonts`。
> >
> **app 多字体(M2.5x,`display.multi_font`)**:launcher 注入 `MEOW_MULTI_FONT=0/1` 与 6 类 `MEOW_FONT_CLS_<类>_PNG/IDX`(命中则覆盖全局;类值为空=跟随全局不注入)。SDK `meow_ui_gfx` 的 `meow_gfx_load_multifont()` 读 env → 调 `meow_font_config_multifont()`(同 png 去重、≤4 套上限、缺类回退全局)构建**逐字符分类路由**:`meow_font_drawtext/width_text/line_height` 按 `meow_font_class_of(cp)`(`MEOW_FCLS_CJK/_CJK_SYM/_NUM/_LATIN/_LATIN_SYM/_OTHER`)用对应字库取字形与度量;`meow_font_has_glyph(cp)` 可预判缺字。`display.multi_font=0`/无 env → 单字体(现状,零额外开销)。底层接口见 meow_font.h:`meow_font_load_slot`/`meow_font_unload_slot`/`meow_font_enable_multifont`/`meow_font_set_class_slot`/`meow_font_config_multifont`;`MEOW_MAX_SLOTS=5`。 > **app 多字体(M2.5x,`display.multi_font`)**:launcher 注入 `MEOW_MULTI_FONT=0/1` 与 6 类 `MEOW_FONT_CLS_<类>_PNG/IDX`(命中则覆盖全局;类值为空=跟随全局不注入)。SDK `meow_ui_gfx` 的 `meow_gfx_load_multifont()` 读 env → 调 `meow_font_config_multifont()`(同 png 去重、≤4 套上限、缺类回退全局)构建**逐字符分类路由**:`meow_font_drawtext/width_text/line_height` 按 `meow_font_class_of(cp)`(`MEOW_FCLS_CJK/_CJK_SYM/_NUM/_LATIN/_LATIN_SYM/_OTHER`)用对应字库取字形与度量;`meow_font_has_glyph(cp)` 可预判缺字。`display.multi_font=0`/无 env → 单字体(现状,零额外开销)。底层接口见 meow_font.h:`meow_font_load_slot`/`meow_font_unload_slot`/`meow_font_enable_multifont`/`meow_font_set_class_slot`/`meow_font_config_multifont`;`MEOW_MAX_SLOTS=5`。
>
> **app 字体档位(M2.5x,`app.json "fonts"` 声明 + `font.load_full`)**:launcher 按 `app.json "fonts":"common|gbk|full"` 与系统「加载大字库」开关(`font.load_full`,默认 0;开=强制 ≥gbk 档)算出**生效档**,注入 `MEOW_FONT_TIER=<0|1|2>`(0=**common**≈4k 常用 1=**gbk**≈20k+ 大 2=**full** 全/可选),并把 `MEOW_FONT_PNG/IDX` 指向该档字库绝对路径——`meow_font_effective_paths_tiered()` 在卡内 `MeowOS/fonts/<family>/` 按 `font.json` 的 `charset`(`common|gbk|full`)选库(gbk 请求接受 gbk/full;full 请求优先 full 再降级 gbk;无匹配回退全局 `font.file`)。SDK `meow_ui_gfx.c load_font()` 载入后,应用可经 `meow_font_tier()` 查询当前档、`meow_font_set_tier()` 切档(触发 `meow_font_reload_tier()` **真重载**:大字库切换先 unload 旧再 load 新,释放旧 atlas,防 GBK/Full 大库双驻留)。**判缺字**:`meow_font_has_glyph(cp)` 预判当前字库是否含该码位(common 小库下缺字→app 回退/提示,避免静默豆腐)。底层接口见 meow_font.h `meow_font_reload_tier`;`font/gen/gen_font.py --charset common|gbk|full` 按档生成 `font.png/font.idx/font.json`(`font.json` 写 `charset`+`glyph_count`)。
--- ---
+4 -4
View File
@@ -161,9 +161,9 @@
- **preset 动态化**:启动/进设置页扫描一次 `MeowOS/fonts/*/`,重建「字体文件」下拉(默认字体 + ≤16 套卡内字体;上限防爆)。扫描仅此二时机,勿放热路径。 - **preset 动态化**:启动/进设置页扫描一次 `MeowOS/fonts/*/`,重建「字体文件」下拉(默认字体 + ≤16 套卡内字体;上限防爆)。扫描仅此二时机,勿放热路径。
- **MeowPanel 预留**:`meow_font_import_zip()`/`meow_font_export_zip()` 为 TODO 声明(不实现),供 MeowPanel 后续接入(`.zip` 导入校验→解压到 `MeowOS/fonts/<family>/`、导出打包)。 - **MeowPanel 预留**:`meow_font_import_zip()`/`meow_font_export_zip()` 为 TODO 声明(不实现),供 MeowPanel 后续接入(`.zip` 导入校验→解压到 `MeowOS/fonts/<family>/`、导出打包)。
- **多字体(M2.5x,`display.multi_font`)**:按字符类别(CJK/CJK_SYM/NUM/LATIN/LATIN_SYM/OTHER)逐字符路由,每类可各选一套字库(`font.class_*`,默认空=跟随全局 `font.file`);同 family 去重、同时装载不同字体 ≤4 套(超出 WARN「过多不同字体会增内存」并截断);开关关 = 单字体(现状)。详见 §5.7.7「多字体」。 - **多字体(M2.5x,`display.multi_font`)**:按字符类别(CJK/CJK_SYM/NUM/LATIN/LATIN_SYM/OTHER)逐字符路由,每类可各选一套字库(`font.class_*`,默认空=跟随全局 `font.file`);同 family 去重、同时装载不同字体 ≤4 套(超出 WARN「过多不同字体会增内存」并截断);开关关 = 单字体(现状)。详见 §5.7.7「多字体」。
- **字体档位 / 大字库(M2.5x)**:抽象出**字库大小档位**,让系统/应用/设置页都知道选的是多大字库 `[源:用户 2026-08-30]`。 - **字体档位 / 大字库(M2.5x,本次实现)**:抽象出**字库大小档位**,让系统/应用/设置页都知道选的是多大字库 `[源:用户 2026-08-30]`。
- 档位枚举:`MEOW_FONT_TIER_COMMON`(**默认 / ≈4k** 码位)/ `MEOW_FONT_TIER_GBK`(**大 / ≈20k+**)/ `MEOW_FONT_TIER_FULL`(**全 / 可选**,码位/体积最大)。 - 档位枚举:`MEOW_FONT_TIER_COMMON`(**默认 / ≈4k** 码位)/ `MEOW_FONT_TIER_GBK`(**大 / ≈20k+**)/ `MEOW_FONT_TIER_FULL`(**全 / 可选**,码位/体积最大)。
- `font/gen/gen_font.py` 增 `--charset common|gbk|full` 按档生成 `font.png/font.idx`(现行仅 `--extra` 思路升级为**档位选择**,**本次新增**);每个 `MeowOS/fonts/<family>/font.json` 标 `charset` + `glyph_count`(覆盖声明/信息用)。 - `font/gen/gen_font.py` `--charset common|gbk|full` 按档生成 `font.png/font.idx/font.json`(**已实现**,现行仅 `--extra` 思路升级为**档位选择**);每个 `MeowOS/fonts/<family>/font.json` 标 `charset` + `glyph_count`(覆盖声明/信息用,launcher/settings 据此标注与按需装载)。
- **默认用小字库(`common`≈4k)**;仅当 app 声明需大字库(`app.json "fonts":"full"` 或档位)才为该 app 加载大字库(`gbk`≈20k+),见 §5.7.8。 - **默认用小字库(`common`≈4k)**;仅当 app 声明需大字库(`app.json "fonts":"full"` 或档位)才为该 app 加载大字库(`gbk`≈20k+),见 §5.7.8。
- **系统设置「加载大字库」开关**(`font.load_full`,强制全系统用大字库),见 SYSTEM_SETTINGS §1;**每个字体选项标注字库大小标记**(如 `default(common≈4k)` / `gbk≈20k` / `full`),让用户知道所选字库量级。 - **系统设置「加载大字库」开关**(`font.load_full`,强制全系统用大字库),见 SYSTEM_SETTINGS §1;**每个字体选项标注字库大小标记**(如 `default(common≈4k)` / `gbk≈20k` / `full`),让用户知道所选字库量级。
- 判缺字:SDK `meow_font_has_glyph(cp)` 预判当前字库是否含该码位(缺字 → app 回退/提示)。 - 判缺字:SDK `meow_font_has_glyph(cp)` 预判当前字库是否含该码位(缺字 → app 回退/提示)。
@@ -296,9 +296,9 @@
- **launcher env 注入**(launch_app,app 跟随系统字体):`MEOW_MULTI_FONT=0/1`;`MEOW_FONT_PNG/IDX`(全局,保留);6 类 `MEOW_FONT_CLS_<类>_PNG/IDX`(命中则覆盖全局;类值为空=跟随全局不注入)。SDK `meow_ui_gfx` 读 env 构建同一映射;无 env/关 → 单字体。 - **launcher env 注入**(launch_app,app 跟随系统字体):`MEOW_MULTI_FONT=0/1`;`MEOW_FONT_PNG/IDX`(全局,保留);6 类 `MEOW_FONT_CLS_<类>_PNG/IDX`(命中则覆盖全局;类值为空=跟随全局不注入)。SDK `meow_ui_gfx` 读 env 构建同一映射;无 env/关 → 单字体。
- **持久化**:类别字体设置在 settings.conf(板载,无卡也可存),经 `meow_settings_*` 读写,随设置页变更即重配。 - **持久化**:类别字体设置在 settings.conf(板载,无卡也可存),经 `meow_settings_*` 读写,随设置页变更即重配。
- **内存**(推断解码 atlas = `plane_w×plane_h×(depth 4→0.5 / 8→1.0)` 字节;每套另加 glyph 数组 ≈ glyph_count×16B、idx 头保留约 128KB,idx 原始缓冲载毕即释)。实测仓库字库(depth 4):misans(CJK)≈2.44MiB、source_han_sans(CJK)≈2.53MiB、meslo(Latin)≈1.68MiB、google_sans(Latin)≈1.12MiB;按用户估算口径(**depth 8**):CJK ≈ 5-6MB、Latin ≈ 0.3-0.5MB(后者为窄覆盖 Latin 专库)。**典型「1CJK+1Latin」增量**:全局=CJK,另加一套 Latin → 增加 ≈0.3-1.7MB(depth 8 约 0.5MB;实测 depth4 latin 1.1-1.7MB);全局=Latin 另加一套 CJK → 增加 ≈2.4-2.5MB(depth 8 约 5-6MB)。`display.multi_font=0` 时仅全局一套,无任何额外装载。 - **内存**(推断解码 atlas = `plane_w×plane_h×(depth 4→0.5 / 8→1.0)` 字节;每套另加 glyph 数组 ≈ glyph_count×16B、idx 头保留约 128KB,idx 原始缓冲载毕即释)。实测仓库字库(depth 4):misans(CJK)≈2.44MiB、source_han_sans(CJK)≈2.53MiB、meslo(Latin)≈1.68MiB、google_sans(Latin)≈1.12MiB;按用户估算口径(**depth 8**):CJK ≈ 5-6MB、Latin ≈ 0.3-0.5MB(后者为窄覆盖 Latin 专库)。**典型「1CJK+1Latin」增量**:全局=CJK,另加一套 Latin → 增加 ≈0.3-1.7MB(depth 8 约 0.5MB;实测 depth4 latin 1.1-1.7MB);全局=Latin 另加一套 CJK → 增加 ≈2.4-2.5MB(depth 8 约 5-6MB)。`display.multi_font=0` 时仅全局一套,无任何额外装载。
- **5.7.8 字体档位分层 / 按需大字库(app 声明 + 系统开关)** `[源:用户 2026-08-30]` - **5.7.8 字体档位分层 / 按需大字库(app 声明 + 系统开关,本次实现)** `[源:用户 2026-08-30]`
- **口径**:默认用**小字库 `common`(≈4k 码位)**;当 app 声明需要**大字库**(如小说阅读器,`app.json` 声明 `"fonts":"full"` 或档位 `"gbk"`/`"common"`)时,才为该 app 加载**大字库 `gbk`(≈20k+)**。档位与 `gen_font.py --charset`、`font.json charset/glyph_count` 见 §5.2「字体档位/大字库」。 - **口径**:默认用**小字库 `common`(≈4k 码位)**;当 app 声明需要**大字库**(如小说阅读器,`app.json` 声明 `"fonts":"full"` 或档位 `"gbk"`/`"common"`)时,才为该 app 加载**大字库 `gbk`(≈20k+)**。档位与 `gen_font.py --charset`、`font.json charset/glyph_count` 见 §5.2「字体档位/大字库」。
- **app 级 / activity 级**:app 级以 `app.json "fonts":"<tier>"` 声明;activity 级(如需)以 `route.<id>.fonts` 声明(沿用 `route.<id>` 扩展口径)。launcher 启动 app/切 activity 时据声明选加载大/小字库,并经 env 注入(`MEOW_FONT_PNG/IDX` 指向所选档位字库;新增 `MEOW_FONT_TIER` 标注档位)。 - **app 级 / activity 级**:app 级以 `app.json "fonts":"<tier>"` 声明(launcher `meow_parse_app_json` 已解析);activity 级(如需)以 `route.<id>.fonts` 声明(沿用 `route.<id>` 扩展口径)。launcher 启动 app 时据声明 + `font.load_full` 算**生效档**,经 env 注入(`MEOW_FONT_PNG/IDX` 指向所选档位字库绝对路径;`MEOW_FONT_TIER` 标注档位;launcher `meow_font_effective_paths_tiered` 按卡内 `font.json charset` 选库)。
- **系统设置「加载大字库」**(`font.load_full`,默认 0):开 → **强制全系统用大字库(gbk 档)**,覆盖 app 未声明 + 覆盖默认 `font.file`;关(默认)→ 默认小字库,app 声明才大字库。 - **系统设置「加载大字库」**(`font.load_full`,默认 0):开 → **强制全系统用大字库(gbk 档)**,覆盖 app 未声明 + 覆盖默认 `font.file`;关(默认)→ 默认小字库,app 声明才大字库。
- **SDK 判缺字**:`meow_font_has_glyph(cp)` 预判当前字库是否含该码位 → app 据此决定回退/提示(避免 `common` 小字库下静默豆腐或缺字)。 - **SDK 判缺字**:`meow_font_has_glyph(cp)` 预判当前字库是否含该码位 → app 据此决定回退/提示(避免 `common` 小字库下静默豆腐或缺字)。
- **⚠️ 与 §5.7.3 冲突**:§5.7.3 `"fonts":[<handle>…]` 为**应用自带字库句柄数组**;本小节 `"fonts":"full"` 为**字库档位字符串**。同名键两种语义,需统一解析口径(见 §7 A-14)。 - **⚠️ 与 §5.7.3 冲突**:§5.7.3 `"fonts":[<handle>…]` 为**应用自带字库句柄数组**;本小节 `"fonts":"full"` 为**字库档位字符串**。同名键两种语义,需统一解析口径(见 §7 A-14)。
+93 -6
View File
@@ -44,8 +44,11 @@ idx 二进制布局(little-endian, version=2):
+15 u8 dy +15 u8 dy
""" """
import argparse import argparse
import hashlib
import json
import os import os
import struct import struct
import unicodedata
import zlib import zlib
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
@@ -96,32 +99,80 @@ def pack_plane_depth(plane_l8, width, height, depth):
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# 字符集 # 字符集(字体档位:common / gbk / full)
# 口径见 docs/MEOWOS_SPEC_REF.md §5.2「字体档位/大字库」与 §5.7.8:
# common ≈ 4k 码位(ASCII + GB2312 常用 + 标点 + --extra) = 现状;
# gbk ≈ 20k+(再并入 CJK 统一表意 U+4E00-9FFF + CJK 符号/全角,含常用简繁/生僻);
# full ≈ 全 Unicode 可渲染区间(gbk + CJK 扩展 A/B-F + 拉丁/希腊/西里尔 + 通用标点,可选)。
# 每档在产物 font.json 写入 charset + glyph_count(见 write_font_json)。
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
def build_charset(extra=""): CHARSET_TIERS = ("common", "gbk", "full")
CHARSET_ALIAS = {"0": "common", "common": "common", "1": "gbk", "gbk": "gbk",
"2": "full", "full": "full"}
def build_charset(tier="common", extra=""):
if tier not in CHARSET_TIERS:
tier = "common"
cps = [] cps = []
def add(cp): def add(cp):
if cp not in cps: if 0 <= cp <= 0x10FFFF and cp not in cps:
cps.append(cp) cps.append(cp)
# ASCII 可打印字符
for c in range(0x20, 0x7F): for c in range(0x20, 0x7F):
add(c) add(c)
# GB2312 一/二级常用简体(B0-D7 + D8 常用二级首段)
for hi in range(0xB0, 0xD8): for hi in range(0xB0, 0xD8):
for lo in range(0xA1, 0xFF): for lo in range(0xA1, 0xFF):
try: try:
add(ord(bytes([hi, lo]).decode("gb2312"))) add(ord(bytes([hi, lo]).decode("gb2312")))
except UnicodeDecodeError: except UnicodeDecodeError:
pass pass
for hi in range(0xA1, 0xA4): # GB2312 符号/全角(A1-A4)
for hi in range(0xA1, 0xA5):
for lo in range(0xA1, 0xFF): for lo in range(0xA1, 0xFF):
try: try:
add(ord(bytes([hi, lo]).decode("gb2312"))) add(ord(bytes([hi, lo]).decode("gb2312")))
except UnicodeDecodeError: except UnicodeDecodeError:
pass pass
if tier in ("gbk", "full"):
# CJK 统一表意(含常用简繁/生僻):U+4E00-9FFF ≈ 2万+
for cp in range(0x4E00, 0xA000):
add(cp)
# CJK 符号与标点 + 全角形式(补齐 common 未含的)
for cp in range(0x3000, 0x3040):
add(cp)
for cp in range(0xFF00, 0xFFF0):
add(cp)
if tier == "full":
# 拉丁补充 / 希腊 / 西里尔 等常用非 CJK(Latin-1 起已覆盖 AI 常见重音)
for cp in range(0xA0, 0x0530):
add(cp)
# CJK 扩展 A + 兼容表意
for cp in range(0x3400, 0x4DC0):
add(cp)
for cp in range(0xF900, 0xFB00):
add(cp)
# 通用标点 / 货币等(— … “ ” 等已在 gbk;此处补全)
for cp in range(0x2000, 0x2070):
add(cp)
for cp in range(0x20A0, 0x20D0):
add(cp)
# CJK 扩展 B-F(补充平面)
for cp in range(0x20000, 0x2EBF0):
add(cp)
for cp in range(0x30000, 0x31350):
add(cp)
for ch in extra: for ch in extra:
add(ord(ch)) add(ord(ch))
return sorted(cps) return sorted(cps)
def charset_glyph_count(cps):
"""glyph_count = 字符集码位数(信息用,落 font.json)。"""
return len(cps)
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# 逐字形渲染 # 逐字形渲染
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
@@ -227,6 +278,33 @@ def write_idx(path, bit_depth, faces, glyph_tabs):
return len(header) + len(body) return len(header) + len(body)
# --------------------------------------------------------------------------
# font.json(元数据 + 完整性校验;schema 见 docs/MEOWOS_SPEC_REF.md §5.2)
# --------------------------------------------------------------------------
def write_font_json(out_dir, name, family, tier, glyph_count, sizes, bitdepth,
png_path, idx_path):
def sha256(path):
h = hashlib.sha256()
with open(path, "rb") as f:
for blk in iter(lambda: f.read(1 << 16), b""):
h.update(blk)
return h.hexdigest()
meta = {
"family": family or name, # 同目录名(信息用;扫描端以目录名为 family)
"name": name, # 下拉显示名(缺则回退目录名)
"glyph_count": glyph_count, # 信息用
"charset": tier, # common|gbk|full(档位;驱动按需加载)
"size_list": sizes, # 原生面字号
"bit_depth": bitdepth, # 4|8
"sha256_png": sha256(png_path), # font.png 校验(扫描端仅 WARN)
"sha256_idx": sha256(idx_path), # font.idx 校验(扫描端仅 WARN)
}
jpath = os.path.join(out_dir, "font.json")
with open(jpath, "w", encoding="utf-8") as f:
json.dump(meta, f, ensure_ascii=False, indent=2)
return jpath
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# 主流程 # 主流程
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
@@ -236,14 +314,18 @@ def main():
ap.add_argument("--sizes", default="28,20") ap.add_argument("--sizes", default="28,20")
ap.add_argument("--out", required=True) ap.add_argument("--out", required=True)
ap.add_argument("--name", default="font") ap.add_argument("--name", default="font")
ap.add_argument("--family", default="", help="font.json family(缺省 = --name;信息用)")
ap.add_argument("--charset", default="common", choices=CHARSET_TIERS,
help="字体档位:common≈4k / gbk≈20k+ / full(可选,默认不推)")
ap.add_argument("--bitdepth", type=int, default=4, choices=(4, 8)) ap.add_argument("--bitdepth", type=int, default=4, choices=(4, 8))
ap.add_argument("--extra", default="") ap.add_argument("--extra", default="")
args = ap.parse_args() args = ap.parse_args()
os.makedirs(args.out, exist_ok=True) os.makedirs(args.out, exist_ok=True)
sizes = [int(s) for s in args.sizes.split(",") if s.strip()] sizes = [int(s) for s in args.sizes.split(",") if s.strip()]
cps = build_charset(args.extra) tier = CHARSET_ALIAS.get(args.charset, "common")
print(f"字符集: {len(cps)} 码位") cps = build_charset(tier, args.extra)
print(f"字符集: {len(cps)} 码位 (charset={tier})")
all_planes = [] # (aw, ah, plane_l8 bytes, tab) all_planes = [] # (aw, ah, plane_l8 bytes, tab)
max_w = 0 max_w = 0
@@ -311,10 +393,15 @@ def main():
idx_path = os.path.join(args.out, f"{args.name}.idx") idx_path = os.path.join(args.out, f"{args.name}.idx")
idx_size = write_idx(idx_path, args.bitdepth, face_meta, [p[3] for p in all_planes]) idx_size = write_idx(idx_path, args.bitdepth, face_meta, [p[3] for p in all_planes])
# ---- font.json(写入 charset + glyph_count + sha256,供扫描端标注档位/大小标记) ----
jpath = write_font_json(args.out, args.name, args.family, tier, charset_glyph_count(cps),
sizes, args.bitdepth, png_path, idx_path)
png_size = os.path.getsize(png_path) png_size = os.path.getsize(png_path)
total = png_size + idx_size total = png_size + idx_size
print(f"\nfont.png {png_size} B") print(f"\nfont.png {png_size} B")
print(f"font.idx {idx_size} B") print(f"font.idx {idx_size} B")
print(f"font.json {os.path.getsize(jpath)} B (charset={tier}, glyph_count={len(cps)})")
print(f"合计 {total} B = {total/1024:.1f} KiB = {total/1048576:.2f} MiB (预算 2MiB)") print(f"合计 {total} B = {total/1024:.1f} KiB = {total/1048576:.2f} MiB (预算 2MiB)")
+16
View File
@@ -529,6 +529,22 @@ int meow_font_load(const char *png_path, const char *idx_path) {
return meow_font_load_slot(0, png_path, idx_path); 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_enable_multifont(int on) { g_multifont = on ? 1 : 0; }
void meow_font_set_class_slot(meow_fclass cls, int slot) { void meow_font_set_class_slot(meow_fclass cls, int slot) {
+9
View File
@@ -33,6 +33,15 @@ int meow_font_load(const char *png_path, const char *idx_path);
/* 释放字库。 */ /* 释放字库。 */
void meow_font_unload(void); 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)。 */ /* 某字号是否存在(命中原生面返回 1)。 */
int meow_font_has_size(int size); int meow_font_has_size(int size);
+32
View File
@@ -266,6 +266,38 @@ int main(void) {
CHECK(distinct == MEOW_MAX_SLOTS, "distinct capped at MEOW_MAX_SLOTS"); CHECK(distinct == MEOW_MAX_SLOTS, "distinct capped at MEOW_MAX_SLOTS");
} }
/* ---- (3) 字体档位分层:meow_font_reload_tier(大字库按档装载/切换)----
* 用同两套 stub(lat=common 模拟,cjk=gbk 模拟)+ 一个不会释放的 cjk 大面,验证
* tier>0 装载大字库、切换 unload 旧/load 新、失败回退。 */
printf("\n== 字体档位分层 meow_font_reload_tier ==\n");
/* 档位 0=common/1=gbk/2=full(与 meow_ui.h MEOW_FONT_TIER_* 一致;本测试只链 meow_font.h) */
r = meow_font_reload_tier(0, "/tmp/mf_lat.png", "/tmp/mf_lat.idx");
CHECK(r == 0, "reload_tier(common, lat) ok");
CHECK(meow_font_has_glyph('A') == 1, "reload common -> has_glyph(A)=1");
CHECK(meow_font_has_glyph(0x4E2D) == 0, "reload common -> has_glyph(中)=0 (lat no cjk)");
CHECK(meow_font_width_text("A", 20) == 10, "reload common -> w(A)=10");
/* 切到大字库(cjk 槽):应 unload 旧 lat 再 load cjk → '中' 可用、'A' 缺。 */
r = meow_font_reload_tier(1, "/tmp/mf_cjk.png", "/tmp/mf_cjk.idx");
CHECK(r == 0, "reload_tier(gbk, cjk) ok");
CHECK(meow_font_has_glyph(0x4E2D) == 1, "reload gbk -> has_glyph(中)=1");
CHECK(meow_font_has_glyph('A') == 0, "reload gbk -> has_glyph(A)=0 (cjk lacks lat)");
CHECK(meow_font_width_text("中", 20) == 30, "reload gbk -> w(中)=30 (cjk slot)");
CHECK(meow_font_line_height(20) == 33, "reload gbk -> lh(20)=33 (cjk ascent25+desc8)");
/* 切回 common:再 unload cjk / load lat → 'A' 回来。 */
r = meow_font_reload_tier(0, "/tmp/mf_lat.png", "/tmp/mf_lat.idx");
CHECK(r == 0, "reload_tier(common) again ok");
CHECK(meow_font_has_glyph('A') == 1, "reload back common -> has_glyph(A)=1");
CHECK(meow_font_has_glyph(0x4E2D) == 0, "reload back common -> has_glyph(中)=0");
CHECK(meow_font_width_text("A", 20) == 10, "reload back common -> w(A)=10");
/* Full 档(tier=2)同一套 cjk 作为大字库装载也应成功(档仅记录,装载同路径)。 */
r = meow_font_reload_tier(2, "/tmp/mf_cjk.png", "/tmp/mf_cjk.idx");
CHECK(r == 0, "reload_tier(full, cjk) ok");
CHECK(meow_font_has_glyph(0x4E2D) == 1, "reload full -> has_glyph(中)=1");
/* 非法参数:NULL/空路径 → 负值(不崩),且字库已被上次 reload 释放(档位装载失败不驻留)。 */
r = meow_font_reload_tier(1, NULL, NULL);
CHECK(r < 0, "reload_tier(NULL paths) -> negative");
CHECK(meow_font_has_glyph('A') == 0, "reload failed -> font unloaded (has_glyph=0)");
printf("\n%s (%d failures)\n", g_fail ? "RESULT: FAIL" : "RESULT: PASS", g_fail); printf("\n%s (%d failures)\n", g_fail ? "RESULT: FAIL" : "RESULT: PASS", g_fail);
return g_fail ? 1 : 0; return g_fail ? 1 : 0;
} }
+9
View File
@@ -33,6 +33,15 @@ int meow_font_load(const char *png_path, const char *idx_path);
/* 释放字库。 */ /* 释放字库。 */
void meow_font_unload(void); 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)。 */ /* 某字号是否存在(命中原生面返回 1)。 */
int meow_font_has_size(int size); int meow_font_has_size(int size);
Binary file not shown.
+191 -45
View File
@@ -791,9 +791,14 @@ static void draw_text(int x, int y, const char *s, int scale, uint32_t color)
#define MEOW_FONTS_CFG "MeowOS/fonts" #define MEOW_FONTS_CFG "MeowOS/fonts"
#define FONT_PRESETS_MAX 17 /* 1 默认 + ≤16 卡内字体(防爆) */ #define FONT_PRESETS_MAX 17 /* 1 默认 + ≤16 卡内字体(防爆) */
static const char *font_presets[FONT_PRESETS_MAX]; /* SC_PRESET 值 = 卡根相对路径 */ static const char *font_presets[FONT_PRESETS_MAX]; /* SC_PRESET 值 = 卡根相对路径 */
static const char *font_preset_labels[FONT_PRESETS_MAX]; /* SC_PRESET 中文标签 */ static const char *font_preset_labels[FONT_PRESETS_MAX]; /* SC_PRESET 中文标签(含档位大小标记) */
/* M2.5x 字体档位:每套的 charset 档("common"/"gbk"/"full",取自 font.json,缺省 "common")
* 与 glyph_count(信息用)。供档位分层装载(meow_font_effective_paths_tiered)与设置页标注。 */
static const char *font_preset_tiers[FONT_PRESETS_MAX];
static int font_preset_glyphs[FONT_PRESETS_MAX];
static char font_preset_val_buf[FONT_PRESETS_MAX][320]; /* 值持久存储(指向上面指针数组) */ static char font_preset_val_buf[FONT_PRESETS_MAX][320]; /* 值持久存储(指向上面指针数组) */
static char font_preset_label_buf[FONT_PRESETS_MAX][64]; /* 标签持久存储 */ static char font_preset_label_buf[FONT_PRESETS_MAX][80]; /* 标签持久存储 */
static char font_preset_tier_buf[FONT_PRESETS_MAX][8]; /* charset 持久存储 */
static int font_presets_n = 0; /* 运行时动态计数(默认=1,仅"默认字体") */ static int font_presets_n = 0; /* 运行时动态计数(默认=1,仅"默认字体") */
static int mfont_ready = 0; static int mfont_ready = 0;
static int mfont_forced = 0; /* font.file 变更→强制真实(un)load,跳过 SDK 复用捷径 */ static int mfont_forced = 0; /* font.file 变更→强制真实(un)load,跳过 SDK 复用捷径 */
@@ -821,6 +826,11 @@ static int meow_font_resolve_path(const char *ref, char *out, int olen)
return 0; return 0;
} }
/* 前向声明:meow_font_ensure 在下方 font 区中调用(定义在 meow_font_effective_paths_tiered 处)。 */
static int meow_font_effective_tier(int app_tier);
static int meow_font_effective_paths_tiered(int tier, char *out_png, int plen,
char *out_idx, int ilen, char *out_family, int flen);
/* 解析 font.file 指向的 png + 派生同目录 idx(font.png → font.idx)。 /* 解析 font.file 指向的 png + 派生同目录 idx(font.png → font.idx)。
* 仅当指向的 png 存在(access R_OK)返回 0;否则 -1(交由回退链)。 * 仅当指向的 png 存在(access R_OK)返回 0;否则 -1(交由回退链)。
* 返回 0:out_png/out_idx 已填设备绝对路径。 */ * 返回 0:out_png/out_idx 已填设备绝对路径。 */
@@ -838,22 +848,25 @@ static int meow_font_resolve_selected(char *out_png, int png_len, char *out_idx,
return 0; return 0;
} }
/* 尝试加载字库;成功返回 1。优先级(M2.5x): /* 尝试加载字库;成功返回 1。优先级(M2.5x,font.load_full 已接入):
* 1) font.file(设置键,真正接管加载;相对路径按卡根解析)→ 存在且能 load 即用; * 0) 生效档 = font.load_full ? gbk : common(launcher 自身 UI 跟随系统大字库开关);
* 1) 生效字体(tier 解析,tier0 即 font.file 所选;相对路径按卡根解析);
* 2) 卡 MeowOS/fonts/default/font.png(默认字库); * 2) 卡 MeowOS/fonts/default/font.png(默认字库);
* 3) 固件 /usr/share/meowos/font/font.png。 * 3) 固件 /usr/share/meowos/font/font.png。
* A-1/A-2:SDK meow_ui_init() 已用同一 meow_font.o 加载过(可能成功也可能未成功)。 * A-1/A-2:SDK meow_ui_init() 已用同一 meow_font.o 加载过(可能成功也可能未成功)。
* 当 font.file 指向「默认卡字库」或未设置时复用 SDK 已加载状态,避免重复 unload+reload; * 当字体指向「默认卡字库」或未设置时复用 SDK 已加载状态,避免重复 unload+reload;
* 当 font.file 指向自定义字库时必须显式 load(SDK 只加载默认,不读 font.file)。 * 当字体指向自定义字库时必须显式 load(SDK 只加载默认,不读 font.file)。
* mfont_forced=1(font.file 变更触发)时无条件真实 reload,跳过上述两条捷径。 * mfont_forced=1(font.file/font.load_full 变更触发)时无条件真实 reload,跳过上述捷径。
* 全失败 → mfont_ready=0,ui_draw 回退 5x7 点阵(系统不死)。 */ * 全失败 → mfont_ready=0,ui_draw 回退 5x7 点阵(系统不死)。 */
static int meow_font_ensure(void) static int meow_font_ensure(void)
{ {
if (mfont_ready && !mfont_forced) return 1; if (mfont_ready && !mfont_forced) return 1;
mfont_forced = 0; mfont_forced = 0;
char fpng[320], fidx[320]; char fpng[400], fidx[400], fam[96];
int have_sel = (meow_font_resolve_selected(fpng, sizeof fpng, fidx, sizeof fidx) == 0); int tier = meow_font_effective_tier(MEOW_FONT_TIER_COMMON); /* launcher 自身 UI 跟随 load_full */
int have_sel = (meow_font_effective_paths_tiered(tier, fpng, sizeof fpng,
fidx, sizeof fidx, fam, sizeof fam) == 0);
/* SDK 已加载且所选字库为「默认卡字库」(或未设置)→ 复用,避免重复解码。 */ /* SDK 已加载且所选字库为「默认卡字库」(或未设置)→ 复用,避免重复解码。 */
if (meow_gfx_mfont_ready() && (!have_sel || !strcmp(fpng, UI_FONT_PNG_CARD))) { if (meow_gfx_mfont_ready() && (!have_sel || !strcmp(fpng, UI_FONT_PNG_CARD))) {
@@ -862,15 +875,15 @@ static int meow_font_ensure(void)
return 1; return 1;
} }
/* font.file 优先。 */ /* 生效字体优先(tier=common 时即 font.file;load_full → 大字库 gbk 档)。 */
if (have_sel && meow_font_load(fpng, fidx) == 0) { if (have_sel && meow_font_load(fpng, fidx) == 0) {
mfont_ready = 1; mfont_ready = 1;
logmsg("meow_font: loaded font.file %s (%d/%d sizes)", logmsg("meow_font: loaded %s (%d/%d sizes), tier=%d", fpng,
fpng, meow_font_has_size(28), meow_font_has_size(20)); meow_font_has_size(28), meow_font_has_size(20), tier);
return 1; return 1;
} }
if (have_sel) if (have_sel)
logmsg("meow_font: font.file %s load failed, fallback", fpng); logmsg("meow_font: %s load failed, fallback", fpng);
/* 卡默认 → 固件。 */ /* 卡默认 → 固件。 */
if (meow_font_load(UI_FONT_PNG_CARD, UI_FONT_IDX_CARD) == 0 || if (meow_font_load(UI_FONT_PNG_CARD, UI_FONT_IDX_CARD) == 0 ||
meow_font_load(UI_FONT_PNG_SYS, UI_FONT_IDX_SYS) == 0) { meow_font_load(UI_FONT_PNG_SYS, UI_FONT_IDX_SYS) == 0) {
@@ -915,17 +928,79 @@ static int meow_font_effective_paths(char *out_png, int plen, char *out_idx, int
return -1; return -1;
} }
/* 【tier 选择入口,font 域占位】按字体档位取「当前生效字体」png/idx,供 launch_app 注入 /* 档位 → font.json charset 字符串名(common/gbk/full)。 */
* MEOW_FONT_PNG/IDX/APP(配合 MEOW_FONT_TIER)。基座只实现 tier=COMMON(=meow_font_effective_paths); static const char *font_tier_charset(int tier)
* tier>0 的大字库(GBK/Full)绝对路径由后续 font 代理据此演进填充(约定 MeowOS/fonts/<family>/ {
* <tier>/…,或 settings 键 font.tier_gbk / font.tier_full —— 见 MEOW_APP_SDK.md 字体分层)。 if (tier >= MEOW_FONT_TIER_FULL) return "full";
* 未实现前暂回退全局字体。返回 0 成功,-1 无可用。 */ if (tier >= MEOW_FONT_TIER_GBK) return "gbk";
return "common";
}
/* 生效档:app 声明档(0/1/2,越界 clamp)合并「加载大字库」开关(font.load_full)。
* 开 → 强制 ≥GBK(gbk≈20k 档大字库);关(默认)→ 保持 app 声明(缺省 common≈4k)。
* 断言:0<=返回<=2。 */
static int meow_font_effective_tier(int app_tier)
{
if (app_tier < MEOW_FONT_TIER_COMMON) app_tier = MEOW_FONT_TIER_COMMON;
if (app_tier > MEOW_FONT_TIER_FULL) app_tier = MEOW_FONT_TIER_FULL;
int lf = 0;
if (meow_settings_get_bool("font.load_full", &lf) == 0 && lf)
if (app_tier < MEOW_FONT_TIER_GBK) app_tier = MEOW_FONT_TIER_GBK;
return app_tier;
}
/* 【tier 选择入口,M2.5x 已实现】按字体档位取「当前生效字体」png/idx,供 launch_app 注入
* MEOW_FONT_PNG/IDX/APP(配合 MEOW_FONT_TIER)。口径(MEOWOS_SPEC_REF §5.2/§5.7.8):
* - 生效档 = max(app 声明档, font.load_full ? GBK : COMMON):系统「加载大字库」开 →
* 强制 ≥gbk;
* - tier<=common → 走既有 meow_font_effective_paths(font.file/卡默认/固件);
* - tier>0 → 在已扫描的 MeowOS/fonts/<family>/ 里找 charset 匹配(gbk 请求接受 gbk/full;
* full 请求接受 full)的那套,以其 font.png/idx 绝对路径返回(family = 目录名);
* - 找不到匹配的大字库 → 回退 meow_font_effective_paths(避免 app 直接点到固件小库)。
* 返回 0 成功(out_png/out_idx/out_family 已填),-1 无可用。 */
static int meow_font_effective_paths_tiered(int tier, char *out_png, int plen, static int meow_font_effective_paths_tiered(int tier, char *out_png, int plen,
char *out_idx, int ilen, char *out_family, int flen) char *out_idx, int ilen, char *out_family, int flen)
{ {
if (tier == MEOW_FONT_TIER_COMMON || tier < MEOW_FONT_TIER_COMMON) int eff = meow_font_effective_tier(tier); /* 合并 font.load_full:开 → 强制 ≥GBK */
if (eff <= MEOW_FONT_TIER_COMMON)
return meow_font_effective_paths(out_png, plen, out_idx, ilen, out_family, flen); return meow_font_effective_paths(out_png, plen, out_idx, ilen, out_family, flen);
/* TODO(font 代理):按 tier 解析大字库绝对路径(GBK/Full);暂回退全局(tier0)。 */ /* 大字库:从扫描结果选 charset 匹配的一套。
* 打分(高优先):gbk 请求 → gbk=2 > full=1;full 请求 → full=2 > gbk=1(降级可接受)。 */
const char *want = font_tier_charset(eff);
char ref[320];
int found_family = -1, best_score = 0;
for (int i = 0; i < font_presets_n; i++) {
const char *cs = font_preset_tiers[i] ? font_preset_tiers[i] : "common";
int score = 0;
if (!strcmp(cs, want)) score = 2;
else if (!strcmp(cs, "full") || !strcmp(cs, "gbk")) score = 1; /* 降级 */
if (score > best_score) { best_score = score; found_family = i; }
}
if (found_family >= 0)
snprintf(ref, sizeof ref, "%s", font_presets[found_family]);
if (found_family >= 0) {
/* 相对路径按卡根解析为设备绝对路径(与 meow_font_effective_paths 同源)。 */
if (meow_font_resolve_path(ref, out_png, plen) != 0) return -1;
if (access(out_png, R_OK) != 0) goto fallback;
snprintf(out_idx, ilen, "%s", out_png);
size_t n = strlen(out_idx);
if (n > 4 && !strcmp(out_idx + n - 4, ".png")) {
out_idx[n-4] = '.'; out_idx[n-3] = 'i'; out_idx[n-2] = 'd';
out_idx[n-1] = 'x'; out_idx[n] = 0;
}
const char *prefix = "/MeowOS/fonts/";
const char *p = strstr(out_png, prefix);
if (p) {
const char *q = p + strlen(prefix);
const char *end = strrchr(q, '/');
int rest = end ? (int)(end - q) : 0;
if (rest > 0 && rest < flen) { memcpy(out_family, q, rest); out_family[rest] = 0; }
else snprintf(out_family, flen, "default");
} else snprintf(out_family, flen, "default");
return 0;
}
fallback:
/* 无匹配大字库/不可读 → 回退全局(font.file 所选;其可能是大字库或默认)。 */
return meow_font_effective_paths(out_png, plen, out_idx, ilen, out_family, flen); return meow_font_effective_paths(out_png, plen, out_idx, ilen, out_family, flen);
} }
@@ -942,7 +1017,10 @@ static void meow_font_reload_multifont(void)
int on = 0; int on = 0;
meow_settings_get_bool("display.multi_font", &on); meow_settings_get_bool("display.multi_font", &on);
char gpng[400], gidx[400], gfam[96]; char gpng[400], gidx[400], gfam[96];
if (meow_font_effective_paths(gpng, sizeof gpng, gidx, sizeof gidx, gfam, sizeof gfam) != 0) /* 全局跟生效档(合并 font.load_full → 大字库),与 meow_font_ensure 同源,避免槽0实际装载与
* 此处理解不一致(多字体去重以全局 png 为基准)。 */
if (meow_font_effective_paths_tiered(meow_font_effective_tier(MEOW_FONT_TIER_COMMON),
gpng, sizeof gpng, gidx, sizeof gidx, gfam, sizeof gfam) != 0)
return; /* 无全局字体:无法多字体 */ return; /* 无全局字体:无法多字体 */
/* 各类候选(静态缓冲保活;config_multifont 同步读取,不持指针) */ /* 各类候选(静态缓冲保活;config_multifont 同步读取,不持指针) */
static char cpng[MEOW_FCLS_COUNT][400], cidx[MEOW_FCLS_COUNT][400]; static char cpng[MEOW_FCLS_COUNT][400], cidx[MEOW_FCLS_COUNT][400];
@@ -1666,9 +1744,12 @@ static int font_sha_eq(const char *a, const char *b)
/* 解析 font.json:提取 name / sha256_png / sha256_idx(文件缺失或解析不出则留空)。 */ /* 解析 font.json:提取 name / sha256_png / sha256_idx(文件缺失或解析不出则留空)。 */
static void meow_font_json_probe(const char *json_path, char *name, int nlen, static void meow_font_json_probe(const char *json_path, char *name, int nlen,
char *sha_png, int slen, char *sha_idx, int xlen) char *sha_png, int slen, char *sha_idx, int xlen,
char *charset, int clen, int *glyph_count)
{ {
name[0] = 0; sha_png[0] = 0; sha_idx[0] = 0; name[0] = 0; sha_png[0] = 0; sha_idx[0] = 0;
if (charset) charset[0] = 0;
if (glyph_count) *glyph_count = 0;
FILE *f = fopen(json_path, "r"); FILE *f = fopen(json_path, "r");
if (!f) return; if (!f) return;
char buf[256]; char buf[256];
@@ -1676,14 +1757,39 @@ static void meow_font_json_probe(const char *json_path, char *name, int nlen,
if (!name[0]) json_str(buf, "name", name, nlen); if (!name[0]) json_str(buf, "name", name, nlen);
if (!sha_png[0]) json_str(buf, "sha256_png", sha_png, slen); if (!sha_png[0]) json_str(buf, "sha256_png", sha_png, slen);
if (!sha_idx[0]) json_str(buf, "sha256_idx", sha_idx, xlen); if (!sha_idx[0]) json_str(buf, "sha256_idx", sha_idx, xlen);
/* M2.5x 字体档位:charset(common|gbk|full)+ glyph_count(信息用,驱动档位分层/标注)。 */
if (charset && !charset[0]) {
char v[8] = "";
if (json_str(buf, "charset", v, sizeof v) > 0 && v[0])
snprintf(charset, clen, "%s", v);
}
if (glyph_count && *glyph_count == 0) {
char v[16] = "";
if (json_str(buf, "glyph_count", v, sizeof v) > 0 && v[0])
*glyph_count = atoi(v);
}
} }
fclose(f); fclose(f);
} }
/* 探测一个 <dirpath>/<family> 套件是否合法:三件套齐全 → 解析 label + 校验 sha。 /* 档位大小标记(设置页字体选项标注):common≈4k / gbk≈20k / full。
* 返回 1=进列表,0=跳过(缺组件/json 不可读)。sha 失败仅 WARN。 */ * 由 font.json 的 charset + glyph_count 派生;缺 charset 回退 common。 */
static void font_tier_marker(const char *charset, int glyph_count, char *out, int olen)
{
const char *tier = (charset && charset[0]) ? charset : "common";
if (!strcmp(tier, "full")) { snprintf(out, olen, "full"); return; }
if (glyph_count > 0)
snprintf(out, olen, "%s≈%dk", tier, (glyph_count + 500) / 1000);
else
snprintf(out, olen, "%s", tier);
}
/* 探测一个 <dirpath>/<family> 套件是否合法:三件套齐全 → 解析 label + 档位 + 校验 sha。
* 返回 1=进列表,0=跳过(缺组件/json 不可读)。sha 失败仅 WARN。
* 输出:label=显示名+大小标记 `名字(common≈4k)`;charset/glyph_count=档位信息。 */
static int meow_font_probe_family(const char *dirpath, const char *family, static int meow_font_probe_family(const char *dirpath, const char *family,
char *label, int llen) char *label, int llen,
char *charset, int clen, int *glyph_count)
{ {
char ppath[400], ipath[400], jpath[400]; char ppath[400], ipath[400], jpath[400];
snprintf(ppath, sizeof ppath, "%s/font.png", dirpath); snprintf(ppath, sizeof ppath, "%s/font.png", dirpath);
@@ -1694,9 +1800,15 @@ static int meow_font_probe_family(const char *dirpath, const char *family,
return 0; return 0;
} }
char name[64] = "", jsha_png[80] = "", jsha_idx[80] = ""; char name[64] = "", jsha_png[80] = "", jsha_idx[80] = "";
char cs[8] = ""; int gc = 0;
meow_font_json_probe(jpath, name, sizeof name, jsha_png, sizeof jsha_png, meow_font_json_probe(jpath, name, sizeof name, jsha_png, sizeof jsha_png,
jsha_idx, sizeof jsha_idx); jsha_idx, sizeof jsha_idx, cs, sizeof cs, &gc);
snprintf(label, llen, "%s", name[0] ? name : family); /* 显示名:name 或回退目录名 */ /* 显示名 = 名 + 档位大小标记(如 `default(common≈4k)` / `gbk≈20k` / `full`) */
char marker[24];
font_tier_marker(cs, gc, marker, sizeof marker);
snprintf(label, llen, "%s(%s)", name[0] ? name : family, marker);
if (charset) snprintf(charset, clen, "%s", cs[0] ? cs : "common");
if (glyph_count) *glyph_count = gc;
/* 完整性校验:仅 WARN,不硬失败;该套仍标记可选。 */ /* 完整性校验:仅 WARN,不硬失败;该套仍标记可选。 */
char apng[80] = "", aidx[80] = ""; char apng[80] = "", aidx[80] = "";
if (font_sha256_file(ppath, apng, sizeof apng) != 0) if (font_sha256_file(ppath, apng, sizeof apng) != 0)
@@ -1707,7 +1819,7 @@ static int meow_font_probe_family(const char *dirpath, const char *family,
logmsg("fonts: '%s' sha256(idx) 不可用,跳过校验", family); logmsg("fonts: '%s' sha256(idx) 不可用,跳过校验", family);
else if (jsha_idx[0] && !font_sha_eq(aidx, jsha_idx)) else if (jsha_idx[0] && !font_sha_eq(aidx, jsha_idx))
logmsg("fonts: '%s' WARN sha256_idx 不匹配(仍可选)", family); logmsg("fonts: '%s' WARN sha256_idx 不匹配(仍可选)", family);
logmsg("fonts: add '%s' (%s)", label, family); logmsg("fonts: add '%s' (%s) charset=%s glyph_count=%d", label, family, cs[0]?cs:"common", gc);
return 1; return 1;
} }
@@ -1717,7 +1829,20 @@ static void meow_font_scan_presets_root(const char *card_root)
{ {
font_presets_n = 0; font_presets_n = 0;
font_presets[0] = "MeowOS/fonts/default/font.png"; /* 默认字库 = MeowOS/fonts/default */ font_presets[0] = "MeowOS/fonts/default/font.png"; /* 默认字库 = MeowOS/fonts/default */
font_preset_labels[0] = "默认字体"; font_preset_labels[0] = font_preset_label_buf[0]; /* 指向缓冲(运行时派生含大小标记) */
/* 默认套也读 font.json 档位(存在则跟真实档,缺则 common);档位信息供分层装载/标注。 */
{
char jp[400], cs[8] = ""; int gc = 0;
char nm0[64] = "", sp0[80] = "", si0[80] = "";
snprintf(jp, sizeof jp, "%s/%s/default/font.json", card_root, MEOW_FONTS_CFG);
meow_font_json_probe(jp, nm0, sizeof nm0, sp0, sizeof sp0, si0, sizeof si0,
cs, sizeof cs, &gc);
char mk[24]; font_tier_marker(cs, gc, mk, sizeof mk);
snprintf(font_preset_label_buf[0], sizeof font_preset_label_buf[0], "默认字体(%s)", mk);
snprintf(font_preset_tier_buf[0], sizeof font_preset_tier_buf[0], "%s", cs[0]?cs:"common");
font_preset_tiers[0] = font_preset_tier_buf[0];
font_preset_glyphs[0] = gc;
}
font_presets_n = 1; font_presets_n = 1;
char root[340]; char root[340];
snprintf(root, sizeof root, "%s/%s", card_root, MEOW_FONTS_CFG); snprintf(root, sizeof root, "%s/%s", card_root, MEOW_FONTS_CFG);
@@ -1726,16 +1851,20 @@ static void meow_font_scan_presets_root(const char *card_root)
struct dirent *de; struct dirent *de;
while ((de = readdir(d)) && font_presets_n < FONT_PRESETS_MAX) { while ((de = readdir(d)) && font_presets_n < FONT_PRESETS_MAX) {
if (de->d_name[0] == '.') continue; if (de->d_name[0] == '.') continue;
char dirpath[400], label[64]; char dirpath[400], label[80], cs[8] = ""; int gc = 0;
snprintf(dirpath, sizeof dirpath, "%s/%s", root, de->d_name); snprintf(dirpath, sizeof dirpath, "%s/%s", root, de->d_name);
if (meow_font_probe_family(dirpath, de->d_name, label, sizeof label) == 0) if (meow_font_probe_family(dirpath, de->d_name, label, sizeof label,
cs, sizeof cs, &gc) == 0)
continue; /* 缺组件/损坏 → 跳过该套 */ continue; /* 缺组件/损坏 → 跳过该套 */
int i = font_presets_n; int i = font_presets_n;
snprintf(font_preset_val_buf[i], sizeof font_preset_val_buf[i], snprintf(font_preset_val_buf[i], sizeof font_preset_val_buf[i],
"%s/%s/font.png", MEOW_FONTS_CFG, de->d_name); "%s/%s/font.png", MEOW_FONTS_CFG, de->d_name);
snprintf(font_preset_label_buf[i], sizeof font_preset_label_buf[i], "%s", label); snprintf(font_preset_label_buf[i], sizeof font_preset_label_buf[i], "%s", label);
snprintf(font_preset_tier_buf[i], sizeof font_preset_tier_buf[i], "%s", cs[0]?cs:"common");
font_presets[i] = font_preset_val_buf[i]; font_presets[i] = font_preset_val_buf[i];
font_preset_labels[i] = font_preset_label_buf[i]; font_preset_labels[i] = font_preset_label_buf[i];
font_preset_tiers[i] = font_preset_tier_buf[i];
font_preset_glyphs[i] = gc;
font_presets_n++; font_presets_n++;
} }
closedir(d); closedir(d);
@@ -2321,17 +2450,14 @@ static void settings_sync_cache_listener(const char *key)
else if (!strcmp(key, "power.forceoff_ms")) { int v; if (meow_settings_get_int("power.forceoff_ms", &v) == 0) power_forceoff_ms = v; } else if (!strcmp(key, "power.forceoff_ms")) { int v; if (meow_settings_get_int("power.forceoff_ms", &v) == 0) power_forceoff_ms = v; }
} }
/* ---- 通知 dump 回调占位(通知#6 转储 TF 卡;接线/占位,不做 TF 写)---- /* ---- 通知 dump 回调(通知#6 转储 TF 卡;做成真)----
* meow_notif 在 TTL 到点(meow_notif_tick,§8 10s)调用此回调归档一条通知,随后释放槽。 * meow_notif 在 TTL 到点(meow_notif_tick,§8 10s)调用此回调归档一条通知,随后释放槽。
* 基座只把回调挂上来(见 settings_load 的 meow_notif_set_dump_cb);真实 TF 落盘 * 本回调把 meow_notif 结构体转成一行 CSV 追加写入 <卡>/MeowOS/notifications/notifications.log
* (按 NOTIFICATION_SYSTEM.md §8 / TF_CARD_LAYOUT.md 写 <卡>/MeowOS/notify/...)由后续 * (含 mkdir -p 与按总大小滚动截断、保留最新 10MB),落盘实现在 meow_notif.c 的
* 通知代理在回调体内实现。注意 meow_notif.c 的 archive_one 自身已 log 一行 "dump: ...", * meow_notif_dump_tf()。无卡/不可写仅打点降级(NOTIFICATION_SYSTEM.md §5/§8 + MEWSPEC §5.3)。 */
* 故本回调保持轻量(避免重复刷屏),落盘逻辑填在下方 TODO 处。 */
static void notif_dump_tf_placeholder(const meow_notif_t *n) static void notif_dump_tf_placeholder(const meow_notif_t *n)
{ {
(void)n; meow_notif_dump_tf(n);
/* TODO(通知代理, M2c):在此写 TF 卡 <MeowOS>/notify/<date>/<id>.json 等;
* 基座不实现,仅保留挂接点(触发时机=meow_notif_tick 内 10s 到点/止机转储)。 */
} }
/* 启动加载(M2.11/M2.24):统一由 meow_settings 后端读 settings.conf; /* 启动加载(M2.11/M2.24):统一由 meow_settings 后端读 settings.conf;
@@ -2342,7 +2468,7 @@ static void settings_load(void)
{ {
meow_settings_init(); /* 幂等:注册默认 + 读 settings.conf */ meow_settings_init(); /* 幂等:注册默认 + 读 settings.conf */
meow_notif_init(); /* M2a:通知存储初始化(幂等,读 notify.* 配额) */ meow_notif_init(); /* M2a:通知存储初始化(幂等,读 notify.* 配额) */
meow_notif_set_dump_cb(notif_dump_tf_placeholder); /* 基座(通知#6):挂 dump 回调(TF 转储占位,M2c 落盘由后续代理实现) */ meow_notif_set_dump_cb(notif_dump_tf_placeholder); /* 通知#6:挂 dump 回调 = TF 转储(meow_notif_dump_tf:一行 CSV 追加 + 10MB 滚动截断) */
if (!settings_cache_registered) { if (!settings_cache_registered) {
meow_settings_add_listener(settings_sync_cache_listener); meow_settings_add_listener(settings_sync_cache_listener);
settings_cache_registered = 1; settings_cache_registered = 1;
@@ -4483,6 +4609,10 @@ static const srow_t SMENU[] = {
/* 字体主题 */ /* 字体主题 */
{ "字体主题", NULL, SC_HEADER, 0,0,0, NULL,NULL,0 }, { "字体主题", NULL, SC_HEADER, 0,0,0, NULL,NULL,0 },
{ "字体文件", "font.file", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 }, { "字体文件", "font.file", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 },
/* M2.5x 字体档位分层/按需大字库:「加载大字库」= font.load_full(int 0/1 默认 0)。
* 开 → 强制全系统用大字库 gbk≈20k 档(覆盖 app 未声明/覆盖默认 font.file);
* 关(默认)→ 小字库 common≈4k,仅 app 声明 fonts:"full" 才大字库。 */
{ "加载大字库", "font.load_full", SC_SWITCH, 0,0,0, NULL,NULL,0 },
/* M2.5x 多字体:开关 + 6 个字符类别字体(preset=可用 MeowOS/fonts/<family>/ 列表; /* M2.5x 多字体:开关 + 6 个字符类别字体(preset=可用 MeowOS/fonts/<family>/ 列表;
* 值空 = 跟随全局 font.file)。仅开关开启时 6 行可见(s_row_visible)。 */ * 值空 = 跟随全局 font.file)。仅开关开启时 6 行可见(s_row_visible)。 */
{ "多字体", "display.multi_font", SC_SWITCH, 0,0,0, NULL,NULL,0 }, { "多字体", "display.multi_font", SC_SWITCH, 0,0,0, NULL,NULL,0 },
@@ -5433,6 +5563,20 @@ static void s_commit(const srow_t *r, int save_now)
meow_font_reload_multifont(); /* M2.5x:全局字体变更 → 重配多字体(各类跟随全局者随之更新) */ meow_font_reload_multifont(); /* M2.5x:全局字体变更 → 重配多字体(各类跟随全局者随之更新) */
meow_dirty_full(); /* 字体变化可能影响所有文本渲染,整帧重绘 */ meow_dirty_full(); /* 字体变化可能影响所有文本渲染,整帧重绘 */
} }
/* M2.5x「加载大字库」开关(font.load_full)变更 → 强制重载 launcher 自身字体(小/大字库切换),
* 并重配多字体;大字库 atlas 大,meow_font_ensure 先 unload 旧再 load 新(防双驻留)。 */
else if (!strcmp(r->key, "font.load_full")) {
mfont_ready = 0;
mfont_forced = 1; /* 跳过 SDK 复用捷径,强制按生效档(gbk/common)真实 reload */
if (!meow_font_ensure())
logmsg("font.load_full: reload failed -> dotmatrix fallback");
else {
int lf = 0; meow_settings_get_bool("font.load_full", &lf);
logmsg("font.load_full: reloaded (load_full=%d)", lf);
}
meow_font_reload_multifont();
meow_dirty_full();
}
/* M2.5x 多字体:开关或任一类别字体变更 → 重新去重装载类别字库并重绘(多字体可能影响文本渲染)。 */ /* M2.5x 多字体:开关或任一类别字体变更 → 重新去重装载类别字库并重绘(多字体可能影响文本渲染)。 */
else if (!strcmp(r->key, "display.multi_font") || !strncmp(r->key, "font.class_", 11)) { else if (!strcmp(r->key, "display.multi_font") || !strncmp(r->key, "font.class_", 11)) {
meow_font_reload_multifont(); meow_font_reload_multifont();
@@ -6846,12 +6990,14 @@ static int launch_app(struct app *a)
meow_settings_get_bool("display.multi_font", &mf_on); meow_settings_get_bool("display.multi_font", &mf_on);
snprintf(env_mf, sizeof env_mf, "MEOW_MULTI_FONT=%d", mf_on ? 1 : 0); snprintf(env_mf, sizeof env_mf, "MEOW_MULTI_FONT=%d", mf_on ? 1 : 0);
envp[ne++] = env_mf; envp[ne++] = env_mf;
/* M2.x(基座):字库档 MEOW_FONT_TIER=<0|1|2> —— 按 app.json "fonts" 声明注入; /* M2.5x:字库档 MEOW_FONT_TIER=<0|1|2> —— 按 app.json "fonts" 声明注入,并合并
* 0=默认4K常用(未声明/缺省),1=大字库GBK,2=Full。SDK meow_ui_gfx 读入存档, * 「加载大字库」开关(font.load_full → 强制 ≥GBK)得**生效档**;0=默认4K常用,
* 真正按档装载大字库由后续 font 代理实现(见 meow_font_effective_paths_tiered)。 */ * 1=大字库GBK,2=Full。SDK meow_ui_gfx 读入存档并按档装载(见
snprintf(env_tier, sizeof env_tier, "MEOW_FONT_TIER=%d", a->fonts); * meow_font_effective_paths_tiered;真正装载由 meow_font_reload_tier 实现)。 */
int eff_tier = meow_font_effective_tier(a->fonts);
snprintf(env_tier, sizeof env_tier, "MEOW_FONT_TIER=%d", eff_tier);
envp[ne++] = env_tier; envp[ne++] = env_tier;
have_global = (meow_font_effective_paths_tiered(a->fonts, fpng, sizeof fpng, have_global = (meow_font_effective_paths_tiered(eff_tier, fpng, sizeof fpng,
fidx, sizeof fidx, fam, sizeof fam) == 0); fidx, sizeof fidx, fam, sizeof fam) == 0);
if (have_global) { if (have_global) {
snprintf(env_fpng, sizeof env_fpng, "MEOW_FONT_PNG=%s", fpng); snprintf(env_fpng, sizeof env_fpng, "MEOW_FONT_PNG=%s", fpng);
+196 -1
View File
@@ -24,6 +24,11 @@
#include <strings.h> /* strcasecmp */ #include <strings.h> /* strcasecmp */
#include <stdarg.h> #include <stdarg.h>
#include <time.h> #include <time.h>
#include <sys/stat.h> /* mkdir / stat */
#include <sys/types.h>
#include <unistd.h> /* open/close/write/pread/lseek/ftruncate */
#include <fcntl.h> /* O_* */
#include <errno.h> /* errno/strerror */
#include "meow_notif.h" #include "meow_notif.h"
#include "meow_settings.h" /* B2a:读 notify.* 配额(MEOW_OK/MEOW_ERR_*) */ #include "meow_settings.h" /* B2a:读 notify.* 配额(MEOW_OK/MEOW_ERR_*) */
@@ -89,6 +94,192 @@ static void n_strncpy(char *dst, const char *src, size_t len)
dst[len - 1] = 0; dst[len - 1] = 0;
} }
/* ================= TF 卡转储(通知#6 / M2c,NOTIFICATION_SYSTEM.md §5/§8)=================
* 每条通知一行 CSV 追加到 <卡根>/MeowOS/notifications/notifications.log,并按日志总大小
* 滚动截断(保留最新 10MB)。系统文件统一放 <卡根>/MeowOS/ 下(TF_CARD_LAYOUT.md)。 */
#define MEOW_NOTIF_TF_DIR "/usr/data/mnt/sd_0/MeowOS/notifications"
#define MEOW_NOTIF_TF_LOG MEOW_NOTIF_TF_DIR "/notifications.log"
#define MEOW_NOTIF_LOG_MAX_BYTES ((size_t)10 * 1024 * 1024) /* §8:保留最新 10MB */
/* 单行 CSV 上限:app_id(32)/category(24)/summary(64)/body(96)/target_activity(48)
* 全引号转义后仍远小于此。 */
#define MEOW_NOTIF_CSV_LINE_MAX 1024
static const char *g_tf_path = MEOW_NOTIF_TF_LOG;
static size_t g_tf_max = MEOW_NOTIF_LOG_MAX_BYTES;
/* 递归 mkdir -p:沿 path 把每一级目录建出来(EEXIST 无害;无卡/只读则静默失败) */
static void tf_mkdirs(const char *path)
{
char tmp[512];
n_strncpy(tmp, path, sizeof tmp);
for (char *p = tmp + 1; *p; p++) {
if (*p == '/') {
*p = '\0';
mkdir(tmp, 0755);
*p = '/';
}
}
}
/* 全量写入(处理短写/EINTR) */
static int tf_write_all(int fd, const char *buf, size_t len)
{
size_t off = 0;
while (off < len) {
ssize_t w = write(fd, buf + off, len - off);
if (w < 0) {
if (errno == EINTR) continue;
return -1;
}
off += (size_t)w;
}
return 0;
}
/* CSV 字段:去换行(\r\n->空格,保证单行)+ 转义(双引号包裹,内部 " 翻倍) */
static void csv_field(char *dst, size_t dstsz, const char *src)
{
if (!dst || dstsz == 0) return;
size_t o = 0;
dst[o++] = '"';
for (const char *p = src ? src : ""; *p; p++) {
if (o + 4 > dstsz) break; /* 留 2 字节给翻倍引号 + 收尾引号 + NUL */
if (*p == '\r' || *p == '\n') dst[o++] = ' '; /* 去换行 */
else if (*p == '"') { dst[o++] = '"'; dst[o++] = '"'; }
else dst[o++] = *p;
}
if (o + 1 >= dstsz) o = dstsz - 1;
dst[o++] = '"';
dst[o] = '\0';
}
/* 生成一行 CSV:
* timestamp,id,app_id,category,urgency,title,body,target_activity,ongoing,media,dismissed
* title<-summary;urgency:meow_notif 结构无该字段(保持既有 API),列值留空 ""。
* 返回写入长度(不含 NUL)。 */
static size_t tf_line(const meow_notif_t *n, char *buf, size_t bufsz)
{
char a_app [2 + 2 * MEOW_NOTIF_APP_ID_MAX + 2];
char a_cat [2 + 2 * MEOW_NOTIF_CAT_MAX + 2];
char a_sum [2 + 2 * MEOW_NOTIF_SUMMARY_MAX + 2];
char a_body[2 + 2 * MEOW_NOTIF_BODY_MAX + 2];
char a_tgt [2 + 2 * MEOW_NOTIF_ACTIVITY_MAX + 2];
csv_field(a_app, sizeof a_app, n->app_id);
csv_field(a_cat, sizeof a_cat, n->category);
csv_field(a_sum, sizeof a_sum, n->summary);
csv_field(a_body, sizeof a_body, n->body);
csv_field(a_tgt, sizeof a_tgt, n->target_activity);
int r = snprintf(buf, bufsz,
"%llu,%u,%s,%s,\"\",%s,%s,%s,%u,%u,%u\n",
(unsigned long long)n->created_ms,
(unsigned)n->id,
a_app, a_cat, a_sum, a_body, a_tgt,
n->ongoing ? 1u : 0u,
n->media ? 1u : 0u,
n->dismissed ? 1u : 0u);
if (r < 0) return 0;
return ((size_t)r < bufsz) ? (size_t)r : bufsz - 1;
}
/* 滚动截断:文件超过 g_tf_max 时删除最旧行(从尾部读窗口定位第一条换行作为保留起点),
* 把结尾 ≤max 字节的完整行重写回文件头。窗口内无换行则保留整窗(只保证 ≤max)。 */
static int tf_trim(const char *path)
{
int fd = open(path, O_RDWR);
if (fd < 0) return -1;
struct stat st;
if (fstat(fd, &st) != 0) { close(fd); return -1; }
off_t size = st.st_size;
if (size <= (off_t)g_tf_max) { close(fd); return 0; }
off_t start = size - (off_t)g_tf_max; /* 保留窗口起点 */
off_t keep = start; /* 最终保留起点(行对齐后) */
int found = 0;
char chunk[8192];
for (off_t pos = start; pos < size && !found; ) {
size_t want = (size_t)((size - pos) < (off_t)sizeof chunk ? (size - pos) : (off_t)sizeof chunk);
ssize_t rd = pread(fd, chunk, want, pos);
if (rd <= 0) break;
for (ssize_t i = 0; i < rd; i++) {
if (chunk[i] == '\n') { keep = pos + i + 1; found = 1; break; }
}
pos += rd;
if (rd < (ssize_t)want) break; /* 读到文件尾 */
}
/* 重写:把 [keep, size) 拷贝回文件头,再 ftruncate 掉尾部旧数据 */
off_t remain = size - keep;
if (lseek(fd, 0, SEEK_SET) < 0) { close(fd); return -1; }
off_t wpos = 0;
char buf[8192];
while (remain > 0) {
size_t want = (size_t)(remain < (off_t)sizeof buf ? remain : (off_t)sizeof buf);
ssize_t rd = pread(fd, buf, want, keep + wpos);
if (rd <= 0) break;
if (tf_write_all(fd, buf, (size_t)rd) < 0) { close(fd); return -1; }
wpos += rd;
remain -= rd;
}
ftruncate(fd, wpos);
close(fd);
return 0;
}
/* 把一条通知追加为一行 CSV 到 TF 日志(含 mkdir -p 与 10MB 滚动截断)。
* 返回 NOTIF_OK(0);无卡/不可写/参数非法返回负(降级:仅打点,不崩)。 */
int meow_notif_dump_tf(const meow_notif_t *n)
{
if (!n) return NOTIF_ERR_INVAL;
if (!g_tf_path || !g_tf_path[0]) return NOTIF_ERR_INVAL;
tf_mkdirs(g_tf_path); /* 目录不存在则 mkdir -p*/
char line[MEOW_NOTIF_CSV_LINE_MAX];
size_t len = tf_line(n, line, sizeof line);
if (len == 0) return NOTIF_ERR_INVAL;
int fd = open(g_tf_path, O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd < 0) {
notif_log("tf: append open fail %s (%s)", g_tf_path, strerror(errno));
return NOTIF_ERR_IO;
}
if (tf_write_all(fd, line, len) != 0) {
close(fd);
notif_log("tf: append write fail %s (%s)", g_tf_path, strerror(errno));
return NOTIF_ERR_IO;
}
long size = -1;
struct stat st;
if (fstat(fd, &st) == 0) size = (long)st.st_size;
close(fd);
if (size > (long)g_tf_max) { /* >10MB:滚动截断,保留最新 */
tf_trim(g_tf_path);
notif_log("tf: rolled to <= %zuB", g_tf_max);
}
return NOTIF_OK;
}
/* 开机清板载通知:清空转储日志文件,保留目录(通知#6 / §8:启动时清板载) */
void meow_notif_clear_tf(void)
{
if (!g_tf_path || !g_tf_path[0]) return;
tf_mkdirs(g_tf_path);
int fd = open(g_tf_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd >= 0) close(fd);
else notif_log("tf: clear fail %s (%s)", g_tf_path, strerror(errno));
}
/* 覆盖转储日志路径/滚动上限(宿主单测用;NULL/0 恢复默认) */
void meow_notif_set_tf_path(const char *path)
{
g_tf_path = (path && path[0]) ? path : MEOW_NOTIF_TF_LOG;
}
void meow_notif_set_tf_max(size_t max_bytes)
{
g_tf_max = max_bytes ? max_bytes : MEOW_NOTIF_LOG_MAX_BYTES;
}
/* ---- 配额读取(B2a;键缺省/取失败用默认值)---- */ /* ---- 配额读取(B2a;键缺省/取失败用默认值)---- */
static void read_quota(void) static void read_quota(void)
{ {
@@ -113,7 +304,11 @@ int meow_notif_init(void)
g_dumped_total = 0; g_dumped_total = 0;
read_quota(); /* B2a:notify.* 配额 */ read_quota(); /* B2a:notify.* 配额 */
g_inited = 1; g_inited = 1;
notif_log("init: slots=%d default dump_cb=log-only (M2c -> TF)", MEOW_NOTIF_MAX_SLOTS); /* 通知#6:启动清板载通知 + 内存表(内存表上面已 memset;这里清 TF 转储日志,
* 保留目录)。每会话从零开始(通知会话级),不被电源事件清(before_poweroff 只管内存)。 */
meow_notif_clear_tf();
notif_log("init: slots=%d tf_log=%s (dump_cb wired by launcher -> meow_notif_dump_tf)",
MEOW_NOTIF_MAX_SLOTS, g_tf_path);
return NOTIF_OK; return NOTIF_OK;
} }
+13 -1
View File
@@ -32,7 +32,8 @@ enum {
NOTIF_ERR_NOMEM = -6, /* 槽满/内存不足 */ NOTIF_ERR_NOMEM = -6, /* 槽满/内存不足 */
NOTIF_ERR_NOTINIT = -7, /* 未 init */ NOTIF_ERR_NOTINIT = -7, /* 未 init */
NOTIF_ERR_QUOTA = -12, /* 配额超限(App 类型数 / 类型条数 / 媒体数) */ NOTIF_ERR_QUOTA = -12, /* 配额超限(App 类型数 / 类型条数 / 媒体数) */
NOTIF_ERR_NOENT = -2 /* 通知 id 不存在 */ NOTIF_ERR_NOENT = -2, /* 通知 id 不存在 */
NOTIF_ERR_IO = -5 /* 文件/存储 IO 失败(TF 转储,无卡/不可写) */
}; };
/* == 容量/长度(单设备上限;定长,控制静态内存)== */ /* == 容量/长度(单设备上限;定长,控制静态内存)== */
@@ -118,6 +119,17 @@ void meow_notif_before_poweroff(void);
/* 覆盖转储回调(NULL=恢复默认日志;M2c 接 TF 落盘)。 */ /* 覆盖转储回调(NULL=恢复默认日志;M2c 接 TF 落盘)。 */
void meow_notif_set_dump_cb(meow_notif_dump_cb cb); void meow_notif_set_dump_cb(meow_notif_dump_cb cb);
/* == 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;urgency 结构无字段故列留空;均做 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 表(前缀)。 */ /* 调试:打印当前 Active 表/App 表(前缀)。 */
void meow_notif_dump(const char *log_prefix); void meow_notif_dump(const char *log_prefix);
+4
View File
@@ -104,6 +104,10 @@ static const meow_setting_def g_defs[] = {
{ "font.class_latin", SET_STR, "", 0,0, NULL, NULL }, { "font.class_latin", SET_STR, "", 0,0, NULL, NULL },
{ "font.class_latin_sym", SET_STR, "", 0,0, NULL, NULL }, { "font.class_latin_sym", SET_STR, "", 0,0, NULL, NULL },
{ "font.class_other", SET_STR, "", 0,0, NULL, NULL }, { "font.class_other", SET_STR, "", 0,0, NULL, NULL },
/* M2.5x 字体档位分层 / 按需大字库:font.load_full(int 0/1 默认 0)=「加载大字库」开关,
* 1=强制全系统用大字库 gbk≈20k 档(覆盖 app 未声明/覆盖默认 font.file);
* 0=默认小字库 common≈4k,仅 app 声明 fonts:"full" 才大字库。见 §5.2/§5.7.8。 */
{ "font.load_full", SET_INT, "0", 0, 1, NULL, NULL },
{ "font.size.title", SET_INT, "28", 0, 0, NULL, NULL }, { "font.size.title", SET_INT, "28", 0, 0, NULL, NULL },
{ "font.size.body", SET_INT, "20", 0, 0, NULL, NULL }, { "font.size.body", SET_INT, "20", 0, 0, NULL, NULL },
{ "ui.style", SET_ENUM, "md3", 0, 0, "md3,miui", "Material You,MIUI" }, { "ui.style", SET_ENUM, "md3", 0, 0, "md3,miui", "Material You,MIUI" },
+158
View File
@@ -0,0 +1,158 @@
/* test_notif_tf.c — 宿主单测:通知#6 TF 转储(CSV 转义 / 10MB 滚动截断 / init 清板载+内存表)。
*
* 直接 #include "meow_notif.c"(同 TU 平移其 static 常量/函数),stub 掉 meow_settings_get_int
* (配额读——返回负=键缺失即用默认,涵盖 CSV/截断/init 逻辑)。宿主 gcc 编译运行即可,不进固件。
*
* 运行:gcc -D_GNU_SOURCE -o test_notif_tf test_notif_tf.c && ./test_notif_tf
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
/* stub:meow_notif.c 的 read_quota() 读 notify.* 配额。<0 => 键缺失=> 用默认值 */
int meow_settings_get_int(const char *key, int *out)
{
(void)key; (void)out;
return -2; /* MEOW_ERR_NOENT */
}
#include "meow_notif.c" /* 把全部实现(含 statics)收进本 TU */
static int g_fail = 0;
#define CHECK(cond, msg) do { \
if (!(cond)) { fprintf(stderr, "FAIL: %s (%s:%d)\n", msg, __FILE__, __LINE__); g_fail++; } \
else fprintf(stderr, "ok: %s\n", msg); \
} while (0)
static char *read_all(const char *path, long *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);
char *s = malloc((size_t)n + 1);
if (!s) { fclose(f); return NULL; }
size_t got = fread(s, 1, (size_t)n, f);
s[got] = 0;
fclose(f);
if (out_len) *out_len = n;
return s;
}
static void make_notif(meow_notif_t *n, uint32_t id, const char *sum, const char *body)
{
memset(n, 0, sizeof *n);
n->id = id;
n->created_ms = 1234567890123ULL;
strcpy(n->app_id, "com.app");
strcpy(n->category, "general");
strcpy(n->summary, sum);
strcpy(n->body, body);
strcpy(n->target_activity, "act");
n->ongoing = 1;
}
/* --- 1) CSV 转义 + 去换行 --- */
static void test_csv_escape(const char *path)
{
meow_notif_set_tf_path(path);
meow_notif_set_tf_max(1024 * 1024); /* 放大上限避免此测触发截断 */
meow_notif_t n;
/* summary: 逗号 + 双引号 + 换行;body: CRLF + tab */
make_notif(&n, 42, "Hel\"lo, world\nline2", "Body\r\nwith\ttab");
n.media = 1; /* 让 media 列=1 */
CHECK(meow_notif_dump_tf(&n) == 0, "dump_tf returns 0");
char *s = read_all(path, NULL);
CHECK(s != NULL, "file exists after dump");
const char *expect =
"1234567890123,42,\"com.app\",\"general\",\"\",\"Hel\"\"lo, world line2\","
"\"Body with\ttab\",\"act\",1,1,0\n"; /* ongoing=1 media=1 dismissed=0 */
CHECK(s && strcmp(s, expect) == 0, "CSV line matches expected (escape+newline-strip)");
if (s) { fprintf(stderr, " got: %s", s); fprintf(stderr, " exp: %s", expect); }
free(s);
}
/* --- 2) 10MB 滚动截断(假文件:把上限压到很小模拟)--- */
static void test_trim(const char *path)
{
meow_notif_set_tf_path(path);
meow_notif_set_tf_max(256); /* 用 256B 模拟 10MB 上限 */
/* 先写 4 条不同标记,让文件超过 256B */
meow_notif_t n;
make_notif(&n, 1, "lineAAAA", "");
meow_notif_dump_tf(&n);
make_notif(&n, 2, "lineBBBB", "");
meow_notif_dump_tf(&n);
make_notif(&n, 3, "lineCCCC", "");
meow_notif_dump_tf(&n);
make_notif(&n, 4, "lineDDDD", "");
meow_notif_dump_tf(&n);
long len = 0;
char *s = read_all(path, &len);
CHECK(s != NULL && len <= 256, "after roll, file size <= limit (10MB analog)");
CHECK(s && strstr(s, "lineDDDD") != NULL, "newest row retained after roll");
CHECK(s && strstr(s, "lineAAAA") == NULL, "oldest row rolled away");
if (s) fprintf(stderr, " rolled content (%ldB):\n%s", len, s);
/* 追加一条仍 0 成功 */
make_notif(&n, 5, "lineEEEE", "");
CHECK(meow_notif_dump_tf(&n) == 0, "append after roll ok");
free(s);
}
/* --- 3) init 清板载 + 清内存表,且 post 正常 --- */
static void test_init_clear(const char *path)
{
meow_notif_set_tf_path(path);
meow_notif_set_tf_max(1024 * 1024);
/* 先塞点板载内容 */
meow_notif_t n;
make_notif(&n, 99, "junk", "");
meow_notif_dump_tf(&n);
/* init:应清板载文件 + 清内存表 */
CHECK(meow_notif_init() == NOTIF_OK, "meow_notif_init ok");
long len = -1;
char *s = read_all(path, &len);
CHECK(s != NULL && len == 0, "board log cleared at init (dir kept)");
CHECK(meow_notif_count(NULL) == 0, "memory table cleared at init");
/* init 后 post 一条,应活跃 + 可转储 */
make_notif(&n, 0, "fresh", "");
uint32_t id = 0;
CHECK(meow_notif_post(&n, &id) == NOTIF_OK, "post after init ok");
CHECK(id != 0 && meow_notif_count(NULL) == 1, "one active notif after post");
free(s);
}
int main(void)
{
char tmp[] = "/tmp/meow_notif_tf_test_XXXXXX";
char *dir = mkdtemp(tmp);
if (!dir) { perror("mkdtemp"); return 2; }
char p1[512], p2[512], p3[512];
snprintf(p1, sizeof p1, "%s/csv.log", dir);
snprintf(p2, sizeof p2, "%s/trim.log", dir);
snprintf(p3, sizeof p3, "%s/init.log", dir);
CHECK(p1[0] != 0, "tmpdir ready");
test_csv_escape(p1);
test_trim(p2);
test_init_clear(p3);
/* 清理:删除日志文件 + 目录(保底) */
unlink(p1); unlink(p2); unlink(p3); rmdir(dir);
fprintf(stderr, "\n%s (failures=%d)\n", g_fail ? "== TEST FAILED ==" : "== TEST PASSED ==", g_fail);
return g_fail ? 1 : 0;
}
+8 -6
View File
@@ -328,22 +328,24 @@ const char *meow_notify_urgency_str(meow_notify_urgency u);
const char *meow_notify_category_str(meow_notify_category cat); const char *meow_notify_category_str(meow_notify_category cat);
/* ============================================================ */ /* ============================================================ */
/* §10 字体分层 / 大字库(草案/待定案 —— 本批仅加声明/枚举/宏,不实现) */ /* §10 字体分层 / 大字库(M2.5x:基座声明 + font 域已实现按档装载) */
/* ============================================================ */ /* ============================================================ */
/* 字体「档位」概念:launcher 按 app.json 声明的字库档("fonts":<档>)向 app 注入 /* 字体「档位」概念:launcher 按 app.json 声明的字库档("fonts":<档>)向 app 注入
* MEOW_FONT_TIER=<0|1|2>;SDK(meow_ui_gfx / 后续 meow_font 大字库加载)据档选库: * MEOW_FONT_TIER=<0|1|2>;SDK(meow_ui_gfx / meow_font 大字库加载)据档选库:
* MEOW_FONT_TIER_COMMON=0 默认 4K 常用字库; * MEOW_FONT_TIER_COMMON=0 默认 4K 常用字库;
* MEOW_FONT_TIER_GBK =1 大字库 GBK; * MEOW_FONT_TIER_GBK =1 大字库 GBK;
* MEOW_FONT_TIER_FULL =2 Full(全字符集)。 * MEOW_FONT_TIER_FULL =2 Full(全字符集)。
* 后续 font 域代理据此在 meow_font.c / gen_font.py 实现大字库生成与按档装载。 * 大字库生成由 font/gen/gen_font.py --charset 按档产出;按档装载由 meow_font_reload_tier()
* 既有 meow_font_* 符号(class_of/has_glyph/load_slot/config_multifont…)保留不动; * 实现(先 unload 旧再 load 新,释放旧 atlas 防大字库双驻留)。launcher 经
* 大字库装载以 set_tier/tier 作为入口,或由后续代理按 meow_font_config_multifont 扩展。 */ * meow_font_effective_paths_tiered() 按档解析大字库绝对路径并注入 MEOW_FONT_PNG/IDX。
* 既有 meow_font_* 符号(class_of/has_glyph/load_slot/config_multifont…)保留不动。
* 判缺字:meow_font_has_glyph(cp) 预判当前字库是否含该码位。 */
#define MEOW_FONT_TIER_COMMON 0 #define MEOW_FONT_TIER_COMMON 0
#define MEOW_FONT_TIER_GBK 1 #define MEOW_FONT_TIER_GBK 1
#define MEOW_FONT_TIER_FULL 2 #define MEOW_FONT_TIER_FULL 2
/* 设当前字体档(0/1/2,越界 clamp 到合法值),返回生效档。 /* 设当前字体档(0/1/2,越界 clamp 到合法值),返回生效档。
* 基座在 meow_ui_gfx.c 定义(只存档 + 触发重载请求);真正按档装载大字库由后续 font 代理实现。 */ * meow_ui_gfx.c 定义并触发 meow_font_reload_tier() 真重载(大字库切换释放旧 atlas)。 */
int meow_font_set_tier(int tier); int meow_font_set_tier(int tier);
/* 当前字体档(初始 = MEOW_FONT_TIER_COMMON;meow_ui_init 时读 MEOW_FONT_TIER env 对齐)。 */ /* 当前字体档(初始 = MEOW_FONT_TIER_COMMON;meow_ui_init 时读 MEOW_FONT_TIER env 对齐)。 */
int meow_font_tier(void); int meow_font_tier(void);
+23 -11
View File
@@ -77,8 +77,8 @@ static unsigned long long g_sbar_collapse_start_ms = 0; /* 本次过渡起始
/* ---- 字体档位(MEOW_FONT_TIER,基座存档 + 公共 get/set)---- /* ---- 字体档位(MEOW_FONT_TIER,基座存档 + 公共 get/set)----
* launcher 按 app.json "fonts":<档> 注入 MEOW_FONT_TIER=<0|1|2>;SDK 读入并以 * launcher 按 app.json "fonts":<档> 注入 MEOW_FONT_TIER=<0|1|2>;SDK 读入并以
* meow_font_set_tier/tier 暴露。真正按档装载大字库(GBK/Full)由后续 font 代理在 * meow_font_set_tier/tier 暴露。按档装载大字库(GBK/Full)由 meow_font_reload_tier()
* meow_font.c / meow_gfx_load_multifont 挂钩实现——此处仅记录档位不换库。 */ * 在 meow_font.c 实现;meow_font_set_tier() 在档位变化时经 env/settings 路由到该钩子真重载。 */
static int g_font_tier = MEOW_FONT_TIER_COMMON; static int g_font_tier = MEOW_FONT_TIER_COMMON;
/* ---- 帧率调度(A-1/A-2:从 launcher M2.16 迁入,刷新率调度归口 SDK)---- /* ---- 帧率调度(A-1/A-2:从 launcher M2.16 迁入,刷新率调度归口 SDK)----
@@ -665,20 +665,32 @@ unsigned long long meow_ui_frames_presented(void) { return g_frames_presented; }
int meow_gfx_is_device(void) { return g_fb_mem != NULL; } int meow_gfx_is_device(void) { return g_fb_mem != NULL; }
int meow_gfx_mfont_ready(void) { return g_mfont_ready; } int meow_gfx_mfont_ready(void) { return g_mfont_ready; }
/* ---- 字体档位(公共 API,基座定义)---- /* ---- 字体档位(公共 API,基座定义 + font 域真重载钩子)----
* 注意:meow_font_tier()/meow_font_set_tier() 已在 meow_ui_gfx.c 定义(符号唯一), * meow_font_tier()/meow_font_set_tier() 在 meow_ui_gfx.c 定义(符号唯一),font 代理勿在
* 后续 font 代理勿在 meow_font.c 重复定义;可在 meow_font.c 实现「按档装载大字库」并在 * meow_font.c 重复定义;大字库按档装载由 meow_font.c meow_font_reload_tier() 实现,并在
* meow_font_set_tier 的钩子处唤起(见下)。 */ * meow_font_set_tier() 处唤起(见下)。 */
int meow_font_tier(void) { return g_font_tier; } int meow_font_tier(void) { return g_font_tier; }
int meow_font_set_tier(int tier) int meow_font_set_tier(int tier)
{ {
int old = g_font_tier;
if (tier < MEOW_FONT_TIER_COMMON) tier = MEOW_FONT_TIER_COMMON; if (tier < MEOW_FONT_TIER_COMMON) tier = MEOW_FONT_TIER_COMMON;
if (tier > MEOW_FONT_TIER_FULL) tier = MEOW_FONT_TIER_FULL; if (tier > MEOW_FONT_TIER_FULL) tier = MEOW_FONT_TIER_FULL;
if (tier == g_font_tier) return g_font_tier; if (tier == g_font_tier) return g_font_tier;
g_font_tier = tier; g_font_tier = tier;
/* TBD(基座):真正按档装载大字库(meow_font_load_tier 或 config_multifont 扩展)由 /* 真重载(font 域钩子,M2.5x):按档装载大字库。launcher 已按档解析并注入
* 后续 font 代理在 meow_font.c 实现;此处仅记录档位 + 标记重载(下一轮 load 读 tier)。 */ * MEOW_FONT_PNG/IDX(tier>0 → GBK/Full 大字库绝对路径);重读 env 重载。
* 无 env/失败 → 回退卡默认 MeowOS/fonts/default → 固件。
* 大字库 atlas 大(GBK≈5-15MB+),meow_font_reload_tier 先 unload 旧再 load 新,防双驻留。 */
const char *ep = getenv("MEOW_FONT_PNG");
const char *ei = getenv("MEOW_FONT_IDX");
if (ep && ep[0] && ei && ei[0] && meow_font_reload_tier(tier, ep, ei) == 0)
return g_font_tier;
if (meow_font_reload_tier(tier, MEOW_FONT_PNG_CARD, MEOW_FONT_IDX_CARD) == 0)
return g_font_tier;
if (meow_font_reload_tier(tier, MEOW_FONT_PNG_SYS, MEOW_FONT_IDX_SYS) == 0)
return g_font_tier;
g_font_tier = old; /* 全部失败 → 回退旧档(字库可能已不可用,5x7 点阵兜底,系统不死) */
return g_font_tier; return g_font_tier;
} }
@@ -692,9 +704,9 @@ int meow_font_set_tier(int tier)
static int load_font(void) static int load_font(void)
{ {
/* 字体档位(MEOW_FONT_TIER env,由 launcher 按 app.json "fonts" 注入): /* 字体档位(MEOW_FONT_TIER env,由 launcher 按 app.json "fonts" 注入):
* 0=默认4K常用 1=大字库GBK 2=Full。基座只读入存档 + 对齐公共 get/set; * 0=默认4K常用 1=大字库GBK 2=Full。launcher 已按档解析大字库路径并经 MEOW_FONT_PNG/IDX
* 真正按档装载大字库(GBK/Full 的字库路径/面)由后续 font 代理在 meow_font.c / * 注入(见 meow_font_effective_paths_tiered),故下方 meow_font_load(ep,ei) 即装载
* meow_gfx_load_multifont 挂钩实现——此处仍先装载默认(tier0)字库,档位仅记录。 */ * 对应档位字库(tier>0 → GBK/Full 大字库);此处 read env 存档对齐公共 get/set。 */
const char *etr = getenv("MEOW_FONT_TIER"); const char *etr = getenv("MEOW_FONT_TIER");
if (etr && etr[0]) { if (etr && etr[0]) {
g_font_tier = atoi(etr); g_font_tier = atoi(etr);
+27 -1
View File
@@ -38,6 +38,20 @@
#define QUEUE_N 128 #define QUEUE_N 128
#define MAX_DEVS 8 #define MAX_DEVS 8
/* ── 内部事件:系统级「下拉展开通知中心」(M2.5x 接线)──
* 触摸状态机在抬手判定处识别「SWIPE_DOWN 起始于顶部热区」后压入;事件循环按系统手势处理
* (路由 meow_ui_sys_notif_pull),不下发给 APP 的 on_swipe(顶部热区为系统保留手势)。
* 与左缘返回(SWIPE_LEFT)/ meow_ui_swipe_hint 互不影响——后者仍全屏生效,详见下方触摸机。 */
#define MEOW_EV_NOTIF_PULL 5
/* 顶部下滑 → 通知中心判定:纵向 DOWN 手势的起点(origin_y)是否落在顶部热区内。
* 热区由 meow_ui_statusbar_hotzone() 给出(非全屏=状态栏高、全屏=MEOW_SBAR_HOTZONE_FS、
* 收合=提示线高)。供宿主单测直调(判据与触摸状态机共用)。 */
int meow_ui_input_is_notif_pull(int origin_y, int hotzone)
{
return origin_y >= 0 && origin_y < hotzone;
}
static int g_thread_started = 0; static int g_thread_started = 0;
static pthread_t g_thread; static pthread_t g_thread;
static volatile int g_thread_run = 1; static volatile int g_thread_run = 1;
@@ -165,7 +179,14 @@ static void touch_release(void)
} }
} else { } else {
int dir = mdy > 0 ? MEOW_SWIPE_DOWN : MEOW_SWIPE_UP; int dir = mdy > 0 ? MEOW_SWIPE_DOWN : MEOW_SWIPE_UP;
push_ev(MEOW_EV_SWIPE, dir, ady > adx ? ady : adx); /* 顶部下滑 → 通知中心(系统手势,不下发 APP):起点在顶部热区内才认。
* 热区高度随状态变化(非全屏=整条状态栏、全屏=MEOW_SBAR_HOTZONE_FS 缩小、
* 收合=提示线高)——全屏 app 只在顶部极窄热区被系统占用,故「不影响全屏 app」。 */
if (dir == MEOW_SWIPE_DOWN &&
meow_ui_input_is_notif_pull(t_dy, meow_ui_statusbar_hotzone()))
push_ev(MEOW_EV_NOTIF_PULL, t_dy, ady > adx ? ady : adx);
else
push_ev(MEOW_EV_SWIPE, dir, ady > adx ? ady : adx);
} }
} else { } else {
push_ev(MEOW_EV_TAP, t_cx, t_cy); push_ev(MEOW_EV_TAP, t_cx, t_cy);
@@ -287,6 +308,11 @@ void meow_ui_run(const meow_ui_events *ev)
} else if (e.a == MEOW_SWIPE_LEFT && sb_active) { } else if (e.a == MEOW_SWIPE_LEFT && sb_active) {
meow_ui_request_exit(); meow_ui_request_exit();
} }
} else if (e.type == MEOW_EV_NOTIF_PULL) {
if (in_startup) continue; /* 启动窗口顶区误触残留:丢弃,防进入即开通知中心 */
/* 系统级「下拉展开通知中心」:起始于顶部热区的下滑为系统保留手势,不下发
* APP on_swipe;路由到系统钩子(当前为记录占位,通知中心 UI 为后续里程碑)。 */
meow_ui_sys_notif_pull();
} else if (e.type == MEOW_EV_SWIPE_PROGRESS) { } else if (e.type == MEOW_EV_SWIPE_PROGRESS) {
if (in_startup) continue; /* 启动窗口进度条静音 */ if (in_startup) continue; /* 启动窗口进度条静音 */
int x0 = e.a, xc = e.b, dx = xc - x0; /* dx=当前X-按下X */ int x0 = e.a, xc = e.b, dx = xc - x0; /* dx=当前X-按下X */
+16 -3
View File
@@ -156,18 +156,31 @@ void meow_ui_set_fullscreen(int on)
int meow_ui_statusbar_height(void) int meow_ui_statusbar_height(void)
{ {
return meow_ui_sys_statusbar_active() ? MUI_SBAR_H : 0; if (!meow_ui_sys_statusbar_active()) return 0;
return meow_ui_statusbar_collapsed() ? MUI_SBAR_COLLAPSE_H : MUI_SBAR_H;
} }
/* ---- 内部(gfx/input 用) ---- */ /* ---- 内部(gfx/input 用) ---- */
int meow_ui_sys_statusbar_active(void) int meow_ui_sys_statusbar_active(void)
{ {
return g_statusbar_on && !g_fullscreen; /* collapsed 视为状态栏仍激活(只留提示线):与 fullscreen 并存(全屏启 collapsed)——
* 这样 gfx.c present() 的收合动画块会运行(驱动 g_sbar_collapse_h 插值),且顶部提示线
* 正常绘制/热区可用。fullscreen 且不 collapsed → 整条隐藏(沉浸,inset=0),保持既有语义。 */
return g_statusbar_on && (meow_ui_statusbar_collapsed() || !g_fullscreen);
} }
int meow_ui_sys_statusbar_height(void) int meow_ui_sys_statusbar_height(void)
{ {
return meow_ui_sys_statusbar_active() ? MUI_SBAR_H : 0; if (!meow_ui_sys_statusbar_active()) return 0;
return meow_ui_statusbar_collapsed() ? MUI_SBAR_COLLAPSE_H : MUI_SBAR_H;
}
void meow_ui_sys_notif_pull(void)
{
/* 系统级「下拉展开通知中心」手势接线(M2.5x):输入层识别出「起始于顶部热区的
* SWIPE_DOWN」后路由到此。通知中心 UI 为后续里程碑——此处先落记录(避免哑无痕);
* 届时由对应代理在该入口挂真实展开逻辑(IPC SHOW_ACTIVITY / 覆盖层渲染)。 */
meow_log(MEOW_LOG_WARN, "notif_pull: notification center TBD (wired)");
} }
int meow_ui_sys_statusbar_force(void) int meow_ui_sys_statusbar_force(void)
+18 -2
View File
@@ -34,6 +34,13 @@ typedef struct meow_sys_state {
#define MUI_SBAR_H 28 #define MUI_SBAR_H 28
#define MUI_SBAR_FONT 20 #define MUI_SBAR_FONT 20
/* 收合(collapsed)状态下状态栏的有效加载高度(inset)= 顶部提示线高度(px)。
* 收合=状态栏内容上移出屏、只留一条提示线:app 内容据此上移(非满 28),
* 形成「不完全沉浸(留提示线)」;与 fullscreen(整条隐藏、inset0)区别。
* 取值沿用公共宏 MEOW_SBAR_HOTZONE_FS(=4,全屏时顶部热区/提示线的缩后高度),
* 具体绘制高度(gfx.c 的 MUI_SBAR_HINT_H=6)在渲染端,这里按提示线高度口径即可。 */
#define MUI_SBAR_COLLAPSE_H MEOW_SBAR_HOTZONE_FS
/* 首次全量读取;失败仍返回 0(字段置 -1/0)。 */ /* 首次全量读取;失败仍返回 0(字段置 -1/0)。 */
int meow_ui_sys_init(void); int meow_ui_sys_init(void);
@@ -43,12 +50,21 @@ void meow_ui_sys_refresh(void);
/* 当前快照(内部只读)。 */ /* 当前快照(内部只读)。 */
const meow_sys_state *meow_ui_sys_get(void); const meow_sys_state *meow_ui_sys_get(void);
/* 状态栏是否应绘制(= set_statusbar(1) 且 非 fullscreen)。 */ /* 状态栏是否应绘制(= set_statusbar(1) 且 非 fullscreen),收合(collapsed)兼容:
* collapsed 视为状态栏仍在(只留提示线),故 collapsed 时即使 fullscreen 也返回 1,
* 让渲染端/输入层继续驱动上收动画与提示线;fullscreen 且不 collapsed → 0(整条隐藏,
* 沉浸,保持既有语义)。 */
int meow_ui_sys_statusbar_active(void); int meow_ui_sys_statusbar_active(void);
/* 状态栏高度(px):active 时 28,否则 0(供 meow_ui_statusbar_height 用)。 */ /* 状态栏有效高度(px,供 inset/提示条定位):active 时 28;collapsed 时=提示线高度
* MUI_SBAR_COLLAPSE_H(非满 28,app 内容可上移);否则 0(供 meow_ui_statusbar_height 用)。 */
int meow_ui_sys_statusbar_height(void); int meow_ui_sys_statusbar_height(void);
/* 系统级「下拉展开通知中心」手势挂钩(M2.5x 接线):输入层识别出「起始于顶部热区的
* SWIPE_DOWN」后路由到这里。通知中心 UI 为后续里程碑,此入口作记录/占位;届时由对应
* 代理在此挂真实展开逻辑(IPC SHOW_ACTIVITY / 覆盖层渲染)。 */
void meow_ui_sys_notif_pull(void);
/* 自上次渲染后状态栏数据是否已变(1s 轻刷/显隐切换置位);读取即清除。 /* 自上次渲染后状态栏数据是否已变(1s 轻刷/显隐切换置位);读取即清除。
* 渲染后端 present() 用它决定是否重画顶部条(配合脏区/整帧判断)。 */ * 渲染后端 present() 用它决定是否重画顶部条(配合脏区/整帧判断)。 */
int meow_ui_sys_statusbar_force(void); int meow_ui_sys_statusbar_force(void);