diff --git a/autotests/integration/shell_client_rules_test.cpp b/autotests/integration/shell_client_rules_test.cpp index d7130f29d..0e78ae2b0 100644 --- a/autotests/integration/shell_client_rules_test.cpp +++ b/autotests/integration/shell_client_rules_test.cpp @@ -1,460 +1,2938 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2017 Martin Flöser +Copyright (C) 2019 Vlad Zagorodniy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ + #include "kwin_wayland_test.h" #include "platform.h" #include "rules.h" #include "screens.h" #include "shell_client.h" #include "virtualdesktops.h" #include "wayland_server.h" #include "workspace.h" #include #include +#include + using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_shell_client_rules-0"); - class TestShellClientRules : public QObject { Q_OBJECT + private Q_SLOTS: void initTestCase(); void init(); void cleanup(); - void testApplyInitialDesktop_data(); - void testApplyInitialDesktop(); - void testApplyInitialMinimize_data(); - void testApplyInitialMinimize(); - void testApplyInitialSkipTaskbar_data(); - void testApplyInitialSkipTaskbar(); - void testApplyInitialSkipPager_data(); - void testApplyInitialSkipPager(); - void testApplyInitialSkipSwitcher_data(); - void testApplyInitialSkipSwitcher(); - void testApplyInitialKeepAbove_data(); - void testApplyInitialKeepAbove(); - void testApplyInitialKeepBelow_data(); - void testApplyInitialKeepBelow(); - void testApplyInitialShortcut_data(); - void testApplyInitialShortcut(); - void testApplyInitialDesktopfile_data(); - void testApplyInitialDesktopfile(); - void testOpacityActive_data(); - void testOpacityActive(); + void testDesktopDontAffect_data(); + void testDesktopDontAffect(); + void testDesktopApply_data(); + void testDesktopApply(); + void testDesktopRemember_data(); + void testDesktopRemember(); + void testDesktopForce_data(); + void testDesktopForce(); + void testDesktopApplyNow_data(); + void testDesktopApplyNow(); + void testDesktopForceTemporarily_data(); + void testDesktopForceTemporarily(); + + void testMinimizeDontAffect_data(); + void testMinimizeDontAffect(); + void testMinimizeApply_data(); + void testMinimizeApply(); + void testMinimizeRemember_data(); + void testMinimizeRemember(); + void testMinimizeForce_data(); + void testMinimizeForce(); + void testMinimizeApplyNow_data(); + void testMinimizeApplyNow(); + void testMinimizeForceTemporarily_data(); + void testMinimizeForceTemporarily(); + + void testSkipTaskbarDontAffect_data(); + void testSkipTaskbarDontAffect(); + void testSkipTaskbarApply_data(); + void testSkipTaskbarApply(); + void testSkipTaskbarRemember_data(); + void testSkipTaskbarRemember(); + void testSkipTaskbarForce_data(); + void testSkipTaskbarForce(); + void testSkipTaskbarApplyNow_data(); + void testSkipTaskbarApplyNow(); + void testSkipTaskbarForceTemporarily_data(); + void testSkipTaskbarForceTemporarily(); + + void testSkipPagerDontAffect_data(); + void testSkipPagerDontAffect(); + void testSkipPagerApply_data(); + void testSkipPagerApply(); + void testSkipPagerRemember_data(); + void testSkipPagerRemember(); + void testSkipPagerForce_data(); + void testSkipPagerForce(); + void testSkipPagerApplyNow_data(); + void testSkipPagerApplyNow(); + void testSkipPagerForceTemporarily_data(); + void testSkipPagerForceTemporarily(); + + void testSkipSwitcherDontAffect_data(); + void testSkipSwitcherDontAffect(); + void testSkipSwitcherApply_data(); + void testSkipSwitcherApply(); + void testSkipSwitcherRemember_data(); + void testSkipSwitcherRemember(); + void testSkipSwitcherForce_data(); + void testSkipSwitcherForce(); + void testSkipSwitcherApplyNow_data(); + void testSkipSwitcherApplyNow(); + void testSkipSwitcherForceTemporarily_data(); + void testSkipSwitcherForceTemporarily(); + + void testKeepAboveDontAffect_data(); + void testKeepAboveDontAffect(); + void testKeepAboveApply_data(); + void testKeepAboveApply(); + void testKeepAboveRemember_data(); + void testKeepAboveRemember(); + void testKeepAboveForce_data(); + void testKeepAboveForce(); + void testKeepAboveApplyNow_data(); + void testKeepAboveApplyNow(); + void testKeepAboveForceTemporarily_data(); + void testKeepAboveForceTemporarily(); + + void testKeepBelowDontAffect_data(); + void testKeepBelowDontAffect(); + void testKeepBelowApply_data(); + void testKeepBelowApply(); + void testKeepBelowRemember_data(); + void testKeepBelowRemember(); + void testKeepBelowForce_data(); + void testKeepBelowForce(); + void testKeepBelowApplyNow_data(); + void testKeepBelowApplyNow(); + void testKeepBelowForceTemporarily_data(); + void testKeepBelowForceTemporarily(); + + void testShortcutDontAffect_data(); + void testShortcutDontAffect(); + void testShortcutApply_data(); + void testShortcutApply(); + void testShortcutRemember_data(); + void testShortcutRemember(); + void testShortcutForce_data(); + void testShortcutForce(); + void testShortcutApplyNow_data(); + void testShortcutApplyNow(); + void testShortcutForceTemporarily_data(); + void testShortcutForceTemporarily(); + + void testDesktopFileDontAffect_data(); + void testDesktopFileDontAffect(); + void testDesktopFileApply_data(); + void testDesktopFileApply(); + void testDesktopFileRemember_data(); + void testDesktopFileRemember(); + void testDesktopFileForce_data(); + void testDesktopFileForce(); + void testDesktopFileApplyNow_data(); + void testDesktopFileApplyNow(); + void testDesktopFileForceTemporarily_data(); + void testDesktopFileForceTemporarily(); + + void testActiveOpacityDontAffect_data(); + void testActiveOpacityDontAffect(); + void testActiveOpacityForce_data(); + void testActiveOpacityForce(); + void testActiveOpacityForceTemporarily_data(); + void testActiveOpacityForceTemporarily(); + + void testInactiveOpacityDontAffect_data(); + void testInactiveOpacityDontAffect(); + void testInactiveOpacityForce_data(); + void testInactiveOpacityForce(); + void testInactiveOpacityForceTemporarily_data(); + void testInactiveOpacityForceTemporarily(); + void testMatchAfterNameChange(); }; void TestShellClientRules::initTestCase() { - qRegisterMetaType(); - qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); waylandServer()->initWorkspace(); } void TestShellClientRules::init() { VirtualDesktopManager::self()->setCurrent(VirtualDesktopManager::self()->desktops().first()); QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); screens()->setCurrent(0); } void TestShellClientRules::cleanup() { Test::destroyWaylandConnection(); + + // Unreference the previous config. + RuleBook::self()->setConfig({}); + workspace()->slotReconfigure(); + + // Restore virtual desktops to the initial state. + VirtualDesktopManager::self()->setCount(1); + QCOMPARE(VirtualDesktopManager::self()->count(), 1u); } -#define TEST_DATA( name ) \ +#define TEST_DATA(name) \ void TestShellClientRules::name##_data() \ { \ QTest::addColumn("type"); \ - QTest::addColumn("ruleNumber"); \ - QTest::newRow("wlShell|Force") << Test::ShellSurfaceType::WlShell << 2; \ - QTest::newRow("xdgShellV5|Force") << Test::ShellSurfaceType::XdgShellV5 << 2; \ - QTest::newRow("xdgShellV6|Force") << Test::ShellSurfaceType::XdgShellV6 << 2; \ - QTest::newRow("xdgWmBase|Force") << Test::ShellSurfaceType::XdgShellStable << 2; \ - QTest::newRow("wlShell|Apply") << Test::ShellSurfaceType::WlShell << 3; \ - QTest::newRow("xdgShellV5|Apply") << Test::ShellSurfaceType::XdgShellV5 << 3; \ - QTest::newRow("xdgShellV6|Apply") << Test::ShellSurfaceType::XdgShellV6 << 3; \ - QTest::newRow("xdgWmBase|Apply") << Test::ShellSurfaceType::XdgShellStable << 3; \ - QTest::newRow("wlShell|ApplyNow") << Test::ShellSurfaceType::WlShell << 5; \ - QTest::newRow("xdgShellV5|ApplyNow") << Test::ShellSurfaceType::XdgShellV5 << 5; \ - QTest::newRow("xdgShellV6|ApplyNow") << Test::ShellSurfaceType::XdgShellV6 << 5; \ - QTest::newRow("xdgWmBase|ApplyNow") << Test::ShellSurfaceType::XdgShellStable << 5; \ - QTest::newRow("wlShell|ForceTemporarily") << Test::ShellSurfaceType::WlShell << 6; \ - QTest::newRow("xdgShellV5|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV5 << 6; \ - QTest::newRow("xdgShellV6|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV6 << 6; \ - QTest::newRow("xdgWmBase|ForceTemporarily") << Test::ShellSurfaceType::XdgShellStable << 6; \ -} - -#define TEST_FORCE_DATA( name ) \ -void TestShellClientRules::name##_data() \ -{ \ - QTest::addColumn("type"); \ - QTest::addColumn("ruleNumber"); \ - QTest::newRow("wlShell|Force") << Test::ShellSurfaceType::WlShell << 2; \ - QTest::newRow("xdgShellV5|Force") << Test::ShellSurfaceType::XdgShellV5 << 2; \ - QTest::newRow("xdgShellV6|Force") << Test::ShellSurfaceType::XdgShellV6 << 2; \ - QTest::newRow("xdgWmBase|Force") << Test::ShellSurfaceType::XdgShellStable << 2; \ - QTest::newRow("wlShell|ForceTemporarily") << Test::ShellSurfaceType::WlShell << 6; \ - QTest::newRow("xdgShellV5|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV5 << 6; \ - QTest::newRow("xdgShellV6|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV6 << 6; \ - QTest::newRow("xdgWmBase|ForceTemporarily") << Test::ShellSurfaceType::XdgShellStable << 6; \ + QTest::newRow("XdgShellV5") << Test::ShellSurfaceType::XdgShellV5; \ + QTest::newRow("XdgShellV6") << Test::ShellSurfaceType::XdgShellV6; \ + QTest::newRow("XdgWmBase") << Test::ShellSurfaceType::XdgShellStable; \ } +std::tuple createWindow(Test::ShellSurfaceType type, const QByteArray &appId) +{ + // Create an xdg surface. + Surface *surface = Test::createSurface(); + XdgShellSurface *shellSurface = Test::createXdgShellSurface(type, surface, surface, Test::CreationSetup::CreateOnly); + + // Assign the desired app id. + shellSurface->setAppId(appId); + + // Wait for the initial configure event. + QSignalSpy configureRequestedSpy(shellSurface, &XdgShellSurface::configureRequested); + surface->commit(Surface::CommitFlag::None); + configureRequestedSpy.wait(); + + // Draw content of the surface. + shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); + ShellClient *client = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); + + return {client, surface, shellSurface}; +} -TEST_DATA(testApplyInitialDesktop) +TEST_DATA(testDesktopDontAffect) -void TestShellClientRules::testApplyInitialDesktop() +void TestShellClientRules::testDesktopDontAffect() { - // ensure we have two desktops and are on first desktop - VirtualDesktopManager::self()->setCount(2); - VirtualDesktopManager::self()->setCurrent(VirtualDesktopManager::self()->desktops().first()); + // 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("desktop", 2); + group.writeEntry("desktoprule", 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(); - // install the temporary rule - QFETCH(int, ruleNumber); - QString rule = QStringLiteral("desktop=2\ndesktoprule=%1").arg(ruleNumber); - QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); + // We need at least two virtual desktop for this test. + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(VirtualDesktopManager::self()->count(), 2u); + VirtualDesktopManager::self()->setCurrent(1); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); - QScopedPointer surface(Test::createSurface()); + // Create the test client. QFETCH(Test::ShellSurfaceType, type); - QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + ShellClient *client; + Surface *surface; + XdgShellSurface *shellSurface; + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + + // The client should appear on the current virtual desktop. + QCOMPARE(client->desktop(), 1); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} - auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); - QVERIFY(c); - QCOMPARE(c->desktop(), 2); - QCOMPARE(c->isMinimized(), false); - QCOMPARE(c->isActive(), true); - QCOMPARE(c->skipTaskbar(), false); - QCOMPARE(c->skipPager(), false); - QCOMPARE(c->skipSwitcher(), false); - QCOMPARE(c->keepAbove(), false); - QCOMPARE(c->keepBelow(), false); - QCOMPARE(c->shortcut(), QKeySequence()); +TEST_DATA(testDesktopApply) + +void TestShellClientRules::testDesktopApply() +{ + // 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("desktop", 2); + group.writeEntry("desktoprule", 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(); + + // We need at least two virtual desktop for this test. + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(VirtualDesktopManager::self()->count(), 2u); + VirtualDesktopManager::self()->setCurrent(1); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); + + // 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); + + // The client should appear on the second virtual desktop. + QCOMPARE(client->desktop(), 2); + QCOMPARE(VirtualDesktopManager::self()->current(), 2); + + // We still should be able to move the client between desktops. + workspace()->sendClientToDesktop(client, 1, true); + QCOMPARE(client->desktop(), 1); + QCOMPARE(VirtualDesktopManager::self()->current(), 2); + + // If we re-open the client, it should appear on the second virtual desktop again. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + VirtualDesktopManager::self()->setCurrent(1); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QCOMPARE(client->desktop(), 2); + QCOMPARE(VirtualDesktopManager::self()->current(), 2); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); } -TEST_DATA(testApplyInitialMinimize) +TEST_DATA(testDesktopRemember) -void TestShellClientRules::testApplyInitialMinimize() +void TestShellClientRules::testDesktopRemember() { - // install the temporary rule - QFETCH(int, ruleNumber); - QString rule = QStringLiteral("minimize=1\nminimizerule=%1").arg(ruleNumber); - QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); + // 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("desktop", 2); + group.writeEntry("desktoprule", 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(); - QScopedPointer surface(Test::createSurface()); + // We need at least two virtual desktop for this test. + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(VirtualDesktopManager::self()->count(), 2u); + VirtualDesktopManager::self()->setCurrent(1); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); + + // Create the test client. QFETCH(Test::ShellSurfaceType, type); - QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + ShellClient *client; + Surface *surface; + XdgShellSurface *shellSurface; + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QCOMPARE(client->desktop(), 2); + QCOMPARE(VirtualDesktopManager::self()->current(), 2); + + // Move the client to the first virtual desktop. + workspace()->sendClientToDesktop(client, 1, true); + QCOMPARE(client->desktop(), 1); + QCOMPARE(VirtualDesktopManager::self()->current(), 2); + + // If we create the client again, it should appear on the first virtual desktop. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QCOMPARE(client->desktop(), 1); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} - auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); - QVERIFY(c); - QCOMPARE(c->desktop(), 1); - QCOMPARE(c->isMinimized(), true); - QCOMPARE(c->isActive(), false); - c->setMinimized(false); - QCOMPARE(c->isMinimized(), false); - QCOMPARE(c->skipTaskbar(), false); - QCOMPARE(c->skipPager(), false); - QCOMPARE(c->skipSwitcher(), false); - QCOMPARE(c->keepAbove(), false); - QCOMPARE(c->keepBelow(), false); - QCOMPARE(c->shortcut(), QKeySequence()); +TEST_DATA(testDesktopForce) + +void TestShellClientRules::testDesktopForce() +{ + // 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("desktop", 2); + group.writeEntry("desktoprule", 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(); + + // We need at least two virtual desktop for this test. + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(VirtualDesktopManager::self()->count(), 2u); + VirtualDesktopManager::self()->setCurrent(1); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); + + // 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); + + // The client should appear on the second virtual desktop. + QCOMPARE(client->desktop(), 2); + QCOMPARE(VirtualDesktopManager::self()->current(), 2); + + // Any attempt to move the client to another virtual desktop should fail. + workspace()->sendClientToDesktop(client, 1, true); + QCOMPARE(client->desktop(), 2); + QCOMPARE(VirtualDesktopManager::self()->current(), 2); + + // If we re-open the client, it should appear on the second virtual desktop again. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + VirtualDesktopManager::self()->setCurrent(1); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QCOMPARE(client->desktop(), 2); + QCOMPARE(VirtualDesktopManager::self()->current(), 2); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); } -TEST_DATA(testApplyInitialSkipTaskbar) +TEST_DATA(testDesktopApplyNow) -void TestShellClientRules::testApplyInitialSkipTaskbar() +void TestShellClientRules::testDesktopApplyNow() { - // install the temporary rule - QFETCH(int, ruleNumber); - QString rule = QStringLiteral("skiptaskbar=true\nskiptaskbarrule=%1").arg(ruleNumber); - QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); + // We need at least two virtual desktop for this test. + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(VirtualDesktopManager::self()->count(), 2u); + VirtualDesktopManager::self()->setCurrent(1); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); - QScopedPointer surface(Test::createSurface()); + // Create the test client. QFETCH(Test::ShellSurfaceType, type); - QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + ShellClient *client; + Surface *surface; + XdgShellSurface *shellSurface; + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QCOMPARE(client->desktop(), 1); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); + + // 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("desktop", 2); + group.writeEntry("desktoprule", 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(); - auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); - QVERIFY(c); - QCOMPARE(c->desktop(), 1); - QCOMPARE(c->isMinimized(), false); - QCOMPARE(c->isActive(), true); - QCOMPARE(c->skipTaskbar(), true); - QCOMPARE(c->skipPager(), false); - QCOMPARE(c->skipSwitcher(), false); - QCOMPARE(c->keepAbove(), false); - QCOMPARE(c->keepBelow(), false); - QCOMPARE(c->shortcut(), QKeySequence()); + // The client should have been moved to the second virtual desktop. + QCOMPARE(client->desktop(), 2); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); + + // One should still be able to move the client between desktops. + workspace()->sendClientToDesktop(client, 1, true); + QCOMPARE(client->desktop(), 1); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); + + // The rule should not be applied again. + client->evaluateWindowRules(); + QCOMPARE(client->desktop(), 1); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); } -TEST_DATA(testApplyInitialSkipPager) +TEST_DATA(testDesktopForceTemporarily) -void TestShellClientRules::testApplyInitialSkipPager() +void TestShellClientRules::testDesktopForceTemporarily() { - // install the temporary rule - QFETCH(int, ruleNumber); - QString rule = QStringLiteral("skippager=true\nskippagerrule=%1").arg(ruleNumber); - QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); + // 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("desktop", 2); + group.writeEntry("desktoprule", 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(); - QScopedPointer surface(Test::createSurface()); + // We need at least two virtual desktop for this test. + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(VirtualDesktopManager::self()->count(), 2u); + VirtualDesktopManager::self()->setCurrent(1); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); + + // Create the test client. QFETCH(Test::ShellSurfaceType, type); - QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + ShellClient *client; + Surface *surface; + XdgShellSurface *shellSurface; + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + + // The client should appear on the second virtual desktop. + QCOMPARE(client->desktop(), 2); + QCOMPARE(VirtualDesktopManager::self()->current(), 2); + + // Any attempt to move the client to another virtual desktop should fail. + workspace()->sendClientToDesktop(client, 1, true); + QCOMPARE(client->desktop(), 2); + QCOMPARE(VirtualDesktopManager::self()->current(), 2); + + // The rule should be discarded when the client is withdrawn. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + VirtualDesktopManager::self()->setCurrent(1); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QCOMPARE(client->desktop(), 1); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); + + // One should be able to move the client between desktops. + workspace()->sendClientToDesktop(client, 2, true); + QCOMPARE(client->desktop(), 2); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); + workspace()->sendClientToDesktop(client, 1, true); + QCOMPARE(client->desktop(), 1); + QCOMPARE(VirtualDesktopManager::self()->current(), 1); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} - auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); - QVERIFY(c); - QCOMPARE(c->desktop(), 1); - QCOMPARE(c->isMinimized(), false); - QCOMPARE(c->isActive(), true); - QCOMPARE(c->skipTaskbar(), false); - QCOMPARE(c->skipPager(), true); - QCOMPARE(c->skipSwitcher(), false); - QCOMPARE(c->keepAbove(), false); - QCOMPARE(c->keepBelow(), false); - QCOMPARE(c->shortcut(), QKeySequence()); +TEST_DATA(testMinimizeDontAffect) + +void TestShellClientRules::testMinimizeDontAffect() +{ + // 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("minimize", true); + group.writeEntry("minimizerule", 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->isMinimizable()); + + // The client should not be minimized. + QVERIFY(!client->isMinimized()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); } -TEST_DATA(testApplyInitialSkipSwitcher) +TEST_DATA(testMinimizeApply) -void TestShellClientRules::testApplyInitialSkipSwitcher() +void TestShellClientRules::testMinimizeApply() { - // install the temporary rule - QFETCH(int, ruleNumber); - QString rule = QStringLiteral("skipswitcher=true\nskipswitcherrule=%1").arg(ruleNumber); - QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); + // 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("minimize", true); + group.writeEntry("minimizerule", 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(); - QScopedPointer surface(Test::createSurface()); + // Create the test client. QFETCH(Test::ShellSurfaceType, type); - QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + ShellClient *client; + Surface *surface; + XdgShellSurface *shellSurface; + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(client->isMinimizable()); + + // The client should be minimized. + QVERIFY(client->isMinimized()); + + // We should still be able to unminimize the client. + client->unminimize(); + QVERIFY(!client->isMinimized()); + + // If we re-open the client, it should be minimized back again. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(client->isMinimizable()); + QVERIFY(client->isMinimized()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} - auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); - QVERIFY(c); - QCOMPARE(c->desktop(), 1); - QCOMPARE(c->isMinimized(), false); - QCOMPARE(c->isActive(), true); - QCOMPARE(c->skipTaskbar(), false); - QCOMPARE(c->skipPager(), false); - QCOMPARE(c->skipSwitcher(), true); - QCOMPARE(c->keepAbove(), false); - QCOMPARE(c->keepBelow(), false); - QCOMPARE(c->shortcut(), QKeySequence()); +TEST_DATA(testMinimizeRemember) + +void TestShellClientRules::testMinimizeRemember() +{ + // 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("minimize", false); + group.writeEntry("minimizerule", 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->isMinimizable()); + QVERIFY(!client->isMinimized()); + + // Minimize the client. + client->minimize(); + QVERIFY(client->isMinimized()); + + // If we open the client again, it should be minimized. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(client->isMinimizable()); + QVERIFY(client->isMinimized()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); } -TEST_DATA(testApplyInitialKeepAbove) +TEST_DATA(testMinimizeForce) -void TestShellClientRules::testApplyInitialKeepAbove() +void TestShellClientRules::testMinimizeForce() { - // install the temporary rule - QFETCH(int, ruleNumber); - QString rule = QStringLiteral("above=true\naboverule=%1").arg(ruleNumber); - QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); + // 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("minimize", false); + group.writeEntry("minimizerule", 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(); - QScopedPointer surface(Test::createSurface()); + // Create the test client. QFETCH(Test::ShellSurfaceType, type); - QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + ShellClient *client; + Surface *surface; + XdgShellSurface *shellSurface; + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(!client->isMinimizable()); + QVERIFY(!client->isMinimized()); + + // Any attempt to minimize the client should fail. + client->minimize(); + QVERIFY(!client->isMinimized()); + + // If we re-open the client, the minimized state should still be forced. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(!client->isMinimizable()); + QVERIFY(!client->isMinimized()); + client->minimize(); + QVERIFY(!client->isMinimized()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} - auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); - QVERIFY(c); - QCOMPARE(c->desktop(), 1); - QCOMPARE(c->isMinimized(), false); - QCOMPARE(c->isActive(), true); - QCOMPARE(c->skipTaskbar(), false); - QCOMPARE(c->skipPager(), false); - QCOMPARE(c->skipSwitcher(), false); - QCOMPARE(c->keepAbove(), true); - QCOMPARE(c->keepBelow(), false); - QCOMPARE(c->shortcut(), QKeySequence()); +TEST_DATA(testMinimizeApplyNow) + +void TestShellClientRules::testMinimizeApplyNow() +{ + // 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->isMinimizable()); + QVERIFY(!client->isMinimized()); + + // 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("minimize", true); + group.writeEntry("minimizerule", 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 client should be minimized now. + QVERIFY(client->isMinimizable()); + QVERIFY(client->isMinimized()); + + // One is still able to unminimize the client. + client->unminimize(); + QVERIFY(!client->isMinimized()); + + // The rule should not be applied again. + client->evaluateWindowRules(); + QVERIFY(client->isMinimizable()); + QVERIFY(!client->isMinimized()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); } -TEST_DATA(testApplyInitialKeepBelow) +TEST_DATA(testMinimizeForceTemporarily) -void TestShellClientRules::testApplyInitialKeepBelow() +void TestShellClientRules::testMinimizeForceTemporarily() { - // install the temporary rule - QFETCH(int, ruleNumber); - QString rule = QStringLiteral("below=true\nbelowrule=%1").arg(ruleNumber); - QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); + // 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("minimize", false); + group.writeEntry("minimizerule", 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(); - QScopedPointer surface(Test::createSurface()); + // Create the test client. QFETCH(Test::ShellSurfaceType, type); - QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); - - auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); - QVERIFY(c); - QCOMPARE(c->desktop(), 1); - QCOMPARE(c->isMinimized(), false); - QCOMPARE(c->isActive(), true); - QCOMPARE(c->skipTaskbar(), false); - QCOMPARE(c->skipPager(), false); - QCOMPARE(c->skipSwitcher(), false); - QCOMPARE(c->keepAbove(), false); - QCOMPARE(c->keepBelow(), true); - QCOMPARE(c->shortcut(), QKeySequence()); + ShellClient *client; + Surface *surface; + XdgShellSurface *shellSurface; + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(!client->isMinimizable()); + QVERIFY(!client->isMinimized()); + + // Any attempt to minimize the client should fail until the client is closed. + client->minimize(); + QVERIFY(!client->isMinimized()); + + // The rule should be discarded when the client is closed. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(client->isMinimizable()); + QVERIFY(!client->isMinimized()); + client->minimize(); + QVERIFY(client->isMinimized()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); } -TEST_DATA(testApplyInitialShortcut) +TEST_DATA(testSkipTaskbarDontAffect) -void TestShellClientRules::testApplyInitialShortcut() +void TestShellClientRules::testSkipTaskbarDontAffect() { - // install the temporary rule - QFETCH(int, ruleNumber); - const QKeySequence sequence{Qt::ControlModifier + Qt::ShiftModifier + Qt::MetaModifier + Qt::AltModifier + Qt::Key_Space}; - QString rule = QStringLiteral("shortcut=%1\nshortcutrule=%2").arg(sequence.toString()).arg(ruleNumber); - QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); + // 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("skiptaskbar", true); + group.writeEntry("skiptaskbarrule", 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(); - QScopedPointer surface(Test::createSurface()); + // Create the test client. QFETCH(Test::ShellSurfaceType, type); - QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + ShellClient *client; + Surface *surface; + XdgShellSurface *shellSurface; + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + + // The client should not be affected by the rule. + QVERIFY(!client->skipTaskbar()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} - auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); - QVERIFY(c); - QCOMPARE(c->desktop(), 1); - QCOMPARE(c->isMinimized(), false); - QCOMPARE(c->isActive(), true); - QCOMPARE(c->skipTaskbar(), false); - QCOMPARE(c->skipPager(), false); - QCOMPARE(c->skipSwitcher(), false); - QCOMPARE(c->keepAbove(), false); - QCOMPARE(c->keepBelow(), false); - QCOMPARE(c->shortcut(), sequence); +TEST_DATA(testSkipTaskbarApply) + +void TestShellClientRules::testSkipTaskbarApply() +{ + // 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("skiptaskbar", true); + group.writeEntry("skiptaskbarrule", 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); + + // The client should not be included on a taskbar. + QVERIFY(client->skipTaskbar()); + + // Though one can change that. + client->setOriginalSkipTaskbar(false); + QVERIFY(!client->skipTaskbar()); + + // Reopen the client, the rule should be applied again. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(client->skipTaskbar()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); } -TEST_DATA(testApplyInitialDesktopfile) +TEST_DATA(testSkipTaskbarRemember) -void TestShellClientRules::testApplyInitialDesktopfile() +void TestShellClientRules::testSkipTaskbarRemember() { - // install the temporary rule - QFETCH(int, ruleNumber); - QString rule = QStringLiteral("desktopfile=org.kde.kwin\ndesktopfilerule=%1").arg(ruleNumber); - QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); + // 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("skiptaskbar", true); + group.writeEntry("skiptaskbarrule", 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(); - QScopedPointer surface(Test::createSurface()); + // Create the test client. QFETCH(Test::ShellSurfaceType, type); - QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + ShellClient *client; + Surface *surface; + XdgShellSurface *shellSurface; + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + + // The client should not be included on a taskbar. + QVERIFY(client->skipTaskbar()); + + // Change the skip-taskbar state. + client->setOriginalSkipTaskbar(false); + QVERIFY(!client->skipTaskbar()); + + // Reopen the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + + // The client should be included on a taskbar. + QVERIFY(!client->skipTaskbar()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} - auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); - QVERIFY(c); - QCOMPARE(c->desktop(), 1); - QCOMPARE(c->isMinimized(), false); - QCOMPARE(c->isActive(), true); - QCOMPARE(c->skipTaskbar(), false); - QCOMPARE(c->skipPager(), false); - QCOMPARE(c->skipSwitcher(), false); - QCOMPARE(c->keepAbove(), false); - QCOMPARE(c->keepBelow(), false); - QCOMPARE(c->shortcut(), QKeySequence()); - QCOMPARE(c->desktopFileName(), QByteArrayLiteral("org.kde.kwin")); +TEST_DATA(testSkipTaskbarForce) + +void TestShellClientRules::testSkipTaskbarForce() +{ + // 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("skiptaskbar", true); + group.writeEntry("skiptaskbarrule", 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); + + // The client should not be included on a taskbar. + QVERIFY(client->skipTaskbar()); + + // Any attempt to change the skip-taskbar state should not succeed. + client->setOriginalSkipTaskbar(false); + QVERIFY(client->skipTaskbar()); + + // Reopen the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + + // The skip-taskbar state should be still forced. + QVERIFY(client->skipTaskbar()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); } -TEST_FORCE_DATA(testOpacityActive) +TEST_DATA(testSkipTaskbarApplyNow) -void TestShellClientRules::testOpacityActive() +void TestShellClientRules::testSkipTaskbarApplyNow() { - KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + // 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->skipTaskbar()); + + // 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("skiptaskbar", true); + group.writeEntry("skiptaskbarrule", 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(); - auto group = config->group("1"); - group.writeEntry("opacityactive", 90); - group.writeEntry("opacityinactive", 80); - QFETCH(int, ruleNumber); - group.writeEntry("opacityactiverule", ruleNumber); - group.writeEntry("opacityinactiverule", ruleNumber); + // The client should not be on a taskbar now. + QVERIFY(client->skipTaskbar()); + + // Also, one change the skip-taskbar state. + client->setOriginalSkipTaskbar(false); + QVERIFY(!client->skipTaskbar()); + + // The rule should not be applied again. + client->evaluateWindowRules(); + QVERIFY(!client->skipTaskbar()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testSkipTaskbarForceTemporarily) + +void TestShellClientRules::testSkipTaskbarForceTemporarily() +{ + // 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("skiptaskbar", true); + group.writeEntry("skiptaskbarrule", 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); + + // The client should not be included on a taskbar. + QVERIFY(client->skipTaskbar()); + + // Any attempt to change the skip-taskbar state should not succeed. + client->setOriginalSkipTaskbar(false); + QVERIFY(client->skipTaskbar()); + + // The rule should be discarded when the client is closed. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(!client->skipTaskbar()); + + // The skip-taskbar state is no longer forced. + client->setOriginalSkipTaskbar(true); + QVERIFY(client->skipTaskbar()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testSkipPagerDontAffect) + +void TestShellClientRules::testSkipPagerDontAffect() +{ + // 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("skippager", true); + group.writeEntry("skippagerrule", 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(); - QScopedPointer surface(Test::createSurface()); + // Create the test client. QFETCH(Test::ShellSurfaceType, type); - QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + ShellClient *client; + Surface *surface; + XdgShellSurface *shellSurface; + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + + // The client should not be affected by the rule. + QVERIFY(!client->skipPager()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} - auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); - QVERIFY(c); - QVERIFY(c->isActive()); - QCOMPARE(c->opacity(), 0.9); +TEST_DATA(testSkipPagerApply) + +void TestShellClientRules::testSkipPagerApply() +{ + // 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("skippager", true); + group.writeEntry("skippagerrule", 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(); - // open a second window - QScopedPointer surface2(Test::createSurface()); - QScopedPointer shellSurface2(Test::createShellSurface(type, surface2.data())); + // 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); + + // The client should not be included on a pager. + QVERIFY(client->skipPager()); + + // Though one can change that. + client->setSkipPager(false); + QVERIFY(!client->skipPager()); + + // Reopen the client, the rule should be applied again. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(client->skipPager()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} - auto c2 = Test::renderAndWaitForShown(surface2.data(), QSize(100, 50), Qt::blue); - QVERIFY(c2); - QVERIFY(c2->isActive()); - QVERIFY(!c->isActive()); - QCOMPARE(c2->opacity(), 0.9); - QCOMPARE(c->opacity(), 0.8); +TEST_DATA(testSkipPagerRemember) - workspace()->activateClient(c); - QVERIFY(!c2->isActive()); - QVERIFY(c->isActive()); - QCOMPARE(c->opacity(), 0.9); - QCOMPARE(c2->opacity(), 0.8); +void TestShellClientRules::testSkipPagerRemember() +{ + // 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("skippager", true); + group.writeEntry("skippagerrule", 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); + + // The client should not be included on a pager. + QVERIFY(client->skipPager()); + + // Change the skip-pager state. + client->setSkipPager(false); + QVERIFY(!client->skipPager()); + + // Reopen the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + + // The client should be included on a pager. + QVERIFY(!client->skipPager()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testSkipPagerForce) + +void TestShellClientRules::testSkipPagerForce() +{ + // 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("skippager", true); + group.writeEntry("skippagerrule", 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); + + // The client should not be included on a pager. + QVERIFY(client->skipPager()); + + // Any attempt to change the skip-pager state should not succeed. + client->setSkipPager(false); + QVERIFY(client->skipPager()); + + // Reopen the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + + // The skip-pager state should be still forced. + QVERIFY(client->skipPager()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testSkipPagerApplyNow) + +void TestShellClientRules::testSkipPagerApplyNow() +{ + // 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->skipPager()); + + // 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("skippager", true); + group.writeEntry("skippagerrule", 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 client should not be on a pager now. + QVERIFY(client->skipPager()); + + // Also, one change the skip-pager state. + client->setSkipPager(false); + QVERIFY(!client->skipPager()); + + // The rule should not be applied again. + client->evaluateWindowRules(); + QVERIFY(!client->skipPager()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testSkipPagerForceTemporarily) + +void TestShellClientRules::testSkipPagerForceTemporarily() +{ + // 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("skippager", true); + group.writeEntry("skippagerrule", 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); + + // The client should not be included on a pager. + QVERIFY(client->skipPager()); + + // Any attempt to change the skip-pager state should not succeed. + client->setSkipPager(false); + QVERIFY(client->skipPager()); + + // The rule should be discarded when the client is closed. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(!client->skipPager()); + + // The skip-pager state is no longer forced. + client->setSkipPager(true); + QVERIFY(client->skipPager()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testSkipSwitcherDontAffect) + +void TestShellClientRules::testSkipSwitcherDontAffect() +{ + // 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("skipswitcher", true); + group.writeEntry("skipswitcherrule", 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); + + // The client should not be affected by the rule. + QVERIFY(!client->skipSwitcher()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testSkipSwitcherApply) + +void TestShellClientRules::testSkipSwitcherApply() +{ + // 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("skipswitcher", true); + group.writeEntry("skipswitcherrule", 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); + + // The client should be excluded from window switching effects. + QVERIFY(client->skipSwitcher()); + + // Though one can change that. + client->setSkipSwitcher(false); + QVERIFY(!client->skipSwitcher()); + + // Reopen the client, the rule should be applied again. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(client->skipSwitcher()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testSkipSwitcherRemember) + +void TestShellClientRules::testSkipSwitcherRemember() +{ + // 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("skipswitcher", true); + group.writeEntry("skipswitcherrule", 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); + + // The client should be excluded from window switching effects. + QVERIFY(client->skipSwitcher()); + + // Change the skip-switcher state. + client->setSkipSwitcher(false); + QVERIFY(!client->skipSwitcher()); + + // Reopen the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + + // The client should be included in window switching effects. + QVERIFY(!client->skipSwitcher()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testSkipSwitcherForce) + +void TestShellClientRules::testSkipSwitcherForce() +{ + // 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("skipswitcher", true); + group.writeEntry("skipswitcherrule", 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); + + // The client should be excluded from window switching effects. + QVERIFY(client->skipSwitcher()); + + // Any attempt to change the skip-switcher state should not succeed. + client->setSkipSwitcher(false); + QVERIFY(client->skipSwitcher()); + + // Reopen the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + + // The skip-switcher state should be still forced. + QVERIFY(client->skipSwitcher()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testSkipSwitcherApplyNow) + +void TestShellClientRules::testSkipSwitcherApplyNow() +{ + // 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->skipSwitcher()); + + // 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("skipswitcher", true); + group.writeEntry("skipswitcherrule", 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 client should be excluded from window switching effects now. + QVERIFY(client->skipSwitcher()); + + // Also, one change the skip-switcher state. + client->setSkipSwitcher(false); + QVERIFY(!client->skipSwitcher()); + + // The rule should not be applied again. + client->evaluateWindowRules(); + QVERIFY(!client->skipSwitcher()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testSkipSwitcherForceTemporarily) + +void TestShellClientRules::testSkipSwitcherForceTemporarily() +{ + // 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("skipswitcher", true); + group.writeEntry("skipswitcherrule", 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); + + // The client should be excluded from window switching effects. + QVERIFY(client->skipSwitcher()); + + // Any attempt to change the skip-switcher state should not succeed. + client->setSkipSwitcher(false); + QVERIFY(client->skipSwitcher()); + + // The rule should be discarded when the client is closed. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(!client->skipSwitcher()); + + // The skip-switcher state is no longer forced. + client->setSkipSwitcher(true); + QVERIFY(client->skipSwitcher()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testKeepAboveDontAffect) + +void TestShellClientRules::testKeepAboveDontAffect() +{ + // 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("above", true); + group.writeEntry("aboverule", 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); + + // The keep-above state of the client should not be affected by the rule. + QVERIFY(!client->keepAbove()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testKeepAboveApply) + +void TestShellClientRules::testKeepAboveApply() +{ + // 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("above", true); + group.writeEntry("aboverule", 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); + + // Initially, the client should be kept above. + QVERIFY(client->keepAbove()); + + // One should also be able to alter the keep-above state. + client->setKeepAbove(false); + QVERIFY(!client->keepAbove()); + + // If one re-opens the client, it should be kept above back again. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(client->keepAbove()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testKeepAboveRemember) + +void TestShellClientRules::testKeepAboveRemember() +{ + // 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("above", true); + group.writeEntry("aboverule", 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); + + // Initially, the client should be kept above. + QVERIFY(client->keepAbove()); + + // Unset the keep-above state. + client->setKeepAbove(false); + QVERIFY(!client->keepAbove()); + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + + // Re-open the client, it should not be kept above. + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(!client->keepAbove()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testKeepAboveForce) + +void TestShellClientRules::testKeepAboveForce() +{ + // 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("above", true); + group.writeEntry("aboverule", 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); + + // Initially, the client should be kept above. + QVERIFY(client->keepAbove()); + + // Any attemt to unset the keep-above should not succeed. + client->setKeepAbove(false); + QVERIFY(client->keepAbove()); + + // If we re-open the client, it should still be kept above. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(client->keepAbove()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testKeepAboveApplyNow) + +void TestShellClientRules::testKeepAboveApplyNow() +{ + // 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->keepAbove()); + + // 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("above", true); + group.writeEntry("aboverule", 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 client should now be kept above other clients. + QVERIFY(client->keepAbove()); + + // One is still able to change the keep-above state of the client. + client->setKeepAbove(false); + QVERIFY(!client->keepAbove()); + + // The rule should not be applied again. + client->evaluateWindowRules(); + QVERIFY(!client->keepAbove()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testKeepAboveForceTemporarily) + +void TestShellClientRules::testKeepAboveForceTemporarily() +{ + // 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("above", true); + group.writeEntry("aboverule", 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); + + // Initially, the client should be kept above. + QVERIFY(client->keepAbove()); + + // Any attempt to alter the keep-above state should not succeed. + client->setKeepAbove(false); + QVERIFY(client->keepAbove()); + + // The rule should be discarded when the client is closed. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(!client->keepAbove()); + + // The keep-above state is no longer forced. + client->setKeepAbove(true); + QVERIFY(client->keepAbove()); + client->setKeepAbove(false); + QVERIFY(!client->keepAbove()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testKeepBelowDontAffect) + +void TestShellClientRules::testKeepBelowDontAffect() +{ + // 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("below", true); + group.writeEntry("belowrule", 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); + + // The keep-below state of the client should not be affected by the rule. + QVERIFY(!client->keepBelow()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testKeepBelowApply) + +void TestShellClientRules::testKeepBelowApply() +{ + // 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("below", true); + group.writeEntry("belowrule", 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); + + // Initially, the client should be kept below. + QVERIFY(client->keepBelow()); + + // One should also be able to alter the keep-below state. + client->setKeepBelow(false); + QVERIFY(!client->keepBelow()); + + // If one re-opens the client, it should be kept above back again. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(client->keepBelow()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testKeepBelowRemember) + +void TestShellClientRules::testKeepBelowRemember() +{ + // 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("below", true); + group.writeEntry("belowrule", 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); + + // Initially, the client should be kept below. + QVERIFY(client->keepBelow()); + + // Unset the keep-below state. + client->setKeepBelow(false); + QVERIFY(!client->keepBelow()); + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + + // Re-open the client, it should not be kept below. + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(!client->keepBelow()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testKeepBelowForce) + +void TestShellClientRules::testKeepBelowForce() +{ + // 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("below", true); + group.writeEntry("belowrule", 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); + + // Initially, the client should be kept below. + QVERIFY(client->keepBelow()); + + // Any attemt to unset the keep-below should not succeed. + client->setKeepBelow(false); + QVERIFY(client->keepBelow()); + + // If we re-open the client, it should still be kept below. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(client->keepBelow()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testKeepBelowApplyNow) + +void TestShellClientRules::testKeepBelowApplyNow() +{ + // 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->keepBelow()); + + // 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("below", true); + group.writeEntry("belowrule", 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 client should now be kept below other clients. + QVERIFY(client->keepBelow()); + + // One is still able to change the keep-below state of the client. + client->setKeepBelow(false); + QVERIFY(!client->keepBelow()); + + // The rule should not be applied again. + client->evaluateWindowRules(); + QVERIFY(!client->keepBelow()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testKeepBelowForceTemporarily) + +void TestShellClientRules::testKeepBelowForceTemporarily() +{ + // 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("below", true); + group.writeEntry("belowrule", 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); + + // Initially, the client should be kept below. + QVERIFY(client->keepBelow()); + + // Any attempt to alter the keep-below state should not succeed. + client->setKeepBelow(false); + QVERIFY(client->keepBelow()); + + // The rule should be discarded when the client is closed. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(!client->keepBelow()); + + // The keep-below state is no longer forced. + client->setKeepBelow(true); + QVERIFY(client->keepBelow()); + client->setKeepBelow(false); + QVERIFY(!client->keepBelow()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testShortcutDontAffect) + +void TestShellClientRules::testShortcutDontAffect() +{ + // 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("shortcut", "Ctrl+Alt+1"); + group.writeEntry("shortcutrule", 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); + QCOMPARE(client->shortcut(), QKeySequence()); + client->minimize(); + QVERIFY(client->isMinimized()); + + // If we press the window shortcut, nothing should happen. + QSignalSpy clientUnminimizedSpy(client, &AbstractClient::clientUnminimized); + QVERIFY(clientUnminimizedSpy.isValid()); + quint32 timestamp = 1; + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_1, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_1, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(!clientUnminimizedSpy.wait(100)); + QVERIFY(client->isMinimized()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testShortcutApply) + +void TestShellClientRules::testShortcutApply() +{ + // 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("shortcut", "Ctrl+Alt+1"); + group.writeEntry("shortcutrule", 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); + + // If we press the window shortcut, the window should be brought back to user. + QSignalSpy clientUnminimizedSpy(client, &AbstractClient::clientUnminimized); + QVERIFY(clientUnminimizedSpy.isValid()); + quint32 timestamp = 1; + QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL + Qt::ALT + Qt::Key_1})); + client->minimize(); + QVERIFY(client->isMinimized()); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_1, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_1, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(clientUnminimizedSpy.wait()); + QVERIFY(!client->isMinimized()); + + // One can also change the shortcut. + client->setShortcut(QStringLiteral("Ctrl+Alt+2")); + QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL + Qt::ALT + Qt::Key_2})); + client->minimize(); + QVERIFY(client->isMinimized()); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_2, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_2, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(clientUnminimizedSpy.wait()); + QVERIFY(!client->isMinimized()); + + // The old shortcut should do nothing. + client->minimize(); + QVERIFY(client->isMinimized()); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_1, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_1, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(!clientUnminimizedSpy.wait(100)); + QVERIFY(client->isMinimized()); + + // Reopen the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + + // The window shortcut should be set back to Ctrl+Alt+1. + QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL + Qt::ALT + Qt::Key_1})); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testShortcutRemember) + +void TestShellClientRules::testShortcutRemember() +{ + QSKIP("KWin core doesn't try to save the last used window shortcut"); + + // 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("shortcut", "Ctrl+Alt+1"); + group.writeEntry("shortcutrule", 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); + + // If we press the window shortcut, the window should be brought back to user. + QSignalSpy clientUnminimizedSpy(client, &AbstractClient::clientUnminimized); + QVERIFY(clientUnminimizedSpy.isValid()); + quint32 timestamp = 1; + QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL + Qt::ALT + Qt::Key_1})); + client->minimize(); + QVERIFY(client->isMinimized()); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_1, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_1, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(clientUnminimizedSpy.wait()); + QVERIFY(!client->isMinimized()); + + // Change the window shortcut to Ctrl+Alt+2. + client->setShortcut(QStringLiteral("Ctrl+Alt+2")); + QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL + Qt::ALT + Qt::Key_2})); + client->minimize(); + QVERIFY(client->isMinimized()); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_2, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_2, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(clientUnminimizedSpy.wait()); + QVERIFY(!client->isMinimized()); + + // Reopen the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + + // The window shortcut should be set to the last known value. + QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL + Qt::ALT + Qt::Key_2})); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testShortcutForce) + +void TestShellClientRules::testShortcutForce() +{ + QSKIP("KWin core can't release forced window shortcuts"); + + // 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("shortcut", "Ctrl+Alt+1"); + group.writeEntry("shortcutrule", 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); + + // If we press the window shortcut, the window should be brought back to user. + QSignalSpy clientUnminimizedSpy(client, &AbstractClient::clientUnminimized); + QVERIFY(clientUnminimizedSpy.isValid()); + quint32 timestamp = 1; + QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL + Qt::ALT + Qt::Key_1})); + client->minimize(); + QVERIFY(client->isMinimized()); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_1, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_1, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(clientUnminimizedSpy.wait()); + QVERIFY(!client->isMinimized()); + + // Any attempt to change the window shortcut should not succeed. + client->setShortcut(QStringLiteral("Ctrl+Alt+2")); + QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL + Qt::ALT + Qt::Key_1})); + client->minimize(); + QVERIFY(client->isMinimized()); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_2, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_2, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(!clientUnminimizedSpy.wait(100)); + QVERIFY(client->isMinimized()); + + // Reopen the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + + // The window shortcut should still be forced. + QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL + Qt::ALT + Qt::Key_1})); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testShortcutApplyNow) + +void TestShellClientRules::testShortcutApplyNow() +{ + // 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->shortcut().isEmpty()); + + // 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("shortcut", "Ctrl+Alt+1"); + group.writeEntry("shortcutrule", 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 client should now have a window shortcut assigned. + QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL + Qt::ALT + Qt::Key_1})); + QSignalSpy clientUnminimizedSpy(client, &AbstractClient::clientUnminimized); + QVERIFY(clientUnminimizedSpy.isValid()); + quint32 timestamp = 1; + client->minimize(); + QVERIFY(client->isMinimized()); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_1, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_1, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(clientUnminimizedSpy.wait()); + QVERIFY(!client->isMinimized()); + + // Assign a different shortcut. + client->setShortcut(QStringLiteral("Ctrl+Alt+2")); + QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL + Qt::ALT + Qt::Key_2})); + client->minimize(); + QVERIFY(client->isMinimized()); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_2, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_2, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(clientUnminimizedSpy.wait()); + QVERIFY(!client->isMinimized()); + + // The rule should not be applied again. + client->evaluateWindowRules(); + QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL + Qt::ALT + Qt::Key_2})); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testShortcutForceTemporarily) + +void TestShellClientRules::testShortcutForceTemporarily() +{ + QSKIP("KWin core can't release forced window shortcuts"); + + // 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("shortcut", "Ctrl+Alt+1"); + group.writeEntry("shortcutrule", 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); + + // If we press the window shortcut, the window should be brought back to user. + QSignalSpy clientUnminimizedSpy(client, &AbstractClient::clientUnminimized); + QVERIFY(clientUnminimizedSpy.isValid()); + quint32 timestamp = 1; + QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL + Qt::ALT + Qt::Key_1})); + client->minimize(); + QVERIFY(client->isMinimized()); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_1, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_1, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(clientUnminimizedSpy.wait()); + QVERIFY(!client->isMinimized()); + + // Any attempt to change the window shortcut should not succeed. + client->setShortcut(QStringLiteral("Ctrl+Alt+2")); + QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL + Qt::ALT + Qt::Key_1})); + client->minimize(); + QVERIFY(client->isMinimized()); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_2, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_2, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(!clientUnminimizedSpy.wait(100)); + QVERIFY(client->isMinimized()); + + // The rule should be discarded when the client is closed. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(client->shortcut().isEmpty()); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testDesktopFileDontAffect) + +void TestShellClientRules::testDesktopFileDontAffect() +{ + // Currently, the desktop file name is derived from the app id. If the app id is + // changed, then the old rules will be lost. Either setDesktopFileName should + // be exposed or the desktop file name rule should be removed for wayland clients. + QSKIP("Needs changes in KWin core to pass"); +} + +TEST_DATA(testDesktopFileApply) + +void TestShellClientRules::testDesktopFileApply() +{ + // Currently, the desktop file name is derived from the app id. If the app id is + // changed, then the old rules will be lost. Either setDesktopFileName should + // be exposed or the desktop file name rule should be removed for wayland clients. + QSKIP("Needs changes in KWin core to pass"); +} + +TEST_DATA(testDesktopFileRemember) + +void TestShellClientRules::testDesktopFileRemember() +{ + // Currently, the desktop file name is derived from the app id. If the app id is + // changed, then the old rules will be lost. Either setDesktopFileName should + // be exposed or the desktop file name rule should be removed for wayland clients. + QSKIP("Needs changes in KWin core to pass"); +} + +TEST_DATA(testDesktopFileForce) + +void TestShellClientRules::testDesktopFileForce() +{ + // Currently, the desktop file name is derived from the app id. If the app id is + // changed, then the old rules will be lost. Either setDesktopFileName should + // be exposed or the desktop file name rule should be removed for wayland clients. + QSKIP("Needs changes in KWin core to pass"); +} + +TEST_DATA(testDesktopFileApplyNow) + +void TestShellClientRules::testDesktopFileApplyNow() +{ + // Currently, the desktop file name is derived from the app id. If the app id is + // changed, then the old rules will be lost. Either setDesktopFileName should + // be exposed or the desktop file name rule should be removed for wayland clients. + QSKIP("Needs changes in KWin core to pass"); +} + +TEST_DATA(testDesktopFileForceTemporarily) + +void TestShellClientRules::testDesktopFileForceTemporarily() +{ + // Currently, the desktop file name is derived from the app id. If the app id is + // changed, then the old rules will be lost. Either setDesktopFileName should + // be exposed or the desktop file name rule should be removed for wayland clients. + QSKIP("Needs changes in KWin core to pass"); +} + +TEST_DATA(testActiveOpacityDontAffect) + +void TestShellClientRules::testActiveOpacityDontAffect() +{ + // 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("opacityactive", 90); + group.writeEntry("opacityactiverule", 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 opacity should not be affected by the rule. + QCOMPARE(client->opacity(), 1.0); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testActiveOpacityForce) + +void TestShellClientRules::testActiveOpacityForce() +{ + // 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("opacityactive", 90); + group.writeEntry("opacityactiverule", 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()); + QCOMPARE(client->opacity(), 0.9); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testActiveOpacityForceTemporarily) + +void TestShellClientRules::testActiveOpacityForceTemporarily() +{ + // 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("opacityactive", 90); + group.writeEntry("opacityactiverule", 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()); + QCOMPARE(client->opacity(), 0.9); + + // The rule should be discarded when the client is closed. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(client->isActive()); + QCOMPARE(client->opacity(), 1.0); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testInactiveOpacityDontAffect) + +void TestShellClientRules::testInactiveOpacityDontAffect() +{ + // 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("opacityinactive", 80); + group.writeEntry("opacityinactiverule", 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()); + + // Make the client inactive. + workspace()->setActiveClient(nullptr); + QVERIFY(!client->isActive()); + + // The opacity of the client should not be affected by the rule. + QCOMPARE(client->opacity(), 1.0); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testInactiveOpacityForce) + +void TestShellClientRules::testInactiveOpacityForce() +{ + // 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("opacityinactive", 80); + group.writeEntry("opacityinactiverule", 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()); + QCOMPARE(client->opacity(), 1.0); + + // Make the client inactive. + workspace()->setActiveClient(nullptr); + QVERIFY(!client->isActive()); + + // The opacity should be forced by the rule. + QCOMPARE(client->opacity(), 0.8); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); +} + +TEST_DATA(testInactiveOpacityForceTemporarily) + +void TestShellClientRules::testInactiveOpacityForceTemporarily() +{ + // 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("opacityinactive", 80); + group.writeEntry("opacityinactiverule", 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()); + QCOMPARE(client->opacity(), 1.0); + + // Make the client inactive. + workspace()->setActiveClient(nullptr); + QVERIFY(!client->isActive()); + + // The opacity should be forced by the rule. + QCOMPARE(client->opacity(), 0.8); + + // The rule should be discarded when the client is closed. + delete shellSurface; + delete surface; + std::tie(client, surface, shellSurface) = createWindow(type, "org.kde.foo"); + QVERIFY(client); + QVERIFY(client->isActive()); + QCOMPARE(client->opacity(), 1.0); + workspace()->setActiveClient(nullptr); + QVERIFY(!client->isActive()); + QCOMPARE(client->opacity(), 1.0); + + // Destroy the client. + delete shellSurface; + delete surface; + QVERIFY(Test::waitForWindowDestroyed(client)); } void TestShellClientRules::testMatchAfterNameChange() { KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); config->group("General").writeEntry("count", 1); - auto group = config->group("1"); + KConfigGroup group = config->group("1"); group.writeEntry("above", true); - group.writeEntry("aboverule", 2); + group.writeEntry("aboverule", int(Rules::Force)); group.writeEntry("wmclass", "org.kde.foo"); group.writeEntry("wmclasscomplete", false); - group.writeEntry("wmclassmatch", 1); + group.writeEntry("wmclassmatch", int(Rules::ExactMatch)); group.sync(); RuleBook::self()->setConfig(config); workspace()->slotReconfigure(); QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createXdgShellV6Surface(surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(c->keepAbove(), false); QSignalSpy desktopFileNameSpy(c, &AbstractClient::desktopFileNameChanged); QVERIFY(desktopFileNameSpy.isValid()); shellSurface->setAppId(QByteArrayLiteral("org.kde.foo")); QVERIFY(desktopFileNameSpy.wait()); QCOMPARE(c->keepAbove(), true); } WAYLANDTEST_MAIN(TestShellClientRules) #include "shell_client_rules_test.moc" diff --git a/rules.h b/rules.h index 4c3f0edf1..0496a4acd 100644 --- a/rules.h +++ b/rules.h @@ -1,394 +1,394 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2004 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_RULES_H #define KWIN_RULES_H #include #include #include #include #include "placement.h" #include "options.h" #include "utils.h" class QDebug; class KConfig; class KXMessages; namespace KWin { class AbstractClient; class Client; class Rules; #ifndef KCMRULES // only for kwin core class WindowRules { public: explicit WindowRules(const QVector< Rules* >& rules); WindowRules(); void update(AbstractClient*, int selection); void discardTemporary(); bool contains(const Rules* rule) const; void remove(Rules* rule); Placement::Policy checkPlacement(Placement::Policy placement) const; QRect checkGeometry(QRect rect, bool init = false) const; // use 'invalidPoint' with checkPosition, unlike QSize() and QRect(), QPoint() is a valid point QPoint checkPosition(QPoint pos, bool init = false) const; QSize checkSize(QSize s, bool init = false) const; QSize checkMinSize(QSize s) const; QSize checkMaxSize(QSize s) const; int checkOpacityActive(int s) const; int checkOpacityInactive(int s) const; bool checkIgnoreGeometry(bool ignore, bool init = false) const; int checkDesktop(int desktop, bool init = false) const; int checkScreen(int screen, bool init = false) const; QString checkActivity(QString activity, bool init = false) const; NET::WindowType checkType(NET::WindowType type) const; MaximizeMode checkMaximize(MaximizeMode mode, bool init = false) const; bool checkMinimize(bool minimized, bool init = false) const; ShadeMode checkShade(ShadeMode shade, bool init = false) const; bool checkSkipTaskbar(bool skip, bool init = false) const; bool checkSkipPager(bool skip, bool init = false) const; bool checkSkipSwitcher(bool skip, bool init = false) const; bool checkKeepAbove(bool above, bool init = false) const; bool checkKeepBelow(bool below, bool init = false) const; bool checkFullScreen(bool fs, bool init = false) const; bool checkNoBorder(bool noborder, bool init = false) const; QString checkDecoColor(QString schemeFile) const; bool checkBlockCompositing(bool block) const; int checkFSP(int fsp) const; int checkFPP(int fpp) const; bool checkAcceptFocus(bool focus) const; bool checkCloseable(bool closeable) const; bool checkAutogrouping(bool autogroup) const; bool checkAutogroupInForeground(bool fg) const; QString checkAutogroupById(QString id) const; bool checkStrictGeometry(bool strict) const; QString checkShortcut(QString s, bool init = false) const; bool checkDisableGlobalShortcuts(bool disable) const; QString checkDesktopFile(QString desktopFile, bool init = false) const; private: MaximizeMode checkMaximizeVert(MaximizeMode mode, bool init) const; MaximizeMode checkMaximizeHoriz(MaximizeMode mode, bool init) const; QVector< Rules* > rules; }; #endif class Rules { public: Rules(); explicit Rules(const KConfigGroup&); Rules(const QString&, bool temporary); enum Type { Position = 1<<0, Size = 1<<1, Desktop = 1<<2, MaximizeVert = 1<<3, MaximizeHoriz = 1<<4, Minimize = 1<<5, Shade = 1<<6, SkipTaskbar = 1<<7, SkipPager = 1<<8, SkipSwitcher = 1<<9, Above = 1<<10, Below = 1<<11, Fullscreen = 1<<12, NoBorder = 1<<13, OpacityActive = 1<<14, OpacityInactive = 1<<15, Activity = 1<<16, Screen = 1<<17, DesktopFile = 1 << 18, All = 0xffffffff }; Q_DECLARE_FLAGS(Types, Type) + // All these values are saved to the cfg file, and are also used in kstart! + enum { + Unused = 0, + DontAffect, // use the default value + Force, // force the given value + Apply, // apply only after initial mapping + Remember, // like apply, and remember the value when the window is withdrawn + ApplyNow, // apply immediatelly, then forget the setting + ForceTemporarily // apply and force until the window is withdrawn + }; + enum StringMatch { + FirstStringMatch, + UnimportantMatch = FirstStringMatch, + ExactMatch, + SubstringMatch, + RegExpMatch, + LastStringMatch = RegExpMatch + }; void write(KConfigGroup&) const; bool isEmpty() const; #ifndef KCMRULES bool discardUsed(bool withdrawn); bool match(const AbstractClient* c) const; bool update(AbstractClient*, int selection); bool isTemporary() const; bool discardTemporary(bool force); // removes if temporary and forced or too old bool applyPlacement(Placement::Policy& placement) const; bool applyGeometry(QRect& rect, bool init) const; // use 'invalidPoint' with applyPosition, unlike QSize() and QRect(), QPoint() is a valid point bool applyPosition(QPoint& pos, bool init) const; bool applySize(QSize& s, bool init) const; bool applyMinSize(QSize& s) const; bool applyMaxSize(QSize& s) const; bool applyOpacityActive(int& s) const; bool applyOpacityInactive(int& s) const; bool applyIgnoreGeometry(bool& ignore, bool init) const; bool applyDesktop(int& desktop, bool init) const; bool applyScreen(int& desktop, bool init) const; bool applyActivity(QString& activity, bool init) const; bool applyType(NET::WindowType& type) const; bool applyMaximizeVert(MaximizeMode& mode, bool init) const; bool applyMaximizeHoriz(MaximizeMode& mode, bool init) const; bool applyMinimize(bool& minimized, bool init) const; bool applyShade(ShadeMode& shade, bool init) const; bool applySkipTaskbar(bool& skip, bool init) const; bool applySkipPager(bool& skip, bool init) const; bool applySkipSwitcher(bool& skip, bool init) const; bool applyKeepAbove(bool& above, bool init) const; bool applyKeepBelow(bool& below, bool init) const; bool applyFullScreen(bool& fs, bool init) const; bool applyNoBorder(bool& noborder, bool init) const; bool applyDecoColor(QString &schemeFile) const; bool applyBlockCompositing(bool& block) const; bool applyFSP(int& fsp) const; bool applyFPP(int& fpp) const; bool applyAcceptFocus(bool& focus) const; bool applyCloseable(bool& closeable) const; bool applyAutogrouping(bool& autogroup) const; bool applyAutogroupInForeground(bool& fg) const; bool applyAutogroupById(QString& id) const; bool applyStrictGeometry(bool& strict) const; bool applyShortcut(QString& shortcut, bool init) const; bool applyDisableGlobalShortcuts(bool& disable) const; bool applyDesktopFile(QString &desktopFile, bool init) const; private: #endif bool matchType(NET::WindowType match_type) const; bool matchWMClass(const QByteArray& match_class, const QByteArray& match_name) const; bool matchRole(const QByteArray& match_role) const; bool matchTitle(const QString& match_title) const; bool matchClientMachine(const QByteArray& match_machine, bool local) const; - // All these values are saved to the cfg file, and are also used in kstart! - enum { - Unused = 0, - DontAffect, // use the default value - Force, // force the given value - Apply, // apply only after initial mapping - Remember, // like apply, and remember the value when the window is withdrawn - ApplyNow, // apply immediatelly, then forget the setting - ForceTemporarily // apply and force until the window is withdrawn - }; enum SetRule { UnusedSetRule = Unused, SetRuleDummy = 256 // so that it's at least short int }; enum ForceRule { UnusedForceRule = Unused, ForceRuleDummy = 256 // so that it's at least short int }; - enum StringMatch { - FirstStringMatch, - UnimportantMatch = FirstStringMatch, - ExactMatch, - SubstringMatch, - RegExpMatch, - LastStringMatch = RegExpMatch - }; void readFromCfg(const KConfigGroup& cfg); static SetRule readSetRule(const KConfigGroup&, const QString& key); static ForceRule readForceRule(const KConfigGroup&, const QString& key); static NET::WindowType readType(const KConfigGroup&, const QString& key); static QString readDecoColor(const KConfigGroup &cfg); #ifndef KCMRULES static bool checkSetRule(SetRule rule, bool init); static bool checkForceRule(ForceRule rule); static bool checkSetStop(SetRule rule); static bool checkForceStop(ForceRule rule); #endif int temporary_state; // e.g. for kstart QString description; QByteArray wmclass; StringMatch wmclassmatch; bool wmclasscomplete; QByteArray windowrole; StringMatch windowrolematch; QString title; StringMatch titlematch; QByteArray clientmachine; StringMatch clientmachinematch; NET::WindowTypes types; // types for matching Placement::Policy placement; ForceRule placementrule; QPoint position; SetRule positionrule; QSize size; SetRule sizerule; QSize minsize; ForceRule minsizerule; QSize maxsize; ForceRule maxsizerule; int opacityactive; ForceRule opacityactiverule; int opacityinactive; ForceRule opacityinactiverule; bool ignoregeometry; SetRule ignoregeometryrule; int desktop; SetRule desktoprule; int screen; SetRule screenrule; QString activity; SetRule activityrule; NET::WindowType type; // type for setting ForceRule typerule; bool maximizevert; SetRule maximizevertrule; bool maximizehoriz; SetRule maximizehorizrule; bool minimize; SetRule minimizerule; bool shade; SetRule shaderule; bool skiptaskbar; SetRule skiptaskbarrule; bool skippager; SetRule skippagerrule; bool skipswitcher; SetRule skipswitcherrule; bool above; SetRule aboverule; bool below; SetRule belowrule; bool fullscreen; SetRule fullscreenrule; bool noborder; SetRule noborderrule; QString decocolor; ForceRule decocolorrule; bool blockcompositing; ForceRule blockcompositingrule; int fsplevel; int fpplevel; ForceRule fsplevelrule; ForceRule fpplevelrule; bool acceptfocus; ForceRule acceptfocusrule; bool closeable; ForceRule closeablerule; bool autogroup; ForceRule autogrouprule; bool autogroupfg; ForceRule autogroupfgrule; QString autogroupid; ForceRule autogroupidrule; bool strictgeometry; ForceRule strictgeometryrule; QString shortcut; SetRule shortcutrule; bool disableglobalshortcuts; ForceRule disableglobalshortcutsrule; QString desktopfile; SetRule desktopfilerule; friend QDebug& operator<<(QDebug& stream, const Rules*); }; #ifndef KCMRULES class KWIN_EXPORT RuleBook : public QObject { Q_OBJECT public: virtual ~RuleBook(); WindowRules find(const AbstractClient*, bool); void discardUsed(AbstractClient* c, bool withdraw); void setUpdatesDisabled(bool disable); bool areUpdatesDisabled() const; void load(); void edit(AbstractClient* c, bool whole_app); void requestDiskStorage(); void setConfig(const KSharedConfig::Ptr &config) { m_config = config; } private Q_SLOTS: void temporaryRulesMessage(const QString&); void cleanupTemporaryRules(); void save(); private: void deleteAll(); void initWithX11(); QTimer *m_updateTimer; bool m_updatesDisabled; QList m_rules; QScopedPointer m_temporaryRulesMessages; KSharedConfig::Ptr m_config; KWIN_SINGLETON(RuleBook) }; inline bool RuleBook::areUpdatesDisabled() const { return m_updatesDisabled; } inline bool Rules::checkSetRule(SetRule rule, bool init) { if (rule > (SetRule)DontAffect) { // Unused or DontAffect if (rule == (SetRule)Force || rule == (SetRule) ApplyNow || rule == (SetRule) ForceTemporarily || init) return true; } return false; } inline bool Rules::checkForceRule(ForceRule rule) { return rule == (ForceRule)Force || rule == (ForceRule) ForceTemporarily; } inline bool Rules::checkSetStop(SetRule rule) { return rule != UnusedSetRule; } inline bool Rules::checkForceStop(ForceRule rule) { return rule != UnusedForceRule; } inline WindowRules::WindowRules(const QVector< Rules* >& r) : rules(r) { } inline WindowRules::WindowRules() { } inline bool WindowRules::contains(const Rules* rule) const { return rules.contains(const_cast(rule)); } inline void WindowRules::remove(Rules* rule) { rules.removeOne(rule); } #endif QDebug& operator<<(QDebug& stream, const Rules*); } // namespace Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::Rules::Types) #endif