From a637f457f7d53f7cf5e0008eb19ce642443af830 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 14 Aug 2026 22:34:37 +0900 Subject: [PATCH] fix(xim): honor reset and X11 keyboard state --- Makefile | 4 +- main.c | 2 +- tests/Makefile | 4 +- tests/xim_adapter_test.c | 176 ++++++++++++++++++++++------- tests/xim_live_test.c | 96 ++++------------ tests/xim_test.c | 70 +++++++----- xim/Makefile | 4 +- xim/keymap.c | 59 +++++----- xim/xim.c | 233 +++++++++++++++++++++++++-------------- xim/ximtext.c | 4 + 10 files changed, 394 insertions(+), 258 deletions(-) diff --git a/Makefile b/Makefile index 030cedd..8e8af6b 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ CC = 9c LD = 9l PKG_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon) PKG_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon) -XIM_CFLAGS = $(shell pkg-config --cflags xcb-imdkit xkbcommon) -XIM_LIBS = $(shell pkg-config --libs xcb-imdkit xkbcommon) +XIM_CFLAGS = $(shell pkg-config --cflags xcb-imdkit xkbcommon-x11) +XIM_LIBS = $(shell pkg-config --libs xcb-imdkit xkbcommon-x11) TEXT_CFLAGS = $(shell pkg-config --cflags pangocairo cairo) TEXT_LIBS = $(shell pkg-config --libs pangocairo cairo) CFLAGS = -Wall -Wextra -O2 -g $(PKG_CFLAGS) diff --git a/main.c b/main.c index f5b8fe2..8310f52 100644 --- a/main.c +++ b/main.c @@ -70,7 +70,7 @@ threadmain(int argc, char **argv) display = getenv("DISPLAY"); if(display != nil && display[0] != '\0' && proccreate(ximthread, nil, 32768) < 0) - fprint(2, "strans: xim: cannot create worker\n"); + die("xim: cannot create worker"); if(threadcreate(dictthread, nil, 16384) < 0) die("can't create dictionary worker"); imthread(nil); diff --git a/tests/Makefile b/tests/Makefile index f46a707..3e4ad44 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -14,8 +14,8 @@ IBUS_CLIENT_CFLAGS = $(shell pkg-config --cflags ibus-1.0) IBUS_CLIENT_LIBS = $(shell pkg-config --libs ibus-1.0) X11_CFLAGS = $(shell pkg-config --cflags x11) X11_LIBS = $(shell pkg-config --libs x11) -XIM_CFLAGS = $(shell pkg-config --cflags xcb-imdkit xkbcommon) -XIM_LIBS = $(shell pkg-config --libs xcb-imdkit xkbcommon) +XIM_CFLAGS = $(shell pkg-config --cflags xcb-imdkit xkbcommon-x11) +XIM_LIBS = $(shell pkg-config --libs xcb-imdkit xkbcommon-x11) LIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS) $(XIM_LIBS) PROG = unit_test diff --git a/tests/xim_adapter_test.c b/tests/xim_adapter_test.c index 77951b9..5ef8317 100644 --- a/tests/xim_adapter_test.c +++ b/tests/xim_adapter_test.c @@ -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 diff --git a/tests/xim_live_test.c b/tests/xim_live_test.c index dce3b46..883279f 100644 --- a/tests/xim_live_test.c +++ b/tests/xim_live_test.c @@ -614,46 +614,6 @@ waitcommit(Display *dpy, XIC ic, char *commit, size_t cap) return 0; } -static int -rawtakeover(void) -{ - unsigned char req[Ipcreqsz]; - char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1]; - Ipcresp resp; - struct timeval tv; - int fd; - - fd = ipcconnect(); - if(fd < 0) - return fail("connect raw IPC owner: %s", strerror(errno)); - tv.tv_sec = Eventtimeout / 1000; - tv.tv_usec = (Eventtimeout % 1000) * 1000; - setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); - setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv); - ipcpackreq(req, 0, 0, 'z'); - if(ipcsend(fd, req, sizeof req) < 0 || - ipcreadresp(fd, 0, commit, sizeof commit, - preedit, sizeof preedit, &resp) < 0){ - close(fd); - return fail("raw IPC ownership transfer: %s", strerror(errno)); - } - return fd; -} - -static int -waitclear(Display *dpy, Prelog *p, int empty, int done) -{ - int64_t deadline; - - deadline = nowms() + Eventtimeout; - while(leftms(deadline) > 0){ - pump(dpy, leftms(deadline)); - if(p->empty >= empty && p->done >= done) - return 1; - } - return fail("timed out waiting for empty DRAW and DONE"); -} - static int waitpreedit(Display *dpy, Prelog *p) { @@ -692,7 +652,6 @@ testcallbacks(Display *dpy, XIM im, Window win) char commit[Ipcfieldmax+1], *reset; Prelog log; XIC ic; - int fd, empty, done; memset(&log, 0, sizeof log); ic = callbackic(im, win, &log); @@ -730,42 +689,33 @@ testcallbacks(Display *dpy, XIM im, Window win) return fail("bad initial callback sequence: start=%d draw=%d empty=%d done=%d ordered=%d bad=%d", start, draw, empty, done, ordered, bad); } - empty = log.empty; - done = log.done; - fd = rawtakeover(); - if(fd < 0){ - XDestroyIC(ic); - return 0; - } - if(!waitclear(dpy, &log, empty + 1, done + 1)){ - close(fd); - XDestroyIC(ic); - return 0; - } - /* Leave the live IC time to expose a delayed duplicate transfer clear. */ - pump(dpy, 100); - if(log.empty != empty + 1 || log.done != done + 1 || - log.doneafterempty != 1 || log.badorder != 0){ - int gotempty, gotdone, ordered; - - gotempty = log.empty - empty; - gotdone = log.done - done; - ordered = log.doneafterempty; - close(fd); - XDestroyIC(ic); - return fail("owner loss callback counts: empty=%d done=%d ordered=%d", - gotempty, gotdone, ordered); - } reset = Xutf8ResetIC(ic); - if(reset != NULL) - XFree(reset); + if(reset == NULL || strcmp(reset, "ㄱ") != 0){ + if(reset != NULL) + XFree(reset); + XDestroyIC(ic); + return fail("ResetIC did not return pending preedit"); + } + XFree(reset); + if(!sendkey(dpy, ic, win, XK_r, 0, commit, sizeof commit) || + !sendkey(dpy, ic, win, XK_k, 0, commit, sizeof commit) || + !sendkey(dpy, ic, win, XK_Return, 0, commit, sizeof commit)){ + XDestroyIC(ic); + return 0; + } + if(commit[0] == '\0' && !waitcommit(dpy, ic, commit, sizeof commit)){ + XDestroyIC(ic); + return fail("input context did not compose after ResetIC"); + } + if(strcmp(commit, "가") != 0){ + XDestroyIC(ic); + return fail("post-reset composition did not contain 가"); + } XUnsetICFocus(ic); XDestroyIC(ic); pump(dpy, 100); - close(fd); - pump(dpy, 100); - if(log.empty != empty + 1 || log.done != done + 1) - return fail("late lifecycle produced duplicate clear callbacks"); + if(log.badorder != 0) + return fail("bad callback order after ResetIC"); return 1; } diff --git a/tests/xim_test.c b/tests/xim_test.c index a1464ce..d6e44ad 100644 --- a/tests/xim_test.c +++ b/tests/xim_test.c @@ -4,18 +4,61 @@ #include #include #include +#include #include -uint32_t keymaplookup(const uint32_t*, int, uint16_t); +uint32_t keymaplookup(struct xkb_state*, uint8_t, uint16_t); char *ximcompound(const char*, size_t, size_t*); enum { ShiftMask = 1<<0, LockMask = 1<<1, + Mod2Mask = 1<<4, + Mod5Mask = 1<<7, Group1 = 1<<13, }; +static struct xkb_state* +keystate(const 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); + assert(context != NULL); + keymap = xkb_keymap_new_from_names(context, &names, + XKB_KEYMAP_COMPILE_NO_FLAGS); + assert(keymap != NULL); + state = xkb_state_new(keymap); + assert(state != NULL); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + return state; +} + +static void +checkkeys(void) +{ + struct xkb_state *state; + + state = keystate("de,ru"); + assert(keymaplookup(state, 38, 0) == XKB_KEY_a); + assert(keymaplookup(state, 38, ShiftMask) == XKB_KEY_A); + assert(keymaplookup(state, 38, LockMask) == XKB_KEY_A); + assert(keymaplookup(state, 38, LockMask|ShiftMask) == XKB_KEY_a); + assert(keymaplookup(state, 26, Mod5Mask) == XKB_KEY_EuroSign); + assert(keymaplookup(state, 26, Group1) == XKB_KEY_Cyrillic_u); + assert(keymaplookup(state, 87, 0) == XKB_KEY_KP_End); + assert(keymaplookup(state, 87, Mod2Mask) == XKB_KEY_KP_1); + xkb_state_unref(state); + assert(keymaplookup(NULL, 38, 0) == XKB_KEY_NoSymbol); +} + static void checktext(const char *s) { @@ -53,11 +96,7 @@ checkcorpus(void) { static const char *text[] = { "한글", - "かなカナ", - "漢字", "𠀋", - "😀", - "❤️", "👩‍💻", }; size_t i; @@ -69,28 +108,9 @@ checkcorpus(void) int main(void) { - static const uint32_t letters[] = { - XKB_KEY_a, XKB_KEY_A, XKB_KEY_Cyrillic_ef, XKB_KEY_Cyrillic_EF, - }; - static const uint32_t inferred[] = { XKB_KEY_a, XKB_KEY_NoSymbol }; - static const uint32_t punctuation[] = { XKB_KEY_1, XKB_KEY_exclam }; - static const uint32_t missing[] = { - XKB_KEY_a, XKB_KEY_A, XKB_KEY_NoSymbol, XKB_KEY_NoSymbol, - }; - xcb_compound_text_init(); - assert(keymaplookup(letters, 4, 0) == XKB_KEY_a); - assert(keymaplookup(letters, 4, ShiftMask) == XKB_KEY_A); - assert(keymaplookup(letters, 4, LockMask) == XKB_KEY_A); - assert(keymaplookup(letters, 4, LockMask|ShiftMask) == XKB_KEY_a); - assert(keymaplookup(letters, 4, Group1) == XKB_KEY_Cyrillic_ef); - assert(keymaplookup(letters, 4, Group1|ShiftMask) == XKB_KEY_Cyrillic_EF); - assert(keymaplookup(inferred, 2, ShiftMask) == XKB_KEY_A); - assert(keymaplookup(punctuation, 2, LockMask) == XKB_KEY_1); - assert(keymaplookup(punctuation, 2, LockMask|ShiftMask) == XKB_KEY_exclam); - assert(keymaplookup(missing, 4, Group1) == XKB_KEY_a); - assert(keymaplookup(NULL, 0, 0) == XKB_KEY_NoSymbol); + checkkeys(); checkcorpus(); checktext("A😀한"); checkutf8run(); diff --git a/xim/Makefile b/xim/Makefile index 4b22575..76acfe2 100644 --- a/xim/Makefile +++ b/xim/Makefile @@ -1,6 +1,6 @@ CC = cc -XIM_CFLAGS = $(shell pkg-config --cflags xcb-imdkit xkbcommon) -XIM_LIBS = $(shell pkg-config --libs xcb-imdkit xkbcommon) +XIM_CFLAGS = $(shell pkg-config --cflags xcb-imdkit xkbcommon-x11) +XIM_LIBS = $(shell pkg-config --libs xcb-imdkit xkbcommon-x11) CFLAGS = -Wall -Wextra -O2 -I.. $(XIM_CFLAGS) TEST = xim_test diff --git a/xim/keymap.c b/xim/keymap.c index 460b100..935877f 100644 --- a/xim/keymap.c +++ b/xim/keymap.c @@ -1,37 +1,38 @@ #include +#include #include - -enum -{ - ShiftMask = 1<<0, - LockMask = 1<<1, - GroupShift = 13, - GroupMask = 3, -}; +#include uint32_t -keymaplookup(const uint32_t *syms, int nsyms, uint16_t state) +keymaplookup(struct xkb_state *state, uint8_t keycode, uint16_t corestate) { - uint32_t lo, hi, lower, upper; - int col, group, shift; + static const char *modname[] = { + XKB_MOD_NAME_SHIFT, + XKB_MOD_NAME_CAPS, + XKB_MOD_NAME_CTRL, + XKB_MOD_NAME_MOD1, + XKB_MOD_NAME_MOD2, + XKB_MOD_NAME_MOD3, + XKB_MOD_NAME_MOD4, + XKB_MOD_NAME_MOD5, + }; + struct xkb_keymap *keymap; + xkb_mod_index_t index; + xkb_mod_mask_t mods; + int i; - if(syms == NULL || nsyms <= 0) + if(state == NULL) return XKB_KEY_NoSymbol; - group = (state >> GroupShift) & GroupMask; - col = group * 2; - if(col >= nsyms || syms[col] == XKB_KEY_NoSymbol) - col = 0; - lo = syms[col]; - if(lo == XKB_KEY_NoSymbol) - return XKB_KEY_NoSymbol; - hi = col + 1 < nsyms ? syms[col + 1] : XKB_KEY_NoSymbol; - lower = xkb_keysym_to_lower(lo); - upper = xkb_keysym_to_upper(lo); - if(hi == XKB_KEY_NoSymbol) - hi = lower != upper ? upper : lo; - shift = (state & ShiftMask) != 0; - /* Lock reverses Shift only for keys with an alphabetic case pair. */ - if((state & LockMask) && lower != upper) - shift = !shift; - return shift ? hi : lo; + keymap = xkb_state_get_keymap(state); + mods = 0; + for(i = 0; i < (int)(sizeof modname / sizeof modname[0]); i++){ + if(!(corestate & (1U << i))) + continue; + index = xkb_keymap_mod_get_index(keymap, modname[i]); + if(index != XKB_MOD_INVALID && index < 8 * sizeof mods) + mods |= (xkb_mod_mask_t)1 << index; + } + xkb_state_update_mask(state, mods, 0, 0, 0, 0, + (corestate >> 13) & 3); + return xkb_state_key_get_one_sym(state, keycode); } diff --git a/xim/xim.c b/xim/xim.c index fe44a1c..0b71147 100644 --- a/xim/xim.c +++ b/xim/xim.c @@ -7,10 +7,11 @@ #include #include #include +#include #include #include -u32int keymaplookup(const u32int*, int, u16int); +u32int keymaplookup(struct xkb_state*, uint8_t, u16int); char *ximcompound(const char*, size_t, size_t*); typedef struct Ic Ic; @@ -35,6 +36,7 @@ struct Ic typedef struct Clientenc Clientenc; struct Clientenc { + Clientenc *next; xcb_im_client_t *client; int encoding; }; @@ -47,14 +49,13 @@ enum static xcb_connection_t *conn; static xcb_im_t *xim; -static xcb_keysym_t *kmap; -static uint8_t minkc, maxkc; -static uint8_t symsper; +static struct xkb_state *kstate; static xcb_window_t rootwin; static int opened; static Ic *ics; static Ic *preowner; -static Clientenc clientenc[Maxclients]; +static Ic passthrough; +static Clientenc *clientenc; static Channel *replyc; static int wakefd[2] = {-1, -1}; @@ -148,48 +149,61 @@ wakedrain(void) static int getencoding(xcb_im_client_t *client) { - int i; + Clientenc *p; - for(i = 0; i < nelem(clientenc); i++) - if(clientenc[i].client == client) - return clientenc[i].encoding; + for(p = clientenc; p != nil; p = p->next) + if(p->client == client) + return p->encoding; return Ecompound; } -static void +static int setencoding(xcb_im_client_t *client, int encoding) { - int empty, i; + Clientenc *p; if(client == nil) - return; - empty = -1; - for(i = 0; i < nelem(clientenc); i++){ - if(clientenc[i].client == client){ - clientenc[i].encoding = encoding; - return; + return -1; + for(p = clientenc; p != nil; p = p->next) + if(p->client == client){ + p->encoding = encoding; + return 0; } - if(empty < 0 && clientenc[i].client == nil) - empty = i; - } - if(empty >= 0){ - clientenc[empty].client = client; - clientenc[empty].encoding = encoding; - } + p = calloc(1, sizeof *p); + if(p == nil) + return -1; + p->client = client; + p->encoding = encoding; + p->next = clientenc; + clientenc = p; + return 0; } static void clearencoding(xcb_im_client_t *client) { - int i; + Clientenc *p, **link; - for(i = 0; i < nelem(clientenc); i++) - if(clientenc[i].client == client){ - memset(&clientenc[i], 0, sizeof clientenc[i]); + for(link = &clientenc; (p = *link) != nil; link = &p->next) + if(p->client == client){ + *link = p->next; + free(p); return; } } +static void +clearencodings(void) +{ + Clientenc *next, *p; + + for(p = clientenc; p != nil; p = next){ + next = p->next; + free(p); + } + clientenc = nil; +} + static void ximlog(char *msg) { @@ -199,46 +213,46 @@ ximlog(char *msg) static int kinit(void) { - xcb_get_keyboard_mapping_cookie_t c; - xcb_get_keyboard_mapping_reply_t *r; - const xcb_setup_t *setup; - xcb_keysym_t *syms; - uint8_t newmin, newmax, newper; - int n; + struct xkb_context *context; + struct xkb_keymap *keymap; + struct xkb_state *state; + int32_t device; + int ok; - setup = xcb_get_setup(conn); - if(setup == nil) - return -1; - newmin = setup->min_keycode; - newmax = setup->max_keycode; - c = xcb_get_keyboard_mapping(conn, newmin, newmax - newmin + 1); - r = xcb_get_keyboard_mapping_reply(conn, c, nil); - if(r == nil) - return -1; - newper = r->keysyms_per_keycode; - n = xcb_get_keyboard_mapping_keysyms_length(r); - syms = malloc(n * sizeof(xcb_keysym_t)); - if(syms == nil){ - free(r); - return -1; - } - memcpy(syms, xcb_get_keyboard_mapping_keysyms(r), - n * sizeof(xcb_keysym_t)); - free(kmap); - kmap = syms; - minkc = newmin; - maxkc = newmax; - symsper = newper; - free(r); - return 0; + context = nil; + keymap = nil; + state = nil; + ok = -1; + context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if(context == nil || !xkb_x11_setup_xkb_extension(conn, + XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION, + XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, nil, nil, nil, nil)) + goto out; + device = xkb_x11_get_core_keyboard_device_id(conn); + if(device < 0) + goto out; + keymap = xkb_x11_keymap_new_from_device(context, conn, device, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if(keymap == nil) + goto out; + state = xkb_state_new(keymap); + if(state == nil) + goto out; + xkb_state_unref(kstate); + kstate = state; + state = nil; + ok = 0; +out: + xkb_state_unref(state); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + return ok; } static u32int kget(uint8_t kc, u16int state) { - if(kmap == nil || kc < minkc || kc > maxkc) - return 0; - return keymaplookup(kmap + (kc - minkc) * symsper, symsper, state); + return keymaplookup(kstate, kc, state); } static xcb_screen_t* @@ -257,13 +271,13 @@ getscreen(int scr) return nil; } -static int +static void readattrs(Ic *state) { const xcb_im_preedit_attr_t *attr; xcb_window_t clientwin, focuswin; xcb_point_t spot; - int changed, hasspot; + int hasspot; clientwin = xcb_im_input_context_get_client_window(state->xic); focuswin = xcb_im_input_context_get_focus_window(state->xic); @@ -278,14 +292,10 @@ readattrs(Ic *state) hasspot = 1; } } - changed = clientwin != state->clientwin || focuswin != state->focuswin || - hasspot != state->hasspot || (hasspot && - (spot.x != state->spot.x || spot.y != state->spot.y)); state->clientwin = clientwin; state->focuswin = focuswin; state->spot = spot; state->hasspot = hasspot; - return changed; } static int @@ -507,6 +517,41 @@ release(Ic *state) state->engaged = 0; } +static void +resetic(Ic *state, xcb_im_reset_ic_reply_fr_t *reply) +{ + Keyres pending, res; + char utf[Maxutf], *wire; + size_t nwire; + int nbyte; + + if(state == nil) + return; + sclear(&pending.preedit); + if(!state->engaged){ + clearpreedit(state); + return; + } + sendrequest(state, Keycap, 0, 0, &pending); + sendrequest(state, Keyreset, 0, 0, &res); + clearpreedit(state); + if(reply == nil || !pending.eaten || pending.preedit.n == 0) + return; + nbyte = stoutf(&pending.preedit, utf, sizeof utf); + if(state->encoding == Ecompound) + wire = ximcompound(utf, nbyte, &nwire); + else{ + nwire = nbyte; + wire = malloc(nwire); + if(wire != nil) + memmove(wire, utf, nwire); + } + if(wire == nil) + return; + reply->committed_string = (uchar*)wire; + reply->byte_length_of_committed_string = nwire; +} + static void kpress(Ic *state, xcb_key_press_event_t *ev) { @@ -574,15 +619,13 @@ setstyle(Ic *state, u32int style) } static void -iccreate(xcb_im_client_t *client, xcb_im_input_context_t *ic) +icinstall(xcb_im_client_t *client, xcb_im_input_context_t *ic, Ic *state) { - Ic *state; - - if(ic == nil || xcb_im_input_context_get_data(ic) != nil) - return; - state = calloc(1, sizeof *state); - if(state == nil) + if(state == nil){ + ximlog("out of memory; passing input context through"); + xcb_im_input_context_set_data(ic, &passthrough, nil); return; + } state->xic = ic; state->client = client; state->encoding = getencoding(client); @@ -593,6 +636,14 @@ iccreate(xcb_im_client_t *client, xcb_im_input_context_t *ic) xcb_im_input_context_set_data(ic, state, icfree); } +static void +iccreate(xcb_im_client_t *client, xcb_im_input_context_t *ic) +{ + if(ic == nil || xcb_im_input_context_get_data(ic) != nil) + return; + icinstall(client, ic, calloc(1, sizeof(Ic))); +} + static void callback(xcb_im_t *im, xcb_im_client_t *client, xcb_im_input_context_t *ic, const xcb_im_packet_header_fr_t *hdr, void *frame, void *arg, void *user) @@ -604,9 +655,9 @@ callback(xcb_im_t *im, xcb_im_client_t *client, xcb_im_input_context_t *ic, USED(frame); USED(user); if(hdr->major_opcode == XCB_XIM_ENCODING_NEGOTIATION){ - if(arg != nil) - setencoding(client, - *(u16int*)arg == 1 ? Eutf8 : Ecompound); + if(arg != nil && setencoding(client, + *(u16int*)arg == 1 ? Eutf8 : Ecompound) < 0) + die("xim: cannot remember client encoding"); return; } if(hdr->major_opcode == XCB_XIM_DISCONNECT || @@ -627,6 +678,14 @@ callback(xcb_im_t *im, xcb_im_client_t *client, xcb_im_input_context_t *ic, state = xcb_im_input_context_get_data(ic); if(state == nil) return; + if(state == &passthrough){ + if(hdr->major_opcode == XCB_XIM_FORWARD_EVENT){ + ev = arg; + if(ev != nil) + xcb_im_forward_event(xim, ic, ev); + } + return; + } switch(hdr->major_opcode){ case XCB_XIM_FORWARD_EVENT: ev = arg; @@ -643,10 +702,12 @@ callback(xcb_im_t *im, xcb_im_client_t *client, xcb_im_input_context_t *ic, } break; case XCB_XIM_DESTROY_IC: - case XCB_XIM_RESET_IC: case XCB_XIM_UNSET_IC_FOCUS: release(state); break; + case XCB_XIM_RESET_IC: + resetic(state, arg); + break; } } @@ -665,15 +726,15 @@ ximclose(void) } opened = 0; preowner = nil; - memset(clientenc, 0, sizeof clientenc); + clearencodings(); for(state = ics; state != nil; state = next){ next = state->next; free(state); } ics = nil; rootwin = XCB_NONE; - free(kmap); - kmap = nil; + xkb_state_unref(kstate); + kstate = nil; if(conn != nil){ xcb_disconnect(conn); conn = nil; @@ -753,7 +814,7 @@ ximthread(void *arg) threadsetname("xim"); if(ximinit() < 0){ ximclose(); - return; + die("xim: initialization failed"); } fd = xcb_get_file_descriptor(conn); pfd[0].fd = fd; @@ -785,7 +846,9 @@ ximthread(void *arg) if(pfd[0].revents & (POLLERR|POLLHUP|POLLNVAL)) break; } - if(xcb_connection_has_error(conn)) - ximlog("X server disconnected"); + n = xcb_connection_has_error(conn); ximclose(); + if(n) + die("xim: X server disconnected"); + die("xim: frontend stopped"); } diff --git a/xim/ximtext.c b/xim/ximtext.c index bf9bd57..4adddbf 100644 --- a/xim/ximtext.c +++ b/xim/ximtext.c @@ -11,6 +11,8 @@ ximutf8compound(const char *s, size_t len, size_t *outlen) char *ct; size_t n; + if(outlen != NULL) + *outlen = 0; if(s == NULL || len > SIZE_MAX - (sizeof begin + sizeof end - 1)) return NULL; n = len + sizeof begin + sizeof end - 2; @@ -31,6 +33,8 @@ ximcompound(const char *s, size_t len, size_t *outlen) { char *ct; + if(outlen != NULL) + *outlen = 0; if(s == NULL) return NULL; ct = xcb_utf8_to_compound_text(s, len, outlen);