From 9961fb80a73e8766bf4a26c5b7ded1440efbc0f3 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Fri, 17 Apr 2026 08:56:35 -0400 Subject: [PATCH] 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. --- src/video/wayland/SDL_waylandeventthread.c | 210 +++++++++++++++++++++ src/video/wayland/SDL_waylandeventthread.h | 36 ++++ src/video/wayland/SDL_waylandmouse.c | 188 ++---------------- src/video/wayland/SDL_waylandvideo.c | 11 +- src/video/wayland/SDL_waylandvideo.h | 2 + 5 files changed, 270 insertions(+), 177 deletions(-) create mode 100644 src/video/wayland/SDL_waylandeventthread.c create mode 100644 src/video/wayland/SDL_waylandeventthread.h diff --git a/src/video/wayland/SDL_waylandeventthread.c b/src/video/wayland/SDL_waylandeventthread.c new file mode 100644 index 0000000000..ae8e124d04 --- /dev/null +++ b/src/video/wayland/SDL_waylandeventthread.c @@ -0,0 +1,210 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + 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 + +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 diff --git a/src/video/wayland/SDL_waylandeventthread.h b/src/video/wayland/SDL_waylandeventthread.h new file mode 100644 index 0000000000..81f9f606b4 --- /dev/null +++ b/src/video/wayland/SDL_waylandeventthread.h @@ -0,0 +1,36 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + 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_ diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c index 40c05495c9..5533ee7133 100644 --- a/src/video/wayland/SDL_waylandmouse.c +++ b/src/video/wayland/SDL_waylandmouse.c @@ -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 diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 9404947f5e..1c1fef5bcd 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -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; diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h index f06895aa40..17a9ba24fa 100644 --- a/src/video/wayland/SDL_waylandvideo.h +++ b/src/video/wayland/SDL_waylandvideo.h @@ -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;