Wayland: Fix spurious key repeat events when some user defined callback takes a long time to execute

On compositors that support compositor key repeat events, use those, for
complete robustness. Sadly no actual compositor implements these yet.

Otherwise use a timer fd/pipe to queue the repeat events and only
dispatch them after events from the compositor are handled. This means
release events from the compositor will prevent spurious repeat events.
One can, in the worst case lose some repeat events if there is a very
large interval between the start of the timer and the next poll, but
that is unavoidable and is why repeat events should come from the compositor
in the first place.

Fixes #9224
This commit is contained in:
Kovid Goyal
2025-12-03 22:26:09 +05:30
parent 7d24c82d4d
commit 16008b950a
5 changed files with 103 additions and 24 deletions

View File

@@ -181,6 +181,9 @@ Detailed list of changes
pager in search mode. If any text is currently selected it is automatically
searched for.
- Wayland: Fix spurious key repeat events when some user defined callback takes
a long time to execute (:iss:`9224`)
0.44.0 [2025-11-03]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

29
glfw/backend_utils.c vendored
View File

@@ -234,6 +234,12 @@ mark_wakep_fd_ready(int fd UNUSED, int events UNUSED, void *data) {
((EventLoopData*)(data))->wakeup_fd_ready = true;
}
static void
mark_key_repeat_fd_ready(int fd UNUSED, int events UNUSED, void *data) {
((EventLoopData*)(data))->key_repeat_fd_ready = true;
}
bool
initPollData(EventLoopData *eld, int display_fd) {
if (!addWatch(eld, "display", display_fd, POLLIN, 1, NULL, NULL)) return false;
@@ -246,6 +252,19 @@ initPollData(EventLoopData *eld, int display_fd) {
const int wakeup_fd = eld->wakeupFds[0];
#endif
if (!addWatch(eld, "wakeup", wakeup_fd, POLLIN, 1, mark_wakep_fd_ready, eld)) return false;
#ifdef HAS_TIMER_FD
eld->key_repeat_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
if (eld->key_repeat_fd < 0) return false;
const int key_repeat_fd = eld->key_repeat_fd;
#else
if (pipe2(eld->key_repeat_fds, O_CLOEXEC | O_NONBLOCK) != 0) return false;
const int key_repeat_fd = eld->key_repeat_fds[0];
#endif
(void)key_repeat_fd; (void)mark_key_repeat_fd_ready;
#ifdef _GLFW_WAYLAND
if (!addWatch(eld, "key_repeat", key_repeat_fd, POLLIN, 1, mark_key_repeat_fd_ready, eld)) return false;
#endif
return true;
}
@@ -269,7 +288,6 @@ wakeupEventLoop(EventLoopData *eld) {
#endif
}
#ifndef HAS_EVENT_FD
static void
closeFds(int *fds, size_t count) {
while(count--) {
@@ -280,15 +298,20 @@ closeFds(int *fds, size_t count) {
fds++;
}
}
#endif
void
finalizePollData(EventLoopData *eld) {
(void)closeFds;
#ifdef HAS_EVENT_FD
close(eld->wakeupFd); eld->wakeupFd = -1;
#else
closeFds(eld->wakeupFds, arraysz(eld->wakeupFds));
#endif
#ifdef HAS_TIMER_FD
close(eld->key_repeat_fd); eld->key_repeat_fd = -1;
#else
closeFds(eld->key_repeat_fds, arraysz(eld->key_repeat_fds));
#endif
}
int
@@ -298,7 +321,7 @@ pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func displ
EVDBG("pollForEvents final timeout: %.3f", monotonic_t_to_s_double(timeout));
int result;
monotonic_t end_time = monotonic() + timeout;
eld->wakeup_fd_ready = false;
eld->wakeup_fd_ready = false; eld->key_repeat_fd_ready = false;
while(1) {
if (timeout >= 0) {

11
glfw/backend_utils.h vendored
View File

@@ -36,6 +36,10 @@
#define HAS_EVENT_FD
#include <sys/eventfd.h>
#endif
#if __has_include(<sys/timerfd.h>)
#define HAS_TIMER_FD
#include <sys/timerfd.h>
#endif
#else
#define HAS_EVENT_FD
#include <sys/eventfd.h>
@@ -73,7 +77,12 @@ typedef struct {
#else
int wakeupFds[2];
#endif
bool wakeup_data_read, wakeup_fd_ready;
#ifdef HAS_TIMER_FD
int key_repeat_fd;
#else
int key_repeat_fds[2];
#endif
bool wakeup_data_read, wakeup_fd_ready, key_repeat_fd_ready;
nfds_t watches_count, timers_count;
Watch watches[32];
Timer timers[128];

63
glfw/wl_init.c vendored
View File

@@ -298,6 +298,41 @@ static void keyboardHandleKeymap(void* data UNUSED,
}
static void
start_key_repeat_timer(bool initial) {
if (_glfw.wl.keyboardRepeatRate <= 0) return;
#ifdef HAS_TIMER_FD
(void)initial;
struct itimerspec new_value = {.it_value={.tv_nsec = _glfw.wl.keyboardRepeatDelay}, .it_interval={.tv_nsec = (s_to_monotonic_t(1ll) / (monotonic_t)_glfw.wl.keyboardRepeatRate)}};
if (_glfw.wl.eventLoopData.key_repeat_fd > -1) timerfd_settime(
_glfw.wl.eventLoopData.key_repeat_fd, 0, &new_value, NULL);
#else
monotonic_t interval = _glfw.wl.keyboardRepeatDelay;
if (!initial) interval = (s_to_monotonic_t(1ll) / (monotonic_t)_glfw.wl.keyboardRepeatRate);
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, interval);
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1);
#endif
}
static void
stop_key_repeat_timer(void) {
#ifdef HAS_TIMER_FD
struct itimerspec new_value = {0};
if (_glfw.wl.eventLoopData.key_repeat_fd > -1) timerfd_settime(_glfw.wl.eventLoopData.key_repeat_fd, 0, &new_value, NULL);
#else
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0);
#endif
}
#ifndef HAS_TIMER_FD
static void
send_key_repeat_timer_event(id_type timer_id UNUSED, void *data UNUSED) {
char b = 1;
b += write(_glfw.wl.eventLoopData.key_repeat_fds[1], &b, 1);
if (_glfw.wl.keyboardRepeatRate > 0) start_key_repeat_timer(false);
}
#endif
static void keyboardHandleEnter(void* data UNUSED,
struct wl_keyboard* keyboard UNUSED,
uint32_t serial,
@@ -314,7 +349,7 @@ static void keyboardHandleEnter(void* data UNUSED,
if (keys && _glfw.wl.keyRepeatInfo.key) {
wl_array_for_each(key, keys) {
if (*key == _glfw.wl.keyRepeatInfo.key) {
if (_glfw.wl.keyboardRepeatRate > 0) toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1);
if (_glfw.wl.keyboardRepeatRate > 0) start_key_repeat_timer(true);
break;
}
}
@@ -334,22 +369,9 @@ static void keyboardHandleLeave(void* data UNUSED,
_glfw.wl.serial = serial;
_glfw.wl.keyboardFocusId = 0;
_glfwInputWindowFocus(window, false);
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0);
stop_key_repeat_timer();
}
static void
dispatchPendingKeyRepeats(id_type timer_id UNUSED, void *data UNUSED) {
if (_glfw.wl.keyRepeatInfo.keyboardFocusId != _glfw.wl.keyboardFocusId || _glfw.wl.keyboardRepeatRate == 0) return;
_GLFWwindow* window = _glfwWindowForId(_glfw.wl.keyboardFocusId);
if (!window) return;
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, _glfw.wl.keyRepeatInfo.key, GLFW_REPEAT);
if (_glfw.wl.keyboardRepeatRate > 0) {
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (s_to_monotonic_t(1ll) / (monotonic_t)_glfw.wl.keyboardRepeatRate));
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1);
}
}
static void keyboardHandleKey(void* data UNUSED,
struct wl_keyboard* keyboard UNUSED,
uint32_t serial,
@@ -375,11 +397,10 @@ static void keyboardHandleKey(void* data UNUSED,
{
_glfw.wl.keyRepeatInfo.key = key;
_glfw.wl.keyRepeatInfo.keyboardFocusId = window->id;
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, _glfw.wl.keyboardRepeatDelay);
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1);
start_key_repeat_timer(true);
} else if (action == GLFW_RELEASE && key == _glfw.wl.keyRepeatInfo.key) {
_glfw.wl.keyRepeatInfo.key = 0;
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0);
stop_key_repeat_timer();
}
}
@@ -447,7 +468,7 @@ static void seatHandleCapabilities(void* data UNUSED,
wl_keyboard_destroy(_glfw.wl.keyboard);
_glfw.wl.keyboard = NULL;
_glfw.wl.keyboardFocusId = 0;
if (_glfw.wl.keyRepeatInfo.keyRepeatTimer) toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0);
stop_key_repeat_timer();
}
}
@@ -854,7 +875,9 @@ int _glfwPlatformInit(bool *supports_window_occlusion)
}
glfw_dbus_init(&_glfw.wl.dbus, &_glfw.wl.eventLoopData);
glfw_initialize_desktop_settings();
_glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-key-repeat", ms_to_monotonic_t(500ll), 0, true, dispatchPendingKeyRepeats, NULL, NULL);
#ifndef HAS_TIMER_FD
_glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-key-repeat", ms_to_monotonic_t(500ll), 0, true, send_key_repeat_timer_event, NULL, NULL);
#endif
_glfw.wl.cursorAnimationTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-cursor-animation", ms_to_monotonic_t(500ll), 0, true, animateCursorImage, NULL, NULL);
_glfw.wl.registry = wl_display_get_registry(_glfw.wl.display);

