SDL: Added SDL_GetWindowMouseRect()

From fd79607eb077d364b5e46b1e48436cf29c86eee9 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 8 Nov 2021 21:34:48 -0800
Subject: [PATCH] Added SDL_GetWindowMouseRect()

Also guarantee that we won't get mouse movement outside the confining area, even if the OS implementation allows it (e.g. macOS)
---
 include/SDL_video.h                   | 47 +++++++++++++++++----------
 src/dynapi/SDL_dynapi_overrides.h     |  1 +
 src/dynapi/SDL_dynapi_procs.h         |  1 +
 src/events/SDL_mouse.c                | 28 +++++++++++++---
 src/video/SDL_sysvideo.h              |  4 ++-
 src/video/SDL_video.c                 | 25 ++++++++++++--
 src/video/cocoa/SDL_cocoawindow.h     |  3 +-
 src/video/cocoa/SDL_cocoawindow.m     | 21 +++---------
 src/video/windows/SDL_windowswindow.c | 17 +++-------
 src/video/windows/SDL_windowswindow.h |  3 +-
 src/video/x11/SDL_x11events.c         |  4 +--
 src/video/x11/SDL_x11xfixes.c         | 21 +++++++++---
 src/video/x11/SDL_x11xfixes.h         |  2 +-
 13 files changed, 111 insertions(+), 66 deletions(-)

diff --git a/include/SDL_video.h b/include/SDL_video.h
index 5f4f4f55fe..fc462d76d8 100644
--- a/include/SDL_video.h
+++ b/include/SDL_video.h
@@ -1381,6 +1381,36 @@ extern DECLSPEC SDL_bool SDLCALL SDL_GetWindowMouseGrab(SDL_Window * window);
  */
 extern DECLSPEC SDL_Window * SDLCALL SDL_GetGrabbedWindow(void);
 
+/**
+ * Confines the cursor to the specified area of a window.
+ *
+ * Note that this does NOT grab the cursor, it only defines the area a cursor
+ * is restricted to when the window has mouse focus.
+ *
+ * \param window The window that will be associated with the barrier.
+ * \param rect A rectangle area in window-relative coordinates. If NULL the
+ *  barrier for the specified window will be destroyed.
+ *
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \sa SDL_GetWindowMouseRect
+ * \sa SDL_SetWindowGrab
+ */
+extern DECLSPEC int SDLCALL SDL_SetWindowMouseRect(SDL_Window * window, const SDL_Rect * rect);
+
+/**
+ * Get the mouse confinement rectangle of a window.
+ *
+ * \param window The window to query
+ *
+ * \returns A pointer to the mouse confinement rectangle of a window,
+ *          or NULL if there isn't one.
+ *
+ * \sa SDL_SetWindowMouseRect
+ */
+extern DECLSPEC const SDL_Rect * SDLCALL SDL_GetWindowMouseRect(SDL_Window * window);
+
 /**
  * Set the brightness (gamma multiplier) for a given window's display.
  *
@@ -1497,23 +1527,6 @@ extern DECLSPEC int SDLCALL SDL_SetWindowModalFor(SDL_Window * modal_window, SDL
  */
 extern DECLSPEC int SDLCALL SDL_SetWindowInputFocus(SDL_Window * window);
 
