fix: repair XIM context lifecycle
This commit is contained in:
1
Makefile
1
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)"
|
||||
|
||||
38
tests/xim_test.c
Normal file
38
tests/xim_test.c
Normal file
@@ -0,0 +1,38 @@
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <xkbcommon/xkbcommon-keysyms.h>
|
||||
#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;
|
||||
}
|
||||
13
xim/Makefile
13
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
|
||||
|
||||
37
xim/keymap.c
Normal file
37
xim/keymap.c
Normal file
@@ -0,0 +1,37 @@
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#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;
|
||||
}
|
||||
8
xim/keymap.h
Normal file
8
xim/keymap.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef STRANS_XIM_KEYMAP_H
|
||||
#define STRANS_XIM_KEYMAP_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
uint32_t keymaplookup(const uint32_t*, int, uint16_t);
|
||||
|
||||
#endif
|
||||
127
xim/main.c
127
xim/main.c
@@ -9,15 +9,21 @@
|
||||
#include <xcb-imdkit/imdkit.h>
|
||||
#include <xcb-imdkit/encoding.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user