21
glfw/wl_window.c vendored
View File

@@ -41,6 +41,7 @@
#include <fcntl.h>
#include <sys/mman.h>
#include <assert.h>
#include <unistd.h>
#define debug debug_rendering
@@ -1306,6 +1307,25 @@ wayland_read_events(int poll_result, int events, void *data UNUSED) {
else wl_display_cancel_read(_glfw.wl.display);
}
static void
handle_key_repeat_events(void) {
uint64_t num_events = 0;
#ifdef HAS_TIMER_FD
if (read(_glfw.wl.eventLoopData.key_repeat_fd, &num_events, sizeof(num_events)) < (ssize_t)sizeof(num_events)) return;
#else
char buf[16];
while(1) {
ssize_t num = read(_glfw.wl.eventLoopData.key_repeat_fds[0], buf, sizeof(buf));
if (num > 0) num_events += num;
else if (num == 0 || errno != EINTR) break;
}
#endif
if (_glfw.wl.keyRepeatInfo.keyboardFocusId != _glfw.wl.keyboardFocusId || _glfw.wl.keyboardRepeatRate == 0) return;
_GLFWwindow* window = _glfwWindowForId(_glfw.wl.keyboardFocusId);
if (!window || !_glfw.wl.keyRepeatInfo.key) return;
while (num_events--) glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, _glfw.wl.keyRepeatInfo.key, GLFW_REPEAT);
}
static void handleEvents(monotonic_t timeout)
{
struct wl_display* display = _glfw.wl.display;
@@ -1345,6 +1365,7 @@ static void handleEvents(monotonic_t timeout)
glfw_dbus_session_bus_dispatch();
EVDBG("other dispatch done");
if (_glfw.wl.eventLoopData.wakeup_fd_ready) check_for_wakeup_events(&_glfw.wl.eventLoopData);
if (_glfw.wl.eventLoopData.key_repeat_fd_ready) handle_key_repeat_events();
}
static struct wl_cursor*