From 520320535e41988a8c8fc15535de5b213625508c Mon Sep 17 00:00:00 2001 From: Stenzek Date: Thu, 2 Mar 2023 22:01:14 +1000 Subject: [PATCH] Qt: Clear all keyboard bind states when focus is lost --- pcsx2-qt/QtHost.cpp | 9 ++++- pcsx2/Frontend/InputManager.cpp | 65 +++++++++++++++++++++++++++++++++ pcsx2/Frontend/InputManager.h | 3 ++ 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index 51fbdb4055..97a542b2a3 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -795,17 +795,22 @@ void EmuThread::onDisplayWindowResized(int width, int height, float scale) void EmuThread::onApplicationStateChanged(Qt::ApplicationState state) { // NOTE: This is executed on the emu thread, not UI thread. - if (!m_pause_on_focus_loss || !VMManager::HasValidVM()) + if (!VMManager::HasValidVM()) return; const bool focus_loss = (state != Qt::ApplicationActive); if (focus_loss) { - if (!m_was_paused_by_focus_loss && VMManager::GetState() == VMState::Running) + if (m_pause_on_focus_loss && !m_was_paused_by_focus_loss && VMManager::GetState() == VMState::Running) { m_was_paused_by_focus_loss = true; VMManager::SetPaused(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 { diff --git a/pcsx2/Frontend/InputManager.cpp b/pcsx2/Frontend/InputManager.cpp index c6eddc592a..3700172e2f 100644 --- a/pcsx2/Frontend/InputManager.cpp +++ b/pcsx2/Frontend/InputManager.cpp @@ -923,6 +923,71 @@ bool InputManager::ProcessEvent(InputBindingKey key, float value, bool skip_butt 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/pcsx2/Frontend/InputManager.h b/pcsx2/Frontend/InputManager.h index afd8424768..894af7e650 100644 --- a/pcsx2/Frontend/InputManager.h +++ b/pcsx2/Frontend/InputManager.h @@ -257,6 +257,9 @@ namespace InputManager /// 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);