#include "dat.h" #include "fn.h" #include #include #include #include #include #include "imv2.h" #include "vkv1.h" /* * text-input-v3 content purposes whose keys the engine never sees. IBus * numbers these alike but does not share them: one enum here is cheaper * than reaching into dat.h and ibus.c both. */ enum { Purposepassword = 8, Purposepin = 9, Maxcode = 8*32, /* keycodes the forwarded bitmap holds */ }; static struct wl_display *display; static struct wl_seat *seat; static struct zwp_input_method_manager_v2 *immgr; static struct zwp_virtual_keyboard_manager_v1 *vkmgr; static struct zwp_input_method_v2 *im; static struct zwp_virtual_keyboard_v1 *vk; static struct zwp_input_method_keyboard_grab_v2 *grab; static struct xkb_context *xkbctx; static struct xkb_state *kstate; static Channel *replyc; /* * The protocol gives one input context per seat, so the engine knows us * by one address; nil would mean no owner at all. */ static int context; static u32int serial; /* done events counted; the serial commit takes */ static int active; /* a text input is being served */ static int pendingactive; static int activated; /* an activate arrived since the last done */ static u32int purpose, pendingpurpose; static u32int sent[Maxcode/32]; /* keycodes passed on and still down */ static u32int lasttime; /* the compositor's clock, for a release of our own */ static int engaged; /* the engine owes this context a release */ static int preshown; /* our preedit is on the client's screen */ static int haskeymap; static int gone; /* unavailable: the seat is another input method's */ static void grabkeyboard(void); static void wllog(char *msg) { fprint(2, "strans: wl: %s\n", msg); } static int hidden(void) { return purpose == Purposepassword || purpose == Purposepin; } static void sendrequest(int op, u32int ks, u32int mod, Keyres *res) { Keyreq kr; memset(&kr, 0, sizeof kr); kr.owner = &context; kr.clientpre = 1; /* set_preedit_string draws it; the popup does not */ kr.op = op; kr.ks = ks; kr.mod = mod; kr.reply = replyc; chansend(keyc, &kr); chanrecv(replyc, res); } /* The engine's modifier bits, read from the grab's own keyboard state. */ static u32int modmask(void) { static char *name[] = { XKB_MOD_NAME_SHIFT, XKB_MOD_NAME_CTRL, XKB_MOD_NAME_ALT, XKB_MOD_NAME_LOGO, }; static u32int bit[] = {Mshift, Mctrl, Malt, Msuper}; u32int m; int i; m = 0; if(kstate == nil) return m; for(i = 0; i < nelem(name); i++) if(xkb_state_mod_name_is_active(kstate, name[i], XKB_STATE_MODS_EFFECTIVE) > 0) m |= bit[i]; return m; } static int issent(u32int code) { return code < Maxcode && (sent[code/32] & 1<<(code%32)) != 0; } /* * A key the engine did not take goes to the client as a key, not as * text: eaten = 0 means it is not ours, and committing it would lie to a * terminal and to anything with a keybinding. */ static void forward(u32int code, u32int state) { if(!haskeymap || code >= Maxcode) return; zwp_virtual_keyboard_v1_key(vk, lasttime, code, state); if(state == WL_KEYBOARD_KEY_STATE_PRESSED) sent[code/32] |= 1<<(code%32); else sent[code/32] &= ~(1<<(code%32)); } /* Whatever we passed on is still down; the client must not keep it. */ static void releasekeys(void) { u32int code; for(code = 0; code < Maxcode; code++) if(issent(code)) forward(code, WL_KEYBOARD_KEY_STATE_RELEASED); } /* * The engine's text, then the composed text, in one commit_string: the * compositor keeps only the last one sent before a commit. A commit is * an event and goes only when there is something, while a preedit is * state and goes every time, empty to take the last one down. */ static void answer(Keyres *res, char *tail) { char utf[2*Maxutf]; int n; n = stoutf(&res->commit, utf, sizeof utf); if(tail[0] != '\0') n += snprint(utf+n, sizeof utf - n, "%s", tail); if(n > 0) zwp_input_method_v2_commit_string(im, utf); n = stoutf(&res->preedit, utf, sizeof utf); if(n > 0 || preshown) /* an empty one only to take the last down */ zwp_input_method_v2_set_preedit_string(im, utf, n, n); zwp_input_method_v2_commit(im, serial); preshown = n > 0; } static void releasegrab(void) { if(grab == nil) return; zwp_input_method_keyboard_grab_v2_release(grab); grab = nil; } /* * The context is going: give the client its keys back. The engine's * pending text goes nowhere, because the compositor sends the text input * its leave before it sends us the deactivate, and a commit with nothing * focused to take it is dropped whatever serial it carries. */ static void leave(void) { Keyres res; releasekeys(); if(engaged){ sendrequest(Keyrelease, 0, 0, &res); engaged = 0; } preshown = 0; } /* A purpose that turned secret with text pending gets the text first. */ static void flushpending(void) { Keyres res; if(!engaged) return; sendrequest(Keyreset, 0, 0, &res); answer(&res, ""); } static void imactivate(void*, struct zwp_input_method_v2*) { pendingactive = 1; activated = 1; } static void imdeactivate(void*, struct zwp_input_method_v2*) { pendingactive = 0; } static void imsurrounding(void*, struct zwp_input_method_v2*, const char*, u32int, u32int) { } static void imcause(void*, struct zwp_input_method_v2*, u32int) { } static void imcontent(void*, struct zwp_input_method_v2*, u32int, u32int what) { pendingpurpose = what; } /* * activate, deactivate and content_type are pending until done, and the * count of done events is the serial a commit must carry: wlroots throws * away a whole commit whose serial does not match, without a word. */ static void imdone(void*, struct zwp_input_method_v2*) { int washidden; serial++; washidden = hidden(); purpose = pendingpurpose; if(activated || (active && !pendingactive)) leave(); else if(active && !washidden && hidden()) flushpending(); if(activated) grabkeyboard(); else if(!pendingactive) releasegrab(); active = pendingactive; activated = 0; } static void imgone(void*, struct zwp_input_method_v2*) { gone = 1; } static const struct zwp_input_method_v2_listener imlisten = { imactivate, imdeactivate, imsurrounding, imcause, imcontent, imdone, imgone, }; /* * The same fd goes to the virtual keyboard, so a key we pass on means * there what it meant here. */ static void grabkeymap(void*, struct zwp_input_method_keyboard_grab_v2*, u32int format, int fd, u32int size) { struct xkb_keymap *keymap; struct xkb_state *state; char *s; if(format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1){ close(fd); return; } s = mmap(nil, size, PROT_READ, MAP_PRIVATE, fd, 0); if(s == MAP_FAILED){ close(fd); return; } keymap = xkb_keymap_new_from_string(xkbctx, s, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); munmap(s, size); state = keymap != nil ? xkb_state_new(keymap) : nil; xkb_keymap_unref(keymap); if(state == nil){ wllog("cannot read the keyboard mapping"); close(fd); return; } xkb_state_unref(kstate); kstate = state; zwp_virtual_keyboard_v1_keymap(vk, format, fd, size); close(fd); haskeymap = 1; } static void grabkey(void*, struct zwp_input_method_keyboard_grab_v2*, u32int, u32int time, u32int code, u32int state) { Keyres res; char text[Maxutf]; u32int key, sym; lasttime = time; if(state != WL_KEYBOARD_KEY_STATE_PRESSED){ if(issent(code)) forward(code, state); return; } if(!active || hidden() || kstate == nil){ forward(code, state); return; } sym = xkb_state_key_get_one_sym(kstate, code + 8); if(composekey(sym, text, sizeof text)) return; /* a sequence in the making */ key = ipckeysym(sym, xkb_keysym_to_utf32(sym)); if(keymeaningful(key)) engaged = 1; if(text[0] != '\0') /* Composed text follows whatever was pending. */ sendrequest(Keyreset, 0, 0, &res); else sendrequest(Keypress, key, modmask(), &res); answer(&res, text); if(text[0] == '\0' && !res.eaten) forward(code, state); } /* * Our own lookup must see Shift, and so must the client: wlroots derives * no state from a virtual keyboard's keys, so a modifier forwarded as a * keycode arrives bare and Ctrl+C reaches the client as c. */ static void grabmodifiers(void*, struct zwp_input_method_keyboard_grab_v2*, u32int, u32int depressed, u32int latched, u32int locked, u32int group) { if(kstate != nil) xkb_state_update_mask(kstate, depressed, latched, locked, 0, 0, group); if(haskeymap) zwp_virtual_keyboard_v1_modifiers(vk, depressed, latched, locked, group); } static void grabrepeat(void*, struct zwp_input_method_keyboard_grab_v2*, int, int) { } static const struct zwp_input_method_keyboard_grab_v2_listener grablisten = { grabkeymap, grabkey, grabmodifiers, grabrepeat, }; /* * sway hands keys to the grab holder whenever the grab object exists, * without asking whether the input method is active, so a grab held * while inactive takes every key in the session. Two activates can also * arrive without a deactivate between them, and a second grab_keyboard * then fails and leaves a dead object: release before grabbing again. */ static void grabkeyboard(void) { releasegrab(); grab = zwp_input_method_v2_grab_keyboard(im); zwp_input_method_keyboard_grab_v2_add_listener(grab, &grablisten, nil); } static void regglobal(void*, struct wl_registry *r, u32int name, const char *iface, u32int) { if(strcmp(iface, wl_seat_interface.name) == 0){ if(seat == nil) /* the first seat is ours */ seat = wl_registry_bind(r, name, &wl_seat_interface, 1); }else if(strcmp(iface, zwp_input_method_manager_v2_interface.name) == 0) immgr = wl_registry_bind(r, name, &zwp_input_method_manager_v2_interface, 1); else if(strcmp(iface, zwp_virtual_keyboard_manager_v1_interface.name) == 0) vkmgr = wl_registry_bind(r, name, &zwp_virtual_keyboard_manager_v1_interface, 1); } static void regremove(void*, struct wl_registry*, u32int) { } static const struct wl_registry_listener reglisten = {regglobal, regremove}; /* Another frontend may have taken the engine; then our preedit is stale. */ static void checkowner(void) { Keyres res; if(!preshown) return; sendrequest(Keycap, 0, 0, &res); if(res.eaten) return; zwp_input_method_v2_set_preedit_string(im, "", 0, 0); zwp_input_method_v2_commit(im, serial); preshown = 0; } /* * Whether this session has the protocol, not whether it is Wayland: * GNOME and KWin set WAYLAND_DISPLAY and have no manager, and XIM is the * only path left to them. */ int wlinit(void) { struct wl_registry *reg; display = wl_display_connect(nil); if(display == nil) return 0; reg = wl_display_get_registry(display); wl_registry_add_listener(reg, ®listen, nil); wl_display_roundtrip(display); /* Without the virtual keyboard every key we do not eat is lost. */ if(seat != nil && immgr != nil && vkmgr != nil) return 1; wl_display_disconnect(display); display = nil; return 0; } /* * One process touches libwayland: it reads events, dispatches them and * answers the engine, in order. dispatch reads only when poll says * there is something, so it never blocks and the owner check still runs. */ void wlthread(void*) { struct pollfd pfd; int n; threadsetname("wl"); replyc = chancreate(sizeof(Keyres), 0); xkbctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if(xkbctx == nil){ wllog("cannot make an xkb context"); return; } im = zwp_input_method_manager_v2_get_input_method(immgr, seat); zwp_input_method_v2_add_listener(im, &imlisten, nil); vk = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(vkmgr, seat); pfd.fd = wl_display_get_fd(display); pfd.events = POLLIN; for(;;){ if(wl_display_flush(display) < 0 && errno != EAGAIN) break; pfd.revents = 0; n = poll(&pfd, 1, preshown ? Ownerpoll : -1); if(n < 0 && errno != EINTR) break; if(pfd.revents & POLLIN){ if(wl_display_dispatch(display) < 0) break; }else if(wl_display_dispatch_pending(display) < 0) break; if(gone){ wllog("another input method already has the seat"); releasegrab(); wl_display_flush(display); return; } checkowner(); } if(wl_display_get_error(display) != 0) die("wl: compositor disconnected"); die("wl: frontend stopped"); }