SDL: Don't need to use raw input to track the mouse during mouse capture (thanks Brick!)

From 5e89b3c89e90841f4f2a42c1e498f6aa3bb2604c Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 14 Oct 2021 11:46:07 -0700
Subject: [PATCH] Don't need to use raw input to track the mouse during mouse
 capture (thanks Brick!)

---
 src/video/windows/SDL_windowsevents.c | 83 ++++++++++++++++-----------
 src/video/windows/SDL_windowsmouse.c  | 22 ++++---
 src/video/windows/SDL_windowswindow.c | 12 ----
 src/video/windows/SDL_windowswindow.h |  1 +
 4 files changed, 62 insertions(+), 56 deletions(-)

diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index 6307fac70e..60f87d764f 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -742,6 +742,19 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
     case WM_MOUSEMOVE:
         {
             SDL_Mouse *mouse = SDL_GetMouse();
+
+            if (!data->mouse_tracked) {
+                TRACKMOUSEEVENT trackMouseEvent;
+
+                trackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT);
+                trackMouseEvent.dwFlags = TME_LEAVE;
+                trackMouseEvent.hwndTrack = data->hwnd;
+
+                if (TrackMouseEvent(&trackMouseEvent)) {
+                    data->mouse_tracked = SDL_TRUE;
+                }
+            }
+
             if (!mouse->relative_mode || mouse->relative_mode_warp) {
                 /* Only generate mouse events for real mouse */
                 if (GetMouseMessageSource() != SDL_MOUSE_EVENT_SOURCE_TOUCH &&
@@ -781,13 +794,10 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
             RAWINPUT inp;
             UINT size = sizeof(inp);
             const SDL_bool isRelative = mouse->relative_mode || mouse->relative_mode_warp;
-            const SDL_bool isCapture = ((data->window->flags & SDL_WINDOW_MOUSE_CAPTURE) != 0);
 
             /* Relative mouse motion is delivered to the window with keyboard focus */
             if (!isRelative || data->window != SDL_GetKeyboardFocus()) {
-                if (!isCapture) {
-                    break;
-                }
+                break;
             }
 
             GetRawInputData(hRawInput, RID_INPUT, &inp, &size, sizeof(RAWINPUTHEADER));
@@ -910,28 +920,6 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
                     }
                     WIN_CheckRawMouseButtons(rawmouse->usButtonFlags, data, mouseID);
 
-                } else if (isCapture) {
-                    /* we check for where Windows thinks the system cursor lives in this case, so we don't really lose mouse accel, etc. */
-                    POINT pt;
-                    RECT hwndRect;
-                    HWND currentHnd;
-
-                    GetCursorPos(&pt);
-                    currentHnd = WindowFromPoint(pt);
-                    ScreenToClient(hwnd, &pt);
-                    GetClientRect(hwnd, &hwndRect);
-
-                    /* if in the window, WM_MOUSEMOVE, etc, will cover it. */
-                    if(currentHnd != hwnd || pt.x < 0 || pt.y < 0 || pt.x > hwndRect.right || pt.y > hwndRect.right) {
-                        SDL_bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
-
-                        SDL_SendMouseMotion(data->window, mouseID, 0, (int)pt.x, (int)pt.y);
-                        SDL_SendMouseButton(data->window, mouseID, GetAsyncKeyState(VK_LBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, !swapButtons ? SDL_BUTTON_LEFT : SDL_BUTTON_RIGHT);
-                        SDL_SendMouseButton(data->window, mouseID, GetAsyncKeyState(VK_RBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, !swapButtons ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT);
-                        SDL_SendMouseButton(data->window, mouseID, GetAsyncKeyState(VK_MBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_MIDDLE);
-                        SDL_SendMouseButton(data->window, mouseID, GetAsyncKeyState(VK_XBUTTON1) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_X1);
-                        SDL_SendMouseButton(data->window, mouseID, GetAsyncKeyState(VK_XBUTTON2) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_X2);
-                    }
                 } else {
                     SDL_assert(0 && "Shouldn't happen");
                 }
@@ -951,7 +939,6 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         }
         break;
 
-#ifdef WM_MOUSELEAVE
     case WM_MOUSELEAVE:
         if (SDL_GetMouseFocus() == data->window && !SDL_GetMouse()->relative_mode && !(data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
             if (!IsIconic(hwnd)) {
@@ -973,18 +960,16 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
             }
         }
 
-        /* When WM_MOUSELEAVE is fired we can be assured that the cursor has left the window.
-           Regardless of relative mode, it is important that mouse focus is reset as there is a potential
-           race condition when in the process of leaving/entering relative mode, resulting in focus never
-           being lost. This then causes a cascading failure where SDL_WINDOWEVENT_ENTER / SDL_WINDOWEVENT_LEAVE
-           can stop firing permanently, due to the focus being in the wrong state and TrackMouseEvent never
-           resubscribing. */
-        if (!(data->window->flags & SDL_WINDOW_MOUSE_CAPTURE))
+        /* When WM_MOUSELEAVE is fired we can be assured that the cursor has left the window */
+        if (!(data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
             SDL_SetMouseFocus(NULL);
+        }
+
+        /* Once we get WM_MOUSELEAVE we're guaranteed that the window is no longer tracked */
+        data->mouse_tracked = SDL_FALSE;
 
         returnCode = 0;
         break;
-#endif /* WM_MOUSELEAVE */
 
     case WM_KEYDOWN:
     case WM_SYSKEYDOWN:
@@ -1467,6 +1452,31 @@ static void WIN_UpdateClipCursorForWindows()
     }
 }
 
+static void WIN_UpdateMouseCapture()
+{
+    SDL_Window* focusWindow = SDL_GetKeyboardFocus();
+
+    if (focusWindow && (focusWindow->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
+        SDL_WindowData *data = (SDL_WindowData *) focusWindow->driverdata;
+
+        if (!data->mouse_tracked) {
+            POINT pt;
+
+            if (GetCursorPos(&pt) && ScreenToClient(data->hwnd, &pt)) {
+                SDL_bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
+                SDL_MouseID mouseID = SDL_GetMouse()->mouseID;
+
+                SDL_SendMouseMotion(data->window, mouseID, 0, (int)pt.x, (int)pt.y);
+                SDL_SendMouseButton(data->window, mouseID, GetAsyncKeyState(VK_LBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, !swapButtons ? SDL_BUTTON_LEFT : SDL_BUTTON_RIGHT);
+                SDL_SendMouseButton(data->window, mouseID, GetAsyncKeyState(VK_RBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, !swapButtons ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT);
+                SDL_SendMouseButton(data->window, mouseID, GetAsyncKeyState(VK_MBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_MIDDLE);
+                SDL_SendMouseButton(data->window, mouseID, GetAsyncKeyState(VK_XBUTTON1) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_X1);
+                SDL_SendMouseButton(data->window, mouseID, GetAsyncKeyState(VK_XBUTTON2) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_X2);
+            }
+        }
+    }
+}
+
 /* A message hook called before TranslateMessage() */
 static SDL_WindowsMessageHook g_WindowsMessageHook = NULL;
 static void *g_WindowsMessageHookData = NULL;
@@ -1584,6 +1594,9 @@ WIN_PumpEvents(_THIS)
 
     /* Update the clipping rect in case someone else has stolen it */
     WIN_UpdateClipCursorForWindows();
+
+    /* Update mouse capture */
+    WIN_UpdateMouseCapture();
 }
 
 
diff --git a/src/video/windows/SDL_windowsmouse.c b/src/video/windows/SDL_windowsmouse.c
index a43ea45b79..031fcc03ec 100644
--- a/src/video/windows/SDL_windowsmouse.c
+++ b/src/video/windows/SDL_windowsmouse.c
@@ -292,18 +292,22 @@ WIN_SetRelativeMouseMode(SDL_bool enabled)
 static int
 WIN_CaptureMouse(SDL_Window *window)
 {
-    if (!window) {
-        SDL_Window *focusWin = SDL_GetKeyboardFocus();
-        if (focusWin) {
-            WIN_OnWindowEnter(SDL_GetVideoDevice(), focusWin);  /* make sure WM_MOUSELEAVE messages are (re)enabled. */
+    if (window) {
+        SDL_WindowData *data = (SDL_WindowData *)window->driverdata;
+        SetCapture(data->hwnd);
+    } else {
+        SDL_Window *focus_window = SDL_GetMouseFocus();
+       
+        if (focus_window) {
+            SDL_WindowData *data = (SDL_WindowData *)focus_window->driverdata;
+            if (!data->mouse_tracked) {
+                SDL_SetMouseFocus(NULL);
+            }
         }
+        ReleaseCapture();
     }
 
-    /* While we were thinking of SetCapture() when designing this API in SDL,
-       we didn't count on the fact that SetCapture() only tracks while the
-       left mouse button is held down! Instead, we listen for raw mouse input
-       and manually query the mouse when it leaves the window. :/ */
-    return ToggleRawInput(window != NULL);
+    return 0;
 }
 
 static Uint32
diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c
index 8013d49515..0cfc006a22 100644
--- a/src/video/windows/SDL_windowswindow.c
+++ b/src/video/windows/SDL_windowswindow.c
@@ -953,18 +953,6 @@ void WIN_OnWindowEnter(_THIS, SDL_Window * window)
     if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) {
         WIN_SetWindowPositionInternal(_this, window, SWP_NOCOPYBITS | SWP_NOSIZE | SWP_NOACTIVATE);
     }
-
-#ifdef WM_MOUSELEAVE
-    {
-        TRACKMOUSEEVENT trackMouseEvent;
-
-        trackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT);
-        trackMouseEvent.dwFlags = TME_LEAVE;
-        trackMouseEvent.hwndTrack = data->hwnd;
-
-        TrackMouseEvent(&trackMouseEvent);
-    }
-#endif /* WM_MOUSELEAVE */
 }
 
 void
diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h
index b7d1ce1e05..015fb0f287 100644
--- a/src/video/windows/SDL_windowswindow.h
+++ b/src/video/windows/SDL_windowswindow.h
@@ -53,6 +53,7 @@ typedef struct
     SDL_bool in_window_deactivation;
     RECT cursor_clipped_rect;
     SDL_Point last_raw_mouse_position;
+    SDL_bool mouse_tracked;
     struct SDL_VideoData *videodata;
 #if SDL_VIDEO_OPENGL_EGL  
     EGLSurface egl_surface;