fix(xim): honor reset and X11 keyboard state

This commit is contained in:
2026-08-14 22:34:37 +09:00
parent 9804c1f55c
commit a637f457f7
10 changed files with 394 additions and 258 deletions

View File

@@ -31,6 +31,9 @@ static void wirestart(xcb_im_t*, xcb_im_input_context_t*);
static void wiredraw(xcb_im_t*, xcb_im_input_context_t*,
xcb_im_preedit_draw_fr_t*);
static void wiredone(xcb_im_t*, xcb_im_input_context_t*);
static void wireforward(xcb_im_t*, xcb_im_input_context_t*,
xcb_key_press_event_t*);
static void wiresetdata(xcb_im_input_context_t*, void*, xcb_im_free_function);
static xcb_get_geometry_cookie_t wiregeometry(xcb_connection_t*, xcb_drawable_t);
static xcb_get_geometry_reply_t* wiregeometryreply(xcb_connection_t*,
xcb_get_geometry_cookie_t, xcb_generic_error_t**);
@@ -44,6 +47,8 @@ static int wireflush(xcb_connection_t*);
#define xcb_im_preedit_start_callback wirestart
#define xcb_im_preedit_draw_callback wiredraw
#define xcb_im_preedit_done_callback wiredone
#define xcb_im_forward_event wireforward
#define xcb_im_input_context_set_data wiresetdata
#define xcb_im_input_context_get_data(ic) \
(wirebinding(ic) == nil ? nil : wirebinding(ic)->data)
#define xcb_im_input_context_get_client_window(ic) \
@@ -64,6 +69,8 @@ static int wireflush(xcb_connection_t*);
#undef xcb_im_preedit_start_callback
#undef xcb_im_preedit_draw_callback
#undef xcb_im_preedit_done_callback
#undef xcb_im_forward_event
#undef xcb_im_input_context_set_data
#undef xcb_im_input_context_get_data
#undef xcb_im_input_context_get_client_window
#undef xcb_im_input_context_get_focus_window
@@ -82,6 +89,7 @@ enum
Wdraw,
Wdone,
Wcommit,
Wforward,
Maxwire = 8,
};
@@ -189,6 +197,29 @@ wiredone(xcb_im_t *im, xcb_im_input_context_t *ic)
newwire(Wdone, ic);
}
static void
wireforward(xcb_im_t *im, xcb_im_input_context_t *ic,
xcb_key_press_event_t *ev)
{
Wirecall *call;
USED(im);
call = newwire(Wforward, ic);
if(call != nil)
call->keysym = ev->detail;
}
static void
wiresetdata(xcb_im_input_context_t *ic, void *data, xcb_im_free_function freefn)
{
Icbinding *binding;
USED(freefn);
binding = wirebinding(ic);
if(binding != nil)
binding->data = data;
}
static Wirewin*
wirewindow(xcb_window_t win)
{
@@ -306,6 +337,29 @@ wirewin(int n, xcb_window_t win, int x, int y, int h)
return w;
}
static struct xkb_state*
testkeystate(char *layout)
{
struct xkb_context *context;
struct xkb_keymap *keymap;
struct xkb_rule_names names;
struct xkb_state *state;
memset(&names, 0, sizeof names);
names.layout = layout;
context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if(context == nil)
return nil;
keymap = xkb_keymap_new_from_names(context, &names,
XKB_KEYMAP_COMPILE_NO_FLAGS);
xkb_context_unref(context);
if(keymap == nil)
return nil;
state = xkb_state_new(keymap);
xkb_keymap_unref(keymap);
return state;
}
void testengineinit(int);
void testenginehandle(Keyreq*);
void* testengineowner(void);
@@ -727,17 +781,16 @@ xim_adapter_placement_updates(struct ct *t)
Ximfix f;
Keyreq req;
Ic state;
xcb_keysym_t key, *oldkmap;
uint8_t oldmin, oldmax, oldper;
struct xkb_state *oldstate;
ic = (xcb_im_input_context_t*)(uintptr)71;
memset(&state, 0, sizeof state);
oldkmap = kmap;
oldmin = minkc;
oldmax = maxkc;
oldper = symsper;
oldstate = kstate;
if(!ximbegin(t, &f, LangJP))
goto cleanup;
kstate = testkeystate("us");
if(!CT_CHECK(t, kstate != nil))
goto cleanup;
wireclear();
state.xic = ic;
state.style = XCB_IM_PreeditPosition | XCB_IM_StatusNothing;
@@ -749,12 +802,8 @@ xim_adapter_placement_updates(struct ct *t)
b->preattr.spot_location.x = 5;
b->preattr.spot_location.y = 6;
wirewin(0, 11, 100, 200, 30);
key = 'n';
kmap = &key;
minkc = maxkc = 8;
symsper = 1;
memset(&ev, 0, sizeof ev);
ev.detail = 8;
ev.detail = 57;
kpress(&state, &ev);
req = nexttrace(t, &f, Keypress, &state);
CT_CHECK(t, req.caret.valid);
@@ -785,10 +834,9 @@ xim_adapter_placement_updates(struct ct *t)
req = nexttrace(t, &f, Keycaret, &state);
CT_CHECK(t, !req.caret.valid);
cleanup:
kmap = oldkmap;
minkc = oldmin;
maxkc = oldmax;
symsper = oldper;
if(kstate != oldstate)
xkb_state_unref(kstate);
kstate = oldstate;
preowner = nil;
ximend(&f);
}
@@ -797,31 +845,27 @@ void
xim_adapter_encoding_negotiation(struct ct *t)
{
xcb_im_packet_header_fr_t hdr;
xcb_im_client_t *a, *b;
xcb_im_client_t *clients[Maxclients+1];
u16int selected;
int i;
a = (xcb_im_client_t*)(uintptr)1;
b = (xcb_im_client_t*)(uintptr)2;
memset(clientenc, 0, sizeof clientenc);
clearencodings();
CT_EQ_SIZE(t, 2, nelem(encs));
CT_EQ_STR(t, "COMPOUND_TEXT", encs[0]);
CT_EQ_STR(t, "UTF8_STRING", encs[1]);
memset(&hdr, 0, sizeof hdr);
hdr.major_opcode = XCB_XIM_ENCODING_NEGOTIATION;
selected = 1;
callback(nil, a, nil, &hdr, nil, &selected, nil);
selected = 0;
callback(nil, b, nil, &hdr, nil, &selected, nil);
CT_EQ_INT(t, Eutf8, getencoding(a));
CT_EQ_INT(t, Ecompound, getencoding(b));
for(i = 0; i < nelem(clients); i++){
clients[i] = (xcb_im_client_t*)(uintptr)(i+1);
selected = i == 0 ? 0 : 1;
callback(nil, clients[i], nil, &hdr, nil, &selected, nil);
}
CT_EQ_INT(t, Ecompound, getencoding(clients[0]));
CT_EQ_INT(t, Eutf8, getencoding(clients[Maxclients]));
hdr.major_opcode = XCB_XIM_DISCONNECT;
callback(nil, a, nil, &hdr, nil, nil, nil);
CT_EQ_INT(t, Ecompound, getencoding(a));
CT_EQ_INT(t, Ecompound, getencoding(b));
hdr.major_opcode = XCB_XIM_CLOSE;
callback(nil, b, nil, &hdr, nil, nil, nil);
CT_EQ_PTR(t, nil, clientenc[0].client);
CT_EQ_PTR(t, nil, clientenc[1].client);
for(i = 0; i < nelem(clients); i++)
callback(nil, clients[i], nil, &hdr, nil, nil, nil);
CT_EQ_PTR(t, nil, clientenc);
}
void
@@ -875,12 +919,7 @@ void
xim_adapter_callback_unicode(struct ct *t)
{
static char *text[] = {
"",
"かなカナ",
"漢字",
"𠀋",
"😀",
"❤️",
"𠀋",
"👩‍💻",
};
Ic state;
@@ -942,16 +981,57 @@ void
xim_adapter_callback_cleanup(struct ct *t)
{
static int opcode[] = {
XCB_XIM_RESET_IC,
XCB_XIM_UNSET_IC_FOCUS,
XCB_XIM_DESTROY_IC,
};
xcb_im_packet_header_fr_t hdr;
xcb_im_reset_ic_reply_fr_t reply;
xcb_key_press_event_t ev;
xcb_im_client_t *client;
xcb_im_input_context_t *ic;
Ximfix f;
Keyres res;
Ic state, *dead;
int i;
ic = (xcb_im_input_context_t*)(uintptr)29;
memset(&state, 0, sizeof state);
if(!ximbegin(t, &f, LangJP))
return;
wireclear();
wirebind(0, ic, &state);
state.xic = ic;
state.style = XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing;
state.cap = Cclientpreedit;
state.encoding = Eutf8;
keypress(&state, 'k', 0, &res);
nexttrace(t, &f, Keypress, &state);
keypress(&state, 'a', 0, &res);
nexttrace(t, &f, Keypress, &state);
updatepreedit(&state, &res.preedit);
memset(&hdr, 0, sizeof hdr);
memset(&reply, 0, sizeof reply);
hdr.major_opcode = XCB_XIM_RESET_IC;
callback(nil, nil, ic, &hdr, nil, &reply, nil);
nexttrace(t, &f, Keycap, &state);
nexttrace(t, &f, Keyreset, &state);
notrace(t, &f);
CT_EQ_INT(t, strlen(""), reply.byte_length_of_committed_string);
CT_EQ_MEM(t, "", reply.committed_string,
reply.byte_length_of_committed_string);
free(reply.committed_string);
CT_CHECK(t, state.engaged);
CT_EQ_PTR(t, &state, testengineowner());
CT_EQ_INT(t, Wdraw, wirecalls[2].op);
CT_EQ_INT(t, Wdone, wirecalls[3].op);
keypress(&state, 'n', 0, &res);
nexttrace(t, &f, Keypress, &state);
checkstr(t, "", &res.preedit);
release(&state);
nexttrace(t, &f, Keyrelease, &state);
preowner = nil;
ximend(&f);
for(i = 0; i < nelem(opcode); i++){
ic = (xcb_im_input_context_t*)(uintptr)(30+i);
wireclear();
@@ -994,6 +1074,24 @@ xim_adapter_callback_cleanup(struct ct *t)
CT_EQ_INT(t, Wdraw, wirecalls[2].op);
CT_EQ_INT(t, Wdone, wirecalls[3].op);
CT_EQ_PTR(t, nil, preowner);
ic = (xcb_im_input_context_t*)(uintptr)42;
wireclear();
wirebind(0, ic, nil);
icinstall(nil, ic, nil);
CT_EQ_PTR(t, &passthrough, icbindings[0].data);
memset(&ev, 0, sizeof ev);
ev.response_type = XCB_KEY_PRESS;
ev.detail = 38;
hdr.major_opcode = XCB_XIM_FORWARD_EVENT;
callback(nil, nil, ic, &hdr, nil, &ev, nil);
CT_EQ_INT(t, 1, nwirecalls);
CT_EQ_INT(t, Wforward, wirecalls[0].op);
CT_EQ_UINT(t, ev.detail, wirecalls[0].keysym);
ev.response_type = XCB_KEY_RELEASE;
callback(nil, nil, ic, &hdr, nil, &ev, nil);
CT_EQ_INT(t, 2, nwirecalls);
CT_EQ_INT(t, Wforward, wirecalls[1].op);
}
void