diff --git a/autotests/integration/shell_client_rules_test.cpp b/autotests/integration/shell_client_rules_test.cpp index 89659d611..73d23a485 100644 --- a/autotests/integration/shell_client_rules_test.cpp +++ b/autotests/integration/shell_client_rules_test.cpp @@ -1,329 +1,391 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2017 Martin Flöser 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 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 testOpacityActive_data(); + void testOpacityActive(); }; void TestShellClientRules::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", 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(); } #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("wlShell|Apply") << Test::ShellSurfaceType::WlShell << 3; \ QTest::newRow("xdgShellV5|Apply") << Test::ShellSurfaceType::XdgShellV5 << 3; \ QTest::newRow("xdgShellV6|Apply") << Test::ShellSurfaceType::XdgShellV6 << 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("wlShell|ForceTemporarily") << Test::ShellSurfaceType::WlShell << 6; \ QTest::newRow("xdgShellV5|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV5 << 6; \ QTest::newRow("xdgShellV6|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV6 << 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("wlShell|ForceTemporarily") << Test::ShellSurfaceType::WlShell << 6; \ + QTest::newRow("xdgShellV5|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV5 << 6; \ + QTest::newRow("xdgShellV6|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV6 << 6; \ +} + + TEST_DATA(testApplyInitialDesktop) void TestShellClientRules::testApplyInitialDesktop() { // ensure we have two desktops and are on first desktop VirtualDesktopManager::self()->setCount(2); VirtualDesktopManager::self()->setCurrent(VirtualDesktopManager::self()->desktops().first()); // 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)); QScopedPointer surface(Test::createSurface()); 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(), 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(testApplyInitialMinimize) void TestShellClientRules::testApplyInitialMinimize() { // 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)); QScopedPointer surface(Test::createSurface()); 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(), 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(testApplyInitialSkipTaskbar) void TestShellClientRules::testApplyInitialSkipTaskbar() { // 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)); QScopedPointer surface(Test::createSurface()); 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(), true); QCOMPARE(c->skipPager(), false); QCOMPARE(c->skipSwitcher(), false); QCOMPARE(c->keepAbove(), false); QCOMPARE(c->keepBelow(), false); QCOMPARE(c->shortcut(), QKeySequence()); } TEST_DATA(testApplyInitialSkipPager) void TestShellClientRules::testApplyInitialSkipPager() { // 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)); QScopedPointer surface(Test::createSurface()); 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(), true); QCOMPARE(c->skipSwitcher(), false); QCOMPARE(c->keepAbove(), false); QCOMPARE(c->keepBelow(), false); QCOMPARE(c->shortcut(), QKeySequence()); } TEST_DATA(testApplyInitialSkipSwitcher) void TestShellClientRules::testApplyInitialSkipSwitcher() { // 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)); QScopedPointer surface(Test::createSurface()); 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(), true); QCOMPARE(c->keepAbove(), false); QCOMPARE(c->keepBelow(), false); QCOMPARE(c->shortcut(), QKeySequence()); } TEST_DATA(testApplyInitialKeepAbove) void TestShellClientRules::testApplyInitialKeepAbove() { // 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)); QScopedPointer surface(Test::createSurface()); 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(), true); QCOMPARE(c->keepBelow(), false); QCOMPARE(c->shortcut(), QKeySequence()); } TEST_DATA(testApplyInitialKeepBelow) void TestShellClientRules::testApplyInitialKeepBelow() { // 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)); QScopedPointer surface(Test::createSurface()); 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()); } TEST_DATA(testApplyInitialShortcut) void TestShellClientRules::testApplyInitialShortcut() { // 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)); QScopedPointer surface(Test::createSurface()); 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(), false); QCOMPARE(c->shortcut(), sequence); } +TEST_FORCE_DATA(testOpacityActive) + +void TestShellClientRules::testOpacityActive() +{ + KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + config->group("General").writeEntry("count", 1); + + auto group = config->group("1"); + group.writeEntry("opacityactive", 90); + group.writeEntry("opacityinactive", 80); + QFETCH(int, ruleNumber); + group.writeEntry("opacityactiverule", ruleNumber); + group.writeEntry("opacityinactiverule", ruleNumber); + group.sync(); + + RuleBook::self()->setConfig(config); + workspace()->slotReconfigure(); + + QScopedPointer surface(Test::createSurface()); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + + auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(c); + QVERIFY(c->isActive()); + QCOMPARE(c->opacity(), 0.9); + + // open a second window + QScopedPointer surface2(Test::createSurface()); + QScopedPointer shellSurface2(Test::createShellSurface(type, surface2.data())); + + 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); + + workspace()->activateClient(c); + QVERIFY(!c2->isActive()); + QVERIFY(c->isActive()); + QCOMPARE(c->opacity(), 0.9); + QCOMPARE(c2->opacity(), 0.8); +} + WAYLANDTEST_MAIN(TestShellClientRules) #include "shell_client_rules_test.moc" diff --git a/rules.cpp b/rules.cpp index 031dde354..8804dc57c 100644 --- a/rules.cpp +++ b/rules.cpp @@ -1,1150 +1,1152 @@ /******************************************************************** 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 . *********************************************************************/ #include "rules.h" #include #include #include #include #include #include #include #include #ifndef KCMRULES #include "client.h" #include "client_machine.h" #include "screens.h" #include "workspace.h" #endif namespace KWin { Rules::Rules() : temporary_state(0) , wmclassmatch(UnimportantMatch) , wmclasscomplete(UnimportantMatch) , windowrolematch(UnimportantMatch) , titlematch(UnimportantMatch) , clientmachinematch(UnimportantMatch) , types(NET::AllTypesMask) , placementrule(UnusedForceRule) , positionrule(UnusedSetRule) , sizerule(UnusedSetRule) , minsizerule(UnusedForceRule) , maxsizerule(UnusedForceRule) , opacityactiverule(UnusedForceRule) , opacityinactiverule(UnusedForceRule) , ignoregeometryrule(UnusedSetRule) , desktoprule(UnusedSetRule) , screenrule(UnusedSetRule) , activityrule(UnusedSetRule) , typerule(UnusedForceRule) , maximizevertrule(UnusedSetRule) , maximizehorizrule(UnusedSetRule) , minimizerule(UnusedSetRule) , shaderule(UnusedSetRule) , skiptaskbarrule(UnusedSetRule) , skippagerrule(UnusedSetRule) , skipswitcherrule(UnusedSetRule) , aboverule(UnusedSetRule) , belowrule(UnusedSetRule) , fullscreenrule(UnusedSetRule) , noborderrule(UnusedSetRule) , decocolorrule(UnusedForceRule) , blockcompositingrule(UnusedForceRule) , fsplevelrule(UnusedForceRule) , fpplevelrule(UnusedForceRule) , acceptfocusrule(UnusedForceRule) , closeablerule(UnusedForceRule) , autogrouprule(UnusedForceRule) , autogroupfgrule(UnusedForceRule) , autogroupidrule(UnusedForceRule) , strictgeometryrule(UnusedForceRule) , shortcutrule(UnusedSetRule) , disableglobalshortcutsrule(UnusedForceRule) { } Rules::Rules(const QString& str, bool temporary) : temporary_state(temporary ? 2 : 0) { QTemporaryFile file; if (file.open()) { QByteArray s = str.toUtf8(); file.write(s.data(), s.length()); } file.flush(); KConfig cfg(file.fileName(), KConfig::SimpleConfig); readFromCfg(cfg.group(QString())); if (description.isEmpty()) description = QStringLiteral("temporary"); } #define READ_MATCH_STRING( var, func ) \ var = cfg.readEntry( #var ) func; \ var##match = (StringMatch) qMax( FirstStringMatch, \ qMin( LastStringMatch, static_cast< StringMatch >( cfg.readEntry( #var "match",0 )))); #define READ_SET_RULE( var, func, def ) \ var = func ( cfg.readEntry( #var, def)); \ var##rule = readSetRule( cfg, QStringLiteral( #var "rule" ) ); #define READ_SET_RULE_DEF( var , func, def ) \ var = func ( cfg.readEntry( #var, def )); \ var##rule = readSetRule( cfg, QStringLiteral( #var "rule" ) ); #define READ_FORCE_RULE( var, func, def) \ var = func ( cfg.readEntry( #var, def)); \ var##rule = readForceRule( cfg, QStringLiteral( #var "rule" ) ); #define READ_FORCE_RULE2( var, def, func, funcarg ) \ var = func ( cfg.readEntry( #var, def),funcarg ); \ var##rule = readForceRule( cfg, QStringLiteral( #var "rule" ) ); Rules::Rules(const KConfigGroup& cfg) : temporary_state(0) { readFromCfg(cfg); } static int limit0to4(int i) { return qMax(0, qMin(4, i)); } void Rules::readFromCfg(const KConfigGroup& cfg) { description = cfg.readEntry("Description"); if (description.isEmpty()) // capitalized first, lowercase for backwards compatibility description = cfg.readEntry("description"); READ_MATCH_STRING(wmclass, .toLower().toLatin1()); wmclasscomplete = cfg.readEntry("wmclasscomplete" , false); READ_MATCH_STRING(windowrole, .toLower().toLatin1()); READ_MATCH_STRING(title,); READ_MATCH_STRING(clientmachine, .toLower().toLatin1()); types = NET::WindowTypeMask(cfg.readEntry("types", NET::AllTypesMask)); READ_FORCE_RULE2(placement, QString(), Placement::policyFromString, false); READ_SET_RULE_DEF(position, , invalidPoint); READ_SET_RULE(size, , QSize()); if (size.isEmpty() && sizerule != (SetRule)Remember) sizerule = UnusedSetRule; READ_FORCE_RULE(minsize, , QSize()); if (!minsize.isValid()) minsize = QSize(1, 1); READ_FORCE_RULE(maxsize, , QSize()); if (maxsize.isEmpty()) maxsize = QSize(32767, 32767); READ_FORCE_RULE(opacityactive, , 0); if (opacityactive < 0 || opacityactive > 100) opacityactive = 100; READ_FORCE_RULE(opacityinactive, , 0); if (opacityinactive < 0 || opacityinactive > 100) opacityinactive = 100; READ_SET_RULE(ignoregeometry, , false); READ_SET_RULE(desktop, , 0); READ_SET_RULE(screen, , 0); READ_SET_RULE(activity, , QString()); type = readType(cfg, QStringLiteral("type")); typerule = type != NET::Unknown ? readForceRule(cfg, QStringLiteral("typerule")) : UnusedForceRule; READ_SET_RULE(maximizevert, , false); READ_SET_RULE(maximizehoriz, , false); READ_SET_RULE(minimize, , false); READ_SET_RULE(shade, , false); READ_SET_RULE(skiptaskbar, , false); READ_SET_RULE(skippager, , false); READ_SET_RULE(skipswitcher, , false); READ_SET_RULE(above, , false); READ_SET_RULE(below, , false); READ_SET_RULE(fullscreen, , false); READ_SET_RULE(noborder, , false); decocolor = readDecoColor(cfg); decocolorrule = decocolor.isEmpty() ? UnusedForceRule : readForceRule(cfg, QStringLiteral("decocolorrule")); READ_FORCE_RULE(blockcompositing, , false); READ_FORCE_RULE(fsplevel, limit0to4, 0); // fsp is 0-4 READ_FORCE_RULE(fpplevel, limit0to4, 0); // fpp is 0-4 READ_FORCE_RULE(acceptfocus, , false); READ_FORCE_RULE(closeable, , false); READ_FORCE_RULE(autogroup, , false); READ_FORCE_RULE(autogroupfg, , true); READ_FORCE_RULE(autogroupid, , QString()); READ_FORCE_RULE(strictgeometry, , false); READ_SET_RULE(shortcut, , QString()); READ_FORCE_RULE(disableglobalshortcuts, , false); } #undef READ_MATCH_STRING #undef READ_SET_RULE #undef READ_FORCE_RULE #undef READ_FORCE_RULE2 #define WRITE_MATCH_STRING( var, force ) \ if ( !var.isEmpty() || force ) \ { \ cfg.writeEntry( #var, var ); \ cfg.writeEntry( #var "match", (int)var##match ); \ } \ else \ { \ cfg.deleteEntry( #var ); \ cfg.deleteEntry( #var "match" ); \ } #define WRITE_SET_RULE( var, func ) \ if ( var##rule != UnusedSetRule ) \ { \ cfg.writeEntry( #var, func ( var )); \ cfg.writeEntry( #var "rule", (int)var##rule ); \ } \ else \ { \ cfg.deleteEntry( #var ); \ cfg.deleteEntry( #var "rule" ); \ } #define WRITE_FORCE_RULE( var, func ) \ if ( var##rule != UnusedForceRule ) \ { \ cfg.writeEntry( #var, func ( var )); \ cfg.writeEntry( #var "rule", (int)var##rule ); \ } \ else \ { \ cfg.deleteEntry( #var ); \ cfg.deleteEntry( #var "rule" ); \ } void Rules::write(KConfigGroup& cfg) const { cfg.writeEntry("Description", description); // always write wmclass WRITE_MATCH_STRING(wmclass, true); cfg.writeEntry("wmclasscomplete", wmclasscomplete); WRITE_MATCH_STRING(windowrole, false); WRITE_MATCH_STRING(title, false); WRITE_MATCH_STRING(clientmachine, false); if (types != NET::AllTypesMask) cfg.writeEntry("types", uint(types)); else cfg.deleteEntry("types"); WRITE_FORCE_RULE(placement, Placement::policyToString); WRITE_SET_RULE(position,); WRITE_SET_RULE(size,); WRITE_FORCE_RULE(minsize,); WRITE_FORCE_RULE(maxsize,); WRITE_FORCE_RULE(opacityactive,); WRITE_FORCE_RULE(opacityinactive,); WRITE_SET_RULE(ignoregeometry,); WRITE_SET_RULE(desktop,); WRITE_SET_RULE(screen,); WRITE_SET_RULE(activity,); WRITE_FORCE_RULE(type, int); WRITE_SET_RULE(maximizevert,); WRITE_SET_RULE(maximizehoriz,); WRITE_SET_RULE(minimize,); WRITE_SET_RULE(shade,); WRITE_SET_RULE(skiptaskbar,); WRITE_SET_RULE(skippager,); WRITE_SET_RULE(skipswitcher,); WRITE_SET_RULE(above,); WRITE_SET_RULE(below,); WRITE_SET_RULE(fullscreen,); WRITE_SET_RULE(noborder,); auto colorToString = [](const QString &value) -> QString { if (value.endsWith(QLatin1String(".colors"))) { return QFileInfo(value).baseName(); } else { return value; } }; WRITE_FORCE_RULE(decocolor, colorToString); WRITE_FORCE_RULE(blockcompositing,); WRITE_FORCE_RULE(fsplevel,); WRITE_FORCE_RULE(fpplevel,); WRITE_FORCE_RULE(acceptfocus,); WRITE_FORCE_RULE(closeable,); WRITE_FORCE_RULE(autogroup,); WRITE_FORCE_RULE(autogroupfg,); WRITE_FORCE_RULE(autogroupid,); WRITE_FORCE_RULE(strictgeometry,); WRITE_SET_RULE(shortcut,); WRITE_FORCE_RULE(disableglobalshortcuts,); } #undef WRITE_MATCH_STRING #undef WRITE_SET_RULE #undef WRITE_FORCE_RULE // returns true if it doesn't affect anything bool Rules::isEmpty() const { return(placementrule == UnusedForceRule && positionrule == UnusedSetRule && sizerule == UnusedSetRule && minsizerule == UnusedForceRule && maxsizerule == UnusedForceRule && opacityactiverule == UnusedForceRule && opacityinactiverule == UnusedForceRule && ignoregeometryrule == UnusedSetRule && desktoprule == UnusedSetRule && screenrule == UnusedSetRule && activityrule == UnusedSetRule && typerule == UnusedForceRule && maximizevertrule == UnusedSetRule && maximizehorizrule == UnusedSetRule && minimizerule == UnusedSetRule && shaderule == UnusedSetRule && skiptaskbarrule == UnusedSetRule && skippagerrule == UnusedSetRule && skipswitcherrule == UnusedSetRule && aboverule == UnusedSetRule && belowrule == UnusedSetRule && fullscreenrule == UnusedSetRule && noborderrule == UnusedSetRule && decocolorrule == UnusedForceRule && blockcompositingrule == UnusedForceRule && fsplevelrule == UnusedForceRule && fpplevelrule == UnusedForceRule && acceptfocusrule == UnusedForceRule && closeablerule == UnusedForceRule && autogrouprule == UnusedForceRule && autogroupfgrule == UnusedForceRule && autogroupidrule == UnusedForceRule && strictgeometryrule == UnusedForceRule && shortcutrule == UnusedSetRule && disableglobalshortcutsrule == UnusedForceRule); } Rules::SetRule Rules::readSetRule(const KConfigGroup& cfg, const QString& key) { int v = cfg.readEntry(key, 0); if (v >= DontAffect && v <= ForceTemporarily) return static_cast< SetRule >(v); return UnusedSetRule; } Rules::ForceRule Rules::readForceRule(const KConfigGroup& cfg, const QString& key) { int v = cfg.readEntry(key, 0); if (v == DontAffect || v == Force || v == ForceTemporarily) return static_cast< ForceRule >(v); return UnusedForceRule; } NET::WindowType Rules::readType(const KConfigGroup& cfg, const QString& key) { int v = cfg.readEntry(key, 0); if (v >= NET::Normal && v <= NET::Splash) return static_cast< NET::WindowType >(v); return NET::Unknown; } QString Rules::readDecoColor(const KConfigGroup &cfg) { QString themeName = cfg.readEntry("decocolor", QString()); if (themeName.isEmpty()) { return QString(); } // find the actual scheme file return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("color-schemes/") + themeName + QLatin1String(".colors")); } bool Rules::matchType(NET::WindowType match_type) const { if (types != NET::AllTypesMask) { if (match_type == NET::Unknown) match_type = NET::Normal; // NET::Unknown->NET::Normal is only here for matching if (!NET::typeMatchesMask(match_type, types)) return false; } return true; } bool Rules::matchWMClass(const QByteArray& match_class, const QByteArray& match_name) const { if (wmclassmatch != UnimportantMatch) { // TODO optimize? QByteArray cwmclass = wmclasscomplete ? match_name + ' ' + match_class : match_class; if (wmclassmatch == RegExpMatch && QRegExp(QString::fromUtf8(wmclass)).indexIn(QString::fromUtf8(cwmclass)) == -1) return false; if (wmclassmatch == ExactMatch && wmclass != cwmclass) return false; if (wmclassmatch == SubstringMatch && !cwmclass.contains(wmclass)) return false; } return true; } bool Rules::matchRole(const QByteArray& match_role) const { if (windowrolematch != UnimportantMatch) { if (windowrolematch == RegExpMatch && QRegExp(QString::fromUtf8(windowrole)).indexIn(QString::fromUtf8(match_role)) == -1) return false; if (windowrolematch == ExactMatch && windowrole != match_role) return false; if (windowrolematch == SubstringMatch && !match_role.contains(windowrole)) return false; } return true; } bool Rules::matchTitle(const QString& match_title) const { if (titlematch != UnimportantMatch) { if (titlematch == RegExpMatch && QRegExp(title).indexIn(match_title) == -1) return false; if (titlematch == ExactMatch && title != match_title) return false; if (titlematch == SubstringMatch && !match_title.contains(title)) return false; } return true; } bool Rules::matchClientMachine(const QByteArray& match_machine, bool local) const { if (clientmachinematch != UnimportantMatch) { // if it's localhost, check also "localhost" before checking hostname if (match_machine != "localhost" && local && matchClientMachine("localhost", true)) return true; if (clientmachinematch == RegExpMatch && QRegExp(QString::fromUtf8(clientmachine)).indexIn(QString::fromUtf8(match_machine)) == -1) return false; if (clientmachinematch == ExactMatch && clientmachine != match_machine) return false; if (clientmachinematch == SubstringMatch && !match_machine.contains(clientmachine)) return false; } return true; } #ifndef KCMRULES bool Rules::match(const AbstractClient* c) const { if (!matchType(c->windowType(true))) return false; if (!matchWMClass(c->resourceClass(), c->resourceName())) return false; if (!matchRole(c->windowRole().toLower())) return false; if (!matchClientMachine(c->clientMachine()->hostName(), c->clientMachine()->isLocal())) return false; if (titlematch != UnimportantMatch) // track title changes to rematch rules QObject::connect(c, &AbstractClient::captionChanged, c, &AbstractClient::evaluateWindowRules, // QueuedConnection, because title may change before // the client is ready (could segfault!) static_cast(Qt::QueuedConnection|Qt::UniqueConnection)); if (!matchTitle(c->captionNormal())) return false; return true; } #define NOW_REMEMBER(_T_, _V_) ((selection & _T_) && (_V_##rule == (SetRule)Remember)) bool Rules::update(AbstractClient* c, int selection) { // TODO check this setting is for this client ? bool updated = false; if NOW_REMEMBER(Position, position) { if (!c->isFullScreen()) { QPoint new_pos = position; // don't use the position in the direction which is maximized if ((c->maximizeMode() & MaximizeHorizontal) == 0) new_pos.setX(c->pos().x()); if ((c->maximizeMode() & MaximizeVertical) == 0) new_pos.setY(c->pos().y()); updated = updated || position != new_pos; position = new_pos; } } if NOW_REMEMBER(Size, size) { if (!c->isFullScreen()) { QSize new_size = size; // don't use the position in the direction which is maximized if ((c->maximizeMode() & MaximizeHorizontal) == 0) new_size.setWidth(c->size().width()); if ((c->maximizeMode() & MaximizeVertical) == 0) new_size.setHeight(c->size().height()); updated = updated || size != new_size; size = new_size; } } if NOW_REMEMBER(Desktop, desktop) { updated = updated || desktop != c->desktop(); desktop = c->desktop(); } if NOW_REMEMBER(Screen, screen) { updated = updated || screen != c->screen(); screen = c->screen(); } if NOW_REMEMBER(Activity, activity) { // TODO: ivan - multiple activities support const QString & joinedActivities = c->activities().join(QStringLiteral(",")); updated = updated || activity != joinedActivities; activity = joinedActivities; } if NOW_REMEMBER(MaximizeVert, maximizevert) { updated = updated || maximizevert != bool(c->maximizeMode() & MaximizeVertical); maximizevert = c->maximizeMode() & MaximizeVertical; } if NOW_REMEMBER(MaximizeHoriz, maximizehoriz) { updated = updated || maximizehoriz != bool(c->maximizeMode() & MaximizeHorizontal); maximizehoriz = c->maximizeMode() & MaximizeHorizontal; } if NOW_REMEMBER(Minimize, minimize) { updated = updated || minimize != c->isMinimized(); minimize = c->isMinimized(); } if NOW_REMEMBER(Shade, shade) { updated = updated || (shade != (c->shadeMode() != ShadeNone)); shade = c->shadeMode() != ShadeNone; } if NOW_REMEMBER(SkipTaskbar, skiptaskbar) { updated = updated || skiptaskbar != c->skipTaskbar(); skiptaskbar = c->skipTaskbar(); } if NOW_REMEMBER(SkipPager, skippager) { updated = updated || skippager != c->skipPager(); skippager = c->skipPager(); } if NOW_REMEMBER(SkipSwitcher, skipswitcher) { updated = updated || skipswitcher != c->skipSwitcher(); skipswitcher = c->skipSwitcher(); } if NOW_REMEMBER(Above, above) { updated = updated || above != c->keepAbove(); above = c->keepAbove(); } if NOW_REMEMBER(Below, below) { updated = updated || below != c->keepBelow(); below = c->keepBelow(); } if NOW_REMEMBER(Fullscreen, fullscreen) { updated = updated || fullscreen != c->isFullScreen(); fullscreen = c->isFullScreen(); } if NOW_REMEMBER(NoBorder, noborder) { updated = updated || noborder != c->noBorder(); noborder = c->noBorder(); } return updated; } #undef NOW_REMEMBER #define APPLY_RULE( var, name, type ) \ bool Rules::apply##name( type& arg, bool init ) const \ { \ if ( checkSetRule( var##rule, init )) \ arg = this->var; \ return checkSetStop( var##rule ); \ } #define APPLY_FORCE_RULE( var, name, type ) \ bool Rules::apply##name( type& arg ) const \ { \ if ( checkForceRule( var##rule )) \ arg = this->var; \ return checkForceStop( var##rule ); \ } APPLY_FORCE_RULE(placement, Placement, Placement::Policy) bool Rules::applyGeometry(QRect& rect, bool init) const { QPoint p = rect.topLeft(); QSize s = rect.size(); bool ret = false; // no short-circuiting if (applyPosition(p, init)) { rect.moveTopLeft(p); ret = true; } if (applySize(s, init)) { rect.setSize(s); ret = true; } return ret; } bool Rules::applyPosition(QPoint& pos, bool init) const { if (this->position != invalidPoint && checkSetRule(positionrule, init)) pos = this->position; return checkSetStop(positionrule); } bool Rules::applySize(QSize& s, bool init) const { if (this->size.isValid() && checkSetRule(sizerule, init)) s = this->size; return checkSetStop(sizerule); } APPLY_FORCE_RULE(minsize, MinSize, QSize) APPLY_FORCE_RULE(maxsize, MaxSize, QSize) APPLY_FORCE_RULE(opacityactive, OpacityActive, int) APPLY_FORCE_RULE(opacityinactive, OpacityInactive, int) APPLY_RULE(ignoregeometry, IgnoreGeometry, bool) APPLY_RULE(desktop, Desktop, int) APPLY_RULE(screen, Screen, int) APPLY_RULE(activity, Activity, QString) APPLY_FORCE_RULE(type, Type, NET::WindowType) bool Rules::applyMaximizeHoriz(MaximizeMode& mode, bool init) const { if (checkSetRule(maximizehorizrule, init)) mode = static_cast< MaximizeMode >((maximizehoriz ? MaximizeHorizontal : 0) | (mode & MaximizeVertical)); return checkSetStop(maximizehorizrule); } bool Rules::applyMaximizeVert(MaximizeMode& mode, bool init) const { if (checkSetRule(maximizevertrule, init)) mode = static_cast< MaximizeMode >((maximizevert ? MaximizeVertical : 0) | (mode & MaximizeHorizontal)); return checkSetStop(maximizevertrule); } APPLY_RULE(minimize, Minimize, bool) bool Rules::applyShade(ShadeMode& sh, bool init) const { if (checkSetRule(shaderule, init)) { if (!this->shade) sh = ShadeNone; if (this->shade && sh == ShadeNone) sh = ShadeNormal; } return checkSetStop(shaderule); } APPLY_RULE(skiptaskbar, SkipTaskbar, bool) APPLY_RULE(skippager, SkipPager, bool) APPLY_RULE(skipswitcher, SkipSwitcher, bool) APPLY_RULE(above, KeepAbove, bool) APPLY_RULE(below, KeepBelow, bool) APPLY_RULE(fullscreen, FullScreen, bool) APPLY_RULE(noborder, NoBorder, bool) APPLY_FORCE_RULE(decocolor, DecoColor, QString) APPLY_FORCE_RULE(blockcompositing, BlockCompositing, bool) APPLY_FORCE_RULE(fsplevel, FSP, int) APPLY_FORCE_RULE(fpplevel, FPP, int) APPLY_FORCE_RULE(acceptfocus, AcceptFocus, bool) APPLY_FORCE_RULE(closeable, Closeable, bool) APPLY_FORCE_RULE(autogroup, Autogrouping, bool) APPLY_FORCE_RULE(autogroupfg, AutogroupInForeground, bool) APPLY_FORCE_RULE(autogroupid, AutogroupById, QString) APPLY_FORCE_RULE(strictgeometry, StrictGeometry, bool) APPLY_RULE(shortcut, Shortcut, QString) APPLY_FORCE_RULE(disableglobalshortcuts, DisableGlobalShortcuts, bool) #undef APPLY_RULE #undef APPLY_FORCE_RULE bool Rules::isTemporary() const { return temporary_state > 0; } bool Rules::discardTemporary(bool force) { if (temporary_state == 0) // not temporary return false; if (force || --temporary_state == 0) { // too old delete this; return true; } return false; } #define DISCARD_USED_SET_RULE( var ) \ do { \ if ( var##rule == ( SetRule ) ApplyNow || ( withdrawn && var##rule == ( SetRule ) ForceTemporarily )) \ var##rule = UnusedSetRule; \ } while ( false ) #define DISCARD_USED_FORCE_RULE( var ) \ do { \ if ( withdrawn && var##rule == ( ForceRule ) ForceTemporarily ) \ var##rule = UnusedForceRule; \ } while ( false ) void Rules::discardUsed(bool withdrawn) { DISCARD_USED_FORCE_RULE(placement); DISCARD_USED_SET_RULE(position); DISCARD_USED_SET_RULE(size); DISCARD_USED_FORCE_RULE(minsize); DISCARD_USED_FORCE_RULE(maxsize); DISCARD_USED_FORCE_RULE(opacityactive); DISCARD_USED_FORCE_RULE(opacityinactive); DISCARD_USED_SET_RULE(ignoregeometry); DISCARD_USED_SET_RULE(desktop); DISCARD_USED_SET_RULE(screen); DISCARD_USED_SET_RULE(activity); DISCARD_USED_FORCE_RULE(type); DISCARD_USED_SET_RULE(maximizevert); DISCARD_USED_SET_RULE(maximizehoriz); DISCARD_USED_SET_RULE(minimize); DISCARD_USED_SET_RULE(shade); DISCARD_USED_SET_RULE(skiptaskbar); DISCARD_USED_SET_RULE(skippager); DISCARD_USED_SET_RULE(skipswitcher); DISCARD_USED_SET_RULE(above); DISCARD_USED_SET_RULE(below); DISCARD_USED_SET_RULE(fullscreen); DISCARD_USED_SET_RULE(noborder); DISCARD_USED_FORCE_RULE(decocolor); DISCARD_USED_FORCE_RULE(blockcompositing); DISCARD_USED_FORCE_RULE(fsplevel); DISCARD_USED_FORCE_RULE(fpplevel); DISCARD_USED_FORCE_RULE(acceptfocus); DISCARD_USED_FORCE_RULE(closeable); DISCARD_USED_FORCE_RULE(autogroup); DISCARD_USED_FORCE_RULE(autogroupfg); DISCARD_USED_FORCE_RULE(autogroupid); DISCARD_USED_FORCE_RULE(strictgeometry); DISCARD_USED_SET_RULE(shortcut); DISCARD_USED_FORCE_RULE(disableglobalshortcuts); } #undef DISCARD_USED_SET_RULE #undef DISCARD_USED_FORCE_RULE #endif QDebug& operator<<(QDebug& stream, const Rules* r) { return stream << "[" << r->description << ":" << r->wmclass << "]" ; } #ifndef KCMRULES void WindowRules::discardTemporary() { QVector< Rules* >::Iterator it2 = rules.begin(); for (QVector< Rules* >::Iterator it = rules.begin(); it != rules.end(); ) { if ((*it)->discardTemporary(true)) ++it; else { *it2++ = *it++; } } rules.erase(it2, rules.end()); } void WindowRules::update(AbstractClient* c, int selection) { bool updated = false; for (QVector< Rules* >::ConstIterator it = rules.constBegin(); it != rules.constEnd(); ++it) if ((*it)->update(c, selection)) // no short-circuiting here updated = true; if (updated) RuleBook::self()->requestDiskStorage(); } #define CHECK_RULE( rule, type ) \ type WindowRules::check##rule( type arg, bool init ) const \ { \ if ( rules.count() == 0 ) \ return arg; \ type ret = arg; \ for ( QVector< Rules* >::ConstIterator it = rules.constBegin(); \ it != rules.constEnd(); \ ++it ) \ { \ if ( (*it)->apply##rule( ret, init )) \ break; \ } \ return ret; \ } #define CHECK_FORCE_RULE( rule, type ) \ type WindowRules::check##rule( type arg ) const \ { \ if ( rules.count() == 0 ) \ return arg; \ type ret = arg; \ for ( QVector< Rules* >::ConstIterator it = rules.begin(); \ it != rules.end(); \ ++it ) \ { \ if ( (*it)->apply##rule( ret )) \ break; \ } \ return ret; \ } CHECK_FORCE_RULE(Placement, Placement::Policy) QRect WindowRules::checkGeometry(QRect rect, bool init) const { return QRect(checkPosition(rect.topLeft(), init), checkSize(rect.size(), init)); } CHECK_RULE(Position, QPoint) CHECK_RULE(Size, QSize) CHECK_FORCE_RULE(MinSize, QSize) CHECK_FORCE_RULE(MaxSize, QSize) CHECK_FORCE_RULE(OpacityActive, int) CHECK_FORCE_RULE(OpacityInactive, int) CHECK_RULE(IgnoreGeometry, bool) CHECK_RULE(Desktop, int) CHECK_RULE(Activity, QString) CHECK_FORCE_RULE(Type, NET::WindowType) CHECK_RULE(MaximizeVert, MaximizeMode) CHECK_RULE(MaximizeHoriz, MaximizeMode) MaximizeMode WindowRules::checkMaximize(MaximizeMode mode, bool init) const { bool vert = checkMaximizeVert(mode, init) & MaximizeVertical; bool horiz = checkMaximizeHoriz(mode, init) & MaximizeHorizontal; return static_cast< MaximizeMode >((vert ? MaximizeVertical : 0) | (horiz ? MaximizeHorizontal : 0)); } int WindowRules::checkScreen(int screen, bool init) const { if ( rules.count() == 0 ) return screen; int ret = screen; for ( QVector< Rules* >::ConstIterator it = rules.constBegin(); it != rules.constEnd(); ++it ) { if ( (*it)->applyScreen( ret, init )) break; } if (ret >= Screens::self()->count()) ret = screen; return ret; } CHECK_RULE(Minimize, bool) CHECK_RULE(Shade, ShadeMode) CHECK_RULE(SkipTaskbar, bool) CHECK_RULE(SkipPager, bool) CHECK_RULE(SkipSwitcher, bool) CHECK_RULE(KeepAbove, bool) CHECK_RULE(KeepBelow, bool) CHECK_RULE(FullScreen, bool) CHECK_RULE(NoBorder, bool) CHECK_FORCE_RULE(DecoColor, QString) CHECK_FORCE_RULE(BlockCompositing, bool) CHECK_FORCE_RULE(FSP, int) CHECK_FORCE_RULE(FPP, int) CHECK_FORCE_RULE(AcceptFocus, bool) CHECK_FORCE_RULE(Closeable, bool) CHECK_FORCE_RULE(Autogrouping, bool) CHECK_FORCE_RULE(AutogroupInForeground, bool) CHECK_FORCE_RULE(AutogroupById, QString) CHECK_FORCE_RULE(StrictGeometry, bool) CHECK_RULE(Shortcut, QString) CHECK_FORCE_RULE(DisableGlobalShortcuts, bool) #undef CHECK_RULE #undef CHECK_FORCE_RULE // Client void AbstractClient::setupWindowRules(bool ignore_temporary) { disconnect(this, &AbstractClient::captionChanged, this, &AbstractClient::evaluateWindowRules); m_rules = RuleBook::self()->find(this, ignore_temporary); // check only after getting the rules, because there may be a rule forcing window type } // Applies Force, ForceTemporarily and ApplyNow rules // Used e.g. after the rules have been modified using the kcm. void AbstractClient::applyWindowRules() { // apply force rules // Placement - does need explicit update, just like some others below // Geometry : setGeometry() doesn't check rules auto client_rules = rules(); QRect orig_geom = QRect(pos(), sizeForClientSize(clientSize())); // handle shading QRect geom = client_rules->checkGeometry(orig_geom); if (geom != orig_geom) setGeometry(geom); // MinSize, MaxSize handled by Geometry // IgnoreGeometry setDesktop(desktop()); workspace()->sendClientToScreen(this, screen()); setOnActivities(activities()); // Type maximize(maximizeMode()); // Minimize : functions don't check, and there are two functions if (client_rules->checkMinimize(isMinimized())) minimize(); else unminimize(); setShade(shadeMode()); setOriginalSkipTaskbar(skipTaskbar()); setSkipPager(skipPager()); setSkipSwitcher(skipSwitcher()); setKeepAbove(keepAbove()); setKeepBelow(keepBelow()); setFullScreen(isFullScreen(), true); setNoBorder(noBorder()); updateColorScheme(); // FSP // AcceptFocus : if (workspace()->mostRecentlyActivatedClient() == this && !client_rules->checkAcceptFocus(true)) workspace()->activateNextClient(this); // Closeable QSize s = adjustedSize(); if (s != size()) resizeWithChecks(s); // Autogrouping : Only checked on window manage // AutogroupInForeground : Only checked on window manage // AutogroupById : Only checked on window manage // StrictGeometry setShortcut(rules()->checkShortcut(shortcut().toString())); // see also Client::setActive() if (isActive()) { setOpacity(rules()->checkOpacityActive(qRound(opacity() * 100.0)) / 100.0); workspace()->disableGlobalShortcutsForClient(rules()->checkDisableGlobalShortcuts(false)); } else setOpacity(rules()->checkOpacityInactive(qRound(opacity() * 100.0)) / 100.0); } void Client::updateWindowRules(Rules::Types selection) { if (!isManaged()) // not fully setup yet return; AbstractClient::updateWindowRules(selection); } void AbstractClient::updateWindowRules(Rules::Types selection) { if (RuleBook::self()->areUpdatesDisabled()) return; m_rules.update(this, selection); } void AbstractClient::finishWindowRules() { updateWindowRules(Rules::All); m_rules = WindowRules(); } // Workspace KWIN_SINGLETON_FACTORY(RuleBook) RuleBook::RuleBook(QObject *parent) : QObject(parent) , m_updateTimer(new QTimer(this)) , m_updatesDisabled(false) , m_temporaryRulesMessages() { initWithX11(); connect(kwinApp(), &Application::x11ConnectionChanged, this, &RuleBook::initWithX11); connect(m_updateTimer, SIGNAL(timeout()), SLOT(save())); m_updateTimer->setInterval(1000); m_updateTimer->setSingleShot(true); } RuleBook::~RuleBook() { save(); deleteAll(); } void RuleBook::initWithX11() { auto c = kwinApp()->x11Connection(); if (!c) { m_temporaryRulesMessages.reset(); return; } m_temporaryRulesMessages.reset(new KXMessages(c, kwinApp()->x11RootWindow(), "_KDE_NET_WM_TEMPORARY_RULES", nullptr)); connect(m_temporaryRulesMessages.data(), SIGNAL(gotMessage(QString)), SLOT(temporaryRulesMessage(QString))); } void RuleBook::deleteAll() { qDeleteAll(m_rules); m_rules.clear(); } WindowRules RuleBook::find(const AbstractClient* c, bool ignore_temporary) { QVector< Rules* > ret; for (QList< Rules* >::Iterator it = m_rules.begin(); it != m_rules.end(); ) { if (ignore_temporary && (*it)->isTemporary()) { ++it; continue; } if ((*it)->match(c)) { Rules* rule = *it; qCDebug(KWIN_CORE) << "Rule found:" << rule << ":" << c; if (rule->isTemporary()) it = m_rules.erase(it); else ++it; ret.append(rule); continue; } ++it; } return WindowRules(ret); } void RuleBook::edit(AbstractClient* c, bool whole_app) { save(); QStringList args; args << QStringLiteral("--wid") << QString::number(c->window()); if (whole_app) args << QStringLiteral("--whole-app"); QProcess *p = new Process(this); p->setArguments(args); p->setProcessEnvironment(kwinApp()->processStartupEnvironment()); p->setProgram(QStringLiteral(KWIN_RULES_DIALOG_BIN)); connect(p, static_cast(&QProcess::finished), p, &QProcess::deleteLater); connect(p, static_cast(&QProcess::error), this, [p] (QProcess::ProcessError e) { if (e == QProcess::FailedToStart) { qCDebug(KWIN_CORE) << "Failed to start" << p->program(); } } ); p->start(); } void RuleBook::load() { deleteAll(); - KConfig cfg(QStringLiteral(KWIN_NAME "rulesrc"), KConfig::NoGlobals); - int count = cfg.group("General").readEntry("count", 0); + if (!m_config) { + m_config = KSharedConfig::openConfig(QStringLiteral(KWIN_NAME "rulesrc"), KConfig::NoGlobals); + } + int count = m_config->group("General").readEntry("count", 0); for (int i = 1; i <= count; ++i) { - KConfigGroup cg(&cfg, QString::number(i)); + KConfigGroup cg(m_config, QString::number(i)); Rules* rule = new Rules(cg); m_rules.append(rule); } } void RuleBook::save() { m_updateTimer->stop(); KConfig cfg(QStringLiteral(KWIN_NAME "rulesrc"), KConfig::NoGlobals); QStringList groups = cfg.groupList(); for (QStringList::ConstIterator it = groups.constBegin(); it != groups.constEnd(); ++it) cfg.deleteGroup(*it); cfg.group("General").writeEntry("count", m_rules.count()); int i = 1; for (QList< Rules* >::ConstIterator it = m_rules.constBegin(); it != m_rules.constEnd(); ++it) { if ((*it)->isTemporary()) continue; KConfigGroup cg(&cfg, QString::number(i)); (*it)->write(cg); ++i; } } void RuleBook::temporaryRulesMessage(const QString& message) { bool was_temporary = false; for (QList< Rules* >::ConstIterator it = m_rules.constBegin(); it != m_rules.constEnd(); ++it) if ((*it)->isTemporary()) was_temporary = true; Rules* rule = new Rules(message, true); m_rules.prepend(rule); // highest priority first if (!was_temporary) QTimer::singleShot(60000, this, SLOT(cleanupTemporaryRules())); } void RuleBook::cleanupTemporaryRules() { bool has_temporary = false; for (QList< Rules* >::Iterator it = m_rules.begin(); it != m_rules.end(); ) { if ((*it)->discardTemporary(false)) { // deletes (*it) it = m_rules.erase(it); } else { if ((*it)->isTemporary()) has_temporary = true; ++it; } } if (has_temporary) QTimer::singleShot(60000, this, SLOT(cleanupTemporaryRules())); } void RuleBook::discardUsed(AbstractClient* c, bool withdrawn) { bool updated = false; for (QList< Rules* >::Iterator it = m_rules.begin(); it != m_rules.end(); ) { if (c->rules()->contains(*it)) { updated = true; (*it)->discardUsed(withdrawn); if ((*it)->isEmpty()) { c->removeRule(*it); Rules* r = *it; it = m_rules.erase(it); delete r; continue; } } ++it; } if (updated) requestDiskStorage(); } void RuleBook::requestDiskStorage() { m_updateTimer->start(); } void RuleBook::setUpdatesDisabled(bool disable) { m_updatesDisabled = disable; if (!disable) { foreach (Client * c, Workspace::self()->clientList()) c->updateWindowRules(Rules::All); } } #endif } // namespace diff --git a/rules.h b/rules.h index 007ce5b71..f5367bf39 100644 --- a/rules.h +++ b/rules.h @@ -1,386 +1,392 @@ /******************************************************************** 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; 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, All = 0xffffffff }; Q_DECLARE_FLAGS(Types, Type) void write(KConfigGroup&) const; bool isEmpty() const; #ifndef KCMRULES void 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; 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; 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 qFind(rules.begin(), rules.end(), rule) != rules.end(); } inline void WindowRules::remove(Rules* rule) { QVector< Rules* >::Iterator pos = qFind(rules.begin(), rules.end(), rule); if (pos != rules.end()) rules.erase(pos); } #endif QDebug& operator<<(QDebug& stream, const Rules*); } // namespace Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::Rules::Types) #endif