Implemented SDL_CaptureMouse().
authorRyan C. Gordon <icculus@icculus.org>
Sat, 24 May 2014 01:30:37 -0400
changeset 8927 be64f5daf64b
parent 8783 400f1d2b9e52
child 8928 f60f16d29e37
Implemented SDL_CaptureMouse().
include/SDL_mouse.h
include/SDL_video.h
src/dynapi/SDL_dynapi_overrides.h
src/dynapi/SDL_dynapi_procs.h
src/events/SDL_keyboard.c
src/events/SDL_mouse.c
src/events/SDL_mouse_c.h
src/test/SDL_test_common.c
src/video/SDL_video.c
src/video/windows/SDL_windowsmouse.c
src/video/x11/SDL_x11mouse.c
--- a/include/SDL_mouse.h	Sat May 24 01:27:19 2014 -0400
+++ b/include/SDL_mouse.h	Sat May 24 01:30:37 2014 -0400
@@ -117,6 +117,37 @@
 extern DECLSPEC int SDLCALL SDL_SetRelativeMouseMode(SDL_bool enabled);
 
 /**
+ *  \brief Capture the mouse, to track input outside an SDL window.
+ *
+ *  \param enabled Whether or not to enable capturing
+ *
+ *  Capturing enables your app to obtain mouse events globally, instead of
+ *  just within your window. Not all video targets support this function.
+ *  When capturing is enabled, the current window will get all mouse events,
+ *  but unlike relative mode, no change is made to the cursor and it is
+ *  not restrained to your window.
+ *
+ *  This function may also deny mouse input to other windows--both those in
+ *  your application and others on the system--so you should use this
+ *  function sparingly, and in small bursts. For example, you might want to
+ *  track the mouse while the user is dragging something, until the user
+ *  releases a mouse button. It is not recommended that you capture the mouse
+ *  for long periods of time, such as the entire time your app is running.
+ *
+ *  While captured, mouse events still report coordinates relative to the
+ *  current (foreground) window, but those coordinates may be outside the
+ *  bounds of the window (including negative values). Capturing is only
+ *  allowed for the foreground window. If the window loses focus while
+ *  capturing, the capture will be disabled automatically.
+ *
+ *  While capturing is enabled, the current window will have the
+ *  SDL_WINDOW_MOUSE_CAPTURE flag set.
+ *
+ *  \return 0 on success, or -1 if not supported.
+ */
+extern DECLSPEC int SDLCALL SDL_CaptureMouse(SDL_bool enabled);
+
+/**
  *  \brief Query whether relative mouse mode is enabled.
  *
  *  \sa SDL_SetRelativeMouseMode()
--- a/include/SDL_video.h	Sat May 24 01:27:19 2014 -0400
+++ b/include/SDL_video.h	Sat May 24 01:30:37 2014 -0400
@@ -108,7 +108,8 @@
     SDL_WINDOW_MOUSE_FOCUS = 0x00000400,        /**< window has mouse focus */
     SDL_WINDOW_FULLSCREEN_DESKTOP = ( SDL_WINDOW_FULLSCREEN | 0x00001000 ),
     SDL_WINDOW_FOREIGN = 0x00000800,            /**< window not created by SDL */
