diff --git a/src/common/emulatorConfig.cpp b/src/common/emulatorConfig.cpp index 5d421df97..ccceb5f8c 100644 --- a/src/common/emulatorConfig.cpp +++ b/src/common/emulatorConfig.cpp @@ -72,6 +72,10 @@ bool FullscreenEnabled() { return g_config->fullscreen_enabled; } +bool HideCursorEnabled() { + return g_config->hide_cursor_enabled; +} + bool VrEnabled() { return g_config->vr_enabled; } diff --git a/src/common/emulatorConfig.h b/src/common/emulatorConfig.h index 7ace3c175..58a9e24d2 100644 --- a/src/common/emulatorConfig.h +++ b/src/common/emulatorConfig.h @@ -53,6 +53,7 @@ struct ConfigOptions { PresentMode present_mode = PresentMode::Mailbox; int32_t gpu_index = -1; bool fullscreen_enabled = false; + bool hide_cursor_enabled = false; bool vr_enabled = false; bool amd_cpu_enabled = false; uint32_t vblank_frequency = 60; @@ -93,6 +94,7 @@ uint32_t GetControllerVibrationIntensity(); PresentMode GetPresentMode(); int32_t GetGpuIndex(); bool FullscreenEnabled(); +bool HideCursorEnabled(); bool VrEnabled(); bool AmdCpuEnabled(); uint32_t GetVblankFrequency(); diff --git a/src/graphics/presentation/window/hostInput.cpp b/src/graphics/presentation/window/hostInput.cpp index 425e01686..d04d74fbf 100644 --- a/src/graphics/presentation/window/hostInput.cpp +++ b/src/graphics/presentation/window/hostInput.cpp @@ -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(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 && diff --git a/src/launcher/forms/configuration_edit_dialog.ui b/src/launcher/forms/configuration_edit_dialog.ui index 05daec599..2ac240c5c 100644 --- a/src/launcher/forms/configuration_edit_dialog.ui +++ b/src/launcher/forms/configuration_edit_dialog.ui @@ -173,6 +173,12 @@ Enable tessellation support (experimental) + + + Hide the host cursor after 2 seconds without mouse activity + Auto-hide cursor + + diff --git a/src/launcher/include/configuration.h b/src/launcher/include/configuration.h index 638f7ed5d..c5e01351b 100644 --- a/src/launcher/include/configuration.h +++ b/src/launcher/include/configuration.h @@ -119,6 +119,7 @@ public: PresentMode present_mode = PresentMode::Mailbox; int gpu_index = -1; bool fullscreen_enabled = false; + bool hide_cursor_enabled = false; bool readback_linear_images = false; bool tessellation_enabled = false; int vblank_frequency = 60; @@ -150,6 +151,7 @@ public: present_mode = other.present_mode; gpu_index = other.gpu_index; fullscreen_enabled = other.fullscreen_enabled; + hide_cursor_enabled = other.hide_cursor_enabled; readback_linear_images = other.readback_linear_images; tessellation_enabled = other.tessellation_enabled; vblank_frequency = other.vblank_frequency; @@ -196,6 +198,7 @@ public: KYTY_CFG_SET(present_mode); KYTY_CFG_SET(gpu_index); KYTY_CFG_SET(fullscreen_enabled); + KYTY_CFG_SET(hide_cursor_enabled); KYTY_CFG_SET(readback_linear_images); KYTY_CFG_SET(tessellation_enabled); KYTY_CFG_SET(vblank_frequency); @@ -238,6 +241,7 @@ public: present_mode = PresentMode::Mailbox; } KYTY_CFG_GET(fullscreen_enabled); + KYTY_CFG_GET(hide_cursor_enabled); KYTY_CFG_GET(readback_linear_images); KYTY_CFG_GET(tessellation_enabled); vblank_frequency = s->value("vblank_frequency", vblank_frequency).toInt(); diff --git a/src/launcher/src/configurationEditDialog.cpp b/src/launcher/src/configurationEditDialog.cpp index 5e1c173df..682be2a91 100644 --- a/src/launcher/src/configurationEditDialog.cpp +++ b/src/launcher/src/configurationEditDialog.cpp @@ -273,6 +273,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) { info.gpu_index >= 0 && info.gpu_index < m_ui->comboBox_gpu->count() - 1 ? info.gpu_index + 1 : 0); m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled); + m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_enabled); m_ui->checkBox_readback->setChecked(info.readback_linear_images); m_ui->checkBox_tessellation->setChecked(info.tessellation_enabled); m_ui->spinBox_vblank_frequency->setValue(info.vblank_frequency); @@ -425,6 +426,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo TextToEnum(ui.comboBox_present_mode->currentText()); info.gpu_index = ui.comboBox_gpu->currentIndex() - 1; info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked(); + info.hide_cursor_enabled = ui.checkBox_hide_cursor->isChecked(); info.readback_linear_images = ui.checkBox_readback->isChecked(); info.tessellation_enabled = ui.checkBox_tessellation->isChecked(); info.vblank_frequency = ui.spinBox_vblank_frequency->value(); diff --git a/src/launcher/src/mainDialog.cpp b/src/launcher/src/mainDialog.cpp index 81d404004..43792d4fe 100644 --- a/src/launcher/src/mainDialog.cpp +++ b/src/launcher/src/mainDialog.cpp @@ -240,6 +240,9 @@ static QStringList CreateEmulatorArgs(const Configuration& info) { if (info.fullscreen_enabled) { args << "--fullscreen"; } + if (info.hide_cursor_enabled) { + args << "--hide-cursor"; + } args << "--readback-linear-images" << BoolArg(info.readback_linear_images); if (info.tessellation_enabled) { args << "--tessellation"; diff --git a/src/main.cpp b/src/main.cpp index be1607bf5..f821e931d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -61,6 +61,8 @@ static void PrintUsage() { ::printf( " --gpu Vulkan physical device index. Default: auto.\n"); ::printf(" --fullscreen Run in borderless desktop fullscreen.\n"); + ::printf( + " --hide-cursor Hide the cursor after 2 s idle. Default: off.\n"); ::printf(" --vr Enable the virtual VR headset.\n"); ::printf(" --amd-cpu Apply AMD CPU instruction patches.\n"); ::printf(" --vblank-frequency Virtual vblank frequency. Default: 60.\n"); @@ -205,6 +207,11 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he continue; } + if (arg == "--hide-cursor") { + options.config.hide_cursor_enabled = true; + continue; + } + if (arg == "--vr") { options.config.vr_enabled = true; continue;