From 41d431de279325034dcc6c3e73a1bad0df4f78ed Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Sat, 17 Oct 2020 15:47:25 +0300 Subject: [PATCH] Adapt to input region changes in kwayland-server SurfaceInterface::inputIsInfinite() has been dropped. If the surface has no any input region specified, SurfaceInterface::input() will return a region that corresponds to the rect of the surface (0, 0, width, height). While the new design is more robust, for example it's no longer possible to forget to check SurfaceInterface::inputIsInfinite(), it has shown some issues in the input stack of kwin. Currently, acceptsInput() will return false if you attempt to click the server-side decoration for a surface whose input region is not empty. Therefore, it's possible for an application to set an input region with a width and a height of 1. If user doesn't know about KSysGuard or the possibility of closing apps via the task manager, they won't be able to close such an application. Another issue is that if an application has specified an empty input region on purpose, user will be still able click it. With the new behavior of SurfaceInterface::input(), this is no longer an issue and it is handled properly by kwin. --- abstract_client.cpp | 10 +++++ abstract_client.h | 1 + autotests/integration/pointer_input.cpp | 49 +++++++++++++++++++++++++ input.cpp | 15 +------- inputpanelv1client.cpp | 3 -- pointer_input.cpp | 3 +- toplevel.cpp | 8 ++++ toplevel.h | 5 +++ 8 files changed, 76 insertions(+), 18 deletions(-) diff --git a/abstract_client.cpp b/abstract_client.cpp index f3fcb50d7c..8969d291a1 100644 --- a/abstract_client.cpp +++ b/abstract_client.cpp @@ -2575,6 +2575,16 @@ QRect AbstractClient::inputGeometry() const return Toplevel::inputGeometry(); } +bool AbstractClient::hitTest(const QPoint &point) const +{ + if (isDecorated()) { + if (m_decoration.inputRegion.contains(mapToFrame(point))) { + return true; + } + } + return Toplevel::hitTest(point); +} + QRect AbstractClient::virtualKeyboardGeometry() const { return m_virtualKeyboardGeometry; diff --git a/abstract_client.h b/abstract_client.h index 79ab9ba0c4..9aecb96110 100644 --- a/abstract_client.h +++ b/abstract_client.h @@ -754,6 +754,7 @@ public: virtual void showContextHelp(); QRect inputGeometry() const override; + bool hitTest(const QPoint &point) const override; /** * @returns the geometry of the virtual keyboard diff --git a/autotests/integration/pointer_input.cpp b/autotests/integration/pointer_input.cpp index 63f27ade3f..89b0451f8d 100644 --- a/autotests/integration/pointer_input.cpp +++ b/autotests/integration/pointer_input.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -123,6 +124,8 @@ private Q_SLOTS: void testResizeCursor(); void testMoveCursor(); void testHideShowCursor(); + void testDefaultInputRegion(); + void testEmptyInputRegion(); private: void render(KWayland::Client::Surface *surface, const QSize &size = QSize(100, 50)); @@ -1619,6 +1622,52 @@ void PointerInputTest::testHideShowCursor() QCOMPARE(kwinApp()->platform()->isCursorHidden(), false); } +void PointerInputTest::testDefaultInputRegion() +{ + // This test verifies that a surface that hasn't specified the input region can be focused. + + // Create a test client. + using namespace KWayland::Client; + QScopedPointer surface(Test::createSurface()); + QVERIFY(!surface.isNull()); + QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); + QVERIFY(!shellSurface.isNull()); + AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(client); + + // Move the point to the center of the surface. + Cursors::self()->mouse()->setPos(client->frameGeometry().center()); + QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), client->surface()); + + // Destroy the test client. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +void PointerInputTest::testEmptyInputRegion() +{ + // This test verifies that a surface that has specified an empty input region can't be focused. + + // Create a test client. + using namespace KWayland::Client; + QScopedPointer surface(Test::createSurface()); + QVERIFY(!surface.isNull()); + std::unique_ptr inputRegion(m_compositor->createRegion(QRegion())); + surface->setInputRegion(inputRegion.get()); + QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); + QVERIFY(!shellSurface.isNull()); + AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(client); + + // Move the point to the center of the surface. + Cursors::self()->mouse()->setPos(client->frameGeometry().center()); + QVERIFY(!waylandServer()->seat()->focusedPointerSurface()); + + // Destroy the test client. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); +} + } WAYLANDTEST_MAIN(KWin::PointerInputTest) diff --git a/input.cpp b/input.cpp index 0fbc2871d6..6f44cd7d12 100644 --- a/input.cpp +++ b/input.cpp @@ -2418,17 +2418,6 @@ Qt::MouseButtons InputRedirection::qtButtonStates() const return m_pointer->buttons(); } -static bool acceptsInput(Toplevel *t, const QPoint &pos) -{ - const QRegion input = t->inputShape(); - if (input.isEmpty()) { - return true; - } - // TODO: What about sub-surfaces sticking outside the main surface? - const QPoint localPoint = pos - t->bufferGeometry().topLeft(); - return input.contains(localPoint); -} - Toplevel *InputRedirection::findToplevel(const QPoint &pos) { if (!Workspace::self()) { @@ -2443,7 +2432,7 @@ Toplevel *InputRedirection::findToplevel(const QPoint &pos) } const QList &unmanaged = Workspace::self()->unmanagedList(); foreach (Unmanaged *u, unmanaged) { - if (u->inputGeometry().contains(pos) && acceptsInput(u, pos)) { + if (u->hitTest(pos)) { return u; } } @@ -2482,7 +2471,7 @@ Toplevel *InputRedirection::findManagedToplevel(const QPoint &pos) continue; } } - if (t->inputGeometry().contains(pos) && acceptsInput(t, pos)) { + if (t->hitTest(pos)) { return t; } } while (it != stacking.begin()); diff --git a/inputpanelv1client.cpp b/inputpanelv1client.cpp index 4d56d94866..50bbbe86c3 100644 --- a/inputpanelv1client.cpp +++ b/inputpanelv1client.cpp @@ -107,9 +107,6 @@ NET::WindowType InputPanelV1Client::windowType(bool, int) const QRect InputPanelV1Client::inputGeometry() const { - if (surface()->inputIsInfinite()) { - return frameGeometry(); - } return surface()->input().boundingRect().translated(pos()); } diff --git a/pointer_input.cpp b/pointer_input.cpp index 4812a11d37..6966e8923a 100644 --- a/pointer_input.cpp +++ b/pointer_input.cpp @@ -620,8 +620,7 @@ template static QRegion getConstraintRegion(Toplevel *t, T *constraint) { const QRegion windowShape = t->inputShape(); - const QRegion windowRegion = windowShape.isEmpty() ? QRegion(0, 0, t->clientSize().width(), t->clientSize().height()) : windowShape; - const QRegion intersected = constraint->region().isEmpty() ? windowRegion : windowRegion.intersected(constraint->region()); + const QRegion intersected = constraint->region().isEmpty() ? windowShape : windowShape.intersected(constraint->region()); return intersected.translated(t->pos() + t->clientPos()); } diff --git a/toplevel.cpp b/toplevel.cpp index 0694361185..126c8165ed 100644 --- a/toplevel.cpp +++ b/toplevel.cpp @@ -789,6 +789,14 @@ QMatrix4x4 Toplevel::inputTransformation() const return m; } +bool Toplevel::hitTest(const QPoint &point) const +{ + if (m_surface && m_surface->isMapped()) { + return m_surface->inputSurfaceAt(mapToLocal(point)); + } + return inputGeometry().contains(point); +} + QPoint Toplevel::mapToFrame(const QPoint &point) const { return point - frameGeometry().topLeft(); diff --git a/toplevel.h b/toplevel.h index e16cd2176b..a7674928e0 100644 --- a/toplevel.h +++ b/toplevel.h @@ -540,6 +540,11 @@ public: */ virtual QMatrix4x4 inputTransformation() const; + /** + * Returns @c true if the toplevel can accept input at the specified position @a point. + */ + virtual bool hitTest(const QPoint &point) const; + /** * The window has a popup grab. This means that when it got mapped the * parent window had an implicit (pointer) grab.