Qt: Fix firing multiple bindings with chords

This commit is contained in:
Connor McLaughlin 2022-04-25 15:25:09 +10:00 committed by refractionpcsx2
parent 8e23d8d557
commit a524410b0a
5 changed files with 74 additions and 6 deletions

View File

@ -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<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;
}

View File

@ -18,6 +18,7 @@
#include <QtWidgets/QStackedWidget>
#include <QtWidgets/QWidget>
#include <optional>
#include <vector>
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<int> m_keys_pressed_with_modifiers;
};
class DisplayContainer final : public QStackedWidget

View File

@ -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);
}

View File

@ -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;

View File

@ -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<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();
@ -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);