File indexing completed on 2024-04-21 16:22:52

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 <QDebug>
0016 #include <QDir>
0017 #include <QIcon>
0018 #include <QMimeData>
0019 #include <QMutexLocker>
0020 #include <QStandardPaths>
0021 #include <QUrl>
0022 #include <QUrlQuery>
0023 
0024 #include <KActivities/ResourceInstance>
0025 #include <KIO/CommandLauncherJob>
0026 #include <KLocalizedString>
0027 #include <KNotificationJobUiDelegate>
0028 #include <KSycoca>
0029 
0030 #include "../core/kcmmetadatahelpers.h"
0031 
0032 K_PLUGIN_CLASS_WITH_JSON(SystemsettingsRunner, "systemsettingsrunner.json")
0033 
0034 SystemsettingsRunner::SystemsettingsRunner(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args)
0035     : Plasma::AbstractRunner(parent, metaData, args)
0036 {
0037     setObjectName(QStringLiteral("SystemsettingsRunner"));
0038     setPriority(AbstractRunner::HighestPriority);
0039 
0040     addSyntax(Plasma::RunnerSyntax(QStringLiteral(":q:"), i18n("Finds system settings modules whose names or descriptions match :q:")));
0041     // teardown is called in the main thread when all matches are over
0042     connect(this, &SystemsettingsRunner::teardown, this, [this]() {
0043         m_modules.clear();
0044     });
0045 }
0046 
0047 void SystemsettingsRunner::match(Plasma::RunnerContext &context)
0048 {
0049     {
0050         // The match method is called multithreaded, to make sure we do not start multiple plugin searches or
0051         // write to the list in different threads the lock is used
0052         QMutexLocker lock(&m_mutex);
0053         if (m_modules.isEmpty()) {
0054             KSycoca::disableAutoRebuild();
0055             m_modules = findKCMsMetaData(MetaDataSource::All, false);
0056         }
0057     }
0058     matchNameKeyword(context);
0059 }
0060 
0061 void SystemsettingsRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match)
0062 {
0063     Q_UNUSED(context)
0064 
0065     const auto data = match.data().value<KPluginMetaData>();
0066 
0067     KIO::CommandLauncherJob *job = nullptr;
0068     if (isKinfoCenterKcm(data)) {
0069         job = new KIO::CommandLauncherJob(QStringLiteral("kinfocenter"), {data.pluginId()});
0070         job->setDesktopName(QStringLiteral("org.kde.kinfocenter"));
0071     } else if (!data.value(QStringLiteral("X-KDE-System-Settings-Parent-Category")).isEmpty()) {
0072         job = new KIO::CommandLauncherJob(QStringLiteral("systemsettings"), {data.pluginId()});
0073         job->setDesktopName(QStringLiteral("systemsettings"));
0074     } else {
0075         // If we have created the KPluginMetaData from a desktop file KCMShell needs the pluginId, otherwise we can give
0076         // it the absolute path to the plugin. That works in any case, see commit 866d730fd098775f6b16cc8ba15974af80700d12 in kde-cli-tools
0077         bool isDesktopFile = data.metaDataFileName().endsWith(QLatin1String(".desktop"));
0078         job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell5"), {isDesktopFile ? data.pluginId() : data.fileName()});
0079     }
0080     auto *delegate = new KNotificationJobUiDelegate;
0081     delegate->setAutoErrorHandlingEnabled(true);
0082     job->setUiDelegate(delegate);
0083     job->start();
0084 
0085     KActivities::ResourceInstance::notifyAccessed(QUrl(QStringLiteral("systemsettings:") + data.pluginId()), QStringLiteral("org.kde.krunner"));
0086 }
0087 
0088 QMimeData *SystemsettingsRunner::mimeDataForMatch(const Plasma::QueryMatch &match)
0089 {
0090     const auto value = match.data().value<KPluginMetaData>();
0091     if (value.isValid()) {
0092         if (value.metaDataFileName().endsWith(QLatin1String(".desktop"))) {
0093             auto *data = new QMimeData();
0094             data->setUrls(QList<QUrl>{QUrl::fromLocalFile(value.metaDataFileName())});
0095             return data;
0096         }
0097         if (KService::Ptr ptr = KService::serviceByStorageId(value.pluginId() + QLatin1String(".desktop"))) {
0098             auto *data = new QMimeData();
0099             data->setUrls(QList<QUrl>{QUrl::fromLocalFile(ptr->entryPath())});
0100             return data;
0101         }
0102     }
0103     return nullptr;
0104 }
0105 
0106 void SystemsettingsRunner::setupMatch(const KPluginMetaData &data, Plasma::QueryMatch &match)
0107 {
0108     const QString name = data.name();
0109 
0110     match.setText(name);
0111     if (data.metaDataFileName().endsWith(QLatin1String(".desktop"))) {
0112         QUrl url(data.metaDataFileName());
0113         url.setScheme(QStringLiteral("applications"));
0114         match.setUrls({url});
0115     } else {
0116         QUrl url(data.pluginId());
0117         url.setScheme(QStringLiteral("applications"));
0118         match.setUrls({url});
0119     }
0120     match.setSubtext(data.description());
0121 
0122     if (!data.iconName().isEmpty()) {
0123         match.setIconName(data.iconName());
0124     }
0125     match.setId(data.pluginId()); // KRunner needs the id to adjust the relevance for often launched KCMs
0126     match.setData(QVariant::fromValue(data));
0127 }
0128 
0129 void SystemsettingsRunner::matchNameKeyword(Plasma::RunnerContext &ctx)
0130 {
0131     QList<Plasma::QueryMatch> matches;
0132     const QString query = ctx.query();
0133 
0134     for (const KPluginMetaData &data : qAsConst(m_modules)) {
0135         const QString name = data.name();
0136         const QString description = data.description();
0137         const QStringList keywords = data.value(QStringLiteral("X-KDE-Keywords")).split(QLatin1Char(','));
0138 
0139         Plasma::QueryMatch match(this);
0140         setupMatch(data, match);
0141         qreal relevance = -1;
0142         Plasma::QueryMatch::Type type = Plasma::QueryMatch::CompletionMatch;
0143 
0144         auto checkMatchAndRelevance = [query, data, &relevance](const QString &value, qreal relevanceValue) {
0145             if (value.startsWith(query, Qt::CaseInsensitive)) {
0146                 relevance = relevanceValue + 0.1;
0147                 return true;
0148             }
0149             for (const QString &queryWord : query.split(QLatin1Char(' '))) {
0150                 if (relevance == -1 && queryWord.length() > 3 && value.contains(queryWord, Qt::CaseInsensitive)) {
0151                     relevance = relevanceValue;
0152                     return true;
0153                 }
0154             }
0155             return false;
0156         };
0157 
0158         // check for matches and set relevance
0159         if (checkMatchAndRelevance(name, 0.8)) { // name starts with query or contains any query word
0160         } else if (checkMatchAndRelevance(description, 0.5)) { // description starts with query or contains any query word
0161         } else if (std::any_of(keywords.begin(), keywords.end(), [&query](const QString &keyword) {
0162                        return keyword.startsWith(query, Qt::CaseInsensitive);
0163                    })) {
0164             if (keywords.contains(query, Qt::CaseInsensitive)) { // any of the keywords matches query
0165                 relevance = 0.5;
0166             } else { // any of the keywords starts with query
0167                 relevance = 0.2;
0168             }
0169         } else { // none of the properties matches
0170             continue; // skip this KCM
0171         }
0172 
0173         // set type
0174         if (name.compare(query, Qt::CaseInsensitive) == 0) { // name matches exactly
0175             type = Plasma::QueryMatch::ExactMatch;
0176         } else if (name.startsWith(query, Qt::CaseInsensitive) || description.startsWith(query, Qt::CaseInsensitive)) { // name or description matches as start
0177             type = Plasma::QueryMatch::PossibleMatch;
0178         } else if (keywords.contains(query, Qt::CaseInsensitive)) { // any of the keywords matches exactly
0179             type = Plasma::QueryMatch::PossibleMatch;
0180         }
0181 
0182         match.setRelevance(relevance);
0183         match.setType(type);
0184 
0185         if (isKinfoCenterKcm(data)) {
0186             match.setMatchCategory(i18n("System Information"));
0187         } else {
0188             match.setMatchCategory(i18n("System Settings"));
0189         }
0190 
0191         matches << match;
0192     }
0193     ctx.addMatches(matches);
0194 }
0195 
0196 #include "systemsettingsrunner.moc"