From 702a4ff68877890129963b02d4913f0ef5b74641 Mon Sep 17 00:00:00 2001 From: Vlad Zagorodniy Date: Tue, 9 Jul 2019 21:07:21 +0300 Subject: [PATCH] [wayland] Implement maximize rules Summary: There is still one small issue that has to be addressed in the future: xdg-toplevel doesn't have states like MAXIMIZED_VERT or MAXIMIZED_HORZ, thus Window Rules KCM should display only single maximize rule(not two) for wayland clients. Test Plan: The new tests pass. Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D19414 --- .../integration/shell_client_rules_test.cpp | 610 ++++++++++++++++++ shell_client.cpp | 22 +- workspace.cpp | 3 + 3 files changed, 632 insertions(+), 3 deletions(-) diff --git a/autotests/integration/shell_client_rules_test.cpp b/autotests/integration/shell_client_rules_test.cpp index 4c2ea11ef5..9be41966d2 100644 --- a/autotests/integration/shell_client_rules_test.cpp +++ b/autotests/integration/shell_client_rules_test.cpp @@ -75,6 +75,19 @@ private Q_SLOTS: void testSizeForceTemporarily_data(); void testSizeForceTemporarily(); + void testMaximizeDontAffect_data(); + void testMaximizeDontAffect(); + void testMaximizeApply_data(); + void testMaximizeApply(); + void testMaximizeRemember_data(); + void testMaximizeRemember(); + void testMaximizeForce_data(); + void testMaximizeForce(); + void testMaximizeApplyNow_data(); + void testMaximizeApplyNow(); + void testMaximizeForceTemporarily_data(); + void testMaximizeForceTemporarily(); + void testDesktopDontAffect_data(); void testDesktopDontAffect(); void testDesktopApply_data(); @@ -1248,6 +1261,603 @@ void TestShellClientRules::testSizeForceTemporarily() QVERIFY(Test::waitForWindowDestroyed(client)); } +TEST_DATA(testMaximizeDontAffect) + +void TestShellClientRules::testMaximizeDontAffect() +{ + // Initialize RuleBook with the test rule. + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + config->group("General").writeEntry("count", 1); + KConfigGroup group = config->group("1"); + group.writeEntry("maximizehoriz", true); + group.writeEntry("maximizehorizrule", int(Rules::DontAffect)); + group.writeEntry("maximizevert", true); + group.writeEntry("maximizevertrule", int(Rules::DontAffect)); + group.writeEntry("wmclass", "org.kde.foo"); + group.writeEntry("wmclasscomplete", false); + group.writeEntry("wmclassmatch", int(Rules::ExactMatch)); + group.sync(); + RuleBook::self()->setConfig(config); + workspace()->slotReconfigure(); + + // 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)); + shellSurface->setAppId("org.kde.foo"); + 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(100, 50), Qt::blue); + QVERIFY(client); + QVERIFY(client->isActive()); + QVERIFY(client->isMaximizable()); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->size(), QSize(100, 50)); + + // 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)); + + // Destroy the client. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testMaximizeApply) + +void TestShellClientRules::testMaximizeApply() +{ + // Initialize RuleBook with the test rule. + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + config->group("General").writeEntry("count", 1); + KConfigGroup group = config->group("1"); + group.writeEntry("maximizehoriz", true); + group.writeEntry("maximizehorizrule", int(Rules::Apply)); + group.writeEntry("maximizevert", true); + group.writeEntry("maximizevertrule", int(Rules::Apply)); + group.writeEntry("wmclass", "org.kde.foo"); + group.writeEntry("wmclasscomplete", false); + group.writeEntry("wmclassmatch", int(Rules::ExactMatch)); + group.sync(); + RuleBook::self()->setConfig(config); + workspace()->slotReconfigure(); + + // 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)); + shellSurface->setAppId("org.kde.foo"); + 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(1280, 1024)); + 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(1280, 1024), Qt::blue); + QVERIFY(client); + QVERIFY(client->isActive()); + QVERIFY(client->isMaximizable()); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(client->size(), QSize(1280, 1024)); + + // 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)); + + // One should still be able to change the maximized state of the client. + workspace()->slotWindowMaximize(); + QVERIFY(configureRequestedSpy->wait()); + QCOMPARE(configureRequestedSpy->count(), 3); + 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)); + + QSignalSpy geometryChangedSpy(client, &AbstractClient::geometryChanged); + QVERIFY(geometryChangedSpy.isValid()); + shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value()); + Test::render(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->size(), QSize(100, 50)); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + + // If we create the client again, it should be initially maximized. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); + surface.reset(Test::createSurface()); + shellSurface.reset(createXdgShellSurface(type, surface.data(), surface.data(), Test::CreationSetup::CreateOnly)); + configureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &XdgShellSurface::configureRequested)); + shellSurface->setAppId("org.kde.foo"); + surface->commit(Surface::CommitFlag::None); + + QVERIFY(configureRequestedSpy->wait()); + QCOMPARE(configureRequestedSpy->count(), 1); + QCOMPARE(configureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); + states = configureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(XdgShellSurface::State::Activated)); + QVERIFY(states.testFlag(XdgShellSurface::State::Maximized)); + + shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value()); + client = Test::renderAndWaitForShown(surface.data(), QSize(1280, 1024), Qt::blue); + QVERIFY(client); + QVERIFY(client->isActive()); + QVERIFY(client->isMaximizable()); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(client->size(), QSize(1280, 1024)); + + 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)); + + // Destroy the client. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testMaximizeRemember) + +void TestShellClientRules::testMaximizeRemember() +{ + // Initialize RuleBook with the test rule. + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + config->group("General").writeEntry("count", 1); + KConfigGroup group = config->group("1"); + group.writeEntry("maximizehoriz", true); + group.writeEntry("maximizehorizrule", int(Rules::Remember)); + group.writeEntry("maximizevert", true); + group.writeEntry("maximizevertrule", int(Rules::Remember)); + group.writeEntry("wmclass", "org.kde.foo"); + group.writeEntry("wmclasscomplete", false); + group.writeEntry("wmclassmatch", int(Rules::ExactMatch)); + group.sync(); + RuleBook::self()->setConfig(config); + workspace()->slotReconfigure(); + + // 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)); + shellSurface->setAppId("org.kde.foo"); + 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(1280, 1024)); + 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(1280, 1024), Qt::blue); + QVERIFY(client); + QVERIFY(client->isActive()); + QVERIFY(client->isMaximizable()); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(client->size(), QSize(1280, 1024)); + + // 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)); + + // One should still be able to change the maximized state of the client. + workspace()->slotWindowMaximize(); + QVERIFY(configureRequestedSpy->wait()); + QCOMPARE(configureRequestedSpy->count(), 3); + 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)); + + QSignalSpy geometryChangedSpy(client, &AbstractClient::geometryChanged); + QVERIFY(geometryChangedSpy.isValid()); + shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value()); + Test::render(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->size(), QSize(100, 50)); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + + // If we create the client again, it should not be maximized (because last time it wasn't). + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); + surface.reset(Test::createSurface()); + shellSurface.reset(createXdgShellSurface(type, surface.data(), surface.data(), Test::CreationSetup::CreateOnly)); + configureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &XdgShellSurface::configureRequested)); + shellSurface->setAppId("org.kde.foo"); + surface->commit(Surface::CommitFlag::None); + + 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)); + + shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value()); + client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(client); + QVERIFY(client->isActive()); + QVERIFY(client->isMaximizable()); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->size(), QSize(100, 50)); + + 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)); + + // Destroy the client. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testMaximizeForce) + +void TestShellClientRules::testMaximizeForce() +{ + // Initialize RuleBook with the test rule. + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + config->group("General").writeEntry("count", 1); + KConfigGroup group = config->group("1"); + group.writeEntry("maximizehoriz", true); + group.writeEntry("maximizehorizrule", int(Rules::Force)); + group.writeEntry("maximizevert", true); + group.writeEntry("maximizevertrule", int(Rules::Force)); + group.writeEntry("wmclass", "org.kde.foo"); + group.writeEntry("wmclasscomplete", false); + group.writeEntry("wmclassmatch", int(Rules::ExactMatch)); + group.sync(); + RuleBook::self()->setConfig(config); + workspace()->slotReconfigure(); + + // 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)); + shellSurface->setAppId("org.kde.foo"); + 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(1280, 1024)); + 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(1280, 1024), Qt::blue); + QVERIFY(client); + QVERIFY(client->isActive()); + QVERIFY(!client->isMaximizable()); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(client->size(), QSize(1280, 1024)); + + // 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)); + + // Any attempt to change the maximized state should not succeed. + const QRect oldGeometry = client->geometry(); + workspace()->slotWindowMaximize(); + QVERIFY(!configureRequestedSpy->wait(100)); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(client->geometry(), oldGeometry); + + // If we create the client again, the maximized state should still be forced. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); + surface.reset(Test::createSurface()); + shellSurface.reset(createXdgShellSurface(type, surface.data(), surface.data(), Test::CreationSetup::CreateOnly)); + configureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &XdgShellSurface::configureRequested)); + shellSurface->setAppId("org.kde.foo"); + surface->commit(Surface::CommitFlag::None); + + QVERIFY(configureRequestedSpy->wait()); + QCOMPARE(configureRequestedSpy->count(), 1); + QCOMPARE(configureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); + states = configureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(XdgShellSurface::State::Activated)); + QVERIFY(states.testFlag(XdgShellSurface::State::Maximized)); + + shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value()); + client = Test::renderAndWaitForShown(surface.data(), QSize(1280, 1024), Qt::blue); + QVERIFY(client); + QVERIFY(client->isActive()); + QVERIFY(!client->isMaximizable()); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(client->size(), QSize(1280, 1024)); + + 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)); + + // Destroy the client. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testMaximizeApplyNow) + +void TestShellClientRules::testMaximizeApplyNow() +{ + // 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)); + shellSurface->setAppId("org.kde.foo"); + 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(100, 50), Qt::blue); + QVERIFY(client); + QVERIFY(client->isActive()); + QVERIFY(client->isMaximizable()); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->size(), QSize(100, 50)); + + // 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)); + + // Initialize RuleBook with the test rule. + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + config->group("General").writeEntry("count", 1); + KConfigGroup group = config->group("1"); + group.writeEntry("maximizehoriz", true); + group.writeEntry("maximizehorizrule", int(Rules::ApplyNow)); + group.writeEntry("maximizevert", true); + group.writeEntry("maximizevertrule", int(Rules::ApplyNow)); + group.writeEntry("wmclass", "org.kde.foo"); + group.writeEntry("wmclasscomplete", false); + group.writeEntry("wmclassmatch", int(Rules::ExactMatch)); + group.sync(); + RuleBook::self()->setConfig(config); + workspace()->slotReconfigure(); + + // We should receive a configure event with a new surface size. + 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::Activated)); + 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->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + + // The client still has to be maximizeable. + QVERIFY(client->isMaximizable()); + + // Restore the client. + workspace()->slotWindowMaximize(); + QVERIFY(configureRequestedSpy->wait()); + QCOMPARE(configureRequestedSpy->count(), 4); + QCOMPARE(configureRequestedSpy->last().at(0).toSize(), QSize(100, 50)); + states = configureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(XdgShellSurface::State::Activated)); + QVERIFY(!states.testFlag(XdgShellSurface::State::Maximized)); + + shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value()); + Test::render(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(client->size(), QSize(100, 50)); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + + // The rule should be discarded after it's been applied. + const QRect oldGeometry = client->geometry(); + client->evaluateWindowRules(); + QVERIFY(!configureRequestedSpy->wait(100)); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->geometry(), oldGeometry); + + // Destroy the client. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testMaximizeForceTemporarily) + +void TestShellClientRules::testMaximizeForceTemporarily() +{ + // Initialize RuleBook with the test rule. + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + config->group("General").writeEntry("count", 1); + KConfigGroup group = config->group("1"); + group.writeEntry("maximizehoriz", true); + group.writeEntry("maximizehorizrule", int(Rules::ForceTemporarily)); + group.writeEntry("maximizevert", true); + group.writeEntry("maximizevertrule", int(Rules::ForceTemporarily)); + group.writeEntry("wmclass", "org.kde.foo"); + group.writeEntry("wmclasscomplete", false); + group.writeEntry("wmclassmatch", int(Rules::ExactMatch)); + group.sync(); + RuleBook::self()->setConfig(config); + workspace()->slotReconfigure(); + + // 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)); + shellSurface->setAppId("org.kde.foo"); + 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(1280, 1024)); + 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(1280, 1024), Qt::blue); + QVERIFY(client); + QVERIFY(client->isActive()); + QVERIFY(!client->isMaximizable()); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(client->size(), QSize(1280, 1024)); + + // 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)); + + // Any attempt to change the maximized state should not succeed. + const QRect oldGeometry = client->geometry(); + workspace()->slotWindowMaximize(); + QVERIFY(!configureRequestedSpy->wait(100)); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(client->geometry(), oldGeometry); + + // The rule should be discarded if we close the client. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); + surface.reset(Test::createSurface()); + shellSurface.reset(createXdgShellSurface(type, surface.data(), surface.data(), Test::CreationSetup::CreateOnly)); + configureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &XdgShellSurface::configureRequested)); + shellSurface->setAppId("org.kde.foo"); + surface->commit(Surface::CommitFlag::None); + + 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)); + + shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value()); + client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(client); + QVERIFY(client->isActive()); + QVERIFY(client->isMaximizable()); + QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(client->size(), QSize(100, 50)); + + 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)); + + // Destroy the client. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); +} + TEST_DATA(testDesktopDontAffect) void TestShellClientRules::testDesktopDontAffect() diff --git a/shell_client.cpp b/shell_client.cpp index 3be1a7a14a..5628707ea5 100644 --- a/shell_client.cpp +++ b/shell_client.cpp @@ -194,6 +194,12 @@ void ShellClient::initSurface(T *shellSurface) // ignore for wl_shell - there it is mutual exclusive and messes with the geometry return; } + + // If the maximized state of the client hasn't been changed due to a window + // rule or because the requested state is the same as the current, then the + // compositor still has to send a configure event. + RequestGeometryBlocker blocker(this); + maximize(maximized ? MaximizeFull : MaximizeRestore); } ); @@ -366,6 +372,8 @@ void ShellClient::finishInit() { setGeometry(ruledGeometry); } + maximize(rules()->checkMaximize(maximizeMode(), true)); + setDesktop(rules()->checkDesktop(desktop(), true)); setDesktopFileName(rules()->checkDesktopFile(desktopFileName(), true).toUtf8()); if (rules()->checkMinimize(isMinimized(), true)) { @@ -384,6 +392,11 @@ void ShellClient::finishInit() { needsPlacement = false; } + // Don't place the client if the maximize state is set by a rule. + if (requestedMaximizeMode() != MaximizeRestore) { + needsPlacement = false; + } + discardTemporaryRules(); RuleBook::self()->discardUsed(this, false); // Remove Apply Now rules. updateWindowRules(Rules::All); @@ -777,6 +790,9 @@ bool ShellClient::isMaximizable() const if (!isResizable()) { return false; } + if (rules()->checkMaximize(MaximizeRestore) != MaximizeRestore || rules()->checkMaximize(MaximizeFull) != MaximizeFull) { + return false; + } return true; } @@ -876,9 +892,9 @@ void ShellClient::changeMaximize(bool horizontal, bool vertical, bool adjust) if (horizontal) m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeHorizontal); } - // TODO: add more checks as in Client - if (m_requestedMaximizeMode == oldMode) { + m_requestedMaximizeMode = rules()->checkMaximize(m_requestedMaximizeMode); + if (!adjust && m_requestedMaximizeMode == oldMode) { return; } @@ -924,7 +940,6 @@ void ShellClient::changeMaximize(bool horizontal, bool vertical, bool adjust) } } - // TODO: check rules if (m_requestedMaximizeMode == MaximizeFull) { m_geomMaximizeRestore = oldGeometry; // TODO: Client has more checks @@ -1485,6 +1500,7 @@ void ShellClient::updateMaximizeMode(MaximizeMode maximizeMode) } m_maximizeMode = maximizeMode; + updateWindowRules(Rules::MaximizeHoriz | Rules::MaximizeVert | Rules::Position | Rules::Size); emit clientMaximizedStateChanged(this, m_maximizeMode); emit clientMaximizedStateChanged(this, m_maximizeMode & MaximizeHorizontal, m_maximizeMode & MaximizeVertical); diff --git a/workspace.cpp b/workspace.cpp index 4aecea969d..82b493f6a1 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -301,6 +301,9 @@ void Workspace::init() if (c->isFullScreen()) { placementDone = true; } + if (c->maximizeMode() == MaximizeMode::MaximizeFull) { + placementDone = true; + } if (c->rules()->checkPosition(invalidPoint, true) != invalidPoint) { placementDone = true; }