2323
2424#ifdef SDL_VIDEO_DRIVER_WAYLAND
2525
26+ #include <errno.h>
27+
2628#include "../SDL_sysvideo.h"
2729#include "../SDL_video_c.h"
2830
31+ #include "../../core/unix/SDL_poll.h"
2932#include "../../events/SDL_mouse_c.h"
3033#include "SDL_waylandvideo.h"
3134#include "../SDL_pixels_c.h"
@@ -305,6 +308,164 @@ static struct wl_buffer *Wayland_SeatGetCursorFrame(SDL_WaylandSeat *seat, int f
305308 return NULL ;
306309}
307310
311+ static struct CursorThreadContext
312+ {
313+ SDL_Thread * thread ;
314+ struct wl_event_queue * queue ;
315+ struct wl_proxy * compositor_wrapper ;
316+ SDL_Mutex * lock ;
317+ bool should_exit ;
318+ } cursor_thread_context ;
319+
320+ static void handle_cursor_thread_exit (void * data , struct wl_callback * wl_callback , uint32_t callback_data )
321+ {
322+ wl_callback_destroy (wl_callback );
323+ cursor_thread_context .should_exit = true;
324+ }
325+
326+ static const struct wl_callback_listener cursor_thread_exit_listener = {
327+ handle_cursor_thread_exit
328+ };
329+
330+ static int SDLCALL Wayland_CursorThreadFunc (void * data )
331+ {
332+ struct wl_display * display = data ;
333+ const int display_fd = WAYLAND_wl_display_get_fd (display );
334+ int ret ;
335+
336+ /* The lock must be held whenever dispatching to avoid a race condition when setting
337+ * or destroying cursor frame callbacks, as adding the callback followed by setting
338+ * the listener is not an atomic operation, and the callback proxy must not be
339+ * destroyed while in the callback handler.
340+ *
341+ * Any error other than EAGAIN is fatal and causes the thread to exit.
342+ */
343+ while (!cursor_thread_context .should_exit ) {
344+ if (WAYLAND_wl_display_prepare_read_queue (display , cursor_thread_context .queue ) == 0 ) {
345+ Sint64 timeoutNS = -1 ;
346+
347+ ret = WAYLAND_wl_display_flush (display );
348+
349+ if (ret < 0 ) {
350+ if (errno == EAGAIN ) {
351+ // If the flush failed with EAGAIN, don't block as not to inhibit other threads from reading events.
352+ timeoutNS = SDL_MS_TO_NS (1 );
353+ } else {
354+ WAYLAND_wl_display_cancel_read (display );
355+ return -1 ;
356+ }
357+ }
358+
359+ // Wait for a read/write operation to become possible.
360+ ret = SDL_IOReady (display_fd , SDL_IOR_READ , timeoutNS );
361+
362+ if (ret <= 0 ) {
363+ WAYLAND_wl_display_cancel_read (display );
364+ if (ret < 0 ) {
365+ return -1 ;
366+ }
367+
368+ // Nothing to read, and woke to flush; try again.
369+ continue ;
370+ }
371+
372+ ret = WAYLAND_wl_display_read_events (display );
373+ if (ret == -1 ) {
374+ return -1 ;
375+ }
376+ }
377+
378+ SDL_LockMutex (cursor_thread_context .lock );
379+ ret = WAYLAND_wl_display_dispatch_queue_pending (display , cursor_thread_context .queue );
380+ SDL_UnlockMutex (cursor_thread_context .lock );
381+
382+ if (ret < 0 ) {
383+ return -1 ;
384+ }
385+ }
386+
387+ return 0 ;
388+ }
389+
390+ static bool Wayland_StartCursorThread (SDL_VideoData * data )
391+ {
392+ if (!cursor_thread_context .thread ) {
393+ cursor_thread_context .queue = WAYLAND_wl_display_create_queue (data -> display );
394+ if (!cursor_thread_context .queue ) {
395+ goto cleanup ;
396+ }
397+
398+ cursor_thread_context .compositor_wrapper = WAYLAND_wl_proxy_create_wrapper (data -> compositor );
399+ if (!cursor_thread_context .compositor_wrapper ) {
400+ goto cleanup ;
401+ }
402+ WAYLAND_wl_proxy_set_queue (cursor_thread_context .compositor_wrapper , cursor_thread_context .queue );
403+
404+ cursor_thread_context .lock = SDL_CreateMutex ();
405+ if (!cursor_thread_context .lock ) {
406+ goto cleanup ;
407+ }
408+
409+ cursor_thread_context .thread = SDL_CreateThread (Wayland_CursorThreadFunc , "wl_cursor_surface" , data -> display );
410+ if (!cursor_thread_context .thread ) {
411+ goto cleanup ;
412+ }
413+
414+ return true;
415+ }
416+
417+ cleanup :
418+ if (cursor_thread_context .lock ) {
419+ SDL_DestroyMutex (cursor_thread_context .lock );
420+ }
421+
422+ if (cursor_thread_context .compositor_wrapper ) {
423+ WAYLAND_wl_proxy_wrapper_destroy (cursor_thread_context .compositor_wrapper );
424+ }
425+
426+ if (cursor_thread_context .queue ) {
427+ WAYLAND_wl_event_queue_destroy (cursor_thread_context .queue );
428+ }
429+
430+ SDL_zero (cursor_thread_context );
431+
432+ return false;
433+ }
434+
435+ static void Wayland_DestroyCursorThread (SDL_VideoData * data )
436+ {
437+ if (cursor_thread_context .thread ) {
438+ // Dispatch the exit event to unblock the animation thread and signal it to exit.
439+ struct wl_proxy * display_wrapper = WAYLAND_wl_proxy_create_wrapper (data -> display );
440+ WAYLAND_wl_proxy_set_queue (display_wrapper , cursor_thread_context .queue );
441+
442+ SDL_LockMutex (cursor_thread_context .lock );
443+ struct wl_callback * cb = wl_display_sync ((struct wl_display * )display_wrapper );
444+ wl_callback_add_listener (cb , & cursor_thread_exit_listener , NULL );
445+ SDL_UnlockMutex (cursor_thread_context .lock );
446+
447+ WAYLAND_wl_proxy_wrapper_destroy (display_wrapper );
448+
449+ int ret = WAYLAND_wl_display_flush (data -> display );
450+ if (ret == -1 && errno == EAGAIN ) {
451+ // The timeout is long, but shutting down the thread requires a successful flush.
452+ ret = SDL_IOReady (WAYLAND_wl_display_get_fd (data -> display ), SDL_IOR_WRITE , SDL_MS_TO_NS (1000 ));
453+ if (ret >= 0 ) {
454+ ret = WAYLAND_wl_display_flush (data -> display );
455+ }
456+ }
457+
458+ // Wait for the thread to return. Don't wait if the flush failed, or this can hang.
459+ if (ret >= 0 ) {
460+ SDL_WaitThread (cursor_thread_context .thread , NULL );
461+ }
462+
463+ WAYLAND_wl_proxy_wrapper_destroy (cursor_thread_context .compositor_wrapper );
464+ WAYLAND_wl_event_queue_destroy (cursor_thread_context .queue );
465+ SDL_zero (cursor_thread_context );
466+ }
467+ }
468+
308469static void cursor_frame_done (void * data , struct wl_callback * cb , uint32_t time );
309470static const struct wl_callback_listener cursor_frame_listener = {
310471 cursor_frame_done
@@ -362,6 +523,51 @@ static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time)
362523 wl_surface_commit (seat -> pointer .cursor_state .surface );
363524}
364525
526+ void Wayland_SeatSetCursorFrameCallback (SDL_WaylandSeat * seat )
527+ {
528+ if (cursor_thread_context .lock ) {
529+ SDL_LockMutex (cursor_thread_context .lock );
530+ }
531+
532+ seat -> pointer .cursor_state .frame_callback = wl_surface_frame (seat -> pointer .cursor_state .surface );
533+ wl_callback_add_listener (seat -> pointer .cursor_state .frame_callback , & cursor_frame_listener , seat );
534+
535+ if (cursor_thread_context .lock ) {
536+ SDL_UnlockMutex (cursor_thread_context .lock );
537+ }
538+ }
539+
540+ void Wayland_SeatDestroyCursorFrameCallback (SDL_WaylandSeat * seat )
541+ {
542+ if (cursor_thread_context .lock ) {
543+ SDL_LockMutex (cursor_thread_context .lock );
544+ }
545+
546+ if (seat -> pointer .cursor_state .frame_callback ) {
547+ wl_callback_destroy (seat -> pointer .cursor_state .frame_callback );
548+ seat -> pointer .cursor_state .frame_callback = NULL ;
549+ }
550+
551+ if (cursor_thread_context .lock ) {
552+ SDL_UnlockMutex (cursor_thread_context .lock );
553+ }
554+ }
555+
556+ static void Wayland_SeatResetCursorAnimation (SDL_WaylandSeat * seat , bool lock )
557+ {
558+ if (lock && cursor_thread_context .lock ) {
559+ SDL_LockMutex (cursor_thread_context .lock );
560+ }
561+
562+ seat -> pointer .cursor_state .last_frame_callback_time_ms = SDL_GetTicks ();
563+ seat -> pointer .cursor_state .current_frame_time_ms = 0 ;
564+ seat -> pointer .cursor_state .current_frame = 0 ;
565+
566+ if (lock && cursor_thread_context .lock ) {
567+ SDL_UnlockMutex (cursor_thread_context .lock );
568+ }
569+ }
570+
365571static Wayland_CachedSystemCursor * Wayland_CacheSystemCursor (SDL_CursorData * cdata , struct wl_cursor * cursor , int size )
366572{
367573 Wayland_CachedSystemCursor * cache = NULL ;
@@ -718,10 +924,8 @@ static void Wayland_FreeCursorData(SDL_CursorData *d)
718924 wl_list_for_each (seat , & video_data -> seat_list , link )
719925 {
720926 if (seat -> pointer .current_cursor == d ) {
721- if (seat -> pointer .cursor_state .frame_callback ) {
722- wl_callback_destroy (seat -> pointer .cursor_state .frame_callback );
723- seat -> pointer .cursor_state .frame_callback = NULL ;
724- }
927+ Wayland_SeatDestroyCursorFrameCallback (seat );
928+
725929 if (seat -> pointer .cursor_state .surface ) {
726930 wl_surface_attach (seat -> pointer .cursor_state .surface , NULL , 0 , 0 );
727931 }
@@ -851,13 +1055,17 @@ static void Wayland_SeatSetCursor(SDL_WaylandSeat *seat, SDL_Cursor *cursor)
8511055 int hot_y ;
8521056
8531057 // Stop the frame callback for old animated cursors.
854- if (seat -> pointer .cursor_state .frame_callback && cursor_data != seat -> pointer .current_cursor ) {
855- wl_callback_destroy (seat -> pointer .cursor_state .frame_callback );
856- seat -> pointer .cursor_state .frame_callback = NULL ;
1058+ if (cursor_data != seat -> pointer .current_cursor ) {
1059+ Wayland_SeatDestroyCursorFrameCallback (seat );
8571060 }
8581061
8591062 if (cursor ) {
8601063 if (cursor_data == seat -> pointer .current_cursor ) {
1064+ // Restart the animation sequence if the cursor didn't change.
1065+ if (cursor_data -> num_frames > 1 ) {
1066+ Wayland_SeatResetCursorAnimation (seat , true);
1067+ }
1068+
8611069 return ;
8621070 }
8631071
@@ -895,21 +1103,16 @@ static void Wayland_SeatSetCursor(SDL_WaylandSeat *seat, SDL_Cursor *cursor)
8951103 seat -> pointer .current_cursor = cursor_data ;
8961104
8971105 if (!seat -> pointer .cursor_state .surface ) {
898- seat -> pointer .cursor_state .surface = wl_compositor_create_surface (seat -> display -> compositor );
1106+ if (cursor_thread_context .compositor_wrapper ) {
1107+ seat -> pointer .cursor_state .surface = wl_compositor_create_surface ((struct wl_compositor * )cursor_thread_context .compositor_wrapper );
1108+ } else {
1109+ seat -> pointer .cursor_state .surface = wl_compositor_create_surface (seat -> display -> compositor );
1110+ }
8991111 }
9001112
9011113 struct wl_buffer * buffer = Wayland_SeatGetCursorFrame (seat , 0 );
9021114 wl_surface_attach (seat -> pointer .cursor_state .surface , buffer , 0 , 0 );
9031115
904- // If more than one frame is available, create a frame callback to run the animation.
905- if (cursor_data -> num_frames > 1 ) {
906- seat -> pointer .cursor_state .last_frame_callback_time_ms = SDL_GetTicks ();
907- seat -> pointer .cursor_state .current_frame_time_ms = 0 ;
908- seat -> pointer .cursor_state .current_frame = 0 ;
909- seat -> pointer .cursor_state .frame_callback = wl_surface_frame (seat -> pointer .cursor_state .surface );
910- wl_callback_add_listener (seat -> pointer .cursor_state .frame_callback , & cursor_frame_listener , seat );
911- }
912-
9131116 // A scale value of 0 indicates that a viewport with the returned destination size should be used.
9141117 if (!scale ) {
9151118 if (!seat -> pointer .cursor_state .viewport ) {
@@ -934,8 +1137,15 @@ static void Wayland_SeatSetCursor(SDL_WaylandSeat *seat, SDL_Cursor *cursor)
9341137 wl_surface_damage (seat -> pointer .cursor_state .surface , 0 , 0 , SDL_MAX_SINT32 , SDL_MAX_SINT32 );
9351138 }
9361139
1140+ // If more than one frame is available, create a frame callback to run the animation.
1141+ if (cursor_data -> num_frames > 1 ) {
1142+ Wayland_SeatResetCursorAnimation (seat , false);
1143+ Wayland_SeatSetCursorFrameCallback (seat );
1144+ }
1145+
9371146 wl_surface_commit (seat -> pointer .cursor_state .surface );
9381147 } else {
1148+ Wayland_SeatDestroyCursorFrameCallback (seat );
9391149 seat -> pointer .current_cursor = NULL ;
9401150 wl_pointer_set_cursor (seat -> pointer .wl_pointer , seat -> pointer .enter_serial , NULL , 0 , 0 );
9411151 }
@@ -1180,7 +1390,7 @@ void Wayland_RecreateCursors(void)
11801390}
11811391#endif // 0
11821392
1183- void Wayland_InitMouse (void )
1393+ void Wayland_InitMouse (SDL_VideoData * data )
11841394{
11851395 SDL_Mouse * mouse = SDL_GetMouse ();
11861396
@@ -1194,6 +1404,10 @@ void Wayland_InitMouse(void)
11941404 mouse -> SetRelativeMouseMode = Wayland_SetRelativeMouseMode ;
11951405 mouse -> GetGlobalMouseState = Wayland_GetGlobalMouseState ;
11961406
1407+ if (!Wayland_StartCursorThread (data )) {
1408+ SDL_LogError (SDL_LOG_CATEGORY_VIDEO , "wayland: Failed to start cursor animation event thread" );
1409+ }
1410+
11971411 SDL_HitTestResult r = SDL_HITTEST_NORMAL ;
11981412 while (r <= SDL_HITTEST_RESIZE_LEFT ) {
11991413 switch (r ) {
@@ -1248,6 +1462,7 @@ void Wayland_InitMouse(void)
12481462
12491463void Wayland_FiniMouse (SDL_VideoData * data )
12501464{
1465+ Wayland_DestroyCursorThread (data );
12511466 Wayland_FreeCursorThemes (data );
12521467
12531468#ifdef SDL_USE_LIBDBUS
0 commit comments