From a82be242ea207fb5c7384d2be7703456b368a414 Mon Sep 17 00:00:00 2001 From: Ismael Asensio Date: Wed, 17 Jun 2020 19:54:14 +0200 Subject: [PATCH] [kwinrules] Launch full KCM when editing from window menu When rules configuration is invoked from window `Alt+F3` menu, we call a custom binary `kwin_rules_dialog` which currently provides only the rule edition dialog by embedding `RulesEditor.qml` within a QQuickView. This MR changes that behavior to call the full KCM from the menu. The code to match previous rules, or compose a new one based on window properties has been ported to the KCM from the dialog, so the overall interaction is similar. It has several advantages: - uses only one entry-point to the code - adds discoverability to the full KCM (I guess many users know how to create a rule, but not where to delete it later) And a drawback: - only one instance of the KCM can be called at a time, so it will show an error when calling it from two different windows, or if the KCM is open in System Settings This drawback can be solved after adding argument passing via dBus in KCM infraestructure. BUG: 433837 CCBUG: 417923 --- src/kcmkwin/kwinrules/CMakeLists.txt | 6 +- src/kcmkwin/kwinrules/kcmrules.cpp | 167 +++++++++++- src/kcmkwin/kwinrules/kcmrules.h | 7 + src/kcmkwin/kwinrules/main.cpp | 240 +++--------------- .../package/contents/ui/RulesEditor.qml | 16 +- src/kcmkwin/kwinrules/rulebookmodel.cpp | 86 +++++++ src/kcmkwin/kwinrules/rulebookmodel.h | 2 + src/kcmkwin/kwinrules/rulesdialog.cpp | 74 ------ src/kcmkwin/kwinrules/rulesdialog.h | 41 --- 9 files changed, 298 insertions(+), 341 deletions(-) delete mode 100644 src/kcmkwin/kwinrules/rulesdialog.cpp delete mode 100644 src/kcmkwin/kwinrules/rulesdialog.h diff --git a/src/kcmkwin/kwinrules/CMakeLists.txt b/src/kcmkwin/kwinrules/CMakeLists.txt index c68f7bdca5..64b19cec58 100644 --- a/src/kcmkwin/kwinrules/CMakeLists.txt +++ b/src/kcmkwin/kwinrules/CMakeLists.txt @@ -16,6 +16,7 @@ set(kwinrules_SRCS optionsmodel.cpp ruleitem.cpp rulesmodel.cpp + rulebookmodel.cpp ) kconfig_add_kcfg_files(kwinrules_SRCS ../../rulesettings.kcfgc) @@ -33,6 +34,7 @@ set(kcm_libs Qt::Quick Qt::QuickWidgets + KF5::KCMUtils KF5::I18n KF5::QuickAddons KF5::WindowSystem @@ -44,11 +46,11 @@ if (KWIN_BUILD_ACTIVITIES) endif() target_link_libraries(KWinRulesObjects ${kcm_libs} ${kwin_kcm_rules_XCB_LIBS}) -add_executable(kwin_rules_dialog main.cpp rulesdialog.cpp) +add_executable(kwin_rules_dialog main.cpp) target_link_libraries(kwin_rules_dialog KWinRulesObjects) install(TARGETS kwin_rules_dialog DESTINATION ${KDE_INSTALL_LIBEXECDIR}) -add_library(kcm_kwinrules MODULE kcmrules.cpp rulebookmodel.cpp) +add_library(kcm_kwinrules MODULE kcmrules.cpp) target_link_libraries(kcm_kwinrules KWinRulesObjects) kcoreaddons_desktop_to_json(kcm_kwinrules "kcm_kwinrules.desktop" SERVICE_TYPES kcmodule.desktop) diff --git a/src/kcmkwin/kwinrules/kcmrules.cpp b/src/kcmkwin/kwinrules/kcmrules.cpp index 8eab547d80..bc47099687 100644 --- a/src/kcmkwin/kwinrules/kcmrules.cpp +++ b/src/kcmkwin/kwinrules/kcmrules.cpp @@ -40,6 +40,12 @@ KCMKWinRules::KCMKWinRules(QObject *parent, const QVariantList &arguments) " KWin as your window manager. If you do use a different window manager, please refer to its documentation" " for how to customize window behavior.

")); + QStringList argList; + for (const QVariant &arg : arguments) { + argList << arg.toString(); + } + parseArguments(argList); + connect(m_rulesModel, &RulesModel::descriptionChanged, this, [this]{ if (m_editIndex.isValid()) { m_ruleBookModel->setDescriptionAt(m_editIndex.row(), m_rulesModel->description()); @@ -49,14 +55,69 @@ KCMKWinRules::KCMKWinRules(QObject *parent, const QVariantList &arguments) connect(m_ruleBookModel, &RulesModel::dataChanged, this, &KCMKWinRules::updateNeedsSave); } +void KCMKWinRules::parseArguments(const QStringList &args) +{ + // When called from window menu, "uuid" and "whole-app" are set in arguments list + bool nextArgIsUuid = false; + QUuid uuid = QUuid(); + + // TODO: Use a better argument parser + for (const QString &arg : args) { + if (arg == QLatin1String("uuid")) { + nextArgIsUuid = true; + } else if (nextArgIsUuid) { + uuid = QUuid(arg); + nextArgIsUuid = false; + } else if (arg.startsWith("uuid=")) { + uuid = arg.mid(strlen("uuid=")); + } else if (arg == QLatin1String("whole-app")) { + m_wholeApp = true; + } + } + + if (uuid.isNull()) { + qDebug() << "Invalid window uuid."; + return; + } + + // Get the Window properties + QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"), + QStringLiteral("/KWin"), + QStringLiteral("org.kde.KWin"), + QStringLiteral("getWindowInfo")); + message.setArguments({uuid.toString()}); + QDBusPendingReply async = QDBusConnection::sessionBus().asyncCall(message); + + QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this); + connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this, uuid](QDBusPendingCallWatcher *self) { + QDBusPendingReply reply = *self; + self->deleteLater(); + if (!reply.isValid() || reply.value().isEmpty()) { + qDebug() << "Error retrieving properties for window" << uuid; + return; + } + qDebug() << "Retrieved properties for window" << uuid; + m_winProperties = reply.value(); + + if (m_alreadyLoaded) { + createRuleFromProperties(); + } + }); +} + void KCMKWinRules::load() { m_ruleBookModel->load(); - - m_editIndex = QModelIndex(); - emit editIndexChanged(); - setNeedsSave(false); + + if (!m_winProperties.isEmpty() && !m_alreadyLoaded) { + createRuleFromProperties(); + } else { + m_editIndex = QModelIndex(); + emit editIndexChanged(); + } + + m_alreadyLoaded = true; } void KCMKWinRules::save() @@ -76,6 +137,26 @@ void KCMKWinRules::updateNeedsSave() emit needsSaveChanged(); } +void KCMKWinRules::createRuleFromProperties() +{ + if (m_winProperties.isEmpty()) { + return; + } + + QModelIndex matchedIndex = m_ruleBookModel->findRuleWithProperties(m_winProperties, m_wholeApp); + if (!matchedIndex.isValid()) { + m_ruleBookModel->insertRow(0); + m_ruleBookModel->setRuleAt(0, ruleForProperties(m_winProperties, m_wholeApp)); + matchedIndex = m_ruleBookModel->index(0); + updateNeedsSave(); + } + + editRule(matchedIndex.row()); + m_rulesModel->setSuggestedProperties(m_winProperties); + + m_winProperties.clear(); +} + void KCMKWinRules::saveCurrentRule() { if (m_editIndex.isValid() && needsSave()) { @@ -249,6 +330,84 @@ void KCMKWinRules::importFromFile(const QUrl &path) updateNeedsSave(); } +// Code adapted from original `findRule()` method in `kwin_rules_dialog::main.cpp` +Rules *KCMKWinRules::ruleForProperties(const QVariantMap &windowProperties, bool wholeApp) const +{ + const QByteArray wmclass_class = windowProperties.value("resourceClass").toByteArray().toLower(); + const QByteArray wmclass_name = windowProperties.value("resourceName").toByteArray().toLower(); + const QByteArray role = windowProperties.value("role").toByteArray().toLower(); + const NET::WindowType type = static_cast(windowProperties.value("type").toInt()); + const QString title = windowProperties.value("caption").toString(); + const QByteArray machine = windowProperties.value("clientMachine").toByteArray(); + + Rules *rule = new Rules(); + + if (wholeApp) { + rule->description = i18n("Application settings for %1", QString::fromLatin1(wmclass_class)); + // TODO maybe exclude some types? If yes, then also exclude them when searching. + rule->types = NET::AllTypesMask; + rule->titlematch = Rules::UnimportantMatch; + rule->clientmachine = machine; // set, but make unimportant + rule->clientmachinematch = Rules::UnimportantMatch; + rule->windowrolematch = Rules::UnimportantMatch; + if (wmclass_name == wmclass_class) { + rule->wmclasscomplete = false; + rule->wmclass = wmclass_class; + rule->wmclassmatch = Rules::ExactMatch; + } else { + // WM_CLASS components differ - perhaps the app got -name argument + rule->wmclasscomplete = true; + rule->wmclass = wmclass_name + ' ' + wmclass_class; + rule->wmclassmatch = Rules::ExactMatch; + } + return rule; + } + + rule->description = i18n("Window settings for %1", QString::fromLatin1(wmclass_class)); + if (type == NET::Unknown) { + rule->types = NET::NormalMask; + } else { + rule->types = NET::WindowTypeMask(1 << type); // convert type to its mask + } + rule->title = title; // set, but make unimportant + rule->titlematch = Rules::UnimportantMatch; + rule->clientmachine = machine; // set, but make unimportant + rule->clientmachinematch = Rules::UnimportantMatch; + if (!role.isEmpty() && role != "unknown" && role != "unnamed") { // Qt sets this if not specified + rule->windowrole = role; + rule->windowrolematch = Rules::ExactMatch; + if (wmclass_name == wmclass_class) { + rule->wmclasscomplete = false; + rule->wmclass = wmclass_class; + rule->wmclassmatch = Rules::ExactMatch; + } else { + // WM_CLASS components differ - perhaps the app got -name argument + rule->wmclasscomplete = true; + rule->wmclass = wmclass_name + ' ' + wmclass_class; + rule->wmclassmatch = Rules::ExactMatch; + } + } else { // no role set + if (wmclass_name != wmclass_class) { + rule->wmclasscomplete = true; + rule->wmclass = wmclass_name + ' ' + wmclass_class; + rule->wmclassmatch = Rules::ExactMatch; + } else { + // This is a window that has no role set, and both components of WM_CLASS + // match (possibly only differing in case), which most likely means either + // the application doesn't give a damn about distinguishing its various + // windows, or it's an app that uses role for that, but this window + // lacks it for some reason. Use non-complete WM_CLASS matching, also + // include window title in the matching, and pray it causes many more positive + // matches than negative matches. + rule->titlematch = Rules::ExactMatch; + rule->wmclasscomplete = false; + rule->wmclass = wmclass_class; + rule->wmclassmatch = Rules::ExactMatch; + } + } + return rule; +} + K_PLUGIN_CLASS_WITH_JSON(KCMKWinRules, "kcm_kwinrules.json"); } // namespace diff --git a/src/kcmkwin/kwinrules/kcmrules.h b/src/kcmkwin/kwinrules/kcmrules.h index b41c36cede..6f96eea8c2 100644 --- a/src/kcmkwin/kwinrules/kcmrules.h +++ b/src/kcmkwin/kwinrules/kcmrules.h @@ -51,12 +51,19 @@ private slots: private: int editIndex() const; void saveCurrentRule(); + void parseArguments(const QStringList &args); + void createRuleFromProperties(); + Rules *ruleForProperties(const QVariantMap &windowProperties, bool wholeApp) const; private: RuleBookModel *m_ruleBookModel; RulesModel* m_rulesModel; QPersistentModelIndex m_editIndex; + + bool m_alreadyLoaded = false; + QVariantMap m_winProperties; + bool m_wholeApp = false; }; } // namespace diff --git a/src/kcmkwin/kwinrules/main.cpp b/src/kcmkwin/kwinrules/main.cpp index 75bea63992..70929bbfc9 100644 --- a/src/kcmkwin/kwinrules/main.cpp +++ b/src/kcmkwin/kwinrules/main.cpp @@ -1,191 +1,18 @@ /* SPDX-FileCopyrightText: 2004 Lubos Lunak + SPDX-FileCopyrightText: 2018 Nicolas Fella + SPDX-FileCopyrightText: 2020 Ismael Asensio - SPDX-License-Identifier: GPL-2.0-or-later + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL */ -#include #include -#include -#include -#include - -#include "rulebooksettings.h" -#include "rulesdialog.h" -#include "../../rules.h" -#include - -#include -#include -#include -#include +#include +#include #include -Q_DECLARE_METATYPE(NET::WindowType) - -namespace KWin -{ - -static Rules *findRule(const QVector &rules, const QVariantMap &data, bool whole_app) -{ - QByteArray wmclass_class = data.value("resourceClass").toByteArray().toLower(); - QByteArray wmclass_name = data.value("resourceName").toByteArray().toLower(); - QByteArray role = data.value("role").toByteArray().toLower(); - NET::WindowType type = data.value("type").value(); - QString title = data.value("caption").toString(); - QByteArray machine = data.value("clientMachine").toByteArray(); - Rules* best_match = nullptr; - int match_quality = 0; - for (const auto rule : rules) { - // try to find an exact match, i.e. not a generic rule - int quality = 0; - bool generic = true; - if (rule->wmclassmatch != Rules::ExactMatch) - continue; // too generic - if (!rule->matchWMClass(wmclass_class, wmclass_name)) - continue; - // from now on, it matches the app - now try to match for a specific window - if (rule->wmclasscomplete) { - quality += 1; - generic = false; // this can be considered specific enough (old X apps) - } - if (!whole_app) { - if (rule->windowrolematch != Rules::UnimportantMatch) { - quality += rule->windowrolematch == Rules::ExactMatch ? 5 : 1; - generic = false; - } - if (rule->titlematch != Rules::UnimportantMatch) { - quality += rule->titlematch == Rules::ExactMatch ? 3 : 1; - generic = false; - } - if (rule->types != NET::AllTypesMask) { - int bits = 0; - for (unsigned int bit = 1; - bit < 1U << 31; - bit <<= 1) - if (rule->types & bit) - ++bits; - if (bits == 1) - quality += 2; - } - if (generic) // ignore generic rules, use only the ones that are for this window - continue; - } else { - if (rule->types == NET::AllTypesMask) - quality += 2; - } - if (!rule->matchType(type) - || !rule->matchRole(role) - || !rule->matchTitle(title) - || !rule->matchClientMachine(machine, data.value("localhost").toBool())) - continue; - if (quality > match_quality) { - best_match = rule; - match_quality = quality; - } - } - if (best_match != nullptr) - return best_match; - Rules* ret = new Rules; - if (whole_app) { - ret->description = i18n("Application settings for %1", QString::fromLatin1(wmclass_class)); - // TODO maybe exclude some types? If yes, then also exclude them above - // when searching. - ret->types = NET::AllTypesMask; - ret->titlematch = Rules::UnimportantMatch; - ret->clientmachine = machine; // set, but make unimportant - ret->clientmachinematch = Rules::UnimportantMatch; - ret->windowrolematch = Rules::UnimportantMatch; - if (wmclass_name == wmclass_class) { - ret->wmclasscomplete = false; - ret->wmclass = wmclass_class; - ret->wmclassmatch = Rules::ExactMatch; - } else { - // WM_CLASS components differ - perhaps the app got -name argument - ret->wmclasscomplete = true; - ret->wmclass = wmclass_name + ' ' + wmclass_class; - ret->wmclassmatch = Rules::ExactMatch; - } - return ret; - } - ret->description = i18n("Window settings for %1", QString::fromLatin1(wmclass_class)); - if (type == NET::Unknown) - ret->types = NET::NormalMask; - else - ret->types = NET::WindowTypeMask( 1 << type); // convert type to its mask - ret->title = title; // set, but make unimportant - ret->titlematch = Rules::UnimportantMatch; - ret->clientmachine = machine; // set, but make unimportant - ret->clientmachinematch = Rules::UnimportantMatch; - if (!role.isEmpty() - && role != "unknown" && role != "unnamed") { // Qt sets this if not specified - ret->windowrole = role; - ret->windowrolematch = Rules::ExactMatch; - if (wmclass_name == wmclass_class) { - ret->wmclasscomplete = false; - ret->wmclass = wmclass_class; - ret->wmclassmatch = Rules::ExactMatch; - } else { - // WM_CLASS components differ - perhaps the app got -name argument - ret->wmclasscomplete = true; - ret->wmclass = wmclass_name + ' ' + wmclass_class; - ret->wmclassmatch = Rules::ExactMatch; - } - } else { // no role set - if (wmclass_name != wmclass_class) { - ret->wmclasscomplete = true; - ret->wmclass = wmclass_name + ' ' + wmclass_class; - ret->wmclassmatch = Rules::ExactMatch; - } else { - // This is a window that has no role set, and both components of WM_CLASS - // match (possibly only differing in case), which most likely means either - // the application doesn't give a damn about distinguishing its various - // windows, or it's an app that uses role for that, but this window - // lacks it for some reason. Use non-complete WM_CLASS matching, also - // include window title in the matching, and pray it causes many more positive - // matches than negative matches. - ret->titlematch = Rules::ExactMatch; - ret->wmclasscomplete = false; - ret->wmclass = wmclass_class; - ret->wmclassmatch = Rules::ExactMatch; - } - } - return ret; -} - -static void edit(const QVariantMap &data, bool whole_app) -{ - RuleBookSettings settings(KConfig::NoGlobals); - QVector rules = settings.rules(); - Rules *orig_rule = findRule(rules, data, whole_app); - RulesDialog dlg; - if (whole_app) - dlg.setWindowTitle(i18nc("Window caption for the application wide rules dialog", "Edit Application-Specific Settings")); - // dlg.edit() creates new Rules instance if edited - Rules* edited_rule = dlg.edit(orig_rule, data, true); - if (edited_rule == nullptr || edited_rule->isEmpty()) { - rules.removeAll(orig_rule); - delete orig_rule; - if (orig_rule != edited_rule) - delete edited_rule; - } else if (edited_rule != orig_rule) { - int pos = rules.indexOf(orig_rule); - if (pos != -1) - rules[ pos ] = edited_rule; - else - rules.prepend(edited_rule); - delete orig_rule; - } - settings.setRules(rules); - settings.save(); - // Send signal to all kwin instances - QDBusMessage message = - QDBusMessage::createSignal("/KWin", "org.kde.KWin", "reloadConfig"); - QDBusConnection::sessionBus().send(message); - qApp->quit(); -} - -} // namespace +#include +#include int main(int argc, char* argv[]) { @@ -194,48 +21,39 @@ int main(int argc, char* argv[]) KLocalizedString::setApplicationDomain("kcm_kwinrules"); app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); - app.setApplicationDisplayName(i18n("KWin")); app.setApplicationName("kwin_rules_dialog"); - app.setApplicationVersion("1.0"); - bool whole_app = false; - QUuid uuid; - { - QCommandLineParser parser; - parser.setApplicationDescription(i18n("KWin helper utility")); - parser.addOption(QCommandLineOption("uuid", i18n("KWin id of the window for special window settings."), "uuid")); - parser.addOption(QCommandLineOption("whole-app", i18n("Whether the settings should affect all windows of the application."))); - parser.process(app); + app.setWindowIcon(QIcon::fromTheme("preferences-system-windows-actions")); + app.setApplicationVersion("2.0"); - uuid = QUuid::fromString(parser.value("uuid")); - whole_app = parser.isSet("whole-app"); - } + QCommandLineParser parser; + parser.setApplicationDescription(i18n("KWinRules KCM launcher")); + parser.addOption(QCommandLineOption("uuid", i18n("KWin id of the window for special window settings."), "uuid")); + parser.addOption(QCommandLineOption("whole-app", i18n("Whether the settings should affect all windows of the application."))); + parser.process(app); + const QUuid uuid = QUuid::fromString(parser.value("uuid")); + const bool whole_app = parser.isSet("whole-app"); if (uuid.isNull()) { printf("%s\n", qPrintable(i18n("This helper utility is not supposed to be called directly."))); return 1; } - QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"), - QStringLiteral("/KWin"), - QStringLiteral("org.kde.KWin"), - QStringLiteral("getWindowInfo")); - message.setArguments({uuid.toString()}); - QDBusPendingReply async = QDBusConnection::sessionBus().asyncCall(message); - QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, &app); - QObject::connect(callWatcher, &QDBusPendingCallWatcher::finished, &app, - [&whole_app] (QDBusPendingCallWatcher *self) { - QDBusPendingReply reply = *self; - self->deleteLater(); - if (!reply.isValid() || reply.value().isEmpty()) { - qApp->quit(); - return; - } - KWin::edit(reply.value(), whole_app); - } - ); + app.setApplicationDisplayName((whole_app) ? i18nc("Window caption for the application wide rules dialog", "Edit Application-Specific Settings") + : i18n("Edit Window-Specific Settings")); + QStringList kcm_args; + kcm_args << QStringLiteral("uuid=%1").arg(uuid.toString()); + if (whole_app) { + kcm_args << QStringLiteral("whole-app"); + } + KCMultiDialog *dialog = new KCMultiDialog; + dialog->addModule(QStringLiteral("kcm_kwinrules"), kcm_args); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); + + app.setQuitOnLastWindowClosed(true); return app.exec(); } diff --git a/src/kcmkwin/kwinrules/package/contents/ui/RulesEditor.qml b/src/kcmkwin/kwinrules/package/contents/ui/RulesEditor.qml index 97b4d236be..9521e0a86f 100644 --- a/src/kcmkwin/kwinrules/package/contents/ui/RulesEditor.qml +++ b/src/kcmkwin/kwinrules/package/contents/ui/RulesEditor.qml @@ -16,9 +16,7 @@ import org.kde.kcms.kwinrules 1.0 ScrollViewKCM { id: rulesEditor - property var rulesModel: kcm.rulesModel - - title: rulesModel.description + title: kcm.rulesModel.description view: ListView { id: rulesView @@ -73,7 +71,7 @@ ScrollViewKCM { visible: warningList.count > 0 Repeater { id: warningList - model: rulesModel.warningMessages + model: kcm.rulesModel.warningMessages delegate: Kirigami.InlineMessage { text: modelData @@ -104,8 +102,8 @@ ScrollViewKCM { enabled: !propertySheet.sheetOpen && !errorSheet.sheetOpen onClicked: { overlayModel.onlySuggestions = true; - rulesModel.detectWindowProperties(Math.max(delaySpin.value * 1000, - Kirigami.Units.shortDuration)); + kcm.rulesModel.detectWindowProperties(Math.max(delaySpin.value * 1000, + Kirigami.Units.shortDuration)); } } QQC2.SpinBox { @@ -123,7 +121,7 @@ ScrollViewKCM { } Connections { - target: rulesModel + target: kcm.rulesModel function onShowSuggestions() { overlayModel.onlySuggestions = true; propertySheet.sheetOpen = true; @@ -268,7 +266,7 @@ ScrollViewKCM { KSortFilterProxyModel { id: enabledRulesModel - sourceModel: rulesModel + sourceModel: kcm.rulesModel filterRowCallback: (source_row, source_parent) => { var index = sourceModel.index(source_row, 0, source_parent); return sourceModel.data(index, RulesModel.EnabledRole); @@ -277,7 +275,7 @@ ScrollViewKCM { KSortFilterProxyModel { id: overlayModel - sourceModel: rulesModel + sourceModel: kcm.rulesModel property bool onlySuggestions: false onOnlySuggestionsChanged: { diff --git a/src/kcmkwin/kwinrules/rulebookmodel.cpp b/src/kcmkwin/kwinrules/rulebookmodel.cpp index 167a7d29e6..54e5534721 100644 --- a/src/kcmkwin/kwinrules/rulebookmodel.cpp +++ b/src/kcmkwin/kwinrules/rulebookmodel.cpp @@ -1,4 +1,5 @@ /* + SPDX-FileCopyrightText: 2004 Lubos Lunak SPDX-FileCopyrightText: 2020 Ismael Asensio SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL @@ -6,6 +7,7 @@ #include "rulebookmodel.h" +#include namespace KWin { @@ -170,4 +172,88 @@ void RuleBookModel::save() m_ruleBook->save(); } +// Code adapted from original `findRule()` method in `kwin_rules_dialog::main.cpp` +QModelIndex RuleBookModel::findRuleWithProperties(const QVariantMap &info, bool wholeApp) const +{ + const QByteArray wmclass_class = info.value("resourceClass").toByteArray().toLower(); + const QByteArray wmclass_name = info.value("resourceName").toByteArray().toLower(); + const QByteArray role = info.value("role").toByteArray().toLower(); + const NET::WindowType type = static_cast(info.value("type").toInt()); + const QString title = info.value("caption").toString(); + const QByteArray machine = info.value("clientMachine").toByteArray(); + const bool isLocalHost = info.value("localhost").toBool(); + + int bestMatchRow = -1; + int match_quality = 0; + + for (int row = 0; row < m_rules.count(); row++) { + Rules *rule = m_rules.at(row); + + /* clang-format off */ + // If the rule doesn't match try the next one + if (!rule->matchWMClass(wmclass_class, wmclass_name) + || !rule->matchType(type) + || !rule->matchRole(role) + || !rule->matchTitle(title) + || !rule->matchClientMachine(machine, isLocalHost)) { + continue; + } + /* clang-format on */ + + if (rule->wmclassmatch != Rules::ExactMatch) { + continue; // too generic + } + + // Now that the rule matches the window, check the quality of the match + // It stablishes a quality depending on the match policy of the rule + int quality = 0; + bool generic = true; + + // from now on, it matches the app - now try to match for a specific window + if (rule->wmclasscomplete) { + quality += 1; + generic = false; // this can be considered specific enough (old X apps) + } + if (!wholeApp) { + if (rule->windowrolematch != Rules::UnimportantMatch) { + quality += rule->windowrolematch == Rules::ExactMatch ? 5 : 1; + generic = false; + } + if (rule->titlematch != Rules::UnimportantMatch) { + quality += rule->titlematch == Rules::ExactMatch ? 3 : 1; + generic = false; + } + if (rule->types != NET::AllTypesMask) { + // Checks that type fits the mask, and only one of the types + int bits = 0; + for (unsigned int bit = 1; bit < 1U << 31; bit <<= 1) { + if (rule->types & bit) { + ++bits; + } + } + if (bits == 1) { + quality += 2; + } + } + if (generic) { // ignore generic rules, use only the ones that are for this window + continue; + } + } else { + if (rule->types == NET::AllTypesMask) { + quality += 2; + } + } + + if (quality > match_quality) { + bestMatchRow = row; + match_quality = quality; + } + } + + if (bestMatchRow < 0) { + return QModelIndex(); + } + return index(bestMatchRow); +} + } // namespace diff --git a/src/kcmkwin/kwinrules/rulebookmodel.h b/src/kcmkwin/kwinrules/rulebookmodel.h index aaf39378d2..1dde70db4a 100644 --- a/src/kcmkwin/kwinrules/rulebookmodel.h +++ b/src/kcmkwin/kwinrules/rulebookmodel.h @@ -42,6 +42,8 @@ public: void load(); void save(); + QModelIndex findRuleWithProperties(const QVariantMap &info, bool wholeApp) const; + private: RuleBookSettings *m_ruleBook; QVector m_rules; diff --git a/src/kcmkwin/kwinrules/rulesdialog.cpp b/src/kcmkwin/kwinrules/rulesdialog.cpp deleted file mode 100644 index c59a473411..0000000000 --- a/src/kcmkwin/kwinrules/rulesdialog.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/* - SPDX-FileCopyrightText: 2004 Lubos Lunak - SPDX-FileCopyrightText: 2020 Ismael Asensio - - SPDX-License-Identifier: GPL-2.0-or-later -*/ - -#include "rulesdialog.h" - -#include -#include -#include -#include -#include - -#include - - -namespace KWin -{ - -RulesDialog::RulesDialog(QWidget* parent, const char* name) - : QDialog(parent) - , m_rulesModel(new RulesModel(this)) -{ - setObjectName(name); - setModal(true); - setWindowTitle(i18n("Edit Window-Specific Settings")); - setWindowIcon(QIcon::fromTheme("preferences-system-windows-actions")); - setLayout(new QVBoxLayout); - - // Init RuleEditor QML QuickView - QQuickView *quickView = new QQuickView(); - quickView->setSource(QUrl::fromLocalFile(QStandardPaths::locate( - QStandardPaths::GenericDataLocation, - QStringLiteral("kpackage/kcms/kcm_kwinrules/contents/ui/RulesEditor.qml")))); - quickView->setResizeMode(QQuickView::SizeRootObjectToView); - quickView->rootObject()->setProperty("rulesModel", QVariant::fromValue(m_rulesModel)); - - m_quickWidget = QWidget::createWindowContainer(quickView, this); - m_quickWidget->setMinimumSize(QSize(650, 575)); - layout()->addWidget(m_quickWidget); - - QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); - layout()->addWidget(buttons); -} - -// window is set only for Alt+F3/Window-specific settings, because the dialog -// is then related to one specific window -Rules* RulesDialog::edit(Rules* r, const QVariantMap& info, bool show_hints) -{ - Q_UNUSED(show_hints); - - m_rules = r; - - m_rulesModel->importFromRules(m_rules); - if (!info.isEmpty()) { - m_rulesModel->setSuggestedProperties(info); - } - - exec(); - - return m_rules; -} - -void RulesDialog::accept() -{ - m_rules = m_rulesModel->exportToRules(); - QDialog::accept(); -} - -} diff --git a/src/kcmkwin/kwinrules/rulesdialog.h b/src/kcmkwin/kwinrules/rulesdialog.h deleted file mode 100644 index 78c4447055..0000000000 --- a/src/kcmkwin/kwinrules/rulesdialog.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - SPDX-FileCopyrightText: 2004 Lubos Lunak - SPDX-FileCopyrightText: 2020 Ismael Asensio - - SPDX-License-Identifier: GPL-2.0-or-later -*/ - -#ifndef KWIN_RULESDIALOG_H -#define KWIN_RULESDIALOG_H - -#include "rulesmodel.h" -#include "../../rules.h" - -#include - -namespace KWin -{ - -class Rules; - -class RulesDialog : public QDialog -{ - Q_OBJECT - -public: - explicit RulesDialog(QWidget* parent = nullptr, const char* name = nullptr); - - Rules* edit(Rules* r, const QVariantMap& info, bool show_hints); - -protected: - void accept() override; - -private: - RulesModel* m_rulesModel; - QWidget *m_quickWidget; - Rules* m_rules; -}; - -} // namespace - -#endif // KWIN_RULESDIALOG_H