File indexing completed on 2024-12-08 10:56:04

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 // own
0010 #include "effectloader.h"
0011 // config
0012 #include <config-kwin.h>
0013 // KWin
0014 #include "plugin.h"
0015 #include "scripting/scriptedeffect.h"
0016 #include "utils/common.h"
0017 #include <kwineffects.h>
0018 // KDE
0019 #include <KConfigGroup>
0020 #include <KPackage/Package>
0021 #include <KPackage/PackageLoader>
0022 // Qt
0023 #include <QDebug>
0024 #include <QFutureWatcher>
0025 #include <QStaticPlugin>
0026 #include <QStringList>
0027 #include <QtConcurrentRun>
0028 
0029 namespace KWin
0030 {
0031 
0032 AbstractEffectLoader::AbstractEffectLoader(QObject *parent)
0033     : QObject(parent)
0034 {
0035 }
0036 
0037 AbstractEffectLoader::~AbstractEffectLoader()
0038 {
0039 }
0040 
0041 void AbstractEffectLoader::setConfig(KSharedConfig::Ptr config)
0042 {
0043     m_config = config;
0044 }
0045 
0046 LoadEffectFlags AbstractEffectLoader::readConfig(const QString &effectName, bool defaultValue) const
0047 {
0048     Q_ASSERT(m_config);
0049     KConfigGroup plugins(m_config, QStringLiteral("Plugins"));
0050 
0051     const QString key = effectName + QStringLiteral("Enabled");
0052 
0053     // do we have a key for the effect?
0054     if (plugins.hasKey(key)) {
0055         // we have a key in the config, so read the enabled state
0056         const bool load = plugins.readEntry(key, defaultValue);
0057         return load ? LoadEffectFlags(LoadEffectFlag::Load) : LoadEffectFlags();
0058     }
0059     // we don't have a key, so we just use the enabled by default value
0060     if (defaultValue) {
0061         return LoadEffectFlag::Load | LoadEffectFlag::CheckDefaultFunction;
0062     }
0063     return LoadEffectFlags();
0064 }
0065 
0066 static const QString s_nameProperty = QStringLiteral("X-KDE-PluginInfo-Name");
0067 static const QString s_jsConstraint = QStringLiteral("[X-Plasma-API] == 'javascript'");
0068 static const QString s_serviceType = QStringLiteral("KWin/Effect");
0069 
0070 ScriptedEffectLoader::ScriptedEffectLoader(QObject *parent)
0071     : AbstractEffectLoader(parent)
0072     , m_queue(new EffectLoadQueue<ScriptedEffectLoader, KPluginMetaData>(this))
0073 {
0074 }
0075 
0076 ScriptedEffectLoader::~ScriptedEffectLoader()
0077 {
0078 }
0079 
0080 bool ScriptedEffectLoader::hasEffect(const QString &name) const
0081 {
0082     return findEffect(name).isValid();
0083 }
0084 
0085 bool ScriptedEffectLoader::isEffectSupported(const QString &name) const
0086 {
0087     // scripted effects are in general supported
0088     if (!ScriptedEffect::supported()) {
0089         return false;
0090     }
0091     return hasEffect(name);
0092 }
0093 
0094 QStringList ScriptedEffectLoader::listOfKnownEffects() const
0095 {
0096     const auto effects = findAllEffects();
0097     QStringList result;
0098     for (const auto &service : effects) {
0099         result << service.pluginId();
0100     }
0101     return result;
0102 }
0103 
0104 bool ScriptedEffectLoader::loadEffect(const QString &name)
0105 {
0106     auto effect = findEffect(name);
0107     if (!effect.isValid()) {
0108         return false;
0109     }
0110     return loadEffect(effect, LoadEffectFlag::Load);
0111 }
0112 
0113 bool ScriptedEffectLoader::loadEffect(const KPluginMetaData &effect, LoadEffectFlags flags)
0114 {
0115     const QString name = effect.pluginId();
0116     if (!flags.testFlag(LoadEffectFlag::Load)) {
0117         qCDebug(KWIN_CORE) << "Loading flags disable effect: " << name;
0118         return false;
0119     }
0120     if (m_loadedEffects.contains(name)) {
0121         qCDebug(KWIN_CORE) << name << "already loaded";
0122         return false;
0123     }
0124 
0125     if (!ScriptedEffect::supported()) {
0126         qCDebug(KWIN_CORE) << "Effect is not supported: " << name;
0127         return false;
0128     }
0129 
0130     ScriptedEffect *e = ScriptedEffect::create(effect);
0131     if (!e) {
0132         qCDebug(KWIN_CORE) << "Could not initialize scripted effect: " << name;
0133         return false;
0134     }
0135     connect(e, &ScriptedEffect::destroyed, this, [this, name]() {
0136         m_loadedEffects.removeAll(name);
0137     });
0138 
0139     qCDebug(KWIN_CORE) << "Successfully loaded scripted effect: " << name;
0140     Q_EMIT effectLoaded(e, name);
0141     m_loadedEffects << name;
0142     return true;
0143 }
0144 
0145 void ScriptedEffectLoader::queryAndLoadAll()
0146 {
0147     if (m_queryConnection) {
0148         return;
0149     }
0150     // perform querying for the services in a thread
0151     QFutureWatcher<QList<KPluginMetaData>> *watcher = new QFutureWatcher<QList<KPluginMetaData>>(this);
0152     m_queryConnection = connect(
0153         watcher, &QFutureWatcher<QList<KPluginMetaData>>::finished, this, [this, watcher]() {
0154             const auto effects = watcher->result();
0155             for (const auto &effect : effects) {
0156                 const LoadEffectFlags flags = readConfig(effect.pluginId(), effect.isEnabledByDefault());
0157                 if (flags.testFlag(LoadEffectFlag::Load)) {
0158                     m_queue->enqueue(qMakePair(effect, flags));
0159                 }
0160             }
0161             watcher->deleteLater();
0162             m_queryConnection = QMetaObject::Connection();
0163         },
0164         Qt::QueuedConnection);
0165 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0166     watcher->setFuture(QtConcurrent::run(this, &ScriptedEffectLoader::findAllEffects));
0167 #else
0168     watcher->setFuture(QtConcurrent::run(&ScriptedEffectLoader::findAllEffects, this));
0169 #endif
0170 }
0171 
0172 QList<KPluginMetaData> ScriptedEffectLoader::findAllEffects() const
0173 {
0174     return KPackage::PackageLoader::self()->listPackages(s_serviceType, QStringLiteral("kwin/effects"));
0175 }
0176 
0177 KPluginMetaData ScriptedEffectLoader::findEffect(const QString &name) const
0178 {
0179     const auto plugins = KPackage::PackageLoader::self()->findPackages(s_serviceType, QStringLiteral("kwin/effects"),
0180                                                                        [name](const KPluginMetaData &metadata) {
0181                                                                            return metadata.pluginId().compare(name, Qt::CaseInsensitive) == 0;
0182                                                                        });
0183     if (!plugins.isEmpty()) {
0184         return plugins.first();
0185     }
0186     return KPluginMetaData();
0187 }
0188 
0189 void ScriptedEffectLoader::clear()
0190 {
0191     disconnect(m_queryConnection);
0192     m_queryConnection = QMetaObject::Connection();
0193     m_queue->clear();
0194 }
0195 
0196 PluginEffectLoader::PluginEffectLoader(QObject *parent)
0197     : AbstractEffectLoader(parent)
0198     , m_pluginSubDirectory(QStringLiteral("kwin/effects/plugins"))
0199 {
0200 }
0201 
0202 PluginEffectLoader::~PluginEffectLoader()
0203 {
0204 }
0205 
0206 bool PluginEffectLoader::hasEffect(const QString &name) const
0207 {
0208     const auto info = findEffect(name);
0209     return info.isValid();
0210 }
0211 
0212 KPluginMetaData PluginEffectLoader::findEffect(const QString &name) const
0213 {
0214     const auto plugins = KPluginMetaData::findPlugins(m_pluginSubDirectory,
0215                                                       [name](const KPluginMetaData &data) {
0216                                                           return data.pluginId().compare(name, Qt::CaseInsensitive) == 0;
0217                                                       });
0218     if (plugins.isEmpty()) {
0219         return KPluginMetaData();
0220     }
0221     return plugins.first();
0222 }
0223 
0224 bool PluginEffectLoader::isEffectSupported(const QString &name) const
0225 {
0226     if (EffectPluginFactory *effectFactory = factory(findEffect(name))) {
0227         return effectFactory->isSupported();
0228     }
0229     return false;
0230 }
0231 
0232 EffectPluginFactory *PluginEffectLoader::factory(const KPluginMetaData &info) const
0233 {
0234     if (!info.isValid()) {
0235         return nullptr;
0236     }
0237     KPluginFactory *factory;
0238     if (info.isStaticPlugin()) {
0239         // in case of static plugins we don't need to worry about the versions, because
0240         // they are shipped as part of the kwin executables
0241         factory = KPluginFactory::loadFactory(info).plugin;
0242     } else {
0243         QPluginLoader loader(info.fileName());
0244         if (loader.metaData().value("IID").toString() != EffectPluginFactory_iid) {
0245             qCDebug(KWIN_CORE) << info.pluginId() << " has not matching plugin version, expected " << PluginFactory_iid << "got "
0246                                << loader.metaData().value("IID");
0247             return nullptr;
0248         }
0249         factory = qobject_cast<KPluginFactory *>(loader.instance());
0250     }
0251     if (!factory) {
0252         qCDebug(KWIN_CORE) << "Did not get KPluginFactory for " << info.pluginId();
0253         return nullptr;
0254     }
0255     return dynamic_cast<EffectPluginFactory *>(factory);
0256 }
0257 
0258 QStringList PluginEffectLoader::listOfKnownEffects() const
0259 {
0260     const auto plugins = findAllEffects();
0261     QStringList result;
0262     for (const auto &plugin : plugins) {
0263         result << plugin.pluginId();
0264     }
0265     qCDebug(KWIN_CORE) << result;
0266     return result;
0267 }
0268 
0269 bool PluginEffectLoader::loadEffect(const QString &name)
0270 {
0271     const auto info = findEffect(name);
0272     if (!info.isValid()) {
0273         return false;
0274     }
0275     return loadEffect(info, LoadEffectFlag::Load);
0276 }
0277 
0278 bool PluginEffectLoader::loadEffect(const KPluginMetaData &info, LoadEffectFlags flags)
0279 {
0280     if (!info.isValid()) {
0281         qCDebug(KWIN_CORE) << "Plugin info is not valid";
0282         return false;
0283     }
0284     const QString name = info.pluginId();
0285     if (!flags.testFlag(LoadEffectFlag::Load)) {
0286         qCDebug(KWIN_CORE) << "Loading flags disable effect: " << name;
0287         return false;
0288     }
0289     if (m_loadedEffects.contains(name)) {
0290         qCDebug(KWIN_CORE) << name << " already loaded";
0291         return false;
0292     }
0293     EffectPluginFactory *effectFactory = factory(info);
0294     if (!effectFactory) {
0295         qCDebug(KWIN_CORE) << "Couldn't get an EffectPluginFactory for: " << name;
0296         return false;
0297     }
0298 
0299     effects->makeOpenGLContextCurrent();
0300     if (!effectFactory->isSupported()) {
0301         qCDebug(KWIN_CORE) << "Effect is not supported: " << name;
0302         return false;
0303     }
0304 
0305     if (flags.testFlag(LoadEffectFlag::CheckDefaultFunction)) {
0306         if (!effectFactory->enabledByDefault()) {
0307             qCDebug(KWIN_CORE) << "Enabled by default function disables effect: " << name;
0308             return false;
0309         }
0310     }
0311 
0312     // ok, now we can try to create the Effect
0313     Effect *e = effectFactory->createEffect();
0314     if (!e) {
0315         qCDebug(KWIN_CORE) << "Failed to create effect: " << name;
0316         return false;
0317     }
0318     // insert in our loaded effects
0319     m_loadedEffects << name;
0320     connect(e, &Effect::destroyed, this, [this, name]() {
0321         m_loadedEffects.removeAll(name);
0322     });
0323     qCDebug(KWIN_CORE) << "Successfully loaded plugin effect: " << name;
0324     Q_EMIT effectLoaded(e, name);
0325     return true;
0326 }
0327 
0328 void PluginEffectLoader::queryAndLoadAll()
0329 {
0330     const auto effects = findAllEffects();
0331     for (const auto &effect : effects) {
0332         const LoadEffectFlags flags = readConfig(effect.pluginId(), effect.isEnabledByDefault());
0333         if (flags.testFlag(LoadEffectFlag::Load)) {
0334             loadEffect(effect, flags);
0335         }
0336     }
0337 }
0338 
0339 QVector<KPluginMetaData> PluginEffectLoader::findAllEffects() const
0340 {
0341     return KPluginMetaData::findPlugins(m_pluginSubDirectory);
0342 }
0343 
0344 void PluginEffectLoader::setPluginSubDirectory(const QString &directory)
0345 {
0346     m_pluginSubDirectory = directory;
0347 }
0348 
0349 void PluginEffectLoader::clear()
0350 {
0351 }
0352 
0353 EffectLoader::EffectLoader(QObject *parent)
0354     : AbstractEffectLoader(parent)
0355 {
0356     m_loaders << new ScriptedEffectLoader(this)
0357               << new PluginEffectLoader(this);
0358     for (auto it = m_loaders.constBegin(); it != m_loaders.constEnd(); ++it) {
0359         connect(*it, &AbstractEffectLoader::effectLoaded, this, &AbstractEffectLoader::effectLoaded);
0360     }
0361 }
0362 
0363 EffectLoader::~EffectLoader()
0364 {
0365 }
0366 
0367 bool EffectLoader::hasEffect(const QString &name) const
0368 {
0369     return std::any_of(m_loaders.cbegin(), m_loaders.cend(), [&name](const auto &loader) {
0370         return loader->hasEffect(name);
0371     });
0372 }
0373 
0374 bool EffectLoader::isEffectSupported(const QString &name) const
0375 {
0376     return std::any_of(m_loaders.cbegin(), m_loaders.cend(), [&name](const auto &loader) {
0377         return loader->isEffectSupported(name);
0378     });
0379 }
0380 
0381 QStringList EffectLoader::listOfKnownEffects() const
0382 {
0383     QStringList result;
0384     for (auto it = m_loaders.constBegin(); it != m_loaders.constEnd(); ++it) {
0385         result << (*it)->listOfKnownEffects();
0386     }
0387     return result;
0388 }
0389 
0390 bool EffectLoader::loadEffect(const QString &name)
0391 {
0392     for (auto it = m_loaders.constBegin(); it != m_loaders.constEnd(); ++it) {
0393         if ((*it)->loadEffect(name)) {
0394             return true;
0395         }
0396     }
0397     return false;
0398 }
0399 
0400 void EffectLoader::queryAndLoadAll()
0401 {
0402     for (auto it = m_loaders.constBegin(); it != m_loaders.constEnd(); ++it) {
0403         (*it)->queryAndLoadAll();
0404     }
0405 }
0406 
0407 void EffectLoader::setConfig(KSharedConfig::Ptr config)
0408 {
0409     AbstractEffectLoader::setConfig(config);
0410     for (auto it = m_loaders.constBegin(); it != m_loaders.constEnd(); ++it) {
0411         (*it)->setConfig(config);
0412     }
0413 }
0414 
0415 void EffectLoader::clear()
0416 {
0417     for (auto it = m_loaders.constBegin(); it != m_loaders.constEnd(); ++it) {
0418         (*it)->clear();
0419     }
0420 }
0421 
0422 } // namespace KWin