window: auto-hide the cursor over the game window (#958)

* window: auto-hide the cursor over the game window

Hide the cursor when the game window is created and whenever the mouse
has been idle for two seconds; any mouse motion, button or wheel event
shows it again. Games that draw their own cursor (e.g. Blue Prince) no
longer show the host pointer on top of it.

Only real mouse input counts as activity: synthetic touch/pen mouse
events and zero-delta motion (sent by some platforms on focus or cursor
visibility changes) are ignored. The event loop only waits with a
timeout while the cursor is visible, so it still blocks indefinitely
once the cursor is hidden. The cursor is restored on shutdown.

Uses SDL_HideCursor/SDL_ShowCursor, which SDL implements for X11,
Wayland, Cocoa and Win32 and which only affect the cursor over the
emulator window.

Closes #282

* window: make cursor auto-hide optional in CLI and launcher

Co-authored-by: Anatolii Kucheruk <anatolii.kucheruk01@gmail.com>

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
Co-authored-by: Anatolii Kucheruk <anatolii.kucheruk01@gmail.com>
This commit is contained in:
Aryan Sharma
2026-10-03 19:45:15 +05:30
committed by GitHub
parent 820856b301
commit feeced81d2
8 changed files with 78 additions and 8 deletions
+50 -8
View File
@@ -72,6 +72,7 @@ struct Binding {
};
constexpr int MOUSE_POLL_INTERVAL_MS = 33;
constexpr uint64_t CURSOR_IDLE_HIDE_MS = 2000;
constexpr std::string_view MOUSE_SENSITIVITY = "MouseSensitivity=";
struct MouseJoystickState {
@@ -81,7 +82,8 @@ struct MouseJoystickState {
};
MouseJoystickState g_mouse;
SDL_Window* g_mouse_window = nullptr;
SDL_Window* g_mouse_window = nullptr;
uint64_t g_cursor_hide_at = 0;
std::size_t ControlFromName(std::string_view name) {
const auto info = std::find_if(CONTROL_INFO.begin(), CONTROL_INFO.end(),
@@ -380,11 +382,31 @@ int PollMouse(uint64_t now_ms) {
return MOUSE_POLL_INTERVAL_MS;
}
bool IsCursorActivity(const SDL_Event& event) {
SDL_MouseID which;
switch (event.type) {
case SDL_EVENT_MOUSE_MOTION:
if (event.motion.xrel == 0.0f && event.motion.yrel == 0.0f) {
return false;
}
which = event.motion.which;
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP: which = event.button.which; break;
case SDL_EVENT_MOUSE_WHEEL: which = event.wheel.which; break;
default: return false;
}
return which != SDL_TOUCH_MOUSEID && which != SDL_PEN_MOUSEID;
}
} // namespace
void HostInputInit(SDL_Window* window) {
GetInputMap();
g_mouse_window = window;
if (Config::HideCursorEnabled()) {
g_cursor_hide_at = SDL_GetTicks() + CURSOR_IDLE_HIDE_MS;
}
}
void HostInputShutdown() {
@@ -393,7 +415,11 @@ void HostInputShutdown() {
CenterMouseStick();
g_mouse = {};
}
g_mouse_window = nullptr;
g_mouse_window = nullptr;
g_cursor_hide_at = 0;
if (Config::HideCursorEnabled()) {
SDL_ShowCursor();
}
}
void HostInputKey(int key_code, bool down) {
@@ -429,20 +455,36 @@ void HostInputToggleMouseToJoystick() {
}
bool HostInputWaitEvent(SDL_Event* event) {
bool has_event;
int timeout = -1;
if (!g_mouse.enabled || SDL_GetKeyboardFocus() != g_mouse_window) {
g_mouse.next_poll = 0;
CenterMouseStick();
has_event = SDL_WaitEvent(event);
if (!has_event) {
EXIT("%s\n", SDL_GetError());
}
} else {
if (g_mouse.next_poll == 0) {
SDL_GetRelativeMouseState(nullptr, nullptr);
g_mouse.next_poll = SDL_GetTicks() + MOUSE_POLL_INTERVAL_MS;
}
has_event = SDL_WaitEventTimeout(event, PollMouse(SDL_GetTicks()));
timeout = PollMouse(SDL_GetTicks());
}
if (g_cursor_hide_at != 0) {
const auto now_ms = SDL_GetTicks();
const int cursor_timeout =
now_ms < g_cursor_hide_at ? static_cast<int>(g_cursor_hide_at - now_ms) : 0;
timeout = timeout < 0 ? cursor_timeout : std::min(timeout, cursor_timeout);
}
const bool has_event = SDL_WaitEventTimeout(event, timeout);
if (Config::HideCursorEnabled()) {
const auto now_ms = SDL_GetTicks();
if (has_event && !g_mouse.enabled && IsCursorActivity(*event) &&
SDL_GetWindowFromEvent(event) == g_mouse_window) {
SDL_ShowCursor();
g_cursor_hide_at = now_ms + CURSOR_IDLE_HIDE_MS;
} else if (g_cursor_hide_at != 0 && now_ms >= g_cursor_hide_at) {
SDL_HideCursor();
g_cursor_hide_at = 0;
}
}
if (has_event && event->type == SDL_EVENT_WINDOW_FOCUS_LOST &&