diff --git a/autotests/integration/shell_client_test.cpp b/autotests/integration/shell_client_test.cpp --- a/autotests/integration/shell_client_test.cpp +++ b/autotests/integration/shell_client_test.cpp @@ -405,6 +405,24 @@ QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + ShellSurface* wlShellSurface = nullptr; + XdgShellSurface *xdgShellSurface = nullptr; + // fullscreen the window + switch (type) { + case Test::ShellSurfaceType::WlShell: + wlShellSurface = qobject_cast(shellSurface.data()); + break; + case Test::ShellSurfaceType::XdgShellV5: + case Test::ShellSurfaceType::XdgShellV6: + xdgShellSurface = qobject_cast(shellSurface.data()); + break; + default: + Q_UNREACHABLE(); + break; + } + QVERIFY(wlShellSurface || xdgShellSurface); + QVERIFY(!(wlShellSurface && xdgShellSurface)); + // create deco QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); @@ -429,20 +447,18 @@ QVERIFY(geometryChangedSpy.isValid()); QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangeRequestedSpy.isValid()); + QSignalSpy configureRequestedSpy(shellSurface.data(), SIGNAL(configureRequested(QSize, KWayland::Client::XdgShellSurface::States, quint32))); + if (xdgShellSurface) { + QVERIFY(configureRequestedSpy.isValid()); + } - // fullscreen the window - switch (type) { - case Test::ShellSurfaceType::WlShell: - qobject_cast(shellSurface.data())->setFullscreen(); - break; - case Test::ShellSurfaceType::XdgShellV5: - case Test::ShellSurfaceType::XdgShellV6: - qobject_cast(shellSurface.data())->setFullscreen(true); - break; - default: - Q_UNREACHABLE(); - break; + if (wlShellSurface) { + wlShellSurface->setFullscreen(); + } + if (xdgShellSurface) { + xdgShellSurface->setFullscreen(true); } + QVERIFY(fullscreenChangedSpy.wait()); QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 1); @@ -452,7 +468,12 @@ QCOMPARE(c->clientSize(), QSize(100, 50)); QVERIFY(geometryChangedSpy.isEmpty()); - // render at the new size + if (xdgShellSurface) { + for (const auto &it: configureRequestedSpy) { + xdgShellSurface->ackConfigure(it[2].toInt()); + } + } + Test::render(surface.data(), sizeChangeRequestedSpy.first().first().toSize(), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 1); @@ -462,17 +483,11 @@ QCOMPARE(c->layer(), ActiveLayer); // swap back to normal - switch (type) { - case Test::ShellSurfaceType::WlShell: - qobject_cast(shellSurface.data())->setToplevel(); - break; - case Test::ShellSurfaceType::XdgShellV5: - case Test::ShellSurfaceType::XdgShellV6: - qobject_cast(shellSurface.data())->setFullscreen(false); - break; - default: - Q_UNREACHABLE(); - break; + if (wlShellSurface) { + wlShellSurface->setToplevel(); + } + if (xdgShellSurface) { + xdgShellSurface->setFullscreen(false); } QVERIFY(fullscreenChangedSpy.wait()); QVERIFY(sizeChangeRequestedSpy.wait()); @@ -602,6 +617,24 @@ QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + ShellSurface* wlShellSurface = nullptr; + XdgShellSurface *xdgShellSurface = nullptr; + // fullscreen the window + switch (type) { + case Test::ShellSurfaceType::WlShell: + wlShellSurface = qobject_cast(shellSurface.data()); + break; + case Test::ShellSurfaceType::XdgShellV5: + case Test::ShellSurfaceType::XdgShellV6: + xdgShellSurface = qobject_cast(shellSurface.data()); + break; + default: + Q_UNREACHABLE(); + break; + } + QVERIFY(wlShellSurface || xdgShellSurface); + QVERIFY(!(wlShellSurface && xdgShellSurface)); + // create deco QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); @@ -624,38 +657,30 @@ QVERIFY(geometryChangedSpy.isValid()); QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangeRequestedSpy.isValid()); + QSignalSpy configureRequestedSpy(shellSurface.data(), SIGNAL(configureRequested(QSize, KWayland::Client::XdgShellSurface::States, quint32))); + if (xdgShellSurface) { + QVERIFY(configureRequestedSpy.isValid()); + } // change to maximize - switch (type) { - case Test::ShellSurfaceType::WlShell: - qobject_cast(shellSurface.data())->setMaximized(); - break; - case Test::ShellSurfaceType::XdgShellV5: - case Test::ShellSurfaceType::XdgShellV6: - qobject_cast(shellSurface.data())->setMaximized(true); - break; - default: - Q_UNREACHABLE(); - break; + if (wlShellSurface) { + wlShellSurface->setMaximized(); + } + if (xdgShellSurface) { + xdgShellSurface->setMaximized(true); } QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 1); QCOMPARE(c->maximizeMode(), MaximizeFull); QCOMPARE(geometryChangedSpy.isEmpty(), false); geometryChangedSpy.clear(); // fullscreen the window - switch (type) { - case Test::ShellSurfaceType::WlShell: - qobject_cast(shellSurface.data())->setFullscreen(); - break; - case Test::ShellSurfaceType::XdgShellV5: - case Test::ShellSurfaceType::XdgShellV6: - qobject_cast(shellSurface.data())->setFullscreen(true); - break; - default: - Q_UNREACHABLE(); - break; + if (wlShellSurface) { + wlShellSurface->setFullscreen(); + } + if (xdgShellSurface) { + xdgShellSurface->setFullscreen(true); } QVERIFY(fullscreenChangedSpy.wait()); if (decoMode == ServerSideDecoration::Mode::Server) { @@ -668,6 +693,12 @@ QCOMPARE(c->clientSize(), QSize(100, 50)); QVERIFY(geometryChangedSpy.isEmpty()); + if (xdgShellSurface) { + for (const auto &it: configureRequestedSpy) { + xdgShellSurface->ackConfigure(it[2].toInt()); + } + } + // render at the new size Test::render(surface.data(), sizeChangeRequestedSpy.last().first().toSize(), Qt::red); QVERIFY(geometryChangedSpy.wait()); diff --git a/shell_client.h b/shell_client.h --- a/shell_client.h +++ b/shell_client.h @@ -202,14 +202,25 @@ void updateClientOutputs(); KWayland::Server::XdgShellSurfaceInterface::States xdgSurfaceStates() const; void updateShowOnScreenEdge(); + // called on surface commit and processes all m_pendingConfigureRequests up to m_lastAckedConfigureReqest + void updatePendingGeometry(); static void deleteClient(ShellClient *c); KWayland::Server::ShellSurfaceInterface *m_shellSurface; KWayland::Server::XdgShellSurfaceInterface *m_xdgShellSurface; KWayland::Server::XdgShellPopupInterface *m_xdgShellPopup; QSize m_clientSize; - ClearablePoint m_positionAfterResize; // co-ordinates saved from a requestGeometry call, real geometry will be updated after the next damage event when the client has resized + struct PendingConfigureRequest { + //note for wl_shell we have no serial, so serialId and m_lastAckedConfigureRequest will always be 0 + //meaning we treat a surface commit as having processed all requests + quint32 serialId = 0; + // position to apply after a resize operation has been completed + QPoint positionAfterResize; + }; + QVector m_pendingConfigureRequests; + quint32 m_lastAckedConfigureRequest = 0; + QRect m_geomFsRestore; //size and position of the window before it was set to fullscreen bool m_closing = false; quint32 m_windowId = 0; diff --git a/shell_client.cpp b/shell_client.cpp --- a/shell_client.cpp +++ b/shell_client.cpp @@ -261,6 +261,10 @@ } }); + connect(m_xdgShellSurface, &XdgShellSurfaceInterface::configureAcknowledged, this, [this](int serial) { + m_lastAckedConfigureRequest = serial; + }); + connect(global, &XdgShellInterface::pingTimeout, this, [this](qint32 serial) { auto it = m_pingSerials.find(serial); @@ -322,6 +326,10 @@ m_hasPopupGrab = true; }); + connect(m_xdgShellSurface, &XdgShellSurfaceInterface::configureAcknowledged, this, [this](int serial) { + m_lastAckedConfigureRequest = serial; + }); + QRect position = QRect(m_xdgShellPopup->transientOffset(), m_xdgShellPopup->initialSize()); m_xdgShellPopup->configure(position); @@ -485,13 +493,7 @@ auto s = surface(); if (s->size().isValid()) { m_clientSize = s->size(); - QPoint position = geom.topLeft(); - if (m_positionAfterResize.isValid()) { - addLayerRepaint(geometry()); - position = m_positionAfterResize.point(); - m_positionAfterResize.clear(); - } - doSetGeometry(QRect(position, m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); + updatePendingGeometry(); } markAsMapped(); setDepth((s->buffer()->hasAlphaChannel() && !isDesktop()) ? 32 : 24); @@ -601,7 +603,7 @@ geom = geometryBeforeUpdateBlocking(); } // TODO: better merge with Client's implementation - if (QSize(w, h) == geom.size() && !m_positionAfterResize.isValid()) { + if (QSize(w, h) == geom.size() && !isWaitingForMoveResizeSync()) { // size didn't change, update directly doSetGeometry(QRect(x, y, w, h)); } else { @@ -1170,29 +1172,55 @@ m_blockedRequestGeometry = rect; return; } - m_positionAfterResize.setPoint(rect.topLeft()); + PendingConfigureRequest configureRequest; + configureRequest.positionAfterResize = rect.topLeft(); + const QSize size = rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); if (m_shellSurface) { m_shellSurface->requestSize(size); } if (m_xdgShellSurface) { - m_xdgShellSurface->configure(xdgSurfaceStates(), size); + configureRequest.serialId = m_xdgShellSurface->configure(xdgSurfaceStates(), size); } if (m_xdgShellPopup) { auto parent = transientFor(); if (parent) { const QPoint globalClientContentPos = parent->geometry().topLeft() + parent->clientPos(); const QPoint relativeOffset = rect.topLeft() -globalClientContentPos; - m_xdgShellPopup->configure(QRect(relativeOffset, rect.size())); + configureRequest.serialId = m_xdgShellPopup->configure(QRect(relativeOffset, rect.size())); } } + m_pendingConfigureRequests.append(configureRequest); + m_blockedRequestGeometry = QRect(); if (m_internal) { m_internalWindow->setGeometry(QRect(rect.topLeft() + QPoint(borderLeft(), borderTop()), rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); } } +void ShellClient::updatePendingGeometry() +{ + QPoint position = geom.topLeft(); + for (auto it = m_pendingConfigureRequests.begin(); it != m_pendingConfigureRequests.end(); it++) { + if (it->serialId > m_lastAckedConfigureRequest) { + //this serial is not acked yet, therefore we know all future serials are not + break; + } + if (it->serialId == m_lastAckedConfigureRequest) { + if (position != it->positionAfterResize) { + addLayerRepaint(geometry()); + } + position = it->positionAfterResize; + + m_pendingConfigureRequests.erase(m_pendingConfigureRequests.begin(), ++it); + break; + } + //else serialId < m_lastAckedConfigureRequest and the state is now irrelevant and can be ignored + } + doSetGeometry(QRect(position, m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); +} + void ShellClient::clientFullScreenChanged(bool fullScreen) { setFullScreen(fullScreen, false); @@ -1534,7 +1562,7 @@ bool ShellClient::isWaitingForMoveResizeSync() const { - return m_positionAfterResize.isValid(); + return !m_pendingConfigureRequests.isEmpty(); } void ShellClient::doResizeSync()