grabrepeat was an empty body, and under the grab the compositor feeds the client nothing while a key is down: one press, then only repeat_info, leaving the repeat to whoever holds the grab. So a key strans ate acted once however long it was held. Hold Backspace over a syllable and one jamo goes; hold a jamo key and you get one where every other window gives four a second. X11 has none of this -- the server auto-repeats and xim.c dispatches every press -- so the same keyboard behaved differently depending on which frontend the window went through. Under headless sway, which advertises repeat_info(25, 600), holding Backspace 1.5s over 라 with 가가가나다 committed behind it: before preedit 라 -> ㄹ, and no key at the client at all after preedit 라 -> ㄹ -> empty, then 9 BackSpace at the client fcitx5 and kime both keep driving the repeat after the engine stops wanting the key, and both then must send a release before every press, or the client sees a key held down and starts a repeat of its own on top of theirs. That machinery exists only to undo the choice that created it. strans does not need it: forward() already hands a key the engine did not take to the client as a key, and the client repeats that correctly -- the same hold with nothing pending, which takes that untouched path, delivers 24. So the rule is one sentence. strans repeats what the engine takes; when the engine stops taking it, presskey has already passed the key on, the deadline is dropped, and the client repeats it from there. 9 against 24 is that handover: driving every tick here would have made them one number. It costs one pause of the client's own delay, 600ms, where the preedit empties. It buys no release-and-press fiction anywhere in the file, no second repeat engine, and no timestamp arithmetic -- exactly one key is forwarded per hold, so there is no run of stale timestamps to mend. nsec() is gettimeofday, which steps; a deadline wants the clock ipc.c already uses. The owner poll stands down while a key repeats, because the repeat's own Keypress carries our owner and takes the engine back, so it would have nothing to find. Arming needs the engine and cannot be reached from the unit suite; ending a repeat can, and a repeat outliving its key is the worst this could do. wl/repeat-ends was checked by breaking it five ways -- a constant rate, a constant delay, the empty body back again, the key's own release no longer ending it, and a repeat into a dead context staying armed -- each failing only its own line. Phases A-G unchanged at both scales; 90 unit, check-live, check-stress and valgrind clean. One limit worth writing down: the GTK probe prints the BackSpace it receives and never deletes on one, on this path or on the plain pass-through, so the phase proves the keys arrive and says nothing about the text behind them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
769 lines
19 KiB
C
769 lines
19 KiB
C
#include "dat.h"
|
|
#include "fn.h"
|
|
|
|
#include <errno.h>
|
|
#include <poll.h>
|
|
#include <sys/mman.h>
|
|
#include <time.h>
|
|
#include <wayland-client.h>
|
|
#include <xkbcommon/xkbcommon.h>
|
|
#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 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.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;
|
|
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
|
|
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;
|
|
}
|
|
|
|
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 && 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");
|
|
}
|