wayland: Refactor the cursor event thread into a general purpose event thread

No functional changes, just tidies the mouse code, and allows for future generalized use of the event handling thread, if required.
This commit is contained in:
Frank Praznik
2026-04-17 08:56:35 -04:00
parent bd7c9e467b
commit 9961fb80a7
5 changed files with 270 additions and 177 deletions

View File

@@ -0,0 +1,210 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_VIDEO_DRIVER_WAYLAND
#include "../../core/unix/SDL_poll.h"
#include "SDL_waylandeventthread.h"
#include "SDL_waylandvideo.h"
#include <errno.h>
struct Wayland_EventThreadContext
{
SDL_VideoData *wayland_data;
SDL_Thread *thread;
struct wl_event_queue *queue;
SDL_Mutex *dispatch_lock;
bool should_exit;
};
static void handle_event_thread_exit(void *data, struct wl_callback *wl_callback, uint32_t callback_data)
{
Wayland_EventThreadContext *context = (Wayland_EventThreadContext *)data;
wl_callback_destroy(wl_callback);
context->should_exit = true;
}
static const struct wl_callback_listener event_thread_exit_listener = {
handle_event_thread_exit
};
static int SDLCALL Wayland_EventThreadFunc(void *data)
{
Wayland_EventThreadContext *context = (Wayland_EventThreadContext *)data;
struct wl_display *display = context->wayland_data->display;
const int display_fd = WAYLAND_wl_display_get_fd(display);
int ret;
/* The lock must be held whenever dispatching to avoid a race condition when adding
* or destroying objects using the queue.
*
* Any error other than EAGAIN is fatal and causes the thread to exit.
*/
while (!context->should_exit) {
if (WAYLAND_wl_display_prepare_read_queue(display, context->queue) == 0) {
Sint64 timeoutNS = -1;
ret = WAYLAND_wl_display_flush(display);
if (ret < 0) {
if (errno == EAGAIN) {
// If the flush failed with EAGAIN, don't block as not to inhibit other threads from reading events.
timeoutNS = SDL_MS_TO_NS(1);
} else {
WAYLAND_wl_display_cancel_read(display);
return -1;
}
}
// Wait for a read/write operation to become possible.
ret = SDL_IOReady(display_fd, SDL_IOR_READ, timeoutNS);
if (ret <= 0) {
WAYLAND_wl_display_cancel_read(display);
if (ret < 0) {
return -1;
}
// Nothing to read, and woke to flush; try again.
continue;
}
ret = WAYLAND_wl_display_read_events(display);
if (ret == -1) {
return -1;
}
}
SDL_LockMutex(context->dispatch_lock);
ret = WAYLAND_wl_display_dispatch_queue_pending(display, context->queue);
SDL_UnlockMutex(context->dispatch_lock);
if (ret < 0) {
return -1;
}
}
return 0;
}
Wayland_EventThreadContext *Wayland_CreateEventThread(SDL_VideoData *data, const char *queue_name)
{
Wayland_EventThreadContext *context = SDL_calloc(1, sizeof(Wayland_EventThreadContext));
if (!context) {
return NULL;
}
context->wayland_data = data;
context->queue = Wayland_DisplayCreateQueue(data->display, queue_name);
if (!context->queue) {
goto cleanup;
}
context->dispatch_lock = SDL_CreateMutex();
if (!context->dispatch_lock) {
goto cleanup;
}
context->thread = SDL_CreateThread(Wayland_EventThreadFunc, "wl_event_thread", context);
if (!context->thread) {
goto cleanup;
}
return context;
cleanup:
if (context->dispatch_lock) {
SDL_DestroyMutex(context->dispatch_lock);
}
if (context->queue) {
WAYLAND_wl_event_queue_destroy(context->queue);
}
SDL_free(context);
return NULL;
}
void Wayland_DestroyEventThread(Wayland_EventThreadContext *context)
{
if (context->thread) {
// Dispatch the exit event to unblock the thread and signal it to exit.
struct wl_display *display = context->wayland_data->display;
struct wl_proxy *display_wrapper = Wayland_CreateEventThreadProxyWrapper(context, display);
SDL_LockMutex(context->dispatch_lock);
struct wl_callback *cb = wl_display_sync((struct wl_display *)display_wrapper);
wl_callback_add_listener(cb, &event_thread_exit_listener, context);
SDL_UnlockMutex(context->dispatch_lock);
WAYLAND_wl_proxy_wrapper_destroy(display_wrapper);
int ret = WAYLAND_wl_display_flush(display);
while (ret == -1 && errno == EAGAIN) {
// Shutting down the thread requires a successful flush.
ret = SDL_IOReady(WAYLAND_wl_display_get_fd(display), SDL_IOR_WRITE, -1);
if (ret >= 0) {
ret = WAYLAND_wl_display_flush(display);
}
}
// Avoid a warning if the flush failed due to a broken connection.
if (ret < 0) {
wl_callback_destroy(cb);
}
// Wait for the thread to return; it will exit automatically on a broken connection.
SDL_WaitThread(context->thread, NULL);
WAYLAND_wl_event_queue_destroy(context->queue);
SDL_DestroyMutex(context->dispatch_lock);
SDL_free(context);
}
}
void *Wayland_CreateEventThreadProxyWrapper(Wayland_EventThreadContext *context, void *obj)
{
struct wl_proxy *wrapper = WAYLAND_wl_proxy_create_wrapper(obj);
if (wrapper) {
WAYLAND_wl_proxy_set_queue(wrapper, context->queue);
return wrapper;
}
return NULL;
}
void Wayland_LockEventThread(Wayland_EventThreadContext *context)
{
if (context) {
SDL_LockMutex(context->dispatch_lock);
}
}
void Wayland_UnlockEventThread(Wayland_EventThreadContext *context)
{
if (context) {
SDL_UnlockMutex(context->dispatch_lock);
}
}
#endif // SDL_VIDEO_DRIVER_WAYLAND

