Skip to content

Commit

Permalink
Qt: Fix firing multiple bindings with chords
Browse files Browse the repository at this point in the history
  • Loading branch information
stenzek authored and refractionpcsx2 committed Apr 27, 2022
1 parent 8e23d8d commit a524410
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 6 deletions.
28 changes: 25 additions & 3 deletions pcsx2-qt/DisplayWidget.cpp
Expand Up @@ -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);
}
Expand Down Expand Up @@ -142,12 +143,33 @@ bool DisplayWidget::event(QEvent* event)
case QEvent::KeyRelease:
{
const QKeyEvent* key_event = static_cast<QKeyEvent*>(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<int>(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;
}

Expand Down
4 changes: 3 additions & 1 deletion pcsx2-qt/DisplayWidget.h
Expand Up @@ -18,6 +18,7 @@
#include <QtWidgets/QStackedWidget>
#include <QtWidgets/QWidget>
#include <optional>
#include <vector>

class DisplayWidget final : public QWidget
{
Expand All @@ -42,7 +43,7 @@ class DisplayWidget final : public QWidget
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);
Expand All @@ -54,6 +55,7 @@ class DisplayWidget final : public QWidget
QPoint m_relative_mouse_start_position{};
QPoint m_relative_mouse_last_position{};
bool m_relative_mouse_enabled = false;
std::vector<int> m_keys_pressed_with_modifiers;
};

class DisplayContainer final : public QStackedWidget
Expand Down
2 changes: 1 addition & 1 deletion pcsx2-qt/EmuThread.cpp
Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion pcsx2-qt/EmuThread.h
Expand Up @@ -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;
Expand Down
44 changes: 44 additions & 0 deletions pcsx2/Frontend/InputManager.cpp
Expand Up @@ -650,9 +650,16 @@ 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++)
Expand All @@ -663,6 +670,43 @@ bool InputManager::InvokeEvents(InputBindingKey key, float value)
const u8 bit = static_cast<u8>(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();

// 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<u8>(1) << i;
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));
Expand Down

0 comments on commit a524410

Please sign in to comment.