Mouse Capture and Global Mouse State
Learn how to track mouse movements and button states across your entire application, even when the mouse leaves your window.
In our earlier lessons on mouse events and tracking, we focused on what the mouse does while hovering over our windows-that is, when one of our windows has mouse focus.
However, in certain scenarios, we may need to know what's happening with the mouse even when it's outside our application's windows. This lesson covers the techniques for accomplishing this and explores scenarios where these capabilities are useful.
Global Mouse State
Previously, we introduced the SDL_GetMouseState() function, which allows us to determine the mouse's position within our window.
The similar SDL_GetGlobalMouseState() function lets us find the cursor's position anywhere on the user's desktop. It has a familiar API: it accepts two pointers to float variables and updates them with the horizontal and vertical positions of the cursor.
float x, y;
SDL_GetGlobalMouseState(&x, &y);While SDL_GetMouseState() reports the position relative to the top-left of our window, SDL_GetGlobalMouseState() reports it relative to the top-left corner of the primary display.
Note that this means either coordinate can be negative. This can happen in multi-monitor setups where the user has a display positioned above or to the left of their primary display.
Similar to SDL_GetMouseState(), SDL_GetGlobalMouseState() allows us to pass a nullptr for either argument if we're only interested in one of the coordinates:
// We only care about the horizontal position
float x;
SDL_GetGlobalMouseState(&x, nullptr);// We only care about the vertical position
float y;
SDL_GetGlobalMouseState(nullptr, &y);Button State
In addition to reporting the mouse's position, the SDL_GetGlobalMouseState() function lets us determine which buttons are currently pressed. It does this by returning a SDL_MouseButtonFlags (an alias for Uint32), which acts as a bitmask. We can use the & operator to examine individual bits and determine if specific buttons are pressed.
SDL provides the SDL_BUTTON_MASK() macro to help us isolate the bits associated with each mouse button. For example, SDL_BUTTON_MASK(SDL_BUTTON_LEFT) checks if the left button is pressed.
The following program logs out some information about the mouse state on every tick:
Files
Mouse Capture
Previously, we covered how SDL reports mouse events like movement and button presses only if the event happens while one of our windows has mouse focus - that is, when the user's cursor is hovering over one of our windows:
src/main.cpp
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"
void HandleWindowEvent(const SDL_WindowEvent& E) {
if (E.type == SDL_EVENT_WINDOW_MOUSE_ENTER) {
std::cout << "Mouse Entered Window - "
"Tracking Resumed\n";
} else if (E.type == SDL_EVENT_WINDOW_MOUSE_LEAVE) {
std::cout << "Mouse Left Window - "
"Tracking Stopped\n";
}
}
void HandleMouseMotion(const SDL_MouseMotionEvent& E) {
std::cout << "Mouse moved to "
<< E.x << ", " << E.y << '\n';
}
int main(int, char**) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_Event E;
bool IsRunning = true;
while (IsRunning) {
while (SDL_PollEvent(&E)) {
if (E.type == SDL_EVENT_QUIT) {
IsRunning = false;
} else if (E.type == SDL_EVENT_WINDOW_MOUSE_ENTER ||
E.type == SDL_EVENT_WINDOW_MOUSE_LEAVE
) {
HandleWindowEvent(E.window);
} else if (E.type == SDL_EVENT_MOUSE_MOTION) {
HandleMouseMotion(E.motion);
}
}
GameWindow.Render();
GameWindow.Update();
}
SDL_Quit();
return 0;
}Mouse moved to 2, 120
Mouse moved to 0, 120
Mouse Left Window - Tracking Stopped
Mouse Entered Window - Tracking Resumed
Mouse moved to 1, 108
Mouse moved to 2, 107In some situations, we prefer that mouse events are reported even when none of our windows have mouse focus. SDL refers to this as mouse capture, which we can enable using the SDL_CaptureMouse() function.
We pass true to enable capturing or false to disable it:
// Enable mouse capture
SDL_CaptureMouse(true);
// Disable mouse capture
SDL_CaptureMouse(false);Enabling mouse capture for prolonged periods is not recommended, as it can interfere with other applications the user might be interacting with. Instead, we typically enable mouse capture briefly for certain actions and then disable it when those actions are complete.
The most common use case for mouse capture is to support drag-and-drop interactions. For example, if we want to allow the user to drag something from one of our windows to another, there will be a point where neither of our windows has mouse focus.
Even if the user is dragging and dropping within the same window, their mouse may temporarily move outside the window.
Without mouse capture, we would not be able to track the cursor when our windows lack mouse focus. So, we typically enable mouse capture throughout such actions to remove that restriction.
Getting Capture State
Enabling mouse capture requires one of our windows to have input focus (i.e., keyboard focus). Additionally, if we lose input focus, mouse capture is automatically disabled.
The SDL_WindowFlags bit set includes an SDL_WINDOW_MOUSE_CAPTURE flag. While it's unusual to enable this when creating a window, this flag is still helpful, as we can retrieve the flags at any time and examine the SDL_WINDOW_MOUSE_CAPTURE bit to see if we're currently capturing the mouse.
The following program begins mouse capture when the user presses space, and stops when they press escape. It also logs the capture state on every tick:
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"
int main(int, char**) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_Event E;
bool IsRunning = true;
while (IsRunning) {
while (SDL_PollEvent(&E)) {
if (E.type == SDL_EVENT_QUIT) {
IsRunning = false;
} else if (E.type == SDL_EVENT_KEY_DOWN) {
if (E.key.key == SDLK_SPACE) {
SDL_CaptureMouse(true);
}
} else if (E.type == SDL_EVENT_KEY_UP) {
if (E.key.key == SDLK_ESCAPE) {
SDL_CaptureMouse(false);
}
}
}
bool HasCaptureFlag =
SDL_GetWindowFlags(GameWindow.GetRaw()) &
SDL_WINDOW_MOUSE_CAPTURE;
std::cout << "Window has capture flag: "
<< (HasCaptureFlag ? "YES" : "NO") << "\n";
GameWindow.Render();
GameWindow.Update();
}
SDL_Quit();
return 0;
}Window has capture flag: NO
Window has capture flag: NO
Window has capture flag: YES
...Error Handling
SDL_CaptureMouse() now returns a bool in SDL3. It returns true if it was successful, or false otherwise. We can use this to react to errors and call SDL_GetError() for a description of what went wrong.
The most common reasons for SDL_CaptureMouse() to fail are that no window has input focus when we try to enable it, or the platform does not support it at all.
if (!SDL_CaptureMouse(true)) {
std::cout << "Error: " << SDL_GetError();
}Error: No window has focusMouse Auto Capture
The drag-and-drop use case for enabling mouse capture is so common that SDL automatically enables mouse capture when the user holds down one of their mouse buttons.
The following program demonstrates this in action:
src/main.cpp
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"
int main(int, char**) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_Event E;
bool IsRunning = true;
while (IsRunning) {
while (SDL_PollEvent(&E)) {
if (E.type == SDL_EVENT_QUIT) {
IsRunning = false;
} else if (E.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
std::cout << "Mouse button down\n";
} else if (E.type == SDL_EVENT_MOUSE_BUTTON_UP) {
std::cout << "Mouse button up\n";
}
}
std::cout << "Capturing Mouse: ";
if (SDL_GetWindowFlags(GameWindow.GetRaw()) &
SDL_WINDOW_MOUSE_CAPTURE
) {
std::cout << "true\n";
} else {
std::cout << "false\n";
}
GameWindow.Render();
GameWindow.Update();
}
SDL_Quit();
return 0;
}Capturing Mouse: false
Capturing Mouse: false
Mouse button down
Capturing Mouse: true
Capturing Mouse: true
Mouse button up
Capturing Mouse: falseThis behavior is controlled by the SDL_HINT_MOUSE_AUTO_CAPTURE hint. By default, it is set to "1", meaning auto-capture is enabled.
We can disable auto-capture by setting it to "0" using SDL_SetHint():
src/main.cpp
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"
int main(int, char**) {
SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_Event E;
bool IsRunning = true;
while (IsRunning) {
while (SDL_PollEvent(&E)) {
if (E.type == SDL_EVENT_QUIT) {
IsRunning = false;
} else if (E.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
std::cout << "Mouse button down\n";
} else if (E.type == SDL_EVENT_MOUSE_BUTTON_UP) {
std::cout << "Mouse button up\n";
}
}
std::cout << "Capturing Mouse: ";
if (SDL_GetWindowFlags(GameWindow.GetRaw()) &
SDL_WINDOW_MOUSE_CAPTURE
) {
std::cout << "true\n";
} else {
std::cout << "false\n";
}
GameWindow.Render();
GameWindow.Update();
}
SDL_Quit();
return 0;
}Capturing Mouse: false
Capturing Mouse: false
Mouse button down
Capturing Mouse: false
Capturing Mouse: false
Mouse button up
Capturing Mouse: falseSummary
SDL's mouse capture and global mouse state features allow us to track mouse movement and button states beyond our window boundaries. This lets us implement advanced mouse interactions like drag-and-drop. Key topics covered:
- Using
SDL_GetGlobalMouseState()to track mouse position and button states across the entire desktop. - Understanding the difference between window-relative and global mouse coordinates.
- Implementing mouse capture to maintain mouse event tracking outside window boundaries.
- Managing automatic mouse capture during drag operations using SDL hints.
- Handling common error cases and platform-specific considerations.
- Distinguishing between mouse capture and mouse grab functionality.
Relative Mouse Mode
Learn how to restrict cursor movement to a window whilst capturing mouse motion continuously.