#include #include #include #include #include #include #include #include #include "ipc.h" #include "keymap.h" #include "ximtext.h" typedef struct Ic Ic; struct Ic { int fd; }; 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 char *encs[] = {"COMPOUND_TEXT"}; static uint32_t styles[] = { XCB_IM_PreeditNothing | XCB_IM_StatusNothing, XCB_IM_PreeditNone | XCB_IM_StatusNone, }; static void die(char *msg) { fprintf(stderr, "strans-xim: %s\n", msg); exit(1); } static void 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; int n; setup = xcb_get_setup(conn); if(setup == NULL) die("xcb_get_setup failed"); minkc = setup->min_keycode; maxkc = setup->max_keycode; c = xcb_get_keyboard_mapping(conn, minkc, maxkc - minkc + 1); r = xcb_get_keyboard_mapping_reply(conn, c, NULL); if(r == NULL) die("keyboard mapping failed"); symsper = r->keysyms_per_keycode; n = xcb_get_keyboard_mapping_keysyms_length(r); syms = malloc(n * sizeof(xcb_keysym_t)); if(syms == NULL) die("malloc failed"); 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) { if(kmap == NULL || 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 == NULL) die("xcb_get_setup failed"); iter = xcb_setup_roots_iterator(setup); for(; iter.rem; scr--, xcb_screen_next(&iter)) if(scr == 0) return iter.data; die("no screen"); return NULL; } 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 == NULL) return; xcb_im_commit_string(xim, ic, XCB_XIM_LOOKUP_CHARS, ct, clen, 0); xcb_flush(conn); free(ct); } static void srvclose(Ic *state) { if(state == NULL || state->fd < 0) return; close(state->fd); state->fd = -1; } static int srvconnect(Ic *state) { if(state->fd >= 0) return 0; state->fd = ipcconnect(); if(state->fd < 0) return -1; return 0; } static int readresp(Ic *state, xcb_im_input_context_t *ic) { char buf[Ipcfieldmax+1]; Ipcresp resp; if(ipcreadresp(state->fd, 0, buf, sizeof buf, NULL, 0, &resp) < 0) return -1; if(resp.ncommit > 0) commit(ic, buf, resp.ncommit); return resp.eaten; } static void kpress(Ic *state, xcb_im_input_context_t *ic, xcb_key_press_event_t *ev) { unsigned char buf[Ipcreqsz]; uint32_t key, rune; int eaten; 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; ipcpackreq(buf, 0, ev->state, key); eaten = 0; if(srvconnect(state) == 0){ if(ipcsend(state->fd, buf, sizeof buf) < 0 || (eaten = readresp(state, ic)) < 0){ srvclose(state); eaten = 0; } } if(eaten == 0) xcb_im_forward_event(xim, ic, ev); xcb_flush(conn); } static void reset(Ic *state, xcb_im_input_context_t *ic, int release) { unsigned char buf[Ipcreqsz]; if(state == NULL || state->fd < 0) return; 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 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(state, ic, ev); break; case XCB_XIM_RESET_IC: /* ResetIC ends this context's ownership, like focus loss. */ reset(state, ic, 1); break; case XCB_XIM_UNSET_IC_FOCUS: reset(state, ic, 1); break; } } static void ximinit(void) { xcb_screen_t *screen; xcb_window_t win; xcb_im_styles_t st; xcb_im_encodings_t enc; int scr; st.nStyles = 2; st.styles = styles; enc.nEncodings = 1; enc.encodings = encs; xcb_compound_text_init(); conn = xcb_connect(NULL, &scr); if(conn == NULL || xcb_connection_has_error(conn)) die("xcb_connect failed"); screen = getscreen(scr); kinit(); 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, NULL); xim = xcb_im_create(conn, scr, win, "strans", XCB_IM_ALL_LOCALES, &st, NULL, NULL, &enc, XCB_EVENT_MASK_KEY_PRESS, callback, NULL); if(xim == NULL || !xcb_im_open_im(xim)) die("xcb_im failed"); } int main(void) { xcb_generic_event_t *ev; uint8_t type; ximinit(); for(;;){ ev = xcb_wait_for_event(conn); if(ev == NULL) break; type = ev->response_type & ~0x80; if(type == XCB_MAPPING_NOTIFY) kinit(); else xcb_im_filter_event(xim, ev); free(ev); } return 0; }