From 9f7a856d236f9f2496c5bacfae53e8b4b97f2b58 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Thu, 3 Oct 2019 22:43:28 +0300 Subject: [PATCH] [wayland] Implement window geometry more properly Summary: So far the window geometry from xdg-shell wasn't implemented as it should be. A toplevel must have two geometries assigned to it - frame and buffer. The frame geometry describes bounds of the client excluding server-side and client-side drop-shadows. The buffer geometry specifies rectangle on the screen occupied by the main surface. State and geometry handling in XdgShellClient is still a bit broken. This change doesn't intend to fix that, it must be done in another patch asap. Test Plan: New tests pass. Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: davidedmundson, romangg, kwin Tags: #kwin Maniphest Tasks: T10867 Differential Revision: https://phabricator.kde.org/D24455 --- autotests/integration/xdgshellclient_test.cpp | 356 ++++++++++++++++-- deleted.cpp | 6 + deleted.h | 3 + internal_client.cpp | 5 + internal_client.h | 1 + toplevel.h | 16 + unmanaged.cpp | 5 + unmanaged.h | 1 + x11client.cpp | 5 + x11client.h | 2 + xdgshellclient.cpp | 146 ++++--- xdgshellclient.h | 15 +- 12 files changed, 471 insertions(+), 90 deletions(-) diff --git a/autotests/integration/xdgshellclient_test.cpp b/autotests/integration/xdgshellclient_test.cpp index f9b6d9489d..fc2e754919 100644 --- a/autotests/integration/xdgshellclient_test.cpp +++ b/autotests/integration/xdgshellclient_test.cpp @@ -38,6 +38,7 @@ along with this program. If not, see . #include #include #include +#include #include #include #include @@ -114,7 +115,12 @@ private Q_SLOTS: void testXdgInitiallyMaximised(); void testXdgInitiallyFullscreen(); void testXdgInitiallyMinimized(); - void testXdgWindowGeometry(); + void testXdgWindowGeometryIsntSet(); + void testXdgWindowGeometryAttachBuffer(); + void testXdgWindowGeometryAttachSubSurface(); + void testXdgWindowGeometryInteractiveResize(); + void testXdgWindowGeometryFullScreen(); + void testXdgWindowGeometryMaximize(); }; void TestXdgShellClient::initTestCase() @@ -1297,40 +1303,338 @@ void TestXdgShellClient::testXdgInitiallyMinimized() QVERIFY(c->isMinimized()); } -void TestXdgShellClient::testXdgWindowGeometry() +void TestXdgShellClient::testXdgWindowGeometryIsntSet() { + // This test verifies that the effective window geometry corresponds to the + // bounding rectangle of the main surface and its sub-surfaces if no window + // geometry is set by the client. + QScopedPointer surface(Test::createSurface()); - QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data(), nullptr, Test::CreationSetup::CreateOnly)); - QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); + QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); + XdgShellClient *client = Test::renderAndWaitForShown(surface.data(), QSize(200, 100), Qt::red); + QVERIFY(client); + QCOMPARE(client->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(client->frameGeometry().size(), QSize(200, 100)); + + const QPoint oldPosition = client->pos(); + + QSignalSpy geometryChangedSpy(client, &AbstractClient::geometryChanged); + QVERIFY(geometryChangedSpy.isValid()); + Test::render(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->frameGeometry().topLeft(), oldPosition); + QCOMPARE(client->frameGeometry().size(), QSize(100, 50)); + QCOMPARE(client->bufferGeometry().topLeft(), oldPosition); + QCOMPARE(client->bufferGeometry().size(), QSize(100, 50)); + + QScopedPointer childSurface(Test::createSurface()); + QScopedPointer subSurface(Test::createSubSurface(childSurface.data(), surface.data())); + QVERIFY(subSurface); + subSurface->setPosition(QPoint(-20, -10)); + Test::render(childSurface.data(), QSize(100, 50), Qt::blue); surface->commit(Surface::CommitFlag::None); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->frameGeometry().topLeft(), oldPosition); + QCOMPARE(client->frameGeometry().size(), QSize(120, 60)); + QCOMPARE(client->bufferGeometry().topLeft(), oldPosition + QPoint(20, 10)); + QCOMPARE(client->bufferGeometry().size(), QSize(100, 50)); +} - configureRequestedSpy.wait(); - shellSurface->ackConfigure(configureRequestedSpy.first()[2].toUInt()); +void TestXdgShellClient::testXdgWindowGeometryAttachBuffer() +{ + // This test verifies that the effective window geometry remains the same when + // a new buffer is attached and xdg_surface.set_window_geometry is not called + // again. Notice that the window geometry must remain the same even if the new + // buffer is smaller. - // Create a 160x140 window in with a margin of 10(left), 20(top), 30(right), 40(bottom). Giving a total buffer size 200, 100 - shellSurface->setWindowGeometry(QRect(10, 20, 160, 40)); - auto c = Test::renderAndWaitForShown(surface.data(), QSize(200,100), Qt::blue); - configureRequestedSpy.wait(); //window activated after being shown + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); + XdgShellClient *client = Test::renderAndWaitForShown(surface.data(), QSize(200, 100), Qt::red); + QVERIFY(client); + QCOMPARE(client->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(client->frameGeometry().size(), QSize(200, 100)); - QSignalSpy geometryChangedSpy(c, &XdgShellClient::geometryChanged); - // resize to 300,200 in kwin terms - c->setFrameGeometry(QRect(100, 100, 300, 200)); + const QPoint oldPosition = client->pos(); + + QSignalSpy geometryChangedSpy(client, &AbstractClient::geometryChanged); + QVERIFY(geometryChangedSpy.isValid()); + shellSurface->setWindowGeometry(QRect(10, 10, 180, 80)); + surface->commit(Surface::CommitFlag::None); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->frameGeometry().topLeft(), oldPosition); + QCOMPARE(client->frameGeometry().size(), QSize(180, 80)); + QCOMPARE(client->bufferGeometry().topLeft(), oldPosition - QPoint(10, 10)); + QCOMPARE(client->bufferGeometry().size(), QSize(200, 100)); + + Test::render(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->frameGeometry().topLeft(), oldPosition); + QCOMPARE(client->frameGeometry().size(), QSize(180, 80)); + QCOMPARE(client->bufferGeometry().topLeft(), oldPosition - QPoint(10, 10)); + QCOMPARE(client->bufferGeometry().size(), QSize(100, 50)); + + shellSurface->setWindowGeometry(QRect(5, 5, 90, 40)); + surface->commit(Surface::CommitFlag::None); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->frameGeometry().topLeft(), oldPosition); + QCOMPARE(client->frameGeometry().size(), QSize(90, 40)); + QCOMPARE(client->bufferGeometry().topLeft(), oldPosition - QPoint(5, 5)); + QCOMPARE(client->bufferGeometry().size(), QSize(100, 50)); + + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +void TestXdgShellClient::testXdgWindowGeometryAttachSubSurface() +{ + // This test verifies that the effective window geometry remains the same + // when a new sub-surface is added and xdg_surface.set_window_geometry is + // not called again. + + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); + XdgShellClient *client = Test::renderAndWaitForShown(surface.data(), QSize(200, 100), Qt::red); + QVERIFY(client); + QCOMPARE(client->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(client->frameGeometry().size(), QSize(200, 100)); + + const QPoint oldPosition = client->pos(); + + QSignalSpy geometryChangedSpy(client, &XdgShellClient::geometryChanged); + QVERIFY(geometryChangedSpy.isValid()); + shellSurface->setWindowGeometry(QRect(10, 10, 180, 80)); + surface->commit(Surface::CommitFlag::None); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->frameGeometry().topLeft(), oldPosition); + QCOMPARE(client->frameGeometry().size(), QSize(180, 80)); + QCOMPARE(client->bufferGeometry().topLeft(), oldPosition - QPoint(10, 10)); + QCOMPARE(client->bufferGeometry().size(), QSize(200, 100)); + + QScopedPointer childSurface(Test::createSurface()); + QScopedPointer subSurface(Test::createSubSurface(childSurface.data(), surface.data())); + QVERIFY(subSurface); + subSurface->setPosition(QPoint(-20, -20)); + Test::render(childSurface.data(), QSize(100, 50), Qt::blue); + surface->commit(Surface::CommitFlag::None); + QCOMPARE(client->frameGeometry().topLeft(), oldPosition); + QCOMPARE(client->frameGeometry().size(), QSize(180, 80)); + QCOMPARE(client->bufferGeometry().topLeft(), oldPosition - QPoint(10, 10)); + QCOMPARE(client->bufferGeometry().size(), QSize(200, 100)); + + shellSurface->setWindowGeometry(QRect(-15, -15, 50, 40)); + surface->commit(Surface::CommitFlag::None); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->frameGeometry().topLeft(), oldPosition); + QCOMPARE(client->frameGeometry().size(), QSize(50, 40)); + QCOMPARE(client->bufferGeometry().topLeft(), oldPosition - QPoint(-15, -15)); + QCOMPARE(client->bufferGeometry().size(), QSize(200, 100)); +} + +void TestXdgShellClient::testXdgWindowGeometryInteractiveResize() +{ + // This test verifies that correct window geometry is provided along each + // configure event when an xdg-shell is being interactively resized. + + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); + XdgShellClient *client = Test::renderAndWaitForShown(surface.data(), QSize(200, 100), Qt::red); + QVERIFY(client); + QVERIFY(client->isActive()); + QCOMPARE(client->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(client->frameGeometry().size(), QSize(200, 100)); + + QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); + QVERIFY(configureRequestedSpy.isValid()); QVERIFY(configureRequestedSpy.wait()); - // requested geometry should not include the margins we had above - const QSize requestedSize = configureRequestedSpy.last()[0].value(); - QCOMPARE(requestedSize, QSize(300, 200) - QSize(10 + 30, 20 + 40)); - shellSurface->ackConfigure(configureRequestedSpy.last()[2].toUInt()); - Test::render(surface.data(), requestedSize + QSize(10 + 30, 20 + 40), Qt::blue); - geometryChangedSpy.wait(); + QCOMPARE(configureRequestedSpy.count(), 1); - // kwin's concept of geometry should remain the same - QCOMPARE(c->frameGeometry(), QRect(100, 100, 300, 200)); + QSignalSpy geometryChangedSpy(client, &AbstractClient::geometryChanged); + QVERIFY(geometryChangedSpy.isValid()); + shellSurface->setWindowGeometry(QRect(10, 10, 180, 80)); + surface->commit(Surface::CommitFlag::None); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(client->frameGeometry().size(), QSize(180, 80)); - c->setFullScreen(true); - configureRequestedSpy.wait(); - // when full screen, the window geometry (i.e without margins) should fill the screen - const QSize requestedFullScreenSize = configureRequestedSpy.last()[0].value(); - QCOMPARE(requestedFullScreenSize, QSize(1280, 1024)); + QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized); + QVERIFY(clientStartMoveResizedSpy.isValid()); + QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized); + QVERIFY(clientStepUserMovedResizedSpy.isValid()); + QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized); + QVERIFY(clientFinishUserMovedResizedSpy.isValid()); + + // Start interactively resizing the client. + QCOMPARE(workspace()->moveResizeClient(), nullptr); + workspace()->slotWindowResize(); + QCOMPARE(workspace()->moveResizeClient(), client); + QCOMPARE(clientStartMoveResizedSpy.count(), 1); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 2); + XdgShellSurface::States states = configureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(XdgShellSurface::State::Resizing)); + + // Go right. + QPoint cursorPos = KWin::Cursor::pos(); + client->keyPressEvent(Qt::Key_Right); + client->updateMoveResize(KWin::Cursor::pos()); + QCOMPARE(KWin::Cursor::pos(), cursorPos + QPoint(8, 0)); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 3); + states = configureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(XdgShellSurface::State::Resizing)); + QCOMPARE(configureRequestedSpy.last().at(0).toSize(), QSize(188, 80)); + shellSurface->setWindowGeometry(QRect(10, 10, 188, 80)); + shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); + Test::render(surface.data(), QSize(208, 100), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + QCOMPARE(client->bufferGeometry().size(), QSize(208, 100)); + QCOMPARE(client->frameGeometry().size(), QSize(188, 80)); + + // Go down. + cursorPos = KWin::Cursor::pos(); + client->keyPressEvent(Qt::Key_Down); + client->updateMoveResize(KWin::Cursor::pos()); + QCOMPARE(KWin::Cursor::pos(), cursorPos + QPoint(0, 8)); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 4); + states = configureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(XdgShellSurface::State::Resizing)); + QCOMPARE(configureRequestedSpy.last().at(0).toSize(), QSize(188, 88)); + shellSurface->setWindowGeometry(QRect(10, 10, 188, 88)); + shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); + Test::render(surface.data(), QSize(208, 108), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 2); + QCOMPARE(client->bufferGeometry().size(), QSize(208, 108)); + QCOMPARE(client->frameGeometry().size(), QSize(188, 88)); + + // Finish resizing the client. + client->keyPressEvent(Qt::Key_Enter); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QCOMPARE(workspace()->moveResizeClient(), nullptr); +#if 0 + QEXPECT_FAIL("", "XdgShellClient currently doesn't send final configure event", Abort); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 5); + states = configureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(XdgShellSurface::State::Resizing)); +#endif + + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +void TestXdgShellClient::testXdgWindowGeometryFullScreen() +{ + // This test verifies that an xdg-shell receives correct window geometry when + // its fullscreen state gets changed. + + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); + XdgShellClient *client = Test::renderAndWaitForShown(surface.data(), QSize(200, 100), Qt::red); + QVERIFY(client); + QVERIFY(client->isActive()); + QCOMPARE(client->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(client->frameGeometry().size(), QSize(200, 100)); + + QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); + QVERIFY(configureRequestedSpy.isValid()); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 1); + + QSignalSpy geometryChangedSpy(client, &AbstractClient::geometryChanged); + QVERIFY(geometryChangedSpy.isValid()); + shellSurface->setWindowGeometry(QRect(10, 10, 180, 80)); + surface->commit(Surface::CommitFlag::None); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(client->frameGeometry().size(), QSize(180, 80)); + + workspace()->slotWindowFullScreen(); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 2); + QCOMPARE(configureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024)); + XdgShellSurface::States states = configureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(XdgShellSurface::State::Fullscreen)); + shellSurface->setWindowGeometry(QRect(0, 0, 1280, 1024)); + shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); + Test::render(surface.data(), QSize(1280, 1024), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->bufferGeometry().size(), QSize(1280, 1024)); + QCOMPARE(client->frameGeometry().size(), QSize(1280, 1024)); + + workspace()->slotWindowFullScreen(); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 3); + QCOMPARE(configureRequestedSpy.last().at(0).toSize(), QSize(180, 80)); + states = configureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(XdgShellSurface::State::Fullscreen)); + shellSurface->setWindowGeometry(QRect(10, 10, 180, 80)); + shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); + Test::render(surface.data(), QSize(200, 100), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(client->frameGeometry().size(), QSize(180, 80)); + + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +void TestXdgShellClient::testXdgWindowGeometryMaximize() +{ + // This test verifies that an xdg-shell receives correct window geometry when + // its maximized state gets changed. + + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); + XdgShellClient *client = Test::renderAndWaitForShown(surface.data(), QSize(200, 100), Qt::red); + QVERIFY(client); + QVERIFY(client->isActive()); + QCOMPARE(client->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(client->frameGeometry().size(), QSize(200, 100)); + + QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); + QVERIFY(configureRequestedSpy.isValid()); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 1); + + QSignalSpy geometryChangedSpy(client, &AbstractClient::geometryChanged); + QVERIFY(geometryChangedSpy.isValid()); + shellSurface->setWindowGeometry(QRect(10, 10, 180, 80)); + surface->commit(Surface::CommitFlag::None); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(client->frameGeometry().size(), QSize(180, 80)); + + workspace()->slotWindowMaximize(); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 2); + QCOMPARE(configureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024)); + XdgShellSurface::States states = configureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(XdgShellSurface::State::Maximized)); + shellSurface->setWindowGeometry(QRect(0, 0, 1280, 1024)); + shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); + Test::render(surface.data(), QSize(1280, 1024), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->bufferGeometry().size(), QSize(1280, 1024)); + QCOMPARE(client->frameGeometry().size(), QSize(1280, 1024)); + + workspace()->slotWindowMaximize(); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 3); + QCOMPARE(configureRequestedSpy.last().at(0).toSize(), QSize(180, 80)); + states = configureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(XdgShellSurface::State::Maximized)); + shellSurface->setWindowGeometry(QRect(10, 10, 180, 80)); + shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); + Test::render(surface.data(), QSize(200, 100), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(client->frameGeometry().size(), QSize(180, 80)); + + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); } WAYLANDTEST_MAIN(TestXdgShellClient) diff --git a/deleted.cpp b/deleted.cpp index 6ab998d715..c03b7b3466 100644 --- a/deleted.cpp +++ b/deleted.cpp @@ -94,6 +94,7 @@ void Deleted::copyToDeleted(Toplevel* c) { Q_ASSERT(dynamic_cast< Deleted* >(c) == nullptr); Toplevel::copyToDeleted(c); + m_bufferGeometry = c->bufferGeometry(); m_bufferScale = c->bufferScale(); desk = c->desktop(); m_desktops = c->desktops(); @@ -163,6 +164,11 @@ void Deleted::unrefWindow() deleteLater(); } +QRect Deleted::bufferGeometry() const +{ + return m_bufferGeometry; +} + qreal Deleted::bufferScale() const { return m_bufferScale; diff --git a/deleted.h b/deleted.h index 917d36be85..86ee54f409 100644 --- a/deleted.h +++ b/deleted.h @@ -43,6 +43,7 @@ public: void refWindow(); void unrefWindow(); void discard(); + QRect bufferGeometry() const override; qreal bufferScale() const override; int desktop() const override; QStringList activities() const override; @@ -198,6 +199,8 @@ private: void addTransientFor(AbstractClient *parent); void removeTransientFor(Deleted *parent); + QRect m_bufferGeometry; + int delete_refcount; int desk; QStringList activityList; diff --git a/internal_client.cpp b/internal_client.cpp index 73dabf2ba4..8c98e8ab7c 100644 --- a/internal_client.cpp +++ b/internal_client.cpp @@ -94,6 +94,11 @@ bool InternalClient::eventFilter(QObject *watched, QEvent *event) return false; } +QRect InternalClient::bufferGeometry() const +{ + return frameGeometry() - frameMargins(); +} + QStringList InternalClient::activities() const { return QStringList(); diff --git a/internal_client.h b/internal_client.h index 6f826bb007..e98e3c396c 100644 --- a/internal_client.h +++ b/internal_client.h @@ -35,6 +35,7 @@ public: bool eventFilter(QObject *watched, QEvent *event) override; + QRect bufferGeometry() const override; QStringList activities() const override; void blockActivityUpdates(bool b = true) override; qreal bufferScale() const override; diff --git a/toplevel.h b/toplevel.h index 450572cb1a..190b6c8cb7 100644 --- a/toplevel.h +++ b/toplevel.h @@ -79,6 +79,13 @@ class KWIN_EXPORT Toplevel : public QObject */ Q_PROPERTY(QRect geometry READ frameGeometry NOTIFY geometryChanged) + /** + * This property holds rectangle that the pixmap or buffer of this Toplevel + * occupies on the screen. This rectangle includes invisible portions of the + * client, e.g. client-side drop shadows, etc. + */ + Q_PROPERTY(QRect bufferGeometry READ bufferGeometry NOTIFY geometryChanged) + /** * This property holds the geometry of the Toplevel, excluding invisible * portions, e.g. server-side and client-side drop-shadows, etc. @@ -298,6 +305,15 @@ public: * @return a unique identifier for the Toplevel. On X11 same as @ref window */ virtual quint32 windowId() const; + /** + * Returns the geometry of the pixmap or buffer attached to this Toplevel. + * + * For X11 clients, this method returns server-side geometry of the Toplevel. + * + * For Wayland clients, this method returns rectangle that the main surface + * occupies on the screen, in global screen coordinates. + */ + virtual QRect bufferGeometry() const = 0; /** * Returns the geometry of the Toplevel, excluding invisible portions, e.g. * server-side and client-side drop shadows, etc. diff --git a/unmanaged.cpp b/unmanaged.cpp index 2a4bb5b1a2..098d2e5edb 100644 --- a/unmanaged.cpp +++ b/unmanaged.cpp @@ -128,6 +128,11 @@ bool Unmanaged::hasScheduledRelease() const return m_scheduledRelease; } +QRect Unmanaged::bufferGeometry() const +{ + return geom; +} + int Unmanaged::desktop() const { return NET::OnAllDesktops; // TODO for some window types should be the current desktop? diff --git a/unmanaged.h b/unmanaged.h index d2ada46317..2915218192 100644 --- a/unmanaged.h +++ b/unmanaged.h @@ -38,6 +38,7 @@ public: bool track(xcb_window_t w); bool hasScheduledRelease() const; static void deleteUnmanaged(Unmanaged* c); + QRect bufferGeometry() const override; int desktop() const override; QStringList activities() const override; QVector desktops() const override; diff --git a/x11client.cpp b/x11client.cpp index aad0cae53e..dff94c6d29 100644 --- a/x11client.cpp +++ b/x11client.cpp @@ -1971,6 +1971,11 @@ xcb_window_t X11Client::frameId() const return m_frame; } +QRect X11Client::bufferGeometry() const +{ + return geom; +} + Xcb::Property X11Client::fetchShowOnScreenEdge() const { return Xcb::Property(false, window(), atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 0, 1); diff --git a/x11client.h b/x11client.h index c3d22a123f..c2d7a6f8e6 100644 --- a/x11client.h +++ b/x11client.h @@ -89,6 +89,8 @@ public: xcb_window_t inputId() const { return m_decoInputExtent; } xcb_window_t frameId() const override; + QRect bufferGeometry() const override; + bool isTransient() const override; bool groupTransient() const override; bool wasOriginallyGroupTransient() const; diff --git a/xdgshellclient.cpp b/xdgshellclient.cpp index 44538b15eb..aa8bfcc6fa 100644 --- a/xdgshellclient.cpp +++ b/xdgshellclient.cpp @@ -47,6 +47,7 @@ along with this program. If not, see . #include #include #include +#include #include #include @@ -92,7 +93,10 @@ void XdgShellClient::init() createWindowId(); setupCompositing(); updateIcon(); - doSetGeometry(QRect(QPoint(0, 0), m_clientSize)); + + // TODO: Initialize with null rect. + geom = QRect(0, 0, -1, -1); + m_windowGeometry = QRect(0, 0, -1, -1); if (waylandServer()->inputMethodConnection() == surface()->client()) { m_windowType = NET::OnScreenDisplay; @@ -129,6 +133,7 @@ void XdgShellClient::init() connect(m_xdgShellSurface, &XdgShellSurfaceInterface::fullscreenChanged, this, &XdgShellClient::handleFullScreenRequested); connect(m_xdgShellSurface, &XdgShellSurfaceInterface::windowMenuRequested, this, &XdgShellClient::handleWindowMenuRequested); connect(m_xdgShellSurface, &XdgShellSurfaceInterface::transientForChanged, this, &XdgShellClient::handleTransientForChanged); + connect(m_xdgShellSurface, &XdgShellSurfaceInterface::windowGeometryChanged, this, &XdgShellClient::handleWindowGeometryChanged); auto global = static_cast(m_xdgShellSurface->global()); connect(global, &XdgShellInterface::pingDelayed, this, &XdgShellClient::handlePingDelayed); @@ -154,6 +159,7 @@ void XdgShellClient::init() connect(m_xdgShellPopup, &XdgShellPopupInterface::configureAcknowledged, this, &XdgShellClient::handleConfigureAcknowledged); connect(m_xdgShellPopup, &XdgShellPopupInterface::grabRequested, this, &XdgShellClient::handleGrabRequested); connect(m_xdgShellPopup, &XdgShellPopupInterface::destroyed, this, &XdgShellClient::destroyClient); + connect(m_xdgShellPopup, &XdgShellPopupInterface::windowGeometryChanged, this, &XdgShellClient::handleWindowGeometryChanged); } // set initial desktop @@ -289,14 +295,9 @@ void XdgShellClient::deleteClient(XdgShellClient *c) delete c; } -QSize XdgShellClient::toWindowGeometry(const QSize &size) const +QRect XdgShellClient::bufferGeometry() const { - QSize adjustedSize = size - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); - // a client going fullscreen should have the window the contents size of the screen - if (!isFullScreen() && requestedMaximizeMode() != MaximizeFull) { - adjustedSize -= QSize(m_windowMargins.left() + m_windowMargins.right(), m_windowMargins.top() + m_windowMargins.bottom()); - } - return adjustedSize; + return m_bufferGeometry; } QStringList XdgShellClient::activities() const @@ -312,7 +313,7 @@ QPoint XdgShellClient::clientContentPos() const QSize XdgShellClient::clientSize() const { - return m_clientSize; + return m_windowGeometry.size(); } void XdgShellClient::debug(QDebug &stream) const @@ -431,8 +432,7 @@ void XdgShellClient::createDecoration(const QRect &oldGeom) } setDecoration(decoration); // TODO: ensure the new geometry still fits into the client area (e.g. maximized windows) - doSetGeometry(QRect(oldGeom.topLeft(), m_clientSize + (decoration ? QSize(decoration->borderLeft() + decoration->borderRight(), - decoration->borderBottom() + decoration->borderTop()) : QSize()))); + doSetGeometry(QRect(oldGeom.topLeft(), m_windowGeometry.size() + QSize(borderLeft() + borderRight(), borderBottom() + borderTop()))); emit geometryShapeChanged(this, oldGeom); } @@ -491,10 +491,9 @@ void XdgShellClient::setFrameGeometry(int x, int y, int w, int h, ForceGeometry_ } const QSize requestedClientSize = newGeometry.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); - const QSize requestedWindowGeometrySize = toWindowGeometry(newGeometry.size()); - if (requestedClientSize == m_clientSize && - (m_requestedClientSize.isEmpty() || requestedWindowGeometrySize == m_requestedClientSize)) { + if (requestedClientSize == m_windowGeometry.size() && + (m_requestedClientSize.isEmpty() || requestedClientSize == m_requestedClientSize)) { // size didn't change, and we don't need to explicitly request a new size doSetGeometry(newGeometry); updateMaximizeMode(m_requestedMaximizeMode); @@ -504,30 +503,54 @@ void XdgShellClient::setFrameGeometry(int x, int y, int w, int h, ForceGeometry_ } } +QRect XdgShellClient::determineBufferGeometry() const +{ + // Offset of the main surface relative to the frame rect. + const int offsetX = borderLeft() - m_windowGeometry.left(); + const int offsetY = borderTop() - m_windowGeometry.top(); + + QRect bufferGeometry; + bufferGeometry.setX(x() + offsetX); + bufferGeometry.setY(y() + offsetY); + bufferGeometry.setSize(surface()->size()); + + return bufferGeometry; +} + void XdgShellClient::doSetGeometry(const QRect &rect) { - if (geom == rect && pendingGeometryUpdate() == PendingGeometryNone) { - return; - } - if (!m_unmapped) { - addWorkspaceRepaint(visibleRect()); + bool frameGeometryIsChanged = false; + bool bufferGeometryIsChanged = false; + + if (geom != rect) { + geom = rect; + frameGeometryIsChanged = true; } - geom = rect; - updateWindowRules(Rules::Position | Rules::Size); + const QRect bufferGeometry = determineBufferGeometry(); + if (m_bufferGeometry != bufferGeometry) { + m_bufferGeometry = bufferGeometry; + bufferGeometryIsChanged = true; + } + + if (!frameGeometryIsChanged && !bufferGeometryIsChanged) { + return; + } if (m_unmapped && m_geomMaximizeRestore.isEmpty() && !geom.isEmpty()) { // use first valid geometry as restore geometry m_geomMaximizeRestore = geom; } - if (!m_unmapped) { - addWorkspaceRepaint(visibleRect()); - } - if (hasStrut()) { - workspace()->updateClientArea(); + if (frameGeometryIsChanged) { + if (hasStrut()) { + workspace()->updateClientArea(); + } + updateWindowRules(Rules::Position | Rules::Size); } + const auto old = geometryBeforeUpdateBlocking(); + addRepaintDuringGeometryUpdates(); updateGeometryBeforeUpdateBlocking(); emit geometryShapeChanged(this, old); @@ -536,6 +559,13 @@ void XdgShellClient::doSetGeometry(const QRect &rect) } } +void XdgShellClient::doMove(int x, int y) +{ + Q_UNUSED(x) + Q_UNUSED(y) + m_bufferGeometry = determineBufferGeometry(); +} + QByteArray XdgShellClient::windowRole() const { return QByteArray(); @@ -1028,7 +1058,7 @@ void XdgShellClient::requestGeometry(const QRect &rect) QSize size; if (rect.isValid()) { - size = toWindowGeometry(rect.size()); + size = rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); } else { size = QSize(0, 0); } @@ -1080,7 +1110,7 @@ void XdgShellClient::updatePendingGeometry() } //else serialId < m_lastAckedConfigureRequest and the state is now irrelevant and can be ignored } - doSetGeometry(QRect(position, m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); + doSetGeometry(QRect(position, m_windowGeometry.size() + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); updateMaximizeMode(maximizeMode); } @@ -1127,6 +1157,32 @@ void XdgShellClient::handleWindowClassChanged(const QByteArray &windowClass) setDesktopFileName(windowClass); } +static QRect subSurfaceTreeRect(const SurfaceInterface *surface, const QPoint &position = QPoint()) +{ + QRect rect(position, surface->size()); + + const QList> subSurfaces = surface->childSubSurfaces(); + for (const QPointer &subSurface : subSurfaces) { + if (Q_UNLIKELY(!subSurface)) { + continue; + } + const SurfaceInterface *child = subSurface->surface(); + if (Q_UNLIKELY(!child)) { + continue; + } + rect |= subSurfaceTreeRect(child, position + subSurface->position()); + } + + return rect; +} + +void XdgShellClient::handleWindowGeometryChanged(const QRect &windowGeometry) +{ + const QRect boundingRect = subSurfaceTreeRect(surface()); + m_windowGeometry = windowGeometry & boundingRect; + m_hasWindowGeometry = true; +} + void XdgShellClient::handleWindowTitleChanged(const QString &title) { const QString oldSuffix = m_captionSuffix; @@ -1262,9 +1318,10 @@ void XdgShellClient::handleCommitted() return; } - m_clientSize = surface()->size(); + if (!m_hasWindowGeometry) { + m_windowGeometry = subSurfaceTreeRect(surface()); + } - updateWindowMargins(); updatePendingGeometry(); setDepth((surface()->buffer()->hasAlphaChannel() && !isDesktop()) ? 32 : 24); @@ -1303,7 +1360,8 @@ void XdgShellClient::installPlasmaShellSurface(PlasmaShellSurfaceInterface *surf { m_plasmaShellSurface = surface; auto updatePosition = [this, surface] { - QRect rect = QRect(surface->position(), m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom())); + // That's a mis-use of doSetGeometry method. One should instead use move method. + QRect rect = QRect(surface->position(), size()); doSetGeometry(rect); }; auto updateRole = [this, surface] { @@ -1902,32 +1960,6 @@ void XdgShellClient::updateClientOutputs() surface()->setOutputs(clientOutputs); } -void XdgShellClient::updateWindowMargins() -{ - QRect windowGeometry; - QSize clientSize = m_clientSize; - - if (m_xdgShellSurface) { - windowGeometry = m_xdgShellSurface->windowGeometry(); - } else { - windowGeometry = m_xdgShellPopup->windowGeometry(); - if (!clientSize.isValid()) { - clientSize = m_xdgShellPopup->initialSize(); - } - } - - if (windowGeometry.isEmpty() || - windowGeometry.width() > clientSize.width() || - windowGeometry.height() > clientSize.height()) { - m_windowMargins = QMargins(); - } else { - m_windowMargins = QMargins(windowGeometry.left(), - windowGeometry.top(), - clientSize.width() - (windowGeometry.right() + 1), - clientSize.height() - (windowGeometry.bottom() + 1)); - } -} - bool XdgShellClient::isPopupWindow() const { if (Toplevel::isPopupWindow()) { diff --git a/xdgshellclient.h b/xdgshellclient.h index f1dc607461..3b6a39f9b7 100644 --- a/xdgshellclient.h +++ b/xdgshellclient.h @@ -58,6 +58,7 @@ public: XdgShellClient(KWayland::Server::XdgShellPopupInterface *surface); ~XdgShellClient() override; + QRect bufferGeometry() const override; QStringList activities() const override; QPoint clientContentPos() const override; QSize clientSize() const override; @@ -139,11 +140,13 @@ protected: bool acceptsFocus() const override; void doMinimize() override; void updateCaption() override; + void doMove(int x, int y) override; private Q_SLOTS: void handleConfigureAcknowledged(quint32 serial); void handleTransientForChanged(); void handleWindowClassChanged(const QByteArray &windowClass); + void handleWindowGeometryChanged(const QRect &windowGeometry); void handleWindowTitleChanged(const QString &title); void handleMoveRequested(KWayland::Server::SeatInterface *seat, quint32 serial); void handleResizeRequested(KWayland::Server::SeatInterface *seat, quint32 serial, Qt::Edges edges); @@ -173,7 +176,6 @@ private: void updateIcon(); bool shouldExposeToWindowManagement(); void updateClientOutputs(); - void updateWindowMargins(); KWayland::Server::XdgShellSurfaceInterface::States xdgSurfaceStates() const; void updateShowOnScreenEdge(); void updateMaximizeMode(MaximizeMode maximizeMode); @@ -184,15 +186,16 @@ private: void doSetGeometry(const QRect &rect); void unmap(); void markAsMapped(); + QRect determineBufferGeometry() const; static void deleteClient(XdgShellClient *c); - QSize toWindowGeometry(const QSize &geometry) const; - KWayland::Server::XdgShellSurfaceInterface *m_xdgShellSurface; KWayland::Server::XdgShellPopupInterface *m_xdgShellPopup; - // size of the last buffer - QSize m_clientSize; + QRect m_bufferGeometry; + QRect m_windowGeometry; + bool m_hasWindowGeometry = false; + // last size we requested or empty if we haven't sent an explicit request to the client // if empty the client should choose their own default size QSize m_requestedClientSize = QSize(0, 0); @@ -255,8 +258,6 @@ private: QString m_captionSuffix; QHash m_pingSerials; - QMargins m_windowMargins; - bool m_isInitialized = false; friend class Workspace;