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