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 @@ -119,6 +119,12 @@ void testXdgInitiallyMaximised(); void testXdgInitiallyMinimized(); void testXdgWindowGeometry(); + void testMaximizeHorizontal_data(); + void testMaximizeHorizontal(); + void testMaximizeVertical_data(); + void testMaximizeVertical(); + void testMaximizeFull_data(); + void testMaximizeFull(); }; void TestShellClient::initTestCase() @@ -1515,5 +1521,275 @@ QCOMPARE(requestedFullScreenSize, QSize(1280, 1024)); } +void TestShellClient::testMaximizeHorizontal_data() +{ + QTest::addColumn("type"); + QTest::newRow("XdgShellV5") << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("XdgShellV6") << Test::ShellSurfaceType::XdgShellV6; + QTest::newRow("XdgWmBase") << Test::ShellSurfaceType::XdgShellStable; +} + +void TestShellClient::testMaximizeHorizontal() +{ + // Create the test client. + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer surface; + surface.reset(Test::createSurface()); + QScopedPointer shellSurface; + shellSurface.reset(createXdgShellSurface(type, 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()); + ShellClient *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 TestShellClient::testMaximizeVertical_data() +{ + QTest::addColumn("type"); + QTest::newRow("XdgShellV5") << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("XdgShellV6") << Test::ShellSurfaceType::XdgShellV6; + QTest::newRow("XdgWmBase") << Test::ShellSurfaceType::XdgShellStable; +} + +void TestShellClient::testMaximizeVertical() +{ + // Create the test client. + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer surface; + surface.reset(Test::createSurface()); + QScopedPointer shellSurface; + shellSurface.reset(createXdgShellSurface(type, 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()); + ShellClient *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 TestShellClient::testMaximizeFull_data() +{ + QTest::addColumn("type"); + QTest::newRow("XdgShellV5") << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("XdgShellV6") << Test::ShellSurfaceType::XdgShellV6; + QTest::newRow("XdgWmBase") << Test::ShellSurfaceType::XdgShellStable; +} + +void TestShellClient::testMaximizeFull() +{ + // Create the test client. + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer surface; + surface.reset(Test::createSurface()); + QScopedPointer shellSurface; + shellSurface.reset(createXdgShellSurface(type, 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()); + ShellClient *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(TestShellClient) #include "shell_client_test.moc" diff --git a/shell_client.cpp b/shell_client.cpp --- a/shell_client.cpp +++ b/shell_client.cpp @@ -838,6 +838,9 @@ workspace()->clientArea(MaximizeArea, this); const MaximizeMode oldMode = m_requestedMaximizeMode; + // FIXME: Ask for the currently requested geometry instead. Due to geometry + // updates being asynchronous, geometry of the client might be restored to + // an incorrect value. const QRect oldGeometry = geometry(); StackingUpdatesBlocker blocker(workspace()); @@ -851,7 +854,7 @@ } // TODO: add more checks as in Client - if (m_requestedMaximizeMode == oldMode) { + if (!adjust && m_requestedMaximizeMode == oldMode) { return; } @@ -879,48 +882,92 @@ changeMaximizeRecursion = false; } + if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) { + if (!adjust && !(oldMode & MaximizeVertical)) { + m_geomMaximizeRestore.setTop(oldGeometry.top()); + m_geomMaximizeRestore.setBottom(oldGeometry.bottom()); + } + if (!adjust && !(oldMode & MaximizeHorizontal)) { + m_geomMaximizeRestore.setLeft(oldGeometry.left()); + m_geomMaximizeRestore.setRight(oldGeometry.right()); + } + } + // Conditional quick tiling exit points - const auto oldQuickTileMode = quickTileMode(); + // TODO: See if we can simplify this one. + const QuickTileMode oldQuickTileMode = quickTileMode(); if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { if (oldMode == MaximizeFull && !clientArea.contains(m_geomMaximizeRestore.center())) { // Not restoring on the same screen // TODO: The following doesn't work for some reason - //quick_tile_mode = QuickTileNone; // And exit quick tile mode manually - } else if ((oldMode == MaximizeVertical && m_requestedMaximizeMode == MaximizeRestore) || - (oldMode == MaximizeFull && m_requestedMaximizeMode == MaximizeHorizontal)) { + //quick_tile_mode = QuickTileFlag::None; // And exit quick tile mode manually + } else if (oldMode == MaximizeFull || m_requestedMaximizeMode == MaximizeRestore) { // Modifying geometry of a tiled window updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry } } - // TODO: check rules - if (m_requestedMaximizeMode == MaximizeFull) { - m_geomMaximizeRestore = oldGeometry; - // TODO: Client has more checks - if (options->electricBorderMaximize()) { - updateQuickTileMode(QuickTileFlag::Maximize); - } else { - updateQuickTileMode(QuickTileFlag::None); + // We assume that the restore geometry is valid by the time it's used. + const QRect originalGeometry = oldQuickTileMode ? oldGeometry : m_geomMaximizeRestore; + + switch (m_requestedMaximizeMode) { + case MaximizeRestore: { + QRect targetGeometry = originalGeometry; + if (oldMode & MaximizeHorizontal) { + targetGeometry.setLeft(m_geomMaximizeRestore.left()); + targetGeometry.setRight(m_geomMaximizeRestore.right()); } - if (quickTileMode() != oldQuickTileMode) { - emit quickTileModeChanged(); + if (oldMode & MaximizeVertical) { + targetGeometry.setTop(m_geomMaximizeRestore.top()); + targetGeometry.setBottom(m_geomMaximizeRestore.bottom()); } - setGeometry(workspace()->clientArea(MaximizeArea, this)); - workspace()->raiseClient(this); - } else { - if (m_requestedMaximizeMode == MaximizeRestore) { - updateQuickTileMode(QuickTileFlag::None); + setGeometry(targetGeometry); + break; + } + + case MaximizeHorizontal: { + QRect targetGeometry; + if (oldMode & MaximizeVertical) { + targetGeometry.setTop(m_geomMaximizeRestore.top()); + targetGeometry.setBottom(m_geomMaximizeRestore.bottom()); + } else { + targetGeometry.setTop(originalGeometry.top()); + targetGeometry.setBottom(originalGeometry.bottom()); } - if (quickTileMode() != oldQuickTileMode) { - emit quickTileModeChanged(); + targetGeometry.setLeft(clientArea.left()); + targetGeometry.setRight(clientArea.right()); + setGeometry(targetGeometry); + break; + } + + case MaximizeVertical: { + QRect targetGeometry; + if (oldMode & MaximizeHorizontal) { + targetGeometry.setLeft(m_geomMaximizeRestore.left()); + targetGeometry.setRight(m_geomMaximizeRestore.right()); + } else { + targetGeometry.setLeft(originalGeometry.left()); + targetGeometry.setRight(originalGeometry.right()); } + targetGeometry.setTop(clientArea.top()); + targetGeometry.setBottom(clientArea.bottom()); + setGeometry(targetGeometry); + break; + } - if (m_geomMaximizeRestore.isValid()) { - setGeometry(m_geomMaximizeRestore); + case MaximizeFull: + if (options->electricBorderMaximize()) { + updateQuickTileMode(QuickTileFlag::Maximize); } else { - setGeometry(workspace()->clientArea(PlacementArea, this)); + updateQuickTileMode(QuickTileFlag::None); } + setGeometry(clientArea); + break; + } + + if (oldQuickTileMode != quickTileMode()) { + emit quickTileModeChanged(); } }