SDL: x11/wayland: Fix signal handling while blocking in WaitEventTimeout()

From a55986496891f4a285512a49e9b853c195e4e175 Mon Sep 17 00:00:00 2001
From: Cameron Gutman <[EMAIL REDACTED]>
Date: Sat, 30 Oct 2021 19:30:34 -0500
Subject: [PATCH] x11/wayland: Fix signal handling while blocking in
 WaitEventTimeout()

Add a new flag to avoid suppressing EINTR in SDL_IOReady(). Pass the
flag in WaitEventTimeout() to ensure that a SIGINT will wake up
SDL_WaitEvent() without another event coming in.
---
 src/core/unix/SDL_poll.c              |  2 +-
 src/core/unix/SDL_poll.h              |  1 +
 src/events/SDL_events.c               |  2 +-
 src/video/wayland/SDL_waylandevents.c | 18 ++++++++++++++++--
 src/video/x11/SDL_x11events.c         | 21 ++++++++++++++++-----
 5 files changed, 35 insertions(+), 9 deletions(-)

diff --git a/src/core/unix/SDL_poll.c b/src/core/unix/SDL_poll.c
index ec0a53381c..dc0abd13f1 100644
--- a/src/core/unix/SDL_poll.c
+++ b/src/core/unix/SDL_poll.c
@@ -83,7 +83,7 @@ SDL_IOReady(int fd, int flags, int timeoutMS)
         result = select(fd + 1, rfdp, wfdp, NULL, tvp);
 #endif /* HAVE_POLL */
 
-    } while ( result < 0 && errno == EINTR );
+    } while ( result < 0 && errno == EINTR && !(flags & SDL_IOR_NO_RETRY));
 
     return result;
 }
diff --git a/src/core/unix/SDL_poll.h b/src/core/unix/SDL_poll.h
index 10497d1814..7716eabb2f 100644
--- a/src/core/unix/SDL_poll.h
+++ b/src/core/unix/SDL_poll.h
@@ -28,6 +28,7 @@
 
 #define SDL_IOR_READ           0x1
 #define SDL_IOR_WRITE          0x2
+#define SDL_IOR_NO_RETRY       0x4
 
 extern int SDL_IOReady(int fd, int flags, int timeoutMS);
 
diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index 51d3bd64fd..36baef6771 100644
--- a/src/events/SDL_events.c
+++ b/src/events/SDL_events.c
@@ -827,6 +827,7 @@ SDL_WaitEventTimeout_Device(_THIS, SDL_Window *wakeup_window, SDL_Event * event,
            a) All pending events are batch processed after waking up from a wait
            b) Waiting can be completely skipped if events are already available to be pumped
            c) Periodic processing that takes place in some platform PumpEvents() functions happens
+           d) Signals received in WaitEventTimeout() are turned into SDL events
         */
         SDL_PumpEvents();
 
@@ -847,7 +848,6 @@ SDL_WaitEventTimeout_Device(_THIS, SDL_Window *wakeup_window, SDL_Event * event,
             }
             if (status > 0) {
                 /* There is an event, we can return. */
-                SDL_SendPendingSignalEvents();  /* in case we had a signal handler fire, etc. */
                 return 1;
             }
             /* No events found in the queue, call WaitEventTimeout to wait for an event. */
diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index 607c6a2f7f..f52b261fcf 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -58,6 +58,7 @@
 #include <sys/mman.h>
 #include <poll.h>
 #include <unistd.h>
+#include <errno.h>
 #include <xkbcommon/xkbcommon.h>
 #include <xkbcommon/xkbcommon-compose.h>
 #include "../../events/imKStoUCS.h"
@@ -259,12 +260,14 @@ Wayland_WaitEventTimeout(_THIS, int timeout)
     /* 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) {
-        if (SDL_IOReady(WAYLAND_wl_display_get_fd(d->display), SDL_IOR_READ, timeout) > 0) {
+        /* Use SDL_IOR_NO_RETRY to ensure SIGINT will break us out of our wait */
+        int err = SDL_IOReady(WAYLAND_wl_display_get_fd(d->display), SDL_IOR_READ | SDL_IOR_NO_RETRY, 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;
-        } else {
+        } else if (err == 0) {
             /* No events available within the timeout */
             WAYLAND_wl_display_cancel_read(d->display);
 
@@ -277,6 +280,17 @@ Wayland_WaitEventTimeout(_THIS, int timeout)
             }
 
             return 0;
+        } else {
+            /* Error returned from poll()/select() */
+            WAYLAND_wl_display_cancel_read(d->display);
+
+            if (errno == EINTR) {
+                /* If the wait was interrupted by a signal, we may have generated a
+                 * SDL_QUIT event. Let the caller know to call SDL_PumpEvents(). */
+                return 1;
+            } else {
+                return err;
+            }
         }
     } else {
         /* We already had pending events */
diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c
index ccab1fb3ad..664055614a 100644
--- a/src/video/x11/SDL_x11events.c
+++ b/src/video/x11/SDL_x11events.c
@@ -1585,14 +1585,25 @@ X11_WaitEventTimeout(_THIS, int timeout)
         } else {
             return 0;
         }
-    } else if (timeout > 0) {
-        if (SDL_IOReady(ConnectionNumber(display), SDL_IOR_READ, timeout) > 0) {
+    } else {
+        /* Use SDL_IOR_NO_RETRY to ensure SIGINT will break us out of our wait */
+        int err = SDL_IOReady(ConnectionNumber(display), SDL_IOR_READ | SDL_IOR_NO_RETRY, timeout);
+        if (err > 0) {
             X11_XNextEvent(display, &xevent);
-        } else {
+        } else if (err == 0) {
+            /* Timeout */
             return 0;
+        } else {
+            /* Error returned from poll()/select() */
+
+            if (errno == EINTR) {
+                /* If the wait was interrupted by a signal, we may have generated a
+                 * SDL_QUIT event. Let the caller know to call SDL_PumpEvents(). */
+                return 1;
+            } else {
+                return err;
+            }
         }
-    } else {
-        X11_XNextEvent(display, &xevent);
     }
 
     X11_DispatchEvent(_this, &xevent);