#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; xcb_im_input_context_t *xic; xcb_im_client_t *client; int engaged; u32int style; int cap; int encoding; int prestarted; int nprerune; xcb_window_t clientwin; xcb_window_t focuswin; xcb_point_t spot; int hasspot; Caret caret; }; typedef struct Clientenc Clientenc; struct Clientenc { xcb_im_client_t *client; int encoding; }; enum { Ecompound, Eutf8, }; 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 xcb_window_t rootwin; static int opened; static Ic *ics; static Ic *preowner; static Clientenc clientenc[Maxclients]; static Channel *replyc; static char *encs[] = {"COMPOUND_TEXT", "UTF8_STRING"}; static u32int styles[] = { XCB_IM_PreeditPosition | XCB_IM_StatusNothing, XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing, XCB_IM_PreeditNothing | XCB_IM_StatusNothing, }; static int stylecaps[] = {0, Cclientpreedit, 0}; static int stylecap(u32int style, int *cap) { int i; for(i = 0; i < nelem(styles); i++) if(styles[i] == style){ *cap = stylecaps[i]; return 1; } *cap = 0; return 0; } static int getencoding(xcb_im_client_t *client) { int i; for(i = 0; i < nelem(clientenc); i++) if(clientenc[i].client == client) return clientenc[i].encoding; return Ecompound; } static void setencoding(xcb_im_client_t *client, int encoding) { int empty, i; if(client == nil) return; empty = -1; for(i = 0; i < nelem(clientenc); i++){ if(clientenc[i].client == client){ clientenc[i].encoding = encoding; return; } if(empty < 0 && clientenc[i].client == nil) empty = i; } if(empty >= 0){ clientenc[empty].client = client; clientenc[empty].encoding = encoding; } } static void clearencoding(xcb_im_client_t *client) { int i; for(i = 0; i < nelem(clientenc); i++) if(clientenc[i].client == client){ memset(&clientenc[i], 0, sizeof clientenc[i]); return; } } 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 int readattrs(Ic *state) { const xcb_im_preedit_attr_t *attr; xcb_window_t clientwin, focuswin; xcb_point_t spot; int changed, hasspot; clientwin = xcb_im_input_context_get_client_window(state->xic); focuswin = xcb_im_input_context_get_focus_window(state->xic); spot = state->spot; hasspot = 0; if(state->style == (XCB_IM_PreeditPosition | XCB_IM_StatusNothing) && (xcb_im_input_context_get_preedit_attr_mask(state->xic) & XCB_XIM_XNSpotLocation_MASK)){ attr = xcb_im_input_context_get_preedit_attr(state->xic); if(attr != nil){ spot = attr->spot_location; 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 translate(xcb_window_t win, int x, int y, Caret *caret) { xcb_translate_coordinates_cookie_t cookie; xcb_translate_coordinates_reply_t *reply; if(win == XCB_NONE) return 0; cookie = xcb_translate_coordinates(conn, win, rootwin, x, y); reply = xcb_translate_coordinates_reply(conn, cookie, nil); if(reply == nil || !reply->same_screen){ free(reply); return 0; } caret->valid = 1; caret->x = reply->dst_x; caret->y = reply->dst_y; caret->h = 0; free(reply); return 1; } static int placebottom(Ic *state, xcb_window_t win) { xcb_get_geometry_cookie_t gcookie; xcb_get_geometry_reply_t *geometry; int height; if(win == XCB_NONE) return 0; gcookie = xcb_get_geometry(conn, win); geometry = xcb_get_geometry_reply(conn, gcookie, nil); if(geometry == nil) return 0; height = geometry->height; free(geometry); if(!translate(win, 0, 0, &state->caret)) return 0; state->caret.y += height; return 1; } static void place(Ic *state) { xcb_window_t win[2]; int i, nwin; memset(&state->caret, 0, sizeof state->caret); win[0] = state->focuswin; nwin = 1; if(state->clientwin != state->focuswin) win[nwin++] = state->clientwin; if(state->hasspot) for(i = 0; i < nwin; i++) if(translate(win[i], state->spot.x, state->spot.y, &state->caret)) return; for(i = 0; i < nwin; i++) if(placebottom(state, win[i])) return; } static void refreshplace(Ic *state) { readattrs(state); place(state); } static void commit(Ic *state, char *s, int nbyte) { char *wire; size_t nwire; if(nbyte == 0) return; wire = s; nwire = nbyte; if(state->encoding == Ecompound){ wire = ximcompound(s, nbyte, &nwire); if(wire == nil) return; } xcb_im_commit_string(xim, state->xic, XCB_XIM_LOOKUP_CHARS, wire, (u32int)nwire, 0); if(wire != s) free(wire); } static void clearpreedit(Ic *state) { xcb_im_preedit_draw_fr_t frame; if(state == nil || !state->prestarted) return; memset(&frame, 0, sizeof frame); frame.chg_length = state->nprerune; frame.status = 1; xcb_im_preedit_draw_callback(xim, state->xic, &frame); xcb_im_preedit_done_callback(xim, state->xic); state->prestarted = 0; state->nprerune = 0; if(preowner == state) preowner = nil; xcb_flush(conn); } static void updatepreedit(Ic *state, Str *pre) { xcb_im_preedit_draw_fr_t frame; u32int feedback[Maxrunes]; char utf[Maxutf], *wire; size_t nwire; int i, nbyte; if(!(state->style & XCB_IM_PreeditCallbacks)) return; if(pre->n == 0){ clearpreedit(state); return; } nbyte = stoutf(pre, utf, sizeof utf); wire = utf; nwire = nbyte; if(state->encoding == Ecompound){ wire = ximcompound(utf, nbyte, &nwire); if(wire == nil) return; } for(i = 0; i < pre->n; i++) feedback[i] = XCB_XIM_UNDERLINE; if(!state->prestarted){ xcb_im_preedit_start_callback(xim, state->xic); state->prestarted = 1; } memset(&frame, 0, sizeof frame); frame.caret = pre->n; frame.chg_length = state->nprerune; frame.length_of_preedit_string = (u16int)nwire; frame.preedit_string = (uchar*)wire; frame.feedback_array.size = pre->n; frame.feedback_array.items = feedback; xcb_im_preedit_draw_callback(xim, state->xic, &frame); state->nprerune = pre->n; preowner = state; if(wire != utf) free(wire); } 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.cap = state->cap; kr.op = op; kr.ks = key; kr.mod = mod & Mmask; kr.caret = state->caret; kr.reply = replyc; chansend(keyc, &kr); chanrecv(replyc, res); } static void keypress(Ic *state, u32int key, u32int mod, Keyres *res) { if(meaningful(key)){ if(preowner != nil && preowner != state) clearpreedit(preowner); state->engaged = 1; } sendrequest(state, Keypress, key, mod, res); } static void release(Ic *state) { Keyres res; if(state == nil) return; clearpreedit(state); if(!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, wasvalid; 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; if(meaningful(key)){ wasvalid = state->caret.valid; if(!state->engaged) refreshplace(state); else if(readattrs(state)) place(state); if(state->engaged && wasvalid && !state->caret.valid) sendrequest(state, Keycaret, 0, 0, &res); } keypress(state, key, ev->state, &res); n = stoutf(&res.commit, buf, sizeof buf); commit(state, buf, n); updatepreedit(state, &res.preedit); 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 setstyle(Ic *state, u32int style) { int cap; if(!stylecap(style, &cap)) style = XCB_IM_PreeditNothing | XCB_IM_StatusNothing; if(state->prestarted && !(style & XCB_IM_PreeditCallbacks)) clearpreedit(state); state->style = style; state->cap = cap; } static void iccreate(xcb_im_client_t *client, 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->xic = ic; state->client = client; state->encoding = getencoding(client); setstyle(state, xcb_im_input_context_get_input_style(ic)); refreshplace(state); 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(frame); USED(user); if(hdr->major_opcode == XCB_XIM_ENCODING_NEGOTIATION){ if(arg != nil) setencoding(client, *(u16int*)arg == 1 ? Eutf8 : Ecompound); return; } if(hdr->major_opcode == XCB_XIM_DISCONNECT || hdr->major_opcode == XCB_XIM_CLOSE){ for(state = ics; state != nil; state = state->next) if(state->client == client) release(state); clearencoding(client); return; } if(hdr->major_opcode == XCB_XIM_CREATE_IC){ iccreate(client, 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_SET_IC_VALUES: case XCB_XIM_SET_IC_FOCUS: refreshplace(state); if(state->engaged){ Keyres res; sendrequest(state, Keycaret, 0, 0, &res); } break; case XCB_XIM_DESTROY_IC: 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; preowner = nil; memset(clientenc, 0, sizeof clientenc); for(state = ics; state != nil; state = next){ next = state->next; free(state); } ics = nil; rootwin = XCB_NONE; 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; } rootwin = screen->root; 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(); }