From d7372d244216c03b505d748709dfe7f42a240782 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Thu, 16 Mar 2023 21:25:45 +1000 Subject: [PATCH] Qt: Clear all keyboard bind states when focus is lost --- src/duckstation-qt/mainwindow.cpp | 28 +++++++----- src/frontend-common/input_manager.cpp | 65 +++++++++++++++++++++++++++ src/frontend-common/input_manager.h | 3 ++ 3 files changed, 85 insertions(+), 11 deletions(-) diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 859edde32..b7174b3fc 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -675,17 +675,22 @@ void MainWindow::onRunningGameChanged(const QString& filename, const QString& ga void MainWindow::onApplicationStateChanged(Qt::ApplicationState state) { - if (!s_system_valid || !g_settings.pause_on_focus_loss) + if (!s_system_valid) return; const bool focus_loss = (state != Qt::ApplicationActive); if (focus_loss) { - if (!m_was_paused_by_focus_loss && !s_system_paused) + if (g_settings.pause_on_focus_loss && !m_was_paused_by_focus_loss && !s_system_paused) { g_emu_thread->setSystemPaused(true); m_was_paused_by_focus_loss = true; } + + // Clear the state of all keyboard binds. + // That way, if we had a key held down, and lost focus, the bind won't be stuck enabled because we never + // got the key release message, because it happened in another window which "stole" the event. + InputManager::ClearBindStateFromSource(InputManager::MakeHostKeyboardKey(0)); } else { @@ -1728,7 +1733,8 @@ void MainWindow::updateEmulationActions(bool starting, bool running, bool cheevo if (!g_gdb_server->isListening() && g_settings.debugging.enable_gdb_server && starting) { - QMetaObject::invokeMethod(g_gdb_server, "start", Qt::QueuedConnection, Q_ARG(quint16, g_settings.debugging.gdb_server_port)); + QMetaObject::invokeMethod(g_gdb_server, "start", Qt::QueuedConnection, + Q_ARG(quint16, g_settings.debugging.gdb_server_port)); } else if (g_gdb_server->isListening() && !running) { @@ -2753,14 +2759,14 @@ void MainWindow::onToolsOpenDataDirectoryTriggered() void MainWindow::onSettingsTriggeredFromToolbar() { - if (s_system_valid) - { - m_settings_toolbar_menu->exec(QCursor::pos()); - } - else - { - doSettings(); - } + if (s_system_valid) + { + m_settings_toolbar_menu->exec(QCursor::pos()); + } + else + { + doSettings(); + } } void MainWindow::checkForUpdates(bool display_message) diff --git a/src/frontend-common/input_manager.cpp b/src/frontend-common/input_manager.cpp index 98893e08b..a22d9eb22 100644 --- a/src/frontend-common/input_manager.cpp +++ b/src/frontend-common/input_manager.cpp @@ -915,6 +915,71 @@ bool InputManager::InvokeEvents(InputBindingKey key, float value, GenericInputBi return true; } +void InputManager::ClearBindStateFromSource(InputBindingKey key) +{ + // Why are we doing it this way? Because any of the bindings could cause a reload and invalidate our iterators :(. + // Axis handlers should be fine, so we'll do those as a first pass. + for (const auto& [match_key, binding] : s_binding_map) + { + if (key.source_type != match_key.source_type || key.source_subtype != match_key.source_subtype || + key.source_index != match_key.source_index || !IsAxisHandler(binding->handler)) + { + continue; + } + + for (u32 i = 0; i < binding->num_keys; i++) + { + if (binding->keys[i].MaskDirection() != match_key) + continue; + + std::get(binding->handler)(0.0f); + break; + } + } + + // Now go through the button handlers, and pick them off. + bool matched; + do + { + matched = false; + + for (const auto& [match_key, binding] : s_binding_map) + { + if (key.source_type != match_key.source_type || key.source_subtype != match_key.source_subtype || + key.source_index != match_key.source_index || IsAxisHandler(binding->handler)) + { + continue; + } + + for (u32 i = 0; i < binding->num_keys; i++) + { + if (binding->keys[i].MaskDirection() != match_key) + continue; + + // Skip if we weren't pressed. + const u8 bit = static_cast(1) << i; + if ((binding->current_mask & bit) == 0) + continue; + + // Only fire handler if we're changing from active state. + const u8 current_mask = binding->current_mask; + binding->current_mask &= ~bit; + + if (current_mask == binding->full_mask) + { + std::get(binding->handler)(0.0f); + matched = true; + break; + } + } + + // Need to start again, might've reloaded. + if (matched) + break; + } + } while (matched); +} + bool InputManager::PreprocessEvent(InputBindingKey key, float value, GenericInputBinding generic_key) { // does imgui want the event? diff --git a/src/frontend-common/input_manager.h b/src/frontend-common/input_manager.h index 41a506a0c..5cab2a31d 100644 --- a/src/frontend-common/input_manager.h +++ b/src/frontend-common/input_manager.h @@ -284,6 +284,9 @@ void AddVibrationBinding(u32 pad_index, const InputBindingKey* motor_0_binding, /// Returns true if anything was bound to this key, otherwise false. bool InvokeEvents(InputBindingKey key, float value, GenericInputBinding generic_key = GenericInputBinding::Unknown); +/// Clears internal state for any binds with a matching source/index. +void ClearBindStateFromSource(InputBindingKey key); + /// Sets a hook which can be used to intercept events before they're processed by the normal bindings. /// This is typically used when binding new controls to detect what gets pressed. void SetHook(InputInterceptHook::Callback callback);