File indexing completed on 2024-03-24 15:25:27

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 1999 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
0004     SPDX-FileCopyrightText: 2000 Matthias Elter <elter@kde.org>
0005     SPDX-FileCopyrightText: 2003, 2004, 2006 Matthias Kretz <kretz@kde.org>
0006     SPDX-FileCopyrightText: 2004 Frans Englich <frans.englich@telia.com>
0007     SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
0008 
0009     SPDX-License-Identifier: LGPL-2.0-only
0010 */
0011 
0012 #include "kcmoduleloader.h"
0013 #include "kcmoduledata.h"
0014 #include "kcmoduleqml_p.h"
0015 #include <kcmutils_debug.h>
0016 
0017 #include <QLabel>
0018 #include <QLibrary>
0019 #include <QVBoxLayout>
0020 
0021 #include <KAboutData>
0022 #include <KAuthorized>
0023 #include <KCModule>
0024 #include <KLocalizedString>
0025 #include <KMessageBox>
0026 #include <KPluginFactory>
0027 
0028 #include <KQuickAddons/ConfigModule>
0029 
0030 using namespace KCModuleLoader;
0031 
0032 /***************************************************************/
0033 /**
0034  * When something goes wrong in loading the module, this one
0035  * jumps in as a "dummy" module.
0036  */
0037 class KCMError : public KCModule
0038 {
0039 public:
0040     KCMError(const QString &msg, const QString &details, QWidget *parent)
0041         : KCModule(parent)
0042     {
0043         QVBoxLayout *topLayout = new QVBoxLayout(this);
0044         QLabel *lab = new QLabel(msg, this);
0045         lab->setWordWrap(true);
0046         topLayout->addWidget(lab);
0047         lab = new QLabel(details, this);
0048         lab->setWordWrap(true);
0049         topLayout->addWidget(lab);
0050     }
0051 };
0052 
0053 KCModule *KCModuleLoader::loadModule(const KPluginMetaData &metaData, QWidget *parent, const QVariantList &args)
0054 {
0055     if (!KAuthorized::authorizeControlModule(metaData.pluginId())) {
0056         return reportError(ErrorReporting::Inline,
0057                            i18n("The module %1 is disabled.", metaData.pluginId()),
0058                            i18n("The module has been disabled by the system administrator."),
0059                            parent);
0060     }
0061     const QVariantList args2 = QVariantList(args) << metaData.rawData().value(QStringLiteral("X-KDE-KCM-Args")).toArray();
0062 
0063     auto factoryResult = KPluginFactory::loadFactory(metaData);
0064     QString pluginKeyword = metaData.value(QStringLiteral("X-KDE-PluginKeyword"));
0065     if (!factoryResult) {
0066         // This is where QML KCMs used to be before the namespaces were changed based on https://phabricator.kde.org/T14517
0067         // But the X-KDE-Library did not reflect this change, instead the "kcms" namespace was prepended
0068         if (KPluginMetaData data(QLatin1String("kcms/") + metaData.fileName()); data.isValid()) {
0069             factoryResult = KPluginFactory::loadFactory(data);
0070             pluginKeyword.clear();
0071         }
0072     }
0073 
0074     if (!factoryResult) {
0075         return reportError(ErrorReporting::Inline, factoryResult.errorString, QString(), parent);
0076     }
0077 
0078     KPluginFactory *factory = factoryResult.plugin;
0079     QT_WARNING_PUSH
0080     QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
0081     QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
0082 
0083 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 90)
0084     const auto qmlKCMResult = factory->create<KQuickAddons::ConfigModule>(pluginKeyword, parent, args2);
0085 #else
0086     const auto qmlKCMResult = factory->create<KQuickAddons::ConfigModule>(parent, args2);
0087 #endif
0088 
0089     if (qmlKCMResult) {
0090         std::unique_ptr<KQuickAddons::ConfigModule> kcm(qmlKCMResult);
0091 
0092         if (!kcm->mainUi()) {
0093             return reportError(ErrorReporting::Inline, i18n("Error loading QML file."), kcm->errorString(), parent);
0094         }
0095         qCDebug(KCMUTILS_LOG) << "loaded KCM" << factory->metaData().pluginId() << "from path" << factory->metaData().fileName();
0096         return new KCModuleQml(std::move(kcm), parent, args2);
0097     }
0098 
0099 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 90)
0100     const auto kcmoduleResult = factory->create<KCModule>(pluginKeyword, parent, args2);
0101 #else
0102     const auto kcmoduleResult = factory->create<KCModule>(parent, args2);
0103 #endif
0104     QT_WARNING_POP
0105 
0106     if (kcmoduleResult) {
0107         qCDebug(KCMUTILS_LOG) << "loaded KCM" << factory->metaData().pluginId() << "from path" << factory->metaData().fileName();
0108         return kcmoduleResult;
0109     }
0110 
0111     return reportError(ErrorReporting::Inline, QString(), QString(), parent);
0112 }
0113 
0114 KCModule *KCModuleLoader::reportError(ErrorReporting report, const QString &text, const QString &details, QWidget *parent)
0115 {
0116     QString realDetails = details;
0117     if (realDetails.isNull()) {
0118         realDetails = i18n(
0119             "<qt><p>Possible reasons:<ul><li>An error occurred during your last "
0120             "system upgrade, leaving an orphaned control module behind</li><li>You have old third party "
0121             "modules lying around.</li></ul></p><p>Check these points carefully and try to remove "
0122             "the module mentioned in the error message. If this fails, consider contacting "
0123             "your distributor or packager.</p></qt>");
0124     }
0125     if (report & KCModuleLoader::Dialog) {
0126         KMessageBox::detailedError(parent, text, realDetails);
0127     }
0128     if (report & KCModuleLoader::Inline) {
0129         return new KCMError(text, realDetails, parent);
0130     }
0131     return nullptr;
0132 }
0133 
0134 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 88)
0135 QT_WARNING_PUSH
0136 QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
0137 QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
0138 KCModule *KCModuleLoader::loadModule(const QString &module, ErrorReporting report, QWidget *parent, const QStringList &args)
0139 {
0140     return loadModule(KCModuleInfo(module), report, parent, args);
0141 }
0142 
0143 KCModule *KCModuleLoader::loadModule(const KCModuleInfo &mod, ErrorReporting report, QWidget *parent, const QStringList &args)
0144 {
0145     /*
0146      * Simple libraries as modules are the easiest case:
0147      *  We just have to load the library and get the module
0148      *  from the factory.
0149      */
0150 
0151     if (!mod.isValid()) {
0152         return reportError(report,
0153                            i18n("The module %1 could not be found.", mod.moduleName()),
0154                            i18n("<qt><p>The diagnosis is:<br />The desktop file %1 could not be found.</p></qt>", mod.fileName()),
0155                            parent);
0156     }
0157     if (mod.service() && mod.service()->noDisplay()) {
0158         return reportError(
0159             report,
0160             i18n("The module %1 is disabled.", mod.moduleName()),
0161             i18n("<qt><p>Either the hardware/software the module configures is not available or the module has been disabled by the administrator.</p></qt>"),
0162             parent);
0163     }
0164 
0165     if (!mod.library().isEmpty()) {
0166         QString error;
0167         QVariantList args2;
0168         const QVariantList additionalArgs = mod.property(QStringLiteral("X-KDE-KCM-Args")).toList();
0169         args2.reserve(args.count() + additionalArgs.count());
0170         for (const QString &arg : args) {
0171             args2 << arg;
0172         }
0173         args2 << additionalArgs;
0174 
0175         KCModule *module = nullptr;
0176 
0177         const auto result =
0178             KPluginFactory::instantiatePlugin<KQuickAddons::ConfigModule>(KPluginMetaData(QLatin1String("kcms/") + mod.library()), nullptr, args2);
0179 
0180         if (result) {
0181             std::unique_ptr<KQuickAddons::ConfigModule> cm(result.plugin);
0182 
0183             if (!cm->mainUi()) {
0184                 return reportError(report, i18n("Error loading QML file."), cm->errorString(), parent);
0185             }
0186             module = new KCModuleQml(std::move(cm), parent, args2);
0187             return module;
0188         } else {
0189             // If the error is that the plugin was not found fall through to the compat code
0190             // Otherwise abort and show the error to the user
0191             if (result.errorReason != KPluginFactory::INVALID_PLUGIN) {
0192                 return reportError(report, i18n("Error loading config module"), result.errorString, parent);
0193             } else {
0194                 qCDebug(KCMUTILS_LOG) << "Couldn't find plugin" << QLatin1String("kcms/") + mod.library()
0195                                       << "-- falling back to old-style loading from desktop file";
0196             }
0197         }
0198 
0199         if (mod.service()) {
0200             module = mod.service()->createInstance<KCModule>(parent, args2, &error);
0201         }
0202         if (module) {
0203             return module;
0204         } else
0205 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 85)
0206         {
0207             // get the create_ function
0208             QLibrary lib(QPluginLoader(mod.library()).fileName());
0209             if (lib.load()) {
0210                 KCModule *(*create)(QWidget *, const char *);
0211                 QByteArray factorymethod("create_");
0212                 factorymethod += mod.handle().toLatin1();
0213                 create = reinterpret_cast<KCModule *(*)(QWidget *, const char *)>(lib.resolve(factorymethod.constData()));
0214                 if (create) {
0215                     return create(parent, mod.handle().toLatin1().constData());
0216                 } else {
0217                     qCWarning(KCMUTILS_LOG) << "This module has no valid entry symbol at all. The reason could be that it's still using "
0218                                                "K_EXPORT_COMPONENT_FACTORY with a custom X-KDE-FactoryName which is not supported anymore";
0219                 }
0220                 lib.unload();
0221             }
0222         }
0223 #endif
0224         return reportError(report, error, QString(), parent);
0225     }
0226 
0227     /*
0228      * Ok, we could not load the library.
0229      * Try to run it as an executable.
0230      * This must not be done when calling from kcmshell, or you'll
0231      * have infinite recursion
0232      * (startService calls kcmshell which calls modloader which calls startService...)
0233      *
0234      */
0235     return reportError(report,
0236                        i18n("The module %1 is not a valid configuration module.", mod.moduleName()),
0237                        i18n("<qt>The diagnosis is:<br />The desktop file %1 does not specify a library.</qt>", mod.fileName()),
0238                        parent);
0239 }
0240 
0241 void KCModuleLoader::unloadModule(const KCModuleInfo &mod)
0242 {
0243     // get the library loader instance
0244     QPluginLoader loader(mod.library());
0245     loader.unload();
0246 }
0247 
0248 bool KCModuleLoader::isDefaults(const KCModuleInfo &mod, const QStringList &args)
0249 {
0250     std::unique_ptr<KCModuleData> moduleData(loadModuleData(mod, args));
0251     if (moduleData) {
0252         return moduleData->isDefaults();
0253     }
0254 
0255     return true;
0256 }
0257 
0258 KCModuleData *KCModuleLoader::loadModuleData(const KCModuleInfo &mod, const QStringList &args)
0259 {
0260     if (!mod.service() || mod.service()->noDisplay() || mod.library().isEmpty()) {
0261         return nullptr;
0262     }
0263 
0264     QVariantList args2(args.cbegin(), args.cend());
0265 
0266     const auto result = KPluginFactory::instantiatePlugin<KCModuleData>(KPluginMetaData(QLatin1String("kcms/") + mod.service()->library()), nullptr, args2);
0267 
0268     if (result) {
0269         return result.plugin;
0270     }
0271 
0272     KCModuleData *probe(mod.service()->createInstance<KCModuleData>(nullptr, args2));
0273     if (probe) {
0274         return probe;
0275     }
0276 
0277     return nullptr;
0278 }
0279 #endif