diff --git a/autotests/integration/xdgshellclient_test.cpp b/autotests/integration/xdgshellclient_test.cpp index 02638c292b..26143b8b0d 100644 --- a/autotests/integration/xdgshellclient_test.cpp +++ b/autotests/integration/xdgshellclient_test.cpp @@ -72,6 +72,9 @@ private Q_SLOTS: void testUserCanSetFullscreen(); void testUserSetFullscreen(); + void testMaximizeHorizontal(); + void testMaximizeVertical(); + void testMaximizeFull(); void testMaximizedToFullscreen_data(); void testMaximizedToFullscreen(); void testFullscreenMultipleOutputs(); @@ -1698,5 +1701,248 @@ void TestXdgShellClient::testDoubleMaximize() QVERIFY(states.testFlag(XdgShellSurface::State::Maximized)); } +void TestXdgShellClient::testMaximizeHorizontal() +{ + // Create the test client. + QScopedPointer surface; + surface.reset(Test::createSurface()); + QScopedPointer shellSurface; + shellSurface.reset(Test::createXdgShellStableSurface(surface.data(), surface.data(), Test::CreationSetup::CreateOnly)); + QScopedPointer configureRequestedSpy; + configureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &XdgShellSurface::configureRequested)); + surface->commit(Surface::CommitFlag::None); + + // Wait for the initial configure event. + XdgShellSurface::States states; + QVERIFY(configureRequestedSpy->wait()); + QCOMPARE(configureRequestedSpy->count(), 1); + QCOMPARE(configureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); + states = configureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(XdgShellSurface::State::Activated)); + QVERIFY(!states.testFlag(XdgShellSurface::State::Maximized)); + + // Map the client. + shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value()); + AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(800, 600), Qt::blue); + QVERIFY(client); + QVERIFY(client->isActive()); + QVERIFY(client->isMaximizable()); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->size(), QSize(800, 600)); + + // We should receive a configure event when the client becomes active. + QVERIFY(configureRequestedSpy->wait()); + QCOMPARE(configureRequestedSpy->count(), 2); + states = configureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(XdgShellSurface::State::Activated)); + QVERIFY(!states.testFlag(XdgShellSurface::State::Maximized)); + + // Maximize the test client in horizontal direction. + workspace()->slotWindowMaximizeHorizontal(); + QCOMPARE(client->requestedMaximizeMode(), MaximizeHorizontal); + QCOMPARE(client->maximizeMode(), MaximizeRestore); + QVERIFY(configureRequestedSpy->wait()); + QCOMPARE(configureRequestedSpy->count(), 3); + QCOMPARE(configureRequestedSpy->last().at(0).toSize(), QSize(1280, 600)); + states = configureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(XdgShellSurface::State::Maximized)); + + // Draw contents of the maximized client. + QSignalSpy geometryChangedSpy(client, &AbstractClient::geometryChanged); + QVERIFY(geometryChangedSpy.isValid()); + shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value()); + Test::render(surface.data(), QSize(1280, 600), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->size(), QSize(1280, 600)); + QCOMPARE(client->requestedMaximizeMode(), MaximizeHorizontal); + QCOMPARE(client->maximizeMode(), MaximizeHorizontal); + + // Restore the client. + workspace()->slotWindowMaximizeHorizontal(); + QCOMPARE(client->requestedMaximizeMode(), MaximizeRestore); + QCOMPARE(client->maximizeMode(), MaximizeHorizontal); + QVERIFY(configureRequestedSpy->wait()); + QCOMPARE(configureRequestedSpy->count(), 4); + QCOMPARE(configureRequestedSpy->last().at(0).toSize(), QSize(800, 600)); + states = configureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(XdgShellSurface::State::Maximized)); + + // Draw contents of the restored client. + shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value()); + Test::render(surface.data(), QSize(800, 600), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->size(), QSize(800, 600)); + QCOMPARE(client->requestedMaximizeMode(), MaximizeRestore); + QCOMPARE(client->maximizeMode(), MaximizeRestore); + + // Destroy the client. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +void TestXdgShellClient::testMaximizeVertical() +{ + // Create the test client. + QScopedPointer surface; + surface.reset(Test::createSurface()); + QScopedPointer shellSurface; + shellSurface.reset(Test::createXdgShellStableSurface(surface.data(), surface.data(), Test::CreationSetup::CreateOnly)); + QScopedPointer configureRequestedSpy; + configureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &XdgShellSurface::configureRequested)); + surface->commit(Surface::CommitFlag::None); + + // Wait for the initial configure event. + XdgShellSurface::States states; + QVERIFY(configureRequestedSpy->wait()); + QCOMPARE(configureRequestedSpy->count(), 1); + QCOMPARE(configureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); + states = configureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(XdgShellSurface::State::Activated)); + QVERIFY(!states.testFlag(XdgShellSurface::State::Maximized)); + + // Map the client. + shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value()); + AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(800, 600), Qt::blue); + QVERIFY(client); + QVERIFY(client->isActive()); + QVERIFY(client->isMaximizable()); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->size(), QSize(800, 600)); + + // We should receive a configure event when the client becomes active. + QVERIFY(configureRequestedSpy->wait()); + QCOMPARE(configureRequestedSpy->count(), 2); + states = configureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(XdgShellSurface::State::Activated)); + QVERIFY(!states.testFlag(XdgShellSurface::State::Maximized)); + + // Maximize the test client in vertical direction. + workspace()->slotWindowMaximizeVertical(); + QCOMPARE(client->requestedMaximizeMode(), MaximizeVertical); + QCOMPARE(client->maximizeMode(), MaximizeRestore); + QVERIFY(configureRequestedSpy->wait()); + QCOMPARE(configureRequestedSpy->count(), 3); + QCOMPARE(configureRequestedSpy->last().at(0).toSize(), QSize(800, 1024)); + states = configureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(XdgShellSurface::State::Maximized)); + + // Draw contents of the maximized client. + QSignalSpy geometryChangedSpy(client, &AbstractClient::geometryChanged); + QVERIFY(geometryChangedSpy.isValid()); + shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value()); + Test::render(surface.data(), QSize(800, 1024), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->size(), QSize(800, 1024)); + QCOMPARE(client->requestedMaximizeMode(), MaximizeVertical); + QCOMPARE(client->maximizeMode(), MaximizeVertical); + + // Restore the client. + workspace()->slotWindowMaximizeVertical(); + QCOMPARE(client->requestedMaximizeMode(), MaximizeRestore); + QCOMPARE(client->maximizeMode(), MaximizeVertical); + QVERIFY(configureRequestedSpy->wait()); + QCOMPARE(configureRequestedSpy->count(), 4); + QCOMPARE(configureRequestedSpy->last().at(0).toSize(), QSize(800, 600)); + states = configureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(XdgShellSurface::State::Maximized)); + + // Draw contents of the restored client. + shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value()); + Test::render(surface.data(), QSize(800, 600), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->size(), QSize(800, 600)); + QCOMPARE(client->requestedMaximizeMode(), MaximizeRestore); + QCOMPARE(client->maximizeMode(), MaximizeRestore); + + // Destroy the client. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +void TestXdgShellClient::testMaximizeFull() +{ + // Create the test client. + QScopedPointer surface; + surface.reset(Test::createSurface()); + QScopedPointer shellSurface; + shellSurface.reset(Test::createXdgShellStableSurface(surface.data(), surface.data(), Test::CreationSetup::CreateOnly)); + QScopedPointer configureRequestedSpy; + configureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &XdgShellSurface::configureRequested)); + surface->commit(Surface::CommitFlag::None); + + // Wait for the initial configure event. + XdgShellSurface::States states; + QVERIFY(configureRequestedSpy->wait()); + QCOMPARE(configureRequestedSpy->count(), 1); + QCOMPARE(configureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); + states = configureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(XdgShellSurface::State::Activated)); + QVERIFY(!states.testFlag(XdgShellSurface::State::Maximized)); + + // Map the client. + shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value()); + AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(800, 600), Qt::blue); + QVERIFY(client); + QVERIFY(client->isActive()); + QVERIFY(client->isMaximizable()); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->size(), QSize(800, 600)); + + // We should receive a configure event when the client becomes active. + QVERIFY(configureRequestedSpy->wait()); + QCOMPARE(configureRequestedSpy->count(), 2); + states = configureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(XdgShellSurface::State::Activated)); + QVERIFY(!states.testFlag(XdgShellSurface::State::Maximized)); + + // Maximize the test client. + workspace()->slotWindowMaximize(); + QCOMPARE(client->requestedMaximizeMode(), MaximizeFull); + QCOMPARE(client->maximizeMode(), MaximizeRestore); + QVERIFY(configureRequestedSpy->wait()); + QCOMPARE(configureRequestedSpy->count(), 3); + QCOMPARE(configureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); + states = configureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(XdgShellSurface::State::Maximized)); + + // Draw contents of the maximized client. + QSignalSpy geometryChangedSpy(client, &AbstractClient::geometryChanged); + QVERIFY(geometryChangedSpy.isValid()); + shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value()); + Test::render(surface.data(), QSize(1280, 1024), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->size(), QSize(1280, 1024)); + QCOMPARE(client->requestedMaximizeMode(), MaximizeFull); + QCOMPARE(client->maximizeMode(), MaximizeFull); + + // Restore the client. + workspace()->slotWindowMaximize(); + QCOMPARE(client->requestedMaximizeMode(), MaximizeRestore); + QCOMPARE(client->maximizeMode(), MaximizeFull); + QVERIFY(configureRequestedSpy->wait()); + QCOMPARE(configureRequestedSpy->count(), 4); + QCOMPARE(configureRequestedSpy->last().at(0).toSize(), QSize(800, 600)); + states = configureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(XdgShellSurface::State::Maximized)); + + // Draw contents of the restored client. + shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value()); + Test::render(surface.data(), QSize(800, 600), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->size(), QSize(800, 600)); + QCOMPARE(client->requestedMaximizeMode(), MaximizeRestore); + QCOMPARE(client->maximizeMode(), MaximizeRestore); + + // Destroy the client. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); +} + WAYLANDTEST_MAIN(TestXdgShellClient) #include "xdgshellclient_test.moc" diff --git a/src/xdgshellclient.cpp b/src/xdgshellclient.cpp index 36e71e5204..aec5a19402 100644 --- a/src/xdgshellclient.cpp +++ b/src/xdgshellclient.cpp @@ -1644,6 +1644,19 @@ void XdgToplevelClient::changeMaximize(bool horizontal, bool vertical, bool adju changeMaximizeRecursion = false; } + if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) { + QRect savedGeometry = geometryRestore(); + if (!adjust && !(oldMode & MaximizeVertical)) { + savedGeometry.setTop(oldGeometry.top()); + savedGeometry.setBottom(oldGeometry.bottom()); + } + if (!adjust && !(oldMode & MaximizeHorizontal)) { + savedGeometry.setLeft(oldGeometry.left()); + savedGeometry.setRight(oldGeometry.right()); + } + setGeometryRestore(savedGeometry); + } + // Conditional quick tiling exit points const auto oldQuickTileMode = quickTileMode(); if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { @@ -1659,33 +1672,60 @@ void XdgToplevelClient::changeMaximize(bool horizontal, bool vertical, bool adju } } + const MaximizeMode delta = m_requestedMaximizeMode ^ oldMode; + QRect geometry = oldGeometry; + + if (adjust || (delta & MaximizeHorizontal)) { + if (m_requestedMaximizeMode & MaximizeHorizontal) { + // Stretch the window vertically to fit the size of the maximize area. + geometry.setX(clientArea.x()); + geometry.setWidth(clientArea.width()); + } else if (geometryRestore().isValid()) { + // The window is no longer maximized horizontally and the saved geometry is valid. + geometry.setX(geometryRestore().x()); + geometry.setWidth(geometryRestore().width()); + } else { + // The window is no longer maximized horizontally and the saved geometry is + // invalid. This would happen if the window had been mapped in the maximized state. + // We ask the client to resize the window horizontally to its preferred size. + geometry.setX(clientArea.x()); + geometry.setWidth(0); + } + } + + if (adjust || (delta & MaximizeVertical)) { + if (m_requestedMaximizeMode & MaximizeVertical) { + // Stretch the window horizontally to fit the size of the maximize area. + geometry.setY(clientArea.y()); + geometry.setHeight(clientArea.height()); + } else if (geometryRestore().isValid()) { + // The window is no longer maximized vertically and the saved geometry is valid. + geometry.setY(geometryRestore().y()); + geometry.setHeight(geometryRestore().height()); + } else { + // The window is no longer maximized vertically and the saved geometry is + // invalid. This would happen if the window had been mapped in the maximized state. + // We ask the client to resize the window vertically to its preferred size. + geometry.setY(clientArea.y()); + geometry.setHeight(0); + } + } + if (m_requestedMaximizeMode == MaximizeFull) { - setGeometryRestore(oldGeometry); - // TODO: Client has more checks if (options->electricBorderMaximize()) { updateQuickTileMode(QuickTileFlag::Maximize); } else { updateQuickTileMode(QuickTileFlag::None); } - if (quickTileMode() != oldQuickTileMode) { - doSetQuickTileMode(); - emit quickTileModeChanged(); - } - setFrameGeometry(workspace()->clientArea(MaximizeArea, this)); - } else { - if (m_requestedMaximizeMode == MaximizeRestore) { - updateQuickTileMode(QuickTileFlag::None); - } - if (quickTileMode() != oldQuickTileMode) { - doSetQuickTileMode(); - emit quickTileModeChanged(); - } + } else if (m_requestedMaximizeMode == MaximizeRestore) { + updateQuickTileMode(QuickTileFlag::None); + } - if (geometryRestore().isValid()) { - setFrameGeometry(geometryRestore()); - } else { - setFrameGeometry(workspace()->clientArea(PlacementArea, this)); - } + setFrameGeometry(geometry); + + if (oldQuickTileMode != quickTileMode()) { + doSetQuickTileMode(); + emit quickTileModeChanged(); } doSetMaximized();