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;