SDL: streamline cursor clipping logic on windows (#11237)

From 345cab1e3625d4797483bc6850195506114c371f Mon Sep 17 00:00:00 2001
From: expikr <[EMAIL REDACTED]>
Date: Thu, 19 Dec 2024 09:25:06 +0800
Subject: [PATCH] streamline cursor clipping logic on windows (#11237)

This commit does the following:
- add logic in the `WM_MOUSEMOVE` case of the Window to conditionally call `WIN_UpdateClipCursor` upon receiving cursor motion if SDL is expecting the mouse to be clipped in some way (Fixes #7890)
- remove Windows-specific periodic refresh of cursor clipping and its `SDL_HINT_MOUSE_RELATIVE_CLIP_INTERVAL` hint (superceded by the above bullet point)
- streamline the processing logic within `WIN_UpdateClipCursor` for better readability of each branch, and avoid calling the Platform API until it is absolutely necessary.
- move `relative_mouse_center` field from Windows-specific per-window `SDL_WindowData` to the global `SDL_Mouse` struct, and the corresponding hint callbacks to `SDL_mouse.c` instead of `SDL_windowswindow.c`
---
 include/SDL3/SDL_hints.h              |  17 ---
 src/events/SDL_mouse.c                |  30 ++--
 src/events/SDL_mouse_c.h              |   2 +-
 src/video/windows/SDL_windowsevents.c |  60 ++++----
 src/video/windows/SDL_windowswindow.c | 195 +++++++++++++-------------
 src/video/windows/SDL_windowswindow.h |   6 +-
 6 files changed, 144 insertions(+), 166 deletions(-)

diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index 868a93ee3e14f..1be6d0a1e69f4 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -2614,23 +2614,6 @@ extern "C" {
  */
 #define SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE "SDL_MOUSE_RELATIVE_CURSOR_VISIBLE"
 
-/**
- * Controls how often SDL issues cursor confinement commands to the operating
- * system while relative mode is active, in case the desired confinement state
- * became out-of-sync due to interference from other running programs.
- *
- * The variable can be integers representing milliseconds between each
- * refresh. A value of zero means SDL will not automatically refresh the
- * confinement. The default value varies depending on the operating system,
- * this variable might not have any effects on inapplicable platforms such as
- * those without a cursor.
- *
- * This hint can be set anytime.
- *
- * \since This hint is available since SDL 3.1.3.
- */
-#define SDL_HINT_MOUSE_RELATIVE_CLIP_INTERVAL "SDL_MOUSE_RELATIVE_CLIP_INTERVAL"
-
 /**
  * A variable controlling whether mouse events should generate synthetic touch
  * events.
diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c
index 657bad9a6c237..dea5fc0d03b6a 100644
--- a/src/events/SDL_mouse.c
+++ b/src/events/SDL_mouse.c
@@ -65,17 +65,6 @@ static void SDLCALL SDL_MouseDoubleClickTimeChanged(void *userdata, const char *
     }
 }
 
-static void SDLCALL SDL_MouseRelativeClipIntervalChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
-{
-    SDL_Mouse *mouse = (SDL_Mouse *)userdata;
-
-    if (hint && *hint) {
-        mouse->relative_mode_clip_interval = SDL_atoi(hint);
-    } else {
-        mouse->relative_mode_clip_interval = 3000;
-    }
-}
-
 static void SDLCALL SDL_MouseDoubleClickRadiusChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
 {
     SDL_Mouse *mouse = (SDL_Mouse *)userdata;
@@ -113,6 +102,13 @@ static void SDLCALL SDL_MouseRelativeSpeedScaleChanged(void *userdata, const cha
     }
 }
 
+static void SDLCALL SDL_MouseRelativeModeCenterChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
+{
+    SDL_Mouse *mouse = (SDL_Mouse *)userdata;
+    
+    mouse->relative_mode_center = SDL_GetStringBoolean(hint, true);
+}
+
 static void SDLCALL SDL_MouseRelativeSystemScaleChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
 {
     SDL_Mouse *mouse = (SDL_Mouse *)userdata;
@@ -226,6 +222,9 @@ bool SDL_PreInitMouse(void)
     SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE,
                         SDL_MouseRelativeSystemScaleChanged, mouse);
 
+    SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_MODE_CENTER,
+                        SDL_MouseRelativeModeCenterChanged, mouse);
+
     SDL_AddHintCallback(SDL_HINT_MOUSE_EMULATE_WARP_WITH_RELATIVE,
                         SDL_MouseWarpEmulationChanged, mouse);
 
@@ -249,9 +248,6 @@ bool SDL_PreInitMouse(void)
     SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE,
                         SDL_MouseRelativeCursorVisibleChanged, mouse);
 
-    SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_CLIP_INTERVAL,
-                        SDL_MouseRelativeClipIntervalChanged, mouse);
-
     mouse->was_touch_mouse_events = false; // no touch to mouse movement event pending
 
     mouse->cursor_shown = true;
@@ -1055,6 +1051,9 @@ void SDL_QuitMouse(void)
     SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE,
                         SDL_MouseRelativeSystemScaleChanged, mouse);
 
+    SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_MODE_CENTER, 
+                        SDL_MouseRelativeModeCenterChanged, mouse);
+
     SDL_RemoveHintCallback(SDL_HINT_MOUSE_EMULATE_WARP_WITH_RELATIVE,
                         SDL_MouseWarpEmulationChanged, mouse);
 
@@ -1073,9 +1072,6 @@ void SDL_QuitMouse(void)
     SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE,
                         SDL_MouseRelativeCursorVisibleChanged, mouse);
 
-    SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_CLIP_INTERVAL,
-                        SDL_MouseRelativeClipIntervalChanged, mouse);
-
     for (int i = SDL_mouse_count; i--; ) {
         SDL_RemoveMouse(SDL_mice[i].instance_id, false);
     }
diff --git a/src/events/SDL_mouse_c.h b/src/events/SDL_mouse_c.h
index d77d0a7e10bf4..5bc9a53755791 100644
--- a/src/events/SDL_mouse_c.h
+++ b/src/events/SDL_mouse_c.h
@@ -98,11 +98,11 @@ typedef struct
     bool relative_mode_warp;
     bool relative_mode_warp_motion;
     bool relative_mode_cursor_visible;
+    bool relative_mode_center;
     bool warp_emulation_hint;
     bool warp_emulation_active;
     bool warp_emulation_prohibited;
     Uint64 last_center_warp_time_ns;
-    int relative_mode_clip_interval;
     bool enable_normal_speed_scale;
     float normal_speed_scale;
     bool enable_relative_speed_scale;
diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index 6878bbe1c9ad5..15057e02adcb3 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -350,8 +350,6 @@ static void WIN_UpdateFocus(SDL_Window *window, bool expect_focus)
 
         WIN_UpdateWindowICCProfile(data->window, true);
     } else {
-        RECT rect;
-
         data->in_window_deactivation = true;
 
         SDL_SetKeyboardFocus(NULL);
@@ -361,10 +359,7 @@ static void WIN_UpdateFocus(SDL_Window *window, bool expect_focus)
         }
         WIN_ResetDeadKeys();
 
-        if (GetClipCursor(&rect) && SDL_memcmp(&rect, &data->cursor_clipped_rect, sizeof(rect)) == 0) {
-            ClipCursor(NULL);
-            SDL_zero(data->cursor_clipped_rect);
-        }
+        WIN_UnclipCursorForWindow(window);
 
         data->in_window_deactivation = false;
     }
@@ -1078,6 +1073,10 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
     case WM_NCACTIVATE:
     {
         // Don't immediately clip the cursor in case we're clicking minimize/maximize buttons
+        // This is the only place that this flag is set. This causes all subsequent calls to
+        // WIN_UpdateClipCursor for this window to be no-ops in this frame's message-pumping.
+        // This flag is unset at the end of message pumping each frame for every window, and
+        // should never be carried over between frames.
         data->skip_update_clipcursor = true;
 
         /* Update the focus here, since it's possible to get WM_ACTIVATE and WM_SETFOCUS without
@@ -1120,7 +1119,18 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
 
     case WM_MOUSEMOVE:
     {
-        /* SDL_Mouse *mouse = SDL_GetMouse(); */
+        SDL_Window *window = data->window;
+
+        if (window->flags & SDL_WINDOW_INPUT_FOCUS) {
+            bool wish_clip_cursor = (
+                window->flags & (SDL_WINDOW_MOUSE_RELATIVE_MODE | SDL_WINDOW_MOUSE_GRABBED) ||
+                (window->mouse_rect.w > 0 && window->mouse_rect.h > 0)
+            );
+            if (wish_clip_cursor) {
+                data->skip_update_clipcursor = false;
+                WIN_UpdateClipCursor(window);  
+            }
+        }
 
         if (!data->mouse_tracked) {
             TRACKMOUSEEVENT trackMouseEvent;
@@ -1138,7 +1148,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
             // Only generate mouse events for real mouse
             if (GetMouseMessageSource((ULONG)GetMessageExtraInfo()) != SDL_MOUSE_EVENT_SOURCE_TOUCH &&
                 lParam != data->last_pointer_update) {
-                SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, false, (float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam));
+                SDL_SendMouseMotion(WIN_GetEventTimestamp(), window, SDL_GLOBAL_MOUSE_ID, false, (float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam));
             }
         }
     } break;
@@ -2068,28 +2078,6 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
 }
 
 #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
-static void WIN_UpdateClipCursorForWindows(void)
-{
-    SDL_VideoDevice *_this = SDL_GetVideoDevice();
-    SDL_Window *window;
-    Uint64 now = SDL_GetTicks();
-    const int CLIPCURSOR_UPDATE_INTERVAL_MS = SDL_GetMouse()->relative_mode_clip_interval;
-
-    if (_this) {
-        for (window = _this->windows; window; window = window->next) {
-            SDL_WindowData *data = window->internal;
-            if (data) {
-                if (data->skip_update_clipcursor) {
-                    data->skip_update_clipcursor = false;
-                    WIN_UpdateClipCursor(window);
-                } else if (CLIPCURSOR_UPDATE_INTERVAL_MS > 0 && now >= (data->last_updated_clipcursor + CLIPCURSOR_UPDATE_INTERVAL_MS)) {
-                    WIN_UpdateClipCursor(window);
-                }
-            }
-        }
-    }
-}
-
 static void WIN_UpdateMouseCapture(void)
 {
     SDL_Window *focusWindow = SDL_GetKeyboardFocus();
@@ -2284,7 +2272,17 @@ void WIN_PumpEvents(SDL_VideoDevice *_this)
     }
 
     // Update the clipping rect in case someone else has stolen it
-    WIN_UpdateClipCursorForWindows();
+    if (_this) {
+        SDL_Window *window = _this->windows;
+        while (window) {
+            SDL_WindowData *data = window->internal;
+            if (data && data->skip_update_clipcursor) {
+                data->skip_update_clipcursor = false;
+                WIN_UpdateClipCursor(window);
+            }
+            window = window->next;
+        }
+    }
 
     // Update mouse capture
     WIN_UpdateMouseCapture();
diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c
index d1c8a513915d2..b7a05f6c45b83 100644
--- a/src/video/windows/SDL_windowswindow.c
+++ b/src/video/windows/SDL_windowswindow.c
@@ -386,12 +386,6 @@ bool WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags, SDL_WindowRec
     return result;
 }
 
