SDL: Workaround for Windows occasionally ignoring SetCursorPos() calls

From 40ed9f75c9e1ed1dd99ee699ff4f678438ac3662 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 8 Oct 2021 10:05:27 -0700
Subject: [PATCH] Workaround for Windows occasionally ignoring SetCursorPos()
 calls

Also, since we're flushing mouse motion before and including the warp, we don't need the isWin10FCUorNewer hack to simulate mouse warp motion.

Fixes https://github.com/libsdl-org/SDL/issues/4339 and https://github.com/libsdl-org/SDL/issues/4165
---
 src/video/windows/SDL_windowsevents.c | 21 +--------------------
 src/video/windows/SDL_windowsmouse.c  | 23 +++++++++++++++++------
 src/video/windows/SDL_windowsmouse.h  |  1 +
 3 files changed, 19 insertions(+), 26 deletions(-)

diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index a099fa6319..00ef203c00 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -477,11 +477,6 @@ ShouldGenerateWindowCloseOnAltF4(void)
     return !SDL_GetHintBoolean(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, SDL_FALSE);
 }
 
-/* Win10 "Fall Creators Update" introduced the bug that SetCursorPos() (as used by SDL_WarpMouseInWindow())
-   doesn't reliably generate WM_MOUSEMOVE events anymore (see #3931) which breaks relative mouse mode via warping.
-   This is used to implement a workaround.. */
-static SDL_bool isWin10FCUorNewer = SDL_FALSE;
-
 /* We want to generate mouse events from mouse and pen, and touch events from touchscreens */
 #define MI_WP_SIGNATURE         0xFF515700
 #define MI_WP_SIGNATURE_MASK    0xFFFFFF00
@@ -581,7 +576,7 @@ WarpWithinBoundsRect(int x, int y, RECT *bounds)
             } else {
                 warpY = SDL_clamp(y, targetTop, targetBottom);
             }
-            SetCursorPos(warpX, warpY);
+            WIN_SetCursorPos(warpX, warpY);
         }
     }
 }
@@ -752,18 +747,6 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
                 if (GetMouseMessageSource() != SDL_MOUSE_EVENT_SOURCE_TOUCH &&
                     lParam != data->last_pointer_update) {
                     SDL_SendMouseMotion(data->window, 0, 0, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
-                    if (isWin10FCUorNewer && mouse->relative_mode_warp &&
-                        (data->window->flags & SDL_WINDOW_INPUT_FOCUS) != 0) {
-                        /* To work around #3931, Win10 bug introduced in Fall Creators Update, where
-                           SetCursorPos() (SDL_WarpMouseInWindow()) doesn't reliably generate mouse events anymore,
-                           after each windows mouse event generate a fake event for the middle of the window
-                           if relative_mode_warp is used */
-                        int center_x = 0, center_y = 0;
-                        SDL_GetWindowSize(data->window, &center_x, &center_y);
-                        center_x /= 2;
-                        center_y /= 2;
-                        SDL_SendMouseMotion(data->window, 0, 0, center_x, center_y);
-                    }
                 }
             }
         }
@@ -1703,8 +1686,6 @@ SDL_RegisterApp(char *name, Uint32 style, void *hInst)
         return SDL_SetError("Couldn't register application class");
     }
 
-    isWin10FCUorNewer = IsWin10FCUorNewer();
-
     app_registered = 1;
     return 0;
 }
diff --git a/src/video/windows/SDL_windowsmouse.c b/src/video/windows/SDL_windowsmouse.c
index dd34bdcf78..a43ea45b79 100644
--- a/src/video/windows/SDL_windowsmouse.c
+++ b/src/video/windows/SDL_windowsmouse.c
@@ -236,6 +236,21 @@ WIN_ShowCursor(SDL_Cursor * cursor)
     return 0;
 }
 
+void
+WIN_SetCursorPos(int x, int y)
+{
+    /* We need to jitter the value because otherwise Windows will occasionally inexplicably ignore the SetCursorPos() or SendInput() */
+    SetCursorPos(x, y);
+    SetCursorPos(x+1, y);
+    SetCursorPos(x, y);
+
+    /* Flush any mouse motion prior to or associated with this warp */
+    SDL_last_warp_time = GetTickCount();
+    if (!SDL_last_warp_time) {
+        SDL_last_warp_time = 1;
+    }
+}
+
 static void
 WIN_WarpMouse(SDL_Window * window, int x, int y)
 {
@@ -251,13 +266,9 @@ WIN_WarpMouse(SDL_Window * window, int x, int y)
     pt.x = x;
     pt.y = y;
     ClientToScreen(hwnd, &pt);
-    SetCursorPos(pt.x, pt.y);
+    WIN_SetCursorPos(pt.x, pt.y);
 
-    /* Flush any pending mouse motion and simulate motion for this warp */
-    SDL_last_warp_time = GetTickCount();
-    if (!SDL_last_warp_time) {
-        SDL_last_warp_time = 1;
-    }
+    /* Send the exact mouse motion associated with this warp */
     SDL_SendMouseMotion(window, SDL_GetMouse()->mouseID, 0, x, y);
 }
 
diff --git a/src/video/windows/SDL_windowsmouse.h b/src/video/windows/SDL_windowsmouse.h
index 68279a685e..0dcc530334 100644
--- a/src/video/windows/SDL_windowsmouse.h
+++ b/src/video/windows/SDL_windowsmouse.h
@@ -28,6 +28,7 @@ extern HCURSOR SDL_cursor;
 
 extern void WIN_InitMouse(_THIS);
 extern void WIN_QuitMouse(_THIS);
+extern void WIN_SetCursorPos(int x, int y);
 
 #endif /* SDL_windowsmouse_h_ */