#include "dat.h" #include "fn.h" #include #include #include #include #include #include #include "imv2.h" #include "vkv1.h" enum { Maxcode = 0x300, /* KEY_MAX+1: what evdev can send */ }; /* * What popuplayout may use. The compositor puts the popup at the text * cursor and constrains it there, so this need only be wide enough for * the layout's own width and tall enough for every row it can show. */ #define Popupw PopupBasew #define Popuph ((Maxdisp + 2)*Fontsz + 2*PopupPad + PopupSep) /* * One of two shm buffers, busy from attach until the compositor releases * it. win.c's single grow-only image would be redrawn while the * compositor was still reading it. */ typedef struct Buf Buf; struct Buf { struct wl_buffer *b; u32int *img; int size; int w; int h; int busy; }; static struct wl_display *display; static struct wl_seat *seat; static struct wl_compositor *comp; static struct wl_shm *shm; static struct wl_surface *surface; static struct zwp_input_popup_surface_v2 *popsurf; 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 Str surround, pendingsurround; /* the client's text before the cursor */ 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 reprate; /* repeats a second the seat asks for; 0 is none */ static int repdelay; /* ms a key is held before the first repeat */ static u32int repcode; /* the key repeating */ static vlong repdue; /* when its next repeat falls due; 0 if none */ 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 Buf bufs[2]; static int shown; /* a buffer is attached and the popup is up */ static Drawcmd held; /* a picture that came while both buffers were busy */ static int helddraw; static void grabkeyboard(void); static void popup(Drawcmd*); static void wllog(char *msg) { fprint(2, "strans: wl: %s\n", msg); } /* A repeat deadline needs a clock that cannot step; nsec() is not one. */ static vlong nowms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (vlong)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } 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.surround = surround; kr.reply = replyc; chansend(keyc, &kr); chanrecv(replyc, res); } /* The bytes the last n runes of the client's text take. */ static int backbytes(int n) { char utf[Maxutf]; Str s; int i; sclear(&s); for(i = max(surround.n - n, 0); i < surround.n; i++) sputr(&s, surround.r[i]); return stoutf(&s, utf, sizeof utf); } /* 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; 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; /* The compositor takes the text back before it inserts ours. */ if(res->del > 0) zwp_input_method_v2_delete_surrounding_text(im, backbytes(res->del), 0); 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 bufrelease(void *p, struct wl_buffer*) { ((Buf*)p)->busy = 0; if(helddraw){ helddraw = 0; popup(&held); } } static const struct wl_buffer_listener buflisten = {bufrelease}; static void bufclear(Buf *p) { if(p->b != nil) wl_buffer_destroy(p->b); if(p->img != nil) munmap(p->img, p->size); memset(p, 0, sizeof *p); } /* An anonymous file of pixels, shared with the compositor. */ static int shmfd(int size) { char path[] = "/dev/shm/strans-XXXXXX"; int fd; fd = mkstemp(path); if(fd < 0) return -1; unlink(path); if(ftruncate(fd, size) < 0){ close(fd); return -1; } return fd; } /* * A free buffer of the wanted size. Sizes differ from draw to draw, so * a free one of the wrong size is remade; both busy means the compositor * is still reading them and the picture has to wait. */ static Buf* getbuf(int w, int h) { struct wl_shm_pool *pool; Buf *p; int fd, i, size; for(i = 0; i < nelem(bufs); i++) if(!bufs[i].busy && bufs[i].w == w && bufs[i].h == h) return &bufs[i]; for(i = 0; i < nelem(bufs); i++) if(!bufs[i].busy) break; if(i == nelem(bufs)) return nil; p = &bufs[i]; bufclear(p); size = w * h * sizeof p->img[0]; fd = shmfd(size); if(fd < 0) return nil; p->img = mmap(nil, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if(p->img == MAP_FAILED){ p->img = nil; close(fd); return nil; } pool = wl_shm_create_pool(shm, fd, size); p->b = wl_shm_pool_create_buffer(pool, 0, w, h, w * sizeof p->img[0], WL_SHM_FORMAT_XRGB8888); wl_shm_pool_destroy(pool); close(fd); wl_buffer_add_listener(p->b, &buflisten, p); p->size = size; p->w = w; p->h = h; return p; } /* Unmapping takes the popup down; the compositor puts it back at the * next activate with whatever buffer is still attached. */ static void hidepopup(void) { helddraw = 0; /* a picture still waiting for a buffer is stale now */ if(!shown) return; wl_surface_attach(surface, nil, 0, 0); wl_surface_commit(surface); shown = 0; } static void popup(Drawcmd *dc) { Popup p; Buf *b; popuplayout(dc, Popupw, Popuph, &p); if(p.w <= 0 || p.h <= 0){ hidepopup(); return; } /* A buffer that is not a multiple of the scale is an invalid_size * error at attach, which would kill the connection. */ p.w = (p.w + popupscale - 1) / popupscale * popupscale; p.h = (p.h + popupscale - 1) / popupscale * popupscale; b = getbuf(p.w, p.h); if(b == nil){ held = *dc; /* the engine will not send it twice */ helddraw = 1; return; } popupdraw(b->img, dc, &p); wl_surface_attach(surface, b->b, 0, 0); wl_surface_set_buffer_scale(surface, popupscale); wl_surface_damage_buffer(surface, 0, 0, p.w, p.h); wl_surface_commit(surface); b->busy = 1; shown = 1; } /* * drawc is empty or holds exactly what the engine last published, so one * take is enough. lastdraw follows the engine and not our surface, so * take it wherever the two can part -- but show it only while the engine * is ours: what it drew for another frontend is not ours to put up. */ static void takedraw(void) { Drawcmd dc; if(channbrecv(drawc, &dc) > 0 && engaged) popup(&dc); } /* The compositor places the popup; where the text is does not concern us. */ static void poprect(void*, struct zwp_input_popup_surface_v2*, int, int, int, int) { } static const struct zwp_input_popup_surface_v2_listener poplisten = {poprect}; 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 pending text * goes nowhere either way -- the compositor drops a commit once the text * input has its leave, and a client that disabled its own text input has * stopped listening, so it throws a relayed commit away. XIM and IBus * hand the text back here because their focus-out is a round trip the * client still waits on; a deactivate is not. */ static void leave(void) { Keyres res; repdue = 0; releasekeys(); composedrop(Composewl, &context); if(engaged){ sendrequest(Keyrelease, 0, 0, &res); engaged = 0; takedraw(); } hidepopup(); 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, ""); takedraw(); } /* * A purpose belongs to the activation: the compositor sends a content type * only for a client that asked for one, so a client that asks for none has * none, whatever the last one wanted. */ static void imactivate(void*, struct zwp_input_method_v2*) { pendingactive = 1; activated = 1; pendingpurpose = 0; sclear(&pendingsurround); } static void imdeactivate(void*, struct zwp_input_method_v2*) { pendingactive = 0; } /* * The text around the cursor, of which a reading can use what comes * before it. A text input that sends none leaves this empty, and then * nothing is ever reached back into or taken away. */ static void imsurrounding(void*, struct zwp_input_method_v2*, const char *text, u32int cursor, u32int) { u32int n; n = strlen(text); stail(&pendingsurround, (char*)text, cursor < n ? cursor : n); } 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; surround = pendingsurround; if(activated || (active && !pendingactive)) leave(); else if(active && !washidden && hidden()) flushpending(); if(activated && pendingactive) 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; } /* * A press, from the keyboard or from the repeat deadline. Returns whether * the engine took it: what it did not take has gone on to the client as a * key, and a key at the client is the client's own to repeat. */ static int presskey(u32int code) { Keyres res; char text[Maxutf]; u32int key, sym; sym = xkb_state_key_get_one_sym(kstate, code + 8); if(composekey(Composewl, &context, sym, text, sizeof text)) return 0; /* 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); takedraw(); if(text[0] == '\0' && !res.eaten) forward(code, WL_KEYBOARD_KEY_STATE_PRESSED); /* A finished sequence is one act; only a key is held down. */ return text[0] == '\0' && res.eaten; } static void grabkey(void*, struct zwp_input_method_keyboard_grab_v2*, u32int, u32int time, u32int code, u32int state) { lasttime = time; if(state != WL_KEYBOARD_KEY_STATE_PRESSED){ if(code == repcode) repdue = 0; if(issent(code)) forward(code, state); return; } repdue = 0; /* one key repeats at a time, and this is the newest */ if(!active || hidden() || kstate == nil){ forward(code, state); return; } if(!presskey(code) || reprate <= 0 || !xkb_keymap_key_repeats(xkb_state_get_keymap(kstate), code + 8)) return; repcode = code; repdue = nowms() + repdelay; } /* * 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 rate, int delay) { reprate = rate; repdelay = delay; if(rate <= 0) repdue = 0; } 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, wl_compositor_interface.name) == 0) /* 4 for set_buffer_scale and damage_buffer */ comp = wl_registry_bind(r, name, &wl_compositor_interface, 4); else if(strcmp(iface, wl_shm_interface.name) == 0) shm = wl_registry_bind(r, name, &wl_shm_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 and our * popup are both stale. Nothing later will say so: the engine's own * pictures go to whoever took it, and it settles back at blank. */ static void checkowner(void) { Keyres res; if(!preshown && !shown) return; sendrequest(Keycap, 0, 0, &res); if(res.eaten) return; hidepopup(); if(preshown){ zwp_input_method_v2_set_preedit_string(im, "", 0, 0); zwp_input_method_v2_commit(im, serial); preshown = 0; } } /* * One repeat, once the key has been held long enough. Under the grab the * compositor feeds the client nothing while the key is down, so what it * would have repeated there is ours to make -- but only while the engine * wants the key. Backspace stops being ours once the preedit is empty, * and by then presskey has passed it on, so the key is held down at the * client and the client repeats it from there. */ static void repeat(void) { if(nowms() < repdue) return; if(!active || hidden() || kstate == nil || !presskey(repcode)){ repdue = 0; return; } repdue = nowms() + 1000/reprate; } /* * 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 && comp != nil && shm != 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 ms, 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; } textinit(); 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); surface = wl_compositor_create_surface(comp); popsurf = zwp_input_method_v2_get_input_popup_surface(im, surface); zwp_input_popup_surface_v2_add_listener(popsurf, &poplisten, nil); pfd.fd = wl_display_get_fd(display); pfd.events = POLLIN; for(;;){ if(wl_display_flush(display) < 0 && errno != EAGAIN) break; pfd.revents = 0; if(repdue != 0){ ms = repdue - nowms(); if(ms < 0) ms = 0; }else ms = preshown || shown ? Ownerpoll : -1; n = poll(&pfd, 1, ms); 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(); leave(); wl_display_flush(display); bufclear(&bufs[0]); bufclear(&bufs[1]); textclose(); return; } /* A repeating key's own press keeps the engine ours, so * while one repeats the owner poll has nothing to find. */ if(repdue != 0) repeat(); else checkowner(); } if(wl_display_get_error(display) != 0) die("wl: compositor disconnected"); die("wl: frontend stopped"); }