plugins/stickykeys: Disable when two keys are pressed

BUG: 464453
This commit is contained in:
Nicolas Fella 2024-07-23 16:48:40 +02:00
parent 7d3f58d0ca
commit e3ad1fa04f
3 changed files with 91 additions and 0 deletions

View file

@ -37,12 +37,14 @@ private Q_SLOTS:
void testStick_data(); void testStick_data();
void testLock(); void testLock();
void testLock_data(); void testLock_data();
void testDisableTwoKeys();
}; };
void StickyKeysTest::initTestCase() void StickyKeysTest::initTestCase()
{ {
KConfig kaccessConfig("kaccessrc"); KConfig kaccessConfig("kaccessrc");
kaccessConfig.group(QStringLiteral("Keyboard")).writeEntry("StickyKeys", true); kaccessConfig.group(QStringLiteral("Keyboard")).writeEntry("StickyKeys", true);
kaccessConfig.group(QStringLiteral("Keyboard")).writeEntry("StickyKeysAutoOff", true);
kaccessConfig.sync(); kaccessConfig.sync();
// Use a keyboard layout where right alt triggers Mod5/AltGr // Use a keyboard layout where right alt triggers Mod5/AltGr
@ -217,6 +219,69 @@ void StickyKeysTest::testLock()
Test::keyboardKeyReleased(modifierKey, ++timestamp); Test::keyboardKeyReleased(modifierKey, ++timestamp);
} }
void StickyKeysTest::testDisableTwoKeys()
{
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(KEY_LEFTSHIFT, ++timestamp);
QVERIFY(modifierSpy.wait());
// arguments are: quint32 depressed, quint32 latched, quint32 locked, quint32 group
QCOMPARE(modifierSpy.first()[0], 1); // verify that mod is depressed
QCOMPARE(modifierSpy.first()[1], 1); // verify that mod is latched
modifierSpy.clear();
// press key while modifier is pressed, this disables sticky keys
Test::keyboardKeyPressed(KEY_A, ++timestamp);
QVERIFY(modifierSpy.wait());
QCOMPARE(modifierSpy.first()[0], 1); // verify that mod is depressed
QCOMPARE(modifierSpy.first()[1], 0); // verify that mod is not latched any more
modifierSpy.clear();
Test::keyboardKeyReleased(KEY_A, ++timestamp);
// release mod, the modifier should not be depressed or latched
Test::keyboardKeyReleased(KEY_LEFTSHIFT, ++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
modifierSpy.clear();
// verify that sticky keys are not enabled any more
// press mod, should be depressed but not latched
Test::keyboardKeyPressed(KEY_LEFTCTRL, ++timestamp);
QVERIFY(modifierSpy.wait());
QCOMPARE(modifierSpy.first()[0], 4); // verify that mod is depressed
QCOMPARE(modifierSpy.first()[1], 0); // verify that mod is not latched
modifierSpy.clear();
// release mod, should not be depressed any more
Test::keyboardKeyReleased(KEY_LEFTCTRL, ++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
modifierSpy.clear();
Test::keyboardKeyPressed(KEY_A, ++timestamp);
QVERIFY(!modifierSpy.wait(10));
Test::keyboardKeyReleased(KEY_A, ++timestamp);
QVERIFY(!modifierSpy.wait(10));
}
} }
WAYLANDTEST_MAIN(KWin::StickyKeysTest) WAYLANDTEST_MAIN(KWin::StickyKeysTest)

View file

@ -66,6 +66,7 @@ void StickyKeysFilter::loadConfig(const KConfigGroup &group)
m_lockKeys = group.readEntry<bool>("StickyKeysLatch", true); m_lockKeys = group.readEntry<bool>("StickyKeysLatch", true);
m_showNotificationForLockedKeys = group.readEntry<bool>("kNotifyModifiers", false); m_showNotificationForLockedKeys = group.readEntry<bool>("kNotifyModifiers", false);
m_disableOnTwoKeys = group.readEntry<bool>("StickyKeysAutoOff", false);
if (!m_lockKeys) { if (!m_lockKeys) {
// locking keys is deactivated, unlock all locked keys // locking keys is deactivated, unlock all locked keys
@ -96,6 +97,12 @@ bool StickyKeysFilter::keyEvent(KWin::KeyEvent *event)
{ {
if (m_modifiers.contains(event->key())) { if (m_modifiers.contains(event->key())) {
if (event->type() == QEvent::KeyPress) {
m_pressedModifiers << event->key();
} else {
m_pressedModifiers.remove(event->key());
}
auto keyState = m_keyStates.find(event->key()); auto keyState = m_keyStates.find(event->key());
if (keyState != m_keyStates.end()) { if (keyState != m_keyStates.end()) {
@ -135,6 +142,11 @@ bool StickyKeysFilter::keyEvent(KWin::KeyEvent *event)
} }
} }
} else if (event->type() == QKeyEvent::KeyPress) { } else if (event->type() == QKeyEvent::KeyPress) {
if (!m_pressedModifiers.isEmpty() && m_disableOnTwoKeys) {
disableStickyKeys();
}
// a non-modifier key was pressed, unlatch all unlocked modifiers // a non-modifier key was pressed, unlatch all unlocked modifiers
for (auto it = m_keyStates.keyValueBegin(); it != m_keyStates.keyValueEnd(); ++it) { for (auto it = m_keyStates.keyValueBegin(); it != m_keyStates.keyValueEnd(); ++it) {
@ -151,4 +163,15 @@ bool StickyKeysFilter::keyEvent(KWin::KeyEvent *event)
return false; return false;
} }
void StickyKeysFilter::disableStickyKeys()
{
for (auto it = m_keyStates.keyValueBegin(); it != m_keyStates.keyValueEnd(); ++it) {
it->second = KeyState::None;
KWin::input()->keyboard()->xkb()->setModifierLatched(keyToModifier(static_cast<Qt::Key>(it->first)), false);
KWin::input()->keyboard()->xkb()->setModifierLocked(keyToModifier(static_cast<Qt::Key>(it->first)), false);
}
KWin::input()->uninstallInputEventFilter(this);
}
#include "moc_stickykeys.cpp" #include "moc_stickykeys.cpp"

View file

@ -27,10 +27,13 @@ public:
private: private:
void loadConfig(const KConfigGroup &group); void loadConfig(const KConfigGroup &group);
void disableStickyKeys();
KConfigWatcher::Ptr m_configWatcher; KConfigWatcher::Ptr m_configWatcher;
QMap<int, KeyState> m_keyStates; QMap<int, KeyState> m_keyStates;
QList<int> m_modifiers = {Qt::Key_Shift, Qt::Key_Control, Qt::Key_Alt, Qt::Key_AltGr, Qt::Key_Meta}; QList<int> m_modifiers = {Qt::Key_Shift, Qt::Key_Control, Qt::Key_Alt, Qt::Key_AltGr, Qt::Key_Meta};
bool m_lockKeys = false; bool m_lockKeys = false;
bool m_showNotificationForLockedKeys = false; bool m_showNotificationForLockedKeys = false;
bool m_disableOnTwoKeys = false;
QSet<int> m_pressedModifiers;
}; };