File indexing completed on 2024-05-05 09:57:15

0001 /*
0002     SPDX-FileCopyrightText: 2006 Aaron Seigo <aseigo@kde.org>
0003     SPDX-FileCopyrightText: 2014 Vishesh Handa <vhanda@kde.org>
0004     SPDX-FileCopyrightText: 2016-2020 Harald Sitter <sitter@kde.org>
0005     SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
0006     SPDX-FileCopyrightText: 2022 Natalie Clarius <natalie_clarius@yahoo.de>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-only
0009 */
0010 
0011 #include "systemsettingsrunner.h"
0012 
0013 #include <algorithm>
0014 
0015 #include <QMimeData>
0016 #include <QUrl>
0017 #include <QUrlQuery>
0018 
0019 #include <PlasmaActivities/ResourceInstance>
0020 #include <KIO/CommandLauncherJob>
0021 #include <KLocalizedString>
0022 #include <KNotificationJobUiDelegate>
0023 #include <KSycoca>
0024 
0025 #include "../core/kcmmetadatahelpers.h"
0026 
0027 K_PLUGIN_CLASS_WITH_JSON(SystemsettingsRunner, "systemsettingsrunner.json")
0028 
0029 SystemsettingsRunner::SystemsettingsRunner(QObject *parent, const KPluginMetaData &metaData)
0030     : KRunner::AbstractRunner(parent, metaData)
0031 {
0032     addSyntax(QStringLiteral(":q:"), i18n("Finds system settings modules whose names or descriptions match :q:"));
0033     connect(this, &SystemsettingsRunner::prepare, this, [this]() {
0034         m_modules = findKCMsMetaData(MetaDataSource::All);
0035     });
0036     connect(this, &SystemsettingsRunner::teardown, this, [this]() {
0037         m_modules.clear();
0038     });
0039 }
0040 
0041 void SystemsettingsRunner::match(KRunner::RunnerContext &context)
0042 {
0043     QList<KRunner::QueryMatch> matches;
0044     const QString query = context.query();
0045     const QStringList queryWords{query.split(QLatin1Char(' '))};
0046     for (const KPluginMetaData &data : std::as_const(m_modules)) {
0047         qreal relevance = -1;
0048         const auto checkMatchAndRelevance = [&query, &relevance, &queryWords](const QString &value, qreal relevanceValue) {
0049             if (value.startsWith(query, Qt::CaseInsensitive)) {
0050                 relevance = relevanceValue + 0.1;
0051                 return true;
0052             }
0053             for (const QString &queryWord : queryWords) {
0054                 if (relevance == -1 && queryWord.length() > 3 && value.contains(queryWord, Qt::CaseInsensitive)) {
0055                     relevance = relevanceValue;
0056                     return true;
0057                 }
0058             }
0059             return false;
0060         };
0061 
0062         const QString name = data.name();
0063         const QString description = data.description();
0064         const QStringList keywords = data.value(QStringLiteral("X-KDE-Keywords")).split(QLatin1Char(','));
0065         // check for matches and set relevance
0066         if (query.length() < 3) {
0067             if (name.startsWith(query, Qt::CaseInsensitive)) {
0068                 relevance = 0.9;
0069             } else {
0070                 continue;
0071             }
0072         } else if (checkMatchAndRelevance(name, 0.8)) { // name starts with query or contains any query word
0073         } else if (checkMatchAndRelevance(description, 0.5)) { // description starts with query or contains any query word
0074         } else if (std::any_of(keywords.begin(), keywords.end(), [&query](const QString &keyword) {
0075                        return keyword.startsWith(query, Qt::CaseInsensitive);
0076                    })) {
0077             if (keywords.contains(query, Qt::CaseInsensitive)) { // any of the keywords matches query
0078                 relevance = 0.5;
0079             } else { // any of the keywords starts with query
0080                 relevance = 0.2;
0081             }
0082         } else { // none of the properties matches
0083             continue; // skip this KCM
0084         }
0085 
0086         KRunner::QueryMatch::CategoryRelevance categoryRelevance = KRunner::QueryMatch::CategoryRelevance::Low;
0087         if (name.compare(query, Qt::CaseInsensitive) == 0) { // name matches exactly
0088             categoryRelevance = KRunner::QueryMatch::CategoryRelevance::Highest;
0089         } else if (name.startsWith(query, Qt::CaseInsensitive) || description.startsWith(query, Qt::CaseInsensitive)) { // name or description matches as start
0090             categoryRelevance = KRunner::QueryMatch::CategoryRelevance::Moderate;
0091         } else if (keywords.contains(query, Qt::CaseInsensitive)) { // any of the keywords matches exactly
0092             categoryRelevance = KRunner::QueryMatch::CategoryRelevance::Moderate;
0093         }
0094 
0095         KRunner::QueryMatch match(this);
0096         match.setText(name);
0097         match.setUrls({QUrl(QLatin1String("applications://") + data.pluginId())});
0098         match.setSubtext(description);
0099         match.setIconName(data.iconName()); // If it is not set, KRunner will fall back to the runner's icon
0100         match.setId(data.pluginId()); // KRunner needs the id to adjust the relevance for often launched KCMs
0101         match.setData(QVariant::fromValue(data));
0102         match.setRelevance(relevance);
0103         match.setCategoryRelevance(categoryRelevance);
0104 
0105         if (isKinfoCenterKcm(data)) {
0106             match.setMatchCategory(i18nd("systemsettings", "System Information"));
0107         } else {
0108             match.setMatchCategory(i18nd("systemsettings", "System Settings"));
0109         }
0110 
0111         matches << match;
0112     }
0113     context.addMatches(matches);
0114 }
0115 
0116 void SystemsettingsRunner::run(const KRunner::RunnerContext & /*context*/, const KRunner::QueryMatch &match)
0117 {
0118     const auto data = match.data().value<KPluginMetaData>();
0119 
0120     KIO::CommandLauncherJob *job = nullptr;
0121     if (isKinfoCenterKcm(data)) {
0122         job = new KIO::CommandLauncherJob(QStringLiteral("kinfocenter"), {data.pluginId()});
0123         job->setDesktopName(QStringLiteral("org.kde.kinfocenter"));
0124     } else if (!data.value(QStringLiteral("X-KDE-System-Settings-Parent-Category")).isEmpty()) {
0125         job = new KIO::CommandLauncherJob(QStringLiteral("systemsettings"), {data.pluginId()});
0126         job->setDesktopName(QStringLiteral("systemsettings"));
0127     } else {
0128         // Systemsettings only uses predefined namespaces that kcmshell5/6 also knows about
0129         job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell6"), {data.pluginId()});
0130     }
0131     auto delegate = new KNotificationJobUiDelegate;
0132     delegate->setAutoErrorHandlingEnabled(true);
0133     job->setUiDelegate(delegate);
0134     job->start();
0135 
0136     KActivities::ResourceInstance::notifyAccessed(QUrl(QStringLiteral("systemsettings:") + data.pluginId()), QStringLiteral("org.kde.krunner"));
0137 }
0138 
0139 QMimeData *SystemsettingsRunner::mimeDataForMatch(const KRunner::QueryMatch &match)
0140 {
0141     const auto value = match.data().value<KPluginMetaData>();
0142     if (value.isValid()) {
0143         if (KService::Ptr ptr = KService::serviceByStorageId(value.pluginId() + QLatin1String(".desktop"))) {
0144             auto data = new QMimeData();
0145             data->setUrls(QList<QUrl>{QUrl::fromLocalFile(ptr->entryPath())});
0146             return data;
0147         }
0148     }
0149     return nullptr;
0150 }
0151 
0152 #include "systemsettingsrunner.moc"
0153 
0154 #include "moc_systemsettingsrunner.cpp"