diff --git a/include/SDL_mouse.h b/include/SDL_mouse.h index ebfd18fa7..571754ad2 100644 --- a/include/SDL_mouse.h +++ b/include/SDL_mouse.h @@ -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. * diff --git a/include/SDL_video.h b/include/SDL_video.h index 607062cb5..107c8381b 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 52b3d454e..6f74d135f 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 d84a93885..63d67e55b 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 d5443893b..ed4043e9c 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 3f82b4ee8..3ed1d1418 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 e0d917cbc..34cd259e6 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 6fa984b6a..e304a76e0 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 f8556a6d9..aa729cf3b 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 b49249db6..1f38dcd55 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 38bb86656..74dc8789c 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()); }