Commit b7d2c0e9d62afbd67352a0b47f543b1c525ae2ab

Ryan C. Gordon 2014-05-24T01:30:37

Implemented SDL_CaptureMouse().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
diff --git a/include/SDL_mouse.h b/include/SDL_mouse.h
index ebfd18f..571754a 100644
--- a/include/SDL_mouse.h
+++ b/include/SDL_mouse.h
@@ -117,6 +117,37 @@ extern DECLSPEC void SDLCALL SDL_WarpMouseInWindow(SDL_Window * window,
 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()
diff --git a/include/SDL_video.h b/include/SDL_video.h
index 607062c..107c838 100644
--- a/include/SDL_video.h
+++ b/include/SDL_video.h
@@ -108,7 +108,8 @@ typedef enum
     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;
 
 /**
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 52b3d45..6f74d13 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -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
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index d84a938..63d67e5 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -612,3 +612,4 @@ SDL_DYNAPI_PROC(const wchar_t*,SDL_WinRTGetFSPathUNICODE,(SDL_WinRT_Path a),(a),
 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)
diff --git a/src/events/SDL_keyboard.c b/src/events/SDL_keyboard.c
index d544389..ed4043e 100644
--- a/src/events/SDL_keyboard.c
+++ b/src/events/SDL_keyboard.c
@@ -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 @@ SDL_SetKeyboardFocus(SDL_Window * window)
 
     /* 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);
 
diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c
index 3f82b4e..3ed1d14 100644
--- a/src/events/SDL_mouse.c
+++ b/src/events/SDL_mouse.c
@@ -140,30 +140,17 @@ static SDL_bool
 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 @@ SDL_PrivateSendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relativ
     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 @@ SDL_PrivateSendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relativ
         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;
 
-    /* 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;
-    }
+        // !!! FIXME: shouldn't this be (window) instead of (mouse->focus)?
+        SDL_GetWindowSize(mouse->focus, &x_max, &y_max);
+        --x_max;
+        --y_max;
 
-    if (mouse->y > y_max) {
-        mouse->y = y_max;
-    }
-    if (mouse->y < 0) {
-        mouse->y = 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;
+        }
     }
 
     mouse->xdelta += xrel;
@@ -426,6 +417,7 @@ SDL_MouseQuit(void)
     SDL_Cursor *cursor, *next;
     SDL_Mouse *mouse = SDL_GetMouse();
 
+    SDL_CaptureMouse(SDL_FALSE);
     SDL_SetRelativeMouseMode(SDL_FALSE);
     SDL_ShowCursor(1);
 
@@ -572,6 +564,42 @@ SDL_GetRelativeMouseMode()
     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)
diff --git a/src/events/SDL_mouse_c.h b/src/events/SDL_mouse_c.h
index e0d917c..34cd259 100644
--- a/src/events/SDL_mouse_c.h
+++ b/src/events/SDL_mouse_c.h
@@ -63,6 +63,9 @@ typedef struct
     /* 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;
diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c
index 6fa984b..e304a76 100644
--- a/src/test/SDL_test_common.c
+++ b/src/test/SDL_test_common.c
@@ -999,10 +999,12 @@ default: return "???";
 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 @@ SDLTest_CommonEvent(SDLTest_CommonState * state, SDL_Event * event, int *done)
                     }
                 }
             }
+            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) {
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index f8556a6..aa729cf 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -3269,12 +3269,17 @@ SDL_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
     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_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
         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);
 
diff --git a/src/video/windows/SDL_windowsmouse.c b/src/video/windows/SDL_windowsmouse.c
index b49249d..1f38dcd 100644
--- a/src/video/windows/SDL_windowsmouse.c
+++ b/src/video/windows/SDL_windowsmouse.c
@@ -219,6 +219,19 @@ WIN_SetRelativeMouseMode(SDL_bool enabled)
     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 @@ WIN_InitMouse(_THIS)
     mouse->FreeCursor = WIN_FreeCursor;
     mouse->WarpMouse = WIN_WarpMouse;
     mouse->SetRelativeMouseMode = WIN_SetRelativeMouseMode;
+    mouse->CaptureMouse = WIN_CaptureMouse;
 
     SDL_SetDefaultCursor(WIN_CreateDefaultCursor());
 
diff --git a/src/video/x11/SDL_x11mouse.c b/src/video/x11/SDL_x11mouse.c
index 38bb866..74dc878 100644
--- a/src/video/x11/SDL_x11mouse.c
+++ b/src/video/x11/SDL_x11mouse.c
@@ -330,6 +330,29 @@ X11_SetRelativeMouseMode(SDL_bool enabled)
     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 @@ X11_InitMouse(_THIS)
     mouse->FreeCursor = X11_FreeCursor;
     mouse->WarpMouse = X11_WarpMouse;
     mouse->SetRelativeMouseMode = X11_SetRelativeMouseMode;
+    mouse->CaptureMouse = X11_CaptureMouse;
 
     SDL_SetDefaultCursor(X11_CreateDefaultCursor());
 }