File indexing completed on 2024-04-28 16:48:59

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2004 Lubos Lunak <l.lunak@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "rules.h"
0011 
0012 #include <KXMessages>
0013 #include <QDebug>
0014 #include <QDir>
0015 #include <QFile>
0016 #include <QFileInfo>
0017 #include <QRegularExpression>
0018 #include <QTemporaryFile>
0019 #include <kconfig.h>
0020 
0021 #ifndef KCMRULES
0022 #include "client_machine.h"
0023 #include "main.h"
0024 #include "virtualdesktops.h"
0025 #include "window.h"
0026 #endif
0027 
0028 #include "core/output.h"
0029 #include "rulebooksettings.h"
0030 #include "rulesettings.h"
0031 #include "workspace.h"
0032 
0033 namespace KWin
0034 {
0035 
0036 Rules::Rules()
0037     : temporary_state(0)
0038     , wmclassmatch(UnimportantMatch)
0039     , wmclasscomplete(UnimportantMatch)
0040     , windowrolematch(UnimportantMatch)
0041     , titlematch(UnimportantMatch)
0042     , clientmachinematch(UnimportantMatch)
0043     , types(NET::AllTypesMask)
0044     , placementrule(UnusedForceRule)
0045     , positionrule(UnusedSetRule)
0046     , sizerule(UnusedSetRule)
0047     , minsizerule(UnusedForceRule)
0048     , maxsizerule(UnusedForceRule)
0049     , opacityactiverule(UnusedForceRule)
0050     , opacityinactiverule(UnusedForceRule)
0051     , ignoregeometryrule(UnusedSetRule)
0052     , desktopsrule(UnusedSetRule)
0053     , screenrule(UnusedSetRule)
0054     , activityrule(UnusedSetRule)
0055     , typerule(UnusedForceRule)
0056     , maximizevertrule(UnusedSetRule)
0057     , maximizehorizrule(UnusedSetRule)
0058     , minimizerule(UnusedSetRule)
0059     , shaderule(UnusedSetRule)
0060     , skiptaskbarrule(UnusedSetRule)
0061     , skippagerrule(UnusedSetRule)
0062     , skipswitcherrule(UnusedSetRule)
0063     , aboverule(UnusedSetRule)
0064     , belowrule(UnusedSetRule)
0065     , fullscreenrule(UnusedSetRule)
0066     , noborderrule(UnusedSetRule)
0067     , decocolorrule(UnusedForceRule)
0068     , blockcompositingrule(UnusedForceRule)
0069     , fsplevelrule(UnusedForceRule)
0070     , fpplevelrule(UnusedForceRule)
0071     , acceptfocusrule(UnusedForceRule)
0072     , closeablerule(UnusedForceRule)
0073     , autogrouprule(UnusedForceRule)
0074     , autogroupfgrule(UnusedForceRule)
0075     , autogroupidrule(UnusedForceRule)
0076     , strictgeometryrule(UnusedForceRule)
0077     , shortcutrule(UnusedSetRule)
0078     , disableglobalshortcutsrule(UnusedForceRule)
0079     , desktopfilerule(UnusedSetRule)
0080 {
0081 }
0082 
0083 Rules::Rules(const QString &str, bool temporary)
0084     : temporary_state(temporary ? 2 : 0)
0085 {
0086     QTemporaryFile file;
0087     if (file.open()) {
0088         QByteArray s = str.toUtf8();
0089         file.write(s.data(), s.length());
0090     }
0091     file.flush();
0092     auto cfg = KSharedConfig::openConfig(file.fileName(), KConfig::SimpleConfig);
0093     RuleSettings settings(cfg, QString());
0094     readFromSettings(&settings);
0095     if (description.isEmpty()) {
0096         description = QStringLiteral("temporary");
0097     }
0098 }
0099 
0100 #define READ_MATCH_STRING(var, func) \
0101     var = settings->var() func;      \
0102     var##match = static_cast<StringMatch>(settings->var##match())
0103 
0104 #define READ_SET_RULE(var) \
0105     var = settings->var(); \
0106     var##rule = static_cast<SetRule>(settings->var##rule())
0107 
0108 #define READ_FORCE_RULE(var, func) \
0109     var = func(settings->var());   \
0110     var##rule = convertForceRule(settings->var##rule())
0111 
0112 Rules::Rules(const RuleSettings *settings)
0113     : temporary_state(0)
0114 {
0115     readFromSettings(settings);
0116 }
0117 
0118 void Rules::readFromSettings(const RuleSettings *settings)
0119 {
0120     description = settings->description();
0121     if (description.isEmpty()) {
0122         description = settings->descriptionLegacy();
0123     }
0124     READ_MATCH_STRING(wmclass, );
0125     wmclasscomplete = settings->wmclasscomplete();
0126     READ_MATCH_STRING(windowrole, );
0127     READ_MATCH_STRING(title, );
0128     READ_MATCH_STRING(clientmachine, .toLower());
0129     types = NET::WindowTypeMask(settings->types());
0130     READ_FORCE_RULE(placement, );
0131     READ_SET_RULE(position);
0132     READ_SET_RULE(size);
0133     if (size.isEmpty() && sizerule != static_cast<SetRule>(Remember)) {
0134         sizerule = UnusedSetRule;
0135     }
0136     READ_FORCE_RULE(minsize, );
0137     if (!minsize.isValid()) {
0138         minsize = QSize(1, 1);
0139     }
0140     READ_FORCE_RULE(maxsize, );
0141     if (maxsize.isEmpty()) {
0142         maxsize = QSize(32767, 32767);
0143     }
0144     READ_FORCE_RULE(opacityactive, );
0145     READ_FORCE_RULE(opacityinactive, );
0146     READ_SET_RULE(ignoregeometry);
0147     READ_SET_RULE(desktops);
0148     READ_SET_RULE(screen);
0149     READ_SET_RULE(activity);
0150     READ_FORCE_RULE(type, static_cast<NET::WindowType>);
0151     if (type == NET::Unknown) {
0152         typerule = UnusedForceRule;
0153     }
0154     READ_SET_RULE(maximizevert);
0155     READ_SET_RULE(maximizehoriz);
0156     READ_SET_RULE(minimize);
0157     READ_SET_RULE(shade);
0158     READ_SET_RULE(skiptaskbar);
0159     READ_SET_RULE(skippager);
0160     READ_SET_RULE(skipswitcher);
0161     READ_SET_RULE(above);
0162     READ_SET_RULE(below);
0163     READ_SET_RULE(fullscreen);
0164     READ_SET_RULE(noborder);
0165 
0166     READ_FORCE_RULE(decocolor, getDecoColor);
0167     if (decocolor.isEmpty()) {
0168         decocolorrule = UnusedForceRule;
0169     }
0170 
0171     READ_FORCE_RULE(blockcompositing, );
0172     READ_FORCE_RULE(fsplevel, );
0173     READ_FORCE_RULE(fpplevel, );
0174     READ_FORCE_RULE(acceptfocus, );
0175     READ_FORCE_RULE(closeable, );
0176     READ_FORCE_RULE(autogroup, );
0177     READ_FORCE_RULE(autogroupfg, );
0178     READ_FORCE_RULE(autogroupid, );
0179     READ_FORCE_RULE(strictgeometry, );
0180     READ_SET_RULE(shortcut);
0181     READ_FORCE_RULE(disableglobalshortcuts, );
0182     READ_SET_RULE(desktopfile);
0183 }
0184 
0185 #undef READ_MATCH_STRING
0186 #undef READ_SET_RULE
0187 #undef READ_FORCE_RULE
0188 #undef READ_FORCE_RULE2
0189 
0190 #define WRITE_MATCH_STRING(var, capital, force) \
0191     settings->set##capital##match(var##match);  \
0192     if (!var.isEmpty() || force) {              \
0193         settings->set##capital(var);            \
0194     }
0195 
0196 #define WRITE_SET_RULE(var, capital, func)   \
0197     settings->set##capital##rule(var##rule); \
0198     if (var##rule != UnusedSetRule) {        \
0199         settings->set##capital(func(var));   \
0200     }
0201 
0202 #define WRITE_FORCE_RULE(var, capital, func) \
0203     settings->set##capital##rule(var##rule); \
0204     if (var##rule != UnusedForceRule) {      \
0205         settings->set##capital(func(var));   \
0206     }
0207 
0208 void Rules::write(RuleSettings *settings) const
0209 {
0210     settings->setDescription(description);
0211     // always write wmclass
0212     WRITE_MATCH_STRING(wmclass, Wmclass, true);
0213     settings->setWmclasscomplete(wmclasscomplete);
0214     WRITE_MATCH_STRING(windowrole, Windowrole, false);
0215     WRITE_MATCH_STRING(title, Title, false);
0216     WRITE_MATCH_STRING(clientmachine, Clientmachine, false);
0217     settings->setTypes(types);
0218     WRITE_FORCE_RULE(placement, Placement, );
0219     WRITE_SET_RULE(position, Position, );
0220     WRITE_SET_RULE(size, Size, );
0221     WRITE_FORCE_RULE(minsize, Minsize, );
0222     WRITE_FORCE_RULE(maxsize, Maxsize, );
0223     WRITE_FORCE_RULE(opacityactive, Opacityactive, );
0224     WRITE_FORCE_RULE(opacityinactive, Opacityinactive, );
0225     WRITE_SET_RULE(ignoregeometry, Ignoregeometry, );
0226     WRITE_SET_RULE(desktops, Desktops, );
0227     WRITE_SET_RULE(screen, Screen, );
0228     WRITE_SET_RULE(activity, Activity, );
0229     WRITE_FORCE_RULE(type, Type, );
0230     WRITE_SET_RULE(maximizevert, Maximizevert, );
0231     WRITE_SET_RULE(maximizehoriz, Maximizehoriz, );
0232     WRITE_SET_RULE(minimize, Minimize, );
0233     WRITE_SET_RULE(shade, Shade, );
0234     WRITE_SET_RULE(skiptaskbar, Skiptaskbar, );
0235     WRITE_SET_RULE(skippager, Skippager, );
0236     WRITE_SET_RULE(skipswitcher, Skipswitcher, );
0237     WRITE_SET_RULE(above, Above, );
0238     WRITE_SET_RULE(below, Below, );
0239     WRITE_SET_RULE(fullscreen, Fullscreen, );
0240     WRITE_SET_RULE(noborder, Noborder, );
0241     auto colorToString = [](const QString &value) -> QString {
0242         if (value.endsWith(QLatin1String(".colors"))) {
0243             return QFileInfo(value).baseName();
0244         } else {
0245             return value;
0246         }
0247     };
0248     WRITE_FORCE_RULE(decocolor, Decocolor, colorToString);
0249     WRITE_FORCE_RULE(blockcompositing, Blockcompositing, );
0250     WRITE_FORCE_RULE(fsplevel, Fsplevel, );
0251     WRITE_FORCE_RULE(fpplevel, Fpplevel, );
0252     WRITE_FORCE_RULE(acceptfocus, Acceptfocus, );
0253     WRITE_FORCE_RULE(closeable, Closeable, );
0254     WRITE_FORCE_RULE(autogroup, Autogroup, );
0255     WRITE_FORCE_RULE(autogroupfg, Autogroupfg, );
0256     WRITE_FORCE_RULE(autogroupid, Autogroupid, );
0257     WRITE_FORCE_RULE(strictgeometry, Strictgeometry, );
0258     WRITE_SET_RULE(shortcut, Shortcut, );
0259     WRITE_FORCE_RULE(disableglobalshortcuts, Disableglobalshortcuts, );
0260     WRITE_SET_RULE(desktopfile, Desktopfile, );
0261 }
0262 
0263 #undef WRITE_MATCH_STRING
0264 #undef WRITE_SET_RULE
0265 #undef WRITE_FORCE_RULE
0266 
0267 // returns true if it doesn't affect anything
0268 bool Rules::isEmpty() const
0269 {
0270     return (placementrule == UnusedForceRule
0271             && positionrule == UnusedSetRule
0272             && sizerule == UnusedSetRule
0273             && minsizerule == UnusedForceRule
0274             && maxsizerule == UnusedForceRule
0275             && opacityactiverule == UnusedForceRule
0276             && opacityinactiverule == UnusedForceRule
0277             && ignoregeometryrule == UnusedSetRule
0278             && desktopsrule == UnusedSetRule
0279             && screenrule == UnusedSetRule
0280             && activityrule == UnusedSetRule
0281             && typerule == UnusedForceRule
0282             && maximizevertrule == UnusedSetRule
0283             && maximizehorizrule == UnusedSetRule
0284             && minimizerule == UnusedSetRule
0285             && shaderule == UnusedSetRule
0286             && skiptaskbarrule == UnusedSetRule
0287             && skippagerrule == UnusedSetRule
0288             && skipswitcherrule == UnusedSetRule
0289             && aboverule == UnusedSetRule
0290             && belowrule == UnusedSetRule
0291             && fullscreenrule == UnusedSetRule
0292             && noborderrule == UnusedSetRule
0293             && decocolorrule == UnusedForceRule
0294             && blockcompositingrule == UnusedForceRule
0295             && fsplevelrule == UnusedForceRule
0296             && fpplevelrule == UnusedForceRule
0297             && acceptfocusrule == UnusedForceRule
0298             && closeablerule == UnusedForceRule
0299             && autogrouprule == UnusedForceRule
0300             && autogroupfgrule == UnusedForceRule
0301             && autogroupidrule == UnusedForceRule
0302             && strictgeometryrule == UnusedForceRule
0303             && shortcutrule == UnusedSetRule
0304             && disableglobalshortcutsrule == UnusedForceRule
0305             && desktopfilerule == UnusedSetRule);
0306 }
0307 
0308 Rules::ForceRule Rules::convertForceRule(int v)
0309 {
0310     if (v == DontAffect || v == Force || v == ForceTemporarily) {
0311         return static_cast<ForceRule>(v);
0312     }
0313     return UnusedForceRule;
0314 }
0315 
0316 QString Rules::getDecoColor(const QString &themeName)
0317 {
0318     if (themeName.isEmpty()) {
0319         return QString();
0320     }
0321     // find the actual scheme file
0322     return QStandardPaths::locate(QStandardPaths::GenericDataLocation,
0323                                   QLatin1String("color-schemes/") + themeName + QLatin1String(".colors"));
0324 }
0325 
0326 bool Rules::matchType(NET::WindowType match_type) const
0327 {
0328     if (types != NET::AllTypesMask) {
0329         if (match_type == NET::Unknown) {
0330             match_type = NET::Normal; // NET::Unknown->NET::Normal is only here for matching
0331         }
0332         if (!NET::typeMatchesMask(match_type, types)) {
0333             return false;
0334         }
0335     }
0336     return true;
0337 }
0338 
0339 bool Rules::matchWMClass(const QString &match_class, const QString &match_name) const
0340 {
0341     if (wmclassmatch != UnimportantMatch) {
0342         // TODO optimize?
0343         QString cwmclass = wmclasscomplete
0344             ? match_name + ' ' + match_class
0345             : match_class;
0346         if (wmclassmatch == RegExpMatch && !QRegularExpression(wmclass).match(cwmclass).hasMatch()) {
0347             return false;
0348         }
0349         if (wmclassmatch == ExactMatch && cwmclass.compare(wmclass, Qt::CaseInsensitive) != 0) { // TODO Plasma 6: Make it case sensitive
0350             return false;
0351         }
0352         if (wmclassmatch == SubstringMatch && !cwmclass.contains(wmclass, Qt::CaseInsensitive)) { // TODO Plasma 6: Make it case sensitive
0353             return false;
0354         }
0355     }
0356     return true;
0357 }
0358 
0359 bool Rules::matchRole(const QString &match_role) const
0360 {
0361     if (windowrolematch != UnimportantMatch) {
0362         if (windowrolematch == RegExpMatch && !QRegularExpression(windowrole).match(match_role).hasMatch()) {
0363             return false;
0364         }
0365         if (windowrolematch == ExactMatch && match_role.compare(windowrole, Qt::CaseInsensitive) != 0) { // TODO Plasma 6: Make it case sensitive
0366             return false;
0367         }
0368         if (windowrolematch == SubstringMatch && !match_role.contains(windowrole, Qt::CaseInsensitive)) { // TODO Plasma 6: Make it case sensitive
0369             return false;
0370         }
0371     }
0372     return true;
0373 }
0374 
0375 bool Rules::matchTitle(const QString &match_title) const
0376 {
0377     if (titlematch != UnimportantMatch) {
0378         if (titlematch == RegExpMatch && !QRegularExpression(title).match(match_title).hasMatch()) {
0379             return false;
0380         }
0381         if (titlematch == ExactMatch && title != match_title) {
0382             return false;
0383         }
0384         if (titlematch == SubstringMatch && !match_title.contains(title)) {
0385             return false;
0386         }
0387     }
0388     return true;
0389 }
0390 
0391 bool Rules::matchClientMachine(const QString &match_machine, bool local) const
0392 {
0393     if (clientmachinematch != UnimportantMatch) {
0394         // if it's localhost, check also "localhost" before checking hostname
0395         if (match_machine != "localhost" && local
0396             && matchClientMachine("localhost", true)) {
0397             return true;
0398         }
0399         if (clientmachinematch == RegExpMatch
0400             && !QRegularExpression(clientmachine).match(match_machine).hasMatch()) {
0401             return false;
0402         }
0403         if (clientmachinematch == ExactMatch
0404             && clientmachine != match_machine) {
0405             return false;
0406         }
0407         if (clientmachinematch == SubstringMatch
0408             && !match_machine.contains(clientmachine)) {
0409             return false;
0410         }
0411     }
0412     return true;
0413 }
0414 
0415 #ifndef KCMRULES
0416 bool Rules::match(const Window *c) const
0417 {
0418     if (!matchType(c->windowType(true))) {
0419         return false;
0420     }
0421     if (!matchWMClass(c->resourceClass(), c->resourceName())) {
0422         return false;
0423     }
0424     if (!matchRole(c->windowRole())) {
0425         return false;
0426     }
0427     if (!matchClientMachine(c->clientMachine()->hostName(), c->clientMachine()->isLocal())) {
0428         return false;
0429     }
0430     if (titlematch != UnimportantMatch) { // track title changes to rematch rules
0431         QObject::connect(c, &Window::captionChanged, c, &Window::evaluateWindowRules,
0432                          // QueuedConnection, because title may change before
0433                          // the client is ready (could segfault!)
0434                          static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection));
0435     }
0436     if (!matchTitle(c->captionNormal())) {
0437         return false;
0438     }
0439     return true;
0440 }
0441 
0442 #define NOW_REMEMBER(_T_, _V_) ((selection & _T_) && (_V_##rule == (SetRule)Remember))
0443 
0444 bool Rules::update(Window *c, int selection)
0445 {
0446     // TODO check this setting is for this client ?
0447     bool updated = false;
0448     if NOW_REMEMBER (Position, position) {
0449         if (!c->isFullScreen()) {
0450             QPoint new_pos = position;
0451             // don't use the position in the direction which is maximized
0452             if ((c->maximizeMode() & MaximizeHorizontal) == 0) {
0453                 new_pos.setX(c->pos().x());
0454             }
0455             if ((c->maximizeMode() & MaximizeVertical) == 0) {
0456                 new_pos.setY(c->pos().y());
0457             }
0458             updated = updated || position != new_pos;
0459             position = new_pos;
0460         }
0461     }
0462     if NOW_REMEMBER (Size, size) {
0463         if (!c->isFullScreen()) {
0464             QSize new_size = size;
0465             // don't use the position in the direction which is maximized
0466             if ((c->maximizeMode() & MaximizeHorizontal) == 0) {
0467                 new_size.setWidth(c->size().width());
0468             }
0469             if ((c->maximizeMode() & MaximizeVertical) == 0) {
0470                 new_size.setHeight(c->size().height());
0471             }
0472             updated = updated || size != new_size;
0473             size = new_size;
0474         }
0475     }
0476     if NOW_REMEMBER (Desktops, desktops) {
0477         updated = updated || desktops != c->desktopIds();
0478         desktops = c->desktopIds();
0479     }
0480     if NOW_REMEMBER (Screen, screen) {
0481         updated = updated || screen != c->screen();
0482         screen = c->screen();
0483     }
0484     if NOW_REMEMBER (Activity, activity) {
0485         updated = updated || activity != c->activities();
0486         activity = c->activities();
0487     }
0488     if NOW_REMEMBER (MaximizeVert, maximizevert) {
0489         updated = updated || maximizevert != bool(c->maximizeMode() & MaximizeVertical);
0490         maximizevert = c->maximizeMode() & MaximizeVertical;
0491     }
0492     if NOW_REMEMBER (MaximizeHoriz, maximizehoriz) {
0493         updated = updated || maximizehoriz != bool(c->maximizeMode() & MaximizeHorizontal);
0494         maximizehoriz = c->maximizeMode() & MaximizeHorizontal;
0495     }
0496     if NOW_REMEMBER (Minimize, minimize) {
0497         updated = updated || minimize != c->isMinimized();
0498         minimize = c->isMinimized();
0499     }
0500     if NOW_REMEMBER (Shade, shade) {
0501         updated = updated || (shade != (c->shadeMode() != ShadeNone));
0502         shade = c->shadeMode() != ShadeNone;
0503     }
0504     if NOW_REMEMBER (SkipTaskbar, skiptaskbar) {
0505         updated = updated || skiptaskbar != c->skipTaskbar();
0506         skiptaskbar = c->skipTaskbar();
0507     }
0508     if NOW_REMEMBER (SkipPager, skippager) {
0509         updated = updated || skippager != c->skipPager();
0510         skippager = c->skipPager();
0511     }
0512     if NOW_REMEMBER (SkipSwitcher, skipswitcher) {
0513         updated = updated || skipswitcher != c->skipSwitcher();
0514         skipswitcher = c->skipSwitcher();
0515     }
0516     if NOW_REMEMBER (Above, above) {
0517         updated = updated || above != c->keepAbove();
0518         above = c->keepAbove();
0519     }
0520     if NOW_REMEMBER (Below, below) {
0521         updated = updated || below != c->keepBelow();
0522         below = c->keepBelow();
0523     }
0524     if NOW_REMEMBER (Fullscreen, fullscreen) {
0525         updated = updated || fullscreen != c->isFullScreen();
0526         fullscreen = c->isFullScreen();
0527     }
0528     if NOW_REMEMBER (NoBorder, noborder) {
0529         updated = updated || noborder != c->noBorder();
0530         noborder = c->noBorder();
0531     }
0532     if NOW_REMEMBER (DesktopFile, desktopfile) {
0533         updated = updated || desktopfile != c->desktopFileName();
0534         desktopfile = c->desktopFileName();
0535     }
0536     return updated;
0537 }
0538 
0539 #undef NOW_REMEMBER
0540 
0541 #define APPLY_RULE(var, name, type)                     \
0542     bool Rules::apply##name(type &arg, bool init) const \
0543     {                                                   \
0544         if (checkSetRule(var##rule, init))              \
0545             arg = this->var;                            \
0546         return checkSetStop(var##rule);                 \
0547     }
0548 
0549 #define APPLY_FORCE_RULE(var, name, type)    \
0550     bool Rules::apply##name(type &arg) const \
0551     {                                        \
0552         if (checkForceRule(var##rule))       \
0553             arg = this->var;                 \
0554         return checkForceStop(var##rule);    \
0555     }
0556 
0557 APPLY_FORCE_RULE(placement, Placement, PlacementPolicy)
0558 
0559 bool Rules::applyGeometry(QRectF &rect, bool init) const
0560 {
0561     QPointF p = rect.topLeft();
0562     QSizeF s = rect.size();
0563     bool ret = false; // no short-circuiting
0564     if (applyPosition(p, init)) {
0565         rect.moveTopLeft(p);
0566         ret = true;
0567     }
0568     if (applySize(s, init)) {
0569         rect.setSize(s);
0570         ret = true;
0571     }
0572     return ret;
0573 }
0574 
0575 bool Rules::applyPosition(QPointF &pos, bool init) const
0576 {
0577     if (this->position != invalidPoint && checkSetRule(positionrule, init)) {
0578         pos = this->position;
0579     }
0580     return checkSetStop(positionrule);
0581 }
0582 
0583 bool Rules::applySize(QSizeF &s, bool init) const
0584 {
0585     if (this->size.isValid() && checkSetRule(sizerule, init)) {
0586         s = this->size;
0587     }
0588     return checkSetStop(sizerule);
0589 }
0590 
0591 APPLY_FORCE_RULE(minsize, MinSize, QSizeF)
0592 APPLY_FORCE_RULE(maxsize, MaxSize, QSizeF)
0593 APPLY_FORCE_RULE(opacityactive, OpacityActive, int)
0594 APPLY_FORCE_RULE(opacityinactive, OpacityInactive, int)
0595 APPLY_RULE(ignoregeometry, IgnoreGeometry, bool)
0596 
0597 APPLY_RULE(screen, Screen, int)
0598 APPLY_RULE(activity, Activity, QStringList)
0599 APPLY_FORCE_RULE(type, Type, NET::WindowType)
0600 
0601 bool Rules::applyDesktops(QVector<VirtualDesktop *> &vds, bool init) const
0602 {
0603     if (checkSetRule(desktopsrule, init)) {
0604         vds.clear();
0605         for (auto id : desktops) {
0606             if (auto vd = VirtualDesktopManager::self()->desktopForId(id)) {
0607                 vds << vd;
0608             }
0609         }
0610     }
0611     return checkSetStop(desktopsrule);
0612 }
0613 
0614 bool Rules::applyMaximizeHoriz(MaximizeMode &mode, bool init) const
0615 {
0616     if (checkSetRule(maximizehorizrule, init)) {
0617         mode = static_cast<MaximizeMode>((maximizehoriz ? MaximizeHorizontal : 0) | (mode & MaximizeVertical));
0618     }
0619     return checkSetStop(maximizehorizrule);
0620 }
0621 
0622 bool Rules::applyMaximizeVert(MaximizeMode &mode, bool init) const
0623 {
0624     if (checkSetRule(maximizevertrule, init)) {
0625         mode = static_cast<MaximizeMode>((maximizevert ? MaximizeVertical : 0) | (mode & MaximizeHorizontal));
0626     }
0627     return checkSetStop(maximizevertrule);
0628 }
0629 
0630 APPLY_RULE(minimize, Minimize, bool)
0631 
0632 bool Rules::applyShade(ShadeMode &sh, bool init) const
0633 {
0634     if (checkSetRule(shaderule, init)) {
0635         if (!this->shade) {
0636             sh = ShadeNone;
0637         }
0638         if (this->shade && sh == ShadeNone) {
0639             sh = ShadeNormal;
0640         }
0641     }
0642     return checkSetStop(shaderule);
0643 }
0644 
0645 APPLY_RULE(skiptaskbar, SkipTaskbar, bool)
0646 APPLY_RULE(skippager, SkipPager, bool)
0647 APPLY_RULE(skipswitcher, SkipSwitcher, bool)
0648 APPLY_RULE(above, KeepAbove, bool)
0649 APPLY_RULE(below, KeepBelow, bool)
0650 APPLY_RULE(fullscreen, FullScreen, bool)
0651 APPLY_RULE(noborder, NoBorder, bool)
0652 APPLY_FORCE_RULE(decocolor, DecoColor, QString)
0653 APPLY_FORCE_RULE(blockcompositing, BlockCompositing, bool)
0654 APPLY_FORCE_RULE(fsplevel, FSP, int)
0655 APPLY_FORCE_RULE(fpplevel, FPP, int)
0656 APPLY_FORCE_RULE(acceptfocus, AcceptFocus, bool)
0657 APPLY_FORCE_RULE(closeable, Closeable, bool)
0658 APPLY_FORCE_RULE(autogroup, Autogrouping, bool)
0659 APPLY_FORCE_RULE(autogroupfg, AutogroupInForeground, bool)
0660 APPLY_FORCE_RULE(autogroupid, AutogroupById, QString)
0661 APPLY_FORCE_RULE(strictgeometry, StrictGeometry, bool)
0662 APPLY_RULE(shortcut, Shortcut, QString)
0663 APPLY_FORCE_RULE(disableglobalshortcuts, DisableGlobalShortcuts, bool)
0664 APPLY_RULE(desktopfile, DesktopFile, QString)
0665 
0666 #undef APPLY_RULE
0667 #undef APPLY_FORCE_RULE
0668 
0669 bool Rules::isTemporary() const
0670 {
0671     return temporary_state > 0;
0672 }
0673 
0674 bool Rules::discardTemporary(bool force)
0675 {
0676     if (temporary_state == 0) { // not temporary
0677         return false;
0678     }
0679     if (force || --temporary_state == 0) { // too old
0680         delete this;
0681         return true;
0682     }
0683     return false;
0684 }
0685 
0686 #define DISCARD_USED_SET_RULE(var)                                                                     \
0687     do {                                                                                               \
0688         if (var##rule == (SetRule)ApplyNow || (withdrawn && var##rule == (SetRule)ForceTemporarily)) { \
0689             var##rule = UnusedSetRule;                                                                 \
0690             changed = true;                                                                            \
0691         }                                                                                              \
0692     } while (false)
0693 #define DISCARD_USED_FORCE_RULE(var)                                 \
0694     do {                                                             \
0695         if (withdrawn && var##rule == (ForceRule)ForceTemporarily) { \
0696             var##rule = UnusedForceRule;                             \
0697             changed = true;                                          \
0698         }                                                            \
0699     } while (false)
0700 
0701 bool Rules::discardUsed(bool withdrawn)
0702 {
0703     bool changed = false;
0704     DISCARD_USED_FORCE_RULE(placement);
0705     DISCARD_USED_SET_RULE(position);
0706     DISCARD_USED_SET_RULE(size);
0707     DISCARD_USED_FORCE_RULE(minsize);
0708     DISCARD_USED_FORCE_RULE(maxsize);
0709     DISCARD_USED_FORCE_RULE(opacityactive);
0710     DISCARD_USED_FORCE_RULE(opacityinactive);
0711     DISCARD_USED_SET_RULE(ignoregeometry);
0712     DISCARD_USED_SET_RULE(desktops);
0713     DISCARD_USED_SET_RULE(screen);
0714     DISCARD_USED_SET_RULE(activity);
0715     DISCARD_USED_FORCE_RULE(type);
0716     DISCARD_USED_SET_RULE(maximizevert);
0717     DISCARD_USED_SET_RULE(maximizehoriz);
0718     DISCARD_USED_SET_RULE(minimize);
0719     DISCARD_USED_SET_RULE(shade);
0720     DISCARD_USED_SET_RULE(skiptaskbar);
0721     DISCARD_USED_SET_RULE(skippager);
0722     DISCARD_USED_SET_RULE(skipswitcher);
0723     DISCARD_USED_SET_RULE(above);
0724     DISCARD_USED_SET_RULE(below);
0725     DISCARD_USED_SET_RULE(fullscreen);
0726     DISCARD_USED_SET_RULE(noborder);
0727     DISCARD_USED_FORCE_RULE(decocolor);
0728     DISCARD_USED_FORCE_RULE(blockcompositing);
0729     DISCARD_USED_FORCE_RULE(fsplevel);
0730     DISCARD_USED_FORCE_RULE(fpplevel);
0731     DISCARD_USED_FORCE_RULE(acceptfocus);
0732     DISCARD_USED_FORCE_RULE(closeable);
0733     DISCARD_USED_FORCE_RULE(autogroup);
0734     DISCARD_USED_FORCE_RULE(autogroupfg);
0735     DISCARD_USED_FORCE_RULE(autogroupid);
0736     DISCARD_USED_FORCE_RULE(strictgeometry);
0737     DISCARD_USED_SET_RULE(shortcut);
0738     DISCARD_USED_FORCE_RULE(disableglobalshortcuts);
0739     DISCARD_USED_SET_RULE(desktopfile);
0740 
0741     return changed;
0742 }
0743 #undef DISCARD_USED_SET_RULE
0744 #undef DISCARD_USED_FORCE_RULE
0745 
0746 #endif
0747 
0748 QDebug &operator<<(QDebug &stream, const Rules *r)
0749 {
0750     return stream << "[" << r->description << ":" << r->wmclass << "]";
0751 }
0752 
0753 #ifndef KCMRULES
0754 void WindowRules::discardTemporary()
0755 {
0756     QVector<Rules *>::Iterator it2 = rules.begin();
0757     for (QVector<Rules *>::Iterator it = rules.begin();
0758          it != rules.end();) {
0759         if ((*it)->discardTemporary(true)) {
0760             ++it;
0761         } else {
0762             *it2++ = *it++;
0763         }
0764     }
0765     rules.erase(it2, rules.end());
0766 }
0767 
0768 void WindowRules::update(Window *c, int selection)
0769 {
0770     bool updated = false;
0771     for (QVector<Rules *>::ConstIterator it = rules.constBegin();
0772          it != rules.constEnd();
0773          ++it) {
0774         if ((*it)->update(c, selection)) { // no short-circuiting here
0775             updated = true;
0776         }
0777     }
0778     if (updated) {
0779         workspace()->rulebook()->requestDiskStorage();
0780     }
0781 }
0782 
0783 #define CHECK_RULE(rule, type)                                        \
0784     type WindowRules::check##rule(type arg, bool init) const          \
0785     {                                                                 \
0786         if (rules.count() == 0)                                       \
0787             return arg;                                               \
0788         type ret = arg;                                               \
0789         for (QVector<Rules *>::ConstIterator it = rules.constBegin(); \
0790              it != rules.constEnd();                                  \
0791              ++it) {                                                  \
0792             if ((*it)->apply##rule(ret, init))                        \
0793                 break;                                                \
0794         }                                                             \
0795         return ret;                                                   \
0796     }
0797 
0798 #define CHECK_FORCE_RULE(rule, type)                             \
0799     type WindowRules::check##rule(type arg) const                \
0800     {                                                            \
0801         if (rules.count() == 0)                                  \
0802             return arg;                                          \
0803         type ret = arg;                                          \
0804         for (QVector<Rules *>::ConstIterator it = rules.begin(); \
0805              it != rules.end();                                  \
0806              ++it) {                                             \
0807             if ((*it)->apply##rule(ret))                         \
0808                 break;                                           \
0809         }                                                        \
0810         return ret;                                              \
0811     }
0812 
0813 CHECK_FORCE_RULE(Placement, PlacementPolicy)
0814 
0815 QRectF WindowRules::checkGeometry(QRectF rect, bool init) const
0816 {
0817     return QRectF(checkPosition(rect.topLeft(), init), checkSize(rect.size(), init));
0818 }
0819 
0820 QRectF WindowRules::checkGeometrySafe(QRectF rect, bool init) const
0821 {
0822     return QRectF(checkPositionSafe(rect.topLeft(), init), checkSize(rect.size(), init));
0823 }
0824 
0825 QPointF WindowRules::checkPositionSafe(QPointF pos, bool init) const
0826 {
0827     const auto ret = checkPosition(pos, init);
0828     if (ret == pos || ret == invalidPoint) {
0829         return ret;
0830     }
0831     const auto outputs = workspace()->outputs();
0832     const bool inAnyOutput = std::any_of(outputs.begin(), outputs.end(), [ret](const auto output) {
0833         return output->fractionalGeometry().contains(ret);
0834     });
0835     if (inAnyOutput) {
0836         return ret;
0837     } else {
0838         return invalidPoint;
0839     }
0840 }
0841 
0842 CHECK_RULE(Position, QPointF)
0843 CHECK_RULE(Size, QSizeF)
0844 CHECK_FORCE_RULE(MinSize, QSizeF)
0845 CHECK_FORCE_RULE(MaxSize, QSizeF)
0846 CHECK_FORCE_RULE(OpacityActive, int)
0847 CHECK_FORCE_RULE(OpacityInactive, int)
0848 CHECK_RULE(IgnoreGeometry, bool)
0849 
0850 CHECK_RULE(Desktops, QVector<VirtualDesktop *>)
0851 CHECK_RULE(Activity, QStringList)
0852 CHECK_FORCE_RULE(Type, NET::WindowType)
0853 CHECK_RULE(MaximizeVert, MaximizeMode)
0854 CHECK_RULE(MaximizeHoriz, MaximizeMode)
0855 
0856 MaximizeMode WindowRules::checkMaximize(MaximizeMode mode, bool init) const
0857 {
0858     bool vert = checkMaximizeVert(mode, init) & MaximizeVertical;
0859     bool horiz = checkMaximizeHoriz(mode, init) & MaximizeHorizontal;
0860     return static_cast<MaximizeMode>((vert ? MaximizeVertical : 0) | (horiz ? MaximizeHorizontal : 0));
0861 }
0862 
0863 Output *WindowRules::checkOutput(Output *output, bool init) const
0864 {
0865     if (rules.isEmpty()) {
0866         return output;
0867     }
0868     int ret = workspace()->outputs().indexOf(output);
0869     for (Rules *rule : rules) {
0870         if (rule->applyScreen(ret, init)) {
0871             break;
0872         }
0873     }
0874     Output *ruleOutput = workspace()->outputs().value(ret);
0875     return ruleOutput ? ruleOutput : output;
0876 }
0877 
0878 CHECK_RULE(Minimize, bool)
0879 CHECK_RULE(Shade, ShadeMode)
0880 CHECK_RULE(SkipTaskbar, bool)
0881 CHECK_RULE(SkipPager, bool)
0882 CHECK_RULE(SkipSwitcher, bool)
0883 CHECK_RULE(KeepAbove, bool)
0884 CHECK_RULE(KeepBelow, bool)
0885 CHECK_RULE(FullScreen, bool)
0886 CHECK_RULE(NoBorder, bool)
0887 CHECK_FORCE_RULE(DecoColor, QString)
0888 CHECK_FORCE_RULE(BlockCompositing, bool)
0889 CHECK_FORCE_RULE(FSP, int)
0890 CHECK_FORCE_RULE(FPP, int)
0891 CHECK_FORCE_RULE(AcceptFocus, bool)
0892 CHECK_FORCE_RULE(Closeable, bool)
0893 CHECK_FORCE_RULE(Autogrouping, bool)
0894 CHECK_FORCE_RULE(AutogroupInForeground, bool)
0895 CHECK_FORCE_RULE(AutogroupById, QString)
0896 CHECK_FORCE_RULE(StrictGeometry, bool)
0897 CHECK_RULE(Shortcut, QString)
0898 CHECK_FORCE_RULE(DisableGlobalShortcuts, bool)
0899 CHECK_RULE(DesktopFile, QString)
0900 
0901 #undef CHECK_RULE
0902 #undef CHECK_FORCE_RULE
0903 
0904 RuleBook::RuleBook()
0905     : m_updateTimer(new QTimer(this))
0906     , m_updatesDisabled(false)
0907     , m_temporaryRulesMessages()
0908 {
0909     initializeX11();
0910     connect(kwinApp(), &Application::x11ConnectionChanged, this, &RuleBook::initializeX11);
0911     connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed, this, &RuleBook::cleanupX11);
0912     connect(m_updateTimer, &QTimer::timeout, this, &RuleBook::save);
0913     m_updateTimer->setInterval(1000);
0914     m_updateTimer->setSingleShot(true);
0915 }
0916 
0917 RuleBook::~RuleBook()
0918 {
0919     save();
0920     deleteAll();
0921 }
0922 
0923 void RuleBook::initializeX11()
0924 {
0925     auto c = kwinApp()->x11Connection();
0926     if (!c) {
0927         return;
0928     }
0929     m_temporaryRulesMessages.reset(new KXMessages(c, kwinApp()->x11RootWindow(), "_KDE_NET_WM_TEMPORARY_RULES", nullptr));
0930     connect(m_temporaryRulesMessages.get(), &KXMessages::gotMessage, this, &RuleBook::temporaryRulesMessage);
0931 }
0932 
0933 void RuleBook::cleanupX11()
0934 {
0935     m_temporaryRulesMessages.reset();
0936 }
0937 
0938 void RuleBook::deleteAll()
0939 {
0940     qDeleteAll(m_rules);
0941     m_rules.clear();
0942 }
0943 
0944 WindowRules RuleBook::find(const Window *c, bool ignore_temporary)
0945 {
0946     QVector<Rules *> ret;
0947     for (QList<Rules *>::Iterator it = m_rules.begin();
0948          it != m_rules.end();) {
0949         if (ignore_temporary && (*it)->isTemporary()) {
0950             ++it;
0951             continue;
0952         }
0953         if ((*it)->match(c)) {
0954             Rules *rule = *it;
0955             qCDebug(KWIN_CORE) << "Rule found:" << rule << ":" << c;
0956             if (rule->isTemporary()) {
0957                 it = m_rules.erase(it);
0958             } else {
0959                 ++it;
0960             }
0961             ret.append(rule);
0962             continue;
0963         }
0964         ++it;
0965     }
0966     return WindowRules(ret);
0967 }
0968 
0969 void RuleBook::edit(Window *c, bool whole_app)
0970 {
0971     save();
0972     QStringList args;
0973     args << QStringLiteral("--uuid") << c->internalId().toString();
0974     if (whole_app) {
0975         args << QStringLiteral("--whole-app");
0976     }
0977     QProcess *p = new QProcess(this);
0978     p->setArguments(args);
0979     p->setProcessEnvironment(kwinApp()->processStartupEnvironment());
0980     const QFileInfo buildDirBinary{QDir{QCoreApplication::applicationDirPath()}, QStringLiteral("kwin_rules_dialog")};
0981     p->setProgram(buildDirBinary.exists() ? buildDirBinary.absoluteFilePath() : QStringLiteral(KWIN_RULES_DIALOG_BIN));
0982     p->setProcessChannelMode(QProcess::MergedChannels);
0983     connect(p, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), p, &QProcess::deleteLater);
0984     connect(p, &QProcess::errorOccurred, this, [p](QProcess::ProcessError e) {
0985         if (e == QProcess::FailedToStart) {
0986             qCDebug(KWIN_CORE) << "Failed to start" << p->program();
0987         }
0988     });
0989     p->start();
0990 }
0991 
0992 void RuleBook::load()
0993 {
0994     deleteAll();
0995     if (!m_config) {
0996         m_config = KSharedConfig::openConfig(QStringLiteral("kwinrulesrc"), KConfig::NoGlobals);
0997     } else {
0998         m_config->reparseConfiguration();
0999     }
1000     RuleBookSettings book(m_config);
1001     book.load();
1002     m_rules = book.rules().toList();
1003 }
1004 
1005 void RuleBook::save()
1006 {
1007     m_updateTimer->stop();
1008     if (!m_config) {
1009         qCWarning(KWIN_CORE) << "RuleBook::save invoked without prior invocation of RuleBook::load";
1010         return;
1011     }
1012     QVector<Rules *> filteredRules;
1013     for (const auto &rule : std::as_const(m_rules)) {
1014         if (!rule->isTemporary()) {
1015             filteredRules.append(rule);
1016         }
1017     }
1018     RuleBookSettings settings(m_config);
1019     settings.setRules(filteredRules);
1020     settings.save();
1021 }
1022 
1023 void RuleBook::temporaryRulesMessage(const QString &message)
1024 {
1025     bool was_temporary = false;
1026     for (QList<Rules *>::ConstIterator it = m_rules.constBegin();
1027          it != m_rules.constEnd();
1028          ++it) {
1029         if ((*it)->isTemporary()) {
1030             was_temporary = true;
1031         }
1032     }
1033     Rules *rule = new Rules(message, true);
1034     m_rules.prepend(rule); // highest priority first
1035     if (!was_temporary) {
1036         QTimer::singleShot(60000, this, &RuleBook::cleanupTemporaryRules);
1037     }
1038 }
1039 
1040 void RuleBook::cleanupTemporaryRules()
1041 {
1042     bool has_temporary = false;
1043     for (QList<Rules *>::Iterator it = m_rules.begin();
1044          it != m_rules.end();) {
1045         if ((*it)->discardTemporary(false)) { // deletes (*it)
1046             it = m_rules.erase(it);
1047         } else {
1048             if ((*it)->isTemporary()) {
1049                 has_temporary = true;
1050             }
1051             ++it;
1052         }
1053     }
1054     if (has_temporary) {
1055         QTimer::singleShot(60000, this, &RuleBook::cleanupTemporaryRules);
1056     }
1057 }
1058 
1059 void RuleBook::discardUsed(Window *c, bool withdrawn)
1060 {
1061     bool updated = false;
1062     for (QList<Rules *>::Iterator it = m_rules.begin();
1063          it != m_rules.end();) {
1064         if (c->rules()->contains(*it)) {
1065             if ((*it)->discardUsed(withdrawn)) {
1066                 updated = true;
1067             }
1068             if ((*it)->isEmpty()) {
1069                 c->removeRule(*it);
1070                 Rules *r = *it;
1071                 it = m_rules.erase(it);
1072                 delete r;
1073                 continue;
1074             }
1075         }
1076         ++it;
1077     }
1078     if (updated) {
1079         requestDiskStorage();
1080     }
1081 }
1082 
1083 void RuleBook::requestDiskStorage()
1084 {
1085     m_updateTimer->start();
1086 }
1087 
1088 void RuleBook::setUpdatesDisabled(bool disable)
1089 {
1090     m_updatesDisabled = disable;
1091     if (!disable) {
1092         const auto clients = Workspace::self()->allClientList();
1093         for (Window *c : clients) {
1094             c->updateWindowRules(Rules::All);
1095         }
1096     }
1097 }
1098 
1099 #endif
1100 
1101 } // namespace