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