From bc83065cebd7ac855966da52edd1afcaf0fc2865 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Fri, 10 May 2019 16:34:43 +0100 Subject: [PATCH] Handle XdgShell window geometry in configure request sizes Summary: The size passed to an XDG shell configure request should match the window size of the given window, we don't want to include the size of any shadows that may be drawn by the client. Kwin has the same concept of geometry for both window management, input and rendering. In order to approach this in a way that does not risk any regressions with kwin's current structure AbstractClient::geometry remains the canonical source and we handle the window within that internally within ShellClient treating the windowGeometry as a set of margins from this. This is part of a much bigger task (T10867). This patch addresses windows growing when starting a drag based resize. BUG: 403376 Test Plan: Unit test gtk3-demo Reviewers: #kwin, zzag Reviewed By: #kwin, zzag Subscribers: zzag, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D20937 --- autotests/integration/shell_client_test.cpp | 36 ++++++++++++++++ shell_client.cpp | 48 +++++++++++++++++++-- shell_client.h | 5 +++ 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/autotests/integration/shell_client_test.cpp b/autotests/integration/shell_client_test.cpp index 4c93076726..67fbf31c0d 100644 --- a/autotests/integration/shell_client_test.cpp +++ b/autotests/integration/shell_client_test.cpp @@ -109,6 +109,7 @@ private Q_SLOTS: void testXdgInitialState(); void testXdgInitiallyMaximised(); void testXdgInitiallyMinimized(); + void testXdgWindowGeometry(); }; void TestShellClient::initTestCase() @@ -1463,6 +1464,41 @@ void TestShellClient::testXdgInitiallyMinimized() QVERIFY(c->isMinimized()); } +void TestShellClient::testXdgWindowGeometry() +{ + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data(), nullptr, Test::CreationSetup::CreateOnly)); + QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); + surface->commit(Surface::CommitFlag::None); + + configureRequestedSpy.wait(); + shellSurface->ackConfigure(configureRequestedSpy.first()[2].toUInt()); + + // 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 + + QSignalSpy geometryChangedSpy(c, &ShellClient::geometryChanged); + // resize to 300,200 in kwin terms + c->setGeometry(QRect(100, 100, 300, 200)); + 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(); + + // kwin's concept of geometry should remain the same + QCOMPARE(c->geometry(), QRect(100, 100, 300, 200)); + + 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)); +} WAYLANDTEST_MAIN(TestShellClient) #include "shell_client_test.moc" diff --git a/shell_client.cpp b/shell_client.cpp index c481db9f5b..6e7b4b3f64 100644 --- a/shell_client.cpp +++ b/shell_client.cpp @@ -372,6 +372,7 @@ void ShellClient::finishInit() { SurfaceInterface *s = surface(); disconnect(s, &SurfaceInterface::committed, this, &ShellClient::finishInit); + updateWindowMargins(); if (!isInitialPositionSet()) { QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop()); placeIn(area); @@ -427,6 +428,16 @@ void ShellClient::deleteClient(ShellClient *c) delete c; } +QSize ShellClient::toWindowGeometry(const QSize &size) 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; +} + QStringList ShellClient::activities() const { // TODO: implement @@ -519,6 +530,7 @@ void ShellClient::addDamage(const QRegion &damage) auto s = surface(); if (s->size().isValid()) { m_clientSize = s->size(); + updateWindowMargins(); updatePendingGeometry(); } markAsMapped(); @@ -617,11 +629,11 @@ void ShellClient::setGeometry(int x, int y, int w, int h, ForceGeometry_t force) // reset geometry to the one before blocking, so that we can compare properly geom = geometryBeforeUpdateBlocking(); } - // TODO: better merge with Client's implementation const QSize requestedClientSize = QSize(w, h) - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); + const QSize requestedWindowGeometrySize = toWindowGeometry(QSize(w, h)); if (requestedClientSize == m_clientSize && !isWaitingForMoveResizeSync() && - (m_requestedClientSize.isEmpty() || requestedClientSize == m_requestedClientSize)) { + (m_requestedClientSize.isEmpty() || requestedWindowGeometrySize == m_requestedClientSize)) { // size didn't change, and we don't need to explicitly request a new size doSetGeometry(QRect(x, y, w, h)); updateMaximizeMode(m_requestedMaximizeMode); @@ -1107,7 +1119,7 @@ bool ShellClient::requestGeometry(const QRect &rect) QSize size; if (rect.isValid()) { - size = rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); + size = toWindowGeometry(rect.size()); } else { size = QSize(0, 0); } @@ -1126,7 +1138,7 @@ bool ShellClient::requestGeometry(const QRect &rect) if (parent) { const QPoint globalClientContentPos = parent->geometry().topLeft() + parent->clientPos(); const QPoint relativeOffset = rect.topLeft() - globalClientContentPos; - serialId = m_xdgShellPopup->configure(QRect(relativeOffset, rect.size())); + serialId = m_xdgShellPopup->configure(QRect(relativeOffset, size)); } } @@ -1867,6 +1879,34 @@ void ShellClient::updateClientOutputs() surface()->setOutputs(clientOutputs); } +void ShellClient::updateWindowMargins() +{ + QRect windowGeometry; + QSize clientSize = m_clientSize; + + if (m_xdgShellSurface) { + windowGeometry = m_xdgShellSurface->windowGeometry(); + } else if (m_xdgShellPopup) { + windowGeometry = m_xdgShellPopup->windowGeometry(); + if (!clientSize.isValid()) { + clientSize = m_xdgShellPopup->initialSize(); + } + } else { + return; + } + + 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 ShellClient::isPopupWindow() const { if (Toplevel::isPopupWindow()) { diff --git a/shell_client.h b/shell_client.h index 7c553eb003..74e034cfd2 100644 --- a/shell_client.h +++ b/shell_client.h @@ -214,6 +214,7 @@ private: void setTransient(); bool shouldExposeToWindowManagement(); void updateClientOutputs(); + void updateWindowMargins(); KWayland::Server::XdgShellSurfaceInterface::States xdgSurfaceStates() const; void updateShowOnScreenEdge(); void updateMaximizeMode(MaximizeMode maximizeMode); @@ -222,6 +223,8 @@ private: QPoint popupOffset(const QRect &anchorRect, const Qt::Edges anchorEdge, const Qt::Edges gravity, const QSize popupSize) const; static void deleteClient(ShellClient *c); + QSize toWindowGeometry(const QSize &geometry) const; + KWayland::Server::ShellSurfaceInterface *m_shellSurface; KWayland::Server::XdgShellSurfaceInterface *m_xdgShellSurface; KWayland::Server::XdgShellPopupInterface *m_xdgShellPopup; @@ -291,6 +294,8 @@ private: QString m_captionSuffix; QHash m_pingSerials; + QMargins m_windowMargins; + bool m_compositingSetup = false; };