From e2aa1ae24ceaffbe6d40a7c823b6efba9676d35c Mon Sep 17 00:00:00 2001 From: Stenzek Date: Thu, 25 Dec 2025 19:55:47 +1000 Subject: [PATCH] Qt: Fix handling of mouse double-click events - Fixes double-click events with mouse binds getting dropped. - Fixes early release event when toggling fullscreen. --- src/duckstation-qt/displaywidget.cpp | 10 ++++++--- src/duckstation-qt/mainwindow.h | 6 ++--- src/util/imgui_manager.cpp | 33 ++++++++++++++++++++++++++++ src/util/imgui_manager.h | 3 +++ 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/duckstation-qt/displaywidget.cpp b/src/duckstation-qt/displaywidget.cpp index ea31627f1..ea2a2b18e 100644 --- a/src/duckstation-qt/displaywidget.cpp +++ b/src/duckstation-qt/displaywidget.cpp @@ -327,13 +327,11 @@ bool DisplayWidget::event(QEvent* event) case QEvent::MouseButtonDblClick: { - // since we don't get press and release events for double-click, we need to send both the down and up - // otherwise the second click in a double click won't be registered by the input system + // we don't get press events for double-click, the dblclick event is substituted instead if (!m_relative_mouse_enabled || !InputManager::IsUsingRawInput()) { const u32 button_index = CountTrailingZeros(static_cast(static_cast(event)->button())); emit windowMouseButtonEvent(static_cast(button_index), true); - emit windowMouseButtonEvent(static_cast(button_index), false); } // don't toggle fullscreen when we're bound.. that wouldn't end well. @@ -344,6 +342,12 @@ bool DisplayWidget::event(QEvent* event) Core::GetBoolSettingValue("Main", "DoubleClickTogglesFullscreen", true)) { g_core_thread->toggleFullscreen(); + + // when swapping fullscreen, the window is going to get recreated, and we won't get the release event. + // therefore we need to trigger it here instead, otherwise it gets lost and imgui is confused. + // skip this if we're not running on wankland or using render-to-main, since the window is preserved. + if (QtHost::IsDisplayWidgetContainerNeeded() || g_main_window->canRenderToMainWindow()) + Host::RunOnCoreThread(&ImGuiManager::ClearMouseButtonState); } return true; diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index d88b373d4..b95bb2196 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -119,6 +119,9 @@ public: /// Updates debug menu visibility (hides if disabled). void updateDebugMenuVisibility(); + /// Returns true if rendering to the main window should be allowed. + bool canRenderToMainWindow() const; + void refreshGameList(bool invalidate_cache); void cancelGameListRefresh(); QIcon getIconForGame(const QString& path); @@ -183,9 +186,6 @@ private: void saveDisplayWindowGeometryToConfig(); void restoreDisplayWindowGeometryFromConfig(); - /// Returns true if rendering to the main window should be allowed. - bool canRenderToMainWindow() const; - /// Returns true if the separate-window display widget should use the main window coordinates. bool useMainWindowGeometryForDisplayWindow() const; diff --git a/src/util/imgui_manager.cpp b/src/util/imgui_manager.cpp index 2010d2ebe..82eedfb3e 100644 --- a/src/util/imgui_manager.cpp +++ b/src/util/imgui_manager.cpp @@ -1415,6 +1415,39 @@ bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, float value return s_state.imgui_wants_keyboard.load(std::memory_order_acquire); } +void ImGuiManager::ClearMouseButtonState() +{ + if (!s_state.imgui_context) + return; + + GPUThread::RunOnThread([]() { + if (!s_state.imgui_context) + return; + + ImGuiIO& io = s_state.imgui_context->IO; + for (int i = 0; i < ImGuiMouseButton_COUNT; i++) + { + bool current_state = io.MouseDown[i]; + for (int n = s_state.imgui_context->InputEventsQueue.Size - 1; n >= 0; n--) + { + const ImGuiInputEvent& event = s_state.imgui_context->InputEventsQueue[n]; + if (event.Type == ImGuiInputEventType_MouseButton && event.MouseButton.Button == i) + { + current_state = event.MouseButton.Down; + break; + } + } + + // not down? + if (!current_state) + continue; + + // Queue a button up event. + s_state.imgui_context->IO.AddMouseButtonEvent(i, false); + } + }); +} + const char* ImGuiManager::GetClipboardTextImpl(ImGuiContext* ctx) { const std::string text = Host::GetClipboardText(); diff --git a/src/util/imgui_manager.h b/src/util/imgui_manager.h index e8c1093e2..e207f1878 100644 --- a/src/util/imgui_manager.h +++ b/src/util/imgui_manager.h @@ -174,6 +174,9 @@ bool ProcessHostKeyEvent(InputBindingKey key, float value); /// Called on the CPU thread when any input event fires. Allows imgui to take over controller navigation. bool ProcessGenericInputEvent(GenericInputBinding key, float value); +/// Resets the state of ImGui mouse buttons, call after the window is changed and events get lost. +void ClearMouseButtonState(); + /// Sets an image and scale for a software cursor. Software cursors can be used for things like crosshairs. void SetSoftwareCursor(u32 index, std::string image_path, float image_scale, u32 multiply_color = 0xFFFFFF); bool HasSoftwareCursor(u32 index);