diff --git a/autotests/integration/shell_client_rules_test.cpp b/autotests/integration/shell_client_rules_test.cpp
index 0e78ae2b03..4c2ea11ef5 100644
--- a/autotests/integration/shell_client_rules_test.cpp
+++ b/autotests/integration/shell_client_rules_test.cpp
@@ -20,6 +20,8 @@ along with this program. If not, see .
*********************************************************************/
#include "kwin_wayland_test.h"
+
+#include "cursor.h"
#include "platform.h"
#include "rules.h"
#include "screens.h"
@@ -47,6 +49,32 @@ private Q_SLOTS:
void init();
void cleanup();
+ void testPositionDontAffect_data();
+ void testPositionDontAffect();
+ void testPositionApply_data();
+ void testPositionApply();
+ void testPositionRemember_data();
+ void testPositionRemember();
+ void testPositionForce_data();
+ void testPositionForce();
+ void testPositionApplyNow_data();
+ void testPositionApplyNow();
+ void testPositionForceTemporarily_data();
+ void testPositionForceTemporarily();
+
+ void testSizeDontAffect_data();
+ void testSizeDontAffect();
+ void testSizeApply_data();
+ void testSizeApply();
+ void testSizeRemember_data();
+ void testSizeRemember();
+ void testSizeForce_data();
+ void testSizeForce();
+ void testSizeApplyNow_data();
+ void testSizeApplyNow();
+ void testSizeForceTemporarily_data();
+ void testSizeForceTemporarily();
+
void testDesktopDontAffect_data();
void testDesktopDontAffect();
void testDesktopApply_data();
@@ -251,6 +279,975 @@ std::tuple createWindow(Test::Shell
return {client, surface, shellSurface};
}
+TEST_DATA(testPositionDontAffect)
+
+void TestShellClientRules::testPositionDontAffect()
+{
+ // 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("position", QPoint(42, 42));
+ group.writeEntry("positionrule", 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);
+ ShellClient *client;
+ Surface *surface;
+ XdgShellSurface *shellSurface;
+ std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo");
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+
+ // The position of the client should not be affected by the rule. The default
+ // placement policy will put the client in the top-left corner of the screen.
+ QVERIFY(client->isMovable());
+ QVERIFY(client->isMovableAcrossScreens());
+ QCOMPARE(client->pos(), QPoint(0, 0));
+
+ // Destroy the client.
+ delete shellSurface;
+ delete surface;
+ QVERIFY(Test::waitForWindowDestroyed(client));
+}
+
+TEST_DATA(testPositionApply)
+
+void TestShellClientRules::testPositionApply()
+{
+ // 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("position", QPoint(42, 42));
+ group.writeEntry("positionrule", 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);
+ ShellClient *client;
+ Surface *surface;
+ XdgShellSurface *shellSurface;
+ std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo");
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+
+ // The client should be moved to the position specified by the rule.
+ QVERIFY(client->isMovable());
+ QVERIFY(client->isMovableAcrossScreens());
+ QCOMPARE(client->pos(), QPoint(42, 42));
+
+ // One should still be able to move the client around.
+ QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
+ QVERIFY(clientStartMoveResizedSpy.isValid());
+ QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized);
+ QVERIFY(clientStepUserMovedResizedSpy.isValid());
+ QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized);
+ QVERIFY(clientFinishUserMovedResizedSpy.isValid());
+
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+ workspace()->slotWindowMove();
+ QCOMPARE(workspace()->moveResizeClient(), client);
+ QCOMPARE(clientStartMoveResizedSpy.count(), 1);
+ QVERIFY(client->isMove());
+ QVERIFY(!client->isResize());
+
+ const QPoint cursorPos = KWin::Cursor::pos();
+ client->keyPressEvent(Qt::Key_Right);
+ client->updateMoveResize(KWin::Cursor::pos());
+ QCOMPARE(KWin::Cursor::pos(), cursorPos + QPoint(8, 0));
+ QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
+ QCOMPARE(client->pos(), QPoint(50, 42));
+
+ client->keyPressEvent(Qt::Key_Enter);
+ QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+ QCOMPARE(client->pos(), QPoint(50, 42));
+
+ // The rule should be applied again if the client appears after it's been closed.
+ delete shellSurface;
+ delete surface;
+ QVERIFY(Test::waitForWindowDestroyed(client));
+ std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo");
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+ QVERIFY(client->isMovable());
+ QVERIFY(client->isMovableAcrossScreens());
+ QCOMPARE(client->pos(), QPoint(42, 42));
+
+ // Destroy the client.
+ delete shellSurface;
+ delete surface;
+ QVERIFY(Test::waitForWindowDestroyed(client));
+}
+
+TEST_DATA(testPositionRemember)
+
+void TestShellClientRules::testPositionRemember()
+{
+ // 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("position", QPoint(42, 42));
+ group.writeEntry("positionrule", 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);
+ ShellClient *client;
+ Surface *surface;
+ XdgShellSurface *shellSurface;
+ std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo");
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+
+ // The client should be moved to the position specified by the rule.
+ QVERIFY(client->isMovable());
+ QVERIFY(client->isMovableAcrossScreens());
+ QCOMPARE(client->pos(), QPoint(42, 42));
+
+ // One should still be able to move the client around.
+ QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
+ QVERIFY(clientStartMoveResizedSpy.isValid());
+ QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized);
+ QVERIFY(clientStepUserMovedResizedSpy.isValid());
+ QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized);
+ QVERIFY(clientFinishUserMovedResizedSpy.isValid());
+
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+ workspace()->slotWindowMove();
+ QCOMPARE(workspace()->moveResizeClient(), client);
+ QCOMPARE(clientStartMoveResizedSpy.count(), 1);
+ QVERIFY(client->isMove());
+ QVERIFY(!client->isResize());
+
+ const QPoint cursorPos = KWin::Cursor::pos();
+ client->keyPressEvent(Qt::Key_Right);
+ client->updateMoveResize(KWin::Cursor::pos());
+ QCOMPARE(KWin::Cursor::pos(), cursorPos + QPoint(8, 0));
+ QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
+ QCOMPARE(client->pos(), QPoint(50, 42));
+
+ client->keyPressEvent(Qt::Key_Enter);
+ QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+ QCOMPARE(client->pos(), QPoint(50, 42));
+
+ // The client should be placed at the last know position if we reopen it.
+ delete shellSurface;
+ delete surface;
+ QVERIFY(Test::waitForWindowDestroyed(client));
+ std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo");
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+ QVERIFY(client->isMovable());
+ QVERIFY(client->isMovableAcrossScreens());
+ QCOMPARE(client->pos(), QPoint(50, 42));
+
+ // Destroy the client.
+ delete shellSurface;
+ delete surface;
+ QVERIFY(Test::waitForWindowDestroyed(client));
+}
+
+TEST_DATA(testPositionForce)
+
+void TestShellClientRules::testPositionForce()
+{
+ // 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("position", QPoint(42, 42));
+ group.writeEntry("positionrule", 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);
+ ShellClient *client;
+ Surface *surface;
+ XdgShellSurface *shellSurface;
+ std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo");
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+
+ // The client should be moved to the position specified by the rule.
+ QVERIFY(!client->isMovable());
+ QVERIFY(!client->isMovableAcrossScreens());
+ QCOMPARE(client->pos(), QPoint(42, 42));
+
+ // User should not be able to move the client.
+ QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
+ QVERIFY(clientStartMoveResizedSpy.isValid());
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+ workspace()->slotWindowMove();
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QCOMPARE(clientStartMoveResizedSpy.count(), 0);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+
+ // The position should still be forced if we reopen the client.
+ delete shellSurface;
+ delete surface;
+ QVERIFY(Test::waitForWindowDestroyed(client));
+ std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo");
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+ QVERIFY(!client->isMovable());
+ QVERIFY(!client->isMovableAcrossScreens());
+ QCOMPARE(client->pos(), QPoint(42, 42));
+
+ // Destroy the client.
+ delete shellSurface;
+ delete surface;
+ QVERIFY(Test::waitForWindowDestroyed(client));
+}
+
+TEST_DATA(testPositionApplyNow)
+
+void TestShellClientRules::testPositionApplyNow()
+{
+ // Create the test client.
+ QFETCH(Test::ShellSurfaceType, type);
+ ShellClient *client;
+ Surface *surface;
+ QObject *shellSurface;
+ std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo");
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+
+ // The position of the client isn't set by any rule, thus the default placement
+ // policy will try to put the client in the top-left corner of the screen.
+ QVERIFY(client->isMovable());
+ QVERIFY(client->isMovableAcrossScreens());
+ QCOMPARE(client->pos(), QPoint(0, 0));
+
+ // 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("position", QPoint(42, 42));
+ group.writeEntry("positionrule", 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);
+
+ // The client should be moved to the position specified by the rule.
+ QSignalSpy geometryChangedSpy(client, &AbstractClient::geometryChanged);
+ QVERIFY(geometryChangedSpy.isValid());
+ workspace()->slotReconfigure();
+ QCOMPARE(geometryChangedSpy.count(), 1);
+ QCOMPARE(client->pos(), QPoint(42, 42));
+
+ // We still have to be able to move the client around.
+ QVERIFY(client->isMovable());
+ QVERIFY(client->isMovableAcrossScreens());
+ QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
+ QVERIFY(clientStartMoveResizedSpy.isValid());
+ QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized);
+ QVERIFY(clientStepUserMovedResizedSpy.isValid());
+ QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized);
+ QVERIFY(clientFinishUserMovedResizedSpy.isValid());
+
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+ workspace()->slotWindowMove();
+ QCOMPARE(workspace()->moveResizeClient(), client);
+ QCOMPARE(clientStartMoveResizedSpy.count(), 1);
+ QVERIFY(client->isMove());
+ QVERIFY(!client->isResize());
+
+ const QPoint cursorPos = KWin::Cursor::pos();
+ client->keyPressEvent(Qt::Key_Right);
+ client->updateMoveResize(KWin::Cursor::pos());
+ QCOMPARE(KWin::Cursor::pos(), cursorPos + QPoint(8, 0));
+ QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
+ QCOMPARE(client->pos(), QPoint(50, 42));
+
+ client->keyPressEvent(Qt::Key_Enter);
+ QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+ QCOMPARE(client->pos(), QPoint(50, 42));
+
+ // The rule should not be applied again.
+ client->evaluateWindowRules();
+ QCOMPARE(client->pos(), QPoint(50, 42));
+
+ // Destroy the client.
+ delete shellSurface;
+ delete surface;
+ QVERIFY(Test::waitForWindowDestroyed(client));
+}
+
+TEST_DATA(testPositionForceTemporarily)
+
+void TestShellClientRules::testPositionForceTemporarily()
+{
+ // 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("position", QPoint(42, 42));
+ group.writeEntry("positionrule", 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);
+ ShellClient *client;
+ Surface *surface;
+ XdgShellSurface *shellSurface;
+ std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo");
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+
+ // The client should be moved to the position specified by the rule.
+ QVERIFY(!client->isMovable());
+ QVERIFY(!client->isMovableAcrossScreens());
+ QCOMPARE(client->pos(), QPoint(42, 42));
+
+ // User should not be able to move the client.
+ QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
+ QVERIFY(clientStartMoveResizedSpy.isValid());
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+ workspace()->slotWindowMove();
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QCOMPARE(clientStartMoveResizedSpy.count(), 0);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+
+ // The rule should be discarded if we close the client.
+ delete shellSurface;
+ delete surface;
+ QVERIFY(Test::waitForWindowDestroyed(client));
+ std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo");
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+ QVERIFY(client->isMovable());
+ QVERIFY(client->isMovableAcrossScreens());
+ QCOMPARE(client->pos(), QPoint(0, 0));
+
+ // Destroy the client.
+ delete shellSurface;
+ delete surface;
+ QVERIFY(Test::waitForWindowDestroyed(client));
+}
+
+TEST_DATA(testSizeDontAffect)
+
+void TestShellClientRules::testSizeDontAffect()
+{
+ // 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("size", QSize(480, 640));
+ group.writeEntry("sizerule", 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);
+
+ // The window size shouldn't be enforced by the rule.
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 1);
+ QCOMPARE(configureRequestedSpy->last().first().toSize(), QSize(0, 0));
+
+ // 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->isResizable());
+ QCOMPARE(client->size(), QSize(100, 50));
+
+ // We should receive a configure event when the client becomes active.
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 2);
+
+ // Destroy the client.
+ shellSurface.reset();
+ surface.reset();
+ QVERIFY(Test::waitForWindowDestroyed(client));
+}
+
+TEST_DATA(testSizeApply)
+
+void TestShellClientRules::testSizeApply()
+{
+ // 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("size", QSize(480, 640));
+ group.writeEntry("sizerule", 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);
+
+ // The initial configure event should contain size hint set by the rule.
+ XdgShellSurface::States states;
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 1);
+ QCOMPARE(configureRequestedSpy->last().at(0).toSize(), QSize(480, 640));
+ states = configureRequestedSpy->last().at(1).value();
+ QVERIFY(!states.testFlag(XdgShellSurface::State::Activated));
+ QVERIFY(!states.testFlag(XdgShellSurface::State::Resizing));
+
+ // Map the client.
+ shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value());
+ ShellClient *client = Test::renderAndWaitForShown(surface.data(), QSize(480, 640), Qt::blue);
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+ QVERIFY(client->isResizable());
+ QCOMPARE(client->size(), QSize(480, 640));
+
+ // 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::Resizing));
+
+ // One still should be able to resize the client.
+ QSignalSpy geometryChangedSpy(client, &AbstractClient::geometryChanged);
+ QVERIFY(geometryChangedSpy.isValid());
+ QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
+ QVERIFY(clientStartMoveResizedSpy.isValid());
+ QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized);
+ QVERIFY(clientStepUserMovedResizedSpy.isValid());
+ QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized);
+ QVERIFY(clientFinishUserMovedResizedSpy.isValid());
+ QSignalSpy surfaceSizeChangedSpy(shellSurface.data(), &XdgShellSurface::sizeChanged);
+ QVERIFY(surfaceSizeChangedSpy.isValid());
+
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+ workspace()->slotWindowResize();
+ QCOMPARE(workspace()->moveResizeClient(), client);
+ QCOMPARE(clientStartMoveResizedSpy.count(), 1);
+ QVERIFY(!client->isMove());
+ QVERIFY(client->isResize());
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 3);
+ states = configureRequestedSpy->last().at(1).value();
+ QVERIFY(states.testFlag(XdgShellSurface::State::Activated));
+ QVERIFY(states.testFlag(XdgShellSurface::State::Resizing));
+ shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value());
+
+ const QPoint cursorPos = KWin::Cursor::pos();
+ client->keyPressEvent(Qt::Key_Right);
+ client->updateMoveResize(KWin::Cursor::pos());
+ QCOMPARE(KWin::Cursor::pos(), cursorPos + QPoint(8, 0));
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 4);
+ states = configureRequestedSpy->last().at(1).value();
+ QVERIFY(states.testFlag(XdgShellSurface::State::Activated));
+ QVERIFY(states.testFlag(XdgShellSurface::State::Resizing));
+ QCOMPARE(surfaceSizeChangedSpy.count(), 1);
+ QCOMPARE(surfaceSizeChangedSpy.last().first().toSize(), QSize(488, 640));
+ QCOMPARE(clientStepUserMovedResizedSpy.count(), 0);
+ shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value());
+ Test::render(surface.data(), QSize(488, 640), Qt::blue);
+ QVERIFY(geometryChangedSpy.wait());
+ QCOMPARE(client->size(), QSize(488, 640));
+ QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
+
+ client->keyPressEvent(Qt::Key_Enter);
+ QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+
+ QEXPECT_FAIL("", "Interactive resize is not spec-compliant", Continue);
+ QVERIFY(configureRequestedSpy->wait(10));
+ QEXPECT_FAIL("", "Interactive resize is not spec-compliant", Continue);
+ QCOMPARE(configureRequestedSpy->count(), 5);
+
+ // The rule should be applied again if the client appears after it's been closed.
+ 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().first().toSize(), QSize(480, 640));
+
+ shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value());
+ client = Test::renderAndWaitForShown(surface.data(), QSize(480, 640), Qt::blue);
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+ QVERIFY(client->isResizable());
+ QCOMPARE(client->size(), QSize(480, 640));
+
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 2);
+
+ // Destroy the client.
+ shellSurface.reset();
+ surface.reset();
+ QVERIFY(Test::waitForWindowDestroyed(client));
+}
+
+TEST_DATA(testSizeRemember)
+
+void TestShellClientRules::testSizeRemember()
+{
+ // 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("size", QSize(480, 640));
+ group.writeEntry("sizerule", 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);
+
+ // The initial configure event should contain size hint set by the rule.
+ XdgShellSurface::States states;
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 1);
+ QCOMPARE(configureRequestedSpy->last().first().toSize(), QSize(480, 640));
+ states = configureRequestedSpy->last().at(1).value();
+ QVERIFY(!states.testFlag(XdgShellSurface::State::Activated));
+ QVERIFY(!states.testFlag(XdgShellSurface::State::Resizing));
+
+ // Map the client.
+ shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value());
+ ShellClient *client = Test::renderAndWaitForShown(surface.data(), QSize(480, 640), Qt::blue);
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+ QVERIFY(client->isResizable());
+ QCOMPARE(client->size(), QSize(480, 640));
+
+ // 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::Resizing));
+
+ // One should still be able to resize the client.
+ QSignalSpy geometryChangedSpy(client, &AbstractClient::geometryChanged);
+ QVERIFY(geometryChangedSpy.isValid());
+ QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
+ QVERIFY(clientStartMoveResizedSpy.isValid());
+ QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized);
+ QVERIFY(clientStepUserMovedResizedSpy.isValid());
+ QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized);
+ QVERIFY(clientFinishUserMovedResizedSpy.isValid());
+ QSignalSpy surfaceSizeChangedSpy(shellSurface.data(), &XdgShellSurface::sizeChanged);
+ QVERIFY(surfaceSizeChangedSpy.isValid());
+
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+ workspace()->slotWindowResize();
+ QCOMPARE(workspace()->moveResizeClient(), client);
+ QCOMPARE(clientStartMoveResizedSpy.count(), 1);
+ QVERIFY(!client->isMove());
+ QVERIFY(client->isResize());
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 3);
+ states = configureRequestedSpy->last().at(1).value();
+ QVERIFY(states.testFlag(XdgShellSurface::State::Activated));
+ QVERIFY(states.testFlag(XdgShellSurface::State::Resizing));
+ shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value());
+
+ const QPoint cursorPos = KWin::Cursor::pos();
+ client->keyPressEvent(Qt::Key_Right);
+ client->updateMoveResize(KWin::Cursor::pos());
+ QCOMPARE(KWin::Cursor::pos(), cursorPos + QPoint(8, 0));
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 4);
+ states = configureRequestedSpy->last().at(1).value();
+ QVERIFY(states.testFlag(XdgShellSurface::State::Activated));
+ QVERIFY(states.testFlag(XdgShellSurface::State::Resizing));
+ QCOMPARE(surfaceSizeChangedSpy.count(), 1);
+ QCOMPARE(surfaceSizeChangedSpy.last().first().toSize(), QSize(488, 640));
+ QCOMPARE(clientStepUserMovedResizedSpy.count(), 0);
+ shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value());
+ Test::render(surface.data(), QSize(488, 640), Qt::blue);
+ QVERIFY(geometryChangedSpy.wait());
+ QCOMPARE(client->size(), QSize(488, 640));
+ QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
+
+ client->keyPressEvent(Qt::Key_Enter);
+ QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+
+ QEXPECT_FAIL("", "Interactive resize is not spec-compliant", Continue);
+ QVERIFY(configureRequestedSpy->wait(10));
+ QEXPECT_FAIL("", "Interactive resize is not spec-compliant", Continue);
+ QCOMPARE(configureRequestedSpy->count(), 5);
+
+ // If the client appears again, it should have the last known size.
+ 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().first().toSize(), QSize(488, 640));
+
+ shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value());
+ client = Test::renderAndWaitForShown(surface.data(), QSize(488, 640), Qt::blue);
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+ QVERIFY(client->isResizable());
+ QCOMPARE(client->size(), QSize(488, 640));
+
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 2);
+
+ // Destroy the client.
+ shellSurface.reset();
+ surface.reset();
+ QVERIFY(Test::waitForWindowDestroyed(client));
+}
+
+TEST_DATA(testSizeForce)
+
+void TestShellClientRules::testSizeForce()
+{
+ // 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("size", QSize(480, 640));
+ group.writeEntry("sizerule", 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);
+
+ // The initial configure event should contain size hint set by the rule.
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 1);
+ QCOMPARE(configureRequestedSpy->last().first().toSize(), QSize(480, 640));
+
+ // Map the client.
+ shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value());
+ ShellClient *client = Test::renderAndWaitForShown(surface.data(), QSize(480, 640), Qt::blue);
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+ QVERIFY(!client->isResizable());
+ QCOMPARE(client->size(), QSize(480, 640));
+
+ // We should receive a configure event when the client becomes active.
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 2);
+
+ // Any attempt to resize the client should not succeed.
+ QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
+ QVERIFY(clientStartMoveResizedSpy.isValid());
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+ workspace()->slotWindowResize();
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QCOMPARE(clientStartMoveResizedSpy.count(), 0);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+ QVERIFY(!configureRequestedSpy->wait(100));
+
+ // If the client appears again, the size 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().first().toSize(), QSize(480, 640));
+
+ shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value());
+ client = Test::renderAndWaitForShown(surface.data(), QSize(480, 640), Qt::blue);
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+ QVERIFY(!client->isResizable());
+ QCOMPARE(client->size(), QSize(480, 640));
+
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 2);
+
+ // Destroy the client.
+ shellSurface.reset();
+ surface.reset();
+ QVERIFY(Test::waitForWindowDestroyed(client));
+}
+
+TEST_DATA(testSizeApplyNow)
+
+void TestShellClientRules::testSizeApplyNow()
+{
+ // 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);
+
+ // The expected surface dimensions should be set by the rule.
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 1);
+ QCOMPARE(configureRequestedSpy->last().first().toSize(), QSize(0, 0));
+
+ // 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->isResizable());
+ QCOMPARE(client->size(), QSize(100, 50));
+
+ // We should receive a configure event when the client becomes active.
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 2);
+
+ // 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("size", QSize(480, 640));
+ group.writeEntry("sizerule", 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();
+
+ // The compositor should send a configure event with a new size.
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 3);
+ QCOMPARE(configureRequestedSpy->last().first().toSize(), QSize(480, 640));
+
+ // Draw the surface with the new size.
+ QSignalSpy geometryChangedSpy(client, &AbstractClient::geometryChanged);
+ QVERIFY(geometryChangedSpy.isValid());
+ shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value());
+ Test::render(surface.data(), QSize(480, 640), Qt::blue);
+ QVERIFY(geometryChangedSpy.wait());
+ QCOMPARE(client->size(), QSize(480, 640));
+ QVERIFY(!configureRequestedSpy->wait(100));
+
+ // The rule should not be applied again.
+ client->evaluateWindowRules();
+ QVERIFY(!configureRequestedSpy->wait(100));
+
+ // Destroy the client.
+ shellSurface.reset();
+ surface.reset();
+ QVERIFY(Test::waitForWindowDestroyed(client));
+}
+
+TEST_DATA(testSizeForceTemporarily)
+
+void TestShellClientRules::testSizeForceTemporarily()
+{
+ // 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("size", QSize(480, 640));
+ group.writeEntry("sizerule", 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);
+
+ // The initial configure event should contain size hint set by the rule.
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 1);
+ QCOMPARE(configureRequestedSpy->last().first().toSize(), QSize(480, 640));
+
+ // Map the client.
+ shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value());
+ ShellClient *client = Test::renderAndWaitForShown(surface.data(), QSize(480, 640), Qt::blue);
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+ QVERIFY(!client->isResizable());
+ QCOMPARE(client->size(), QSize(480, 640));
+
+ // We should receive a configure event when the client becomes active.
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 2);
+
+ // Any attempt to resize the client should not succeed.
+ QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
+ QVERIFY(clientStartMoveResizedSpy.isValid());
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+ workspace()->slotWindowResize();
+ QCOMPARE(workspace()->moveResizeClient(), nullptr);
+ QCOMPARE(clientStartMoveResizedSpy.count(), 0);
+ QVERIFY(!client->isMove());
+ QVERIFY(!client->isResize());
+ QVERIFY(!configureRequestedSpy->wait(100));
+
+ // The rule should be discarded when the client is closed.
+ 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().first().toSize(), QSize(0, 0));
+
+ shellSurface->ackConfigure(configureRequestedSpy->last().at(2).value());
+ client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue);
+ QVERIFY(client);
+ QVERIFY(client->isActive());
+ QVERIFY(client->isResizable());
+ QCOMPARE(client->size(), QSize(100, 50));
+
+ QVERIFY(configureRequestedSpy->wait());
+ QCOMPARE(configureRequestedSpy->count(), 2);
+
+ // 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 ca7e246437..3be1a7a14a 100644
--- a/shell_client.cpp
+++ b/shell_client.cpp
@@ -355,9 +355,17 @@ void ShellClient::finishInit() {
updateWindowMargins();
+ bool needsPlacement = !isInitialPositionSet();
+
if (supportsWindowRules()) {
setupWindowRules(false);
+ const QRect originalGeometry = QRect(pos(), sizeForClientSize(clientSize()));
+ const QRect ruledGeometry = rules()->checkGeometry(originalGeometry, true);
+ if (originalGeometry != ruledGeometry) {
+ setGeometry(ruledGeometry);
+ }
+
setDesktop(rules()->checkDesktop(desktop(), true));
setDesktopFileName(rules()->checkDesktopFile(desktopFileName(), true).toUtf8());
if (rules()->checkMinimize(isMinimized(), true)) {
@@ -371,12 +379,17 @@ void ShellClient::finishInit() {
setShortcut(rules()->checkShortcut(shortcut().toString(), true));
updateColorScheme();
+ // Don't place the client if its position is set by a rule.
+ if (rules()->checkPosition(invalidPoint, true) != invalidPoint) {
+ needsPlacement = false;
+ }
+
discardTemporaryRules();
RuleBook::self()->discardUsed(this, false); // Remove Apply Now rules.
updateWindowRules(Rules::All);
}
- if (!isInitialPositionSet()) {
+ if (needsPlacement) {
QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop());
placeIn(area);
}
@@ -624,10 +637,12 @@ void ShellClient::updateDecoration(bool check_workspace_pos, bool force)
void ShellClient::setGeometry(int x, int y, int w, int h, ForceGeometry_t force)
{
+ const QRect newGeometry = rules()->checkGeometry(QRect(x, y, w, h));
+
if (areGeometryUpdatesBlocked()) {
// when the GeometryUpdateBlocker exits the current geom is passed to setGeometry
// thus we need to set it here.
- geom = QRect(x, y, w, h);
+ geom = newGeometry;
if (pendingGeometryUpdate() == PendingGeometryForced)
{} // maximum, nothing needed
else if (force == ForceGeometrySet)
@@ -640,17 +655,17 @@ 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();
}
- const QSize requestedClientSize = QSize(w, h) - QSize(borderLeft() + borderRight(), borderTop() + borderBottom());
- const QSize requestedWindowGeometrySize = toWindowGeometry(QSize(w, h));
+ const QSize requestedClientSize = newGeometry.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom());
+ const QSize requestedWindowGeometrySize = toWindowGeometry(newGeometry.size());
if (requestedClientSize == m_clientSize && !isWaitingForMoveResizeSync() &&
(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));
+ doSetGeometry(newGeometry);
updateMaximizeMode(m_requestedMaximizeMode);
} else {
// size did change, Client needs to provide a new buffer
- requestGeometry(QRect(x, y, w, h));
+ requestGeometry(newGeometry);
}
}
@@ -662,7 +677,9 @@ void ShellClient::doSetGeometry(const QRect &rect)
if (!m_unmapped) {
addWorkspaceRepaint(visibleRect());
}
+
geom = rect;
+ updateWindowRules(Rules::Position | Rules::Size);
if (m_unmapped && m_geomMaximizeRestore.isEmpty() && !geom.isEmpty()) {
// use first valid geometry as restore geometry
@@ -757,6 +774,9 @@ bool ShellClient::isFullScreen() const
bool ShellClient::isMaximizable() const
{
+ if (!isResizable()) {
+ return false;
+ }
return true;
}
@@ -770,6 +790,9 @@ bool ShellClient::isMinimizable() const
bool ShellClient::isMovable() const
{
+ if (rules()->checkPosition(invalidPoint) != invalidPoint) {
+ return false;
+ }
if (m_plasmaShellSurface) {
return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal;
}
@@ -781,6 +804,9 @@ bool ShellClient::isMovable() const
bool ShellClient::isMovableAcrossScreens() const
{
+ if (rules()->checkPosition(invalidPoint) != invalidPoint) {
+ return false;
+ }
if (m_plasmaShellSurface) {
return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal;
}
@@ -792,6 +818,9 @@ bool ShellClient::isMovableAcrossScreens() const
bool ShellClient::isResizable() const
{
+ if (rules()->checkSize(QSize()).isValid()) {
+ return false;
+ }
if (m_plasmaShellSurface) {
return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal;
}
@@ -840,8 +869,6 @@ void ShellClient::changeMaximize(bool horizontal, bool vertical, bool adjust)
const MaximizeMode oldMode = m_requestedMaximizeMode;
const QRect oldGeometry = geometry();
- StackingUpdatesBlocker blocker(workspace());
- RequestGeometryBlocker geometryBlocker(this);
// 'adjust == true' means to update the size only, e.g. after changing workspace size
if (!adjust) {
if (vertical)
@@ -855,6 +882,9 @@ void ShellClient::changeMaximize(bool horizontal, bool vertical, bool adjust)
return;
}
+ StackingUpdatesBlocker blocker(workspace());
+ RequestGeometryBlocker geometryBlocker(this);
+
// call into decoration update borders
if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && m_requestedMaximizeMode == KWin::MaximizeFull)) {
changeMaximizeRecursion = true;
diff --git a/workspace.cpp b/workspace.cpp
index d39e3aeaba..4aecea969d 100644
--- a/workspace.cpp
+++ b/workspace.cpp
@@ -301,6 +301,9 @@ void Workspace::init()
if (c->isFullScreen()) {
placementDone = true;
}
+ if (c->rules()->checkPosition(invalidPoint, true) != invalidPoint) {
+ placementDone = true;
+ }
if (!placementDone) {
c->placeIn(area);
}