Skip to content

Commit

Permalink
Implemented SDL_CaptureMouse().
Browse files Browse the repository at this point in the history
  • Loading branch information
icculus committed May 24, 2014
1 parent 846a3e0 commit b7d2c0e
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 38 deletions.
31 changes: 31 additions & 0 deletions include/SDL_mouse.h
Expand Up @@ -116,6 +116,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.
*
Expand Down
3 changes: 2 additions & 1 deletion include/SDL_video.h
Expand Up @@ -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;

/**
Expand Down
1 change: 1 addition & 0 deletions src/dynapi/SDL_dynapi_overrides.h
Expand Up @@ -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
1 change: 1 addition & 0 deletions src/dynapi/SDL_dynapi_procs.h
Expand Up @@ -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)
11 changes: 11 additions & 0 deletions src/events/SDL_keyboard.c
Expand Up @@ -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"


Expand Down Expand Up @@ -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);

Expand Down
102 changes: 65 additions & 37 deletions src/events/SDL_mouse.c
Expand Up @@ -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");
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions src/events/SDL_mouse_c.h
Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions src/test/SDL_test_common.c
Expand Up @@ -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:
Expand Down Expand Up @@ -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) {
Expand Down
12 changes: 12 additions & 0 deletions src/video/SDL_video.c
Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down
14 changes: 14 additions & 0 deletions src/video/windows/SDL_windowsmouse.c
Expand Up @@ -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)
{
Expand All @@ -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());

Expand Down
24 changes: 24 additions & 0 deletions src/video/x11/SDL_x11mouse.c
Expand Up @@ -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)
{
Expand All @@ -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());
}
Expand Down

0 comments on commit b7d2c0e

Please sign in to comment.