File indexing completed on 2026-07-19 13:01:35
0001 /* 0002 SPDX-FileCopyrightText: 2004 Lubos Lunak <l.lunak@kde.org> 0003 SPDX-FileCopyrightText: 2020 Ismael Asensio <isma.af@gmail.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 #include "rulesmodel.h" 0009 0010 #if KWIN_BUILD_ACTIVITIES 0011 #include "activities.h" 0012 #endif 0013 0014 #include <QIcon> 0015 #include <QQmlEngine> 0016 #include <QtDBus> 0017 0018 #include <KColorSchemeManager> 0019 #include <KConfig> 0020 #include <KLocalizedString> 0021 #include <KWindowSystem> 0022 0023 namespace KWin 0024 { 0025 0026 RulesModel::RulesModel(QObject *parent) 0027 : QAbstractListModel(parent) 0028 { 0029 qmlRegisterUncreatableType<RuleItem>("org.kde.kcms.kwinrules", 1, 0, "RuleItem", 0030 QStringLiteral("Do not create objects of type RuleItem")); 0031 qmlRegisterUncreatableType<RulesModel>("org.kde.kcms.kwinrules", 1, 0, "RulesModel", 0032 QStringLiteral("Do not create objects of type RulesModel")); 0033 qmlRegisterUncreatableType<OptionsModel>("org.kde.kcms.kwinrules", 1, 0, "OptionsModel", 0034 QStringLiteral("Do not create objects of type OptionsModel")); 0035 0036 qDBusRegisterMetaType<KWin::DBusDesktopDataStruct>(); 0037 qDBusRegisterMetaType<KWin::DBusDesktopDataVector>(); 0038 0039 populateRuleList(); 0040 } 0041 0042 RulesModel::~RulesModel() 0043 { 0044 } 0045 0046 QHash<int, QByteArray> RulesModel::roleNames() const 0047 { 0048 return { 0049 {KeyRole, QByteArrayLiteral("key")}, 0050 {NameRole, QByteArrayLiteral("name")}, 0051 {IconRole, QByteArrayLiteral("icon")}, 0052 {IconNameRole, QByteArrayLiteral("iconName")}, 0053 {SectionRole, QByteArrayLiteral("section")}, 0054 {DescriptionRole, QByteArrayLiteral("description")}, 0055 {EnabledRole, QByteArrayLiteral("enabled")}, 0056 {SelectableRole, QByteArrayLiteral("selectable")}, 0057 {ValueRole, QByteArrayLiteral("value")}, 0058 {TypeRole, QByteArrayLiteral("type")}, 0059 {PolicyRole, QByteArrayLiteral("policy")}, 0060 {PolicyModelRole, QByteArrayLiteral("policyModel")}, 0061 {OptionsModelRole, QByteArrayLiteral("options")}, 0062 {SuggestedValueRole, QByteArrayLiteral("suggested")}, 0063 }; 0064 } 0065 0066 int RulesModel::rowCount(const QModelIndex &parent) const 0067 { 0068 if (parent.isValid()) { 0069 return 0; 0070 } 0071 return m_ruleList.size(); 0072 } 0073 0074 QVariant RulesModel::data(const QModelIndex &index, int role) const 0075 { 0076 if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid)) { 0077 return QVariant(); 0078 } 0079 0080 const RuleItem *rule = m_ruleList.at(index.row()); 0081 0082 switch (role) { 0083 case KeyRole: 0084 return rule->key(); 0085 case NameRole: 0086 return rule->name(); 0087 case IconRole: 0088 return rule->icon(); 0089 case IconNameRole: 0090 return rule->iconName(); 0091 case DescriptionRole: 0092 return rule->description(); 0093 case SectionRole: 0094 return rule->section(); 0095 case EnabledRole: 0096 return rule->isEnabled(); 0097 case SelectableRole: 0098 return !rule->hasFlag(RuleItem::AlwaysEnabled) && !rule->hasFlag(RuleItem::SuggestionOnly); 0099 case ValueRole: 0100 return rule->value(); 0101 case TypeRole: 0102 return rule->type(); 0103 case PolicyRole: 0104 return rule->policy(); 0105 case PolicyModelRole: 0106 return rule->policyModel(); 0107 case OptionsModelRole: 0108 return rule->options(); 0109 case SuggestedValueRole: 0110 return rule->suggestedValue(); 0111 } 0112 return QVariant(); 0113 } 0114 0115 bool RulesModel::setData(const QModelIndex &index, const QVariant &value, int role) 0116 { 0117 if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid)) { 0118 return false; 0119 } 0120 0121 RuleItem *rule = m_ruleList.at(index.row()); 0122 0123 switch (role) { 0124 case EnabledRole: 0125 if (value.toBool() == rule->isEnabled()) { 0126 return true; 0127 } 0128 rule->setEnabled(value.toBool()); 0129 break; 0130 case ValueRole: 0131 if (rule->hasFlag(RuleItem::SuggestionOnly)) { 0132 processSuggestion(rule->key(), value); 0133 } 0134 if (value == rule->value()) { 0135 return true; 0136 } 0137 rule->setValue(value); 0138 break; 0139 case PolicyRole: 0140 if (value.toInt() == rule->policy()) { 0141 return true; 0142 } 0143 rule->setPolicy(value.toInt()); 0144 break; 0145 case SuggestedValueRole: 0146 if (value == rule->suggestedValue()) { 0147 return true; 0148 } 0149 rule->setSuggestedValue(value); 0150 break; 0151 default: 0152 return false; 0153 } 0154 0155 writeToSettings(rule); 0156 0157 Q_EMIT dataChanged(index, index, QVector<int>{role}); 0158 if (rule->hasFlag(RuleItem::AffectsDescription)) { 0159 Q_EMIT descriptionChanged(); 0160 } 0161 if (rule->hasFlag(RuleItem::AffectsWarning)) { 0162 Q_EMIT warningMessagesChanged(); 0163 } 0164 0165 return true; 0166 } 0167 0168 QModelIndex RulesModel::indexOf(const QString &key) const 0169 { 0170 const QModelIndexList indexes = match(index(0), RulesModel::KeyRole, key, 1, Qt::MatchFixedString); 0171 if (indexes.isEmpty()) { 0172 return QModelIndex(); 0173 } 0174 return indexes.at(0); 0175 } 0176 0177 RuleItem *RulesModel::addRule(RuleItem *rule) 0178 { 0179 m_ruleList << rule; 0180 m_rules.insert(rule->key(), rule); 0181 0182 return rule; 0183 } 0184 0185 bool RulesModel::hasRule(const QString &key) const 0186 { 0187 return m_rules.contains(key); 0188 } 0189 0190 RuleItem *RulesModel::ruleItem(const QString &key) const 0191 { 0192 return m_rules.value(key); 0193 } 0194 0195 QString RulesModel::description() const 0196 { 0197 const QString desc = m_rules["description"]->value().toString(); 0198 if (!desc.isEmpty()) { 0199 return desc; 0200 } 0201 return defaultDescription(); 0202 } 0203 0204 void RulesModel::setDescription(const QString &description) 0205 { 0206 setData(indexOf("description"), description, RulesModel::ValueRole); 0207 } 0208 0209 QString RulesModel::defaultDescription() const 0210 { 0211 const QString wmclass = m_rules["wmclass"]->value().toString(); 0212 const QString title = m_rules["title"]->isEnabled() ? m_rules["title"]->value().toString() : QString(); 0213 0214 if (!title.isEmpty()) { 0215 return i18n("Window settings for %1", title); 0216 } 0217 if (!wmclass.isEmpty()) { 0218 return i18n("Settings for %1", wmclass); 0219 } 0220 0221 return i18n("New window settings"); 0222 } 0223 0224 void RulesModel::processSuggestion(const QString &key, const QVariant &value) 0225 { 0226 if (key == QLatin1String("wmclasshelper")) { 0227 setData(indexOf("wmclass"), value, RulesModel::ValueRole); 0228 setData(indexOf("wmclasscomplete"), true, RulesModel::ValueRole); 0229 } 0230 } 0231 0232 QStringList RulesModel::warningMessages() const 0233 { 0234 QStringList messages; 0235 0236 if (wmclassWarning()) { 0237 messages << i18n("You have specified the window class as unimportant.\n" 0238 "This means the settings will possibly apply to windows from all applications." 0239 " If you really want to create a generic setting, it is recommended" 0240 " you at least limit the window types to avoid special window types."); 0241 } 0242 0243 if (geometryWarning()) { 0244 messages << i18n("Some applications set their own geometry after starting," 0245 " overriding your initial settings for size and position. " 0246 "To enforce these settings, also force the property \"%1\" to \"Yes\".", 0247 m_rules["ignoregeometry"]->name()); 0248 } 0249 0250 return messages; 0251 } 0252 0253 bool RulesModel::wmclassWarning() const 0254 { 0255 const bool no_wmclass = !m_rules["wmclass"]->isEnabled() 0256 || m_rules["wmclass"]->policy() == Rules::UnimportantMatch; 0257 const bool alltypes = !m_rules["types"]->isEnabled() 0258 || (m_rules["types"]->value() == 0) 0259 || (m_rules["types"]->value() == NET::AllTypesMask) 0260 || ((m_rules["types"]->value().toInt() | (1 << NET::Override)) == 0x3FF); 0261 0262 return (no_wmclass && alltypes); 0263 } 0264 0265 bool RulesModel::geometryWarning() const 0266 { 0267 const bool ignoregeometry = m_rules["ignoregeometry"]->isEnabled() 0268 && m_rules["ignoregeometry"]->policy() == Rules::Force 0269 && m_rules["ignoregeometry"]->value() == true; 0270 0271 const bool initialPos = m_rules["position"]->isEnabled() 0272 && (m_rules["position"]->policy() == Rules::Apply 0273 || m_rules["position"]->policy() == Rules::Remember); 0274 0275 const bool initialSize = m_rules["size"]->isEnabled() 0276 && (m_rules["size"]->policy() == Rules::Apply 0277 || m_rules["size"]->policy() == Rules::Remember); 0278 0279 const bool initialPlacement = m_rules["placement"]->isEnabled() 0280 && m_rules["placement"]->policy() == Rules::Force; 0281 0282 return (!ignoregeometry && (initialPos || initialSize || initialPlacement)); 0283 } 0284 0285 RuleSettings *RulesModel::settings() const 0286 { 0287 return m_settings; 0288 } 0289 0290 void RulesModel::setSettings(RuleSettings *settings) 0291 { 0292 if (m_settings == settings) { 0293 return; 0294 } 0295 0296 beginResetModel(); 0297 0298 m_settings = settings; 0299 0300 for (RuleItem *rule : std::as_const(m_ruleList)) { 0301 const KConfigSkeletonItem *configItem = m_settings->findItem(rule->key()); 0302 const KConfigSkeletonItem *configPolicyItem = m_settings->findItem(rule->policyKey()); 0303 0304 rule->reset(); 0305 0306 if (!configItem) { 0307 continue; 0308 } 0309 0310 const bool isEnabled = configPolicyItem ? configPolicyItem->property() != Rules::Unused 0311 : !configItem->property().toString().isEmpty(); 0312 rule->setEnabled(isEnabled); 0313 0314 const QVariant value = configItem->property(); 0315 rule->setValue(value); 0316 0317 if (configPolicyItem) { 0318 const int policy = configPolicyItem->property().toInt(); 0319 rule->setPolicy(policy); 0320 } 0321 } 0322 0323 endResetModel(); 0324 0325 Q_EMIT descriptionChanged(); 0326 Q_EMIT warningMessagesChanged(); 0327 } 0328 0329 void RulesModel::writeToSettings(RuleItem *rule) 0330 { 0331 KConfigSkeletonItem *configItem = m_settings->findItem(rule->key()); 0332 KConfigSkeletonItem *configPolicyItem = m_settings->findItem(rule->policyKey()); 0333 0334 if (!configItem) { 0335 return; 0336 } 0337 0338 if (rule->isEnabled()) { 0339 configItem->setProperty(rule->value()); 0340 if (configPolicyItem) { 0341 configPolicyItem->setProperty(rule->policy()); 0342 } 0343 } else { 0344 configItem->setDefault(); 0345 if (configPolicyItem) { 0346 configPolicyItem->setDefault(); 0347 } 0348 } 0349 } 0350 0351 void RulesModel::populateRuleList() 0352 { 0353 qDeleteAll(m_ruleList); 0354 m_ruleList.clear(); 0355 0356 // Rule description 0357 auto description = addRule(new RuleItem(QLatin1String("description"), 0358 RulePolicy::NoPolicy, RuleItem::String, 0359 i18n("Description"), i18n("Window matching"), 0360 QIcon::fromTheme("entry-edit"))); 0361 description->setFlag(RuleItem::AlwaysEnabled); 0362 description->setFlag(RuleItem::AffectsDescription); 0363 0364 // Window matching 0365 auto wmclass = addRule(new RuleItem(QLatin1String("wmclass"), 0366 RulePolicy::StringMatch, RuleItem::String, 0367 i18n("Window class (application)"), i18n("Window matching"), 0368 QIcon::fromTheme("window"))); 0369 wmclass->setFlag(RuleItem::AlwaysEnabled); 0370 wmclass->setFlag(RuleItem::AffectsDescription); 0371 wmclass->setFlag(RuleItem::AffectsWarning); 0372 0373 auto wmclasscomplete = addRule(new RuleItem(QLatin1String("wmclasscomplete"), 0374 RulePolicy::NoPolicy, RuleItem::Boolean, 0375 i18n("Match whole window class"), i18n("Window matching"), 0376 QIcon::fromTheme("window"))); 0377 wmclasscomplete->setFlag(RuleItem::AlwaysEnabled); 0378 0379 // Helper item to store the detected whole window class when detecting properties 0380 auto wmclasshelper = addRule(new RuleItem(QLatin1String("wmclasshelper"), 0381 RulePolicy::NoPolicy, RuleItem::String, 0382 i18n("Whole window class"), i18n("Window matching"), 0383 QIcon::fromTheme("window"))); 0384 wmclasshelper->setFlag(RuleItem::SuggestionOnly); 0385 0386 auto types = addRule(new RuleItem(QLatin1String("types"), 0387 RulePolicy::NoPolicy, RuleItem::NetTypes, 0388 i18n("Window types"), i18n("Window matching"), 0389 QIcon::fromTheme("window-duplicate"))); 0390 types->setOptionsData(windowTypesModelData()); 0391 types->setFlag(RuleItem::AlwaysEnabled); 0392 types->setFlag(RuleItem::AffectsWarning); 0393 0394 addRule(new RuleItem(QLatin1String("windowrole"), 0395 RulePolicy::StringMatch, RuleItem::String, 0396 i18n("Window role"), i18n("Window matching"), 0397 QIcon::fromTheme("dialog-object-properties"))); 0398 0399 auto title = addRule(new RuleItem(QLatin1String("title"), 0400 RulePolicy::StringMatch, RuleItem::String, 0401 i18n("Window title"), i18n("Window matching"), 0402 QIcon::fromTheme("edit-comment"))); 0403 title->setFlag(RuleItem::AffectsDescription); 0404 0405 addRule(new RuleItem(QLatin1String("clientmachine"), 0406 RulePolicy::StringMatch, RuleItem::String, 0407 i18n("Machine (hostname)"), i18n("Window matching"), 0408 QIcon::fromTheme("computer"))); 0409 0410 // Size & Position 0411 auto position = addRule(new RuleItem(QLatin1String("position"), 0412 RulePolicy::SetRule, RuleItem::Point, 0413 i18n("Position"), i18n("Size & Position"), 0414 QIcon::fromTheme("transform-move"))); 0415 position->setFlag(RuleItem::AffectsWarning); 0416 0417 auto size = addRule(new RuleItem(QLatin1String("size"), 0418 RulePolicy::SetRule, RuleItem::Size, 0419 i18n("Size"), i18n("Size & Position"), 0420 QIcon::fromTheme("transform-scale"))); 0421 size->setFlag(RuleItem::AffectsWarning); 0422 0423 addRule(new RuleItem(QLatin1String("maximizehoriz"), 0424 RulePolicy::SetRule, RuleItem::Boolean, 0425 i18n("Maximized horizontally"), i18n("Size & Position"), 0426 QIcon::fromTheme("resizecol"))); 0427 0428 addRule(new RuleItem(QLatin1String("maximizevert"), 0429 RulePolicy::SetRule, RuleItem::Boolean, 0430 i18n("Maximized vertically"), i18n("Size & Position"), 0431 QIcon::fromTheme("resizerow"))); 0432 0433 RuleItem *desktops; 0434 if (KWindowSystem::isPlatformX11()) { 0435 // Single selection of Virtual Desktop on X11 0436 desktops = new RuleItem(QLatin1String("desktops"), 0437 RulePolicy::SetRule, RuleItem::Option, 0438 i18n("Virtual Desktop"), i18n("Size & Position"), 0439 QIcon::fromTheme("virtual-desktops")); 0440 } else { 0441 // Multiple selection on Wayland 0442 desktops = new RuleItem(QLatin1String("desktops"), 0443 RulePolicy::SetRule, RuleItem::OptionList, 0444 i18n("Virtual Desktops"), i18n("Size & Position"), 0445 QIcon::fromTheme("virtual-desktops")); 0446 } 0447 addRule(desktops); 0448 desktops->setOptionsData(virtualDesktopsModelData()); 0449 0450 connect(this, &RulesModel::virtualDesktopsUpdated, this, [this]() { 0451 m_rules["desktops"]->setOptionsData(virtualDesktopsModelData()); 0452 const QModelIndex index = indexOf("desktops"); 0453 Q_EMIT dataChanged(index, index, {OptionsModelRole}); 0454 }); 0455 0456 updateVirtualDesktops(); 0457 0458 #if KWIN_BUILD_ACTIVITIES 0459 m_activities = new KActivities::Consumer(this); 0460 0461 auto activity = addRule(new RuleItem(QLatin1String("activity"), 0462 RulePolicy::SetRule, RuleItem::OptionList, 0463 i18n("Activities"), i18n("Size & Position"), 0464 QIcon::fromTheme("activities"))); 0465 activity->setOptionsData(activitiesModelData()); 0466 0467 // Activites consumer may update the available activities later 0468 auto updateActivities = [this]() { 0469 m_rules["activity"]->setOptionsData(activitiesModelData()); 0470 const QModelIndex index = indexOf("activity"); 0471 Q_EMIT dataChanged(index, index, {OptionsModelRole}); 0472 }; 0473 connect(m_activities, &KActivities::Consumer::activitiesChanged, this, updateActivities); 0474 connect(m_activities, &KActivities::Consumer::serviceStatusChanged, this, updateActivities); 0475 #endif 0476 0477 addRule(new RuleItem(QLatin1String("screen"), 0478 RulePolicy::SetRule, RuleItem::Integer, 0479 i18n("Screen"), i18n("Size & Position"), 0480 QIcon::fromTheme("osd-shutd-screen"))); 0481 0482 addRule(new RuleItem(QLatin1String("fullscreen"), 0483 RulePolicy::SetRule, RuleItem::Boolean, 0484 i18n("Fullscreen"), i18n("Size & Position"), 0485 QIcon::fromTheme("view-fullscreen"))); 0486 0487 addRule(new RuleItem(QLatin1String("minimize"), 0488 RulePolicy::SetRule, RuleItem::Boolean, 0489 i18n("Minimized"), i18n("Size & Position"), 0490 QIcon::fromTheme("window-minimize"))); 0491 0492 addRule(new RuleItem(QLatin1String("shade"), 0493 RulePolicy::SetRule, RuleItem::Boolean, 0494 i18n("Shaded"), i18n("Size & Position"), 0495 QIcon::fromTheme("window-shade"))); 0496 0497 auto placement = addRule(new RuleItem(QLatin1String("placement"), 0498 RulePolicy::ForceRule, RuleItem::Option, 0499 i18n("Initial placement"), i18n("Size & Position"), 0500 QIcon::fromTheme("region"))); 0501 placement->setOptionsData(placementModelData()); 0502 placement->setFlag(RuleItem::AffectsWarning); 0503 0504 auto ignoregeometry = addRule(new RuleItem(QLatin1String("ignoregeometry"), 0505 RulePolicy::SetRule, RuleItem::Boolean, 0506 i18n("Ignore requested geometry"), i18n("Size & Position"), 0507 QIcon::fromTheme("view-time-schedule-baselined-remove"), 0508 i18n("Windows can ask to appear in a certain position.\n" 0509 "By default this overrides the placement strategy\n" 0510 "what might be nasty if the client abuses the feature\n" 0511 "to unconditionally popup in the middle of your screen."))); 0512 ignoregeometry->setFlag(RuleItem::AffectsWarning); 0513 0514 addRule(new RuleItem(QLatin1String("minsize"), 0515 RulePolicy::ForceRule, RuleItem::Size, 0516 i18n("Minimum Size"), i18n("Size & Position"), 0517 QIcon::fromTheme("transform-scale"))); 0518 0519 addRule(new RuleItem(QLatin1String("maxsize"), 0520 RulePolicy::ForceRule, RuleItem::Size, 0521 i18n("Maximum Size"), i18n("Size & Position"), 0522 QIcon::fromTheme("transform-scale"))); 0523 0524 addRule(new RuleItem(QLatin1String("strictgeometry"), 0525 RulePolicy::ForceRule, RuleItem::Boolean, 0526 i18n("Obey geometry restrictions"), i18n("Size & Position"), 0527 QIcon::fromTheme("transform-crop-and-resize"), 0528 i18n("Eg. terminals or video players can ask to keep a certain aspect ratio\n" 0529 "or only grow by values larger than one\n" 0530 "(eg. by the dimensions of one character).\n" 0531 "This may be pointless and the restriction prevents arbitrary dimensions\n" 0532 "like your complete screen area."))); 0533 0534 // Arrangement & Access 0535 addRule(new RuleItem(QLatin1String("above"), 0536 RulePolicy::SetRule, RuleItem::Boolean, 0537 i18n("Keep above other windows"), i18n("Arrangement & Access"), 0538 QIcon::fromTheme("window-keep-above"))); 0539 0540 addRule(new RuleItem(QLatin1String("below"), 0541 RulePolicy::SetRule, RuleItem::Boolean, 0542 i18n("Keep below other windows"), i18n("Arrangement & Access"), 0543 QIcon::fromTheme("window-keep-below"))); 0544 0545 addRule(new RuleItem(QLatin1String("skiptaskbar"), 0546 RulePolicy::SetRule, RuleItem::Boolean, 0547 i18n("Skip taskbar"), i18n("Arrangement & Access"), 0548 QIcon::fromTheme("kt-show-statusbar"), 0549 i18n("Window shall (not) appear in the taskbar."))); 0550 0551 addRule(new RuleItem(QLatin1String("skippager"), 0552 RulePolicy::SetRule, RuleItem::Boolean, 0553 i18n("Skip pager"), i18n("Arrangement & Access"), 0554 QIcon::fromTheme("org.kde.plasma.pager"), 0555 i18n("Window shall (not) appear in the manager for virtual desktops"))); 0556 0557 addRule(new RuleItem(QLatin1String("skipswitcher"), 0558 RulePolicy::SetRule, RuleItem::Boolean, 0559 i18n("Skip switcher"), i18n("Arrangement & Access"), 0560 QIcon::fromTheme("preferences-system-windows-effect-flipswitch"), 0561 i18n("Window shall (not) appear in the Alt+Tab list"))); 0562 0563 addRule(new RuleItem(QLatin1String("shortcut"), 0564 RulePolicy::SetRule, RuleItem::Shortcut, 0565 i18n("Shortcut"), i18n("Arrangement & Access"), 0566 QIcon::fromTheme("configure-shortcuts"))); 0567 0568 // Appearance & Fixes 0569 addRule(new RuleItem(QLatin1String("noborder"), 0570 RulePolicy::SetRule, RuleItem::Boolean, 0571 i18n("No titlebar and frame"), i18n("Appearance & Fixes"), 0572 QIcon::fromTheme("dialog-cancel"))); 0573 0574 auto decocolor = addRule(new RuleItem(QLatin1String("decocolor"), 0575 RulePolicy::ForceRule, RuleItem::Option, 0576 i18n("Titlebar color scheme"), i18n("Appearance & Fixes"), 0577 QIcon::fromTheme("preferences-desktop-theme"))); 0578 decocolor->setOptionsData(colorSchemesModelData()); 0579 0580 addRule(new RuleItem(QLatin1String("opacityactive"), 0581 RulePolicy::ForceRule, RuleItem::Percentage, 0582 i18n("Active opacity"), i18n("Appearance & Fixes"), 0583 QIcon::fromTheme("edit-opacity"))); 0584 0585 addRule(new RuleItem(QLatin1String("opacityinactive"), 0586 RulePolicy::ForceRule, RuleItem::Percentage, 0587 i18n("Inactive opacity"), i18n("Appearance & Fixes"), 0588 QIcon::fromTheme("edit-opacity"))); 0589 0590 auto fsplevel = addRule(new RuleItem(QLatin1String("fsplevel"), 0591 RulePolicy::ForceRule, RuleItem::Option, 0592 i18n("Focus stealing prevention"), i18n("Appearance & Fixes"), 0593 QIcon::fromTheme("preferences-system-windows-effect-glide"), 0594 i18n("KWin tries to prevent windows from taking the focus\n" 0595 "(\"activate\") while you're working in another window,\n" 0596 "but this may sometimes fail or superact.\n" 0597 "\"None\" will unconditionally allow this window to get the focus while\n" 0598 "\"Extreme\" will completely prevent it from taking the focus."))); 0599 fsplevel->setOptionsData(focusModelData()); 0600 0601 auto fpplevel = addRule(new RuleItem(QLatin1String("fpplevel"), 0602 RulePolicy::ForceRule, RuleItem::Option, 0603 i18n("Focus protection"), i18n("Appearance & Fixes"), 0604 QIcon::fromTheme("preferences-system-windows-effect-minimize"), 0605 i18n("This controls the focus protection of the currently active window.\n" 0606 "None will always give the focus away,\n" 0607 "Extreme will keep it.\n" 0608 "Otherwise it's interleaved with the stealing prevention\n" 0609 "assigned to the window that wants the focus."))); 0610 fpplevel->setOptionsData(focusModelData()); 0611 0612 addRule(new RuleItem(QLatin1String("acceptfocus"), 0613 RulePolicy::ForceRule, RuleItem::Boolean, 0614 i18n("Accept focus"), i18n("Appearance & Fixes"), 0615 QIcon::fromTheme("preferences-desktop-cursors"), 0616 i18n("Windows may prevent to get the focus (activate) when being clicked.\n" 0617 "On the other hand you might wish to prevent a window\n" 0618 "from getting focused on a mouse click."))); 0619 0620 addRule(new RuleItem(QLatin1String("disableglobalshortcuts"), 0621 RulePolicy::ForceRule, RuleItem::Boolean, 0622 i18n("Ignore global shortcuts"), i18n("Appearance & Fixes"), 0623 QIcon::fromTheme("input-keyboard-virtual-off"), 0624 i18n("When used, a window will receive\n" 0625 "all keyboard inputs while it is active, including Alt+Tab etc.\n" 0626 "This is especially interesting for emulators or virtual machines.\n" 0627 "\n" 0628 "Be warned:\n" 0629 "you won't be able to Alt+Tab out of the window\n" 0630 "nor use any other global shortcut (such as Alt+F2 to show KRunner)\n" 0631 "while it's active!"))); 0632 0633 addRule(new RuleItem(QLatin1String("closeable"), 0634 RulePolicy::ForceRule, RuleItem::Boolean, 0635 i18n("Closeable"), i18n("Appearance & Fixes"), 0636 QIcon::fromTheme("dialog-close"))); 0637 0638 auto type = addRule(new RuleItem(QLatin1String("type"), 0639 RulePolicy::ForceRule, RuleItem::Option, 0640 i18n("Set window type"), i18n("Appearance & Fixes"), 0641 QIcon::fromTheme("window-duplicate"))); 0642 type->setOptionsData(windowTypesModelData()); 0643 0644 addRule(new RuleItem(QLatin1String("desktopfile"), 0645 RulePolicy::SetRule, RuleItem::String, 0646 i18n("Desktop file name"), i18n("Appearance & Fixes"), 0647 QIcon::fromTheme("application-x-desktop"))); 0648 0649 addRule(new RuleItem(QLatin1String("blockcompositing"), 0650 RulePolicy::ForceRule, RuleItem::Boolean, 0651 i18n("Block compositing"), i18n("Appearance & Fixes"), 0652 QIcon::fromTheme("composite-track-on"))); 0653 } 0654 0655 const QHash<QString, QString> RulesModel::x11PropertyHash() 0656 { 0657 static const auto propertyToRule = QHash<QString, QString>{ 0658 {"caption", "title"}, 0659 {"role", "windowrole"}, 0660 {"clientMachine", "clientmachine"}, 0661 {"maximizeHorizontal", "maximizehoriz"}, 0662 {"maximizeVertical", "maximizevert"}, 0663 {"minimized", "minimize"}, 0664 {"shaded", "shade"}, 0665 {"fullscreen", "fullscreen"}, 0666 {"keepAbove", "above"}, 0667 {"keepBelow", "below"}, 0668 {"noBorder", "noborder"}, 0669 {"skipTaskbar", "skiptaskbar"}, 0670 {"skipPager", "skippager"}, 0671 {"skipSwitcher", "skipswitcher"}, 0672 {"type", "type"}, 0673 {"desktopFile", "desktopfile"}, 0674 {"desktops", "desktops"}, 0675 }; 0676 return propertyToRule; 0677 }; 0678 0679 void RulesModel::setSuggestedProperties(const QVariantMap &info) 0680 { 0681 // Properties that cannot be directly applied via x11PropertyHash 0682 const QPoint position = QPoint(info.value("x").toInt(), info.value("y").toInt()); 0683 const QSize size = QSize(info.value("width").toInt(), info.value("height").toInt()); 0684 0685 m_rules["position"]->setSuggestedValue(position); 0686 m_rules["size"]->setSuggestedValue(size); 0687 m_rules["minsize"]->setSuggestedValue(size); 0688 m_rules["maxsize"]->setSuggestedValue(size); 0689 0690 NET::WindowType window_type = static_cast<NET::WindowType>(info.value("type", 0).toInt()); 0691 if (window_type == NET::Unknown) { 0692 window_type = NET::Normal; 0693 } 0694 m_rules["types"]->setSuggestedValue(1 << window_type); 0695 0696 const QString wmsimpleclass = info.value("resourceClass").toString(); 0697 const QString wmcompleteclass = QStringLiteral("%1 %2").arg(info.value("resourceName").toString(), 0698 info.value("resourceClass").toString()); 0699 0700 // This window is not providing the class according to spec (WM_CLASS on X11, appId on Wayland) 0701 // Notify the user that this is a bug within the application, so there's nothing we can do 0702 if (wmsimpleclass.isEmpty()) { 0703 Q_EMIT showErrorMessage(i18n("Window class not available"), 0704 xi18nc("@info", "This application is not providing a class for the window, " 0705 "so KWin cannot use it to match and apply any rules. " 0706 "If you still want to apply some rules to it, " 0707 "try to match other properties like the window title instead.<nl/><nl/>" 0708 "Please consider reporting this bug to the application's developers.")); 0709 } 0710 0711 m_rules["wmclass"]->setSuggestedValue(wmsimpleclass); 0712 m_rules["wmclasshelper"]->setSuggestedValue(wmcompleteclass); 0713 0714 #if KWIN_BUILD_ACTIVITIES 0715 const QStringList activities = info.value("activities").toStringList(); 0716 m_rules["activity"]->setSuggestedValue(activities.isEmpty() ? QStringList{Activities::nullUuid()} 0717 : activities); 0718 #endif 0719 0720 const auto ruleForProperty = x11PropertyHash(); 0721 for (QString &property : info.keys()) { 0722 if (!ruleForProperty.contains(property)) { 0723 continue; 0724 } 0725 const QString ruleKey = ruleForProperty.value(property, QString()); 0726 Q_ASSERT(hasRule(ruleKey)); 0727 0728 m_rules[ruleKey]->setSuggestedValue(info.value(property)); 0729 } 0730 0731 Q_EMIT dataChanged(index(0), index(rowCount() - 1), {RulesModel::SuggestedValueRole}); 0732 } 0733 0734 QList<OptionsModel::Data> RulesModel::windowTypesModelData() const 0735 { 0736 static const auto modelData = QList<OptionsModel::Data>{ 0737 // TODO: Find/create better icons 0738 {0, i18n("All Window Types"), {}, {}, OptionsModel::SelectAllOption}, 0739 {1 << NET::Normal, i18n("Normal Window"), QIcon::fromTheme("window")}, 0740 {1 << NET::Dialog, i18n("Dialog Window"), QIcon::fromTheme("window-duplicate")}, 0741 {1 << NET::Utility, i18n("Utility Window"), QIcon::fromTheme("dialog-object-properties")}, 0742 {1 << NET::Dock, i18n("Dock (panel)"), QIcon::fromTheme("list-remove")}, 0743 {1 << NET::Toolbar, i18n("Toolbar"), QIcon::fromTheme("tools")}, 0744 {1 << NET::Menu, i18n("Torn-Off Menu"), QIcon::fromTheme("overflow-menu-left")}, 0745 {1 << NET::Splash, i18n("Splash Screen"), QIcon::fromTheme("embosstool")}, 0746 {1 << NET::Desktop, i18n("Desktop"), QIcon::fromTheme("desktop")}, 0747 // {1 << NET::Override, i18n("Unmanaged Window")}, deprecated 0748 {1 << NET::TopMenu, i18n("Standalone Menubar"), QIcon::fromTheme("application-menu")}, 0749 {1 << NET::OnScreenDisplay, i18n("On Screen Display"), QIcon::fromTheme("osd-duplicate")}}; 0750 0751 return modelData; 0752 } 0753 0754 QList<OptionsModel::Data> RulesModel::virtualDesktopsModelData() const 0755 { 0756 QList<OptionsModel::Data> modelData; 0757 modelData << OptionsModel::Data{ 0758 QString(), 0759 i18n("All Desktops"), 0760 QIcon::fromTheme("window-pin"), 0761 i18nc("@info:tooltip in the virtual desktop list", "Make the window available on all desktops"), 0762 OptionsModel::ExclusiveOption, 0763 }; 0764 for (const DBusDesktopDataStruct &desktop : m_virtualDesktops) { 0765 modelData << OptionsModel::Data{ 0766 desktop.id, 0767 QString::number(desktop.position + 1).rightJustified(2) + QStringLiteral(": ") + desktop.name, 0768 QIcon::fromTheme("virtual-desktops")}; 0769 } 0770 return modelData; 0771 } 0772 0773 QList<OptionsModel::Data> RulesModel::activitiesModelData() const 0774 { 0775 #if KWIN_BUILD_ACTIVITIES 0776 QList<OptionsModel::Data> modelData; 0777 0778 modelData << OptionsModel::Data{ 0779 Activities::nullUuid(), 0780 i18n("All Activities"), 0781 QIcon::fromTheme("activities"), 0782 i18nc("@info:tooltip in the activity list", "Make the window available on all activities"), 0783 OptionsModel::ExclusiveOption, 0784 }; 0785 0786 const auto activities = m_activities->activities(KActivities::Info::Running); 0787 if (m_activities->serviceStatus() == KActivities::Consumer::Running) { 0788 for (const QString &activityId : activities) { 0789 const KActivities::Info info(activityId); 0790 modelData << OptionsModel::Data{activityId, info.name(), QIcon::fromTheme(info.icon())}; 0791 } 0792 } 0793 0794 return modelData; 0795 #else 0796 return {}; 0797 #endif 0798 } 0799 0800 QList<OptionsModel::Data> RulesModel::placementModelData() const 0801 { 0802 static const auto modelData = QList<OptionsModel::Data>{ 0803 {PlacementDefault, i18n("Default")}, 0804 {PlacementNone, i18n("No Placement")}, 0805 {PlacementSmart, i18n("Minimal Overlapping")}, 0806 {PlacementMaximizing, i18n("Maximized")}, 0807 {PlacementCentered, i18n("Centered")}, 0808 {PlacementRandom, i18n("Random")}, 0809 {PlacementZeroCornered, i18n("In Top-Left Corner")}, 0810 {PlacementUnderMouse, i18n("Under Mouse")}, 0811 {PlacementOnMainWindow, i18n("On Main Window")}}; 0812 return modelData; 0813 } 0814 0815 QList<OptionsModel::Data> RulesModel::focusModelData() const 0816 { 0817 static const auto modelData = QList<OptionsModel::Data>{ 0818 {0, i18n("None")}, 0819 {1, i18n("Low")}, 0820 {2, i18n("Normal")}, 0821 {3, i18n("High")}, 0822 {4, i18n("Extreme")}}; 0823 return modelData; 0824 } 0825 0826 QList<OptionsModel::Data> RulesModel::colorSchemesModelData() const 0827 { 0828 QList<OptionsModel::Data> modelData; 0829 0830 KColorSchemeManager schemes; 0831 QAbstractItemModel *schemesModel = schemes.model(); 0832 0833 // Skip row 0, which is Default scheme 0834 for (int r = 1; r < schemesModel->rowCount(); r++) { 0835 const QModelIndex index = schemesModel->index(r, 0); 0836 modelData << OptionsModel::Data{ 0837 QFileInfo(index.data(Qt::UserRole).toString()).baseName(), 0838 index.data(Qt::DisplayRole).toString(), 0839 index.data(Qt::DecorationRole).value<QIcon>()}; 0840 } 0841 0842 return modelData; 0843 } 0844 0845 void RulesModel::detectWindowProperties(int miliseconds) 0846 { 0847 QTimer::singleShot(miliseconds, this, &RulesModel::selectX11Window); 0848 } 0849 0850 void RulesModel::selectX11Window() 0851 { 0852 QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"), 0853 QStringLiteral("/KWin"), 0854 QStringLiteral("org.kde.KWin"), 0855 QStringLiteral("queryWindowInfo")); 0856 0857 QDBusPendingReply<QVariantMap> async = QDBusConnection::sessionBus().asyncCall(message); 0858 0859 QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this); 0860 connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { 0861 QDBusPendingReply<QVariantMap> reply = *self; 0862 self->deleteLater(); 0863 if (!reply.isValid()) { 0864 if (reply.error().name() == QLatin1String("org.kde.KWin.Error.InvalidWindow")) { 0865 Q_EMIT showErrorMessage(i18n("Unmanaged window"), 0866 i18n("Could not detect window properties. The window is not managed by KWin.")); 0867 } 0868 return; 0869 } 0870 const QVariantMap windowInfo = reply.value(); 0871 setSuggestedProperties(windowInfo); 0872 Q_EMIT showSuggestions(); 0873 }); 0874 } 0875 0876 void RulesModel::updateVirtualDesktops() 0877 { 0878 QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"), 0879 QStringLiteral("/VirtualDesktopManager"), 0880 QStringLiteral("org.freedesktop.DBus.Properties"), 0881 QStringLiteral("Get")); 0882 message.setArguments(QVariantList{ 0883 QStringLiteral("org.kde.KWin.VirtualDesktopManager"), 0884 QStringLiteral("desktops")}); 0885 0886 QDBusPendingReply<QVariant> async = QDBusConnection::sessionBus().asyncCall(message); 0887 0888 QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this); 0889 connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { 0890 QDBusPendingReply<QVariant> reply = *self; 0891 self->deleteLater(); 0892 if (!reply.isValid()) { 0893 return; 0894 } 0895 m_virtualDesktops = qdbus_cast<KWin::DBusDesktopDataVector>(reply.value()); 0896 Q_EMIT virtualDesktopsUpdated(); 0897 }); 0898 } 0899 0900 } // namespace