KCMUtils

kcmoduleloader.cpp
1 /*
2  This file is part of the KDE project
3  SPDX-FileCopyrightText: 1999 Matthias Hoelzer-Kluepfel <[email protected]>
4  SPDX-FileCopyrightText: 2000 Matthias Elter <[email protected]>
5  SPDX-FileCopyrightText: 2003, 2004, 2006 Matthias Kretz <[email protected]>
6  SPDX-FileCopyrightText: 2004 Frans Englich <[email protected]>
7  SPDX-FileCopyrightText: 2021 Alexander Lohnau <[email protected]>
8 
9  SPDX-License-Identifier: LGPL-2.0-only
10 */
11 
12 #include "kcmoduleloader.h"
13 #include "kcmoduledata.h"
14 #include "kcmoduleqml_p.h"
15 #include <kcmutils_debug.h>
16 
17 #include <QLabel>
18 #include <QLibrary>
19 #include <QVBoxLayout>
20 
21 #include <KAboutData>
22 #include <KAuthorized>
23 #include <KCModule>
24 #include <KLocalizedString>
25 #include <KMessageBox>
26 #include <KPluginFactory>
27 #include <KPluginInfo>
28 
29 #include <KQuickAddons/ConfigModule>
30 
31 using namespace KCModuleLoader;
32 
33 /***************************************************************/
34 /**
35  * When something goes wrong in loading the module, this one
36  * jumps in as a "dummy" module.
37  */
38 class KCMError : public KCModule
39 {
40 public:
41  KCMError(const QString &msg, const QString &details, QWidget *parent)
42  : KCModule(parent)
43  {
44  QVBoxLayout *topLayout = new QVBoxLayout(this);
45  QLabel *lab = new QLabel(msg, this);
46  lab->setWordWrap(true);
47  topLayout->addWidget(lab);
48  lab = new QLabel(details, this);
49  lab->setWordWrap(true);
50  topLayout->addWidget(lab);
51  }
52 };
53 
54 KCModule *KCModuleLoader::loadModule(const KPluginMetaData &metaData, QWidget *parent, const QVariantList &args)
55 {
57  return reportError(ErrorReporting::Inline,
58  i18n("The module %1 is disabled.", metaData.pluginId()),
59  i18n("The module has been disabled by the system administrator."),
60  parent);
61  }
62  const QVariantList args2 = QVariantList(args) << metaData.rawData().value(QStringLiteral("X-KDE-KCM-Args")).toArray();
63 
64  auto factoryResult = KPluginFactory::loadFactory(metaData);
65  QString pluginKeyword = metaData.value(QStringLiteral("X-KDE-PluginKeyword"));
66  if (!factoryResult) {
67  // This is where QML KCMs used to be before the namespaces were changed based on https://phabricator.kde.org/T14517
68  // But the X-KDE-Library did not reflect this change, instead the "kcms" namespace was prepended
69  if (KPluginMetaData data(QLatin1String("kcms/") + metaData.fileName()); data.isValid()) {
70  factoryResult = KPluginFactory::loadFactory(data);
71  pluginKeyword.clear();
72  }
73  }
74 
75  if (!factoryResult) {
76  return reportError(ErrorReporting::Inline, factoryResult.errorString, QString(), parent);
77  }
78 
79  KPluginFactory *factory = factoryResult.plugin;
80  QT_WARNING_PUSH
81  QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
82  QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
83 
84 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 90)
85  const auto qmlKCMResult = factory->create<KQuickAddons::ConfigModule>(pluginKeyword, parent, args2);
86 #else
87  const auto qmlKCMResult = factory->create<KQuickAddons::ConfigModule>(parent, args2);
88 #endif
89 
90  if (qmlKCMResult) {
91  std::unique_ptr<KQuickAddons::ConfigModule> kcm(qmlKCMResult);
92 
93  if (!kcm->mainUi()) {
94  return reportError(ErrorReporting::Inline, i18n("Error loading QML file."), kcm->errorString(), parent);
95  }
96  qCDebug(KCMUTILS_LOG) << "loaded KCM" << factory->metaData().pluginId() << "from path" << factory->metaData().fileName();
97  return new KCModuleQml(std::move(kcm), parent, args2);
98  }
99 
100 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 90)
101  const auto kcmoduleResult = factory->create<KCModule>(pluginKeyword, parent, args2);
102 #else
103  const auto kcmoduleResult = factory->create<KCModule>(parent, args2);
104 #endif
105  QT_WARNING_POP
106 
107  if (kcmoduleResult) {
108  qCDebug(KCMUTILS_LOG) << "loaded KCM" << factory->metaData().pluginId() << "from path" << factory->metaData().fileName();
109  return kcmoduleResult;
110  }
111 
112  return reportError(ErrorReporting::Inline, QString(), QString(), parent);
113 }
114 
115 KCModule *KCModuleLoader::reportError(ErrorReporting report, const QString &text, const QString &details, QWidget *parent)
116 {
117  QString realDetails = details;
118  if (realDetails.isNull()) {
119  realDetails = i18n(
120  "<qt><p>Possible reasons:<ul><li>An error occurred during your last "
121  "system upgrade, leaving an orphaned control module behind</li><li>You have old third party "
122  "modules lying around.</li></ul></p><p>Check these points carefully and try to remove "
123  "the module mentioned in the error message. If this fails, consider contacting "
124  "your distributor or packager.</p></qt>");
125  }
126  if (report & KCModuleLoader::Dialog) {
127  KMessageBox::detailedError(parent, text, realDetails);
128  }
129  if (report & KCModuleLoader::Inline) {
130  return new KCMError(text, realDetails, parent);
131  }
132  return nullptr;
133 }
134 
135 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 88)
136 QT_WARNING_PUSH
137 QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
138 QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
139 KCModule *KCModuleLoader::loadModule(const QString &module, ErrorReporting report, QWidget *parent, const QStringList &args)
140 {
141  return loadModule(KCModuleInfo(module), report, parent, args);
142 }
143 
145 {
146  /*
147  * Simple libraries as modules are the easiest case:
148  * We just have to load the library and get the module
149  * from the factory.
150  */
151 
152  if (!mod.isValid()) {
153  return reportError(report,
154  i18n("The module %1 could not be found.", mod.moduleName()),
155  i18n("<qt><p>The diagnosis is:<br />The desktop file %1 could not be found.</p></qt>", mod.fileName()),
156  parent);
157  }
158  if (mod.service() && mod.service()->noDisplay()) {
159  return reportError(
160  report,
161  i18n("The module %1 is disabled.", mod.moduleName()),
162  i18n("<qt><p>Either the hardware/software the module configures is not available or the module has been disabled by the administrator.</p></qt>"),
163  parent);
164  }
165 
166  if (!mod.library().isEmpty()) {
167  QString error;
168  QVariantList args2;
169  const QVariantList additionalArgs = mod.property(QStringLiteral("X-KDE-KCM-Args")).toList();
170  args2.reserve(args.count() + additionalArgs.count());
171  for (const QString &arg : args) {
172  args2 << arg;
173  }
174  args2 << additionalArgs;
175 
176  KCModule *module = nullptr;
177 
178  const auto result =
179  KPluginFactory::instantiatePlugin<KQuickAddons::ConfigModule>(KPluginMetaData(QLatin1String("kcms/") + mod.library()), nullptr, args2);
180 
181  if (result) {
182  std::unique_ptr<KQuickAddons::ConfigModule> cm(result.plugin);
183 
184  if (!cm->mainUi()) {
185  return reportError(report, i18n("Error loading QML file."), cm->errorString(), parent);
186  }
187  module = new KCModuleQml(std::move(cm), parent, args2);
188  return module;
189  } else {
190  // If the error is that the plugin was not found fall through to the compat code
191  // Otherwise abort and show the error to the user
192  if (result.errorReason != KPluginFactory::INVALID_PLUGIN) {
193  return reportError(report, i18n("Error loading config module"), result.errorString, parent);
194  } else {
195  qCDebug(KCMUTILS_LOG) << "Couldn't find plugin" << QLatin1String("kcms/") + mod.library()
196  << "-- falling back to old-style loading from desktop file";
197  }
198  }
199 
200  if (mod.service()) {
201  module = mod.service()->createInstance<KCModule>(parent, args2, &error);
202  }
203  if (module) {
204  return module;
205  } else
206 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 85)
207  {
208  // get the create_ function
209  QLibrary lib(QPluginLoader(mod.library()).fileName());
210  if (lib.load()) {
211  KCModule *(*create)(QWidget *, const char *);
212  QByteArray factorymethod("create_");
213  factorymethod += mod.handle().toLatin1();
214  create = reinterpret_cast<KCModule *(*)(QWidget *, const char *)>(lib.resolve(factorymethod.constData()));
215  if (create) {
216  return create(parent, mod.handle().toLatin1().constData());
217  } else {
218  qCWarning(KCMUTILS_LOG) << "This module has no valid entry symbol at all. The reason could be that it's still using "
219  "K_EXPORT_COMPONENT_FACTORY with a custom X-KDE-FactoryName which is not supported anymore";
220  }
221  lib.unload();
222  }
223  }
224 #endif
225  return reportError(report, error, QString(), parent);
226  }
227 
228  /*
229  * Ok, we could not load the library.
230  * Try to run it as an executable.
231  * This must not be done when calling from kcmshell, or you'll
232  * have infinite recursion
233  * (startService calls kcmshell which calls modloader which calls startService...)
234  *
235  */
236  return reportError(report,
237  i18n("The module %1 is not a valid configuration module.", mod.moduleName()),
238  i18n("<qt>The diagnosis is:<br />The desktop file %1 does not specify a library.</qt>", mod.fileName()),
239  parent);
240 }
241 
243 {
244  // get the library loader instance
245  QPluginLoader loader(mod.library());
246  loader.unload();
247 }
248 
250 {
251  std::unique_ptr<KCModuleData> moduleData(loadModuleData(mod, args));
252  if (moduleData) {
253  return moduleData->isDefaults();
254  }
255 
256  return true;
257 }
258 
260 {
261  if (!mod.service() || mod.service()->noDisplay() || mod.library().isEmpty()) {
262  return nullptr;
263  }
264 
265  QVariantList args2(args.cbegin(), args.cend());
266 
267  const auto result = KPluginFactory::instantiatePlugin<KCModuleData>(KPluginMetaData(QLatin1String("kcms/") + mod.service()->library()), nullptr, args2);
268 
269  if (result) {
270  return result.plugin;
271  }
272 
273  KCModuleData *probe(mod.service()->createInstance<KCModuleData>(nullptr, args2));
274  if (probe) {
275  return probe;
276  }
277 
278  return nullptr;
279 }
280 #endif
QJsonObject rawData() const
bool isNull() const const
T * create(const QString &keyword, QObject *parent=nullptr, const QVariantList &args=QVariantList())
KCMUTILS_EXPORT void unloadModule(const KCModuleInfo &mod)
Unloads the module's library.
void detailedError(QWidget *parent, const QString &text, const QString &details, const QString &caption=QString(), Options options=Notify)
int count(const T &value) const const
void clear()
bool isValid() const
Loads a KControl Module.
QString fileName() const
QByteArray toLatin1() const const
KService::Ptr service() const
void setWordWrap(bool on)
@ Inline
the error report is shown instead of the KCModule that should have * been loaded
KCMUTILS_EXPORT KCModule * loadModule(const KCModuleInfo &module, ErrorReporting report, QWidget *parent=nullptr, const QStringList &args=QStringList())
Loads a KCModule.
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
QString i18n(const char *text, const TYPE &arg...)
ErrorReporting
Determines the way errors are reported.
bool isEmpty() const const
QString moduleName() const
QJsonValue value(const QString &key) const const
bool unload()
QList::const_iterator cend() const const
KCMUTILS_EXPORT bool isDefaults(const KCModuleInfo &module, const QStringList &args=QStringList())
For a specified module, return false if configuration is not the default one and true in all other ca...
KCONFIGCORE_EXPORT bool authorizeControlModule(const QString &menuId)
KPluginMetaData metaData() const
QVariant property(const QString &key) const
A base class that offers information about a KCModule state.
Definition: kcmoduledata.h:25
static Result< KPluginFactory > loadFactory(const KPluginMetaData &data)
bool value(const QString &key, bool defaultValue) const
QJsonArray toArray() const const
bool load()
const char * constData() const const
QList::const_iterator cbegin() const const
QString fileName() const
QList< QVariant > toList() const const
@ Dialog
shows a dialog with the error report
bool isValid() const
Returns true if the KCM was found.
QString library() const
A class that provides information about a KCModule.
Definition: kcmoduleinfo.h:36
QString pluginId() const
QString handle() const
QFunctionPointer resolve(const char *symbol)
KCMUTILS_EXPORT KCModuleData * loadModuleData(const KCModuleInfo &module, const QStringList &args=QStringList())
Loads a KCModuleData.
KCMUTILS_EXPORT KCModule * reportError(ErrorReporting report, const QString &text, const QString &details, QWidget *parent)
Returns a KCModule containing the messages report and text.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sun Jun 26 2022 03:51:23 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.