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"