/* 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 覆盖)。 * * 编译(宿主 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_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ * meow_ui_draw.c meow_ui_view.c meow_ui_img.c stb_image.c \ * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm * (meow_ui_widget.c 新 W_LISTVIEW 引用 meow_ui_img_scaled → 需链 meow_ui_img.c+stb_image.c+-lm) */ #include "meow_ui.h" #include "meow_ui_internal.h" #include #include #include #include #include #include /* 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); } 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) { 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; 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(160000); 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(); /* ① */ /* ②③ 在 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; }