1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
From 801d31a926a4ad7ceeeecc64b273cd514550d613 Mon Sep 17 00:00:00 2001
From: Nicolas Fella <nicolas.fella@gmx.de>
Date: Tue, 9 Jul 2024 12:41:14 +0200
Subject: [PATCH] plugins/stickykeys: Unlatch keys after mouse click
This is how X11 behaves and it allows things like Ctrl+Click to work as expected with sticky keys
---
autotests/integration/sticky_keys_test.cpp | 57 ++++++++++++++++++++++
src/plugins/stickykeys/stickykeys.cpp | 27 ++++++++++
src/plugins/stickykeys/stickykeys.h | 1 +
3 files changed, 85 insertions(+)
--- a/autotests/integration/sticky_keys_test.cpp
+++ b/autotests/integration/sticky_keys_test.cpp
@@ -37,6 +37,8 @@ private Q_SLOTS:
void testStick_data();
void testLock();
void testLock_data();
+ void testMouse();
+ void testMouse_data();
void testDisableTwoKeys();
};
@@ -280,6 +282,61 @@ void StickyKeysTest::testDisableTwoKeys(
Test::keyboardKeyReleased(KEY_A, ++timestamp);
QVERIFY(!modifierSpy.wait(10));
}
+
+void StickyKeysTest::testMouse_data()
+{
+ QTest::addColumn<int>("modifierKey");
+ QTest::addColumn<int>("expectedMods");
+
+ QTest::addRow("Shift") << KEY_LEFTSHIFT << 1;
+ QTest::addRow("Ctrl") << KEY_LEFTCTRL << 4;
+ QTest::addRow("Alt") << KEY_LEFTALT << 8;
+ QTest::addRow("AltGr") << KEY_RIGHTALT << 128;
+}
+
+void StickyKeysTest::testMouse()
+{
+ QFETCH(int, modifierKey);
+ QFETCH(int, expectedMods);
+
+ std::unique_ptr<KWayland::Client::Keyboard> keyboard(Test::waylandSeat()->createKeyboard());
+
+ std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
+ QVERIFY(surface != nullptr);
+ std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
+ QVERIFY(shellSurface != nullptr);
+ Window *waylandWindow = Test::renderAndWaitForShown(surface.get(), QSize(10, 10), Qt::blue);
+ QVERIFY(waylandWindow);
+
+ QSignalSpy modifierSpy(keyboard.get(), &KWayland::Client::Keyboard::modifiersChanged);
+ QVERIFY(modifierSpy.wait());
+ modifierSpy.clear();
+
+ quint32 timestamp = 0;
+
+ // press mod to latch it
+ Test::keyboardKeyPressed(modifierKey, ++timestamp);
+ QVERIFY(modifierSpy.wait());
+ // arguments are: quint32 depressed, quint32 latched, quint32 locked, quint32 group
+ QCOMPARE(modifierSpy.first()[0], expectedMods); // verify that mod is depressed
+ QCOMPARE(modifierSpy.first()[1], expectedMods); // verify that mod is latched
+
+ modifierSpy.clear();
+ // release mod, the modifier should still be latched
+ Test::keyboardKeyReleased(modifierKey, ++timestamp);
+ QVERIFY(modifierSpy.wait());
+ QCOMPARE(modifierSpy.first()[0], 0); // verify that mod is not depressed
+ QCOMPARE(modifierSpy.first()[1], expectedMods); // verify that mod is still latched
+
+ // press and release a mouse button, this unlatches the modifier
+ modifierSpy.clear();
+ Test::pointerButtonPressed(BTN_LEFT, ++timestamp);
+ QVERIFY(!modifierSpy.wait(10));
+ Test::pointerButtonReleased(BTN_LEFT, ++timestamp);
+ QVERIFY(modifierSpy.wait());
+ QCOMPARE(modifierSpy.first()[0], 0); // verify that mod is not depressed
+ QCOMPARE(modifierSpy.first()[1], 0); // verify that mod is not latched any more
+}
}
WAYLANDTEST_MAIN(KWin::StickyKeysTest)
--- a/src/plugins/stickykeys/stickykeys.cpp
+++ b/src/plugins/stickykeys/stickykeys.cpp
@@ -9,6 +9,8 @@
#include "keyboard_input.h"
#include "xkb.h"
+#include <QTimer>
+
#include <KLazyLocalizedString>
#if KWIN_BUILD_NOTIFICATIONS
#include <KNotification>
@@ -183,4 +185,29 @@ void StickyKeysFilter::disableStickyKeys
KWin::input()->uninstallInputEventFilter(this);
}
+bool StickyKeysFilter::pointerButton(KWin::PointerButtonEvent *event)
+{
+ if (event->state == KWin::PointerButtonState::Released) {
+ // unlatch all unlocked modifiers
+ for (auto it = m_keyStates.keyValueBegin(); it != m_keyStates.keyValueEnd(); ++it) {
+
+ if (it->second == Locked) {
+ continue;
+ }
+
+ it->second = KeyState::None;
+
+ KWin::input()->keyboard()->xkb()->setModifierLatched(keyToModifier(static_cast<Qt::Key>(it->first)), false);
+
+ // We need to delay the modifier update until the client received the mouse event, otherwise
+ // the updated modifiers arrive before the mouse event and e.g. Ctrl+Click won't work
+ QTimer::singleShot(0, this, [] {
+ KWin::input()->keyboard()->xkb()->forwardModifiers();
+ });
+ }
+ }
+
+ return false;
+}
+
#include "moc_stickykeys.cpp"
--- a/src/plugins/stickykeys/stickykeys.h
+++ b/src/plugins/stickykeys/stickykeys.h
@@ -18,6 +18,7 @@ public:
explicit StickyKeysFilter();
bool keyboardKey(KWin::KeyboardKeyEvent *event) override;
+ bool pointerButton(KWin::PointerButtonEvent *event) override;
enum KeyState {
None,
|