File indexing completed on 2024-04-21 11:26: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