SDL: Fix weak enforcement of timeouts in SDL_WaitEventTimeout_Device. This will loop pumping events and waiting for a system...

From 1fa154fda3b2a811e95478cd39572b7c306b5ae6 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 13 Oct 2021 09:33:54 -0700
Subject: [PATCH] Fix weak enforcement of timeouts in
 SDL_WaitEventTimeout_Device.  This will loop pumping events and waiting for a
 system event to come in.  However not all system events will turn into an SDL
 event.  It's not unusual for a Windows message to be some internal thing that
 SDL doesn't convert into a message.  In that case the loop will simple circle
 but not exit.  As long as such messages are coming in the loop will continue
 to run regardless of the timeout.  When messages finally stop it'll still
 wait for the full timeout so you can have arbitrarily long delays.

Instead do an absolute elapsed time check since the start of the wait.  If that is exceeded during any iteration the routine exits as the timeout has elapsed.
---
 src/events/SDL_events.c | 24 +++++++++++++++++++-----
 1 file changed, 19 insertions(+), 5 deletions(-)

diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index 771c0a605f..599ec760a5 100644
--- a/src/events/SDL_events.c
+++ b/src/events/SDL_events.c
@@ -786,8 +786,10 @@ SDL_PollEvent(SDL_Event * event)
 }
 
 static int
-SDL_WaitEventTimeout_Device(_THIS, SDL_Window *wakeup_window, SDL_Event * event, int timeout)
+SDL_WaitEventTimeout_Device(_THIS, SDL_Window *wakeup_window, SDL_Event * event, Uint32 start, int timeout)
 {
+	int loop_timeout = timeout;
+
     for (;;) {
         /* Pump events on entry and each time we wake to ensure:
            a) All pending events are batch processed after waking up from a wait
@@ -817,7 +819,16 @@ SDL_WaitEventTimeout_Device(_THIS, SDL_Window *wakeup_window, SDL_Event * event,
                 return 1;
             }
             /* No events found in the queue, call WaitEventTimeout to wait for an event. */
-            status = _this->WaitEventTimeout(_this, timeout);
+			if (timeout > 0) {
+				Uint32 elapsed = SDL_GetTicks() - start;
+				if (elapsed >= (Uint32)timeout) {
+					/* Set wakeup_window to NULL without holding the lock. */
+					_this->wakeup_window = NULL;
+					return 0;
+				}
+				loop_timeout = (int)((Uint32)timeout - elapsed);
+			}
+            status = _this->WaitEventTimeout(_this, loop_timeout);
             /* Set wakeup_window to NULL without holding the lock. */
             _this->wakeup_window = NULL;
             if (status <= 0) {
@@ -872,16 +883,19 @@ SDL_WaitEventTimeout(SDL_Event * event, int timeout)
 {
     SDL_VideoDevice *_this = SDL_GetVideoDevice();
     SDL_Window *wakeup_window;
+	Uint32 start = 0;
     Uint32 expiration = 0;
 
-    if (timeout > 0)
-        expiration = SDL_GetTicks() + timeout;
+    if (timeout > 0) {
+		start = SDL_GetTicks();
+        expiration = start + timeout;
+	}
 
     if (timeout != 0 && _this && _this->WaitEventTimeout && _this->SendWakeupEvent && !SDL_events_need_polling()) {
         /* Look if a shown window is available to send the wakeup event. */
         wakeup_window = SDL_find_active_window(_this);
         if (wakeup_window) {
-            int status = SDL_WaitEventTimeout_Device(_this, wakeup_window, event, timeout);
+            int status = SDL_WaitEventTimeout_Device(_this, wakeup_window, event, start, timeout);
 
             /* There may be implementation-defined conditions where the backend cannot
                reliably wait for the next event. If that happens, fall back to polling. */