From 129548085845b27da503185b5dad53d9958b3249 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 12 Aug 2026 15:56:43 +0900 Subject: [PATCH] fix: repair XIM context lifecycle --- Makefile | 1 + tests/xim_test.c | 38 ++++++++++++++ xim/Makefile | 13 +++-- xim/keymap.c | 37 ++++++++++++++ xim/keymap.h | 8 +++ xim/main.c | 127 ++++++++++++++++++++++++++++++++--------------- 6 files changed, 181 insertions(+), 43 deletions(-) create mode 100644 tests/xim_test.c create mode 100644 xim/keymap.c create mode 100644 xim/keymap.h diff --git a/Makefile b/Makefile index be8ab4f..857a583 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,7 @@ docker-check: docker $(DOCKER_IMAGE) make check check: verify-map test + $(MAKE) -C xim check test: $(MAKE) -C tests check TESTARGS="$(TESTARGS)" diff --git a/tests/xim_test.c b/tests/xim_test.c new file mode 100644 index 0000000..d6f281b --- /dev/null +++ b/tests/xim_test.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include "../xim/keymap.h" + +enum +{ + ShiftMask = 1<<0, + LockMask = 1<<1, + Group1 = 1<<13, +}; + +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, + }; + + 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); + return 0; +} diff --git a/xim/Makefile b/xim/Makefile index abed715..3ba7904 100644 --- a/xim/Makefile +++ b/xim/Makefile @@ -6,6 +6,7 @@ PROG = strans-xim SRCS = $(wildcard *.c) OBJS = $(SRCS:.c=.o) +TEST = xim_test all: $(PROG) @@ -14,7 +15,13 @@ $(PROG): $(OBJS) ../ipc.c ../ipc.h $(OBJS): ../ipc.h -clean: - rm -f $(OBJS) $(PROG) +$(TEST): ../tests/xim_test.c keymap.c keymap.h + $(CC) $(CFLAGS) -o $@ ../tests/xim_test.c keymap.c $(XIM_LIBS) -.PHONY: all clean +check: $(TEST) + ./$(TEST) + +clean: + rm -f $(OBJS) $(PROG) $(TEST) + +.PHONY: all check clean diff --git a/xim/keymap.c b/xim/keymap.c new file mode 100644 index 0000000..c11894c --- /dev/null +++ b/xim/keymap.c @@ -0,0 +1,37 @@ +#include +#include "keymap.h" + +enum +{ + ShiftMask = 1<<0, + LockMask = 1<<1, + GroupShift = 13, + GroupMask = 3, +}; + +uint32_t +keymaplookup(const uint32_t *syms, int nsyms, uint16_t state) +{ + uint32_t lo, hi, lower, upper; + int col, group, shift; + + if(syms == NULL || nsyms <= 0) + 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; +} diff --git a/xim/keymap.h b/xim/keymap.h new file mode 100644 index 0000000..8633237 --- /dev/null +++ b/xim/keymap.h @@ -0,0 +1,8 @@ +#ifndef STRANS_XIM_KEYMAP_H +#define STRANS_XIM_KEYMAP_H + +#include + +uint32_t keymaplookup(const uint32_t*, int, uint16_t); + +#endif diff --git a/xim/main.c b/xim/main.c index 3961ec6..8e6ea5f 100644 --- a/xim/main.c +++ b/xim/main.c @@ -9,15 +9,21 @@ #include #include #include "ipc.h" +#include "keymap.h" + +typedef struct Ic Ic; +struct Ic +{ + int fd; +}; static xcb_connection_t *conn; static xcb_im_t *xim; -static int srvfd = -1; static xcb_keysym_t *kmap; static uint8_t minkc, maxkc; static uint8_t symsper; -static char *encs[] = {"COMPOUND_TEXT", "en_US.UTF-8", ""}; +static char *encs[] = {"COMPOUND_TEXT"}; static uint32_t styles[] = { XCB_IM_PreeditNothing | XCB_IM_StatusNothing, XCB_IM_PreeditNone | XCB_IM_StatusNone, @@ -37,7 +43,7 @@ kinit(void) xcb_get_keyboard_mapping_reply_t *r; const xcb_setup_t *setup; xcb_keysym_t *syms; - int i, n; + int n; setup = xcb_get_setup(conn); if(setup == NULL) @@ -50,26 +56,22 @@ kinit(void) die("keyboard mapping failed"); symsper = r->keysyms_per_keycode; n = xcb_get_keyboard_mapping_keysyms_length(r); - kmap = malloc(n * sizeof(xcb_keysym_t)); - if(kmap == NULL) + syms = malloc(n * sizeof(xcb_keysym_t)); + if(syms == NULL) die("malloc failed"); - syms = xcb_get_keyboard_mapping_keysyms(r); - for(i = 0; i < n; i++) - kmap[i] = syms[i]; + memcpy(syms, xcb_get_keyboard_mapping_keysyms(r), + n * sizeof(xcb_keysym_t)); + free(kmap); + kmap = syms; free(r); } static uint32_t kget(uint8_t kc, uint16_t state) { - int col; - if(kmap == NULL || kc < minkc || kc > maxkc) return 0; - col = (state & Mshift) ? 1 : 0; - if(col >= symsper) - col = 0; - return kmap[(kc - minkc) * symsper + col]; + return keymaplookup(kmap + (kc - minkc) * symsper, symsper, state); } static xcb_screen_t* @@ -106,21 +108,21 @@ commit(xcb_im_input_context_t *ic, char *s, int len) } static void -srvclose(void) +srvclose(Ic *state) { - if(srvfd < 0) + if(state == NULL || state->fd < 0) return; - close(srvfd); - srvfd = -1; + close(state->fd); + state->fd = -1; } static int -srvconnect(void) +srvconnect(Ic *state) { struct sockaddr_un addr; int fd; - if(srvfd >= 0) + if(state->fd >= 0) return 0; fd = socket(AF_UNIX, SOCK_STREAM, 0); if(fd < 0) @@ -132,25 +134,25 @@ srvconnect(void) close(fd); return -1; } - srvfd = fd; + state->fd = fd; return 0; } static int -readresp(xcb_im_input_context_t *ic) +readresp(Ic *state, xcb_im_input_context_t *ic) { char buf[Ipcfieldmax+1]; Ipcresp resp; - if(ipcreadresp(srvfd, 0, buf, sizeof buf, NULL, 0, &resp) < 0) + if(ipcreadresp(state->fd, 0, buf, sizeof buf, NULL, 0, &resp) < 0) return -1; - if(buf[0] != '\0') - commit(ic, buf, strlen(buf)); + if(resp.ncommit > 0) + commit(ic, buf, resp.ncommit); return resp.eaten; } static void -kpress(xcb_im_input_context_t *ic, xcb_key_press_event_t *ev) +kpress(Ic *state, xcb_im_input_context_t *ic, xcb_key_press_event_t *ev) { unsigned char buf[Ipcreqsz]; uint32_t key, rune; @@ -166,10 +168,10 @@ kpress(xcb_im_input_context_t *ic, xcb_key_press_event_t *ev) key = rune; ipcpackreq(buf, 0, ev->state, key); eaten = 0; - if(srvconnect() == 0){ - if(ipcsend(srvfd, buf, sizeof buf) < 0 || - (eaten = readresp(ic)) < 0){ - srvclose(); + if(srvconnect(state) == 0){ + if(ipcsend(state->fd, buf, sizeof buf) < 0 || + (eaten = readresp(state, ic)) < 0){ + srvclose(state); eaten = 0; } } @@ -179,15 +181,43 @@ kpress(xcb_im_input_context_t *ic, xcb_key_press_event_t *ev) } static void -reset(xcb_im_input_context_t *ic) +reset(Ic *state, xcb_im_input_context_t *ic, int release) { unsigned char buf[Ipcreqsz]; - ipcpackreset(buf, 0); - if(srvconnect() < 0) + if(state == NULL || state->fd < 0) return; - if(ipcsend(srvfd, buf, sizeof buf) < 0 || readresp(ic) < 0) - srvclose(); + ipcpackreset(buf, 0); + if(ipcsend(state->fd, buf, sizeof buf) < 0 || + readresp(state, ic) < 0) + release = 1; + if(release) + srvclose(state); +} + +static void +icfree(void *p) +{ + Ic *state; + + state = p; + /* imdkit frees this data on context destruction and client loss. */ + srvclose(state); + free(state); +} + +static void +iccreate(xcb_im_input_context_t *ic) +{ + Ic *state; + + if(ic == NULL || xcb_im_input_context_get_data(ic) != NULL) + return; + state = calloc(1, sizeof *state); + if(state == NULL) + return; + state->fd = -1; + xcb_im_input_context_set_data(ic, state, icfree); } static void @@ -195,20 +225,33 @@ 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) { xcb_key_press_event_t *ev; + Ic *state; (void)im; (void)client; (void)frame; (void)user; + if(hdr->major_opcode == XCB_XIM_CREATE_IC){ + iccreate(ic); + return; + } + if(ic == NULL) + return; + state = xcb_im_input_context_get_data(ic); + if(state == NULL) + return; switch(hdr->major_opcode){ case XCB_XIM_FORWARD_EVENT: ev = arg; if(ev != NULL && (ev->response_type & ~0x80) == XCB_KEY_PRESS) - kpress(ic, ev); + kpress(state, ic, ev); + break; + case XCB_XIM_RESET_IC: + reset(state, ic, 0); break; case XCB_XIM_UNSET_IC_FOCUS: - reset(ic); + reset(state, ic, 1); break; } } @@ -224,7 +267,7 @@ ximinit(void) st.nStyles = 2; st.styles = styles; - enc.nEncodings = 3; + enc.nEncodings = 1; enc.encodings = encs; xcb_compound_text_init(); conn = xcb_connect(NULL, &scr); @@ -247,15 +290,19 @@ int main(void) { xcb_generic_event_t *ev; + uint8_t type; ximinit(); for(;;){ ev = xcb_wait_for_event(conn); if(ev == NULL) break; - xcb_im_filter_event(xim, ev); + type = ev->response_type & ~0x80; + if(type == XCB_MAPPING_NOTIFY) + kinit(); + else + xcb_im_filter_event(xim, ev); free(ev); } - srvclose(); return 0; }