File indexing completed on 2025-02-16 11:23:19
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