Commit bb7a27fadd68161dbc402fb9b23d604b4e238bad

Ryan C. Gordon 2014-05-30T01:51:13

Fixed up SDL_CaptureMouse() on Windows to work like I expected. This would have been a one-line patch to the documentation (specifying that captures only work as long as the left mouse button is pressed), but I didn't like that, so I got a little crazy about this instead.

diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index b04a7a2..6b76215 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -30,6 +30,7 @@
 #include "../../events/SDL_events_c.h"
 #include "../../events/SDL_touch_c.h"
 #include "../../events/scancodes_windows.h"
+#include "SDL_assert.h"
 
 /* Dropfile support */
 #include <shellapi.h>
@@ -428,33 +429,55 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
             HRAWINPUT hRawInput = (HRAWINPUT)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);
 
-            if (!mouse->relative_mode || mouse->relative_mode_warp || mouse->focus != data->window) {
-                break;
+            if (!isRelative || mouse->focus != data->window) {
+                if (!isCapture) {
+                    break;
+                }
             }
 
             GetRawInputData(hRawInput, RID_INPUT, &inp, &size, sizeof(RAWINPUTHEADER));
 
             /* Mouse data */
             if (inp.header.dwType == RIM_TYPEMOUSE) {
-                RAWMOUSE* mouse = &inp.data.mouse;
+                if (isRelative) {
+                    RAWMOUSE* mouse = &inp.data.mouse;
+
+                    if ((mouse->usFlags & 0x01) == MOUSE_MOVE_RELATIVE) {
+                        SDL_SendMouseMotion(data->window, 0, 1, (int)mouse->lLastX, (int)mouse->lLastY);
+                    } else {
+                        /* synthesize relative moves from the abs position */
+                        static SDL_Point initialMousePoint;
+                        if (initialMousePoint.x == 0 && initialMousePoint.y == 0) {
+                            initialMousePoint.x = mouse->lLastX;
+                            initialMousePoint.y = mouse->lLastY;
+                        }
+
+                        SDL_SendMouseMotion(data->window, 0, 1, (int)(mouse->lLastX-initialMousePoint.x), (int)(mouse->lLastY-initialMousePoint.y) );
 
-                if ((mouse->usFlags & 0x01) == MOUSE_MOVE_RELATIVE) {
-                    SDL_SendMouseMotion(data->window, 0, 1, (int)mouse->lLastX, (int)mouse->lLastY);
-                } else {
-                    /* synthesize relative moves from the abs position */
-                    static SDL_Point initialMousePoint;
-                    if (initialMousePoint.x == 0 && initialMousePoint.y == 0) {
                         initialMousePoint.x = mouse->lLastX;
                         initialMousePoint.y = mouse->lLastY;
                     }
-
-                    SDL_SendMouseMotion(data->window, 0, 1, (int)(mouse->lLastX-initialMousePoint.x), (int)(mouse->lLastY-initialMousePoint.y) );
-
-                    initialMousePoint.x = mouse->lLastX;
-                    initialMousePoint.y = mouse->lLastY;
+                    WIN_CheckRawMouseButtons( mouse->usButtonFlags, data );
+                } 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;
+                    HWND hwnd = data->hwnd;
+                    GetCursorPos(&pt);
+                    if (WindowFromPoint(pt) != hwnd) {  /* if in the window, WM_MOUSEMOVE, etc, will cover it. */
+                        ScreenToClient(data->hwnd, &pt);
+                        SDL_SendMouseMotion(data->window, 0, 0, (int) pt.x, (int) pt.y);
+                        SDL_SendMouseButton(data->window, 0, GetKeyState(VK_LBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_LEFT);
+                        SDL_SendMouseButton(data->window, 0, GetKeyState(VK_RBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_RIGHT);
+                        SDL_SendMouseButton(data->window, 0, GetKeyState(VK_MBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_MIDDLE);
+                        SDL_SendMouseButton(data->window, 0, GetKeyState(VK_XBUTTON1) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_X1);
+                        SDL_SendMouseButton(data->window, 0, GetKeyState(VK_XBUTTON2) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_X2);
+                    }
+                } else {
+                    SDL_assert(0 && "Shouldn't happen");
                 }
-                WIN_CheckRawMouseButtons( mouse->usButtonFlags, data );
             }
         }
         break;
@@ -499,7 +522,7 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
 
 #ifdef WM_MOUSELEAVE
     case WM_MOUSELEAVE:
-        if (SDL_GetMouseFocus() == data->window && !SDL_GetMouse()->relative_mode) {
+        if (SDL_GetMouseFocus() == data->window && !SDL_GetMouse()->relative_mode && !(data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
             if (!IsIconic(hwnd)) {
                 POINT cursorPos;
                 GetCursorPos(&cursorPos);
diff --git a/src/video/windows/SDL_windowsmouse.c b/src/video/windows/SDL_windowsmouse.c
index 1f38dcd..0abd212 100644
--- a/src/video/windows/SDL_windowsmouse.c
+++ b/src/video/windows/SDL_windowsmouse.c
@@ -30,6 +30,44 @@
 
 HCURSOR SDL_cursor = NULL;
 
+static int rawInputEnableCount = 0;
+
+static int 
+ToggleRawInput(SDL_bool enabled)
+{
+    RAWINPUTDEVICE rawMouse = { 0x01, 0x02, 0, NULL }; /* Mouse: UsagePage = 1, Usage = 2 */
+
+    if (enabled) {
+        rawInputEnableCount++;
+        if (rawInputEnableCount > 1) {
+            return 0;  /* already done. */
+        }
+    } else {
+        if (rawInputEnableCount == 0) {
+            return 0;  /* already done. */
+        }
+        rawInputEnableCount--;
+        if (rawInputEnableCount > 0) {
+            return 0;  /* not time to disable yet */
+        }
+    }
+
+    if (!enabled) {
+        rawMouse.dwFlags |= RIDEV_REMOVE;
+    }
+
+    /* (Un)register raw input for mice */
+    if (RegisterRawInputDevices(&rawMouse, 1, sizeof(RAWINPUTDEVICE)) == FALSE) {
+
+        /* Only return an error when registering. If we unregister and fail,
+           then it's probably that we unregistered twice. That's OK. */
+        if (enabled) {
+            return SDL_Unsupported();
+        }
+    }
+    return 0;
+}
+
 
 static SDL_Cursor *
 WIN_CreateDefaultCursor()
@@ -201,35 +239,25 @@ WIN_WarpMouse(SDL_Window * window, int x, int y)
 static int
 WIN_SetRelativeMouseMode(SDL_bool enabled)
 {
-    RAWINPUTDEVICE rawMouse = { 0x01, 0x02, 0, NULL }; /* Mouse: UsagePage = 1, Usage = 2 */
-
-    if (!enabled) {
-        rawMouse.dwFlags |= RIDEV_REMOVE;
-    }
-
-    /* (Un)register raw input for mice */
-    if (RegisterRawInputDevices(&rawMouse, 1, sizeof(RAWINPUTDEVICE)) == FALSE) {
-
-        /* Only return an error when registering. If we unregister and fail,
-           then it's probably that we unregistered twice. That's OK. */
-        if (enabled) {
-            return SDL_Unsupported();
-        }
-    }
-    return 0;
+    return ToggleRawInput(enabled);
 }
 
 static int
 WIN_CaptureMouse(SDL_Window *window)
 {
     if (!window) {
-        ReleaseCapture();
-    } else {
-        const SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
-        SetCapture(data->hwnd);
+        SDL_Window *focusWin = SDL_GetKeyboardFocus();
+        if (focusWin) {
+            SDL_WindowData *data = (SDL_WindowData *)focusWin->driverdata;
+            WIN_OnWindowEnter(SDL_GetVideoDevice(), focusWin);  /* make sure WM_MOUSELEAVE messages are (re)enabled. */
+        }
     }
 
-    return 0;
+    /* 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);
 }
 
 void
@@ -259,6 +287,11 @@ WIN_QuitMouse(_THIS)
         mouse->def_cursor = NULL;
         mouse->cur_cursor = NULL;
     }
+
+    if (rawInputEnableCount) {  /* force RAWINPUT off here. */
+        rawInputEnableCount = 1;
+        ToggleRawInput(SDL_FALSE);
+    }
 }
 
 #endif /* SDL_VIDEO_DRIVER_WINDOWS */