fix(xim): honor reset and X11 keyboard state
This commit is contained in:
4
Makefile
4
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)
|
||||
|
||||
2
main.c
2
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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
59
xim/keymap.c
59
xim/keymap.c
@@ -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
233
xim/xim.c
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user