fix(wayland): the first key repeat lands at the delay, not as a burst of the periods since the press

keyboard_key set lastFireTime to the press and TickKeyRepeats caught up on
every period since it, so the first tick past the compositor's delay fired
floor(delay / period) repeats at once: with rate 25 and delay 600 a held
Backspace deleted sixteen characters at the delay mark and a held letter
put fifteen copies in the field, from a hardware keyboard and from an
on-screen keyboard that sends Backspace as key events. A rate above 1000
made the period zero and the loop endless.

The first repeat now lands at the delay, as wl_keyboard.repeat_info
promises (one after `delay`, then `rate` a second): the first tick past
the delay sets lastFireTime to pressTime + delay - period. The period is
at least a millisecond, and the catch-up after a stalled frame is capped
(kKeyRepeatCatchUp, three) with the rest of the stall dropped, so a held
key is never a burst of edits. TickKeyRepeats takes the clock as a
parameter for the tests; the frame path's call is unchanged.

tests/KeyRepeat drives TickKeyRepeats with an injected clock on a
default-constructed Window: nothing before the delay, one at it, one per
period after, a capped catch-up, a key without text, a rate above 1000, a
released key, rate 0.
This commit is contained in:
Jorijn van der Graaf 2026-11-01 00:11:46 +01:00
commit 3f646ec42b
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
4 changed files with 145 additions and 11 deletions

View file

@ -439,6 +439,13 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
if (!windows) {
cfg.tests.push_back(LibraryTest("MouseScroll"));
// Key repeat as wl_keyboard.repeat_info promises it: the first
// repeat at the delay (not a burst that counts the delay as missed
// periods), one per period after, a capped catch-up after a stall,
// a period of at least a millisecond. Drives TickKeyRepeats with
// an injected clock; no compositor.
cfg.tests.push_back(LibraryTest("KeyRepeat"));
// The pointer's enter, move and leave through the helpers both
// native backends report with (Window::PointerEnter and co.).
// Drives the wl_pointer callbacks directly, as MouseScroll does.