#include "dat.h" #include "fn.h" #include #include #include #include #include #include #include #include #include u32int keymaplookup(struct xkb_state*, uint8_t, 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 { Clientenc *next; xcb_im_client_t *client; int encoding; }; enum { Ecompound, Eutf8, }; static xcb_connection_t *conn; static xcb_im_t *xim; static struct xkb_state *kstate; static xcb_window_t rootwin; static int opened; static Ic *ics; static Ic *preowner; static Ic passthrough; static Clientenc *clientenc; static Channel *replyc; static int wakefd[2] = {-1, -1}; 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 wakeinit(void) { int fd[2]; int flags, i; if(wakefd[0] >= 0) return 0; if(pipe(fd) < 0) return -1; for(i = 0; i < nelem(fd); i++){ flags = fcntl(fd[i], F_GETFL); if(flags < 0 || fcntl(fd[i], F_SETFL, flags | O_NONBLOCK) < 0) goto fail; flags = fcntl(fd[i], F_GETFD); if(flags < 0 || fcntl(fd[i], F_SETFD, flags | FD_CLOEXEC) < 0) goto fail; } wakefd[0] = fd[0]; wakefd[1] = fd[1]; return 0; fail: close(fd[0]); close(fd[1]); return -1; } /* imthread only leaves a wake token; XIM ownership stays on the XIM thread. */ void ximownernotify(void) { uchar token; int n; if(wakefd[1] < 0) return; token = 0; do n = write(wakefd[1], &token, 1); while(n < 0 && errno == EINTR); /* EAGAIN means a pending byte will already wake the XIM poll loop. */ } static int wakedrain(void) { uchar buf[32]; int n, total; total = 0; for(;;){ n = read(wakefd[0], buf, sizeof buf); if(n > 0){ total += n; continue; } if(n < 0 && errno == EINTR) continue; break; } return total; } static int getencoding(xcb_im_client_t *client) { Clientenc *p; for(p = clientenc; p != nil; p = p->next) if(p->client == client) return p->encoding; return Ecompound; } static int setencoding(xcb_im_client_t *client, int encoding) { Clientenc *p; if(client == nil) return -1; for(p = clientenc; p != nil; p = p->next) if(p->client == client){ p->encoding = encoding; return 0; } 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) { Clientenc *p, **link; 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) { fprint(2, "strans: xim: %s\n", msg); } static int kinit(void) { struct xkb_context *context; struct xkb_keymap *keymap; struct xkb_state *state; int32_t device; int ok; 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) { return keymaplookup(kstate, kc, 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 readattrs(Ic *state) { const xcb_im_preedit_attr_t *attr; xcb_window_t clientwin, focuswin; xcb_point_t spot; int 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; } } state->clientwin = clientwin; state->focuswin = focuswin; state->spot = spot; state->hasspot = hasspot; } 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 && translate(state->focuswin, 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 checkpreowner(void) { Keyres res; if(preowner == nil) return; sendrequest(preowner, Keycap, 0, 0, &res); if(!res.eaten) clearpreedit(preowner); } static void checkownerwake(void) { if(wakedrain() > 0) checkpreowner(); } 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 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) { 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; refreshplace(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, state->xic, 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; state->style = style; state->cap = cap; } static void icinstall(xcb_im_client_t *client, xcb_im_input_context_t *ic, Ic *state) { 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); 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 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) { 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) < 0) die("xim: cannot remember client encoding"); 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; 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; if(ev != nil && (ev->response_type & ~0x80) == XCB_KEY_PRESS) kpress(state, 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_UNSET_IC_FOCUS: release(state); break; case XCB_XIM_RESET_IC: resetic(state, arg); 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; clearencodings(); for(state = ics; state != nil; state = next){ next = state->next; free(state); } ics = nil; rootwin = XCB_NONE; xkb_state_unref(kstate); kstate = 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; } if(wakeinit() < 0){ ximlog("cannot create owner wake pipe"); 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) { struct pollfd pfd[2]; xcb_generic_event_t *ev; uint8_t type; int fd, n; USED(arg); threadsetname("xim"); if(ximinit() < 0){ ximclose(); die("xim: initialization failed"); } fd = xcb_get_file_descriptor(conn); pfd[0].fd = fd; pfd[0].events = POLLIN; pfd[1].fd = wakefd[0]; pfd[1].events = POLLIN; for(;;){ pfd[0].revents = pfd[1].revents = 0; n = poll(pfd, nelem(pfd), -1); if(n < 0){ if(errno == EINTR) continue; break; } if(pfd[1].revents & POLLIN) checkownerwake(); if(pfd[0].revents) while((ev = xcb_poll_for_event(conn)) != nil){ 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); /* Ownership changes outrank the next queued X event. */ checkownerwake(); } if(pfd[0].revents & (POLLERR|POLLHUP|POLLNVAL)) break; } n = xcb_connection_has_error(conn); ximclose(); if(n) die("xim: X server disconnected"); die("xim: frontend stopped"); }