wayland: Simplify key repeat handling

Remove redundant state variables, remove trivial helper functions, and guard against the unlikely case where a compositor may set the repeat rate to zero while a repeat is active, which would result in a divide-by-zero error.

Key repeat is increasingly handled server-side with seat v10 and above, so consider that the most common path for the future.
This commit is contained in:
Frank Praznik
2026-07-07 12:32:44 -04:00
parent cf6a235a41
commit a3a8141557
2 changed files with 23 additions and 62 deletions

View File

@@ -411,11 +411,9 @@ static void Wayland_SeatSetKeymap(SDL_WaylandSeat *seat)
}
}
// Returns true if a key repeat event was due
static bool keyboard_repeat_handle(SDL_WaylandKeyboardRepeat *repeat_info, Uint64 elapsed)
// Synthesize key repeat events
static void keyboard_repeat_handle(SDL_WaylandKeyboardRepeat *repeat_info, Uint64 elapsed)
{
bool ret = false;
while (elapsed >= repeat_info->next_repeat_ns) {
if (repeat_info->scancode != SDL_SCANCODE_UNKNOWN) {
const Uint64 timestamp = repeat_info->base_time_ns + repeat_info->next_repeat_ns;
@@ -425,26 +423,15 @@ static bool keyboard_repeat_handle(SDL_WaylandKeyboardRepeat *repeat_info, Uint6
SDL_SendKeyboardText(repeat_info->text);
}
repeat_info->next_repeat_ns += SDL_NS_PER_SECOND / (Uint64)repeat_info->repeat_rate;
ret = true;
}
return ret;
}
static void keyboard_repeat_clear(SDL_WaylandKeyboardRepeat *repeat_info)
{
if (!repeat_info->is_initialized) {
return;
}
repeat_info->is_key_down = false;
}
static void keyboard_repeat_set(SDL_WaylandKeyboardRepeat *repeat_info, Uint32 keyboard_id, uint32_t key, Uint32 wl_press_time_ms,
Uint64 base_time_ns, uint32_t scancode, bool has_text, char text[8])
Uint64 base_time_ns, uint32_t scancode, const char *text)
{
if (!repeat_info->is_initialized || !repeat_info->repeat_rate) {
if (!repeat_info->repeat_rate) {
return;
}
repeat_info->is_key_down = true;
repeat_info->keyboard_id = keyboard_id;
repeat_info->key = key;
repeat_info->wl_press_time_ms = wl_press_time_ms;
@@ -452,39 +439,13 @@ static void keyboard_repeat_set(SDL_WaylandKeyboardRepeat *repeat_info, Uint32 k
repeat_info->sdl_press_time_ns = SDL_GetTicksNS();
repeat_info->next_repeat_ns = SDL_MS_TO_NS(repeat_info->repeat_delay_ms);
repeat_info->scancode = scancode;
if (has_text) {
SDL_memcpy(repeat_info->text, text, sizeof(repeat_info->text));
if (text) {
SDL_strlcpy(repeat_info->text, text, SDL_arraysize(repeat_info->text));
} else {
repeat_info->text[0] = '\0';
}
}
static uint32_t keyboard_repeat_get_key(SDL_WaylandKeyboardRepeat *repeat_info)
{
if (repeat_info->is_initialized && repeat_info->is_key_down) {
return repeat_info->key;
}
return 0;
}
static void keyboard_repeat_set_text(SDL_WaylandKeyboardRepeat *repeat_info, const char text[8])
{
if (repeat_info->is_initialized) {
SDL_copyp(repeat_info->text, text);
}
}
static bool keyboard_repeat_is_set(SDL_WaylandKeyboardRepeat *repeat_info)
{
return repeat_info->is_initialized && repeat_info->is_key_down;
}
static bool keyboard_repeat_key_is_set(SDL_WaylandKeyboardRepeat *repeat_info, uint32_t key)
{
return repeat_info->is_initialized && repeat_info->is_key_down && key == repeat_info->key;
}
static void sync_done_handler(void *data, struct wl_callback *callback, uint32_t callback_data)
{
// Nothing to do, just destroy the callback
@@ -527,7 +488,7 @@ int Wayland_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS)
// If key repeat is active, we'll need to cap our maximum wait time to handle repeats
wl_list_for_each (seat, &d->seat_list, link) {
if (keyboard_repeat_is_set(&seat->keyboard.repeat)) {
if (seat->keyboard.repeat.key) {
const Uint64 elapsed = start - seat->keyboard.repeat.sdl_press_time_ns;
const Uint64 next_repeat_wait_time = (seat->keyboard.repeat.next_repeat_ns - elapsed) + 1;
if (timeoutNS < 0 || next_repeat_wait_time <= timeoutNS) {
@@ -664,7 +625,7 @@ void Wayland_PumpEvents(SDL_VideoDevice *_this)
if (ret >= 0) {
// Synthesize key repeat events.
wl_list_for_each (seat, &d->seat_list, link) {
if (keyboard_repeat_is_set(&seat->keyboard.repeat)) {
if (seat->keyboard.repeat.key) {
Wayland_SeatSetKeymap(seat);
const Uint64 elapsed = SDL_GetTicksNS() - seat->keyboard.repeat.sdl_press_time_ns;
@@ -2181,7 +2142,7 @@ static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard,
}
// Stop key repeat before clearing keyboard focus
keyboard_repeat_clear(&seat->keyboard.repeat);
seat->keyboard.repeat.key = 0;
SDL_Window *keyboard_focus = SDL_GetKeyboardFocus();
@@ -2304,7 +2265,7 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
has_text = keyboard_input_get_text(text, seat, key, true, &handled_by_ime);
}
} else {
if (keyboard_repeat_key_is_set(&seat->keyboard.repeat, key)) {
if (key == seat->keyboard.repeat.key) {
/* Send any due key repeat events before stopping the repeat and generating the key up event.
* Compute time based on the Wayland time, as it reports when the release event happened.
* Using SDL_GetTicks would be wrong, as it would report when the release event is processed,
@@ -2312,7 +2273,7 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
*/
const Uint64 elapsed = SDL_MS_TO_NS(time - seat->keyboard.repeat.wl_press_time_ms);
keyboard_repeat_handle(&seat->keyboard.repeat, elapsed);
keyboard_repeat_clear(&seat->keyboard.repeat);
seat->keyboard.repeat.key = 0;
}
keyboard_input_get_text(text, seat, key, false, &handled_by_ime);
}
@@ -2348,7 +2309,7 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
SDL_SendKeyboardText(text);
}
if (seat->keyboard.xkb.keymap && WAYLAND_xkb_keymap_key_repeats(seat->keyboard.xkb.keymap, key + 8)) {
keyboard_repeat_set(&seat->keyboard.repeat, seat->keyboard.sdl_id, key, time, timestamp_ns, scancode, has_text, text);
keyboard_repeat_set(&seat->keyboard.repeat, seat->keyboard.sdl_id, key, time, timestamp_ns, scancode, has_text ? text : NULL);
}
}
}
@@ -2378,12 +2339,11 @@ static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard,
0, 0, group);
// If a key is repeating, update the text to apply the modifier.
if (keyboard_repeat_is_set(&seat->keyboard.repeat)) {
if (seat->keyboard.repeat.key) {
char text[8];
const uint32_t key = keyboard_repeat_get_key(&seat->keyboard.repeat);
if (keyboard_input_get_text(text, seat, key, true, NULL)) {
keyboard_repeat_set_text(&seat->keyboard.repeat, text);
if (keyboard_input_get_text(text, seat, seat->keyboard.repeat.key, true, NULL)) {
SDL_strlcpy(seat->keyboard.repeat.text, text, SDL_arraysize(seat->keyboard.repeat.text));
}
}
@@ -2401,9 +2361,12 @@ static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keybo
int32_t rate, int32_t delay)
{
SDL_WaylandSeat *seat = data;
seat->keyboard.repeat.repeat_rate = SDL_clamp(rate, 0, 1000);
seat->keyboard.repeat.repeat_rate = rate;
seat->keyboard.repeat.repeat_delay_ms = delay;
seat->keyboard.repeat.is_initialized = true;
if (!rate) {
seat->keyboard.repeat.key = 0; // Cancel the repeat to avoid dividing by a rate of zero.
}
}
static const struct wl_keyboard_listener keyboard_listener = {

View File

@@ -43,16 +43,14 @@ typedef struct
{
Sint32 repeat_rate; // Repeat rate in range of [1, 1000] character(s) per second
Sint32 repeat_delay_ms; // Time to first repeat event in milliseconds
Uint32 keyboard_id; // ID of the source keyboard.
bool is_initialized;
bool is_key_down;
Uint32 key;
Uint32 key; // Key code of the repeating key
Uint32 scancode; // Scancode of the repeating key
Uint32 keyboard_id; // ID of the source keyboard
Uint32 wl_press_time_ms; // Key press time as reported by the Wayland API in milliseconds
Uint64 base_time_ns; // Key press time as reported by the Wayland API in nanoseconds
Uint64 sdl_press_time_ns; // Key press time expressed in SDL ticks
Uint64 next_repeat_ns; // Next repeat event in nanoseconds
Uint32 scancode;
char text[8];
} SDL_WaylandKeyboardRepeat;