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