diff --git a/tests/test.h b/tests/test.h index 6567b3a..1f5387d 100644 --- a/tests/test.h +++ b/tests/test.h @@ -127,3 +127,4 @@ void xim_adapter_callback_owner_loss(struct ct*); void xim_adapter_commit_encoding(struct ct*); void wl_modifier_mask(struct ct*); void wl_forwarded_keys(struct ct*); +void wl_repeat_ends(struct ct*); diff --git a/tests/unit_test.c b/tests/unit_test.c index 7c3ca9f..e0df955 100644 --- a/tests/unit_test.c +++ b/tests/unit_test.c @@ -147,6 +147,7 @@ static const struct ct_test tests[] = { { "xim/commit-encoding", xim_adapter_commit_encoding }, { "wl/modifier-mask", wl_modifier_mask }, { "wl/forwarded-keys", wl_forwarded_keys }, + { "wl/repeat-ends", wl_repeat_ends }, }; #else static const struct ct_test tests[] = { diff --git a/tests/wl_adapter_test.c b/tests/wl_adapter_test.c index 810cc48..35757b7 100644 --- a/tests/wl_adapter_test.c +++ b/tests/wl_adapter_test.c @@ -102,3 +102,40 @@ wl_forwarded_keys(struct ct *t) CT_EQ_INT(t, 0, nkeysent); CT_CHECK(t, !issent(30)); } + +/* + * Arming a repeat needs the engine, which is not here; ending one does + * not, and a repeat that outlives its key is the worst this can do. + */ +void +wl_repeat_ends(struct ct *t) +{ + haskeymap = 1; + memset(sent, 0, sizeof sent); + + /* Not a rate or a delay any seat would pick: the body was empty + * once, and a stored constant would pass for the real thing. */ + grabrepeat(nil, nil, 33, 410); + CT_EQ_INT(t, 33, reprate); + CT_EQ_INT(t, 410, repdelay); + + /* The key's own release ends it; another key's release does not. */ + repcode = 14; + repdue = 1; + grabkey(nil, nil, 0, 0, 15, WL_KEYBOARD_KEY_STATE_RELEASED); + CT_CHECK(t, repdue != 0); + grabkey(nil, nil, 0, 0, 14, WL_KEYBOARD_KEY_STATE_RELEASED); + CT_EQ_INT(t, 0, repdue); + + /* So does a seat that has stopped repeating anything. */ + repdue = 1; + grabrepeat(nil, nil, 0, 410); + CT_EQ_INT(t, 0, repdue); + + /* A due repeat with no text input to serve is dropped, not sent. */ + grabrepeat(nil, nil, 33, 410); + active = 0; + repdue = 1; + repeat(); + CT_EQ_INT(t, 0, repdue); +} diff --git a/wl.c b/wl.c index 1134923..70b1f40 100644 --- a/wl.c +++ b/wl.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include "imv2.h" @@ -66,6 +67,10 @@ 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; @@ -84,6 +89,16 @@ 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) { @@ -356,6 +371,7 @@ leave(void) { Keyres res; + repdue = 0; releasekeys(); composedrop(Composewl, &context); if(engaged){ @@ -489,27 +505,21 @@ grabkeymap(void*, struct zwp_input_method_keyboard_grab_v2*, u32int format, haskeymap = 1; } -static void -grabkey(void*, struct zwp_input_method_keyboard_grab_v2*, u32int, - u32int time, u32int code, u32int state) +/* + * 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; - 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(Composewl, &context, sym, text, sizeof text)) - return; /* a sequence in the making */ + return 0; /* a sequence in the making */ key = ipckeysym(sym, xkb_keysym_to_utf32(sym)); if(keymeaningful(key)) engaged = 1; @@ -521,7 +531,33 @@ grabkey(void*, struct zwp_input_method_keyboard_grab_v2*, u32int, 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; } /* @@ -542,8 +578,13 @@ grabmodifiers(void*, struct zwp_input_method_keyboard_grab_v2*, u32int, } static void -grabrepeat(void*, struct zwp_input_method_keyboard_grab_v2*, int, int) +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 = { @@ -614,6 +655,26 @@ checkowner(void) } } +/* + * 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 @@ -648,7 +709,7 @@ void wlthread(void*) { struct pollfd pfd; - int n; + int ms, n; threadsetname("wl"); replyc = chancreate(sizeof(Keyres), 0); @@ -670,7 +731,13 @@ wlthread(void*) if(wl_display_flush(display) < 0 && errno != EAGAIN) break; pfd.revents = 0; - n = poll(&pfd, 1, preshown || shown ? Ownerpoll : -1); + 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){ @@ -688,7 +755,12 @@ wlthread(void*) textclose(); return; } - checkowner(); + /* 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");