#include "dat.h" #include "fn.h" #include #include #include #include u32int keymaplookup(const u32int*, int, u16int); char *ximcompound(const char*, size_t, size_t*); typedef struct Ic Ic; struct Ic { Ic *next; int engaged; }; 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 int opened; static Ic *ics; static Channel *replyc; static char *encs[] = {"COMPOUND_TEXT"}; static u32int styles[] = { XCB_IM_PreeditNothing | XCB_IM_StatusNothing, XCB_IM_PreeditNone | XCB_IM_StatusNone, }; static void ximlog(char *msg) { fprint(2, "strans: xim: %s\n", 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; 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; } 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); } static xcb_screen_t* getscreen(int scr) { xcb_screen_iterator_t iter; const xcb_setup_t *setup; setup = xcb_get_setup(conn); if(setup == nil) return nil; iter = xcb_setup_roots_iterator(setup); for(; iter.rem; scr--, xcb_screen_next(&iter)) if(scr == 0) return iter.data; return nil; } static void commit(xcb_im_input_context_t *ic, char *s, int len) { char *ct; size_t clen; if(len == 0) return; ct = ximcompound(s, len, &clen); if(ct == nil) return; xcb_im_commit_string(xim, ic, XCB_XIM_LOOKUP_CHARS, ct, clen, 0); xcb_flush(conn); free(ct); } static int meaningful(u32int key) { return key != 0 && (key < Kmodfirst || key > Kmodlast); } static void sendrequest(Ic *state, int op, u32int key, u32int mod, Keyres *res) { Keyreq kr; memset(&kr, 0, sizeof kr); kr.owner = state; kr.op = op; kr.ks = key; kr.mod = mod & Mmask; kr.reply = replyc; chansend(keyc, &kr); chanrecv(replyc, res); } static void keypress(Ic *state, u32int key, u32int mod, Keyres *res) { if(meaningful(key)) state->engaged = 1; sendrequest(state, Keypress, key, mod, res); } static void release(Ic *state) { Keyres res; if(state == nil || !state->engaged) return; sendrequest(state, Keyrelease, 0, 0, &res); state->engaged = 0; } static void kpress(Ic *state, xcb_im_input_context_t *ic, xcb_key_press_event_t *ev) { Keyres res; char buf[Maxutf]; u32int key, rune; int n; key = kget(ev->detail, ev->state); rune = xkb_keysym_to_utf32(key); if(rune >= ' ' && rune != 0x7f) key = rune; else if(key >= 0xff00 && key <= 0xffff) key = Kspec + (key - 0xff00); else key = rune; keypress(state, key, ev->state, &res); n = stoutf(&res.commit, buf, sizeof buf); commit(ic, buf, n); if(!res.eaten) xcb_im_forward_event(xim, ic, ev); xcb_flush(conn); } static void icunlink(Ic *state) { Ic **p; for(p = &ics; *p != nil; p = &(*p)->next) if(*p == state){ *p = state->next; return; } } static void icfree(void *p) { Ic *state; state = p; /* The owner remains valid until imthread acknowledges its release. */ release(state); icunlink(state); free(state); } static void iccreate(xcb_im_input_context_t *ic) { Ic *state; if(ic == nil || xcb_im_input_context_get_data(ic) != nil) return; state = calloc(1, sizeof *state); if(state == nil) return; state->next = ics; ics = state; xcb_im_input_context_set_data(ic, state, icfree); } 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) { xcb_key_press_event_t *ev; Ic *state; USED(im); USED(client); USED(frame); USED(user); if(hdr->major_opcode == XCB_XIM_CREATE_IC){ iccreate(ic); return; } if(ic == nil) return; state = xcb_im_input_context_get_data(ic); if(state == nil) return; switch(hdr->major_opcode){ case XCB_XIM_FORWARD_EVENT: ev = arg; if(ev != nil && (ev->response_type & ~0x80) == XCB_KEY_PRESS) kpress(state, ic, ev); break; case XCB_XIM_RESET_IC: case XCB_XIM_UNSET_IC_FOCUS: release(state); break; } } static void ximclose(void) { Ic *state, *next; for(state = ics; state != nil; state = state->next) release(state); if(xim != nil){ if(opened) xcb_im_close_im(xim); xcb_im_destroy(xim); xim = nil; } opened = 0; for(state = ics; state != nil; state = next){ next = state->next; free(state); } ics = nil; free(kmap); kmap = nil; if(conn != nil){ xcb_disconnect(conn); conn = nil; } if(replyc != nil){ chanfree(replyc); replyc = nil; } } static int ximinit(void) { xcb_screen_t *screen; xcb_window_t win; xcb_im_styles_t st; xcb_im_encodings_t enc; int scr; replyc = chancreate(sizeof(Keyres), 0); if(replyc == nil){ ximlog("cannot create reply channel"); return -1; } st.nStyles = nelem(styles); st.styles = styles; enc.nEncodings = nelem(encs); enc.encodings = encs; xcb_compound_text_init(); conn = xcb_connect(nil, &scr); if(conn == nil || xcb_connection_has_error(conn)){ ximlog("cannot connect to X server"); return -1; } screen = getscreen(scr); if(screen == nil){ ximlog("cannot find X screen"); return -1; } if(kinit() < 0){ ximlog("cannot read keyboard mapping"); return -1; } win = xcb_generate_id(conn); xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, screen->root, 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, 0, nil); xim = xcb_im_create(conn, scr, win, "strans", XCB_IM_ALL_LOCALES, &st, nil, nil, &enc, XCB_EVENT_MASK_KEY_PRESS, callback, nil); if(xim == nil){ ximlog("cannot create XIM server"); return -1; } if(!xcb_im_open_im(xim)){ ximlog("cannot claim XIM selection"); return -1; } opened = 1; return 0; } void ximthread(void *arg) { xcb_generic_event_t *ev; uint8_t type; USED(arg); threadsetname("xim"); if(ximinit() < 0){ ximclose(); return; } for(;;){ ev = xcb_wait_for_event(conn); if(ev == nil) break; type = ev->response_type & ~0x80; if(type == XCB_MAPPING_NOTIFY){ if(kinit() < 0) ximlog("cannot refresh keyboard mapping"); }else xcb_im_filter_event(xim, ev); free(ev); } if(xcb_connection_has_error(conn)) ximlog("X server disconnected"); ximclose(); }