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"