Implement internal keyboard repeat
As a Wayland server KWin does not have to emit additional key repeat events (unlike X11). The clients are responsible for handling this based on the provided key repeat information. Internally KWin needs key repeat, though. E.g. the effects need key repeat (filtering in Present Windows), window moving by keyboard needs repeat, etc. etc. This change introduces the internal key repeat. For each key press a QTimer is started which gets canceled again on the key release. If the timer fires it invoked processKey with a new KeyboardKeyAutoRepeat state. This is handled just like a KeyPress, but states are not updated and the QKeyEvent has autorepeat set to true. The event filters check for the autorepeat state and filter the event out if they are not interested in it. E.g. the filters passing the event to the Wayland client need to filter it out. Currently auto-repeat is bound to using libinput. This needs to be modified. The only backend sending repeated events is X11, thus for other backends it should be enabled. Whether creating a timer on each key event is a good idea is something to evaluate in future. Reviewed-By: Bhushan Shah
This commit is contained in:
parent
b59593bd9d
commit
cb3c6a4780
5 changed files with 112 additions and 11 deletions
|
@ -39,6 +39,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#include <KWayland/Client/shm_pool.h>
|
||||
#include <KWayland/Client/surface.h>
|
||||
#include <KWayland/Client/touch.h>
|
||||
#include <KWayland/Server/seat_interface.h>
|
||||
|
||||
//screenlocker
|
||||
#include <KScreenLocker/KsldApp>
|
||||
|
@ -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<HelperEffect> 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;
|
||||
|
|
12
input.cpp
12
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());
|
||||
|
|
3
input.h
3
input.h
|
@ -68,7 +68,8 @@ public:
|
|||
};
|
||||
enum KeyboardKeyState {
|
||||
KeyboardKeyReleased,
|
||||
KeyboardKeyPressed
|
||||
KeyboardKeyPressed,
|
||||
KeyboardKeyAutoRepeat
|
||||
};
|
||||
virtual ~InputRedirection();
|
||||
void init();
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -108,6 +108,7 @@ private:
|
|||
InputRedirection *m_input;
|
||||
bool m_inited = false;
|
||||
QScopedPointer<Xkb> m_xkb;
|
||||
QHash<quint32, QTimer*> m_repeatTimers;
|
||||
};
|
||||
|
||||
inline
|
||||
|
|
Loading…
Reference in a new issue