-/**
- * Confines the cursor in the specified rect area of a window.
- *
- * Note that this does NOT grab the cursor, it only defines the area a cursor
- * is restricted to when the window has mouse focus.
- *
- * \param window The window that will be associated with the barrier.
- * \param rect A rectangle area in window-relative coordinates. If NULL the
- *  barrier for the specified window will be destroyed.
- *
- * \returns 0 on success or a negative error code on failure; call
- *          SDL_GetError() for more information.
- *
- * \sa SDL_SetWindowGrab
- */
-extern DECLSPEC int SDLCALL SDL_SetWindowMouseRect(SDL_Window * window, const SDL_Rect * rect);
-
 /**
  * Set the gamma ramp for the display that owns a given window.
  *
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index fcad9ba510..20d9e702ed 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -843,3 +843,4 @@
 #define SDL_hid_get_serial_number_string SDL_hid_get_serial_number_string_REAL
 #define SDL_hid_get_indexed_string SDL_hid_get_indexed_string_REAL
 #define SDL_SetWindowMouseRect SDL_SetWindowMouseRect_REAL
+#define SDL_GetWindowMouseRect SDL_GetWindowMouseRect_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 1a780c80aa..d2c1d13d27 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -912,3 +912,4 @@ SDL_DYNAPI_PROC(int,SDL_hid_get_product_string,(SDL_hid_device *a, wchar_t *b, s
 SDL_DYNAPI_PROC(int,SDL_hid_get_serial_number_string,(SDL_hid_device *a, wchar_t *b, size_t c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_hid_get_indexed_string,(SDL_hid_device *a, int b, wchar_t *c, size_t d),(a,b,c,d),return)
 SDL_DYNAPI_PROC(int,SDL_SetWindowMouseRect,(SDL_Window *a, const SDL_Rect *b),(a,b),return)
+SDL_DYNAPI_PROC(const SDL_Rect*,SDL_GetWindowMouseRect,(SDL_Window *a),(a),return)
diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c
index fb8e362274..45fb092f76 100644
--- a/src/events/SDL_mouse.c
+++ b/src/events/SDL_mouse.c
@@ -416,24 +416,42 @@ SDL_PrivateSendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relativ
     /* make sure that the pointers find themselves inside the windows,
        unless we have the mouse captured. */
     if (window && ((window->flags & SDL_WINDOW_MOUSE_CAPTURE) == 0)) {
-        int x_max = 0, y_max = 0;
+        int x_min = 0, x_max = 0;
+        int y_min = 0, y_max = 0;
+        const SDL_Rect *confine = SDL_GetWindowMouseRect(window);
 
         SDL_GetWindowSize(window, &x_max, &y_max);
         --x_max;
         --y_max;
 
+        if (confine) {
+            SDL_Rect window_rect;
+            SDL_Rect mouse_rect;
+
+            window_rect.x = 0;
+            window_rect.y = 0;
+            window_rect.w = x_max + 1;
+            window_rect.h = y_max + 1;
+            if (SDL_IntersectRect(confine, &window_rect, &mouse_rect)) {
+                x_min = mouse_rect.x;
+                y_min = mouse_rect.y;
+                x_max = x_min + mouse_rect.w - 1;
+                y_min = y_min + mouse_rect.h - 1;
+            }
+        }
+
         if (mouse->x > x_max) {
             mouse->x = x_max;
         }
-        if (mouse->x < 0) {
-            mouse->x = 0;
+        if (mouse->x < x_min) {
+            mouse->x = x_min;
         }
 
         if (mouse->y > y_max) {
             mouse->y = y_max;
         }
-        if (mouse->y < 0) {
-            mouse->y = 0;
+        if (mouse->y < y_min) {
+            mouse->y = y_min;
         }
     }
 
diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h
index ae29dd3fc0..d57731eda4 100644
--- a/src/video/SDL_sysvideo.h
+++ b/src/video/SDL_sysvideo.h
@@ -103,6 +103,8 @@ struct SDL_Window
     SDL_bool is_destroying;
     SDL_bool is_dropping;       /* drag/drop in progress, expecting SDL_SendDropComplete(). */
 
+    SDL_Rect mouse_rect;
+
     SDL_WindowShaper *shaper;
 
     SDL_HitTest hit_test;
@@ -235,7 +237,7 @@ struct SDL_VideoDevice
     int (*SetWindowGammaRamp) (_THIS, SDL_Window * window, const Uint16 * ramp);
     int (*GetWindowGammaRamp) (_THIS, SDL_Window * window, Uint16 * ramp);
     void* (*GetWindowICCProfile) (_THIS, SDL_Window * window, size_t* size);
-    int (*SetWindowMouseRect)(_THIS, SDL_Window * window, const SDL_Rect * rect);
+    void (*SetWindowMouseRect)(_THIS, SDL_Window * window);
     void (*SetWindowMouseGrab) (_THIS, SDL_Window * window, SDL_bool grabbed);
     void (*SetWindowKeyboardGrab) (_THIS, SDL_Window * window, SDL_bool grabbed);
     void (*DestroyWindow) (_THIS, SDL_Window * window);
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index b6acc670d8..516c183f03 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -2836,10 +2836,29 @@ int
 SDL_SetWindowMouseRect(SDL_Window * window, const SDL_Rect * rect)
 {
     CHECK_WINDOW_MAGIC(window, -1);
-    if (!_this->SetWindowMouseRect) {
-        return SDL_Unsupported();
+
+    if (rect) {
+        SDL_memcpy(&window->mouse_rect, rect, sizeof(*rect));
+    } else {
+        SDL_zero(window->mouse_rect);
+    }
+
+    if (_this->SetWindowMouseRect) {
+        _this->SetWindowMouseRect(_this, window);
+    }
+    return 0;
+}
+
+const SDL_Rect *
+SDL_GetWindowMouseRect(SDL_Window * window)
+{
+    CHECK_WINDOW_MAGIC(window, NULL);
+
+    if (SDL_RectEmpty(&window->mouse_rect)) {
+        return NULL;
+    } else {
+        return &window->mouse_rect;
     }
-    return _this->SetWindowMouseRect(_this, window, rect);
 }
 
 int
diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h
index f7b3626aa4..489db169ff 100644
--- a/src/video/cocoa/SDL_cocoawindow.h
+++ b/src/video/cocoa/SDL_cocoawindow.h
@@ -124,7 +124,6 @@ struct SDL_WindowData
     SDL_bool created;
     SDL_bool inWindowFullscreenTransition;
     NSInteger flash_request;
-    SDL_Rect mouse_rect;
     Cocoa_WindowListener *listener;
     struct SDL_VideoData *videodata;
 #if SDL_VIDEO_OPENGL_EGL
@@ -155,7 +154,7 @@ extern void Cocoa_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDispl
 extern int Cocoa_SetWindowGammaRamp(_THIS, SDL_Window * window, const Uint16 * ramp);
 extern void* Cocoa_GetWindowICCProfile(_THIS, SDL_Window * window, size_t * size);
 extern int Cocoa_GetWindowGammaRamp(_THIS, SDL_Window * window, Uint16 * ramp);
-extern int Cocoa_SetWindowMouseRect(_THIS, SDL_Window * window, const SDL_Rect * rect);
+extern void Cocoa_SetWindowMouseRect(_THIS, SDL_Window * window);
 extern void Cocoa_SetWindowMouseGrab(_THIS, SDL_Window * window, SDL_bool grabbed);
 extern void Cocoa_DestroyWindow(_THIS, SDL_Window * window);
 extern SDL_bool Cocoa_GetWindowWMInfo(_THIS, SDL_Window * window, struct SDL_SysWMinfo *info);
diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m
index 6f82109900..248607106a 100644
--- a/src/video/cocoa/SDL_cocoawindow.m
+++ b/src/video/cocoa/SDL_cocoawindow.m
@@ -335,7 +335,7 @@ static void ConvertNSRect(NSScreen *screen, BOOL fullscreen, NSRect *r)
         return SDL_FALSE;
     }
 
-    if ((window->flags & SDL_WINDOW_MOUSE_GRABBED) || (data->mouse_rect.w > 0 && data->mouse_rect.h > 0)) {
+    if ((window->flags & SDL_WINDOW_MOUSE_GRABBED) || (window->mouse_rect.w > 0 && window->mouse_rect.h > 0)) {
         return SDL_TRUE;
     }
     return SDL_FALSE;
@@ -344,9 +344,7 @@ static void ConvertNSRect(NSScreen *screen, BOOL fullscreen, NSRect *r)
 static SDL_bool
 AdjustCoordinatesForGrab(SDL_Window * window, int x, int y, CGPoint *adjusted)
 {
-    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
-
-    if (data->mouse_rect.w > 0 && data->mouse_rect.h > 0) {
+    if (window->mouse_rect.w > 0 && window->mouse_rect.h > 0) {
         SDL_Rect window_rect;
         SDL_Rect mouse_rect;
 
@@ -355,7 +353,7 @@ static void ConvertNSRect(NSScreen *screen, BOOL fullscreen, NSRect *r)
         window_rect.w = window->w;
         window_rect.h = window->h;
 
-        if (SDL_IntersectRect(&data->mouse_rect, &window_rect, &mouse_rect)) {
+        if (SDL_IntersectRect(&window->mouse_rect, &window_rect, &mouse_rect)) {
             int left = window->x + mouse_rect.x;
             int right = left + mouse_rect.w - 1;
             int top = window->y + mouse_rect.y;
@@ -2093,17 +2091,9 @@ - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
     return 0;
 }
 
-int
-Cocoa_SetWindowMouseRect(_THIS, SDL_Window * window, const SDL_Rect * rect)
+void
+Cocoa_SetWindowMouseRect(_THIS, SDL_Window * window)
 {
-    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
-
-    if (rect) {
-        SDL_memcpy(&data->mouse_rect, rect, sizeof(*rect));
-    } else {
-        SDL_zero(data->mouse_rect);
-    }
-
     /* Move the cursor to the nearest point in the mouse rect */
     if (ShouldAdjustCoordinatesForGrab(window)) {
         int x, y;
@@ -2115,7 +2105,6 @@ - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
             CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
         }
     }
-    return 0;
 }
 
 void
diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c
index 2e1a52578d..7518a2ad80 100644
--- a/src/video/windows/SDL_windowswindow.c
+++ b/src/video/windows/SDL_windowswindow.c
@@ -814,19 +814,10 @@ void WIN_UngrabKeyboard(SDL_Window *window)
     }
 }
 
-int
-WIN_SetWindowMouseRect(_THIS, SDL_Window * window, const SDL_Rect * rect)
+void
+WIN_SetWindowMouseRect(_THIS, SDL_Window * window)
 {
-    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
-
-    if (rect) {
-        SDL_memcpy(&data->mouse_rect, rect, sizeof(*rect));
-    } else {
-        SDL_zero(data->mouse_rect);
-    }
     WIN_UpdateClipCursor(window);
-
-    return 0;
 }
 
 void
@@ -1014,7 +1005,7 @@ WIN_UpdateClipCursor(SDL_Window *window)
     }
 
     if ((mouse->relative_mode || (window->flags & SDL_WINDOW_MOUSE_GRABBED) ||
-         (data->mouse_rect.w > 0 && data->mouse_rect.h > 0)) &&
+         (window->mouse_rect.w > 0 && window->mouse_rect.h > 0)) &&
         (window->flags & SDL_WINDOW_INPUT_FOCUS)) {
         if (mouse->relative_mode && !mouse->relative_mode_warp) {
             if (GetWindowRect(data->hwnd, &rect)) {
@@ -1039,7 +1030,7 @@ WIN_UpdateClipCursor(SDL_Window *window)
             if (GetClientRect(data->hwnd, &rect)) {
                 ClientToScreen(data->hwnd, (LPPOINT) & rect);
                 ClientToScreen(data->hwnd, (LPPOINT) & rect + 1);
-                if (data->mouse_rect.w > 0 && data->mouse_rect.h > 0) {
+                if (window->mouse_rect.w > 0 && window->mouse_rect.h > 0) {
                     RECT mouse_rect, intersection;
 
                     mouse_rect.left = rect.left + data->mouse_rect.x;
diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h
index 3c341d5073..b3715cca38 100644
--- a/src/video/windows/SDL_windowswindow.h
+++ b/src/video/windows/SDL_windowswindow.h
@@ -51,7 +51,6 @@ typedef struct
     Uint32 last_updated_clipcursor;
     SDL_bool windowed_mode_was_maximized;
     SDL_bool in_window_deactivation;
-    SDL_Rect mouse_rect;
     RECT cursor_clipped_rect;
     SDL_Point last_raw_mouse_position;
     SDL_bool mouse_tracked;
@@ -82,7 +81,7 @@ extern void WIN_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay
 extern int WIN_SetWindowGammaRamp(_THIS, SDL_Window * window, const Uint16 * ramp);
 extern void* WIN_GetWindowICCProfile(_THIS, SDL_Window * window, size_t * size);
 extern int WIN_GetWindowGammaRamp(_THIS, SDL_Window * window, Uint16 * ramp);
-extern int WIN_SetWindowMouseRect(_THIS, SDL_Window * window, const SDL_Rect * rect);
+extern void WIN_SetWindowMouseRect(_THIS, SDL_Window * window);
 extern void WIN_SetWindowMouseGrab(_THIS, SDL_Window * window, SDL_bool grabbed);
 extern void WIN_SetWindowKeyboardGrab(_THIS, SDL_Window * window, SDL_bool grabbed);
 extern void WIN_DestroyWindow(_THIS, SDL_Window * window);
diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c
index 57c498d3fc..1bf10d5670 100644
--- a/src/video/x11/SDL_x11events.c
+++ b/src/video/x11/SDL_x11events.c
@@ -1079,7 +1079,7 @@ X11_DispatchEvent(_THIS, XEvent *xevent)
             }
 
 #if SDL_VIDEO_DRIVER_X11_XFIXES
-            /* Disable confiment if the window gets hidden. */
+            /* Disable confinement if the window gets hidden. */
             if (data->pointer_barrier_active == SDL_TRUE) {
                 X11_ConfineCursorWithFlags(_this, data->window, NULL, X11_BARRIER_HANDLED_BY_EVENT);
             }
@@ -1095,7 +1095,7 @@ X11_DispatchEvent(_THIS, XEvent *xevent)
             X11_DispatchMapNotify(data);
 
 #if SDL_VIDEO_DRIVER_X11_XFIXES
-            /* Enable confiment if it was activated. */
+            /* Enable confinement if it was activated. */
             if (data->pointer_barrier_active == SDL_TRUE) {
                 X11_ConfineCursorWithFlags(_this, data->window, &data->barrier_rect, X11_BARRIER_HANDLED_BY_EVENT);
             }
diff --git a/src/video/x11/SDL_x11xfixes.c b/src/video/x11/SDL_x11xfixes.c
index 45d0c6177c..40850dd69b 100644
--- a/src/video/x11/SDL_x11xfixes.c
+++ b/src/video/x11/SDL_x11xfixes.c
@@ -73,10 +73,23 @@ X11_XfixesIsInitialized()
     return xfixes_initialized;
 }
 
-int
-X11_SetWindowMouseRect(_THIS, SDL_Window * window, const SDL_Rect * rect)
+void
+X11_SetWindowMouseRect(_THIS, SDL_Window * window)
 {
-    return X11_ConfineCursorWithFlags(_this, window, rect, 0);
+    if (SDL_RectEmpty(window->mouse_rect)) {
+        X11_ConfineCursorWithFlags(_this, window, NULL, 0);
+    } else {
+        if (window->flags & SDL_WINDOW_INPUT_FOCUS) {
+            X11_ConfineCursorWithFlags(_this, window, &window->mouse_rect, 0);
+        } else {
+            /* Save the state for when we get focus again */
+            SDL_WindowData *wdata = (SDL_WindowData *) window->driverdata;
+
+            SDL_memcpy(&wdata->barrier_rect, &window->mouse_rect, sizeof(wdata->barrier_rect));
+
+            wdata->pointer_barrier_active = SDL_TRUE;
+        }
+    }
 }
 
 int
@@ -102,7 +115,7 @@ X11_ConfineCursorWithFlags(_THIS, SDL_Window * window, const SDL_Rect * rect, in
     wdata = (SDL_WindowData *) window->driverdata;
 
     /* If user did not specify an area to confine, destroy the barrier that was/is assigned to
-     * this window it was assigned*/
+     * this window it was assigned */
     if (rect) {
         int x1, y1, x2, y2;
         SDL_Rect bounds;
diff --git a/src/video/x11/SDL_x11xfixes.h b/src/video/x11/SDL_x11xfixes.h
index e9a464366a..5f1c169183 100644
--- a/src/video/x11/SDL_x11xfixes.h
+++ b/src/video/x11/SDL_x11xfixes.h
@@ -30,7 +30,7 @@
 
 extern void X11_InitXfixes(_THIS);
 extern int X11_XfixesIsInitialized(void);
-extern int X11_SetWindowMouseRect(_THIS, SDL_Window * window, const SDL_Rect * rect);
+extern void X11_SetWindowMouseRect(_THIS, SDL_Window * window);
 extern int X11_ConfineCursorWithFlags(_THIS, SDL_Window * window, const SDL_Rect * rect, int flags);
 extern void X11_DestroyPointerBarrier(_THIS, SDL_Window * window);