SDL: wayland: Don't block on libdecor_dispatch()

From 620476865363e6dc5c0e7d126243a2427911043a Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Thu, 13 Oct 2022 16:24:13 -0400
Subject: [PATCH] wayland: Don't block on libdecor_dispatch()

libdecor_dispatch() needs to be called, as libdecor plugins might do some required internal processing within, however care must be taken to avoid double-blocking in the case of a timeout, which can occur if libdecor_dispatch() and the SDL event processing both work on the main Wayland queue. Additionally, assumptions that libdecor will always dispatch the main queue or not process zero-length queues (can occur if a wait is interrupted by the application queueing an event) should not be made, as this behavior is outside the control of SDL and can change.

SDL handles polling for Wayland events and then calls libdecor to do its internal processing and dispatch. If libdecor operates on the main queue, it will dispatch the queued events and the additional wl_display_dispatch_pending() call will be a NOP. If a libdecor plugin uses its own, separate queue, then the wl_display_dispatch_pending() call will ensure that the main queue is always dispatched.
---
 src/video/wayland/SDL_waylandevents.c | 43 +++++++++++++++++++--------
 1 file changed, 30 insertions(+), 13 deletions(-)

diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index 99a0bc34f259..f74d0fa545e5 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -289,6 +289,25 @@ Wayland_SendWakeupEvent(_THIS, SDL_Window *window)
     WAYLAND_wl_display_flush(d->display);
 }
 
+static int
+dispatch_queued_events(SDL_VideoData *viddata)
+{
+    int ret;
+
+    /*
+     * NOTE: When reconnection is implemented, check if libdecor needs to be
+     *       involved in the reconnection process.
+     */
+#ifdef HAVE_LIBDECOR_H
+    if (viddata->shell.libdecor) {
+        libdecor_dispatch(viddata->shell.libdecor, 0);
+    }
+#endif
+
+    ret = WAYLAND_wl_display_dispatch_pending(viddata->display);
+    return ret >= 0 ? 1 : ret;
+}
+
 int
 Wayland_WaitEventTimeout(_THIS, int timeout)
 {
@@ -321,12 +340,6 @@ Wayland_WaitEventTimeout(_THIS, int timeout)
         }
     }
 
-#ifdef HAVE_LIBDECOR_H
-    if (d->shell.libdecor) {
-        libdecor_dispatch(d->shell.libdecor, timeout);
-    }
-#endif
-
     /* wl_display_prepare_read() will return -1 if the default queue is not empty.
      * If the default queue is empty, it will prepare us for our SDL_IOReady() call. */
     if (WAYLAND_wl_display_prepare_read(d->display) == 0) {
@@ -335,8 +348,7 @@ Wayland_WaitEventTimeout(_THIS, int timeout)
         if (err > 0) {
             /* There are new events available to read */
             WAYLAND_wl_display_read_events(d->display);
-            WAYLAND_wl_display_dispatch_pending(d->display);
-            return 1;
+            return dispatch_queued_events(d);
         } else if (err == 0) {
             /* No events available within the timeout */
             WAYLAND_wl_display_cancel_read(d->display);
@@ -364,8 +376,7 @@ Wayland_WaitEventTimeout(_THIS, int timeout)
         }
     } else {
         /* We already had pending events */
-        WAYLAND_wl_display_dispatch_pending(d->display);
-        return 1;
+        return dispatch_queued_events(d);
     }
 }
 
@@ -376,14 +387,20 @@ Wayland_PumpEvents(_THIS)
     struct SDL_WaylandInput *input = d->input;
     int err;
 
-    WAYLAND_wl_display_flush(d->display);
-
 #ifdef SDL_USE_IME
     if (d->text_input_manager == NULL && SDL_GetEventState(SDL_TEXTINPUT) == SDL_ENABLE) {
         SDL_IME_PumpEvents();
     }
 #endif
 
+#ifdef HAVE_LIBDECOR_H
+    if (d->shell.libdecor) {
+        libdecor_dispatch(d->shell.libdecor, 0);
+    }
+#endif
+
+    WAYLAND_wl_display_flush(d->display);
+
     /* wl_display_prepare_read() will return -1 if the default queue is not empty.
      * If the default queue is empty, it will prepare us for our SDL_IOReady() call. */
     if (WAYLAND_wl_display_prepare_read(d->display) == 0) {
@@ -402,7 +419,7 @@ Wayland_PumpEvents(_THIS)
         keyboard_repeat_handle(&input->keyboard_repeat, elapsed);
     }
 
-    if (err == -1 && !d->display_disconnected) {
+    if (err < 0 && !d->display_disconnected) {
         /* Something has failed with the Wayland connection -- for example,
          * the compositor may have shut down and closed its end of the socket,
          * or there is a library-specific error. No recovery is possible. */