File indexing completed on 2024-04-14 15:42:34

0001 /*
0002  *   SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
0003  *   SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
0004  *
0005  *   SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include <KAuthorized>
0009 #include <KCModuleData>
0010 #include <KFileUtils>
0011 #include <KPluginFactory>
0012 #include <KPluginMetaData>
0013 #include <QGuiApplication>
0014 #include <QStandardPaths>
0015 
0016 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0017 #include <KServiceTypeTrader>
0018 #endif
0019 #include <kservice.h>
0020 #include <set>
0021 
0022 enum MetaDataSource {
0023     SystemSettings = 1,
0024     KInfoCenter = 2,
0025     All = SystemSettings | KInfoCenter,
0026 };
0027 
0028 inline QList<KPluginMetaData> findExternalKCMModules(MetaDataSource source)
0029 {
0030     const auto findExternalModulesInFilesystem = [](const QString &sourceNamespace) {
0031         const QString sourceNamespaceDirName = QStringLiteral("plasma/%1/externalmodules").arg(sourceNamespace);
0032         const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, sourceNamespaceDirName, QStandardPaths::LocateDirectory);
0033         const QStringList files = KFileUtils::findAllUniqueFiles(dirs, QStringList{QStringLiteral("*.desktop")});
0034 
0035         QList<KPluginMetaData> metaDataList;
0036         for (const QString &file : files) {
0037             KService service(file);
0038             QJsonObject kplugin;
0039             kplugin.insert(QLatin1String("Name"), service.name());
0040             kplugin.insert(QLatin1String("Icon"), service.icon());
0041             kplugin.insert(QLatin1String("Description"), service.comment());
0042 
0043             QJsonObject root;
0044             root.insert(QLatin1String("KPlugin"), kplugin);
0045             root.insert(QLatin1String("X-KDE-Weight"), service.property(QStringLiteral("X-KDE-Weight")).toInt());
0046             root.insert(QLatin1String("X-KDE-KInfoCenter-Category"), service.property(QStringLiteral("X-KDE-KInfoCenter-Category")).toString());
0047             root.insert(QLatin1String("X-KDE-System-Settings-Category"), service.property(QStringLiteral("X-KDE-System-Settings-Category")).toString());
0048             root.insert(QLatin1String("IsExternalApp"), true);
0049 
0050             metaDataList << KPluginMetaData(root, file);
0051         }
0052         return metaDataList;
0053     };
0054 
0055     QList<KPluginMetaData> metaDataList;
0056     if (source & SystemSettings) {
0057 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0058         const auto servicesList = KServiceTypeTrader::self()->query(QStringLiteral("SystemSettingsExternalApp"));
0059         for (const auto &s : servicesList) {
0060             const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kservices5/") + s->entryPath());
0061             metaDataList << KPluginMetaData::fromDesktopFile(path);
0062         }
0063 #endif
0064         metaDataList << findExternalModulesInFilesystem(QStringLiteral("systemsettings"));
0065     }
0066 
0067     if (source & KInfoCenter) {
0068         metaDataList << findExternalModulesInFilesystem(QStringLiteral("kinfocenter"));
0069     }
0070 
0071     return metaDataList;
0072 }
0073 
0074 inline QList<KPluginMetaData> findKCMsMetaData(MetaDataSource source, bool useSystemsettingsConstraint = true)
0075 {
0076     QList<KPluginMetaData> modules;
0077     std::set<QString> uniquePluginIds;
0078 
0079     auto filter = [](const KPluginMetaData &data) {
0080         const auto supportedPlatforms = data.value(QStringLiteral("X-KDE-OnlyShowOnQtPlatforms"), QStringList());
0081         return supportedPlatforms.isEmpty() || supportedPlatforms.contains(qGuiApp->platformName());
0082     };
0083 
0084     // We need the exist calls because otherwise the trader language aborts if the property doesn't exist and the second part of the or is not evaluated
0085 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0086     KService::List services;
0087 #endif
0088     QVector<KPluginMetaData> metaDataList = KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms"), filter);
0089     if (source & SystemSettings) {
0090         metaDataList << KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms/systemsettings"), filter);
0091         metaDataList << KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms/systemsettings_qwidgets"), filter);
0092 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0093         services +=
0094             KServiceTypeTrader::self()->query(QStringLiteral("KCModule"),
0095                                               useSystemsettingsConstraint ? QStringLiteral("[X-KDE-System-Settings-Parent-Category] != ''") : QString());
0096 #endif
0097     }
0098     if (source & KInfoCenter) {
0099         metaDataList << KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms/kinfocenter"), filter);
0100 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0101         services += KServiceTypeTrader::self()->query(QStringLiteral("KCModule"), QStringLiteral("[X-KDE-ParentApp] == 'kinfocenter'"));
0102 #endif
0103     }
0104     for (const auto &m : qAsConst(metaDataList)) {
0105         // We check both since porting a module to loading view KPluginMetaData drops ".desktop" from the pluginId()
0106         if (!KAuthorized::authorizeControlModule(m.pluginId()) || !KAuthorized::authorizeControlModule(m.pluginId().append(QStringLiteral(".desktop")))) {
0107             continue;
0108         }
0109         modules << m;
0110         const bool inserted = uniquePluginIds.insert(m.pluginId()).second;
0111         if (!inserted) {
0112             qWarning() << "the plugin" << m.pluginId() << " was found in multiple namespaces";
0113         }
0114     }
0115 
0116 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0117     for (const auto &s : qAsConst(services)) {
0118         if (!s->noDisplay() && !s->exec().isEmpty() && KAuthorized::authorizeControlModule(s->menuId())) {
0119             const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kservices5/") + s->entryPath());
0120             const KPluginMetaData data = KPluginMetaData::fromDesktopFile(path);
0121             const bool inserted = uniquePluginIds.insert(data.pluginId()).second;
0122             if (inserted) {
0123                 modules << data;
0124             }
0125         }
0126     }
0127 #endif
0128     std::stable_sort(modules.begin(), modules.end(), [](const KPluginMetaData &m1, const KPluginMetaData &m2) {
0129         return QString::compare(m1.pluginId(), m2.pluginId(), Qt::CaseInsensitive) < 0;
0130     });
0131     return modules;
0132 }
0133 
0134 inline bool isKinfoCenterKcm(const KPluginMetaData &data)
0135 {
0136     // KServiceTypeTrader compat
0137     if (data.value(QStringLiteral("X-KDE-ParentApp")) == QLatin1String("kinfocenter")) {
0138         return true;
0139     }
0140     // external module or a KCM in the namespace
0141     if (data.fileName().contains(QLatin1String("/kinfocenter/"))) {
0142         return true;
0143     }
0144     return false;
0145 }
0146 
0147 inline KCModuleData *loadModuleData(const KPluginMetaData &data)
0148 {
0149     if (!data.isValid()) {
0150         return nullptr;
0151     }
0152     KCModuleData *moduleData = nullptr;
0153     auto loadFromMetaData = [&moduleData](const KPluginMetaData &data) {
0154         if (data.isValid()) {
0155             auto factory = KPluginFactory::loadFactory(data).plugin;
0156             moduleData = factory ? factory->create<KCModuleData>() : nullptr;
0157         }
0158     };
0159     loadFromMetaData(data);
0160     if (!moduleData) {
0161         loadFromMetaData(KPluginMetaData(QStringLiteral("kcms/") + data.fileName()));
0162     }
0163     return moduleData;
0164 }