File indexing completed on 2024-05-12 05:31:28

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