diff --git a/autotests/wayland/lockscreen.cpp b/autotests/wayland/lockscreen.cpp index b5548e1c05..c3be06f677 100644 --- a/autotests/wayland/lockscreen.cpp +++ b/autotests/wayland/lockscreen.cpp @@ -39,6 +39,7 @@ along with this program. If not, see . #include #include #include +#include //screenlocker #include @@ -64,6 +65,7 @@ private Q_SLOTS: void testScreenEdge(); void testEffects(); void testEffectsKeyboard(); + void testEffectsKeyboardAutorepeat(); void testMoveWindow(); void testPointerShortcut(); void testAxisShortcut_data(); @@ -564,6 +566,47 @@ void LockScreenTest::testEffectsKeyboard() effects->ungrabKeyboard(); } +void LockScreenTest::testEffectsKeyboardAutorepeat() +{ + // this test is just like testEffectsKeyboard, but tests auto repeat key events + // while the key is pressed the Effect should get auto repeated events + // but the lock screen should filter them out + QScopedPointer effect(new HelperEffect); + QSignalSpy inputSpy(effect.data(), &HelperEffect::keyEvent); + QVERIFY(inputSpy.isValid()); + effects->grabKeyboard(effect.data()); + + // we need to configure the key repeat first. It is only enabled on libinput + waylandServer()->seat()->setKeyRepeatInfo(25, 300); + + quint32 timestamp = 1; + KEYPRESS(KEY_A); + QCOMPARE(inputSpy.count(), 1); + QCOMPARE(inputSpy.first().first().toString(), QStringLiteral("a")); + QVERIFY(inputSpy.wait()); + QVERIFY(inputSpy.count() > 1); + // and still more events + QVERIFY(inputSpy.wait()); + QCOMPARE(inputSpy.at(1).first().toString(), QStringLiteral("a")); + + // now release + inputSpy.clear(); + KEYRELEASE(KEY_A); + QCOMPARE(inputSpy.count(), 1); + + // while locked key repeat should not pass any events to the Effect + LOCK + KEYPRESS(KEY_B); + QVERIFY(!inputSpy.wait(200)); + KEYRELEASE(KEY_B); + QVERIFY(!inputSpy.wait(200)); + + UNLOCK + // don't test again, that's covered by testEffectsKeyboard + + effects->ungrabKeyboard(); +} + void LockScreenTest::testMoveWindow() { using namespace KWayland::Client; diff --git a/input.cpp b/input.cpp index ae8afc32f3..d4c2b3e1ba 100644 --- a/input.cpp +++ b/input.cpp @@ -111,7 +111,7 @@ class VirtualTerminalFilter : public InputEventFilter { public: bool keyEvent(QKeyEvent *event) override { // really on press and not on release? X11 switches on press. - if (event->type() == QEvent::KeyPress) { + if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { const xkb_keysym_t keysym = event->nativeVirtualKey(); if (keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { VirtualTerminal::self()->activate(keysym - XKB_KEY_XF86Switch_VT_1 + 1); @@ -162,6 +162,10 @@ public: if (!waylandServer()->isScreenLocked()) { return false; } + if (event->isAutoRepeat()) { + // wayland client takes care of it + return true; + } // send event to KSldApp for global accel // if event is set to accepted it means a whitelisted shortcut was triggered // in that case we filter it out and don't process it further @@ -344,7 +348,7 @@ public: return input()->shortcuts()->processAxis(event->modifiers(), direction); } bool keyEvent(QKeyEvent *event) override { - if (event->type() == QEvent::KeyPress) { + if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { return input()->shortcuts()->processKey(event->modifiers(), event->nativeVirtualKey()); } return false; @@ -626,6 +630,10 @@ public: if (!workspace()) { return false; } + if (event->isAutoRepeat()) { + // handled by Wayland client + return false; + } auto seat = waylandServer()->seat(); input()->keyboard()->update(); seat->setTimestamp(event->timestamp()); diff --git a/input.h b/input.h index 1de4d9f9a0..d24829db71 100644 --- a/input.h +++ b/input.h @@ -68,7 +68,8 @@ public: }; enum KeyboardKeyState { KeyboardKeyReleased, - KeyboardKeyPressed + KeyboardKeyPressed, + KeyboardKeyAutoRepeat }; virtual ~InputRedirection(); void init(); diff --git a/keyboard_input.cpp b/keyboard_input.cpp index 489f0a9e7d..57ebb279ac 100644 --- a/keyboard_input.cpp +++ b/keyboard_input.cpp @@ -268,7 +268,11 @@ KeyboardInputRedirection::KeyboardInputRedirection(InputRedirection *parent) { } -KeyboardInputRedirection::~KeyboardInputRedirection() = default; +KeyboardInputRedirection::~KeyboardInputRedirection() +{ + qDeleteAll(m_repeatTimers); + m_repeatTimers.clear(); +} void KeyboardInputRedirection::init() { @@ -327,21 +331,65 @@ void KeyboardInputRedirection::processKey(uint32_t key, InputRedirection::Keyboa if (!m_inited) { return; } - emit m_input->keyStateChanged(key, state); - const Qt::KeyboardModifiers oldMods = modifiers(); - m_xkb->updateKey(key, state); - if (oldMods != modifiers()) { - emit m_input->keyboardModifiersChanged(modifiers(), oldMods); + QEvent::Type type; + bool autoRepeat = false; + switch (state) { + case InputRedirection::KeyboardKeyAutoRepeat: + autoRepeat = true; + // fall through + case InputRedirection::KeyboardKeyPressed: + type = QEvent::KeyPress; + break; + case InputRedirection::KeyboardKeyReleased: + type = QEvent::KeyRelease; + break; + default: + Q_UNREACHABLE(); } + + if (!autoRepeat) { + emit m_input->keyStateChanged(key, state); + const Qt::KeyboardModifiers oldMods = modifiers(); + m_xkb->updateKey(key, state); + if (oldMods != modifiers()) { + emit m_input->keyboardModifiersChanged(modifiers(), oldMods); + } + } + const xkb_keysym_t keySym = m_xkb->toKeysym(key); - QKeyEvent event((state == InputRedirection::KeyboardKeyPressed) ? QEvent::KeyPress : QEvent::KeyRelease, + QKeyEvent event(type, m_xkb->toQtKey(keySym), m_xkb->modifiers(), key, keySym, 0, - m_xkb->toString(m_xkb->toKeysym(key))); + m_xkb->toString(m_xkb->toKeysym(key)), + autoRepeat); event.setTimestamp(time); + if (state == InputRedirection::KeyboardKeyPressed) { + if (waylandServer()->seat()->keyRepeatDelay() != 0) { + QTimer *timer = new QTimer; + timer->setInterval(waylandServer()->seat()->keyRepeatDelay()); + connect(timer, &QTimer::timeout, this, + [this, timer, time, key] { + const int delay = 1000 / waylandServer()->seat()->keyRepeatRate(); + if (timer->interval() != delay) { + timer->setInterval(delay); + } + // TODO: better time + processKey(key, InputRedirection::KeyboardKeyAutoRepeat, time); + } + ); + m_repeatTimers.insert(key, timer); + timer->start(); + } + } else if (state == InputRedirection::KeyboardKeyReleased) { + auto it = m_repeatTimers.find(key); + if (it != m_repeatTimers.end()) { + delete it.value(); + m_repeatTimers.erase(it); + } + } const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { diff --git a/keyboard_input.h b/keyboard_input.h index 7cada835cc..d123a0e662 100644 --- a/keyboard_input.h +++ b/keyboard_input.h @@ -108,6 +108,7 @@ private: InputRedirection *m_input; bool m_inited = false; QScopedPointer m_xkb; + QHash m_repeatTimers; }; inline