公开 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。
245 lines
11 KiB
C
245 lines
11 KiB
C
/* test_ime_fix.c — M2.71-IME-fix:宿主离屏验证三项修复(480x720,16bpp)。
|
||
* ① shift 大小写正确:shift 关→小写('q'),开→大写('Q');
|
||
* ② 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_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_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"
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <assert.h>
|
||
#include <unistd.h>
|
||
#include <pthread.h>
|
||
|
||
/* ASCII 键盘键位(build_kb:KB_TOP=420 → 首行 y=470;GW=44,GAP=4,GCOL=48,行左缘 x=2):
|
||
* row2 "qwertyuiop" y∈[518,554);row3 "asdfghjkl"+SHIFT y∈[566,602);
|
||
* row5 ESC+SPACE+ENTER y∈[662,698)。 */
|
||
#define K_Q_X 20
|
||
#define K_Q_Y 536
|
||
#define K_SHIFT_X 450
|
||
#define K_SHIFT_Y 584
|
||
#define K_ENTER_X 430
|
||
#define K_ENTER_Y 680
|
||
#define K_ESC_X 20
|
||
#define K_ESC_Y 680
|
||
|
||
/* ---- 像素/配色辅助(离屏 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();
|
||
assert(bb && line > 0);
|
||
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 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);
|
||
c.mode = MEOW_IME_ASCII; c.maxlen = 32; c.on_change = ime_cb;
|
||
meow_ui_text_input_async(&c);
|
||
assert(meow_ime_active() == 1);
|
||
|
||
meow_ime_handle_tap(K_Q_X, K_Q_Y); /* shift 关 → 'q' 小写 */
|
||
assert(strcmp(meow_ime_text(), "q") == 0);
|
||
assert(strcmp(g_echo, "q") == 0);
|
||
assert(meow_ime_text()[0] >= 'a' && meow_ime_text()[0] <= 'z'); /* 明确小写 */
|
||
|
||
meow_ime_handle_tap(K_SHIFT_X, K_SHIFT_Y); /* shift 开 */
|
||
meow_ime_handle_tap(K_Q_X, K_Q_Y); /* 'q' → 'Q' 大写 */
|
||
|
||
assert(strcmp(meow_ime_text(), "qQ") == 0);
|
||
assert(strcmp(g_echo, "qQ") == 0);
|
||
assert(meow_ime_text()[1] >= 'A' && meow_ime_text()[1] <= 'Z'); /* 明确大写 */
|
||
printf("PASS shift: off='q'(lower), on='Q'(upper)\n"); fflush(stdout);
|
||
|
||
meow_ime_handle_tap(K_ESC_X, K_ESC_Y); /* 关闭 */
|
||
meow_ime_close(0); /* 复位(清 g_ime_done/active) */
|
||
assert(meow_ime_active() == 0);
|
||
}
|
||
|
||
/* ---- ②③ 阻塞 IME(经 meow_ui_run 驱动):K_ENTER 不关 / on_tick 喂 / IME 最上 ---- */
|
||
static meow_widget *g_tf;
|
||
static char g_tfbuf[32] = "";
|
||
static int g_tick = 0;
|
||
static int g_sentinel_painted = 0;
|
||
static int g_test_done = 0;
|
||
|
||
static int g_active_after_enter = -1; /* K_ENTER 后 meow_ime_active()(期望 1) */
|
||
static int g_active_after_esc = -1; /* ESC 后 meow_ime_active()(期望 0) */
|
||
static int g_probe_r = -1, g_probe_g = -1, g_probe_b = -1; /* IME 面板区内取样(期望=面板色非哨兵红) */
|
||
static char g_drv_text[64] = ""; /* shift 序列后 meow_ime_text()(期望 "qQ") */
|
||
|
||
/* 把 back_buf 写成 PPM(视觉目检 IME 面板渲染/最上;在 IME 空闲时调用,back_buf 稳定) */
|
||
static void dump_ppm(const char *path)
|
||
{
|
||
int W = meow_ui_width(), H = meow_ui_height();
|
||
FILE *f = fopen(path, "wb");
|
||
if (!f) return;
|
||
fprintf(f, "P6\n%d %d\n255\n", W, H);
|
||
for (int y = 0; y < H; y++) {
|
||
for (int x = 0; x < W; x++) {
|
||
int r, g, b; rd_px(x, y, &r, &g, &b);
|
||
fputc(r, f); fputc(g, f); fputc(b, f);
|
||
}
|
||
}
|
||
fclose(f);
|
||
}
|
||
|
||
/* App on_tick:模拟 demo 动态重绘。IME 打开时先在 IME 面板区内画一条红色哨兵行,
|
||
* 再由 meow_ime_draw 盖回面板色 → 验证「喂 on_tick 后 IME 仍最上」的绘制顺序。 */
|
||
static void host_tick(void)
|
||
{
|
||
g_tick++;
|
||
if (meow_ime_active() && !g_sentinel_painted) {
|
||
g_sentinel_painted = 1;
|
||
meow_ui_fill_rect(0, 500, 480, 8, 0xFF0000); /* 哨兵行:App 内容拟 IME 面板区内 */
|
||
meow_ui_flush_region(0, 500, 480, 8);
|
||
}
|
||
if (g_test_done) meow_ui_request_exit();
|
||
}
|
||
|
||
/* App on_tap:命中 TextField → 打开**阻塞** IME(同 hello demo 路径) */
|
||
static void host_tap(int x, int y)
|
||
{
|
||
(void)x; (void)y;
|
||
if (g_tf && meow_widget_hit(g_tf, x, y)) meow_textfield_activate(g_tf);
|
||
}
|
||
|
||
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(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 开 */
|
||
usleep(60000);
|
||
meow_input_inject(MEOW_EV_TAP, K_Q_X, K_Q_Y); /* shift 开 → 'Q' → 缓冲 "qQ" */
|
||
usleep(90000);
|
||
strncpy(g_drv_text, meow_ime_text(), sizeof g_drv_text - 1);
|
||
g_drv_text[sizeof g_drv_text - 1] = 0;
|
||
|
||
meow_input_inject(MEOW_EV_TAP, K_ENTER_X, K_ENTER_Y); /* K_ENTER:只提交不关 */
|
||
usleep(160000);
|
||
g_active_after_enter = meow_ime_active(); /* 期望 1(键盘仍开) */
|
||
|
||
rd_px(240, 500, &g_probe_r, &g_probe_g, &g_probe_b); /* IME 面板区内取样 */
|
||
dump_ppm("/tmp/ime_panel.ppm"); /* 目检:IME 面板渲染 + 盖住哨兵 */
|
||
printf("[drv] text=%s active_after_enter=%d probe=%02X%02X%02X sentineld=%d\n",
|
||
g_drv_text, g_active_after_enter, g_probe_r, g_probe_g, g_probe_b,
|
||
g_sentinel_painted); fflush(stdout);
|
||
|
||
meow_input_inject(MEOW_EV_TAP, K_ESC_X, K_ESC_Y); /* ESC → 关闭 */
|
||
usleep(160000);
|
||
g_active_after_esc = meow_ime_active(); /* 期望 0 */
|
||
g_test_done = 1;
|
||
return NULL;
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
setenv("MEOW_UI_DUMP", "/tmp/ime_fix.ppm", 1);
|
||
meow_ui_opts o = { 480, 720 };
|
||
assert(meow_ui_init(&o) == 0);
|
||
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);
|
||
meow_ui_events ev; memset(&ev, 0, sizeof ev);
|
||
ev.on_tap = host_tap;
|
||
ev.on_tick = host_tick;
|
||
pthread_t thd; pthread_create(&thd, NULL, driver, NULL);
|
||
meow_ui_run(&ev);
|
||
pthread_join(thd, NULL);
|
||
|
||
/* 断言 ② */
|
||
printf("[assert] active_after_enter=%d (expect 1), active_after_esc=%d (expect 0)\n",
|
||
g_active_after_enter, g_active_after_esc);
|
||
assert(g_active_after_enter == 1); /* K_ENTER 不关 */
|
||
assert(g_active_after_esc == 0); /* ESC 关 */
|
||
|
||
/* 断言 ①(阻塞路径经 on_change 实时同步到字段) */
|
||
printf("[assert] blocking text=%s (expect \"qQ\"), tf=%s (expect \"qQ\")\n",
|
||
g_drv_text, meow_textfield_text(g_tf));
|
||
assert(strcmp(g_drv_text, "qQ") == 0);
|
||
assert(strcmp(meow_textfield_text(g_tf), "qQ") == 0);
|
||
|
||
/* 断言 ③:on_tick 在 IME 打开期间被喂过(哨兵已画)+ IME 面板盖住哨兵 */
|
||
int pr = (th->surfaceContainerHighest >> 16) & 0xFF;
|
||
int pg = (th->surfaceContainerHighest >> 8) & 0xFF;
|
||
int pb = (th->surfaceContainerHighest) & 0xFF;
|
||
printf("[assert] sentinel_painted=%d probe=%02X%02X%02X panel=%02X%02X%02X\n",
|
||
g_sentinel_painted, g_probe_r, g_probe_g, g_probe_b, pr, pg, pb);
|
||
assert(g_sentinel_painted == 1); /* on_tick 在 IME 期间被调(重绘过) */
|
||
assert(g_tick > 0);
|
||
assert(near(g_probe_r, pr) && near(g_probe_g, pg) && near(g_probe_b, pb)); /* IME 面板最上 */
|
||
/* 哨兵红 0xFF0000 与面板色显著不同,确保上面断言有判别力(面板色不应近纯红) */
|
||
assert(!(near(pr, 0xFF) && near(pg, 0x00) && near(pb, 0x00)));
|
||
|
||
printf("ALL PASS\n");
|
||
meow_ui_shutdown();
|
||
return 0;
|
||
}
|