View File

@@ -0,0 +1,36 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_waylandeventthread_h_
#define SDL_waylandeventthread_h_
#include "../SDL_sysvideo.h"
typedef struct Wayland_EventThreadContext Wayland_EventThreadContext;
extern Wayland_EventThreadContext *Wayland_CreateEventThread(SDL_VideoData *data, const char *queue_name);
extern void Wayland_DestroyEventThread(Wayland_EventThreadContext *context);
extern void *Wayland_CreateEventThreadProxyWrapper(Wayland_EventThreadContext *context, void *obj);
extern void Wayland_LockEventThread(Wayland_EventThreadContext *context);
extern void Wayland_UnlockEventThread(Wayland_EventThreadContext *context);
#endif // SDL_waylandeventthread_h_

View File

@@ -324,168 +324,6 @@ static struct wl_buffer *Wayland_CursorStateGetFrame(SDL_WaylandCursorState *sta
return NULL;
}
static struct CursorThreadContext
{
SDL_Thread *thread;
struct wl_event_queue *queue;
struct wl_proxy *compositor_wrapper;
SDL_Mutex *lock;
bool should_exit;
} cursor_thread_context;
static void handle_cursor_thread_exit(void *data, struct wl_callback *wl_callback, uint32_t callback_data)
{
wl_callback_destroy(wl_callback);
cursor_thread_context.should_exit = true;
}
static const struct wl_callback_listener cursor_thread_exit_listener = {
handle_cursor_thread_exit
};
static int SDLCALL Wayland_CursorThreadFunc(void *data)
{
struct wl_display *display = data;
const int display_fd = WAYLAND_wl_display_get_fd(display);
int ret;
/* The lock must be held whenever dispatching to avoid a race condition when setting
* or destroying cursor frame callbacks, as adding the callback followed by setting
* the listener is not an atomic operation, and the callback proxy must not be
* destroyed while in the callback handler.
*
* Any error other than EAGAIN is fatal and causes the thread to exit.
*/
while (!cursor_thread_context.should_exit) {
if (WAYLAND_wl_display_prepare_read_queue(display, cursor_thread_context.queue) == 0) {
Sint64 timeoutNS = -1;
ret = WAYLAND_wl_display_flush(display);
if (ret < 0) {
if (errno == EAGAIN) {
// If the flush failed with EAGAIN, don't block as not to inhibit other threads from reading events.
timeoutNS = SDL_MS_TO_NS(1);
} else {
WAYLAND_wl_display_cancel_read(display);
return -1;
}
}
// Wait for a read/write operation to become possible.
ret = SDL_IOReady(display_fd, SDL_IOR_READ, timeoutNS);
if (ret <= 0) {
WAYLAND_wl_display_cancel_read(display);
if (ret < 0) {
return -1;
}
// Nothing to read, and woke to flush; try again.
continue;
}
ret = WAYLAND_wl_display_read_events(display);
if (ret == -1) {
return -1;
}
}
SDL_LockMutex(cursor_thread_context.lock);
ret = WAYLAND_wl_display_dispatch_queue_pending(display, cursor_thread_context.queue);
SDL_UnlockMutex(cursor_thread_context.lock);
if (ret < 0) {
return -1;
}
}
return 0;
}
static bool Wayland_StartCursorThread(SDL_VideoData *data)
{
if (!cursor_thread_context.thread) {
cursor_thread_context.queue = Wayland_DisplayCreateQueue(data->display, "SDL Cursor Surface Queue");
if (!cursor_thread_context.queue) {
goto cleanup;
}
cursor_thread_context.compositor_wrapper = WAYLAND_wl_proxy_create_wrapper(data->compositor);
if (!cursor_thread_context.compositor_wrapper) {
goto cleanup;
}
WAYLAND_wl_proxy_set_queue(cursor_thread_context.compositor_wrapper, cursor_thread_context.queue);
cursor_thread_context.lock = SDL_CreateMutex();
if (!cursor_thread_context.lock) {
goto cleanup;
}
cursor_thread_context.thread = SDL_CreateThread(Wayland_CursorThreadFunc, "wl_cursor_surface", data->display);
if (!cursor_thread_context.thread) {
goto cleanup;
}
return true;
}
cleanup:
if (cursor_thread_context.lock) {
SDL_DestroyMutex(cursor_thread_context.lock);
}
if (cursor_thread_context.compositor_wrapper) {
WAYLAND_wl_proxy_wrapper_destroy(cursor_thread_context.compositor_wrapper);
}
if (cursor_thread_context.queue) {
WAYLAND_wl_event_queue_destroy(cursor_thread_context.queue);
}
SDL_zero(cursor_thread_context);
return false;
}
static void Wayland_DestroyCursorThread(SDL_VideoData *data)
{
if (cursor_thread_context.thread) {
// Dispatch the exit event to unblock the animation thread and signal it to exit.
struct wl_proxy *display_wrapper = WAYLAND_wl_proxy_create_wrapper(data->display);
WAYLAND_wl_proxy_set_queue(display_wrapper, cursor_thread_context.queue);
SDL_LockMutex(cursor_thread_context.lock);
struct wl_callback *cb = wl_display_sync((struct wl_display *)display_wrapper);
wl_callback_add_listener(cb, &cursor_thread_exit_listener, NULL);
SDL_UnlockMutex(cursor_thread_context.lock);
WAYLAND_wl_proxy_wrapper_destroy(display_wrapper);
int ret = WAYLAND_wl_display_flush(data->display);
while (ret == -1 && errno == EAGAIN) {
// Shutting down the thread requires a successful flush.
ret = SDL_IOReady(WAYLAND_wl_display_get_fd(data->display), SDL_IOR_WRITE, -1);
if (ret >= 0) {
ret = WAYLAND_wl_display_flush(data->display);
}
}
// Avoid a warning if the flush failed due to a broken connection.
if (ret < 0) {
wl_callback_destroy(cb);
}
// Wait for the thread to return; it will exit automatically on a broken connection.
SDL_WaitThread(cursor_thread_context.thread, NULL);
WAYLAND_wl_proxy_wrapper_destroy(cursor_thread_context.compositor_wrapper);
WAYLAND_wl_event_queue_destroy(cursor_thread_context.queue);
SDL_DestroyMutex(cursor_thread_context.lock);
SDL_zero(cursor_thread_context);
}
}
static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time);
static const struct wl_callback_listener cursor_frame_listener = {
cursor_frame_done
@@ -545,24 +383,26 @@ static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time)
void Wayland_CursorStateSetFrameCallback(SDL_WaylandCursorState *state, void *userdata)
{
SDL_LockMutex(cursor_thread_context.lock);
SDL_VideoData *viddata = SDL_GetVideoDevice()->internal;
Wayland_LockEventThread(viddata->event_thread_context);
state->frame_callback = wl_surface_frame(state->surface);
wl_callback_add_listener(state->frame_callback, &cursor_frame_listener, userdata);
SDL_UnlockMutex(cursor_thread_context.lock);
Wayland_UnlockEventThread(viddata->event_thread_context);
}
void Wayland_CursorStateDestroyFrameCallback(SDL_WaylandCursorState *state)
{
SDL_LockMutex(cursor_thread_context.lock);
SDL_VideoData *viddata = SDL_GetVideoDevice()->internal;
Wayland_LockEventThread(viddata->event_thread_context);
if (state->frame_callback) {
wl_callback_destroy(state->frame_callback);
state->frame_callback = NULL;
}
SDL_UnlockMutex(cursor_thread_context.lock);
Wayland_UnlockEventThread(viddata->event_thread_context);
}
static void Wayland_CursorStateResetAnimationState(SDL_WaylandCursorState *state)
@@ -575,9 +415,10 @@ static void Wayland_CursorStateResetAnimationState(SDL_WaylandCursorState *state
static void Wayland_CursorStateResetAnimation(SDL_WaylandCursorState *state, bool lock)
{
if (lock) {
SDL_LockMutex(cursor_thread_context.lock);
SDL_VideoData *viddata = SDL_GetVideoDevice()->internal;
Wayland_LockEventThread(viddata->event_thread_context);
Wayland_CursorStateResetAnimationState(state);
SDL_UnlockMutex(cursor_thread_context.lock);
Wayland_UnlockEventThread(viddata->event_thread_context);
} else {
Wayland_CursorStateResetAnimationState(state);
}
@@ -1175,8 +1016,10 @@ static void Wayland_CursorStateSetCursor(SDL_WaylandCursorState *state, const Wa
state->current_cursor = cursor_data;
if (!state->surface) {
if (cursor_thread_context.compositor_wrapper) {
state->surface = wl_compositor_create_surface((struct wl_compositor *)cursor_thread_context.compositor_wrapper);
if (viddata->event_thread_context) {
struct wl_compositor *compositor_wrapper = Wayland_CreateEventThreadProxyWrapper(viddata->event_thread_context, viddata->compositor);
state->surface = wl_compositor_create_surface(compositor_wrapper);
WAYLAND_wl_proxy_wrapper_destroy(compositor_wrapper);
} else {
state->surface = wl_compositor_create_surface(viddata->compositor);
}
@@ -1541,10 +1384,6 @@ void Wayland_InitMouse(SDL_VideoData *data)
mouse->SetRelativeMouseMode = Wayland_SetRelativeMouseMode;
mouse->GetGlobalMouseState = Wayland_GetGlobalMouseState;
if (!Wayland_StartCursorThread(data)) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "wayland: Failed to start cursor animation event thread");
}
SDL_HitTestResult r = SDL_HITTEST_NORMAL;
while (r <= SDL_HITTEST_RESIZE_LEFT) {
switch (r) {
@@ -1604,7 +1443,6 @@ void Wayland_FiniMouse(SDL_VideoData *data)
sys_cursors[i] = NULL;
}
Wayland_DestroyCursorThread(data);
Wayland_FreeCursorThemes(data);
#ifdef SDL_USE_LIBDBUS

View File

@@ -1684,6 +1684,11 @@ bool Wayland_VideoInit(SDL_VideoDevice *_this)
{
SDL_VideoData *data = _this->internal;
data->event_thread_context = Wayland_CreateEventThread(data, "SDL Event Thread Queue");
if (!data->event_thread_context) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "wayland: Failed to create event thread context");
}
data->xkb_context = WAYLAND_xkb_context_new(0);
if (!data->xkb_context) {
return SDL_SetError("Failed to create XKB context");
@@ -1787,6 +1792,10 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
}
Wayland_FiniMouse(data);
Wayland_QuitKeyboard(_this);
Wayland_DestroyEventThread(data->event_thread_context);
data->event_thread_context = NULL;
if (data->pointer_constraints) {
zwp_pointer_constraints_v1_destroy(data->pointer_constraints);
@@ -1813,8 +1822,6 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
data->key_inhibitor_manager = NULL;
}
Wayland_QuitKeyboard(_this);
if (data->text_input_manager) {
zwp_text_input_manager_v3_destroy(data->text_input_manager);
data->text_input_manager = NULL;

View File

@@ -30,6 +30,7 @@
#include "../SDL_sysvideo.h"
#include "../../core/linux/SDL_dbus.h"
#include "../../core/linux/SDL_ime.h"
#include "SDL_waylandeventthread.h"
struct xkb_context;
struct SDL_WaylandSeat;
@@ -97,6 +98,7 @@ struct SDL_VideoData
struct SDL_WaylandSeat *last_implicit_grab_seat;
struct SDL_WaylandSeat *last_incoming_data_offer_seat;
struct SDL_WaylandSeat *last_incoming_primary_selection_seat;
Wayland_EventThreadContext *event_thread_context;
SDL_DisplayData **output_list;
int output_count;