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

@@ -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)

2
main.c
View File

@@ -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);

View File

@@ -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

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

View File

@@ -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;
}

View File

@@ -4,18 +4,61 @@
#include <stdlib.h>
#include <string.h>
#include <xcb-imdkit/encoding.h>
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-keysyms.h>
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();

View File

@@ -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

View File

@@ -1,37 +1,38 @@
#include <stdint.h>
#include <xcb/xcb.h>
#include <xkbcommon/xkbcommon.h>
enum
{
ShiftMask = 1<<0,
LockMask = 1<<1,
GroupShift = 13,
GroupMask = 3,
};
#include <xkbcommon/xkbcommon-names.h>
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);
}

233
xim/xim.c
View File

@@ -7,10 +7,11 @@
#include <unistd.h>
#include <xcb/xcb.h>
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-x11.h>
#include <xcb-imdkit/imdkit.h>
#include <xcb-imdkit/encoding.h>
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");
}

View File

@@ -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);