-    SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000       /**< window should be created in high-DPI mode if supported */
+    SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000,      /**< window should be created in high-DPI mode if supported */
+    SDL_WINDOW_MOUSE_CAPTURE = 0x00004000       /**< window has mouse captured (unrelated to INPUT_GRABBED) */
 } SDL_WindowFlags;
 
 /**
--- a/src/dynapi/SDL_dynapi_overrides.h	Sat May 24 01:27:19 2014 -0400
+++ b/src/dynapi/SDL_dynapi_overrides.h	Sat May 24 01:30:37 2014 -0400
@@ -579,3 +579,4 @@
 #define SDL_WinRTGetFSPathUNICODE SDL_WinRTGetFSPathUNICODE_REAL
 #define SDL_WinRTGetFSPathUTF8 SDL_WinRTGetFSPathUTF8_REAL
 #define SDL_WinRTRunApp SDL_WinRTRunApp_REAL
+#define SDL_CaptureMouse SDL_CaptureMouse_REAL
--- a/src/dynapi/SDL_dynapi_procs.h	Sat May 24 01:27:19 2014 -0400
+++ b/src/dynapi/SDL_dynapi_procs.h	Sat May 24 01:30:37 2014 -0400
@@ -612,3 +612,4 @@
 SDL_DYNAPI_PROC(const char*,SDL_WinRTGetFSPathUTF8,(SDL_WinRT_Path a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_WinRTRunApp,(int a, char **b, void *c),(a,b,c),return)
 #endif
+SDL_DYNAPI_PROC(int,SDL_CaptureMouse,(SDL_bool a),(a),return)
--- a/src/events/SDL_keyboard.c	Sat May 24 01:27:19 2014 -0400
+++ b/src/events/SDL_keyboard.c	Sat May 24 01:30:37 2014 -0400
@@ -25,6 +25,7 @@
 #include "SDL_timer.h"
 #include "SDL_events.h"
 #include "SDL_events_c.h"
+#include "SDL_assert.h"
 #include "../video/SDL_sysvideo.h"
 
 
@@ -619,6 +620,16 @@
 
     /* See if the current window has lost focus */
     if (keyboard->focus && keyboard->focus != window) {
+
+        /* new window shouldn't think it has mouse captured. */
+        SDL_assert(!window || !(window->flags & SDL_WINDOW_MOUSE_CAPTURE));
+
+        /* old window must lose an existing mouse capture. */
+        if (keyboard->focus->flags & SDL_WINDOW_MOUSE_CAPTURE) {
+            SDL_CaptureMouse(SDL_FALSE);  /* drop the capture. */
+            SDL_assert(!(keyboard->focus->flags & SDL_WINDOW_MOUSE_CAPTURE));
+        }
+
         SDL_SendWindowEvent(keyboard->focus, SDL_WINDOWEVENT_FOCUS_LOST,
                             0, 0);
 
--- a/src/events/SDL_mouse.c	Sat May 24 01:27:19 2014 -0400
+++ b/src/events/SDL_mouse.c	Sat May 24 01:30:37 2014 -0400
@@ -140,30 +140,17 @@
 SDL_UpdateMouseFocus(SDL_Window * window, int x, int y, Uint32 buttonstate)
 {
     SDL_Mouse *mouse = SDL_GetMouse();
-    int w, h;
-    SDL_bool inWindow;
+    SDL_bool inWindow = SDL_TRUE;
 
-    SDL_GetWindowSize(window, &w, &h);
-    if (x < 0 || y < 0 || x >= w || y >= h) {
-        inWindow = SDL_FALSE;
-    } else {
-        inWindow = SDL_TRUE;
+    if ((window->flags & SDL_WINDOW_MOUSE_CAPTURE) == 0) {
+        int w, h;
+        SDL_GetWindowSize(window, &w, &h);
+        if (x < 0 || y < 0 || x >= w || y >= h) {
+            inWindow = SDL_FALSE;
+        }
     }
 
-/* Linux doesn't give you mouse events outside your window unless you grab
-   the pointer.
-
-   Windows doesn't give you mouse events outside your window unless you call
-   SetCapture().
-
-   Both of these are slightly scary changes, so for now we'll punt and if the
-   mouse leaves the window you'll lose mouse focus and reset button state.
-*/
-#ifdef SUPPORT_DRAG_OUTSIDE_WINDOW
-    if (!inWindow && !buttonstate) {
-#else
     if (!inWindow) {
-#endif
         if (window == mouse->focus) {
 #ifdef DEBUG_MOUSE
             printf("Mouse left window, synthesizing move & focus lost event\n");
@@ -204,7 +191,6 @@
     int posted;
     int xrel;
     int yrel;
-    int x_max = 0, y_max = 0;
 
     if (mouse->relative_mode_warp) {
         int center_x = 0, center_y = 0;
@@ -246,24 +232,29 @@
         mouse->y += yrel;
     }
 
-    /* !!! FIXME: shouldn't this be (window) instead of (mouse->focus)? */
-    SDL_GetWindowSize(mouse->focus, &x_max, &y_max);
-    --x_max;
-    --y_max;
+    /* make sure that the pointers find themselves inside the windows,
+       unless we have the mouse captured. */
+    if ((window->flags & SDL_WINDOW_MOUSE_CAPTURE) == 0) {
+        int x_max = 0, y_max = 0;
+
+        // !!! FIXME: shouldn't this be (window) instead of (mouse->focus)?
+        SDL_GetWindowSize(mouse->focus, &x_max, &y_max);
+        --x_max;
+        --y_max;
 
-    /* make sure that the pointers find themselves inside the windows */
-    if (mouse->x > x_max) {
-        mouse->x = x_max;
-    }
-    if (mouse->x < 0) {
-        mouse->x = 0;
-    }
+        if (mouse->x > x_max) {
+            mouse->x = x_max;
+        }
+        if (mouse->x < 0) {
+            mouse->x = 0;
+        }
 
-    if (mouse->y > y_max) {
-        mouse->y = y_max;
-    }
-    if (mouse->y < 0) {
-        mouse->y = 0;
+        if (mouse->y > y_max) {
+            mouse->y = y_max;
+        }
+        if (mouse->y < 0) {
+            mouse->y = 0;
+        }
     }
 
     mouse->xdelta += xrel;
@@ -426,6 +417,7 @@
     SDL_Cursor *cursor, *next;
     SDL_Mouse *mouse = SDL_GetMouse();
 
+    SDL_CaptureMouse(SDL_FALSE);
     SDL_SetRelativeMouseMode(SDL_FALSE);
     SDL_ShowCursor(1);
 
@@ -572,6 +564,42 @@
     return mouse->relative_mode;
 }
 
+int
+SDL_CaptureMouse(SDL_bool enabled)
+{
+    SDL_Mouse *mouse = SDL_GetMouse();
+    SDL_Window *focusWindow;
+    SDL_bool isCaptured;
+
+    if (!mouse->CaptureMouse) {
+        return SDL_Unsupported();
+    }
+
+    focusWindow = SDL_GetKeyboardFocus();
+
+    isCaptured = focusWindow && (focusWindow->flags & SDL_WINDOW_MOUSE_CAPTURE);
+    if (isCaptured == enabled) {
+        return 0;  /* already done! */
+    }
+
+    if (enabled) {
+        if (!focusWindow) {
+            return SDL_SetError("No window has focus");
+        } else if (mouse->CaptureMouse(focusWindow) == -1) {
+            return -1;  /* CaptureMouse() should call SetError */
+        }
+        focusWindow->flags |= SDL_WINDOW_MOUSE_CAPTURE;
+    } else {
+        if (mouse->CaptureMouse(NULL) == -1) {
+            return -1;  /* CaptureMouse() should call SetError */
+        }
+        focusWindow->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
+    }
+
+    return 0;
+}
+
+
 SDL_Cursor *
 SDL_CreateCursor(const Uint8 * data, const Uint8 * mask,
                  int w, int h, int hot_x, int hot_y)
--- a/src/events/SDL_mouse_c.h	Sat May 24 01:27:19 2014 -0400
+++ b/src/events/SDL_mouse_c.h	Sat May 24 01:30:37 2014 -0400
@@ -63,6 +63,9 @@
     /* Set relative mode */
     int (*SetRelativeMouseMode) (SDL_bool enabled);
 
+    /* Set mouse capture */
+    int (*CaptureMouse) (SDL_Window * window);
+
     /* Data common to all mice */
     SDL_MouseID mouseID;
     SDL_Window *focus;
--- a/src/test/SDL_test_common.c	Sat May 24 01:27:19 2014 -0400
+++ b/src/test/SDL_test_common.c	Sat May 24 01:30:37 2014 -0400
@@ -999,10 +999,12 @@
 static void
 SDLTest_PrintEvent(SDL_Event * event)
 {
+#if 0
     if ((event->type == SDL_MOUSEMOTION) || (event->type == SDL_FINGERMOTION)) {
         /* Mouse and finger motion are really spammy */
         return;
     }
+#endif
 
     switch (event->type) {
     case SDL_WINDOWEVENT:
@@ -1379,6 +1381,14 @@
                     }
                 }
             }
+            if (withShift) {
+                SDL_Window *current_win = SDL_GetKeyboardFocus();
+                if (current_win) {
+                    const SDL_bool shouldCapture = (SDL_GetWindowFlags(current_win) & SDL_WINDOW_MOUSE_CAPTURE) == 0;
+                    const int rc = SDL_CaptureMouse(shouldCapture);
+                    printf("%sapturing mouse %s!\n", shouldCapture ? "C" : "Unc", (rc == 0) ? "succeeded" : "failed");
+                }
+            }
             break;
         case SDLK_v:
             if (withControl) {
--- a/src/video/SDL_video.c	Sat May 24 01:27:19 2014 -0400
+++ b/src/video/SDL_video.c	Sat May 24 01:30:37 2014 -0400
@@ -3269,12 +3269,17 @@
     int retval = -1;
     SDL_bool relative_mode;
     int show_cursor_prev;
+    SDL_bool mouse_captured;
+    SDL_Window *current_window;
 
     if (!messageboxdata) {
         return SDL_InvalidParamError("messageboxdata");
     }
 
+    current_window = SDL_GetKeyboardFocus();
+    mouse_captured = current_window && ((SDL_GetWindowFlags(current_window) & SDL_WINDOW_MOUSE_CAPTURE) != 0);
     relative_mode = SDL_GetRelativeMouseMode();
+    SDL_CaptureMouse(SDL_FALSE);
     SDL_SetRelativeMouseMode(SDL_FALSE);
     show_cursor_prev = SDL_ShowCursor(1);
 
@@ -3326,6 +3331,13 @@
         SDL_SetError("No message system available");
     }
 
+    if (current_window) {
+        SDL_RaiseWindow(current_window);
+        if (mouse_captured) {
+            SDL_CaptureMouse(SDL_TRUE);
+        }
+    }
+
     SDL_ShowCursor(show_cursor_prev);
     SDL_SetRelativeMouseMode(relative_mode);
 
--- a/src/video/windows/SDL_windowsmouse.c	Sat May 24 01:27:19 2014 -0400
+++ b/src/video/windows/SDL_windowsmouse.c	Sat May 24 01:30:37 2014 -0400
@@ -219,6 +219,19 @@
     return 0;
 }
 
+static int
+WIN_CaptureMouse(SDL_Window *window)
+{
+    if (!window) {
+        ReleaseCapture();
+    } else {
+        const SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
+        SetCapture(data->hwnd);
+    }
+
+    return 0;
+}
+
 void
 WIN_InitMouse(_THIS)
 {
@@ -230,6 +243,7 @@
     mouse->FreeCursor = WIN_FreeCursor;
     mouse->WarpMouse = WIN_WarpMouse;
     mouse->SetRelativeMouseMode = WIN_SetRelativeMouseMode;
+    mouse->CaptureMouse = WIN_CaptureMouse;
 
     SDL_SetDefaultCursor(WIN_CreateDefaultCursor());
 
--- a/src/video/x11/SDL_x11mouse.c	Sat May 24 01:27:19 2014 -0400
+++ b/src/video/x11/SDL_x11mouse.c	Sat May 24 01:30:37 2014 -0400
@@ -330,6 +330,29 @@
     return -1;
 }
 
+static int
+X11_CaptureMouse(SDL_Window *window)
+{
+    Display *display = GetDisplay();
+
+    if (window) {
+        SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
+        const unsigned int mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask;
+        const int rc = X11_XGrabPointer(display, data->xwindow, False,
+                                        mask, GrabModeAsync, GrabModeAsync,
+                                        None, None, CurrentTime);
+        if (rc != GrabSuccess) {
+            return SDL_SetError("X server refused mouse capture");
+        }
+    } else {
+        X11_XUngrabPointer(display, CurrentTime);
+    }
+
+    X11_XSync(display, False);
+
+    return 0;
+}
+
 void
 X11_InitMouse(_THIS)
 {
@@ -341,6 +364,7 @@
     mouse->FreeCursor = X11_FreeCursor;
     mouse->WarpMouse = X11_WarpMouse;
     mouse->SetRelativeMouseMode = X11_SetRelativeMouseMode;
+    mouse->CaptureMouse = X11_CaptureMouse;
 
     SDL_SetDefaultCursor(X11_CreateDefaultCursor());
 }