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