fix(sdk/ime): IME 修复(shift小写基/OK只提交不关/阻塞喂on_tick agent-169) + dialog INPUT回归(dlg_ime_change_cb实时回写+适配新语义 agent-173) + test_sdk_fixes shift断言更新 + 宿主测试test_ime_fix

This commit is contained in:
MeowOS committed 2026-08-31 02:01:56 +08:00
1 parent 4afd1abb4a
commit 06a69ef7d7
6 files changed
+359 -15

No files matched your search

+4 -2
View File
@@ -141,7 +141,8 @@ typedef struct meow_ime_ctx {
void *ctx;
} meow_ime_ctx;
/* 阻塞:弹出键盘直到确认/取消。返回 1=确认(buf 已回填),0=取消。 */
/* 阻塞:弹出键盘直到关闭(ESC / 点背景取消);回车=只提交不关闭(内容经 on_change 实时同步)。
* 关闭路径返回 0(取消);提交内容经 on_change 回调逐键同步,不依赖返回值回填 buf。 */
int meow_ui_text_input(meow_ime_ctx *ctx);
/* 非阻塞:返回后 app 在 on_tick 里轮询 meow_ime_active()/meow_ime_text() */
@@ -443,7 +444,8 @@ int meow_btn_set_variant(meow_widget *w, int variant); /* 越界 clam
meow_widget *meow_textfield_create(int x, int y, int w, int h, const char *placeholder,
char *buf, int maxlen, meow_ime_mode mode);
void meow_textfield_set_placeholder(meow_widget *w, const char *placeholder);
/* 聚焦并打开**阻塞** IME(同 meow_ui_text_input 内层轮询);返回 1=确认 0=取消;buf 已回填 */
/* 聚焦并打开**阻塞** IME(同 meow_ui_text_input 内层轮询);回车不关闭键盘,ESC/点背景关闭。
* 内容经 on_change 实时同步到 w->buf(返回值关闭路径为 0,提交不依赖返回值回填)。 */
int meow_textfield_activate(meow_widget *w);
const char *meow_textfield_text(meow_widget *w);
+36
View File
@@ -64,6 +64,7 @@ struct meow_dialog {
int done;
meow_ime_ctx ime; /* INPUT 型(拷贝) */
int has_ime;
meow_ime_ctx *ime_orig; /* INPUT:App 传入的原始 ctx(回写最终文本,App 关闭后可读回) */
};
/* 卡片几何:返回标题之后内容起点(行/按钮相对坐标)。 */
@@ -162,6 +163,30 @@ static void dlg_repaint(meow_dialog *d, const meow_theme *th)
meow_ui_flush_region(g.dx, g.dy, g.dw, g.dh);
}
/* M2.71-IME-fix:dialog INPUT 的 on_change——实时把 IME 文本回写进 d->ime.buf(dialog 内部拷贝),
* 并重绘卡片预览区,使用户边打字边看到当前文本。ESC/点背景(result=0)关闭后 d->ime.buf 仍是
* 最新文本(否则 meow_ime_close(0) 不回写 buf → 文本丢失)。d->ime.buf 是 dialog 内部缓冲
* (非 App 持有);App 的最终文本经 ime_orig 在关闭时回写(见 OK 按钮处理)。 */
static void dlg_ime_change_cb(const char *text, void *ctx)
{
meow_dialog *d = (meow_dialog *)ctx;
if (!d) return;
if (text) {
strncpy(d->ime.buf, text, sizeof d->ime.buf - 1);
d->ime.buf[sizeof d->ime.buf - 1] = 0;
}
const meow_theme *th = meow_theme_get();
dlg_repaint(d, th);
}
/* 准备好 dialog 的 IME 上下文:无论 App 传入(set_ime)或内部兜底(show 的默认 ctx),
* 都由 dialog 接管 on_change/ctx,以支持 dlg_ime_change_cb 的实时回写。 */
static void dlg_prep_ime(meow_dialog *d)
{
d->ime.on_change = dlg_ime_change_cb;
d->ime.ctx = d;
}
meow_dialog *meow_dialog_create(meow_dlg_t t, const char *title)
{
meow_dialog *d = (meow_dialog *)calloc(1, sizeof *d);
@@ -183,7 +208,9 @@ void meow_dialog_set_ime(meow_dialog *d, meow_ime_ctx *ime)
{
if (!d || !ime) return;
d->ime = *ime;
d->ime_orig = ime; /* M2.71-IME-fix:记住 App 原始 ctx,关闭时回写最终文本 */
d->has_ime = 1;
dlg_prep_ime(d); /* dialog 接管 on_change:实时回写 d->ime.buf + 重绘卡片 */
}
void meow_dialog_close(meow_dialog *d)
@@ -199,6 +226,8 @@ void meow_dialog_show(meow_dialog *d)
if (d->type == MEOW_DLG_INPUT && !d->has_ime) {
memset(&d->ime, 0, sizeof d->ime);
d->ime.maxlen = 255;
d->ime_orig = NULL; /* 无 App 原始 ctx,关闭时不回写 */
dlg_prep_ime(d);
}
meow_input_start();
meow_input_drain(); /* 排空残留触摸(goodix 长尾) */
@@ -236,6 +265,13 @@ void meow_dialog_show(meow_dialog *d)
} else if (x >= cx && x < cx + g.btn_w) { /* OK */
if (d->type == MEOW_DLG_INPUT) {
d->result = meow_ui_text_input(&d->ime); /* 阻塞编辑 */
/* M2.71-IME-fix:回车只提交不关(键盘仍开);文本经 on_change 已实时
* 写进 d->ime.buf。这里无论 result=0/1 都把最终文本回写 App 原始 ctx,
* 保证 ESC/点背景(result=0)关闭后 App 仍能读到编辑结果(不再丢失)。 */
if (d->ime_orig) {
strncpy(d->ime_orig->buf, d->ime.buf, sizeof d->ime_orig->buf - 1);
d->ime_orig->buf[sizeof d->ime_orig->buf - 1] = 0;
}
} else {
d->result = (d->type == MEOW_DLG_CONFIRM) ? 1 : 1;
}
+26 -9
View File
@@ -1,11 +1,15 @@
/*
* meow_ui_ime.c — 内置触屏软键盘(B1.6)。仅英文 QWERTY / 数字,ASCII。
*
* 阻塞:meow_ui_text_input(ctx) 弹出键盘,自绘自分发直到确认(enter)/取消(esc|背景)。
* 阻塞:meow_ui_text_input(ctx) 弹出键盘,自绘自分发直到关闭(ESC / 点背景取消)。
* 回车(K_ENTER)只提交(mark g_ime_result=1)不关闭,用户可继续输入。
* 阻塞期间周期性喂 App on_tick(meow_ui_feed_tick),动态内容(进度条/时钟)不卡死。
* 非阻塞:meow_ui_text_input_async(ctx) 打开后返回;meow_ui_run 在 on_tick 间隙
* 随帧绘制并拦截触摸,app 用 meow_ime_active()/meow_ime_text() 轮询。
*
* 键盘覆盖屏幕底部约 300px;点按输入;键位 44×36(touch target 达标)。
* M2.71-IME-fix:字母键面存小写("qwertyuiop"…),shift 开→大写、关→小写;
* 数字/符号键不随 shift 转换。旧版键面存大写 → 无论 shift 开关恒输出大写(bug)。
*/
#include "meow_ui.h"
#include "meow_ui_internal.h"
@@ -15,6 +19,11 @@
#include <ctype.h>
#include <unistd.h>
/* 阻塞 IME 循环周期性喂 App on_tick(实现在 meow_ui_input.c,读该 TU 的 g_events)。
* 让 IME 打开期间 App 的动态内容(进度条/时钟)继续刷新;调用在 meow_ime_draw 之前,
* 保证 App 重绘先进 back_buf、IME 面板随后盖上去(IME 始终最上,不被 App 重绘覆盖)。 */
extern void meow_ui_feed_tick(void);
#define IME_MAXKEYS 96
#define KB_TOP 420 /* 面板顶(预览行起点) */
#define GW 44 /* 键宽 */
@@ -94,10 +103,10 @@ static void build_kb(meow_ime_mode mode)
int y = KB_TOP + 50;
if (mode == MEOW_IME_ASCII) {
add_ch_row(y, "1234567890");
add_ch_row(y + GCOL, "QWERTYUIOP");
add_ch_row(y + GCOL, "qwertyuiop");
/* 第 3 行:9 字母 + SHIFT */
{
const char *r = "ASDFGHJKL";
const char *r = "asdfghjkl";
int n = (int)strlen(r);
int total = 10 * GW + 9 * GAP;
int x = (480 - total) / 2;
@@ -106,7 +115,7 @@ static void build_kb(meow_ime_mode mode)
}
/* 第 4 行:7 字母 + BACKSPACE */
{
const char *r = "ZXCVBNM";
const char *r = "zxcvbnm";
int n = (int)strlen(r);
int total = 10 * GW + 9 * GAP;
int x = (480 - total) / 2;
@@ -121,8 +130,9 @@ static void build_kb(meow_ime_mode mode)
add_key(x + GCOL, y + GCOL * 4, (GW + GAP) * 6, GH, K_SPACE, ' ');
add_key(x + GCOL * 8, y + GCOL * 4, 2 * GW + GAP, GH, K_ENTER, '>');
}
} else { /* MEOW_IME_NUM:ESC(取消/关闭) + 空格 + 退格;单独一行回车(OK=提交并关闭)。
* M2.71-bug7:修掉旧版「同格重叠的 BACK+ENTER」与重复回车。 */
} else { /* MEOW_IME_NUM:ESC(取消/关闭) + 空格 + 退格;单独一行回车(OK=只提交不关闭)。
* M2.71-bug7:修掉旧版「同格重叠的 BACK+ENTER」与重复回车。
* M2.71-IME-fix:回车不再关闭(与 ASCII 一致),关闭走 ESC/背景。 */
add_ch_row(y, "1234567890");
add_ch_row(y + GCOL, "-/.:*#+=");
{
@@ -132,7 +142,7 @@ static void build_kb(meow_ime_mode mode)
add_key(x + GCOL, y + GCOL * 2, (GW + GAP) * 7, GH, K_SPACE, ' '); /* 空格 */
add_key(x + GCOL * 8, y + GCOL * 2, (GW + GAP) * 2 - GAP, GH, K_BACK, '<'); /* 退格 */
}
/* 回车(OK=提交并关闭) */
/* 回车(OK=只提交不关闭) */
{
int total = 10 * GW + 9 * GAP;
int x = (480 - total) / 2;
@@ -148,7 +158,10 @@ static void apply_key(const struct kbkey *k)
{
switch (k->kind) {
case K_CH: {
char c = g_shift && is_letter(k->ch) ? (char)toupper((unsigned char)k->ch) : k->ch;
/* M2.71-IME-fix:键面存小写(build_kb "qwertyuiop"…),shift 开→大写、关→小写;
* 数字/符号(非字母)不转换。旧版存大写 + else 分支恒返回大写 → 大小写不区分。 */
char c = k->ch;
if (g_shift && is_letter(c)) c = (char)toupper((unsigned char)c);
int len = (int)strlen(g_ime_buf);
if (len < g_maxlen - 1) {
g_ime_buf[len] = c; g_ime_buf[len + 1] = 0;
@@ -169,7 +182,7 @@ static void apply_key(const struct kbkey *k)
if (g_ime_ctx.on_change) g_ime_ctx.on_change(g_ime_buf, g_ime_ctx.ctx); }
break;
}
case K_ENTER: g_ime_result = 1; g_ime_done = 1; break;
case K_ENTER: g_ime_result = 1; break; /* M2.71-IME-fix:回车只提交(mark),不关键盘;关闭走 ESC/背景 */
case K_ESC: g_ime_result = 0; g_ime_done = 1; break;
}
}
@@ -260,6 +273,10 @@ int meow_ui_text_input(meow_ime_ctx *ctx)
meow_ui_activity_ping(); /* M2.71-bug5:IME 打字交互也重置息屏计时 */
if (e.type == MEOW_EV_TAP) meow_ime_handle_tap(e.a, e.b);
}
/* M2.71-IME-fix:阻塞 IME 期间喂 App on_tick(动态内容继续刷,progress/时钟不卡死)。
* 顺序要求:先喂 on_tick(App 重绘进 back_buf)→ 再 meow_ime_draw(IME 面板随后盖上去),
* 保证 IME 面板覆盖在最上,不被 App 的 d_paint_region 覆盖。 */
meow_ui_feed_tick();
meow_ime_draw();
meow_ui_flush();
usleep(16000);
+13
View File
@@ -445,3 +445,16 @@ void meow_ui_run(const meow_ui_events *ev)
}
}
}
/* M2.71-IME-fix:供 meow_ui_ime.c 的阻塞 IME 循环周期性喂 App on_tick。
* 阻塞 IME 循环不经过 meow_ui_run,无法靠主循环喂 tick,故在此开独立入口;
* 与 meow_ui_run 共用 last_tick_ms(二者互斥运行,无竞争)。调用方在 meow_ime_draw 之前调用,
* 使 App 重绘先进 back_buf、IME 面板随后覆盖(IME 始终最上,不被 App 重绘盖住)。 */
void meow_ui_feed_tick(void)
{
if (!(g_events_set && g_events.on_tick)) return;
uint64_t now = now_ms();
if (now - last_tick_ms < 16) return;
last_tick_ms = now;
g_events.on_tick();
}
+207
View File
@@ -0,0 +1,207 @@
/* 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 \
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread
*/
#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); }
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;
}
+73 -4
View File
@@ -3,7 +3,8 @@
* bug4 disabled 按钮圆角外无灰方块(角点 = 页面背景而非叠暗);
* bug1 圆形进度环带确实画到 = 半径+thk/2 处(bbox 与真实像素一致,漏刷修复的几何前提);
* bug3 modal(Dialog)open→cancel 后背景还原(scrim 不累积/无残影);
* bug7 IME 回显实时同步(on_change 随键入触发)+ 关闭后键盘区背景还原。
* bug7 IME 回显实时同步(on_change 随键入触发)+ 关闭后键盘区背景还原;
* fix1 dialog INPUT 在 ESC/背景(result=0)关闭后文本不丢(M2.71-IME-fix 新语义:回车只提交不关)。
*
* 编译(宿主 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 \
@@ -39,6 +40,7 @@ static int near(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 12
/* ---- 为阻塞模态/IME 在后台注入事件 ---- */
static int g_inj_tap_x = 0, g_inj_tap_y = 0, g_inj_delay = 0;
static int K_DLG_OK_X = 0, K_DLG_OK_Y = 0; /* dialog INPUT 的 OK 按钮中心(运行时算) */
static void *inject_thread(void *arg)
{
(void)arg;
@@ -122,10 +124,14 @@ static void test_ime_echo_and_restore(void)
/* --- 异步:验『回显/输入框实时同步(on_change 随键入触发)』--- */
meow_ui_text_input_async(&c);
assert(meow_ime_active() == 1);
meow_input_inject(MEOW_EV_TAP, 20, 580); /* ASCII 第3行第1键 = 'A'(shift 关 → 缓冲 'A') */
meow_input_inject(MEOW_EV_TAP, 20, 580); /* ASCII 行3第1键 = 'a'(shift 关 → 小写 'a') */
meow_ime_handle_tap(20, 580);
assert(strcmp(g_echo, "A") == 0 && strcmp(meow_ime_text(), "A") == 0);
printf("[bug7] ime echo live-sync after 'A' -> \"%s\" (on_change fired) PASS\n", g_echo); fflush(stdout);
assert(strcmp(g_echo, "a") == 0 && strcmp(meow_ime_text(), "a") == 0);
printf("[bug7] ime echo live-sync after 'a' -> \"%s\" (shift-off lower) PASS\n", g_echo); fflush(stdout);
meow_ime_handle_tap(450, 584); /* SHIFT 开 */
meow_ime_handle_tap(20, 580); /* 'a' → 'A' 大写 */
assert(strcmp(g_echo, "aA") == 0 && strcmp(meow_ime_text(), "aA") == 0);
printf("[bug7] shift-on -> 'A' (echo \"%s\") PASS\n", g_echo); fflush(stdout);
meow_input_inject(MEOW_EV_TAP, 20, 680); /* ESC(X) → 取消 */
meow_ime_handle_tap(20, 680);
assert(meow_ime_active() == 0);
@@ -143,6 +149,68 @@ static void test_ime_echo_and_restore(void)
printf("PASS\n");
}
/* ---- 回归①:dialog INPUT 在 ESC/背景(result=0)关闭后文本不丢(M2.71-IME-fix 新语义)。
* 复现 dialog.c dlg_input_ok_center → 复用 dialog 的 OK 按钮中心几何(与 dlg_calc 同一公式)。 */
static void dialog_input_ok_center(int *ox, int *oy)
{
const meow_theme *th = meow_theme_get();
int W = meow_ui_width(), H = meow_ui_height();
int sb = meow_ui_statusbar_height(); if (sb < 0) sb = 0;
int pad = 24, btn = 44;
int tH = meow_ui_text_height(th->font_title);
int bH = meow_ui_text_height(th->font_body);
int content = bH + 8; /* INPUT(无 items)→ 1 行正文预览 */
int dw = 348; if (dw > W - 40) dw = W - 40;
int dx = (W - dw) / 2;
int dh = pad + tH + 12 + content + pad + (16 + btn);
int avail = H - sb - 20;
if (dh > avail) dh = avail;
int dy = (H - dh) / 2; if (dy < sb) dy = sb;
int row_y = dy + pad + tH + 12;
int btn_y = row_y + content + 16;
int btn_w = (dw - 2 * pad - 12) / 2;
int cx = dx + pad + btn_w + 12; /* OK = 右侧填充按钮左缘 */
*ox = cx + btn_w / 2;
*oy = btn_y + btn / 2;
}
static void *dlg_input_driver(void *arg)
{
(void)arg;
usleep(40000); /* 等 dialog show 起(遮罩+卡片已绘) */
meow_input_inject(MEOW_EV_TAP, K_DLG_OK_X, K_DLG_OK_Y); /* 点 OK → 打开阻塞 IME */
usleep(140000);
meow_input_inject(MEOW_EV_TAP, 20, 584); /* 行3第1键 'a'(shift 关) */
usleep(140000);
meow_input_inject(MEOW_EV_TAP, 20, 680); /* ESC → 关 IME → dialog 随之关 */
return NULL;
}
static void test_dialog_input_preserve(void)
{
const meow_theme *th = meow_theme_get();
meow_ui_clear(th->bg);
meow_ui_flush();
meow_ime_ctx ime; memset(&ime, 0, sizeof ime);
ime.mode = MEOW_IME_ASCII; ime.maxlen = 64;
strcpy(ime.buf, "");
meow_dialog *d = meow_dialog_create(MEOW_DLG_INPUT, "Name");
meow_dialog_set_ime(d, &ime);
dialog_input_ok_center(&K_DLG_OK_X, &K_DLG_OK_Y);
pthread_t thd; pthread_create(&thd, NULL, dlg_input_driver, NULL);
meow_dialog_show(d);
pthread_join(thd, NULL);
/* ESC 关闭(result=0)→ 文本应保留:App 原始 ctx.buf 应含 'a'(不再丢失),且是取消语义 */
printf("[fix1] dialog INPUT after ESC: result=%d App.ime.buf=\"%s\" (expect 0, \"a\")\n",
meow_dialog_result(d), ime.buf);
assert(meow_dialog_result(d) == 0);
assert(strcmp(ime.buf, "a") == 0);
printf("[fix1] dialog INPUT text preserved on ESC PASS\n");
meow_dialog_free(d);
}
int main(void)
{
setenv("MEOW_UI_DUMP", "/tmp/fixes.ppm", 1);
@@ -152,6 +220,7 @@ int main(void)
test_circular_extent();
test_modal_restore();
test_ime_echo_and_restore();
test_dialog_input_preserve();
meow_ui_shutdown();
printf("ALL PASS\n");
return 0;