From 14181ec70d63f9d9bf90b7f0af6a455d3ee62e96 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Tue, 21 Jun 2022 19:31:34 +1000 Subject: [PATCH] InputManager: Fix chord bindings when activating in reverse --- pcsx2-qt/EmuThread.cpp | 8 +-- pcsx2/Frontend/InputManager.cpp | 115 ++++++++++++++++---------------- pcsx2/Frontend/InputManager.h | 7 +- pcsx2/GS/GS.cpp | 30 ++++----- pcsx2/VMManager.cpp | 32 ++++----- 5 files changed, 97 insertions(+), 95 deletions(-) diff --git a/pcsx2-qt/EmuThread.cpp b/pcsx2-qt/EmuThread.cpp index 1186591775..0ddd118278 100644 --- a/pcsx2-qt/EmuThread.cpp +++ b/pcsx2-qt/EmuThread.cpp @@ -952,7 +952,7 @@ SysMtgsThread& GetMTGS() // ------------------------------------------------------------------------ BEGIN_HOTKEY_LIST(g_host_hotkeys) -DEFINE_HOTKEY("ShutdownVM", "System", "Shut Down Virtual Machine", [](bool pressed) { +DEFINE_HOTKEY("ShutdownVM", "System", "Shut Down Virtual Machine", [](s32 pressed) { if (!pressed) { // run it on the host thread, that way we get the confirm prompt (if enabled) @@ -960,16 +960,16 @@ DEFINE_HOTKEY("ShutdownVM", "System", "Shut Down Virtual Machine", [](bool press Q_ARG(bool, true), Q_ARG(bool, true), Q_ARG(bool, true)); } }) -DEFINE_HOTKEY("TogglePause", "System", "Toggle Pause", [](bool pressed) { +DEFINE_HOTKEY("TogglePause", "System", "Toggle Pause", [](s32 pressed) { if (!pressed) g_emu_thread->setVMPaused(VMManager::GetState() != VMState::Paused); }) -DEFINE_HOTKEY("ToggleFullscreen", "General", "Toggle Fullscreen", [](bool pressed) { +DEFINE_HOTKEY("ToggleFullscreen", "General", "Toggle Fullscreen", [](s32 pressed) { if (!pressed) g_emu_thread->toggleFullscreen(); }) // Input Recording Hot Keys -DEFINE_HOTKEY("InputRecToggleMode", "Input Recording", "Toggle Recording Mode", [](bool pressed) { +DEFINE_HOTKEY("InputRecToggleMode", "Input Recording", "Toggle Recording Mode", [](s32 pressed) { if (!pressed) // ?? - not pressed so it is on key up? { g_InputRecordingControls.RecordModeToggle(); diff --git a/pcsx2/Frontend/InputManager.cpp b/pcsx2/Frontend/InputManager.cpp index 2b57caff98..ab135f423b 100644 --- a/pcsx2/Frontend/InputManager.cpp +++ b/pcsx2/Frontend/InputManager.cpp @@ -622,51 +622,20 @@ bool InputManager::IsAxisHandler(const InputEventHandler& handler) bool InputManager::InvokeEvents(InputBindingKey key, float value, GenericInputBinding generic_key) { - if (PreprocessEvent(key, value, generic_key)) + if (DoEventHook(key, value)) return true; + // If imgui ate the event, don't fire our handlers. + const bool skip_button_handlers = PreprocessEvent(key, value, generic_key); + // find all the bindings associated with this key const InputBindingKey masked_key = key.MaskDirection(); const auto range = s_binding_map.equal_range(masked_key); if (range.first == s_binding_map.end()) return false; - // Workaround for modifier keys. Basically, if we bind say, F1 and Shift+F1, and press shift - // and then F1, we'll fire bindings for both F1 and Shift+F1, when we really only want to fire - // the binding for Shift+F1. So, let's search through the binding list, and see if there's a - // "longer" binding (more keys), and if so, only activate that and not the shorter binding(s). - const InputBinding* longest_hotkey_binding = nullptr; - for (auto it = range.first; it != range.second; ++it) - { - InputBinding* binding = it->second.get(); - if (IsAxisHandler(binding->handler)) - continue; - - // find the key which matches us - for (u32 i = 0; i < binding->num_keys; i++) - { - if (binding->keys[i].MaskDirection() != masked_key) - continue; - - const u8 bit = static_cast(1) << i; - const bool negative = binding->keys[i].negative; - const bool new_state = (negative ? (value < 0.0f) : (value > 0.0f)); - const u8 new_mask = (new_state ? (binding->current_mask | bit) : (binding->current_mask & ~bit)); - const bool prev_full_state = (binding->current_mask == binding->full_mask); - const bool new_full_state = (new_mask == binding->full_mask); - - // If we're activating this chord, block activation of other bindings with fewer keys. - if (prev_full_state || new_full_state) - { - if (!longest_hotkey_binding || longest_hotkey_binding->num_keys < binding->num_keys) - longest_hotkey_binding = binding; - } - - break; - } - } - // Now we can actually fire/activate bindings. + u32 min_num_keys = 0; for (auto it = range.first; it != range.second; ++it) { InputBinding* binding = it->second.get(); @@ -681,20 +650,6 @@ bool InputManager::InvokeEvents(InputBindingKey key, float value, GenericInputBi const bool negative = binding->keys[i].negative; const bool new_state = (negative ? (value < 0.0f) : (value > 0.0f)); - // Don't register the key press when we're part of a longer chord. That way, - // the state won't change, and it won't get the released event either. - if (longest_hotkey_binding && new_state && !IsAxisHandler(binding->handler) && - binding->num_keys != longest_hotkey_binding->num_keys) - { - continue; - } - - // update state based on whether the whole chord was activated - const u8 new_mask = (new_state ? (binding->current_mask | bit) : (binding->current_mask & ~bit)); - const bool prev_full_state = (binding->current_mask == binding->full_mask); - const bool new_full_state = (new_mask == binding->full_mask); - binding->current_mask = new_mask; - // invert if we're negative, since the handler expects 0..1 const float value_to_pass = (negative ? ((value < 0.0f) ? -value : 0.0f) : (value > 0.0f) ? value : 0.0f); @@ -704,13 +659,60 @@ bool InputManager::InvokeEvents(InputBindingKey key, float value, GenericInputBi // and 0 on release (when the full state changes). if (IsAxisHandler(binding->handler)) { - if (prev_full_state != new_full_state || value_to_pass >= 0.0f) + if (value_to_pass >= 0.0f) std::get(binding->handler)(value_to_pass); } - else + else if (binding->num_keys >= min_num_keys) { - if (prev_full_state != new_full_state) - std::get(binding->handler)(value_to_pass > 0.0f); + // update state based on whether the whole chord was activated + const u8 new_mask = (new_state ? (binding->current_mask | bit) : (binding->current_mask & ~bit)); + const bool prev_full_state = (binding->current_mask == binding->full_mask); + const bool new_full_state = (new_mask == binding->full_mask); + binding->current_mask = new_mask; + + // Workaround for multi-key bindings that share the same keys. + if (binding->num_keys > 1 && new_full_state && prev_full_state != new_full_state && + range.first != range.second) + { + // Because the binding map isn't ordered, we could iterate in the order of Shift+F1 and then + // F1, which would mean that F1 wouldn't get cancelled and still activate. So, to handle this + // case, we skip activating any future bindings with a fewer number of keys. + min_num_keys = std::max(min_num_keys, binding->num_keys); + + // Basically, if we bind say, F1 and Shift+F1, and press shift and then F1, we'll fire bindings + // for both F1 and Shift+F1, when we really only want to fire the binding for Shift+F1. So, + // when we activate a multi-key chord (key press), we go through the binding map for all the + // other keys in the chord, and cancel them if they have a shorter chord. If they're longer, + // they could still activate and take precedence over us, so we leave them alone. + for (u32 i = 0; i < binding->num_keys; i++) + { + const auto range = s_binding_map.equal_range(binding->keys[i].MaskDirection()); + for (auto it = range.first; it != range.second; ++it) + { + InputBinding* other_binding = it->second.get(); + if (other_binding == binding || IsAxisHandler(other_binding->handler) || + other_binding->num_keys >= binding->num_keys) + { + continue; + } + + // We only need to cancel the binding if it was fully active before. Which in the above + // case of Shift+F1 / F1, it will be. + if (other_binding->current_mask == other_binding->full_mask) + std::get(other_binding->handler)(-1); + + // Zero out the current bits so that we don't release this binding, if the other part + // of the chord releases first. + other_binding->current_mask = 0; + } + } + } + + if (prev_full_state != new_full_state && binding->num_keys >= min_num_keys) + { + const s32 pressed = skip_button_handlers ? -1 : static_cast(value_to_pass > 0.0f); + std::get(binding->handler)(pressed); + } } // bail out, since we shouldn't have the same key twice in the chord @@ -723,9 +725,6 @@ bool InputManager::InvokeEvents(InputBindingKey key, float value, GenericInputBi bool InputManager::PreprocessEvent(InputBindingKey key, float value, GenericInputBinding generic_key) { - if (DoEventHook(key, value)) - return true; - // does imgui want the event? if (key.source_type == InputSourceType::Keyboard) { @@ -739,7 +738,7 @@ bool InputManager::PreprocessEvent(InputBindingKey key, float value, GenericInpu } else if (generic_key != GenericInputBinding::Unknown) { - if (ImGuiManager::ProcessGenericInputEvent(generic_key, value)) + if (ImGuiManager::ProcessGenericInputEvent(generic_key, value) && value != 0.0f) return true; } diff --git a/pcsx2/Frontend/InputManager.h b/pcsx2/Frontend/InputManager.h index 2b3b585d42..0b9f721dbb 100644 --- a/pcsx2/Frontend/InputManager.h +++ b/pcsx2/Frontend/InputManager.h @@ -90,7 +90,7 @@ struct InputBindingKeyHash }; /// Callback type for a binary event. Usually used for hotkeys. -using InputButtonEventHandler = std::function; +using InputButtonEventHandler = std::function; /// Callback types for a normalized event. Usually used for pads. using InputAxisEventHandler = std::function; @@ -110,12 +110,15 @@ struct InputInterceptHook }; /// Hotkeys are actions (e.g. toggle frame limit) which can be bound to keys or chords. +/// The handler is called with an integer representing the key state, where 0 means that +/// one or more keys were released, 1 means all the keys were pressed, and -1 means that +/// the hotkey was cancelled due to a chord with more keys being activated. struct HotkeyInfo { const char* name; const char* category; const char* display_name; - void (*handler)(bool pressed); + void (*handler)(s32 pressed); }; #define DECLARE_HOTKEY_LIST(name) extern const HotkeyInfo name[] #define BEGIN_HOTKEY_LIST(name) const HotkeyInfo name[] = { diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index ed46f04eda..ddf1823a82 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -1623,7 +1623,7 @@ static void HotkeyAdjustZoom(double delta) } BEGIN_HOTKEY_LIST(g_gs_hotkeys) - {"Screenshot", "Graphics", "Save Screenshot", [](bool pressed) { + {"Screenshot", "Graphics", "Save Screenshot", [](s32 pressed) { if (!pressed) { GetMTGS().RunOnGSThread([]() { @@ -1631,7 +1631,7 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys) }); } }}, - {"GSDumpSingleFrame", "Graphics", "Save Single Frame GS Dump", [](bool pressed) { + {"GSDumpSingleFrame", "Graphics", "Save Single Frame GS Dump", [](s32 pressed) { if (!pressed) { GetMTGS().RunOnGSThread([]() { @@ -1639,27 +1639,27 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys) }); } }}, - {"GSDumpMultiFrame", "Graphics", "Save Multi Frame GS Dump", [](bool pressed) { + {"GSDumpMultiFrame", "Graphics", "Save Multi Frame GS Dump", [](s32 pressed) { GetMTGS().RunOnGSThread([pressed]() { - if (pressed) + if (pressed > 0) GSQueueSnapshot(std::string(), std::numeric_limits::max()); else GSStopGSDump(); }); }}, - {"ToggleSoftwareRendering", "Graphics", "Toggle Software Rendering", [](bool pressed) { + {"ToggleSoftwareRendering", "Graphics", "Toggle Software Rendering", [](s32 pressed) { if (!pressed) GetMTGS().ToggleSoftwareRendering(); }}, - {"IncreaseUpscaleMultiplier", "Graphics", "Increase Upscale Multiplier", [](bool pressed) { + {"IncreaseUpscaleMultiplier", "Graphics", "Increase Upscale Multiplier", [](s32 pressed) { if (!pressed) HotkeyAdjustUpscaleMultiplier(1); }}, - {"DecreaseUpscaleMultiplier", "Graphics", "Decrease Upscale Multiplier", [](bool pressed) { + {"DecreaseUpscaleMultiplier", "Graphics", "Decrease Upscale Multiplier", [](s32 pressed) { if (!pressed) HotkeyAdjustUpscaleMultiplier(-1); }}, - {"CycleAspectRatio", "Graphics", "Cycle Aspect Ratio", [](bool pressed) { + {"CycleAspectRatio", "Graphics", "Cycle Aspect Ratio", [](s32 pressed) { if (pressed) return; @@ -1667,7 +1667,7 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys) EmuConfig.CurrentAspectRatio = static_cast((static_cast(EmuConfig.CurrentAspectRatio) + 1) % static_cast(AspectRatioType::MaxCount)); Host::AddKeyedFormattedOSDMessage("CycleAspectRatio", 10.0f, "Aspect ratio set to '%s'.", Pcsx2Config::GSOptions::AspectRatioNames[static_cast(EmuConfig.CurrentAspectRatio)]); }}, - {"CycleMipmapMode", "Graphics", "Cycle Hardware Mipmapping", [](bool pressed) { + {"CycleMipmapMode", "Graphics", "Cycle Hardware Mipmapping", [](s32 pressed) { if (pressed) return; @@ -1684,7 +1684,7 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys) g_gs_renderer->PurgePool(); }); }}, - {"CycleInterlaceMode", "Graphics", "Cycle Deinterlace Mode", [](bool pressed) { + {"CycleInterlaceMode", "Graphics", "Cycle Deinterlace Mode", [](s32 pressed) { if (pressed) return; @@ -1705,15 +1705,15 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys) GetMTGS().RunOnGSThread([new_mode]() { GSConfig.InterlaceMode = new_mode; }); }}, - {"ZoomIn", "Graphics", "Zoom In", [](bool pressed) { + {"ZoomIn", "Graphics", "Zoom In", [](s32 pressed) { if (!pressed) HotkeyAdjustZoom(1.0); }}, - {"ZoomOut", "Graphics", "Zoom Out", [](bool pressed) { + {"ZoomOut", "Graphics", "Zoom Out", [](s32 pressed) { if (!pressed) HotkeyAdjustZoom(-1.0); }}, - {"ToggleTextureDumping", "Graphics", "Toggle Texture Dumping", [](bool pressed) { + {"ToggleTextureDumping", "Graphics", "Toggle Texture Dumping", [](s32 pressed) { if (!pressed) { EmuConfig.GS.DumpReplaceableTextures = !EmuConfig.GS.DumpReplaceableTextures; @@ -1721,7 +1721,7 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys) GetMTGS().ApplySettings(); } }}, - {"ToggleTextureReplacements", "Graphics", "Toggle Texture Replacements", [](bool pressed) { + {"ToggleTextureReplacements", "Graphics", "Toggle Texture Replacements", [](s32 pressed) { if (!pressed) { EmuConfig.GS.LoadTextureReplacements = !EmuConfig.GS.LoadTextureReplacements; @@ -1729,7 +1729,7 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys) GetMTGS().ApplySettings(); } }}, - {"ReloadTextureReplacements", "Graphics", "Reload Texture Replacements", [](bool pressed) { + {"ReloadTextureReplacements", "Graphics", "Reload Texture Replacements", [](s32 pressed) { if (!pressed) { if (!EmuConfig.GS.LoadTextureReplacements) diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index db99442342..2fc54c6152 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -1713,7 +1713,7 @@ static void HotkeySaveStateSlot(s32 slot) } BEGIN_HOTKEY_LIST(g_vm_manager_hotkeys) -DEFINE_HOTKEY("ToggleFrameLimit", "System", "Toggle Frame Limit", [](bool pressed) { +DEFINE_HOTKEY("ToggleFrameLimit", "System", "Toggle Frame Limit", [](s32 pressed) { if (!pressed) { VMManager::SetLimiterMode((EmuConfig.LimiterMode != LimiterModeType::Unlimited) ? @@ -1721,7 +1721,7 @@ DEFINE_HOTKEY("ToggleFrameLimit", "System", "Toggle Frame Limit", [](bool presse LimiterModeType::Nominal); } }) -DEFINE_HOTKEY("ToggleTurbo", "System", "Toggle Turbo", [](bool pressed) { +DEFINE_HOTKEY("ToggleTurbo", "System", "Toggle Turbo", [](s32 pressed) { if (!pressed) { VMManager::SetLimiterMode((EmuConfig.LimiterMode != LimiterModeType::Turbo) ? @@ -1729,21 +1729,21 @@ DEFINE_HOTKEY("ToggleTurbo", "System", "Toggle Turbo", [](bool pressed) { LimiterModeType::Nominal); } }) -DEFINE_HOTKEY("HoldTurbo", "System", "Turbo (Hold)", [](bool pressed) { - if (pressed && !s_limiter_mode_prior_to_hold_interaction.has_value()) +DEFINE_HOTKEY("HoldTurbo", "System", "Turbo (Hold)", [](s32 pressed) { + if (pressed > 0 && !s_limiter_mode_prior_to_hold_interaction.has_value()) { s_limiter_mode_prior_to_hold_interaction = VMManager::GetLimiterMode(); VMManager::SetLimiterMode((s_limiter_mode_prior_to_hold_interaction.value() != LimiterModeType::Turbo) ? LimiterModeType::Turbo : LimiterModeType::Nominal); } - else if (!pressed && s_limiter_mode_prior_to_hold_interaction.has_value()) + else if (pressed >= 0 && s_limiter_mode_prior_to_hold_interaction.has_value()) { VMManager::SetLimiterMode(s_limiter_mode_prior_to_hold_interaction.value()); s_limiter_mode_prior_to_hold_interaction.reset(); } }) -DEFINE_HOTKEY("ToggleSlowMotion", "System", "Toggle Slow Motion", [](bool pressed) { +DEFINE_HOTKEY("ToggleSlowMotion", "System", "Toggle Slow Motion", [](s32 pressed) { if (!pressed) { VMManager::SetLimiterMode((EmuConfig.LimiterMode != LimiterModeType::Slomo) ? @@ -1751,42 +1751,42 @@ DEFINE_HOTKEY("ToggleSlowMotion", "System", "Toggle Slow Motion", [](bool presse LimiterModeType::Nominal); } }) -DEFINE_HOTKEY("IncreaseSpeed", "System", "Increase Target Speed", [](bool pressed) { +DEFINE_HOTKEY("IncreaseSpeed", "System", "Increase Target Speed", [](s32 pressed) { if (!pressed) HotkeyAdjustTargetSpeed(0.1); }) -DEFINE_HOTKEY("DecreaseSpeed", "System", "Decrease Target Speed", [](bool pressed) { +DEFINE_HOTKEY("DecreaseSpeed", "System", "Decrease Target Speed", [](s32 pressed) { if (!pressed) HotkeyAdjustTargetSpeed(-0.1); }) -DEFINE_HOTKEY("ResetVM", "System", "Reset Virtual Machine", [](bool pressed) { +DEFINE_HOTKEY("ResetVM", "System", "Reset Virtual Machine", [](s32 pressed) { if (!pressed && VMManager::HasValidVM()) VMManager::Reset(); }) -DEFINE_HOTKEY("FrameAdvance", "System", "Frame Advance", [](bool pressed) { +DEFINE_HOTKEY("FrameAdvance", "System", "Frame Advance", [](s32 pressed) { if (!pressed) VMManager::FrameAdvance(1); }) -DEFINE_HOTKEY("PreviousSaveStateSlot", "Save States", "Select Previous Save Slot", [](bool pressed) { +DEFINE_HOTKEY("PreviousSaveStateSlot", "Save States", "Select Previous Save Slot", [](s32 pressed) { if (!pressed) HotkeyCycleSaveSlot(-1); }) -DEFINE_HOTKEY("NextSaveStateSlot", "Save States", "Select Next Save Slot", [](bool pressed) { +DEFINE_HOTKEY("NextSaveStateSlot", "Save States", "Select Next Save Slot", [](s32 pressed) { if (!pressed) HotkeyCycleSaveSlot(1); }) -DEFINE_HOTKEY("SaveStateToSlot", "Save States", "Save State To Selected Slot", [](bool pressed) { +DEFINE_HOTKEY("SaveStateToSlot", "Save States", "Save State To Selected Slot", [](s32 pressed) { if (!pressed) VMManager::SaveStateToSlot(s_current_save_slot); }) -DEFINE_HOTKEY("LoadStateFromSlot", "Save States", "Load State From Selected Slot", [](bool pressed) { +DEFINE_HOTKEY("LoadStateFromSlot", "Save States", "Load State From Selected Slot", [](s32 pressed) { if (!pressed) HotkeyLoadStateSlot(s_current_save_slot); }) #define DEFINE_HOTKEY_SAVESTATE_X(slotnum, slotnumstr) DEFINE_HOTKEY("SaveStateToSlot" #slotnum, \ - "Save States", "Save State To Slot " #slotnumstr, [](bool pressed) { if (!pressed) HotkeySaveStateSlot(slotnum); }) + "Save States", "Save State To Slot " #slotnumstr, [](s32 pressed) { if (!pressed) HotkeySaveStateSlot(slotnum); }) DEFINE_HOTKEY_SAVESTATE_X(1, 01) DEFINE_HOTKEY_SAVESTATE_X(2, 02) DEFINE_HOTKEY_SAVESTATE_X(3, 03) @@ -1798,7 +1798,7 @@ DEFINE_HOTKEY_SAVESTATE_X(8, 08) DEFINE_HOTKEY_SAVESTATE_X(9, 09) DEFINE_HOTKEY_SAVESTATE_X(10, 10) #define DEFINE_HOTKEY_LOADSTATE_X(slotnum, slotnumstr) DEFINE_HOTKEY("LoadStateFromSlot" #slotnum, \ - "Save States", "Load State From Slot " #slotnumstr, [](bool pressed) { \ + "Save States", "Load State From Slot " #slotnumstr, [](s32 pressed) { \ if (!pressed) \ HotkeyLoadStateSlot(slotnum); \ })