-static void SDLCALL WIN_MouseRelativeModeCenterChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
-{
-    SDL_WindowData *data = (SDL_WindowData *)userdata;
-    data->mouse_relative_mode_center = SDL_GetStringBoolean(hint, true);
-}
-
 static SDL_WindowEraseBackgroundMode GetEraseBackgroundModeHint(void)
 {
     const char *hint = SDL_GetHint(SDL_HINT_WINDOWS_ERASE_BACKGROUND_MODE);
@@ -443,6 +437,14 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, HWND hwn
     data->dwma_border_color = DWMWA_COLOR_DEFAULT;
     data->hint_erase_background_mode = GetEraseBackgroundModeHint();
 
+
+    // WIN_WarpCursor() jitters by +1, and remote desktop warp wobble is +/- 1
+    LONG remote_desktop_adjustment = GetSystemMetrics(SM_REMOTESESSION) ? 2 : 0;
+    data->cursor_ctrlock_rect.left   = 0 - remote_desktop_adjustment;
+    data->cursor_ctrlock_rect.top    = 0;
+    data->cursor_ctrlock_rect.right  = 1 + remote_desktop_adjustment;
+    data->cursor_ctrlock_rect.bottom = 1;
+
     if (SDL_GetHintBoolean("SDL_WINDOW_RETAIN_CONTENT", false)) {
         data->copybits_flag = 0;
     } else {
@@ -453,8 +455,6 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, HWND hwn
     SDL_Log("SetupWindowData: initialized data->scaling_dpi to %d", data->scaling_dpi);
 #endif
 
-    SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_MODE_CENTER, WIN_MouseRelativeModeCenterChanged, data);
-
 #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
     // Associate the data with the window
     if (!SetProp(hwnd, TEXT("SDL_WindowData"), data)) {
@@ -626,7 +626,6 @@ static void CleanupWindowData(SDL_VideoDevice *_this, SDL_Window *window)
     SDL_WindowData *data = window->internal;
 
     if (data) {
-        SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_MODE_CENTER, WIN_MouseRelativeModeCenterChanged, data);
 
 #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
         if (data->drop_target) {
@@ -1588,107 +1587,109 @@ static BOOL GetClientScreenRect(HWND hwnd, RECT *rect)
            ClientToScreen(hwnd, (LPPOINT)rect + 1); // POINT( right , bottom )
 }
 
+void WIN_UnclipCursorForWindow(SDL_Window *window) {
+    SDL_WindowData *data = window->internal;
+    RECT rect;
+    if (GetClipCursor(&rect) && SDL_memcmp(&rect, &data->cursor_clipped_rect, sizeof(rect)) == 0) {
+        ClipCursor(NULL);
+        SDL_zero(data->cursor_clipped_rect);
+    }
+}
+
 void WIN_UpdateClipCursor(SDL_Window *window)
 {
-    SDL_VideoDevice *videodevice = SDL_GetVideoDevice();
     SDL_WindowData *data = window->internal;
-    SDL_Mouse *mouse = SDL_GetMouse();
-    RECT rect, clipped_rect;
-
-    if (data->in_title_click || data->focus_click_pending) {
-        return;
-    }
-    if (data->skip_update_clipcursor) {
+    if (data->in_title_click || data->focus_click_pending || data->skip_update_clipcursor) {
         return;
     }
-    if (!GetClipCursor(&clipped_rect)) {
+
+    SDL_Rect mouse_rect = window->mouse_rect;
+    bool win_mouse_rect = (mouse_rect.w > 0 && mouse_rect.h > 0);
+    bool win_have_focus = (window->flags & SDL_WINDOW_INPUT_FOCUS);
+    bool win_is_grabbed = (window->flags & SDL_WINDOW_MOUSE_GRABBED);
+    bool win_in_relmode = (window->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE);
+    bool cursor_confine = win_in_relmode || win_is_grabbed || win_mouse_rect;
+
+    // This is verbatim translation of the old logic,
+    // but I don't quite get what it's trying to do.
+    // A clean-room implementation according to MSDN
+    // documentation of GetClipCursor is provided in
+    // a commented-out block below.
+    if (!win_have_focus || !cursor_confine) {
+        SDL_VideoDevice *videodevice = SDL_GetVideoDevice();
+        RECT current;
+        if (!GetClipCursor(&current)) {
+            return;
+        }
+        if (videodevice && (
+            current.left != videodevice->desktop_bounds.x ||
+            current.top  != videodevice->desktop_bounds.y
+        )) {
+            POINT first, second;
+            first.x  = current.left;
+            first.y  = current.top;
+            second.x = current.right  - 1;
+            second.y = current.bottom - 1;
+            if (!PtInRect(&data->cursor_clipped_rect, first) ||
+                !PtInRect(&data->cursor_clipped_rect, second)) {
+                return;
+            }
+        }
+        ClipCursor(NULL);
+        SDL_zero(data->cursor_clipped_rect);
         return;
     }
 
-    if ((mouse->relative_mode || (window->flags & SDL_WINDOW_MOUSE_GRABBED) ||
-         (window->mouse_rect.w > 0 && window->mouse_rect.h > 0)) &&
-        (window->flags & SDL_WINDOW_INPUT_FOCUS)) {
-        if (mouse->relative_mode && !mouse->relative_mode_warp && data->mouse_relative_mode_center) {
-            if (GetClientScreenRect(data->hwnd, &rect)) {
-                // WIN_WarpCursor() jitters by +1, and remote desktop warp wobble is +/- 1
-                LONG remote_desktop_adjustment = GetSystemMetrics(SM_REMOTESESSION) ? 2 : 0;
-                LONG cx, cy;
-
-                cx = (rect.left + rect.right) / 2;
-                cy = (rect.top + rect.bottom) / 2;
+    // if (!win_have_focus || !cursor_confine) {
+    //     RECT current;
+    //     SDL_VideoDevice *videodevice = SDL_GetVideoDevice();
+    //     if (GetClipCursor(&current) && (!videodevice ||
+    //         current.left   != videodevice->desktop_bounds.x ||
+    //         current.top    != videodevice->desktop_bounds.y ||
+    //         current.right  != videodevice->desktop_bounds.x + videodevice->desktop_bounds.w ||
+    //         current.bottom != videodevice->desktop_bounds.y + videodevice->desktop_bounds.h )) {
+    //         ClipCursor(NULL);
+    //         SDL_zero(data->cursor_clipped_rect);
+    //     }
+    //     return;
+    // }
 
-                // Make an absurdly small clip rect
-                rect.left = cx - remote_desktop_adjustment;
-                rect.right = cx + 1 + remote_desktop_adjustment;
-                rect.top = cy;
-                rect.bottom = cy + 1;
+    SDL_Mouse *mouse = SDL_GetMouse();
+    bool lock_to_ctr = (mouse->relative_mode_center && mouse->relative_mode && !mouse->relative_mode_warp);
 
-                if (SDL_memcmp(&rect, &clipped_rect, sizeof(rect)) != 0) {
-                    if (ClipCursor(&rect)) {
-                        data->cursor_clipped_rect = rect;
-                    }
-                }
-            }
-        } else {
-            if (GetClientScreenRect(data->hwnd, &rect)) {
-                if (window->mouse_rect.w > 0 && window->mouse_rect.h > 0) {
-                    SDL_Rect mouse_rect_win_client;
-                    RECT mouse_rect, intersection;
-
-                    // mouse_rect_win_client is the mouse rect in Windows client space
-                    mouse_rect_win_client = window->mouse_rect;
-
-                    // mouse_rect is the rect in Windows screen space
-                    mouse_rect.left = rect.left + mouse_rect_win_client.x;
-                    mouse_rect.top = rect.top + mouse_rect_win_client.y;
-                    mouse_rect.right = mouse_rect.left + mouse_rect_win_client.w;
-                    mouse_rect.bottom = mouse_rect.top + mouse_rect_win_client.h;
-                    if (IntersectRect(&intersection, &rect, &mouse_rect)) {
-                        SDL_memcpy(&rect, &intersection, sizeof(rect));
-                    } else if (window->flags & SDL_WINDOW_MOUSE_GRABBED) {
-                        // Mouse rect was invalid, just do the normal grab
-                    } else {
-                        SDL_zero(rect);
-                    }
-                }
-                if (SDL_memcmp(&rect, &clipped_rect, sizeof(rect)) != 0) {
-                    if (!WIN_IsRectEmpty(&rect)) {
-                        if (ClipCursor(&rect)) {
-                            data->cursor_clipped_rect = rect;
-                        }
-                    } else {
-                        ClipCursor(NULL);
-                        SDL_zero(data->cursor_clipped_rect);
-                    }
-                }
-            }
+    RECT client;
+    if (!GetClientScreenRect(data->hwnd, &client)) {
+        return;
+    }
+    
+    RECT target = client;
+    if (lock_to_ctr) {
+        LONG cx = (client.left + client.right ) / 2;
+        LONG cy = (client.top  + client.bottom) / 2;
+        target = data->cursor_ctrlock_rect;
+        target.left   += cx;
+        target.right  += cx;
+        target.top    += cy;
+        target.bottom += cy;
+    } else if (win_mouse_rect) {
+        RECT custom, overlap;
+        custom.left   = client.left + mouse_rect.x;
+        custom.top    = client.top  + mouse_rect.y;
+        custom.right  = client.left + mouse_rect.x + mouse_rect.w;
+        custom.bottom = client.top  + mouse_rect.y + mouse_rect.h;
+        if (IntersectRect(&overlap, &client, &custom)) {
+            target = overlap;
+        } else if (!win_is_grabbed) {
+            WIN_UnclipCursorForWindow(window);
+            return;
         }
-    } else {
-        bool unclip_cursor = false;
-
-        // If the cursor is clipped to the screen, clear the clip state
-        if (!videodevice ||
-            (clipped_rect.left == videodevice->desktop_bounds.x &&
-             clipped_rect.top == videodevice->desktop_bounds.y)) {
-            unclip_cursor = true;
-        } else {
-            POINT first, second;
+    }
 
-            first.x = clipped_rect.left;
-            first.y = clipped_rect.top;
-            second.x = clipped_rect.right - 1;
-            second.y = clipped_rect.bottom - 1;
-            if (PtInRect(&data->cursor_clipped_rect, first) &&
-                PtInRect(&data->cursor_clipped_rect, second)) {
-                unclip_cursor = true;
-            }
-        }
-        if (unclip_cursor) {
-            ClipCursor(NULL);
-            SDL_zero(data->cursor_clipped_rect);
-        }
+    if (GetClipCursor(&client) && 
+        0 != SDL_memcmp(&target, &client, sizeof(client)) &&
+        ClipCursor(&target)) {
+        data->cursor_clipped_rect = target; // ClipCursor may fail if rect beyond screen
     }
-    data->last_updated_clipcursor = SDL_GetTicks();
 }
 
 bool WIN_SetWindowHitTest(SDL_Window *window, bool enabled)
diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h
index 9814d10acf455..ddc3ddd6d9797 100644
--- a/src/video/windows/SDL_windowswindow.h
+++ b/src/video/windows/SDL_windowswindow.h
@@ -79,11 +79,10 @@ struct SDL_WindowData
     bool in_title_click;
     Uint8 focus_click_pending;
     bool skip_update_clipcursor;
-    Uint64 last_updated_clipcursor;
-    bool mouse_relative_mode_center;
     bool windowed_mode_was_maximized;
     bool in_window_deactivation;
-    RECT cursor_clipped_rect;
+    RECT cursor_clipped_rect; // last successfully committed clipping rect for this window
+    RECT cursor_ctrlock_rect; // this is Windows-specific, but probably does not need to be per-window
     UINT windowed_mode_corner_rounding;
     COLORREF dwma_border_color;
     bool mouse_tracked;
@@ -128,6 +127,7 @@ extern bool WIN_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window
 extern void WIN_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window);
 extern void WIN_OnWindowEnter(SDL_VideoDevice *_this, SDL_Window *window);
 extern void WIN_UpdateClipCursor(SDL_Window *window);
+extern void WIN_UnclipCursorForWindow(SDL_Window *window);
 extern bool WIN_SetWindowHitTest(SDL_Window *window, bool enabled);
 extern void WIN_AcceptDragAndDrop(SDL_Window *window, bool accept);
 extern bool WIN_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation);