SDK: 同步主仓 M15/M16/bug3/MD3 修复 — 时钟 ABI 补全/IME 冷却窗/列表行高派生/形状 token/翻页键
公开 SDK 落后主仓的一批 bugfix(主仓是固件构建源,这些修复已在设备上跑过)。 本次只做「主仓 → 公开仓」单向同步,图像栈方向由主仓侧另一单处理。 时钟 ABI(M16 第二弹,原文件自认「残余未修」): - meow_ui_time.c 147→345 行:futex 相对超时接管(__timedwait/_cp)、 timerfd_create/settime/gettime 的 itimerspec 64↔32 双向转换、 __clock_settime64 越界返 ENOTSUP,另有 4 个纯换算函数 - meow_ui_internal.h:struct meow_time32 / meow_itimerspec32 契约移入内部头 - CLOCK_ABI.md:补接管机制、验证配方与 objdump 证据 - 新增 test_meow_time64.c IME bug3(成对才有意义): - meow_ui_ime.c:IME_TAP_COOLDOWN_MS 200 冷却窗 + 打开时二次 drain - meow_ui_input.c:meow_input_touch_reset() 并在 drain 内调用(复位触摸状态机) - test_ime_fix.c:新增用例④ stray-tap 列表行高(M15,修用户可见 bug): - meow_ui_widget.c:listv_item_h() 由主题字体行高派生,不再写死 56 (两行列表 62 > 56 会让首行标题被裁剪守卫整条跳过) - include/meow_ui_widgets.h:新增 meow_listview_item_h() - 新增 test_meow_ui_listview_rowh.c 其余:meow_select.c / meow_ui_dialog.c 统一 MD3 形状 token; include/meow_ipc.h 补 MSG_KEY_PAGEUP/PAGEDOWN 枚举; meow_ui_sys.c / meow_cachegov.c 取时统一走 meow_ui_time_ms()。 测试辅助函数标定:tuku/test/test_settings_lock_host.c 的注入前等待 150ms→350ms。 该等待按修复前 IME 标定,新冷却窗下首键 dt_open≈114ms 会被当迟到抬起吞掉, 只录到 "68" 两位。仅改等待时长,断言原样保留。 验证:tuku 四套脚本全绿(run_tests 644 PASS / zip 106 / cbz 296 / webtoon PASS); 12 个源文件与主仓逐字节一致;sdk/build_sdk.sh 34 成员; tuku/build_tuku.sh 产出静态 MIPS32r2 ELF。
This commit is contained in:
1 parent
38cc9d4acb
commit
27a3750e36
37 files changed
+1026
-111
No files matched your search
+144
-13
@@ -120,6 +120,11 @@ result = tv_sec*1000 + tv_nsec/1e6
|
||||
`meow_cachegov.c` 统一调 `meow_ui_time_ms()`(cachegov 保留宿主测试注入桩)。
|
||||
5. 构建脚本 `build_sdk.sh` 的 OBJS 增加 `meow_ui_time`;宿主单测 `test_meow_time.c`
|
||||
固定换算边界与「单调且前进」;探针 `tools/clock_abi_probe.c` 用于宿主/真机一眼验收。
|
||||
6. **M16 续修同源入口**(同一 TU,`#if MEOW_TIME32_KERNEL_ABI` 限定 mips o32):
|
||||
`__timedwait_cp`/`__timedwait`(futex 相对超时)、`__timerfd_settime64`/
|
||||
`__timerfd_gettime64`/`timerfd_create`、`__clock_settime64`。换算逻辑抽成平台无关的
|
||||
`meow_time32_{rel,abs}_from_parts()` / itimerspec 转换(`meow_ui_internal.h`),
|
||||
宿主 `test_meow_time64.c` 直接覆盖。清单、逐项证据与未修项配方见 §6/§7。
|
||||
|
||||
非 mips 目标(宿主单测、aarch64 宿主)走普通 `clock_gettime`,行为不变。
|
||||
|
||||
@@ -167,21 +172,147 @@ llvm-nm clock_abi_probe_sdk | grep -E 'clock_gettime|nanosleep'
|
||||
App(hello/reader/files/ncm…)。
|
||||
- **产物**:`meow_ui.a` 变更 → 需**重编并重链四 App**(ncm/reader/files/hello);
|
||||
`time()`/`gettimeofday()` 的修正也在同一份归档里生效(链接期覆盖)。
|
||||
- 真机写入红线未触碰;本单只改 SDK 源与构建脚本。
|
||||
- **同步等待(M16)**:`pthread_cond_timedwait` / `sem_timedwait` / `pthread_join` 超时 /
|
||||
`aio_suspend` / 非 PI 的 `pthread_mutex_timedlock`/rwlock timed 的**相对超时按真实剩余
|
||||
时间**生效(不再「亚秒即超时」)。SDK 侧直接受益者是 `meow_ui_audio.c` 的 5 处条件等待
|
||||
(:312/:351/:417/:431/:499)。
|
||||
`timerfd_*`、`clock_settime` 一并修正(当前无调用方)。
|
||||
- 真机写入红线未触碰;本单只改 SDK 源与文档/测试。
|
||||
|
||||
## 6. 同源残余(未在本单修复,需单独安排)
|
||||
## 6. 同源入口全量清点(M16)
|
||||
|
||||
同一句 `!__ILP32__` 还打穿了以下路径(睡眠族已在上文一并修掉,余下这些症状不同、
|
||||
修复面更大):
|
||||
M16 把 zig 0.17 musl 源里所有 `#if defined(SYS_*_time64) && !defined(__ILP32__)` 守卫
|
||||
逐条编译、反汇编核对(`zig cc --target=mipsel-linux-musleabi`),结论如下。
|
||||
「回退分支」= `__ILP32__` 被定义时实际编译进去的代码。
|
||||
|
||||
| 路径 | 症状 | 说明 |
|
||||
### 6.1 受损(回退分支把 time64 结构直传 time32 系统调用)
|
||||
|
||||
| musl 源(守卫行) | 入口 | 回退分支实际行为 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| `thread/__timedwait.c:14` | futex 相对超时 → `pthread_cond_timedwait` / `sem_timedwait` / `pthread_join` 超时 / `aio_suspend` / 非 PI `pthread_mutex_timedlock` / rwlock timed | `__syscall_cp(SYS_futex=4238, …, to)`,`to` 是 16B time64 结构;内核只读前 8B:`tv_nsec` 落在 `tv_sec` 高半字(恒 0)→ 亚秒等待变 0(立即 ETIMEDOUT)、整秒等待丢纳秒 | **M16 已接管** |
|
||||
| `linux/timerfd.c:14` | `timerfd_settime` | 32B itimerspec 直传 `SYS_timerfd_settime32=4323`;内核按 16B 读 → `value.tv_sec` 读到调用者的 `interval.tv_nsec`,`value.tv_nsec` 读到填充字节 | **M16 已接管** |
|
||||
| `linux/timerfd.c:42` | `timerfd_gettime` | 4322 只写 16B,调用者按 32B 解 → 字段串位 | **M16 已接管** |
|
||||
| `time/clock_settime.c:9` | `clock_settime` | 16B 直传 `SYS_clock_settime32=4262` → nsec 恒 0 | **M16 已接管** |
|
||||
| `signal/sigtimedwait.c:10` | `sigtimedwait` | 16B 直传 `SYS_rt_sigtimedwait=4197` | 未修(SDK/App 未用,§7.3 配方) |
|
||||
| `ipc/semtimedop.c:19` | `semtimedop` | 16B 直传 `SYS_ipc=4117`(`IPCOP_semtimedop`) | 未修(未用) |
|
||||
| `time/timer_settime.c:13` | `timer_settime` | 32B itimerspec 直传 4258 | 未修(未用) |
|
||||
| `time/timer_gettime.c:11` | `timer_gettime` | 4259 只写 16B | 未修(未用) |
|
||||
| `stat/utimensat.c:15` | `utimensat`(`futimens` 同源) | `times[2]`(32B)直传 4316 | 未修(仅 ncm 宿主 bench/测试出现) |
|
||||
| `network/recvmmsg.c:21` | `recvmmsg` | 16B timeout 直传 4335 | 未修(未用) |
|
||||
| `mq/mq_timedsend.c:10` | `mq_timedsend` | 16B 直传 4273 | 未修(未用) |
|
||||
| `thread/pthread_mutex_timedlock.c:8` | PI 互斥量的 timedlock 快路径 | 16B 直传 4238(`FUTEX_LOCK_PI`) | 未修(非 PI 走已修的 `__timedwait`;SDK/App 不用 PI 互斥量) |
|
||||
|
||||
唯一在用的受害者是 futex:`meow_ui_audio.c`(5 处,:312/:351/:417/:431/:499)、
|
||||
`meow_audiod`、ncm 播放线程都用
|
||||
`pthread_cond_timedwait`,修复前「剩 0.4s」会被内核当成 `{0,0}` 立即超时 → 音频环形缓冲
|
||||
的等待退化成忙轮询。
|
||||
|
||||
### 6.2 不受影响(回退分支自己做了宽度转换)——纠正 M11 §6 的猜测
|
||||
|
||||
| musl 源 | 证据 |
|
||||
| --- | --- |
|
||||
| `select/pselect.c:15` | 回退分支 `syscall_cp(SYS_pselect6=4301, …, (long[]){s, ns}, …)`:o32 的 `long`=4B → 正好 8B time32 |
|
||||
| `select/ppoll.c:14` | 同上(4302)。反汇编 `__ppoll_time64`:`lw 0x0/0x8` 读 16B 结构 → `sw` 成栈上两字 → `addiu $4,$zero,0x10ce`(4302) |
|
||||
| `select/select.c:28` | mips 无 `SYS_select`,走 `(long[]){s,ns}` + 4301;同 pselect |
|
||||
| `linux/clock_adjtime.c:40` | 回退分支保留 `if (sizeof(time_t) > sizeof(long))`,构造 long 版 `struct ktimex` |
|
||||
| `misc/getrusage.c:9` | 同上宽度分支(`dest = &ru_maxrss - 4*sizeof(long)` + 回填) |
|
||||
| `linux/wait4.c:11` | 同上宽度分支 |
|
||||
|
||||
M11 §6 把 pselect/ppoll 列为「未验证」是保守估计;本次逐条反汇编确认它们**本来就是对的**,
|
||||
不需要接管(也就没有可修的低风险项)。
|
||||
|
||||
## 7. 接管机制与未修项配方(M16)
|
||||
|
||||
### 7.1 符号名不是 API 名:`_REDIR_TIME64` 把调用点改了名
|
||||
|
||||
zig 的 `alltypes.h` 无条件 `_REDIR_TIME64=1`,头文件里每个受影响 API 都有一行
|
||||
`__REDIR(x, __x_time64)`(`generic-musl/*.h`)。应用里写 `timerfd_settime(...)`,编译出来
|
||||
引用的是 `__timerfd_settime64`。所以接管必须定义**重定向后的符号**:
|
||||
|
||||
| API | 应用引用的符号 | libc 该 `.o` 还定义(必须一并接管,否则重复定义) |
|
||||
| --- | --- | --- |
|
||||
| `futex` 相对超时 | 亚秒 timed wait 立即超时 | `__timedwait_cp` 的 `__futex4_cp` 同款 `!__ILP32__` 守卫;`pthread_cond_timedwait` 的「now」已由本单修正,但相对超时仍按 8 字节读(>1s 的整秒等待基本可用,亚秒等待失效) |
|
||||
| `timerfd_settime` | 到期时间错乱 | `struct itimerspec`(32 字节)当 16 字节读;当前 SDK 未用 timerfd |
|
||||
| `clock_settime`/`clock_adjtime`/`pselect`/`ppoll`/`sigtimedwait`/`semtimedop` 等 | 未验证 | 同族守卫;SDK 目前未直接使用 |
|
||||
| `timerfd_settime/gettime` | `__timerfd_settime64` / `__timerfd_gettime64` | `timerfd_create` |
|
||||
| `clock_settime` | `__clock_settime64` | (无) |
|
||||
| 内部 `__timedwait_cp` | `__timedwait_cp` | `__timedwait`、`__eintr_valid_flag`(弱) |
|
||||
|
||||
建议:向 zig 上游报 musl `__ILP32__` 守卫问题(clang 对 32 位目标统一定义 `__ILP32__`,
|
||||
而该守卫本意只排除 x32);根治应换用容器旧链(gcc,不定义 `__ILP32__`)编 SDK,或
|
||||
downgrade/修补 zig 内置 musl。**不要**指望 `-D_TIME_BITS=32`:zig 的
|
||||
`alltypes.h` 是无条件 `#define __USE_TIME_BITS64 1`,命令行改不动;而只改用户侧头文件会让
|
||||
用户结构(8 字节)与 libc 内部(16 字节,pthread 等)再次错配。
|
||||
只定义 `__timedwait_cp` 是**不行**的:libc 的 `__timedwait.o` 会因 `__timedwait` 未解析被
|
||||
拉入,与本文的重定义撞车。实测:
|
||||
|
||||
```
|
||||
ld.lld: error: duplicate symbol: __timedwait_cp
|
||||
>>> defined at ovr2.c
|
||||
>>> defined at __timedwait.c:34 (.../musl/src/thread/__timedwait.c:34)
|
||||
```
|
||||
|
||||
### 7.2 为什么链接期接管有效
|
||||
|
||||
- 「hidden」不阻止静态链接接管:lld 按归档顺序解析,`meow_ui_time.o` 先于 `libc.a` 参与,
|
||||
libc 内部对 `__timedwait_cp` 的引用就绑到本文(实测 `jal 0x…<__timedwait_cp>`)。
|
||||
- 前提是 `meow_ui_time.o` 被拉入:SDK/App 都引用 `meow_ui_time_ms()`,且 `clock_gettime`
|
||||
被重定向到 `__clock_gettime64`(本文也定义),所以它总会被拉入。**若将来把本 TU 的
|
||||
接管符号拆到没人引用的新 TU,接管会静默失效。**
|
||||
- `__eintr_valid_flag` 给弱定义,libc `signal/sigaction.c` 的强定义优先(同时链 sigaction
|
||||
实测不冲突)。
|
||||
- 取消点语义不重写:`__timedwait_cp` 仍调 libc 的 `__syscall_cp`,`ECANCELED`/`EINTR`
|
||||
处理逐行照抄 musl。
|
||||
- 结构宽度前提:本 TU 与被接管入口的调用者必须用同一套 zig 头(`time_t` 8B、
|
||||
`struct timespec` 16B)。当前四 App 与 launcher 都用 `zig-mipsel-cc` 编译
|
||||
(`$SYSROOT/lib` 只有 `libz.a`,libc 来自 zig 内置 musl),前提成立;若将来换链,
|
||||
必须整链替换、不能混用。
|
||||
|
||||
### 7.3 未修项的配方(同一模式,约 10 行/项)
|
||||
|
||||
1. 在 `meow_ui_time.c` 的 mips 分支里定义 `__<api>_time64`(或该 API 的重定向名);
|
||||
2. 入参展成 `meow_time64_spec` / `meow_time64_itimerspec`,调
|
||||
`meow_time32_rel_from_parts()`(超时类,饱和)或 `meow_time32_abs_from_parts()`
|
||||
(绝对时刻类,越界回 `ENOTSUP`);
|
||||
3. 发 `SYS_<api>_time32`(或 `SYS_<api>` 的 time32 号),输出再反向展开;
|
||||
4. 同一 `.o` 里 libc 定义的所有符号一起接管(见 7.1 表),否则重复定义;
|
||||
5. 宿主单测补边界,交叉反汇编确认结构宽度与 syscall 号。
|
||||
|
||||
不建议顺手全改:这些 API 当前零调用(全仓 `grep` 只有 ncm 的宿主 bench/测试用 `utimensat`),
|
||||
每多接管一个就多一份「转换写错 → 静默写坏文件时间/信号等待」的面;优先级应按「谁先被 App
|
||||
用」来定。根治仍是上游:换容器旧链(gcc 不定义 `__ILP32__`)编 SDK,或修补 zig 内置 musl
|
||||
的 `!__ILP32__` 守卫(本意只排除 x32)。
|
||||
|
||||
## 8. M16 验证
|
||||
|
||||
宿主(aarch64 Termux,gcc):
|
||||
|
||||
```
|
||||
cd patch_v2/sdk
|
||||
gcc -Os -Wall -Wextra -I. -Iinclude -c meow_ui_time.c -o /tmp/m.o # 0 warning
|
||||
gcc -Os -Wall -Wextra -I. -Iinclude test_meow_time64.c /tmp/m.o -o /tmp/t && /tmp/t
|
||||
gcc -Os -Wall -Wextra -I. -Iinclude test_meow_time.c /tmp/m.o -o /tmp/t0 && /tmp/t0
|
||||
```
|
||||
|
||||
- `test_meow_time64`(新,23 项):相对/绝对换算边界(INT32_MAX±1、INT64_MAX、负值)、
|
||||
itimerspec 四字段不串位与越界原子性,以及「同一块 16B 内存按内核 o32 只读 8B」的
|
||||
before/after 模拟(0.4s → `{0,0}` 立即超时;2.3s → 2s 整)。
|
||||
- `test_meow_time`(M11 回归):换算边界 + 单调前进,仍 PASS。
|
||||
|
||||
交叉:
|
||||
|
||||
```
|
||||
bash patch_v2/sdk/build_sdk_host.sh --verify # 76 成员、全 MIPS32r2、0 warning
|
||||
```
|
||||
|
||||
反汇编(本单新增/改写的入口):
|
||||
|
||||
| 符号 | syscall 号 | 缓冲宽度证据 |
|
||||
| --- | --- | --- |
|
||||
| `__timedwait_cp` | `0x108e` = 4238 `SYS_futex` | 超时在栈上 8B(`addiu $18,$sp,0x20` + `sw $18,0x10($sp)`),不再直传 16B 结构 |
|
||||
| `__timerfd_settime64` | `0x10e3` = 4323 `timerfd_settime32` | 从调用者 32B 结构读 `0x0/0x4/0x8/0x10/0x14/0x18`,打包成栈上 16B |
|
||||
| `__timerfd_gettime64` | `0x10e2` = 4322 | 内核只写栈上 16B,再按字段展开到调用者 32B |
|
||||
| `__clock_settime64` | `0x10a6` = 4262 `clock_settime32` | 8B `{int32 sec, int32 nsec}` |
|
||||
|
||||
链接期(真实 `meow_ui.a` + 探针,顺序同 App:`probe.o meow_ui.a … -lc`):
|
||||
|
||||
```
|
||||
llvm-objdump -d probe_sdk | sed -n '/<__pthread_cond_timedwait_time64>:/,/^$/p' | grep jal.__timedwait
|
||||
# cd930: jal 0x419e8 <__timedwait_cp> ← 绑到 meow_ui.a 里的实现
|
||||
llvm-nm probe_sdk | grep -c __timedwait_cp # 1(libc 那份未被拉入)
|
||||
```
|
||||
|
||||
真机验证仍待设备(本单未碰真机):`pthread_cond_timedwait` 传 100ms 超时的等待应真的等
|
||||
约 100ms 再超时,而不是立即返回。
|
||||
+13
-2
@@ -82,10 +82,19 @@ enum {
|
||||
MSG_META = 0x0072, /* ↑ 媒体会话上报元数据/进度/状态(阶段 D 附录 B) */
|
||||
MSG_PLAYCTL = 0x0073, /* ↓ 播放控制下发/↑ 控制器转发到最近活跃媒体会话 */
|
||||
MSG_META_LYRIC = 0x0074, /* ↑ 媒体会话上报当前曲歌词 lrc/tlrc(阶段 D · 批3,分片) */
|
||||
/* ---- 系统翻页信号(READER_UI_DESIGN §E,用户 2026-09-04 拍板):0x0080-0x0081 ----
|
||||
* 语义:launcher 按键层(meow_launcherd/keys.c)在「当前台 App 为其 app.json 声明
|
||||
* volume_page 的前台应用」时,把物理 VOL_UP/VOL_DOWN 转换成系统翻页信号下发(否则照旧
|
||||
* 调音量——音量基本行为/插孔消抖/音量反转绝不破坏)。App 侧(如 reader_app/app/reader_ipc.c)
|
||||
* 持持久连接收本消息 → 上一页/下一页。方向由消息号本身决定(UP=上一页,DOWN=下一页);
|
||||
* 无负载(len=0)。属系统级能力,未来其它 App 可声明复用。 */
|
||||
MSG_KEY_PAGEUP = 0x0080, /* ↓ 系统级:上一页(VOL_UP 语义,仅前台声明 app 收) */
|
||||
MSG_KEY_PAGEDOWN = 0x0081, /* ↓ 系统级:下一页(VOL_DOWN 语义,仅前台声明 app 收) */
|
||||
/* ---- 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 语义) */
|
||||
MSG_AMS_KILL = 0x00A1, /* → 请求 launcher 终止某 App(SIGTERM 语义):后台保活表命中→就地真退序列;
|
||||
* 否则有前台会话→置 pending 交会话消费;都没有→ACK -2(MEOW_ERR_NOENT) */
|
||||
/* ---- audiod 独占音频服务(devdocs/plan/AUDIOD_DESIGN.md §2):0x00C0-0x00CA ----
|
||||
* 语义:App 只经 SDK 客户端 API(meow_ui_audio.c)喂 PCM,audiod 独占 hw:0,0。
|
||||
* 独占+抢占(后开赢);软混音已否决(MIPS32 软 SRC 伤音质,留混音器接口位)。 */
|
||||
@@ -455,7 +464,9 @@ typedef struct m_ams_start {
|
||||
char picker_caller[MEOW_APP_ID_MAX]; /* 32:发起方 app_id(""=系统/CLI) */
|
||||
} m_ams_start_t; /* 160 B */
|
||||
|
||||
/* MSG_AMS_KILL →:请求 launcher 终止某 App(SIGTERM 语义,执行器=launcher)。 */
|
||||
/* MSG_AMS_KILL →:请求 launcher 终止某 App(SIGTERM 语义,执行器=launcher)。
|
||||
* 后台保活 App:launcher 就地走 meow_bg_terminate 真退序列(与后台管理页「杀」同一通道);
|
||||
* 前台 App:置 pending 交前台会话消费;两者皆非 → ACK code=(uint32_t)MEOW_ERR_NOENT(-2)。 */
|
||||
typedef struct m_ams_kill {
|
||||
char app_id[MEOW_APP_ID_MAX]; /* 32 */
|
||||
} m_ams_kill_t; /* 32 B */
|
||||
|
||||
@@ -316,11 +316,16 @@ typedef struct meow_list_item {
|
||||
/* 行数据源回调:idx 有效时填充 out 并返回 1;越界返回 0。每行轻量填充(不做 IO,§5.5)。 */
|
||||
typedef int (*meow_list2_source)(int idx, meow_list_item *out, void *ctx);
|
||||
|
||||
/* 创建:视口(x,y,w,h) + 行数 count + 行数据源 + 选中模式。行高默认 56(可用 set_item_h 改)。 */
|
||||
/* 创建:视口(x,y,w,h) + 行数 count + 行数据源 + 选中模式。
|
||||
* 行高默认按当前主题字体行高派生(双行 max(56, 2*行高+2) / 单行 max(48, 行高)),
|
||||
* 20px 字库(行高 30)下双行=62;显式 set_item_h 可覆盖(App 自管卡高时用)。 */
|
||||
meow_widget *meow_listview_create(int x, int y, int w, int h, int count,
|
||||
meow_list2_source src, void *ctx, meow_list_sel mode);
|
||||
/* 行高(固定;越界/<=0 忽略)。 */
|
||||
/* 行高:显式 px(>0)优先并固定;越界/<=0 忽略(保持字体派生)。返回当前生效行高。 */
|
||||
int meow_listview_set_item_h(meow_widget *lv, int item);
|
||||
/* 当前生效行高(显式值,或未显式时按主题字体行高派生的值)。无效控件返回 0。
|
||||
* 行几何(draw/hit/max_scroll)全部按此值,App 自绘行装饰用它对齐,勿再硬编码 56。 */
|
||||
int meow_listview_item_h(meow_widget *lv);
|
||||
/* 行数(搜索过滤/懒加载/目录切换时动态更新):截断 sel 到新范围 + clamp scroll。 */
|
||||
void meow_listview_set_count(meow_widget *lv, int count);
|
||||
/* 滚动:set 越界 clamp 到 [0, max_scroll];by 相对滚动(App on_swipe/touch_move 用)。 */
|
||||
@@ -394,7 +399,8 @@ int meow_listview_set_vsource(meow_widget *lv, meow_listview_count_fn count,
|
||||
* show_icon=1 画左图标/缩略图;show_right=1 画右尾件。返回 0=成功。 */
|
||||
int meow_listview_set_item_renderer(meow_widget *lv, int lines, int show_icon,
|
||||
int show_right);
|
||||
/* 行档位默认行高:lines=1→48(单行)、2→56(双行);与 set_item_h(精确 px)互补。 */
|
||||
/* 行档位(默认行高口径):lines=1→max(48, 行高)、2→max(56, 2*行高+2),由当前字体行高派生
|
||||
* (与 set_item_h 互补:tier 恢复派生默认,set_item_h 设精确 px)。 */
|
||||
int meow_listview_set_item_tier(meow_widget *lv, int lines);
|
||||
/* 完全自绘:设 draw_item 回调(覆盖默认 renderer);NULL=用默认 renderer。返回 0=成功。 */
|
||||
int meow_listview_set_item_draw(meow_widget *lv, meow_listview_draw_fn draw,
|
||||
|
||||
+1
-3
@@ -62,9 +62,7 @@ static int64_t g_test_now_ms = -1;
|
||||
static int64_t now_ms(void)
|
||||
{
|
||||
if (g_test_now_ms >= 0) return g_test_now_ms;
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
return (int64_t)meow_ui_time_ms();
|
||||
}
|
||||
void meow_cachegov_test_set_time_ms(int64_t t) { g_test_now_ms = t; }
|
||||
|
||||
|
||||
+4
-1
@@ -103,7 +103,10 @@ int meow_select_open(meow_select *s)
|
||||
meow_ui_modal_capture();
|
||||
/* 首次绘制:遮罩 + 底部面板(顶部圆角)+ 标题 + 选项列表。 */
|
||||
meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150);
|
||||
meow_gfx_fill_round(0, py, W, ph, th->radius_card, th->surfaceContainerHighest);
|
||||
/* MD3 sheet 顶角 = corner-extra-large-top(28)(material-web _md-comp-sheet-bottom.scss),
|
||||
* 与 Dialog 一致;同 SDK meow_sheet(meow_ui_dialog.c:361)/dialog 卡(meow_ui_dialog.c:103) 取 28,
|
||||
* 底部面板统一 extra-large,避免同 SDK 两个 bottom sheet 顶部圆角不一致(旧 th->radius_card=16)。 */
|
||||
meow_gfx_fill_round(0, py, W, ph, MEOW_SHAPE_EXTRA_LARGE, th->surfaceContainerHighest);
|
||||
meow_ui_text(pad, py + pad, s->title, th->font_title, th->onSurface);
|
||||
meow_widget_draw(lv);
|
||||
meow_ui_flush();
|
||||
|
||||
@@ -134,12 +134,13 @@ static void dlg_card(meow_dialog *d, const meow_theme *th, const dlg_geom *g)
|
||||
y += meow_ui_text_height(th->font_body) + 8;
|
||||
}
|
||||
}
|
||||
/* 取消(左,text 型)/ 确定(右,filled) */
|
||||
/* 取消(左,outlined)/ 确定(右,filled)——MD3 按钮统一为胶囊(full,全高一半),
|
||||
* 与 SDK widget_btn_radius(meow_ui_widget.c:146) 同款;旧硬编码 radius 10 非 token。 */
|
||||
int bx = g->dx + pad, cx = bx + g->btn_w + 12, by = g->btn_y;
|
||||
meow_ui_draw_rect_round(bx, by, g->btn_w, g->btn_h, 10, th->primary);
|
||||
meow_ui_draw_rect_round(bx, by, g->btn_w, g->btn_h, MEOW_SHAPE_FULL(g->btn_h), th->primary);
|
||||
meow_ui_text_center(bx + g->btn_w / 2, by + (g->btn_h - meow_ui_text_height(th->font_body)) / 2,
|
||||
meow_str("common.cancel"), th->font_body, th->primary);
|
||||
meow_gfx_fill_round(cx, by, g->btn_w, g->btn_h, 10, th->primary);
|
||||
meow_gfx_fill_round(cx, by, g->btn_w, g->btn_h, MEOW_SHAPE_FULL(g->btn_h), th->primary);
|
||||
meow_ui_text_center(cx + g->btn_w / 2, by + (g->btn_h - meow_ui_text_height(th->font_body)) / 2,
|
||||
meow_str("common.ok"), th->font_body, th->onPrimary);
|
||||
}
|
||||
|
||||
@@ -75,6 +75,16 @@ static int g_ime_done = 0;
|
||||
static int g_ime_result = 0;
|
||||
static int g_shift = 0;
|
||||
|
||||
/* WiFi 设置页 bug3(2026-09-15):IME 打开瞬间 TAP 冷却窗。触发打开的那次点按,其「抬起
|
||||
* TAP」(含 goodix 抬起长尾)可能由 SDK 输入线程在打开 drain 之后才读出并入队(子页自有
|
||||
* tfd 与 SDK 输入线程独立读同一 evdev,节奏可滞后 ~20ms+)——阻塞循环把它当「点背景=取消」
|
||||
* 瞬间关键盘(y<KB_TOP)或幻影按键(y≥KB_TOP)。窗口期内的 TAP 一律消费丢弃(镜像
|
||||
* meow_ui_run 的 STARTUP_COOLDOWN_MS 500 先例,取 200 兼顾手感;正常打字第一键必在窗后)。
|
||||
* 防御为双保险:drain 已复位触摸状态机(在途按压不再产 TAP),冷却窗再兜住 drain 前已入队
|
||||
* 未及清/时序竞争残漏的 TAP。 */
|
||||
#define IME_TAP_COOLDOWN_MS 200
|
||||
static uint64_t g_ime_open_ms = 0; /* meow_ui_time_ms() 单调毫秒;0=未记录 */
|
||||
|
||||
static meow_ime_ctx g_ime_ctx; /* 拷贝(on_change 回调用) */
|
||||
static meow_ime_ctx *g_ime_orig = NULL; /* 应用真实 ctx(buf 回填用) */
|
||||
static char g_ime_buf[256];
|
||||
@@ -570,6 +580,11 @@ int meow_ime_long_press(int x, int y)
|
||||
int meow_ime_handle_event(const meow_input_ev *e)
|
||||
{
|
||||
if (!g_ime_active || g_ime_done) return 0;
|
||||
/* 打开冷却窗(bug3):窗口内 TAP 一律消费丢弃——防触发打开那次点按的迟到「抬起 TAP」
|
||||
* 被当「点背景=取消」或幻影按键。消费(return 1)而非透传:事件已属 IME 会话期。 */
|
||||
if (e->type == MEOW_EV_TAP && g_ime_open_ms &&
|
||||
meow_ui_time_ms() - g_ime_open_ms < IME_TAP_COOLDOWN_MS)
|
||||
return 1;
|
||||
switch (e->type) {
|
||||
case MEOW_EV_TAP: return meow_ime_handle_tap(e->a, e->b);
|
||||
case MEOW_EV_TOUCH_DOWN: g_ime_swipe_y = e->b; return 1; /* 记录压点(候选栏滑动判定);覆盖层消费 */
|
||||
@@ -605,6 +620,11 @@ void meow_ui_text_input_async(meow_ime_ctx *ctx)
|
||||
ime_capture(); /* M2.71-bug7:打开前拍下键盘区背景(关闭即还原) */
|
||||
meow_input_start();
|
||||
meow_input_drain();
|
||||
/* bug3 加固(对齐 s_dd_modal/subpage 吸收 goodix 抬起长尾的「排空→40ms→再排空」惯例):
|
||||
* 第一遍 drain 后等 40ms 让输入线程读出迟到事件,第二遍 drain 连状态机一并复位。 */
|
||||
usleep(40000);
|
||||
meow_input_drain();
|
||||
g_ime_open_ms = meow_ui_time_ms(); /* TAP 冷却窗起点(见 IME_TAP_COOLDOWN_MS) */
|
||||
}
|
||||
|
||||
int meow_ui_text_input(meow_ime_ctx *ctx)
|
||||
|
||||
+33
-23
@@ -68,6 +68,8 @@
|
||||
|
||||
/* §6.4 事件分发形式化:手势捕获终点前向声明(meow_input_drain 之前定义后置,见下方实现)。 */
|
||||
void meow_ui_gesture_end(void);
|
||||
/* 触摸状态机复位(定义在触摸状态机变量之后,meow_input_drain 内调用)。 */
|
||||
void meow_input_touch_reset(void);
|
||||
|
||||
/* ── 内部事件:系统级「下拉展开通知中心」(M2.5x 接线)──
|
||||
* 触摸状态机在抬手判定处识别「SWIPE_DOWN 起始于顶部热区」后压入;事件循环按系统手势处理
|
||||
@@ -120,13 +122,6 @@ static uint64_t last_tick_ms = 0;
|
||||
static uint64_t startup_cooldown_until = 0;
|
||||
#define STARTUP_COOLDOWN_MS 500
|
||||
|
||||
static uint64_t now_ms(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t)ts.tv_sec * 1000u + (uint64_t)ts.tv_nsec / 1000000u;
|
||||
}
|
||||
|
||||
/* settings.conf 路径(可被 env MEOW_SETTINGS_FILE 覆盖,宿主测试用;设备默认同 meow_ui_sys.c)。 */
|
||||
static const char *meow_settings_path(void)
|
||||
{
|
||||
@@ -212,6 +207,10 @@ void meow_input_drain(void)
|
||||
g_qh = 0; g_qt = 0;
|
||||
pthread_mutex_unlock(&g_qm);
|
||||
meow_ui_gesture_end(); /* §6.4:清空队列=丢弃未完成手势(如开 IME/模态前),释放捕获防悬垂 */
|
||||
/* WiFi 设置页 bug3(2026-09-15):drain 同时复位触摸状态机——语义自洽:drain=丢弃在途
|
||||
* 手势。否则 drain 前已开始、drain 后才被输入线程读出的「抬起」仍会按残留原点产生
|
||||
* 一个迟到 TAP 入队(IME 打开瞬间被当「点背景=取消」或幻影按键,键盘开即关)。 */
|
||||
meow_input_touch_reset();
|
||||
}
|
||||
|
||||
static void push_ev(int type, int a, int b)
|
||||
@@ -260,6 +259,19 @@ static int t_mv_x = -1, t_mv_y = -1;
|
||||
/* §6.3 通用长按:本次按压是否已触发过长按(触发后抬手不再产生 TAP/SWIPE)。 */
|
||||
static int t_long_fired = 0;
|
||||
|
||||
/* 触摸状态机整体复位:丢弃在途手势(down/原点/坐标/长按触发标记全部归零)。
|
||||
* 调用点:① meow_input_drain(drain=丢弃在途手势,IME/模态打开前);② §10.5 叠加抑制
|
||||
* 进入沿(防抑制期丢失抬手致 t_down 卡死)。效果:复位前已开始、复位后才抬起的按压
|
||||
* 不再产生 TAP/SWIPE(WiFi 设置页 bug3:IME 打开 drain 之后才入队的迟到「抬起 TAP」
|
||||
* 曾把键盘当「点背景=取消」瞬间关闭)。 */
|
||||
void meow_input_touch_reset(void)
|
||||
{
|
||||
t_down = 0; t_press_valid = 0; t_origin_valid = 0;
|
||||
t_cx = -1; t_cy = -1; t_dx = 0; t_dy = 0; t_down_ms = 0;
|
||||
t_mv_x = -1; t_mv_y = -1;
|
||||
t_long_fired = 0;
|
||||
}
|
||||
|
||||
/* §6.3 通用长按:阈值(默认 MEOW_LONG_PRESS_MS;App 可经 meow_ui_set_long_press_ms 配)。 */
|
||||
static int g_long_press_ms = MEOW_LONG_PRESS_MS;
|
||||
/* §6.3 注册的长按回调(NULL=未注册;触发时以 (x,y) 调用,坐标=触发时刻按下点)。 */
|
||||
@@ -275,7 +287,7 @@ static void touch_press(int x, int y)
|
||||
t_origin_valid = 0;
|
||||
t_cx = -1; t_cy = -1;
|
||||
t_dx = 0; t_dy = 0;
|
||||
t_down_ms = now_ms(); /* 记按下时刻:供实时按住时长/按住视觉 */
|
||||
t_down_ms = meow_ui_time_ms(); /* 记按下时刻:供实时按住时长/按住视觉 */
|
||||
t_mv_x = -1; t_mv_y = -1; /* M2.71:拖动过程起点未定 */
|
||||
t_long_fired = 0; /* §6.3:每次新按压复位长按触发标记 */
|
||||
}
|
||||
@@ -373,7 +385,7 @@ int meow_input_touch_y(void) { return t_cy; }
|
||||
unsigned int meow_input_touch_hold_ms(void)
|
||||
{
|
||||
if (!t_down) return 0;
|
||||
uint64_t e = now_ms() - t_down_ms;
|
||||
uint64_t e = meow_ui_time_ms() - t_down_ms;
|
||||
if (e > 0xFFFFFFFFu) e = 0xFFFFFFFFu;
|
||||
return (unsigned int)e;
|
||||
}
|
||||
@@ -384,7 +396,7 @@ void meow_input_touch_set(int down, int x, int y, unsigned hold_ms)
|
||||
if (down) {
|
||||
t_down = 1; t_press_valid = 1; t_origin_valid = 1; t_long_fired = 0;
|
||||
t_cx = x; t_cy = y; t_dx = x; t_dy = y;
|
||||
uint64_t now = now_ms();
|
||||
uint64_t now = meow_ui_time_ms();
|
||||
t_down_ms = (now > (uint64_t)hold_ms) ? (now - (uint64_t)hold_ms) : 0;
|
||||
} else {
|
||||
t_down = 0; t_press_valid = 0; t_origin_valid = 0; t_long_fired = 0;
|
||||
@@ -525,7 +537,7 @@ static uint64_t g_sys_press_ms = 0;
|
||||
|
||||
static void sys_key_press(int code)
|
||||
{
|
||||
uint64_t now = now_ms();
|
||||
uint64_t now = meow_ui_time_ms();
|
||||
if (code == g_sys_press_code && now - g_sys_press_ms < SYS_KEY_DEDUP_MS)
|
||||
return; /* 事件流 + EVIOCGKEY 双源去重:同码窗口内只消化一次 */
|
||||
g_sys_press_code = code;
|
||||
@@ -592,9 +604,7 @@ static void *input_thread(void *arg)
|
||||
if (overlay_input_suppressed()) {
|
||||
if (!overlay_was) { /* 进入沿:复位触摸状态机,防抑制期丢失抬手致 t_down 卡死 */
|
||||
overlay_was = 1;
|
||||
t_down = 0; t_press_valid = 0; t_origin_valid = 0;
|
||||
t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; t_mv_x = t_mv_y = -1;
|
||||
t_long_fired = 0;
|
||||
meow_input_touch_reset();
|
||||
}
|
||||
if (r > 0)
|
||||
for (int i = 0; i < nfd; i++) {
|
||||
@@ -685,7 +695,7 @@ void meow_ui_request_exit(void) { g_exit = 1; }
|
||||
static uint64_t g_activity_last_ms = 0;
|
||||
void meow_ui_activity_ping(void)
|
||||
{
|
||||
uint64_t now = now_ms();
|
||||
uint64_t now = meow_ui_time_ms();
|
||||
if (now - g_activity_last_ms < 500) return; /* 节流:任意交互连续触摸也只 ~2 次/s */
|
||||
g_activity_last_ms = now;
|
||||
if (!meow_gfx_is_device()) return; /* 离屏/宿主:无 launcher 消费 */
|
||||
@@ -698,8 +708,8 @@ void meow_ui_run(const meow_ui_events *ev)
|
||||
if (ev) { g_events = *ev; g_events_set = 1; }
|
||||
g_exit = 0;
|
||||
meow_input_start();
|
||||
last_tick_ms = now_ms();
|
||||
startup_cooldown_until = now_ms() + STARTUP_COOLDOWN_MS; /* M2.22(C):启动窗口丢弃 SWIPE */
|
||||
last_tick_ms = meow_ui_time_ms();
|
||||
startup_cooldown_until = meow_ui_time_ms() + STARTUP_COOLDOWN_MS; /* M2.22(C):启动窗口丢弃 SWIPE */
|
||||
|
||||
while (!g_exit) {
|
||||
/* agent-119:实时触摸按压/按住/释放 → 即时重绘命中按钮区(不等 app 重绘)。
|
||||
@@ -707,10 +717,10 @@ void meow_ui_run(const meow_ui_events *ev)
|
||||
* §6.3 通用长按:每帧检测「按下 ≥ 阈值且位移 ≤ 容忍」跨越(IME 打开不驱动;启动窗口不触发)。 */
|
||||
if (!meow_ime_active()) {
|
||||
meow_widget_touch_refresh();
|
||||
if (now_ms() >= startup_cooldown_until) meow_input_long_press_poll();
|
||||
if (meow_ui_time_ms() >= startup_cooldown_until) meow_input_long_press_poll();
|
||||
} else if (meow_ime_mode_pinyin()) {
|
||||
/* 拼音 IME 打开:喂长按轮询(长按退格清空字段)。英文模式不喂(保持既有行为)。 */
|
||||
if (now_ms() >= startup_cooldown_until) meow_input_long_press_poll();
|
||||
if (meow_ui_time_ms() >= startup_cooldown_until) meow_input_long_press_poll();
|
||||
}
|
||||
|
||||
meow_input_ev e;
|
||||
@@ -719,7 +729,7 @@ void meow_ui_run(const meow_ui_events *ev)
|
||||
if (g_exit) break; /* 已请求退出:不再分发剩余事件,杜绝「退出后又触发」的偶发再进/退 */
|
||||
got = 1;
|
||||
meow_ui_activity_ping(); /* M2.71-bug5:任意交互事件都喂 launcher 重置息屏计时(节流) */
|
||||
int in_startup = (now_ms() < startup_cooldown_until); /* M2.22(C) */
|
||||
int in_startup = (meow_ui_time_ms() < startup_cooldown_until); /* M2.22(C) */
|
||||
if (e.type == MEOW_EV_TAP) {
|
||||
if (in_startup) continue; /* 启动窗口也吞 TAP:旧手势残留点按(如上一次左滑抬起)
|
||||
* 在进入瞬间命中 UI 按钮(如全屏按钮)会误触发——丢弃。 */
|
||||
@@ -834,7 +844,7 @@ void meow_ui_run(const meow_ui_events *ev)
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t now = now_ms();
|
||||
uint64_t now = meow_ui_time_ms();
|
||||
if (g_events_set && g_events.on_tick) {
|
||||
if (now - last_tick_ms >= 16) {
|
||||
last_tick_ms = now;
|
||||
@@ -860,7 +870,7 @@ void meow_ui_run(const meow_ui_events *ev)
|
||||
|
||||
if (meow_ime_active()) { /* 软键盘覆盖层随帧绘制(30fps 节流,省 CPU) */
|
||||
static uint64_t last_ime_ms = 0;
|
||||
uint64_t n = now_ms();
|
||||
uint64_t n = meow_ui_time_ms();
|
||||
if (n - last_ime_ms >= 33) {
|
||||
last_ime_ms = n;
|
||||
meow_ime_draw();
|
||||
@@ -880,7 +890,7 @@ void meow_ui_run(const meow_ui_events *ev)
|
||||
void meow_ui_feed_tick(void)
|
||||
{
|
||||
if (!(g_events_set && g_events.on_tick)) return;
|
||||
uint64_t now = now_ms();
|
||||
uint64_t now = meow_ui_time_ms();
|
||||
if (now - last_tick_ms < 16) return;
|
||||
last_tick_ms = now;
|
||||
g_events.on_tick();
|
||||
|
||||
+51
-1
@@ -172,6 +172,20 @@ int meow_ui_overlay_suppressed_active(void);
|
||||
/* 中文/日文字库(mfont)是否已加载(状态栏「蓝牙」CJK 与回退 "BT" 二选一)。 */
|
||||
int meow_gfx_mfont_ready(void);
|
||||
|
||||
/* ---- 矢量字(FreeType)渲染薄封装(meow_ui_gfx.c 提供 / meow_ui_ft.c 使用)----
|
||||
* meow_ui_ft.o 是独立成员:App 显式调 meow_ft_* 才被拉取,随之才拉入 freetype .o。
|
||||
* 为让「不调 meow_ft_* 的 App 零增量」(gfx.o 不引用 meow_ui_ft.o / FT 符号),gfx.o 只
|
||||
* 提供下面两个绘制原语薄封装(blend 一个灰度像素进 back_buf、标一块脏区)。 */
|
||||
void meow_gfx_ft_blend_px(int x, int y, uint32_t color, int alpha);
|
||||
void meow_gfx_ft_dirty(int x, int y, int w, int h);
|
||||
|
||||
/* ---- 矢量字(FreeType)字形访问(meow_ui_ft.c 提供;宿主单测直接调)----
|
||||
* 返回 0=成功;-1=无已载字体(调用方应降级位图字库)。位图归 LRU 缓存所有,本次用完前
|
||||
* 勿再调 meow_ft_glyph(同字号新光栅化可能逐出旧槽)。 */
|
||||
int meow_ft_glyph(int px, uint32_t cp, int *adv_x,
|
||||
const uint8_t **bmp, int *bw, int *bh, int *left, int *top);
|
||||
int meow_ft_advance(int px, uint32_t cp, int *adv_x); /* 仅测宽不栅格化、不占 LRU 槽 */
|
||||
|
||||
/* ---- 输入层(meow_ui_input.c)---- */
|
||||
/* 语义事件:由输入线程解析后压入队列,run/IME 循环弹出。 */
|
||||
#define MEOW_EV_KEY 1 /* a=keycode, b=1 按下 / 0 抬起 */
|
||||
@@ -193,8 +207,10 @@ void meow_input_start(void);
|
||||
int meow_input_pop(meow_input_ev *ev);
|
||||
/* 注入一条语义事件(宿主验证手势分发/进度条用;设备上无调用方)。 */
|
||||
void meow_input_inject(int type, int a, int b);
|
||||
/* 清空队列(IME 打开前丢弃残留触摸,避免误敲)。 */
|
||||
/* 清空队列(IME 打开前丢弃残留触摸,避免误敲);同时复位触摸状态机(drain=丢弃在途手势)。 */
|
||||
void meow_input_drain(void);
|
||||
/* 触摸状态机整体复位(丢弃在途手势;drain/叠加抑制进入沿调用;宿主单测可直调)。 */
|
||||
void meow_input_touch_reset(void);
|
||||
|
||||
/* ---- M2.71-bug5:App 活动 → 通知 launcher 重置息屏计时 ----
|
||||
* launcher 前台 app 会话的 app_last_input 只在物理键边沿重置(它看不到应用内触摸)。SDK 在
|
||||
@@ -288,4 +304,38 @@ int meow_ime_mode_pinyin(void);
|
||||
/* 秒/纳秒 → 单调毫秒的唯一换算点(meow_ui_time_ms 与宿主单测共用)。 */
|
||||
uint64_t meow_time_ms_from_parts(int64_t sec, int64_t nsec);
|
||||
|
||||
/* ---- mipsel o32 的 time32 结构(内核布局,与 libc 头的 time_t 宽度无关)----
|
||||
* 背景:zig clang 给 32 位目标预定义 __ILP32__,打穿 musl 的 time64 回退守卫,
|
||||
* 一批封装把 16 字节 time64 结构直接交给 time32 系统调用(CLOCK_ABI.md §6)。
|
||||
* 接管这些入口时统一走下面这几个纯转换函数——它们不依赖平台,宿主单测直接覆盖。 */
|
||||
struct meow_time32 {
|
||||
int32_t tv_sec;
|
||||
int32_t tv_nsec;
|
||||
};
|
||||
struct meow_itimerspec32 {
|
||||
struct meow_time32 it_interval;
|
||||
struct meow_time32 it_value;
|
||||
};
|
||||
/* 与 libc 结构解耦的 64 位镜像(避免宿主/交叉两端 time_t 宽度差异进测试)。 */
|
||||
struct meow_time64_spec {
|
||||
int64_t tv_sec;
|
||||
int64_t tv_nsec;
|
||||
};
|
||||
struct meow_time64_itimerspec {
|
||||
struct meow_time64_spec it_interval;
|
||||
struct meow_time64_spec it_value;
|
||||
};
|
||||
|
||||
/* 相对超时(futex / poll / select 的「等多久」):sec 超出 int32 时饱和
|
||||
* (正→INT32_MAX、负→INT32_MIN,与 musl CLAMP 一致),nsec 原样。 */
|
||||
void meow_time32_rel_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out);
|
||||
/* 绝对时刻(clock_settime / timerfd / utimensat):sec 落不进 int32 返回 -1
|
||||
* (调用方回 ENOTSUP,与 musl time64 路径的判定一致),nsec 原样。 */
|
||||
int meow_time32_abs_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out);
|
||||
/* itimerspec(timerfd / POSIX timer)转换;任一侧越界返回 -1 且 *out 不变。 */
|
||||
int meow_time32_itimerspec_from_64(const struct meow_time64_itimerspec *in,
|
||||
struct meow_itimerspec32 *out);
|
||||
void meow_time64_itimerspec_from_32(const struct meow_itimerspec32 *in,
|
||||
struct meow_time64_itimerspec *out);
|
||||
|
||||
#endif /* MEOW_UI_INTERNAL_H */
|
||||
+1
-8
@@ -46,13 +46,6 @@ static int g_statusbar_force = 0; /* 1s 刷新后置位,强制重画顶部
|
||||
* present() 据此决定是否画出顶条/提示线;App 进程保持 0(不渲染)+ 仍 inset 28(launcher 叠加)。 */
|
||||
static int g_sys_render = 0; /* 1=本进程为系统 UI(渲染);0=App(不渲染,仅 inset) */
|
||||
|
||||
static uint64_t now_ms(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t)ts.tv_sec * 1000u + (uint64_t)ts.tv_nsec / 1000000u;
|
||||
}
|
||||
|
||||
/* 读一份 sysfs/文件字符串(剥尾随换行);失败回退 "" */
|
||||
static void read_str(const char *path, char *buf, size_t n)
|
||||
{
|
||||
@@ -163,7 +156,7 @@ void meow_ui_sys_refresh(void)
|
||||
|
||||
/* 蓝牙:hci0 存在性检查便宜;上电状态贵(hciconfig spawn),30s 缓存。 */
|
||||
static uint64_t last_bt_ms = 0;
|
||||
uint64_t n = now_ms();
|
||||
uint64_t n = meow_ui_time_ms();
|
||||
g_sys.bt_seen = access("/sys/class/bluetooth/hci0", F_OK) == 0;
|
||||
if (!g_sys.bt_seen) {
|
||||
g_sys.bt_on = 0;
|
||||
|
||||
+207
-9
@@ -25,23 +25,75 @@
|
||||
* `usleep(16000)` 立即返回(SDK 里帧节拍/轮询会变忙等空转)。这里一并按 time32
|
||||
* 转换后重发,恢复亚秒睡眠。
|
||||
*
|
||||
* 残余(未修):futex 相对超时(pthread 定时等待的剩余时间按 8 字节读)、timerfd_settime、
|
||||
* clock_settime/adjtime、pselect/ppoll 超时——同为该守卫的受害者,见 CLOCK_ABI.md §6。
|
||||
* M16(同源第二弹;逐项证据见 CLOCK_ABI.md §6):同一句 `!__ILP32__` 还打穿了别的入口,
|
||||
* 本文件按风险分档接管:
|
||||
* - futex 相对超时(`__timedwait_cp`/`__timedwait`):**已接管**。它是进程同步核心,
|
||||
* 但 musl 原文逻辑可完整复刻(含 __syscall_cp 取消点语义),且是 SDK/app 唯一在用的
|
||||
* 受害者(meow_ui_audio 的 pthread_cond_timedwait 在亚秒等待下会立即超时)。
|
||||
* - timerfd_settime/gettime、clock_settime:**已接管**(纯参数转换,无内部状态)。
|
||||
* - pselect/ppoll/select、getrusage、wait4、clock_adjtime:**未受影响**(musl 回退分支
|
||||
* 自己就转成 `long[2]`/有宽度判断),本单只补证据,不改代码。
|
||||
* - sigtimedwait、semtimedop、timer_settime/gettime、utimensat、recvmmsg、mq_timedsend、
|
||||
* pthread_mutex_timedlock 的 PI 快路径:仍同源受损但 SDK/App 均未使用,风险收益比不划算,
|
||||
* 修复配方已写进 CLOCK_ABI.md §7 待后续单。
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h"
|
||||
|
||||
#if defined(__mips__) && !defined(__mips64) && (__SIZEOF_LONG__ == 4)
|
||||
#define MEOW_TIME32_KERNEL_ABI 1
|
||||
#include <pthread.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/timerfd.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#define MEOW_TIME32_KERNEL_ABI 0
|
||||
#endif
|
||||
|
||||
/* ---- 平台无关的 time64 → 内核 o32 time32 换算(宿主单测直接覆盖)----
|
||||
* 这几个函数不碰系统调用,只为“结构宽度”这件事提供唯一实现;mips 分支的接管入口
|
||||
* 全部经它们转换,宿主 test_meow_time64.c 用同一份代码验边界。 */
|
||||
void meow_time32_rel_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out)
|
||||
{
|
||||
if (sec > INT32_MAX) sec = INT32_MAX;
|
||||
else if (sec < INT32_MIN) sec = INT32_MIN;
|
||||
out->tv_sec = (int32_t)sec;
|
||||
out->tv_nsec = (int32_t)nsec;
|
||||
}
|
||||
|
||||
int meow_time32_abs_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out)
|
||||
{
|
||||
if (sec > INT32_MAX || sec < INT32_MIN) return -1;
|
||||
out->tv_sec = (int32_t)sec;
|
||||
out->tv_nsec = (int32_t)nsec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_time32_itimerspec_from_64(const struct meow_time64_itimerspec *in,
|
||||
struct meow_itimerspec32 *out)
|
||||
{
|
||||
struct meow_itimerspec32 t;
|
||||
if (meow_time32_abs_from_parts(in->it_interval.tv_sec, in->it_interval.tv_nsec,
|
||||
&t.it_interval)) return -1;
|
||||
if (meow_time32_abs_from_parts(in->it_value.tv_sec, in->it_value.tv_nsec,
|
||||
&t.it_value)) return -1;
|
||||
*out = t;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void meow_time64_itimerspec_from_32(const struct meow_itimerspec32 *in,
|
||||
struct meow_time64_itimerspec *out)
|
||||
{
|
||||
out->it_interval.tv_sec = in->it_interval.tv_sec;
|
||||
out->it_interval.tv_nsec = in->it_interval.tv_nsec;
|
||||
out->it_value.tv_sec = in->it_value.tv_sec;
|
||||
out->it_value.tv_nsec = in->it_value.tv_nsec;
|
||||
}
|
||||
|
||||
uint64_t meow_time_ms_from_parts(int64_t sec, int64_t nsec)
|
||||
{
|
||||
return (uint64_t)sec * 1000u + (uint64_t)nsec / 1000000u;
|
||||
@@ -50,7 +102,7 @@ uint64_t meow_time_ms_from_parts(int64_t sec, int64_t nsec)
|
||||
uint64_t meow_ui_time_ms(void)
|
||||
{
|
||||
#if MEOW_TIME32_KERNEL_ABI
|
||||
struct meow_time32 { int32_t tv_sec; int32_t tv_nsec; } t;
|
||||
struct meow_time32 t;
|
||||
t.tv_sec = 0;
|
||||
t.tv_nsec = 0;
|
||||
if (syscall(SYS_clock_gettime32, CLOCK_MONOTONIC, &t) != 0) return 0;
|
||||
@@ -63,12 +115,6 @@ uint64_t meow_ui_time_ms(void)
|
||||
}
|
||||
|
||||
#if MEOW_TIME32_KERNEL_ABI
|
||||
/* 内核 o32 time32 布局(与 libc 头的 time_t 宽度无关)。 */
|
||||
struct meow_time32 {
|
||||
int32_t tv_sec;
|
||||
int32_t tv_nsec;
|
||||
};
|
||||
|
||||
static int32_t time32_clamp(int64_t v)
|
||||
{
|
||||
if (v > INT32_MAX) return INT32_MAX;
|
||||
@@ -144,4 +190,156 @@ int __nanosleep_time64(const struct timespec *req, struct timespec *rem)
|
||||
if (r) { errno = r; return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- futex 相对超时(__timedwait_cp / __timedwait 强定义)----
|
||||
* 同源缺陷:musl thread/__timedwait.c 的 __futex4_cp 也在 `#if defined(SYS_futex_time64)
|
||||
* && !defined(__ILP32__)` 里做 time64,被 __ILP32__ 打穿后走 `#else`:
|
||||
* __syscall_cp(SYS_futex(4238), addr, op, val, to) ← to 是 16 字节 time64 结构
|
||||
* 内核按 o32 time32 只读前 8 字节 {int32 sec, int32 nsec}:
|
||||
* 偏移 0..3 = tv_sec 低字(够用),偏移 4..7 = tv_sec 高字(正常计时恒 0)。
|
||||
* 于是「还剩 0.4s」被当成 {0,0} → 立刻超时;「剩 2.3s」被当成 2s 整。
|
||||
* 受害者:pthread_cond_timedwait / sem_timedwait / pthread_join 超时 / aio_suspend
|
||||
* (SDK 的 meow_ui_audio.c 与 meow_audiod、ncm 播放线程都在用)。
|
||||
*
|
||||
* 风险控制:不重写任何同步算法,只把 musl 的 __timedwait_cp 原文搬过来,唯一改动是
|
||||
* 发系统调用前把相对超时转成 time32;取消点仍走 libc 的 __syscall_cp(ECANCELED/
|
||||
* EINTR 语义不变)。链接期接管必须同时定义 __timedwait —— 否则 libc 的 __timedwait.o
|
||||
* 会因 `__timedwait` 未解析被拉入,与本文的 __timedwait_cp 撞成 duplicate symbol
|
||||
* (ld.lld 实测会报错)。__eintr_valid_flag 给弱定义,真正的强定义在 libc sigaction.o。
|
||||
*
|
||||
* 未覆盖:pthread_mutex_timedlock 的 PTHREAD_PRIO_INHERIT 快路径(同文件里的静态
|
||||
* __futex4,直接调 FUTEX_LOCK_PI);SDK/App 未使用 PI 互斥量,见 CLOCK_ABI.md §7。 */
|
||||
#define MEOW_FUTEX_WAIT 0
|
||||
#define MEOW_FUTEX_PRIVATE 128
|
||||
|
||||
int __pthread_setcancelstate(int new, int *old);
|
||||
long __syscall_cp(long nr, long a, long b, long c, long d, long e, long f);
|
||||
__attribute__((weak)) volatile int __eintr_valid_flag = 0;
|
||||
|
||||
static int meow_futex4_cp(volatile void *addr, int op, int val, const struct timespec *to)
|
||||
{
|
||||
struct meow_time32 t32;
|
||||
void *top = 0;
|
||||
long r;
|
||||
|
||||
if (to) {
|
||||
meow_time32_rel_from_parts((int64_t)to->tv_sec, (int64_t)to->tv_nsec, &t32);
|
||||
top = &t32;
|
||||
}
|
||||
r = __syscall_cp(SYS_futex, (long)addr, op, val, (long)top, 0, 0);
|
||||
if (r != -ENOSYS) return (int)r;
|
||||
return (int)__syscall_cp(SYS_futex, (long)addr,
|
||||
op & ~MEOW_FUTEX_PRIVATE, val, (long)top, 0, 0);
|
||||
}
|
||||
|
||||
int __timedwait_cp(volatile int *addr, int val, clockid_t clk,
|
||||
const struct timespec *at, int priv);
|
||||
int __timedwait(volatile int *addr, int val, clockid_t clk,
|
||||
const struct timespec *at, int priv);
|
||||
|
||||
int __timedwait_cp(volatile int *addr, int val, clockid_t clk,
|
||||
const struct timespec *at, int priv)
|
||||
{
|
||||
int r;
|
||||
struct timespec to, *top = 0;
|
||||
|
||||
if (priv) priv = MEOW_FUTEX_PRIVATE;
|
||||
|
||||
if (at) {
|
||||
/* musl 原文是 `>= 1000000000UL`(负值按无符号也判 EINVAL);显式判负以保 -Wextra 0 warning */
|
||||
if (at->tv_nsec < 0 || at->tv_nsec >= 1000000000L) return EINVAL;
|
||||
if (__clock_gettime(clk, &to)) return EINVAL;
|
||||
to.tv_sec = at->tv_sec - to.tv_sec;
|
||||
if ((to.tv_nsec = at->tv_nsec - to.tv_nsec) < 0) {
|
||||
to.tv_sec--;
|
||||
to.tv_nsec += 1000000000;
|
||||
}
|
||||
if (to.tv_sec < 0) return ETIMEDOUT;
|
||||
top = &to;
|
||||
}
|
||||
|
||||
r = -meow_futex4_cp(addr, MEOW_FUTEX_WAIT | priv, val, top);
|
||||
if (r != EINTR && r != ETIMEDOUT && r != ECANCELED) r = 0;
|
||||
if (r == EINTR && !__eintr_valid_flag) r = 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
int __timedwait(volatile int *addr, int val, clockid_t clk,
|
||||
const struct timespec *at, int priv)
|
||||
{
|
||||
int cs, r;
|
||||
__pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
|
||||
r = __timedwait_cp(addr, val, clk, at, priv);
|
||||
__pthread_setcancelstate(cs, 0);
|
||||
return r;
|
||||
}
|
||||
|
||||
/* ---- timerfd(musl src/linux/timerfd.c 同款缺陷)----
|
||||
* 回退分支把 32 字节 time64 `struct itimerspec` 直接交给内核;o32 内核只认
|
||||
* 16 字节 {interval.sec, interval.nsec, value.sec, value.nsec}。实际读到的
|
||||
* value.sec = 调用者 interval.tv_nsec(错位),value.nsec = 结构填充(垃圾);
|
||||
* gettime 反向把内核的前 16 字节写进错位的字段。SDK/App 当前未用 timerfd,
|
||||
* 但成本极低,顺手按内核 ABI 转换。三个符号必须同一 TU 全定义:libc 的
|
||||
* timerfd.o 同时提供 create/settime/gettime,缺一个就会把它拉进来导致重复定义。 */
|
||||
int timerfd_create(int clockid, int flags)
|
||||
{
|
||||
return (int)syscall(SYS_timerfd_create, clockid, flags);
|
||||
}
|
||||
|
||||
int __timerfd_settime64(int fd, int flags, const struct itimerspec *new,
|
||||
struct itimerspec *old);
|
||||
int __timerfd_gettime64(int fd, struct itimerspec *cur);
|
||||
|
||||
int __timerfd_settime64(int fd, int flags, const struct itimerspec *new,
|
||||
struct itimerspec *old)
|
||||
{
|
||||
struct meow_time64_itimerspec in;
|
||||
struct meow_itimerspec32 n32, o32;
|
||||
|
||||
if (!new) { errno = EFAULT; return -1; }
|
||||
in.it_interval.tv_sec = new->it_interval.tv_sec;
|
||||
in.it_interval.tv_nsec = new->it_interval.tv_nsec;
|
||||
in.it_value.tv_sec = new->it_value.tv_sec;
|
||||
in.it_value.tv_nsec = new->it_value.tv_nsec;
|
||||
if (meow_time32_itimerspec_from_64(&in, &n32)) { errno = ENOTSUP; return -1; }
|
||||
|
||||
if (syscall(SYS_timerfd_settime32, fd, flags, &n32, old ? &o32 : 0) != 0)
|
||||
return -1;
|
||||
if (old) {
|
||||
old->it_interval.tv_sec = o32.it_interval.tv_sec;
|
||||
old->it_interval.tv_nsec = o32.it_interval.tv_nsec;
|
||||
old->it_value.tv_sec = o32.it_value.tv_sec;
|
||||
old->it_value.tv_nsec = o32.it_value.tv_nsec;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __timerfd_gettime64(int fd, struct itimerspec *cur)
|
||||
{
|
||||
struct meow_itimerspec32 c32;
|
||||
|
||||
if (!cur) { errno = EFAULT; return -1; }
|
||||
if (syscall(SYS_timerfd_gettime32, fd, &c32) != 0) return -1;
|
||||
cur->it_interval.tv_sec = c32.it_interval.tv_sec;
|
||||
cur->it_interval.tv_nsec = c32.it_interval.tv_nsec;
|
||||
cur->it_value.tv_sec = c32.it_value.tv_sec;
|
||||
cur->it_value.tv_nsec = c32.it_value.tv_nsec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- clock_settime(musl src/time/clock_settime.c 同款缺陷)----
|
||||
* 回退分支把 16 字节 time64 结构直接交给 SYS_clock_settime32;内核只写/读 8 字节,
|
||||
* nsec 位永远为 0。绝对时刻超出 int32 时回 ENOTSUP(与 musl time64 路径一致)。 */
|
||||
int __clock_settime64(clockid_t clk, const struct timespec *ts)
|
||||
{
|
||||
struct meow_time32 t;
|
||||
|
||||
if (!ts) { errno = EFAULT; return -1; }
|
||||
if (meow_time32_abs_from_parts((int64_t)ts->tv_sec, (int64_t)ts->tv_nsec, &t)) {
|
||||
errno = ENOTSUP;
|
||||
return -1;
|
||||
}
|
||||
if (syscall(SYS_clock_settime32, clk, &t) != 0) return -1;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
+35
-10
@@ -79,7 +79,7 @@ struct meow_widget {
|
||||
int maxlen; /* W_TEXTFIELD 上限(含 NUL) */
|
||||
meow_ime_mode ime_mode; /* W_TEXTFIELD IME 模式 */
|
||||
int value; /* W_PROGRESS:0-100 / -1=不定; W_BADGE:计数 */
|
||||
int item_h; /* W_LIST:行高/列宽(lazy),默认 44 */
|
||||
int item_h; /* W_LIST:行高/列宽(lazy),默认 44;W_LISTVIEW:显式行高,0=按字体行高派生 */
|
||||
int scroll; /* W_LIST:滚动偏移(lazy) */
|
||||
int horizontal; /* W_LIST:1=横向(LazyRow) */
|
||||
int back_on; /* W_TOPAPPBAR:显示返回箭头 */
|
||||
@@ -1051,7 +1051,24 @@ int meow_list_set_container(meow_widget *w, int on)
|
||||
* 宿主/设备需图片图标的 App 经 meow_ui_img_load_* 强引用 meow_ui_img.o → 此处自动解析。 */
|
||||
extern void meow_ui_img_scaled(int x, int y, int w, int h, meow_img *img) __attribute__((weak));
|
||||
|
||||
static int listv_item_h(const meow_widget *w) { return (w->item_h > 0) ? w->item_h : 56; }
|
||||
/* 行高:显式 meow_listview_set_item_h 优先(App 自管行几何时按自己算出的值传,如 ncm 70);
|
||||
* 未显式(item_h<=0)→ 按当前主题正文字体行高派生,保证「文本块 ≤ 行高」:
|
||||
* lines=2:max(56, 2*line_h + MEOW_LISTV_SUB_GAP) (20px 字库行高 30 → 62)
|
||||
* lines=1:max(48, line_h)
|
||||
* 旧默认固定 56 在真机 source_han_serif 20px(ascent 24 + descent 6 = 30)下小于双行
|
||||
* 文本块 62 → 居中后 ty = 行顶 - 3,首行(行顶==视口顶)标题被 `ty >= vy0` 裁剪守卫整条
|
||||
* 跳过,只剩副标题(M12 finding / M15 本源修复)。派生值由主题字库实时算出,draw/hit/
|
||||
* max_scroll 三路共用同一函数 → 行几何恒一致;改字库档位/主题后自动跟随。 */
|
||||
static int listv_item_h(const meow_widget *w)
|
||||
{
|
||||
if (w->item_h > 0) return w->item_h;
|
||||
const meow_theme *th = meow_theme_get();
|
||||
int line_h = th ? meow_ui_text_height(th->font_body) : 0;
|
||||
if (w->lines == 1)
|
||||
return (line_h > 48) ? line_h : 48;
|
||||
int h = 2 * line_h + MEOW_LISTV_SUB_GAP;
|
||||
return (h > 56) ? h : 56;
|
||||
}
|
||||
|
||||
/* §5.5 虚拟化辅助:当前行数(pool 模式实时查 count_fn;src2 模式用缓存的 w->count)。 */
|
||||
static int listv_count(const meow_widget *w)
|
||||
@@ -1203,7 +1220,7 @@ meow_widget *meow_listview_create(int x, int y, int w, int h, int count,
|
||||
t->owner = meow_ui_activity_active();
|
||||
t->state = MEOW_WS_ENABLED;
|
||||
t->count = count > 0 ? count : 0; t->src2 = src; t->ctx = ctx;
|
||||
t->sel = -1; t->item_h = 56; t->scroll = 0; t->swipe_idx = -1;
|
||||
t->sel = -1; t->item_h = 0; t->scroll = 0; t->swipe_idx = -1; /* item_h=0 → 按字体行高派生(见 listv_item_h) */
|
||||
t->sel_mode = (mode >= MEOW_LIST_SEL_NONE && mode <= MEOW_LIST_SEL_MULTI) ? mode : MEOW_LIST_SEL_NONE;
|
||||
t->lines = 2; t->show_icon = 1; t->show_right = 1; /* 默认双行富 item + 图标 + 右尾件(src2 布局不变) */
|
||||
t->container = 1; /* M6.37 P1:SDK 默认列表走 MD3 容器模式——行=surfaceContainer 圆角卡 + 空隙 + 去分隔线 + 卡内 tonal 选中,
|
||||
@@ -1225,7 +1242,12 @@ int meow_listview_set_item_h(meow_widget *w, int item)
|
||||
listv_ensure_pool(w); /* 行高变小 → 可见行增多 → 池容量随之调整 */
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
|
||||
}
|
||||
return w->item_h;
|
||||
return listv_item_h(w); /* 返回当前生效行高(显式值或字体派生值) */
|
||||
}
|
||||
int meow_listview_item_h(meow_widget *w)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return 0;
|
||||
return listv_item_h(w);
|
||||
}
|
||||
void meow_listview_set_count(meow_widget *w, int count)
|
||||
{
|
||||
@@ -1398,7 +1420,7 @@ int meow_listview_set_item_renderer(meow_widget *w, int lines, int show_icon,
|
||||
if (lines < 1) lines = 1;
|
||||
if (lines > 2) lines = 2;
|
||||
w->lines = lines; w->show_icon = show_icon ? 1 : 0; w->show_right = show_right ? 1 : 0;
|
||||
if (w->item_h <= 0) w->item_h = (lines == 1) ? 48 : 56; /* 未显式定行高 → 档位默认 */
|
||||
/* 未显式定行高(item_h<=0)→ 保持派生模式(按 lines 档位从字体行高算),不写死像素 */
|
||||
listv_ensure_pool(w); /* 行档位可能变化 → 确保池容量 */
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
|
||||
return 0;
|
||||
@@ -1410,7 +1432,7 @@ int meow_listview_set_item_tier(meow_widget *w, int lines)
|
||||
if (lines < 1) lines = 1;
|
||||
if (lines > 2) lines = 2;
|
||||
w->lines = lines;
|
||||
w->item_h = (lines == 1) ? 48 : 56; /* 档位默认高(可随后 set_item_h 覆盖) */
|
||||
w->item_h = 0; /* 档位=字体派生默认(单行 max(48,行高) / 双行 max(56,2*行高+2));set_item_h 可覆盖 */
|
||||
listv_ensure_pool(w);
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
|
||||
return 0;
|
||||
@@ -2007,8 +2029,9 @@ void meow_widget_draw(meow_widget *w)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* ① src2 模式(既有):每帧按需取行(meow_list2_source),行为不变。
|
||||
* 行 = 图标(左) + 标题 + 副标题(次要色) + 右侧操作区;选中行 = primaryContainer。 */
|
||||
/* ① src2 模式(既有):每帧按需取行(meow_list2_source),虚拟化行为不变。
|
||||
* 行 = 图标(左) + 标题 + 副标题(次要色) + 右侧操作区;选中行 = primaryContainer。
|
||||
* lines=1(set_item_renderer/tier 单行档位)时不画副标题,与 pool 分支同口径。 */
|
||||
for (int i = first; i < last; i++) {
|
||||
meow_list_item it;
|
||||
memset(&it, 0, sizeof it);
|
||||
@@ -2055,9 +2078,11 @@ void meow_widget_draw(meow_widget *w)
|
||||
if (icy + isz >= vy0 && icy < vy1) /* 图标不越视口边界才画(防泄漏) */
|
||||
listv_draw_icon(th, rlx + pad, icy, it.icon_kind, it.icon, iccol);
|
||||
|
||||
/* 标题 + 副标题(两行居中;无副标题=单行居中;越界不画防泄漏) */
|
||||
/* 标题 + 副标题(lines=2 有副标题→两行居中;lines=1 或无副标题→单行居中;越界不画防泄漏)。
|
||||
* lines 档位与 pool 分支同口径:tier/renderer 设单行时明确不画副标题,
|
||||
* 否则 48px 单行档位里塞 62px 文本块 → 首行标题被裁剪守卫跳过。 */
|
||||
int ty;
|
||||
if (it.subtitle && it.subtitle[0]) {
|
||||
if (w->lines == 2 && it.subtitle && it.subtitle[0]) {
|
||||
ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2;
|
||||
if (it.title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(rtx, ty, it.title, text_maxw, th->font_body, textcol);
|
||||
|
||||
+42
-8
@@ -3,15 +3,18 @@
|
||||
* ② K_ENTER 只提交不关(meow_ime_active() 仍真),ESC 后关;
|
||||
* ③ 阻塞 IME 循环喂 on_tick(动态内容重绘)且 IME 面板仍在最上
|
||||
* (On_tick 在 IME 面板区画红色哨兵行,IME 绘制后应盖回面板色,不被 App 覆盖)。
|
||||
* ④ bug3(2026-09-15):IME 打开 ~200ms TAP 冷却窗——打开后立即注入的 stray-TAP
|
||||
* (背景/键区)被吞不取消;冷却窗后背景 TAP=取消语义保留。
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -Wall -Wextra -o test_ime_fix test_ime_fix.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_media.c \
|
||||
* meow_ui_img.c meow_notify_ipc.c stb_image.c meow_ui_nav.c \
|
||||
* -I. -Iinclude -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* (meow_ui_widget.c 新 W_LISTVIEW 引用 meow_ui_img_scaled → 需链 meow_ui_img.c+stb_image.c+-lm)
|
||||
* meow_ui_draw.c meow_ui_view.c meow_ui_img.c meow_notify_ipc.c stb_image.c meow_jpeg.c meow_png_stream.c \
|
||||
* meow_ui_anim.c meow_ui_media.c meow_ui_nav.c meow_pinyin.c meow_ui_box.c meow_ui_scroll.c \
|
||||
* -I. -Iinclude -DMEOW_UI_HOST_TEST -pthread -lm -lz
|
||||
* (meow_ui_widget.c 新 W_LISTVIEW 引用 meow_ui_img_scaled → 需链 meow_ui_img.c+stb_image.c+-lm;
|
||||
* widget/input/ime/view 后续又引入 anim/media/nav/pinyin/box/scroll 依赖,一并链接)
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h"
|
||||
@@ -55,6 +58,38 @@ static int near(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 14
|
||||
/* ---- ① 异步 shift 测试(直接 handle_tap,确定语义,无阻塞循环) ---- */
|
||||
static char g_echo[64] = "";
|
||||
static void ime_cb(const char *t, void *ctx) { (void)ctx; snprintf(g_echo, sizeof g_echo, "%s", t); }
|
||||
|
||||
/* ④ WiFi 设置页 bug3(2026-09-15):IME 打开 ~200ms TAP 冷却窗——触发打开那次点按的迟到
|
||||
* 「抬起 TAP」(goodix 长尾,经 SDK 输入线程 drain 后才入队)不得把键盘当「点背景=取消」
|
||||
* 瞬间关掉;冷却窗后点背景=取消的语义保留。 */
|
||||
static void test_stray_tap(void)
|
||||
{
|
||||
meow_ime_ctx c; memset(&c, 0, sizeof c);
|
||||
c.mode = MEOW_IME_ASCII; c.maxlen = 32; c.on_change = ime_cb;
|
||||
meow_ui_text_input_async(&c);
|
||||
assert(meow_ime_active() == 1);
|
||||
|
||||
/* 打开后立即注入背景 TAP(y<KB_TOP=420):冷却窗内必须被吞——不取消、键盘仍 active */
|
||||
meow_input_ev stray = { MEOW_EV_TAP, 240, 100 };
|
||||
assert(meow_ime_handle_event(&stray) == 1); /* 消费(吞掉) */
|
||||
assert(meow_ime_active() == 1); /* 键盘未被关(修复前此处已 done=取消) */
|
||||
|
||||
/* 窗口内键区 TAP 也丢弃(防幻影按键) */
|
||||
meow_input_ev key = { MEOW_EV_TAP, K_Q_X, K_Q_Y };
|
||||
assert(meow_ime_handle_event(&key) == 1);
|
||||
assert(meow_ime_active() == 1);
|
||||
assert(meow_ime_text()[0] == 0); /* 无幻影字符入缓冲 */
|
||||
|
||||
/* 冷却窗(200ms)后:点背景=取消 语义保留 */
|
||||
usleep(250000);
|
||||
assert(meow_ime_handle_event(&stray) == 1);
|
||||
assert(meow_ime_active() == 0); /* 已取消(g_ime_done) */
|
||||
printf("PASS stray-tap: 窗内背景/键区 TAP 吞掉不取消, 窗后背景 TAP 取消\n"); fflush(stdout);
|
||||
|
||||
meow_ime_close(0); /* 复位(清 g_ime_done/active) */
|
||||
assert(meow_ime_active() == 0);
|
||||
}
|
||||
|
||||
static void test_shift(void)
|
||||
{
|
||||
meow_ime_ctx c; memset(&c, 0, sizeof c);
|
||||
@@ -95,8 +130,6 @@ static char g_drv_text[64] = ""; /* shift 序列后 meow_ime_text()(期
|
||||
/* 把 back_buf 写成 PPM(视觉目检 IME 面板渲染/最上;在 IME 空闲时调用,back_buf 稳定) */
|
||||
static void dump_ppm(const char *path)
|
||||
{
|
||||
unsigned char *bb = meow_ui_back_buf();
|
||||
int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp();
|
||||
int W = meow_ui_width(), H = meow_ui_height();
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return;
|
||||
@@ -135,7 +168,7 @@ static void *driver(void *arg)
|
||||
(void)arg;
|
||||
usleep(600000); /* 等 meow_ui_run 起动(startup cooldown=500ms) */
|
||||
meow_input_inject(MEOW_EV_TAP, 150, 322); /* 点 TextField → 阻塞 IME */
|
||||
usleep(160000);
|
||||
usleep(350000); /* 等过 IME 打开 TAP 冷却窗(200ms,bug3)+ drain 40ms */
|
||||
meow_input_inject(MEOW_EV_TAP, K_Q_X, K_Q_Y); /* shift 关 → 'q' */
|
||||
usleep(90000);
|
||||
meow_input_inject(MEOW_EV_TAP, K_SHIFT_X, K_SHIFT_Y); /* shift 开 */
|
||||
@@ -170,6 +203,7 @@ int main(void)
|
||||
const meow_theme *th = meow_theme_get();
|
||||
|
||||
test_shift(); /* ① */
|
||||
test_stray_tap(); /* ④ bug3:打开冷却窗吞 stray-TAP */
|
||||
|
||||
/* ②③ 在 meow_ui_run 里驱动(g_events 仅 meow_ui_run 能设置) */
|
||||
g_tf = meow_textfield_create(10, 300, 300, 44, "Type here...", g_tfbuf, 32, MEOW_IME_ASCII);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 另链批1 meow_lyric.c 提供歌词数据;meow_lyric_view.c 为被测控件):
|
||||
* gcc -Wall -Wextra -o test_meow_lyric_view test_meow_lyric_view.c test_host_stub.c \
|
||||
* meow_lyric.c meow_lyric_view.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c meow_ui_nav.c \
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -Wall -Wextra -o test_meow_md3 test_meow_md3.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c meow_ui_nav.c \
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
/* test_meow_time64.c — 宿主单测:time64 → 内核 o32 time32 的换算与结构宽度。
|
||||
*
|
||||
* 背景(M16):zig clang 给 32 位 mips 预定义 __ILP32__,打穿 musl 的 time64 回退守卫,
|
||||
* futex 相对超时 / timerfd / clock_settime 等入口把 16 字节(time64)结构直接交给 time32
|
||||
* 系统调用。meow_ui_time.c 的接管入口统一经 meow_ui_internal.h 里这几个纯换算函数转换,
|
||||
* 本测试在宿主上把「换算结果 + 内核实际读到的宽度」固定住,不需要 mips 设备。
|
||||
*
|
||||
* 编译(patch_v2/sdk 目录下):
|
||||
* gcc -Os -Wall -Wextra -I. -Iinclude -c meow_ui_time.c -o /tmp/meow_ui_time.o
|
||||
* gcc -Os -Wall -Wextra -I. -Iinclude test_meow_time64.c /tmp/meow_ui_time.o \
|
||||
* -o test_meow_time64
|
||||
* 运行:./test_meow_time64 (exit 0=PASS / 1=FAIL)
|
||||
*/
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h"
|
||||
|
||||
static int g_fail = 0;
|
||||
|
||||
static void check(const char *name, int ok)
|
||||
{
|
||||
if (ok) {
|
||||
printf("ok %s\n", name);
|
||||
} else {
|
||||
printf("FAIL %s\n", name);
|
||||
g_fail = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void check_time32(const char *name, const struct meow_time32 *got,
|
||||
int32_t sec, int32_t nsec)
|
||||
{
|
||||
if (got->tv_sec == sec && got->tv_nsec == nsec) {
|
||||
printf("ok %s = {%d, %d}\n", name, (int)got->tv_sec, (int)got->tv_nsec);
|
||||
} else {
|
||||
printf("FAIL %s = {%d, %d},期望 {%d, %d}\n", name,
|
||||
(int)got->tv_sec, (int)got->tv_nsec, (int)sec, (int)nsec);
|
||||
g_fail = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 模拟 o32 内核对「调用者给的指针」的实际读法:只读前 8 字节,按两个 int32 解释。 */
|
||||
static void kernel_time32_view(const void *ptr, struct meow_time32 *out)
|
||||
{
|
||||
memcpy(out, ptr, sizeof *out);
|
||||
}
|
||||
|
||||
static void test_rel(void)
|
||||
{
|
||||
struct meow_time32 t;
|
||||
|
||||
meow_time32_rel_from_parts(5, 123456789, &t);
|
||||
check_time32("rel 普通值", &t, 5, 123456789);
|
||||
|
||||
meow_time32_rel_from_parts(0, 500000000, &t);
|
||||
check_time32("rel 亚秒(原缺陷会变 {0,0})", &t, 0, 500000000);
|
||||
|
||||
meow_time32_rel_from_parts(INT32_MAX, 999999999, &t);
|
||||
check_time32("rel int32 上界", &t, INT32_MAX, 999999999);
|
||||
|
||||
meow_time32_rel_from_parts((int64_t)INT32_MAX + 1, 1, &t);
|
||||
check_time32("rel 上溢饱和到 INT32_MAX", &t, INT32_MAX, 1);
|
||||
|
||||
meow_time32_rel_from_parts(INT64_MAX, 0, &t);
|
||||
check_time32("rel INT64_MAX 饱和", &t, INT32_MAX, 0);
|
||||
|
||||
meow_time32_rel_from_parts(-1, 500000000, &t);
|
||||
check_time32("rel 负值透传(调用方另判 ETIMEDOUT)", &t, -1, 500000000);
|
||||
|
||||
meow_time32_rel_from_parts((int64_t)INT32_MIN - 1, 0, &t);
|
||||
check_time32("rel 下溢饱和到 INT32_MIN", &t, INT32_MIN, 0);
|
||||
}
|
||||
|
||||
static void test_abs(void)
|
||||
{
|
||||
struct meow_time32 t;
|
||||
|
||||
t.tv_sec = 7;
|
||||
t.tv_nsec = 7;
|
||||
check("abs 普通值", meow_time32_abs_from_parts(1700000000, 250000000, &t) == 0);
|
||||
check_time32("abs 普通值内容", &t, 1700000000, 250000000);
|
||||
|
||||
check("abs int32 上界接受", meow_time32_abs_from_parts(INT32_MAX, 0, &t) == 0);
|
||||
check("abs int32+1 拒绝", meow_time32_abs_from_parts((int64_t)INT32_MAX + 1, 0, &t) == -1);
|
||||
check("abs int32 下界接受", meow_time32_abs_from_parts(INT32_MIN, 0, &t) == 0);
|
||||
check("abs int32-1 拒绝", meow_time32_abs_from_parts((int64_t)INT32_MIN - 1, 0, &t) == -1);
|
||||
check("abs 2038 后一秒拒绝", meow_time32_abs_from_parts(2147483648LL, 0, &t) == -1);
|
||||
}
|
||||
|
||||
static void test_itimerspec(void)
|
||||
{
|
||||
struct meow_time64_itimerspec in;
|
||||
struct meow_itimerspec32 out, back32;
|
||||
struct meow_time64_itimerspec back;
|
||||
|
||||
in.it_interval.tv_sec = 1;
|
||||
in.it_interval.tv_nsec = 250000000;
|
||||
in.it_value.tv_sec = 3600;
|
||||
in.it_value.tv_nsec = 999999999;
|
||||
memset(&out, 0xAA, sizeof out);
|
||||
check("itimerspec 四字段转换成功",
|
||||
meow_time32_itimerspec_from_64(&in, &out) == 0);
|
||||
check_time32("itimerspec interval", &out.it_interval, 1, 250000000);
|
||||
check_time32("itimerspec value", &out.it_value, 3600, 999999999);
|
||||
|
||||
/* 越界必须整体失败且不改 *out(timerfd_settime 回 ENOTSUP 的前提) */
|
||||
memcpy(&back32, &out, sizeof back32);
|
||||
in.it_value.tv_sec = (int64_t)INT32_MAX + 1;
|
||||
check("itimerspec 越界拒绝", meow_time32_itimerspec_from_64(&in, &out) == -1);
|
||||
check("itimerspec 越界不写坏输出", memcmp(&back32, &out, sizeof out) == 0);
|
||||
|
||||
/* 反向(timerfd_gettime)字段不串位 */
|
||||
memset(&back, 0, sizeof back);
|
||||
meow_time64_itimerspec_from_32(&back32, &back);
|
||||
check("itimerspec 反向 sec/nsec 不串位",
|
||||
back.it_interval.tv_sec == 1 && back.it_interval.tv_nsec == 250000000 &&
|
||||
back.it_value.tv_sec == 3600 && back.it_value.tv_nsec == 999999999);
|
||||
}
|
||||
|
||||
/* 结构宽度证明:同一块内存按「libc 头 ABI」与「内核 o32 ABI」读出的值不同。 */
|
||||
static void test_struct_width(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
if (sizeof(time_t) != 8 || offsetof(struct timespec, tv_nsec) != 8) {
|
||||
printf("skip 结构宽度模拟(宿主 time_t/timespec 布局与 ILP32+time64 不一致)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* 剩 0.4s:16 字节 time64 结构的前 8 字节被内核读成 {sec=0, nsec=0} → 立即超时 */
|
||||
memset(&ts, 0, sizeof ts);
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = 400000000;
|
||||
struct meow_time32 old_view;
|
||||
kernel_time32_view(&ts, &old_view);
|
||||
check("旧 ABI:0.4s 被内核读成 {0,0}(立即超时)",
|
||||
old_view.tv_sec == 0 && old_view.tv_nsec == 0);
|
||||
|
||||
/* 剩 2.3s:旧 ABI 丢掉 0.3s,只剩 2s 整 */
|
||||
memset(&ts, 0, sizeof ts);
|
||||
ts.tv_sec = 2;
|
||||
ts.tv_nsec = 300000000;
|
||||
kernel_time32_view(&ts, &old_view);
|
||||
check("旧 ABI:2.3s 被内核读成 2s 整",
|
||||
old_view.tv_sec == 2 && old_view.tv_nsec == 0);
|
||||
|
||||
/* 新路径:先转换再交给内核,宽度与值都对 */
|
||||
struct meow_time32 fixed;
|
||||
meow_time32_rel_from_parts((int64_t)ts.tv_sec, (int64_t)ts.tv_nsec, &fixed);
|
||||
struct meow_time32 new_view;
|
||||
kernel_time32_view(&fixed, &new_view);
|
||||
check("新 ABI:内核读到 {2, 300000000}", new_view.tv_sec == 2 &&
|
||||
new_view.tv_nsec == 300000000);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
test_rel();
|
||||
test_abs();
|
||||
test_itimerspec();
|
||||
test_struct_width();
|
||||
|
||||
printf(g_fail ? "== FAIL ==\n" : "== PASS ==\n");
|
||||
return g_fail;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/draw/activity/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_anim test_meow_ui_anim.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c meow_ui_nav.c \
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 widget/draw/activity/theme/gfx + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_box test_meow_ui_box.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c meow_ui_nav.c \
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* 编译(宿主 gcc,链真实 widget/draw/input/activity/gfx/sys/theme/vol/log/dialog/ime/notify/ipc +
|
||||
* 打桩 font/global;完整超集便于链接):
|
||||
* gcc -Wall -Wextra -o test_meow_ui_dispatch test_meow_ui_dispatch.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c meow_ui_nav.c \
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* 编译(宿主 gcc,链真实 SDK 源全集 + 打桩 font/global;**不链 meow_ui_nav.c**——本测以测试内
|
||||
* meow_ui_nav_request_exit 桩计数验证触发):
|
||||
* gcc -Wall -Wextra -o test_meow_ui_home_gesture test_meow_ui_home_gesture.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -Wall -Wextra -o test_meow_ui_iconbtn test_meow_ui_iconbtn.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c meow_ui_nav.c \
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_lazygrid test_meow_ui_lazygrid.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_lazygrid.c meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c meow_ui_nav.c \
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
/* test_meow_ui_listview_rowh.c — 宿主回归(M15):listview 默认行高必须由字体行高派生,
|
||||
* 首行(scroll=0)标题必须真正画出来。
|
||||
*
|
||||
* 根因(M12 finding):双行 renderer 把「标题+副标题」文本块
|
||||
* block = 2 * meow_ui_text_height(font_body) + MEOW_LISTV_SUB_GAP(2)
|
||||
* 在行内垂直居中(ty = 行顶 + (item - block)/2),且文本只在 `ty >= 视口顶` 时才画。
|
||||
* 真机 source_han_serif 20px(ascent 24 + descent 6)行高 = 30 → block = 62;
|
||||
* 旧 SDK 默认行高固定 56 < 62 → ty = 行顶 - 3,首行(行顶 == 视口顶)标题被整条跳过,
|
||||
* 只剩副标题 → 所有 App 的两行列表首行标题消失。
|
||||
*
|
||||
* 宿主 5x7 字体行高是 14,照原样永远复现不了。故用链接期 `-Wl,--wrap=meow_ui_text_height`
|
||||
* 把行高替换成真机实测的 30,驱动同一份 SDK 代码(同 M12 app/test/test_first_row_title_render.c):
|
||||
* A 行高 30 + 显式 item_h=56 → 首行标题带 0 像素(bug 形态,防回归哨兵)
|
||||
* B 行高 30 + 不设 item_h → 派生 62 = block,首行标题/副标题都有像素
|
||||
* C 行高 30 + 显式 item_h=70 → ncm 语义(App 自管卡高)原样生效,标题可见
|
||||
* 断言全部读 back_buf 真实像素(region_has),非目检。
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_listview_rowh test_meow_ui_listview_rowh.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c meow_ui_nav.c \
|
||||
* meow_jpeg.c meow_png_stream.c \
|
||||
* -I. -Iinclude -DMEOW_UI_HOST_TEST -pthread -lm -lz \
|
||||
* -Wl,--wrap=meow_ui_text_height
|
||||
* 运行:./test_meow_ui_listview_rowh
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp:真实像素断言 */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* --wrap 替换实现:真机 source_han_serif 20px 实测行高(font.idx ascent+descent)。 */
|
||||
int __wrap_meow_ui_text_height(int size)
|
||||
{
|
||||
(void)size;
|
||||
return 30;
|
||||
}
|
||||
|
||||
#define LH 30 /* 真机正文行高 */
|
||||
#define BLOCK (2 * LH + 2) /* 双行文本块 = 62(与 MEOW_LISTV_SUB_GAP 同口径) */
|
||||
#define LV_X 24
|
||||
#define LV_Y 40
|
||||
#define LV_W 432
|
||||
#define LV_H 180
|
||||
#define NR 5 /* 行数 */
|
||||
|
||||
static int g_pass = 0, g_fail = 0;
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (cond) { g_pass++; } \
|
||||
else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \
|
||||
} while (0)
|
||||
|
||||
/* ---- back_buf 像素读取(画完即读,真实渲染断言) ---- */
|
||||
static void rd_px(int x, int y, int *r, int *g, int *b)
|
||||
{
|
||||
unsigned char *bb = meow_ui_back_buf();
|
||||
int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp();
|
||||
if (!bb || x < 0 || y < 0) { *r = *g = *b = 0; return; }
|
||||
if (bpp == 16) {
|
||||
uint16_t px = *(uint16_t *)(bb + (size_t)y * line + (size_t)x * 2);
|
||||
*r = (((px >> 11) & 0x1F) << 3) | ((px >> 11) & 0x1F) >> 2;
|
||||
*g = (((px >> 5) & 0x3F) << 2) | ((px >> 5) & 0x3F) >> 4;
|
||||
*b = ((px & 0x1F) << 3) | ((px & 0x1F) >> 2);
|
||||
} else {
|
||||
uint32_t px = *(uint32_t *)(bb + (size_t)y * line + (size_t)x * 4);
|
||||
*r = (px >> 16) & 0xFF; *g = (px >> 8) & 0xFF; *b = px & 0xFF;
|
||||
}
|
||||
}
|
||||
static int near2(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 14; }
|
||||
static int px_near(int x, int y, int r, int g, int b)
|
||||
{
|
||||
int rr, gg, bb; rd_px(x, y, &rr, &gg, &bb);
|
||||
return near2(rr, r) && near2(gg, g) && near2(bb, b);
|
||||
}
|
||||
static int region_has(int x, int y, int w, int h, int r, int g, int b)
|
||||
{
|
||||
int cnt = 0;
|
||||
for (int dy = 0; dy < h; dy++)
|
||||
for (int dx = 0; dx < w; dx++)
|
||||
if (px_near(x + dx, y + dy, r, g, b)) cnt++;
|
||||
return cnt;
|
||||
}
|
||||
#define UNPK(c, rr, gg, bb) do { (rr) = ((c) >> 16) & 0xFF; (gg) = ((c) >> 8) & 0xFF; (bb) = (c) & 0xFF; } while (0)
|
||||
|
||||
/* ---- 数据源:src2(每帧取行)与 pool(count/bind)各一套,行内容相同 ---- */
|
||||
static const char *const k_title[NR] = { "T1", "T2", "T3", "T4", "T5" };
|
||||
static const char *const k_sub[NR] = { "S1", "S2", "S3", "S4", "S5" };
|
||||
|
||||
static int src2_cb(int idx, meow_list_item *out, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (idx < 0 || idx >= NR) return 0;
|
||||
memset(out, 0, sizeof *out);
|
||||
out->title = k_title[idx];
|
||||
out->subtitle = k_sub[idx];
|
||||
out->right = MEOW_LIST_RIGHT_NONE;
|
||||
out->enabled = 1;
|
||||
return 1;
|
||||
}
|
||||
static int pool_count(void *ctx) { (void)ctx; return NR; }
|
||||
static void pool_bind(int idx, meow_lv_item *it, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
memset(it, 0, sizeof *it);
|
||||
if (idx < 0 || idx >= NR) return;
|
||||
it->title = k_title[idx];
|
||||
it->subtitle = k_sub[idx];
|
||||
it->enabled = 1;
|
||||
}
|
||||
|
||||
/* 造一个 src2 双行列表(扁平基线,便于按颜色断言行内文本带)。不设 item_h → 走派生。 */
|
||||
static meow_widget *make_lv(void)
|
||||
{
|
||||
meow_widget *lv = meow_listview_create(LV_X, LV_Y, LV_W, LV_H, NR,
|
||||
src2_cb, NULL, MEOW_LIST_SEL_NONE);
|
||||
if (!lv) return NULL;
|
||||
meow_listview_set_container(lv, 0);
|
||||
meow_listview_set_item_renderer(lv, 2, 0, 0); /* 双行、无图标、无尾件 → text_x = LV_X+16 */
|
||||
return lv;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
meow_ui_opts o = { 480, 720 };
|
||||
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
|
||||
const meow_theme *th = meow_theme_get();
|
||||
int os_r, os_g, os_b, osv_r, osv_g, osv_b;
|
||||
UNPK(th->onSurface, os_r, os_g, os_b);
|
||||
UNPK(th->onSurfaceVariant, osv_r, osv_g, osv_b);
|
||||
const int text_x = LV_X + 16; /* show_icon=0, container=0 */
|
||||
|
||||
/* ---- T0 --wrap 生效:宿主行高被替换为真机 30 ---- */
|
||||
CHECK(meow_ui_text_height(th->font_body) == LH, "wrap line_h=%d (expect %d)",
|
||||
meow_ui_text_height(th->font_body), LH);
|
||||
|
||||
/* ---- T1 默认(不设 item_h):行高按字体行高派生,首行标题可见 ---- */
|
||||
{
|
||||
meow_widget *lv = make_lv();
|
||||
CHECK(lv != NULL, "create");
|
||||
CHECK(meow_listview_item_h(lv) == BLOCK, "derived item_h=%d (expect %d = 2*%d+2)",
|
||||
meow_listview_item_h(lv), BLOCK, LH);
|
||||
CHECK(meow_listview_item_h(lv) >= 2 * LH + 2, "item_h >= 2*line_h + gap");
|
||||
CHECK(meow_listview_scroll(lv) == 0, "scroll=0");
|
||||
CHECK(meow_listview_max_scroll(lv) == NR * BLOCK - LV_H, "max_scroll=%d",
|
||||
meow_listview_max_scroll(lv));
|
||||
CHECK(meow_listview_hit(lv, LV_X + 10, LV_Y) == 0, "hit row0");
|
||||
CHECK(meow_listview_hit(lv, LV_X + 10, LV_Y + BLOCK) == 1, "hit row1 (item_h=%d)",
|
||||
meow_listview_item_h(lv));
|
||||
|
||||
int ty = LV_Y + (BLOCK - BLOCK) / 2; /* 62-62 → 首行标题顶 == 视口顶 */
|
||||
int sy = ty + LH + 2;
|
||||
meow_ui_clear(th->bg);
|
||||
meow_widget_draw(lv);
|
||||
CHECK(region_has(text_x, ty, 60, LH, os_r, os_g, os_b) > 2,
|
||||
"首行标题可见(scroll=0, ty=%d)", ty);
|
||||
CHECK(region_has(text_x, sy, 60, LH, osv_r, osv_g, osv_b) > 2,
|
||||
"首行副标题可见(sy=%d)", sy);
|
||||
CHECK(region_has(text_x, LV_Y - 20, 60, 20, os_r, os_g, os_b) == 0,
|
||||
"视口上方无标题泄漏");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ---- T2 裸 create(不调 renderer):默认 lines=2 同样派生 ---- */
|
||||
{
|
||||
meow_widget *lv = meow_listview_create(LV_X, LV_Y, LV_W, LV_H, NR,
|
||||
src2_cb, NULL, MEOW_LIST_SEL_NONE);
|
||||
CHECK(lv != NULL && meow_listview_item_h(lv) == BLOCK, "bare create item_h=%d",
|
||||
lv ? meow_listview_item_h(lv) : -1);
|
||||
/* set_item_h(<=0) 忽略:仍返回派生值 */
|
||||
CHECK(meow_listview_set_item_h(lv, 0) == BLOCK, "set_item_h(0) keeps derived");
|
||||
CHECK(meow_listview_item_h(lv) == BLOCK, "item_h unchanged after set_item_h(0)");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ---- T3 显式 set_item_h 优先(ncm 语义:App 按自身卡高传 70)---- */
|
||||
{
|
||||
meow_widget *lv = make_lv();
|
||||
CHECK(meow_listview_set_item_h(lv, 70) == 70, "explicit item_h returns 70");
|
||||
CHECK(meow_listview_item_h(lv) == 70, "explicit item_h=70 wins over derived %d", BLOCK);
|
||||
CHECK(meow_listview_max_scroll(lv) == NR * 70 - LV_H, "max_scroll uses 70");
|
||||
int ty = LV_Y + (70 - BLOCK) / 2; /* 4 */
|
||||
int sy = ty + LH + 2;
|
||||
meow_ui_clear(th->bg);
|
||||
meow_widget_draw(lv);
|
||||
CHECK(region_has(text_x, ty, 60, LH, os_r, os_g, os_b) > 2, "显式 70 首行标题可见");
|
||||
CHECK(region_has(text_x, sy, 60, LH, osv_r, osv_g, osv_b) > 2, "显式 70 首行副标题可见");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ---- T4 档位:tier 恢复派生默认(2→62 / 1→48),单行不画副标题 ---- */
|
||||
{
|
||||
meow_widget *lv = make_lv();
|
||||
CHECK(meow_listview_set_item_h(lv, 70) == 70, "pre-set explicit 70");
|
||||
CHECK(meow_listview_set_item_tier(lv, 2) == 0, "tier(2)");
|
||||
CHECK(meow_listview_item_h(lv) == BLOCK, "tier(2) 派生 item_h=%d", meow_listview_item_h(lv));
|
||||
CHECK(meow_listview_set_item_tier(lv, 1) == 0, "tier(1)");
|
||||
CHECK(meow_listview_item_h(lv) == 48, "tier(1) item_h=%d (expect 48)",
|
||||
meow_listview_item_h(lv));
|
||||
int ty = LV_Y + (48 - LH) / 2; /* 9 */
|
||||
meow_ui_clear(th->bg);
|
||||
meow_widget_draw(lv);
|
||||
CHECK(region_has(text_x, ty, 60, LH, os_r, os_g, os_b) > 2, "单行档位标题可见");
|
||||
CHECK(region_has(text_x, LV_Y + 41, 60, 6, osv_r, osv_g, osv_b) == 0,
|
||||
"单行档位不画副标题");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ---- T5 pool 模式(set_vsource + 默认 renderer):派生行高 + 首行标题可见 ---- */
|
||||
{
|
||||
meow_widget *lv = meow_listview_create(LV_X, LV_Y, LV_W, LV_H, 0,
|
||||
NULL, NULL, MEOW_LIST_SEL_NONE);
|
||||
meow_listview_set_container(lv, 0);
|
||||
CHECK(meow_listview_set_vsource(lv, pool_count, pool_bind, NULL) == 0, "vsource");
|
||||
CHECK(meow_listview_set_item_renderer(lv, 2, 0, 0) == 0, "renderer(2,0,0)");
|
||||
CHECK(meow_listview_item_h(lv) == BLOCK, "pool item_h=%d", meow_listview_item_h(lv));
|
||||
int ty = LV_Y + (BLOCK - BLOCK) / 2;
|
||||
int sy = ty + LH + 2;
|
||||
meow_ui_clear(th->bg);
|
||||
meow_widget_draw(lv);
|
||||
CHECK(region_has(text_x, ty, 60, LH, os_r, os_g, os_b) > 2, "pool 首行标题可见");
|
||||
CHECK(region_has(text_x, sy, 60, LH, osv_r, osv_g, osv_b) > 2, "pool 首行副标题可见");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ---- T6 旧行为哨兵:显式 56(< block 62)→ 首行标题仍被裁剪守卫跳过 ----
|
||||
* 记录 bug 形态,证明上面的像素断言确实能区分「画出」与「跳过」;
|
||||
* 派生默认生效后这条路径只剩「App 明确要 56」时才会走到。 */
|
||||
{
|
||||
meow_widget *lv = make_lv();
|
||||
CHECK(meow_listview_set_item_h(lv, 56) == 56, "explicit 56");
|
||||
CHECK(meow_listview_item_h(lv) == 56, "explicit 56 stays 56");
|
||||
meow_ui_clear(th->bg);
|
||||
meow_widget_draw(lv);
|
||||
int ty = LV_Y + (56 - BLOCK) / 2; /* -3 → 行顶上方 */
|
||||
int sy = ty + LH + 2; /* 29 */
|
||||
CHECK(region_has(text_x, LV_Y, 60, LH, os_r, os_g, os_b) == 0,
|
||||
"旧 56 行高:首行标题被跳过(bug 形态)");
|
||||
CHECK(region_has(text_x, sy, 60, LH, osv_r, osv_g, osv_b) > 2,
|
||||
"旧 56 行高:首行副标题仍画(症状「只有副标题」)");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
printf("%s: %d passed, %d failed\n", g_fail ? "FAIL" : "PASS", g_pass, g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_listvirt test_meow_ui_listvirt.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c meow_ui_nav.c \
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/draw/activity/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_scroll test_meow_ui_scroll.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c meow_ui_nav.c \
|
||||
* -I. -Iinclude -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* meow_jpeg.c meow_png_stream.c \
|
||||
* -I. -Iinclude -DMEOW_UI_HOST_TEST -pthread -lm -lz
|
||||
* 运行:./test_meow_ui_scroll
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
@@ -45,6 +46,17 @@ static meow_widget *make_list(void)
|
||||
return lv;
|
||||
}
|
||||
|
||||
/* 喂一串向下(正)速度采样,模拟快速下划 */
|
||||
static void feed_drag(meow_scroll *s, int step, int n)
|
||||
{
|
||||
uint64_t now = meow_ui_time_ms();
|
||||
for (int i = 0; i < n; i++) {
|
||||
meow_scroll_track(s, step, now + (uint64_t)i * 16);
|
||||
}
|
||||
/* 最后用一个真实稍晚时刻的 track 让采样时间戳真实(保持一致钟) */
|
||||
meow_scroll_track(s, 0, meow_ui_time_ms());
|
||||
}
|
||||
|
||||
static void pump_until_idle(meow_scroll *s, int *max_off, int *mono_ok)
|
||||
{
|
||||
int prev = meow_scroll_offset(s);
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
* 阻塞式模态用后台线程驱动(同 test_ime_fix 范式),主线程经 meow_input_inject 喂触摸。
|
||||
* 编译(宿主 gcc):
|
||||
* gcc -o test_meow_ui_select test_meow_ui_select.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_select.c meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c meow_ui_nav.c \
|
||||
* -I. -Iinclude -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* meow_select.c meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
|
||||
* meow_jpeg.c meow_png_stream.c meow_ui_nav.c \
|
||||
* -I. -Iinclude -DMEOW_UI_HOST_TEST -pthread -lm -lz
|
||||
* 运行:./test_meow_ui_select
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
@@ -111,10 +112,12 @@ static void test_select_highlight(void)
|
||||
pthread_t th; pthread_create(&th, NULL, sel_driver, &a);
|
||||
/* 等 open 的 drain + 首帧绘制完成(高亮已画进 back_buf) */
|
||||
usleep(150000);
|
||||
/* 单选高亮:cur=2(第 3 行)选中背景 = primaryContainer */
|
||||
/* 单选高亮:cur=2(第 3 行)选中背景 = primaryContainer。
|
||||
* meow_listview 默认容器模式(行=surfaceContainer 12dp 圆角卡 + MEOW_LISTV_CARD_MARGIN=8 横向内缩),
|
||||
* 选中卡左缘=8、文本左缘=24;采样点取 x=10(卡内、文本左侧),避免取到左侧空白边距(旧 x=6 落在卡外)。 */
|
||||
const meow_theme *thm = meow_theme_get();
|
||||
int pc_r, pc_g, pc_b; UNPK(thm->primaryContainer, pc_r, pc_g, pc_b);
|
||||
int hx = 12, hy = list_y + 2 * SEL_ROW + SEL_ROW / 2; /* 容器卡横向内缩 8px(MEOW_LISTV_CARD_MARGIN),探针取卡内 */
|
||||
int hx = 10, hy = list_y + 2 * SEL_ROW + SEL_ROW / 2;
|
||||
int r, g, b; rd_px(hx, hy, &r, &g, &b);
|
||||
CHECK(near2(r, pc_r) && near2(g, pc_g) && near2(b, pc_b),
|
||||
"T1 highlight row2 primaryContainer got %02X%02X%02X", r, g, b);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
* 编译(宿主 gcc):
|
||||
* gcc -o test_meow_ui_swipe test_meow_ui_swipe.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c meow_ui_nav.c \
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_theme test_meow_ui_theme.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_img.c meow_ui_media.c \
|
||||
* meow_notify_ipc.c stb_image.c meow_ui_nav.c \
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -Wall -Wextra -o test_meow_ui_theme_export test_meow_ui_theme_export.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c meow_ui_nav.c \
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -Wall -Wextra -o test_meow_ui_view_event test_meow_ui_view_event.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c meow_ui_nav.c \
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_widgets test_meow_ui_widgets.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_img.c meow_ui_media.c \
|
||||
* meow_notify_ipc.c stb_image.c meow_ui_nav.c \
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/ime/media/notify_ipc + 打桩 font/global):
|
||||
* gcc -Wall -Wextra -o test_notif_gesture test_notif_gesture.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_ime.c meow_ui_str.c meow_ui_activity.c meow_ui_media.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_notify_ipc.c meow_ui_nav.c \
|
||||
* -I. -Iinclude -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -Wall -Wextra -o test_overlay_suppress test_overlay_suppress.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_media.c \
|
||||
* meow_notify_ipc.c meow_ui_nav.c \
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* -I. -Iinclude -I../hello_app ../hello_app/app.c -o app.o
|
||||
* 2) 编译本文件 + 其余 hello 模块 + SDK 源 + 打桩,链接 app.o:
|
||||
* gcc -Wall -Wextra -DMEOW_UI_HOST_TEST -o test_p1p2p3_repro test_p1p2p3_repro.c app.o \
|
||||
* test_host_stub.c meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* test_host_stub.c meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_ui_img.c meow_ui_config.c meow_ui_notify.c meow_notify_ipc.c meow_ui_draw.c \
|
||||
* meow_ui_view.c meow_ui_nav.c meow_ui_media.c stb_image.c \
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
*
|
||||
* 编译(宿主 gcc,同 test_ime_fix):
|
||||
* gcc -Wall -Wextra -o test_pinyin test_pinyin.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_img.c meow_ui_media.c \
|
||||
* meow_notify_ipc.c stb_image.c meow_ui_nav.c -I. -Iinclude -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -Wall -o test_sdk_fixes test_sdk_fixes.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_gfx.c meow_ui_time.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_media.c \
|
||||
* meow_notify_ipc.c meow_ui_nav.c \
|
||||
|
||||
@@ -79,7 +79,9 @@ static void key_tap(char d)
|
||||
static void *pin_injector(void *arg)
|
||||
{
|
||||
pin_job *job = (pin_job *)arg;
|
||||
usleep(150000); /* 等 text_input 开键盘并 drain 完残留事件 */
|
||||
usleep(350000); /* 等 text_input 开键盘 + drain 完残留事件,并等过
|
||||
* IME_TAP_COOLDOWN_MS(200) 冷却窗(bug3):窗内 TAP 被
|
||||
* 当迟到抬起吞掉,150ms 时首键 dt_open≈114ms 会丢 */
|
||||
for (int p = 0; p < job->passes; p++) {
|
||||
if (p) usleep(300000); /* 两遍输入之间等第二遍开键盘 */
|
||||
for (const char *s = job->digits; *s; s++) {
|
||||
|
||||
Reference in new issue
Block a user