diff --git a/pcsx2-qt/DisplayWidget.cpp b/pcsx2-qt/DisplayWidget.cpp index 8dd92912ed..e4091ea2ac 100644 --- a/pcsx2-qt/DisplayWidget.cpp +++ b/pcsx2-qt/DisplayWidget.cpp @@ -42,6 +42,7 @@ DisplayWidget::DisplayWidget(QWidget* parent) setAttribute(Qt::WA_NativeWindow, true); setAttribute(Qt::WA_NoSystemBackground, true); setAttribute(Qt::WA_PaintOnScreen, true); + setAttribute(Qt::WA_KeyCompression, false); setFocusPolicy(Qt::StrongFocus); setMouseTracking(true); } @@ -142,12 +143,33 @@ bool DisplayWidget::event(QEvent* event) case QEvent::KeyRelease: { const QKeyEvent* key_event = static_cast(event); - if (!key_event->isAutoRepeat()) + if (key_event->isAutoRepeat()) + return true; + + // For some reason, Windows sends "fake" key events. + // Scenario: Press shift, press F1, release shift, release F1. + // Events: Shift=Pressed, F1=Pressed, Shift=Released, **F1=Pressed**, F1=Released. + // To work around this, we keep track of keys pressed with modifiers in a list, and + // discard the press event when it's been previously activated. It's pretty gross, + // but I can't think of a better way of handling it, and there doesn't appear to be + // any window flag which changes this behavior that I can see. + + const int key = key_event->key(); + const bool pressed = (key_event->type() == QEvent::KeyPress); + const auto it = std::find(m_keys_pressed_with_modifiers.begin(), m_keys_pressed_with_modifiers.end(), key); + if (it != m_keys_pressed_with_modifiers.end()) { - emit windowKeyEvent(key_event->key(), static_cast(key_event->modifiers()), - event->type() == QEvent::KeyPress); + if (pressed) + return true; + else + m_keys_pressed_with_modifiers.erase(it); + } + else if (key_event->modifiers() != Qt::NoModifier && pressed) + { + m_keys_pressed_with_modifiers.push_back(key); } + emit windowKeyEvent(key, pressed); return true; } diff --git a/pcsx2-qt/DisplayWidget.h b/pcsx2-qt/DisplayWidget.h index cffc351f99..7ed34295b4 100644 --- a/pcsx2-qt/DisplayWidget.h +++ b/pcsx2-qt/DisplayWidget.h @@ -18,6 +18,7 @@ #include #include #include +#include class DisplayWidget final : public QWidget { @@ -42,7 +43,7 @@ Q_SIGNALS: void windowResizedEvent(int width, int height, float scale); void windowRestoredEvent(); void windowClosedEvent(); - void windowKeyEvent(int key_code, int mods, bool pressed); + void windowKeyEvent(int key_code, bool pressed); void windowMouseMoveEvent(int x, int y); void windowMouseButtonEvent(int button, bool pressed); void windowMouseWheelEvent(const QPoint& angle_delta); @@ -54,6 +55,7 @@ private: QPoint m_relative_mouse_start_position{}; QPoint m_relative_mouse_last_position{}; bool m_relative_mouse_enabled = false; + std::vector m_keys_pressed_with_modifiers; }; class DisplayContainer final : public QStackedWidget diff --git a/pcsx2-qt/EmuThread.cpp b/pcsx2-qt/EmuThread.cpp index 2e627ba281..c131ce4ec8 100644 --- a/pcsx2-qt/EmuThread.cpp +++ b/pcsx2-qt/EmuThread.cpp @@ -568,7 +568,7 @@ void EmuThread::onDisplayWindowMouseButtonEvent(int button, bool pressed) void EmuThread::onDisplayWindowMouseWheelEvent(const QPoint& delta_angle) {} -void EmuThread::onDisplayWindowKeyEvent(int key, int mods, bool pressed) +void EmuThread::onDisplayWindowKeyEvent(int key, bool pressed) { InputManager::InvokeEvents(InputManager::MakeHostKeyboardKey(key), pressed ? 1.0f : 0.0f); } diff --git a/pcsx2-qt/EmuThread.h b/pcsx2-qt/EmuThread.h index a09c961233..d837d9f5b4 100644 --- a/pcsx2-qt/EmuThread.h +++ b/pcsx2-qt/EmuThread.h @@ -144,7 +144,7 @@ private Q_SLOTS: void onDisplayWindowMouseWheelEvent(const QPoint& delta_angle); void onDisplayWindowResized(int width, int height, float scale); void onDisplayWindowFocused(); - void onDisplayWindowKeyEvent(int key, int mods, bool pressed); + void onDisplayWindowKeyEvent(int key, bool pressed); private: QThread* m_ui_thread; diff --git a/pcsx2/Frontend/InputManager.cpp b/pcsx2/Frontend/InputManager.cpp index bb47ccda9d..9daafb59f7 100644 --- a/pcsx2/Frontend/InputManager.cpp +++ b/pcsx2/Frontend/InputManager.cpp @@ -650,6 +650,42 @@ bool InputManager::InvokeEvents(InputBindingKey key, float value) 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 (binding->handler.IsAxis()) + 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. for (auto it = range.first; it != range.second; ++it) { InputBinding* binding = it->second.get(); @@ -664,6 +700,14 @@ bool InputManager::InvokeEvents(InputBindingKey key, float value) 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 && !binding->handler.IsAxis() && + 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);