From 4651aa1d7959f065c691a70116c2572dce3e312f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Thu, 11 Aug 2016 11:17:09 +0200 Subject: [PATCH] [wayland] Unset focused keyboard surface when handling key event internally Summary: So far when KWin intercepted a key event a leave was not sent to the Wayland surface currently having keyboard focus. This could result in the Wayland application to start repeating keys. E.g. 1. application gets key press event 2. This triggers an internal window to show 3. key release goes to KWin internal window 4. application starts to repeat key as there is no release With this change whenever KWin intercepts the key event e.g. due to * internal window * Effects grabbing key event * Tabbox the focused keyboard surface is set to null, thus triggering a leave event and the client not starting to repeat the event. Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D2402 --- autotests/integration/internal_window.cpp | 63 +++++++++++++++++++++++ autotests/integration/kwin_wayland_test.h | 1 + autotests/integration/test_helpers.cpp | 12 +++++ input.cpp | 8 ++- 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/autotests/integration/internal_window.cpp b/autotests/integration/internal_window.cpp index 8092d3059a..ee611af600 100644 --- a/autotests/integration/internal_window.cpp +++ b/autotests/integration/internal_window.cpp @@ -28,8 +28,15 @@ along with this program. If not, see . #include #include +#include +#include +#include +#include + #include +using namespace KWayland::Client; + namespace KWin { @@ -41,6 +48,7 @@ class InternalWindowTest : public QObject private Q_SLOTS: void initTestCase(); void init(); + void cleanup(); void testEnterLeave(); void testPointerPressRelease(); void testPointerAxis(); @@ -173,6 +181,13 @@ void InternalWindowTest::initTestCase() void InternalWindowTest::init() { Cursor::setPos(QPoint(1280, 512)); + QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::Seat)); + QVERIFY(Test::waitForWaylandKeyboard()); +} + +void InternalWindowTest::cleanup() +{ + Test::destroyWaylandConnection(); } void InternalWindowTest::testEnterLeave() @@ -285,6 +300,9 @@ void InternalWindowTest::testKeyboard() QVERIFY(releaseSpy.isValid()); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); + auto internalClient = clientAddedSpy.first().first().value(); + QVERIFY(internalClient); + QVERIFY(internalClient->isInternal()); quint32 timestamp = 1; QFETCH(QPoint, cursorPos); @@ -296,6 +314,51 @@ void InternalWindowTest::testKeyboard() kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); QTRY_COMPARE(releaseSpy.count(), 1); QCOMPARE(pressSpy.count(), 1); + + // let's hide the window again and create a "real" window + win.hide(); + clientAddedSpy.clear(); + + QScopedPointer keyboard(Test::waylandSeat()->createKeyboard()); + QVERIFY(!keyboard.isNull()); + QVERIFY(keyboard->isValid()); + QSignalSpy enteredSpy(keyboard.data(), &Keyboard::entered); + QVERIFY(enteredSpy.isValid()); + QSignalSpy leftSpy(keyboard.data(), &Keyboard::left); + QVERIFY(leftSpy.isValid()); + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(Test::createShellSurface(Test::ShellSurfaceType::WlShell, surface.data())); + + // now let's render + auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(c); + QVERIFY(c->isActive()); + + if (enteredSpy.isEmpty()) { + QVERIFY(enteredSpy.wait()); + } + QCOMPARE(enteredSpy.count(), 1); + + QSignalSpy windowShownSpy(internalClient, &ShellClient::windowShown); + QVERIFY(windowShownSpy.isValid()); + win.show(); + QCOMPARE(windowShownSpy.count(), 1); + QVERIFY(leftSpy.isEmpty()); + QVERIFY(!leftSpy.wait(100)); + + // now let's trigger a key, which should result in a leave + kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); + QVERIFY(leftSpy.wait()); + QCOMPARE(pressSpy.count(), 2); + + kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); + QTRY_COMPARE(releaseSpy.count(), 2); + + // after hiding the internal window, next key press should trigger an enter + win.hide(); + kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); + QVERIFY(enteredSpy.wait()); + kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); } void InternalWindowTest::testTouch() diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h index 76ce6b3e49..81f6f80131 100644 --- a/autotests/integration/kwin_wayland_test.h +++ b/autotests/integration/kwin_wayland_test.h @@ -109,6 +109,7 @@ KWayland::Client::PlasmaWindowManagement *waylandWindowManagement(); bool waitForWaylandPointer(); bool waitForWaylandTouch(); +bool waitForWaylandKeyboard(); void flushWaylandConnection(); diff --git a/autotests/integration/test_helpers.cpp b/autotests/integration/test_helpers.cpp index 1bf3d4afd5..9496f2a94f 100644 --- a/autotests/integration/test_helpers.cpp +++ b/autotests/integration/test_helpers.cpp @@ -247,6 +247,18 @@ bool waitForWaylandTouch() return hasTouchSpy.wait(); } +bool waitForWaylandKeyboard() +{ + if (!s_waylandConnection.seat) { + return false; + } + QSignalSpy hasKeyboardSpy(s_waylandConnection.seat, &Seat::hasKeyboardChanged); + if (!hasKeyboardSpy.isValid()) { + return false; + } + return hasKeyboardSpy.wait(); +} + void render(Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format) { QImage img(size, format); diff --git a/input.cpp b/input.cpp index 81630ad3dd..1bc5a71222 100644 --- a/input.cpp +++ b/input.cpp @@ -340,6 +340,7 @@ public: if (!effects || !static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) { return false; } + waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(event); return true; } @@ -487,7 +488,11 @@ class InternalWindowEventFilter : public InputEventFilter { return false; } event->setAccepted(false); - return QCoreApplication::sendEvent(found, event); + if (QCoreApplication::sendEvent(found, event)) { + waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); + return true; + } + return false; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { @@ -718,6 +723,7 @@ public: if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } + waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); if (event->type() == QEvent::KeyPress) TabBox::TabBox::self()->keyPress(event->modifiers() | event->key()); return true;