1 /*
2 SPDX-FileCopyrightText: 2010 Ryan Rix <ry@n.rix.si>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7 #include "pluginloader.h"
8
9 #include <QPointer>
10 #include <QStandardPaths>
11
12 #include <KService>
13 #include <KServiceTypeTrader>
14 #include <QDebug>
15 #include <kdeclarative/kdeclarative.h>
16 #include <kpackage/packageloader.h>
17
18 #include "config-plasma.h"
19
20 #include "applet.h"
21 #include "containment.h"
22 #include "containmentactions.h"
23 #include "dataengine.h"
24 #include "debug_p.h"
25 #include "package.h"
26 #include "private/applet_p.h"
27 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 83)
28 #include "private/package_p.h"
29 #include "private/packagestructure_p.h"
30 #endif
31 #include "private/service_p.h" // for NullService
32 #include "private/storage_p.h"
33
34 namespace Plasma
35 {
36 static PluginLoader *s_pluginLoader = nullptr;
37
38 class PluginLoaderPrivate
39 {
40 public:
PluginLoaderPrivate()41 PluginLoaderPrivate()
42 : isDefaultLoader(false)
43 {
44 }
45
46 static QSet<QString> knownCategories();
47
48 static QSet<QString> s_customCategories;
49 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 83)
50 QHash<QString, QPointer<PackageStructure>> structures;
51 #endif
52 bool isDefaultLoader;
53
54 static QString s_dataEnginePluginDir;
55 static QString s_packageStructurePluginDir;
56 static QString s_plasmoidsPluginDir;
57 static QString s_servicesPluginDir;
58 static QString s_containmentActionsPluginDir;
59
60 class Cache
61 {
62 // We only use this cache during start of the process to speed up many consecutive calls
63 // After that, we're too afraid to produce race conditions and it's not that time-critical anyway
64 // the 20 seconds here means that the cache is only used within 20sec during startup, after that,
65 // complexity goes up and we'd have to update the cache in order to avoid subtle bugs
66 // just not using the cache is way easier then, since it doesn't make *that* much of a difference,
67 // anyway
68 int maxCacheAge = 20;
69 qint64 pluginCacheAge = 0;
70 QHash<QString, KPluginMetaData> plugins;
71
72 public:
73 KPluginMetaData findPluginById(const QString &name, const QString &pluginNamespace);
74 };
75 Cache plasmoidCache;
76 Cache dataengineCache;
77 Cache containmentactionCache;
78 };
79
80 QSet<QString> PluginLoaderPrivate::s_customCategories;
81
82 QString PluginLoaderPrivate::s_dataEnginePluginDir = QStringLiteral("plasma/dataengine");
83 QString PluginLoaderPrivate::s_packageStructurePluginDir = QStringLiteral("plasma/packagestructure");
84 QString PluginLoaderPrivate::s_plasmoidsPluginDir = QStringLiteral("plasma/applets");
85 QString PluginLoaderPrivate::s_servicesPluginDir = QStringLiteral("plasma/services");
86 QString PluginLoaderPrivate::s_containmentActionsPluginDir = QStringLiteral("plasma/containmentactions");
87
knownCategories()88 QSet<QString> PluginLoaderPrivate::knownCategories()
89 {
90 // this is to trick the translation tools into making the correct
91 // strings for translation
92 QSet<QString> categories = s_customCategories;
93 /* clang-format off */
94 categories << QStringLiteral(I18N_NOOP("Accessibility")).toLower()
95 << QStringLiteral(I18N_NOOP("Application Launchers")).toLower()
96 << QStringLiteral(I18N_NOOP("Astronomy")).toLower()
97 << QStringLiteral(I18N_NOOP("Date and Time")).toLower()
98 << QStringLiteral(I18N_NOOP("Development Tools")).toLower()
99 << QStringLiteral(I18N_NOOP("Education")).toLower()
100 << QStringLiteral(I18N_NOOP("Environment and Weather")).toLower()
101 << QStringLiteral(I18N_NOOP("Examples")).toLower()
102 << QStringLiteral(I18N_NOOP("File System")).toLower()
103 << QStringLiteral(I18N_NOOP("Fun and Games")).toLower()
104 << QStringLiteral(I18N_NOOP("Graphics")).toLower()
105 << QStringLiteral(I18N_NOOP("Language")).toLower()
106 << QStringLiteral(I18N_NOOP("Mapping")).toLower()
107 << QStringLiteral(I18N_NOOP("Miscellaneous")).toLower()
108 << QStringLiteral(I18N_NOOP("Multimedia")).toLower()
109 << QStringLiteral(I18N_NOOP("Online Services")).toLower()
110 << QStringLiteral(I18N_NOOP("Productivity")).toLower()
111 << QStringLiteral(I18N_NOOP("System Information")).toLower()
112 << QStringLiteral(I18N_NOOP("Utilities")).toLower()
113 << QStringLiteral(I18N_NOOP("Windows and Tasks")).toLower()
114 << QStringLiteral(I18N_NOOP("Clipboard")).toLower()
115 << QStringLiteral(I18N_NOOP("Tasks")).toLower();
116 /* clang-format on */
117 return categories;
118 }
119
PluginLoader()120 PluginLoader::PluginLoader()
121 : d(new PluginLoaderPrivate)
122 {
123 }
124
~PluginLoader()125 PluginLoader::~PluginLoader()
126 {
127 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 83)
128 for (const auto &wp : std::as_const(d->structures)) {
129 delete wp;
130 }
131 #endif
132 delete d;
133 }
134
135 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 86)
setPluginLoader(PluginLoader * loader)136 void PluginLoader::setPluginLoader(PluginLoader *loader)
137 {
138 if (!s_pluginLoader) {
139 s_pluginLoader = loader;
140 }
141 }
142 #endif
143
self()144 PluginLoader *PluginLoader::self()
145 {
146 if (!s_pluginLoader) {
147 // we have been called before any PluginLoader was set, so just use the default
148 // implementation. this prevents plugins from nefariously injecting their own
149 // plugin loader if the app doesn't
150 s_pluginLoader = new PluginLoader;
151 s_pluginLoader->d->isDefaultLoader = true;
152 }
153
154 return s_pluginLoader;
155 }
156
loadApplet(const QString & name,uint appletId,const QVariantList & args)157 Applet *PluginLoader::loadApplet(const QString &name, uint appletId, const QVariantList &args)
158 {
159 if (name.isEmpty()) {
160 return nullptr;
161 }
162
163 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 86)
164 Applet *applet = d->isDefaultLoader ? nullptr : internalLoadApplet(name, appletId, args);
165 if (applet) {
166 return applet;
167 }
168 #else
169 Applet *applet = nullptr;
170 #endif
171
172 if (appletId == 0) {
173 appletId = ++AppletPrivate::s_maxAppletId;
174 }
175
176 // Need to pass the empty directory because it's where plasmoids used to be
177 auto plugin = d->plasmoidCache.findPluginById(name, PluginLoaderPrivate::s_plasmoidsPluginDir);
178
179 const KPackage::Package p = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"), name);
180
181 // If the applet is using another applet package, search for the plugin of the other applet
182 if (!plugin.isValid()) {
183 const QString parentPlugin = p.metadata().value(QStringLiteral("X-Plasma-RootPath"));
184 if (!parentPlugin.isEmpty()) {
185 plugin = d->plasmoidCache.findPluginById(parentPlugin, PluginLoaderPrivate::s_plasmoidsPluginDir);
186 }
187 }
188
189 if (plugin.isValid()) {
190 QPluginLoader loader(plugin.fileName());
191 QVariantList allArgs = {QVariant::fromValue(p), loader.metaData().toVariantMap(), appletId};
192 allArgs << args;
193 applet = KPluginFactory::instantiatePlugin<Plasma::Applet>(plugin, nullptr, allArgs).plugin;
194 }
195 if (applet) {
196 return applet;
197 }
198
199 if (!applet) {
200 // qCDebug(LOG_PLASMA) << name << "not a C++ applet: Falling back to an empty one";
201
202 QVariantList allArgs;
203 allArgs << QVariant::fromValue(p) << p.metadata().fileName() << appletId << args;
204
205 if (p.metadata().serviceTypes().contains(QLatin1String("Plasma/Containment"))) {
206 applet = new Containment(nullptr, p.metadata(), allArgs);
207 } else {
208 applet = new Applet(nullptr, p.metadata(), allArgs);
209 }
210 }
211
212 const QString localePath = p.filePath("translations");
213 if (!localePath.isEmpty()) {
214 KLocalizedString::addDomainLocaleDir(QByteArray("plasma_applet_") + name.toLatin1(), localePath);
215 }
216 return applet;
217 }
218
loadDataEngine(const QString & name)219 DataEngine *PluginLoader::loadDataEngine(const QString &name)
220 {
221 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 86)
222 DataEngine *engine = d->isDefaultLoader ? nullptr : internalLoadDataEngine(name);
223 if (engine) {
224 return engine;
225 }
226 #else
227 DataEngine *engine = nullptr;
228 #endif
229
230 // Look for C++ plugins first
231 KPluginMetaData plugin = d->dataengineCache.findPluginById(name, PluginLoaderPrivate::s_dataEnginePluginDir);
232 if (plugin.isValid()) {
233 const QVariantList args{QPluginLoader(plugin.fileName()).metaData().toVariantMap()};
234 engine = KPluginFactory::instantiatePlugin<Plasma::DataEngine>(plugin, nullptr, args).plugin;
235 }
236 if (engine) {
237 return engine;
238 }
239
240 const KPackage::Package p = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/DataEngine"), name);
241 if (!p.isValid()) {
242 return nullptr;
243 }
244
245 return new DataEngine(p.metadata(), nullptr);
246 }
247
listAllEngines(const QString & parentApp)248 QStringList PluginLoader::listAllEngines(const QString &parentApp)
249 {
250 QStringList engines;
251 // Look for C++ plugins first
252 auto filter = [&parentApp](const KPluginMetaData &md) -> bool {
253 return md.value(QStringLiteral("X-KDE-ParentApp")) == parentApp;
254 };
255 QVector<KPluginMetaData> plugins;
256 if (parentApp.isEmpty()) {
257 plugins = KPluginMetaData::findPlugins(PluginLoaderPrivate::s_dataEnginePluginDir);
258 } else {
259 plugins = KPluginMetaData::findPlugins(PluginLoaderPrivate::s_dataEnginePluginDir, filter);
260 }
261
262 for (auto &plugin : std::as_const(plugins)) {
263 engines << plugin.pluginId();
264 }
265
266 const QList<KPluginMetaData> packagePlugins = KPackage::PackageLoader::self()->listPackages(QStringLiteral("Plasma/DataEngine"));
267 for (const auto &plugin : packagePlugins) {
268 engines << plugin.pluginId();
269 }
270
271 return engines;
272 }
273
274 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 77)
listEngineInfo(const QString & parentApp)275 KPluginInfo::List PluginLoader::listEngineInfo(const QString &parentApp)
276 {
277 return PluginLoader::self()->listDataEngineInfo(parentApp);
278 }
279 #endif
280
281 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 81)
listEngineInfoByCategory(const QString & category,const QString & parentApp)282 KPluginInfo::List PluginLoader::listEngineInfoByCategory(const QString &category, const QString &parentApp)
283 {
284 KPluginInfo::List list;
285
286 // Look for C++ plugins first
287 auto filterNormal = [&category](const KPluginMetaData &md) -> bool {
288 return md.value(QStringLiteral("X-KDE-PluginInfo-Category")) == category;
289 };
290 auto filterParentApp = [&category, &parentApp](const KPluginMetaData &md) -> bool {
291 return md.value(QStringLiteral("X-KDE-ParentApp")) == parentApp //
292 && md.value(QStringLiteral("X-KDE-PluginInfo-Category")) == category;
293 };
294 QVector<KPluginMetaData> plugins;
295 if (parentApp.isEmpty()) {
296 plugins = KPluginMetaData::findPlugins(PluginLoaderPrivate::s_dataEnginePluginDir, filterNormal);
297 } else {
298 plugins = KPluginMetaData::findPlugins(PluginLoaderPrivate::s_dataEnginePluginDir, filterParentApp);
299 }
300
301 list = KPluginInfo::fromMetaData(plugins);
302
303 // TODO FIXME: PackageLoader needs to have a function to inject packageStructures
304 const QList<KPluginMetaData> packagePlugins = KPackage::PackageLoader::self()->listPackages(QStringLiteral("Plasma/DataEngine"));
305 list << KPluginInfo::fromMetaData(packagePlugins.toVector());
306
307 return list;
308 }
309 #endif
310
loadService(const QString & name,const QVariantList & args,QObject * parent)311 Service *PluginLoader::loadService(const QString &name, const QVariantList &args, QObject *parent)
312 {
313 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 86)
314 Service *service = d->isDefaultLoader ? nullptr : internalLoadService(name, args, parent);
315 if (service) {
316 return service;
317 }
318 #else
319 Service *service = nullptr;
320 #endif
321
322 // TODO: scripting API support
323 if (name.isEmpty()) {
324 return new NullService(QString(), parent);
325 } else if (name == QLatin1String("org.kde.servicestorage")) {
326 return new Storage(parent);
327 }
328
329 // Look for C++ plugins first
330 KPluginMetaData plugin = KPluginMetaData::findPluginById(PluginLoaderPrivate::s_servicesPluginDir, name);
331 if (plugin.isValid()) {
332 service = KPluginFactory::instantiatePlugin<Plasma::Service>(plugin, parent, args).plugin;
333 }
334
335 if (service) {
336 if (service->name().isEmpty()) {
337 service->setName(name);
338 }
339 return service;
340 } else {
341 return new NullService(name, parent);
342 }
343 }
344
loadContainmentActions(Containment * parent,const QString & name,const QVariantList & args)345 ContainmentActions *PluginLoader::loadContainmentActions(Containment *parent, const QString &name, const QVariantList &args)
346 {
347 if (name.isEmpty()) {
348 return nullptr;
349 }
350 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 86)
351 ContainmentActions *actions = d->isDefaultLoader ? nullptr : internalLoadContainmentActions(parent, name, args);
352 if (actions) {
353 return actions;
354 }
355 #endif
356
357 KPluginMetaData plugin = d->containmentactionCache.findPluginById(name, PluginLoaderPrivate::s_containmentActionsPluginDir);
358
359 if (plugin.isValid()) {
360 if (auto res = KPluginFactory::instantiatePlugin<Plasma::ContainmentActions>(plugin, nullptr, {QVariant::fromValue(plugin)})) {
361 return res.plugin;
362 }
363 }
364
365 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 88)
366 QString constraint = QStringLiteral("[X-KDE-PluginInfo-Name] == '%1'").arg(name);
367 KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("Plasma/ContainmentActions"), constraint);
368
369 if (offers.isEmpty()) {
370 #ifndef NDEBUG
371 qCDebug(LOG_PLASMA) << "offers is empty for " << name;
372 #endif
373 return nullptr;
374 }
375
376 KService::Ptr offer = offers.first();
377 qCWarning(LOG_PLASMA) << "Plugin" << name << "was loaded using deprecated KServiceTypeTrader."
378 << "Use embedded json metadata and install the plugin in plasma/containmentactions namespace instead";
379
380 KPluginMetaData data(offer->library());
381 QVariantList allArgs;
382 allArgs << offer->storageId() << args;
383
384 return KPluginFactory::instantiatePlugin<Plasma::ContainmentActions>(data, nullptr, allArgs).plugin;
385 #else
386 return nullptr;
387 #endif
388 }
389
390 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 83)
loadPackage(const QString & packageFormat,const QString & specialization)391 Package PluginLoader::loadPackage(const QString &packageFormat, const QString &specialization)
392 {
393 if (!d->isDefaultLoader) {
394 Package p = internalLoadPackage(packageFormat, specialization);
395 if (p.hasValidStructure()) {
396 return p;
397 }
398 }
399
400 if (packageFormat.isEmpty()) {
401 return Package();
402 }
403
404 const QString hashkey = packageFormat + QLatin1Char('%') + specialization;
405 PackageStructure *structure = d->structures.value(hashkey).data();
406
407 if (structure) {
408 return Package(structure);
409 }
410
411 KPackage::PackageStructure *internalStructure = KPackage::PackageLoader::self()->loadPackageStructure(packageFormat);
412
413 if (internalStructure) {
414 structure = new PackageStructure();
415 structure->d->internalStructure = internalStructure;
416 // fallback to old structures
417 } else {
418 auto filter = [packageFormat](const KPluginMetaData &md) -> bool {
419 return md.value(QStringLiteral("X-KDE-PluginInfo-Name")) == packageFormat;
420 };
421
422 const QVector<KPluginMetaData> plugins = KPluginMetaData::findPlugins(PluginLoaderPrivate::s_packageStructurePluginDir, filter);
423
424 if (!plugins.isEmpty()) {
425 if (auto res = KPluginFactory::instantiatePlugin<Plasma::PackageStructure>(plugins.first())) {
426 structure = res.plugin;
427 } else {
428 qWarning() << "Error loading plugin:" << res.errorString;
429 }
430
431 if (structure) {
432 structure->d->internalStructure = new PackageStructureWrapper(structure);
433 }
434 }
435 }
436
437 if (structure) {
438 d->structures.insert(hashkey, structure);
439 return Package(structure);
440 }
441
442 return Package();
443 }
444 #endif
listAppletMetaDataInternal(const QString & category,const QString & parentApp)445 QList<KPluginMetaData> listAppletMetaDataInternal(const QString &category, const QString &parentApp)
446 {
447 auto platforms = KDeclarative::KDeclarative::runtimePlatform();
448 // For now desktop always lists everything
449 if (platforms.contains(QStringLiteral("desktop"))) {
450 platforms.clear();
451 }
452
453 // FIXME: this assumes we are always use packages.. no pure c++
454 std::function<bool(const KPluginMetaData &)> filter;
455 if (category.isEmpty()) { // use all but the excluded categories
456 KConfigGroup group(KSharedConfig::openConfig(), "General");
457 QStringList excluded = group.readEntry("ExcludeCategories", QStringList());
458
459 filter = [excluded, parentApp, platforms](const KPluginMetaData &md) -> bool {
460 if (!platforms.isEmpty() && !md.formFactors().isEmpty()) {
461 bool found = false;
462 for (const auto &plat : platforms) {
463 if (md.formFactors().contains(plat)) {
464 found = true;
465 break;
466 }
467 }
468
469 if (!found) {
470 return false;
471 }
472 }
473
474 const QString pa = md.value(QStringLiteral("X-KDE-ParentApp"));
475 return (parentApp.isEmpty() || pa == parentApp) && !excluded.contains(md.category());
476 };
477 } else { // specific category (this could be an excluded one - is that bad?)
478
479 filter = [category, parentApp, platforms](const KPluginMetaData &md) -> bool {
480 if (!platforms.isEmpty() && !md.formFactors().isEmpty()) {
481 bool found = false;
482 for (const auto &plat : platforms) {
483 if (md.formFactors().contains(plat)) {
484 found = true;
485 break;
486 }
487 }
488
489 if (!found) {
490 return false;
491 }
492 }
493
494 const QString pa = md.value(QStringLiteral("X-KDE-ParentApp"));
495
496 if (category == QLatin1String("Miscellaneous")) {
497 return (parentApp.isEmpty() || pa == parentApp) && (md.category() == category || md.category().isEmpty());
498 } else {
499 return (parentApp.isEmpty() || pa == parentApp) && md.category() == category;
500 }
501 };
502 }
503
504 return KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QString(), filter);
505 }
506
listAppletMetaData(const QString & category)507 QList<KPluginMetaData> PluginLoader::listAppletMetaData(const QString &category)
508 {
509 return listAppletMetaDataInternal(category, QString());
510 }
511
512 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 88)
listAppletMetaData(const QString & category,const QString & parentApp)513 QList<KPluginMetaData> PluginLoader::listAppletMetaData(const QString &category, const QString &parentApp)
514 {
515 return listAppletMetaDataInternal(category, parentApp);
516 }
517 #endif
518
519 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 28)
listAppletInfo(const QString & category,const QString & parentApp)520 KPluginInfo::List PluginLoader::listAppletInfo(const QString &category, const QString &parentApp)
521 {
522 const auto plugins = listAppletMetaData(category, parentApp);
523
524 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0)
525 KPluginInfo::List list;
526 // NOTE: it still produces kplugininfos from KServices because some user code expects
527 // info.service() to be valid and would crash otherwise
528 for (const auto &md : plugins) {
529 QT_WARNING_PUSH
530 QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
531 QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
532 auto pi = md.metaDataFileName().endsWith(QLatin1String(".json")) ? KPluginInfo(md) : KPluginInfo(KService::serviceByStorageId(md.metaDataFileName()));
533 QT_WARNING_POP
534 if (!pi.isValid()) {
535 qCWarning(LOG_PLASMA) << "Could not load plugin info for plugin :" << md.pluginId() << "skipping plugin";
536 continue;
537 }
538 list << pi;
539 }
540 return list;
541 #else
542 return KPluginInfo::fromMetaData(plugins.toVector());
543 #endif
544 }
545 #endif
546
listAppletMetaDataForMimeType(const QString & mimeType)547 QList<KPluginMetaData> PluginLoader::listAppletMetaDataForMimeType(const QString &mimeType)
548 {
549 auto filter = [&mimeType](const KPluginMetaData &md) -> bool {
550 return md.value(QStringLiteral("X-Plasma-DropMimeTypes"), QStringList()).contains(mimeType);
551 };
552 return KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QString(), filter);
553 }
554 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 83)
listAppletInfoForMimeType(const QString & mimeType)555 KPluginInfo::List PluginLoader::listAppletInfoForMimeType(const QString &mimeType)
556 {
557 return KPluginInfo::fromMetaData(listAppletMetaDataForMimeType(mimeType).toVector());
558 }
559 #endif
560
listAppletMetaDataForUrl(const QUrl & url)561 QList<KPluginMetaData> PluginLoader::listAppletMetaDataForUrl(const QUrl &url)
562 {
563 QString parentApp;
564 QCoreApplication *app = QCoreApplication::instance();
565 if (app) {
566 parentApp = app->applicationName();
567 }
568
569 auto filter = [&parentApp](const KPluginMetaData &md) -> bool {
570 const QString pa = md.value(QStringLiteral("X-KDE-ParentApp"));
571 return (parentApp.isEmpty() || pa == parentApp) //
572 && !md.value(QStringLiteral("X-Plasma-DropUrlPatterns"), QStringList()).isEmpty();
573 };
574 const QList<KPluginMetaData> allApplets = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QString(), filter);
575
576 QList<KPluginMetaData> filtered;
577 for (const KPluginMetaData &md : allApplets) {
578 const QStringList urlPatterns = md.value(QStringLiteral("X-Plasma-DropUrlPatterns"), QStringList());
579 for (const QString &glob : urlPatterns) {
580 QRegExp rx(glob);
581 rx.setPatternSyntax(QRegExp::Wildcard);
582 if (rx.exactMatch(url.toString())) {
583 filtered << md;
584 }
585 }
586 }
587
588 return filtered;
589 }
590
591 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 36)
listAppletInfoForUrl(const QUrl & url)592 KPluginInfo::List PluginLoader::listAppletInfoForUrl(const QUrl &url)
593 {
594 return KPluginInfo::fromMetaData(listAppletMetaDataForUrl(url).toVector());
595 }
596 #endif
597
listAppletCategories(const QString & parentApp,bool visibleOnly)598 QStringList PluginLoader::listAppletCategories(const QString &parentApp, bool visibleOnly)
599 {
600 KConfigGroup group(KSharedConfig::openConfig(), "General");
601 const QStringList excluded = group.readEntry("ExcludeCategories", QStringList());
602 auto filter = [&parentApp, &excluded, visibleOnly](const KPluginMetaData &md) -> bool {
603 const QString pa = md.value(QStringLiteral("X-KDE-ParentApp"));
604 return (parentApp.isEmpty() || pa == parentApp) //
605 && (excluded.isEmpty() || excluded.contains(md.value(QStringLiteral("X-KDE-PluginInfo-Category")))) //
606 && (!visibleOnly || !md.isHidden());
607 };
608 const QList<KPluginMetaData> allApplets = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QString(), filter);
609
610 QStringList categories;
611 for (auto &plugin : allApplets) {
612 if (plugin.category().isEmpty()) {
613 if (!categories.contains(i18nc("misc category", "Miscellaneous"))) {
614 categories << i18nc("misc category", "Miscellaneous");
615 }
616 } else {
617 categories << plugin.category();
618 }
619 }
620 categories.sort();
621 return categories;
622 }
623
setCustomAppletCategories(const QStringList & categories)624 void PluginLoader::setCustomAppletCategories(const QStringList &categories)
625 {
626 PluginLoaderPrivate::s_customCategories = QSet<QString>(categories.begin(), categories.end());
627 }
628
customAppletCategories() const629 QStringList PluginLoader::customAppletCategories() const
630 {
631 return PluginLoaderPrivate::s_customCategories.values();
632 }
633
appletCategory(const QString & appletName)634 QString PluginLoader::appletCategory(const QString &appletName)
635 {
636 if (appletName.isEmpty()) {
637 return QString();
638 }
639
640 const KPackage::Package p = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"), appletName);
641 if (!p.isValid()) {
642 return QString();
643 }
644
645 return p.metadata().category();
646 }
647
648 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 83)
listContainments(const QString & category,const QString & parentApp)649 KPluginInfo::List PluginLoader::listContainments(const QString &category, const QString &parentApp)
650 {
651 return listContainmentsOfType(QString(), category, parentApp);
652 }
653 #endif
654
listContainmentsMetaData(std::function<bool (const KPluginMetaData &)> filter)655 QList<KPluginMetaData> PluginLoader::listContainmentsMetaData(std::function<bool(const KPluginMetaData &)> filter)
656 {
657 auto ownFilter = [filter](const KPluginMetaData &md) -> bool {
658 if (!md.serviceTypes().contains(QLatin1String("Plasma/Containment"))) {
659 return false;
660 }
661
662 return filter(md);
663 };
664
665 return KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QString(), ownFilter);
666 }
667
listContainmentsMetaDataOfType(const QString & type)668 QList<KPluginMetaData> PluginLoader::listContainmentsMetaDataOfType(const QString &type)
669 {
670 auto filter = [type](const KPluginMetaData &md) -> bool {
671 return md.value(QStringLiteral("X-Plasma-ContainmentType")) == type;
672 };
673
674 return listContainmentsMetaData(filter);
675 }
676
677 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 83)
listContainmentsOfType(const QString & type,const QString & category,const QString & parentApp)678 KPluginInfo::List PluginLoader::listContainmentsOfType(const QString &type, const QString &category, const QString &parentApp)
679 {
680 KConfigGroup group(KSharedConfig::openConfig(), "General");
681 auto filter = [&type, &category, &parentApp](const KPluginMetaData &md) -> bool {
682 if (!md.serviceTypes().contains(QLatin1String("Plasma/Containment"))) {
683 return false;
684 }
685 if (!parentApp.isEmpty() && md.value(QStringLiteral("X-KDE-ParentApp")) != parentApp) {
686 return false;
687 }
688
689 if (!type.isEmpty() && md.value(QStringLiteral("X-Plasma-ContainmentType")) != type) {
690 return false;
691 }
692
693 if (!category.isEmpty() && md.value(QStringLiteral("X-KDE-PluginInfo-Category")) != category) {
694 return false;
695 }
696
697 return true;
698 };
699
700 return KPluginInfo::fromMetaData(KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QString(), filter).toVector());
701 }
702 #endif
703
704 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 83)
listContainmentsForMimeType(const QString & mimeType)705 KPluginInfo::List PluginLoader::listContainmentsForMimeType(const QString &mimeType)
706 {
707 auto filter = [&mimeType](const KPluginMetaData &md) -> bool {
708 return md.serviceTypes().contains(QLatin1String("Plasma/Containment"))
709 && md.value(QStringLiteral("X-Plasma-DropMimeTypes"), QStringList()).contains(mimeType);
710 };
711
712 return KPluginInfo::fromMetaData(KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QString(), filter).toVector());
713 }
714 #endif
715
716 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 83)
listContainmentTypes()717 QStringList PluginLoader::listContainmentTypes()
718 {
719 const KPluginInfo::List containmentInfos = listContainments();
720 QSet<QString> types;
721
722 for (const KPluginInfo &containmentInfo : containmentInfos) {
723 const QStringList theseTypes = containmentInfo.property(QStringLiteral("X-Plasma-ContainmentType")).toStringList();
724 for (const QString &type : theseTypes) {
725 types.insert(type);
726 }
727 }
728
729 return types.values();
730 }
731 #endif
732
733 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 77)
listDataEngineInfo(const QString & parentApp)734 KPluginInfo::List PluginLoader::listDataEngineInfo(const QString &parentApp)
735 {
736 return KPluginInfo::fromMetaData(listDataEngineMetaData(parentApp));
737 }
738 #endif
739
listDataEngineMetaData(const QString & parentApp)740 QVector<KPluginMetaData> PluginLoader::listDataEngineMetaData(const QString &parentApp)
741 {
742 auto filter = [&parentApp](const KPluginMetaData &md) -> bool {
743 return md.value(QStringLiteral("X-KDE-ParentApp")) == parentApp;
744 };
745
746 QVector<KPluginMetaData> plugins;
747 if (parentApp.isEmpty()) {
748 plugins = KPluginMetaData::findPlugins(PluginLoaderPrivate::s_dataEnginePluginDir);
749 } else {
750 plugins = KPluginMetaData::findPlugins(PluginLoaderPrivate::s_dataEnginePluginDir, filter);
751 }
752
753 return plugins;
754 }
755
756 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 77)
listContainmentActionsInfo(const QString & parentApp)757 KPluginInfo::List PluginLoader::listContainmentActionsInfo(const QString &parentApp)
758 {
759 return KPluginInfo::fromMetaData(listContainmentActionsMetaData(parentApp));
760 }
761 #endif
762
listContainmentActionsMetaData(const QString & parentApp)763 QVector<KPluginMetaData> PluginLoader::listContainmentActionsMetaData(const QString &parentApp)
764 {
765 auto filter = [&parentApp](const KPluginMetaData &md) -> bool {
766 return md.value(QStringLiteral("X-KDE-ParentApp")) == parentApp;
767 };
768
769 QVector<KPluginMetaData> plugins;
770 if (parentApp.isEmpty()) {
771 plugins = KPluginMetaData::findPlugins(PluginLoaderPrivate::s_containmentActionsPluginDir);
772 } else {
773 plugins = KPluginMetaData::findPlugins(PluginLoaderPrivate::s_containmentActionsPluginDir, filter);
774 }
775
776 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0)
777 QSet<QString> knownPlugins;
778 for (const KPluginMetaData &p : std::as_const(plugins)) {
779 knownPlugins.insert(p.pluginId());
780 }
781 QString constraint;
782 if (!parentApp.isEmpty()) {
783 constraint = QLatin1String("[X-KDE-ParentApp] == '") + parentApp + QLatin1Char('\'');
784 }
785 const KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("Plasma/ContainmentActions"), constraint);
786 for (KService::Ptr s : offers) {
787 if (!knownPlugins.contains(s->pluginKeyword())) {
788 QT_WARNING_PUSH
789 QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
790 QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
791 plugins.append(KPluginInfo(s).toMetaData());
792 QT_WARNING_POP
793 }
794 }
795 #endif
796
797 return plugins;
798 }
799
800 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 86)
internalLoadApplet(const QString & name,uint appletId,const QVariantList & args)801 Applet *PluginLoader::internalLoadApplet(const QString &name, uint appletId, const QVariantList &args)
802 {
803 Q_UNUSED(name)
804 Q_UNUSED(appletId)
805 Q_UNUSED(args)
806 return nullptr;
807 }
808
internalLoadDataEngine(const QString & name)809 DataEngine *PluginLoader::internalLoadDataEngine(const QString &name)
810 {
811 Q_UNUSED(name)
812 return nullptr;
813 }
814
internalLoadContainmentActions(Containment * containment,const QString & name,const QVariantList & args)815 ContainmentActions *PluginLoader::internalLoadContainmentActions(Containment *containment, const QString &name, const QVariantList &args)
816 {
817 Q_UNUSED(containment)
818 Q_UNUSED(name)
819 Q_UNUSED(args)
820 return nullptr;
821 }
822
internalLoadService(const QString & name,const QVariantList & args,QObject * parent)823 Service *PluginLoader::internalLoadService(const QString &name, const QVariantList &args, QObject *parent)
824 {
825 Q_UNUSED(name)
826 Q_UNUSED(args)
827 Q_UNUSED(parent)
828 return nullptr;
829 }
830
831 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 83)
internalLoadPackage(const QString & name,const QString & specialization)832 Package PluginLoader::internalLoadPackage(const QString &name, const QString &specialization)
833 {
834 Q_UNUSED(name);
835 Q_UNUSED(specialization);
836 return Package();
837 }
838 #endif
839
internalAppletInfo(const QString & category) const840 KPluginInfo::List PluginLoader::internalAppletInfo(const QString &category) const
841 {
842 Q_UNUSED(category)
843 return KPluginInfo::List();
844 }
845
internalDataEngineInfo() const846 KPluginInfo::List PluginLoader::internalDataEngineInfo() const
847 {
848 return KPluginInfo::List();
849 }
850
internalServiceInfo() const851 KPluginInfo::List PluginLoader::internalServiceInfo() const
852 {
853 return KPluginInfo::List();
854 }
855
internalContainmentActionsInfo() const856 KPluginInfo::List PluginLoader::internalContainmentActionsInfo() const
857 {
858 return KPluginInfo::List();
859 }
860 #endif
861
862 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 88)
standardInternalInfo(const QString & type,const QString & category=QString ())863 static KPluginInfo::List standardInternalInfo(const QString &type, const QString &category = QString())
864 {
865 QStringList files = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
866 QLatin1String(PLASMA_RELATIVE_DATA_INSTALL_DIR "/internal/") + type + QLatin1String("/*.desktop"),
867 QStandardPaths::LocateFile);
868
869 const KPluginInfo::List allInfo = KPluginInfo::fromFiles(files);
870
871 if (category.isEmpty() || allInfo.isEmpty()) {
872 return allInfo;
873 }
874
875 KPluginInfo::List matchingInfo;
876 for (const KPluginInfo &info : allInfo) {
877 if (info.category().compare(category, Qt::CaseInsensitive) == 0) {
878 matchingInfo << info;
879 }
880 }
881
882 return matchingInfo;
883 }
884
standardInternalAppletInfo(const QString & category) const885 KPluginInfo::List PluginLoader::standardInternalAppletInfo(const QString &category) const
886 {
887 return standardInternalInfo(QStringLiteral("applets"), category);
888 }
889
standardInternalDataEngineInfo() const890 KPluginInfo::List PluginLoader::standardInternalDataEngineInfo() const
891 {
892 return standardInternalInfo(QStringLiteral("dataengines"));
893 }
894
standardInternalServiceInfo() const895 KPluginInfo::List PluginLoader::standardInternalServiceInfo() const
896 {
897 return standardInternalInfo(QStringLiteral("services"));
898 }
899 #endif
900
findPluginById(const QString & name,const QString & pluginNamespace)901 KPluginMetaData PluginLoaderPrivate::Cache::findPluginById(const QString &name, const QString &pluginNamespace)
902 {
903 const qint64 now = qRound64(QDateTime::currentMSecsSinceEpoch() / 1000.0);
904 bool useRuntimeCache = true;
905
906 if (pluginCacheAge == 0) {
907 // Find all the plugins now, but only once
908 pluginCacheAge = now;
909
910 const auto metaDataList = KPluginMetaData::findPlugins(pluginNamespace);
911 for (const KPluginMetaData &metadata : metaDataList) {
912 plugins.insert(metadata.pluginId(), metadata);
913 }
914 } else if (now - pluginCacheAge > maxCacheAge) {
915 // cache is old and we're not within a few seconds of startup anymore
916 useRuntimeCache = false;
917 plugins.clear();
918 }
919
920 // if name wasn't a path, pluginName == name
921 const QString pluginName = name.section(QLatin1Char('/'), -1);
922
923 if (useRuntimeCache) {
924 KPluginMetaData data = plugins.value(name);
925 qCDebug(LOG_PLASMA) << "loading applet by name" << name << useRuntimeCache << data.isValid();
926 return data;
927 } else {
928 return KPluginMetaData::findPluginById(pluginNamespace, pluginName);
929 }
930 }
931
932 } // Plasma Namespace
933