File indexing completed on 2024-11-10 04:56:44
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"