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

@ -34,7 +34,11 @@ export namespace Crafter {
std::string utf8; // UTF-8 to re-emit as onTextInput, if any
std::chrono::time_point<std::chrono::steady_clock> pressTime;
std::chrono::time_point<std::chrono::steady_clock> lastFireTime;
bool fired = false; // a repeat has fired since the press: before it, lastFireTime is the press itself
};
// Repeats one tick may emit at once when frames stalled; the rest of the
// stall is dropped, so a held key is never a burst of edits.
inline constexpr int kKeyRepeatCatchUp = 3;
#endif
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
@ -564,6 +568,7 @@ export namespace Crafter {
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
inline static KeyRepeatState keyRepeat;
static void TickKeyRepeats();
static void TickKeyRepeats(std::chrono::steady_clock::time_point now); // the clock injected, for the tests
#else
static void TickKeyRepeats() {}
#endif