KWin
Loading...
Searching...
No Matches
effectloader.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9// own
10#include "effect/effectloader.h"
11// config
12#include <config-kwin.h>
13// KWin
14#include "effect/effect.h"
16#include "plugin.h"
19#include "scripting/scripting.h"
20#include "utils/common.h"
21// KDE
22#include <KConfigGroup>
23#include <KPackage/Package>
24#include <KPackage/PackageLoader>
25// Qt
26#include <QDebug>
27#include <QFutureWatcher>
28#include <QPluginLoader>
29#include <QQmlComponent>
30#include <QQmlEngine>
31#include <QStaticPlugin>
32#include <QStringList>
33#include <QtConcurrentRun>
34
35namespace KWin
36{
37
39 : QObject(parent)
40{
41}
42
46
47void AbstractEffectLoader::setConfig(KSharedConfig::Ptr config)
48{
49 m_config = config;
50}
51
52LoadEffectFlags AbstractEffectLoader::readConfig(const QString &effectName, bool defaultValue) const
53{
54 Q_ASSERT(m_config);
55 KConfigGroup plugins(m_config, QStringLiteral("Plugins"));
56
57 const QString key = effectName + QStringLiteral("Enabled");
58
59 // do we have a key for the effect?
60 if (plugins.hasKey(key)) {
61 // we have a key in the config, so read the enabled state
62 const bool load = plugins.readEntry(key, defaultValue);
63 return load ? LoadEffectFlags(LoadEffectFlag::Load) : LoadEffectFlags();
64 }
65 // we don't have a key, so we just use the enabled by default value
66 if (defaultValue) {
68 }
69 return LoadEffectFlags();
70}
71
72static const QString s_serviceType = QStringLiteral("KWin/Effect");
73
75 : AbstractEffectLoader(parent)
76 , m_queue(new EffectLoadQueue<ScriptedEffectLoader, KPluginMetaData>(this))
77{
78}
79
83
84bool ScriptedEffectLoader::hasEffect(const QString &name) const
85{
86 return findEffect(name).isValid();
87}
88
89bool ScriptedEffectLoader::isEffectSupported(const QString &name) const
90{
91 // scripted effects are in general supported
93 return false;
94 }
95 return hasEffect(name);
96}
97
99{
100 const auto effects = findAllEffects();
101 QStringList result;
102 for (const auto &service : effects) {
103 result << service.pluginId();
104 }
105 return result;
106}
107
108bool ScriptedEffectLoader::loadEffect(const QString &name)
109{
110 auto effect = findEffect(name);
111 if (!effect.isValid()) {
112 return false;
113 }
114 return loadEffect(effect, LoadEffectFlag::Load);
115}
116
117bool ScriptedEffectLoader::loadEffect(const KPluginMetaData &effect, LoadEffectFlags flags)
118{
119 const QString name = effect.pluginId();
120 if (!flags.testFlag(LoadEffectFlag::Load)) {
121 qCDebug(KWIN_CORE) << "Loading flags disable effect: " << name;
122 return false;
123 }
124
125 if (m_loadedEffects.contains(name)) {
126 qCDebug(KWIN_CORE) << name << "already loaded";
127 return false;
128 }
129
130 const QString api = effect.value(QStringLiteral("X-Plasma-API"));
131 if (api == QLatin1String("javascript")) {
132 return loadJavascriptEffect(effect);
133 } else if (api == QLatin1String("declarativescript")) {
134 return loadDeclarativeEffect(effect);
135 } else {
136 qCWarning(KWIN_CORE, "Failed to load %s effect: invalid X-Plasma-API field: %s. "
137 "Available options are javascript, and declarativescript", qPrintable(name), qPrintable(api));
138 }
139
140 return false;
141}
142
143bool ScriptedEffectLoader::loadJavascriptEffect(const KPluginMetaData &effect)
144{
145 const QString name = effect.pluginId();
147 qCDebug(KWIN_CORE) << "Effect is not supported: " << name;
148 return false;
149 }
150
151 ScriptedEffect *e = ScriptedEffect::create(effect);
152 if (!e) {
153 qCDebug(KWIN_CORE) << "Could not initialize scripted effect: " << name;
154 return false;
155 }
156 connect(e, &ScriptedEffect::destroyed, this, [this, name]() {
157 m_loadedEffects.removeAll(name);
158 });
159
160 qCDebug(KWIN_CORE) << "Successfully loaded scripted effect: " << name;
161 Q_EMIT effectLoaded(e, name);
162 m_loadedEffects << name;
163 return true;
164}
165
166bool ScriptedEffectLoader::loadDeclarativeEffect(const KPluginMetaData &metadata)
167{
168 const QString name = metadata.pluginId();
169 const QString scriptFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
170 QLatin1String("kwin/effects/") + name + QLatin1String("/contents/ui/main.qml"));
171 if (scriptFile.isNull()) {
172 qCWarning(KWIN_CORE) << "Could not locate the effect script";
173 return false;
174 }
175
176 QQmlEngine *engine = Scripting::self()->qmlEngine();
177 QQmlComponent component(engine);
178 component.loadUrl(QUrl::fromLocalFile(scriptFile));
179 if (component.isError()) {
180 qCWarning(KWIN_CORE).nospace() << "Failed to load " << scriptFile << ": " << component.errors();
181 return false;
182 }
183
184 QObject *object = component.beginCreate(engine->rootContext());
185 auto effect = qobject_cast<ScriptedQuickSceneEffect *>(object);
186 if (!effect) {
187 qCDebug(KWIN_CORE) << "Could not initialize scripted effect: " << name;
188 delete object;
189 return false;
190 }
191 effect->setMetaData(metadata);
192 component.completeCreate();
193
194 connect(effect, &Effect::destroyed, this, [this, name]() {
195 m_loadedEffects.removeAll(name);
196 });
197
198 qCDebug(KWIN_CORE) << "Successfully loaded scripted effect: " << name;
199 Q_EMIT effectLoaded(effect, name);
200 m_loadedEffects << name;
201 return true;
202}
203
205{
206 if (m_queryConnection) {
207 return;
208 }
209 // perform querying for the services in a thread
211 m_queryConnection = connect(
212 watcher, &QFutureWatcher<QList<KPluginMetaData>>::finished, this, [this, watcher]() {
213 const auto effects = watcher->result();
214 for (const auto &effect : effects) {
215 const LoadEffectFlags flags = readConfig(effect.pluginId(), effect.isEnabledByDefault());
216 if (flags.testFlag(LoadEffectFlag::Load)) {
217 m_queue->enqueue(qMakePair(effect, flags));
218 }
219 }
220 watcher->deleteLater();
221 m_queryConnection = QMetaObject::Connection();
222 },
223 Qt::QueuedConnection);
224 watcher->setFuture(QtConcurrent::run(&ScriptedEffectLoader::findAllEffects, this));
225}
226
227QList<KPluginMetaData> ScriptedEffectLoader::findAllEffects() const
228{
229 return KPackage::PackageLoader::self()->listPackages(s_serviceType, QStringLiteral("kwin/effects"));
230}
231
232KPluginMetaData ScriptedEffectLoader::findEffect(const QString &name) const
233{
234 const auto plugins = KPackage::PackageLoader::self()->findPackages(s_serviceType, QStringLiteral("kwin/effects"),
235 [name](const KPluginMetaData &metadata) {
236 return metadata.pluginId().compare(name, Qt::CaseInsensitive) == 0;
237 });
238 if (!plugins.isEmpty()) {
239 return plugins.first();
240 }
241 return KPluginMetaData();
242}
243
245{
246 disconnect(m_queryConnection);
247 m_queryConnection = QMetaObject::Connection();
248 m_queue->clear();
249}
250
252 : AbstractEffectLoader(parent)
253 , m_pluginSubDirectory(QStringLiteral("kwin/effects/plugins"))
254{
255}
256
260
261bool PluginEffectLoader::hasEffect(const QString &name) const
262{
263 const auto info = findEffect(name);
264 return info.isValid();
265}
266
267KPluginMetaData PluginEffectLoader::findEffect(const QString &name) const
268{
269 const auto plugins = KPluginMetaData::findPlugins(m_pluginSubDirectory,
270 [name](const KPluginMetaData &data) {
271 return data.pluginId().compare(name, Qt::CaseInsensitive) == 0;
272 });
273 if (plugins.isEmpty()) {
274 return KPluginMetaData();
275 }
276 return plugins.first();
277}
278
279bool PluginEffectLoader::isEffectSupported(const QString &name) const
280{
281 if (EffectPluginFactory *effectFactory = factory(findEffect(name))) {
282 return effectFactory->isSupported();
283 }
284 return false;
285}
286
287EffectPluginFactory *PluginEffectLoader::factory(const KPluginMetaData &info) const
288{
289 if (!info.isValid()) {
290 return nullptr;
291 }
292 KPluginFactory *factory;
293 if (info.isStaticPlugin()) {
294 // in case of static plugins we don't need to worry about the versions, because
295 // they are shipped as part of the kwin executables
296 factory = KPluginFactory::loadFactory(info).plugin;
297 } else {
298 QPluginLoader loader(info.fileName());
299 if (loader.metaData().value("IID").toString() != QLatin1String(EffectPluginFactory_iid)) {
300 qCDebug(KWIN_CORE) << info.pluginId() << " has not matching plugin version, expected " << PluginFactory_iid << "got "
301 << loader.metaData().value("IID");
302 return nullptr;
303 }
304 factory = qobject_cast<KPluginFactory *>(loader.instance());
305 }
306 if (!factory) {
307 qCDebug(KWIN_CORE) << "Did not get KPluginFactory for " << info.pluginId();
308 return nullptr;
309 }
310 return dynamic_cast<EffectPluginFactory *>(factory);
311}
312
314{
315 const auto plugins = findAllEffects();
316 QStringList result;
317 for (const auto &plugin : plugins) {
318 result << plugin.pluginId();
319 }
320 qCDebug(KWIN_CORE) << result;
321 return result;
322}
323
324bool PluginEffectLoader::loadEffect(const QString &name)
325{
326 const auto info = findEffect(name);
327 if (!info.isValid()) {
328 return false;
329 }
330 return loadEffect(info, LoadEffectFlag::Load);
331}
332
333bool PluginEffectLoader::loadEffect(const KPluginMetaData &info, LoadEffectFlags flags)
334{
335 if (!info.isValid()) {
336 qCDebug(KWIN_CORE) << "Plugin info is not valid";
337 return false;
338 }
339 const QString name = info.pluginId();
340 if (!flags.testFlag(LoadEffectFlag::Load)) {
341 qCDebug(KWIN_CORE) << "Loading flags disable effect: " << name;
342 return false;
343 }
344 if (m_loadedEffects.contains(name)) {
345 qCDebug(KWIN_CORE) << name << " already loaded";
346 return false;
347 }
348 EffectPluginFactory *effectFactory = factory(info);
349 if (!effectFactory) {
350 qCDebug(KWIN_CORE) << "Couldn't get an EffectPluginFactory for: " << name;
351 return false;
352 }
353
354 effects->makeOpenGLContextCurrent(); // TODO: remove it
355 if (!effectFactory->isSupported()) {
356 qCDebug(KWIN_CORE) << "Effect is not supported: " << name;
357 return false;
358 }
359
360 if (flags.testFlag(LoadEffectFlag::CheckDefaultFunction)) {
361 if (!effectFactory->enabledByDefault()) {
362 qCDebug(KWIN_CORE) << "Enabled by default function disables effect: " << name;
363 return false;
364 }
365 }
366
367 // ok, now we can try to create the Effect
368 Effect *e = effectFactory->createEffect();
369 if (!e) {
370 qCDebug(KWIN_CORE) << "Failed to create effect: " << name;
371 return false;
372 }
373 // insert in our loaded effects
374 m_loadedEffects << name;
375 connect(e, &Effect::destroyed, this, [this, name]() {
376 m_loadedEffects.removeAll(name);
377 });
378 qCDebug(KWIN_CORE) << "Successfully loaded plugin effect: " << name;
379 Q_EMIT effectLoaded(e, name);
380 return true;
381}
382
384{
385 const auto effects = findAllEffects();
386 for (const auto &effect : effects) {
387 const LoadEffectFlags flags = readConfig(effect.pluginId(), effect.isEnabledByDefault());
388 if (flags.testFlag(LoadEffectFlag::Load)) {
389 loadEffect(effect, flags);
390 }
391 }
392}
393
394QList<KPluginMetaData> PluginEffectLoader::findAllEffects() const
395{
396 return KPluginMetaData::findPlugins(m_pluginSubDirectory);
397}
398
399void PluginEffectLoader::setPluginSubDirectory(const QString &directory)
400{
401 m_pluginSubDirectory = directory;
402}
403
407
409 : AbstractEffectLoader(parent)
410{
411 m_loaders << new ScriptedEffectLoader(this)
412 << new PluginEffectLoader(this);
413 for (auto it = m_loaders.constBegin(); it != m_loaders.constEnd(); ++it) {
415 }
416}
417
421
422bool EffectLoader::hasEffect(const QString &name) const
423{
424 return std::any_of(m_loaders.cbegin(), m_loaders.cend(), [&name](const auto &loader) {
425 return loader->hasEffect(name);
426 });
427}
428
429bool EffectLoader::isEffectSupported(const QString &name) const
430{
431 return std::any_of(m_loaders.cbegin(), m_loaders.cend(), [&name](const auto &loader) {
432 return loader->isEffectSupported(name);
433 });
434}
435
437{
438 QStringList result;
439 for (auto it = m_loaders.constBegin(); it != m_loaders.constEnd(); ++it) {
440 result << (*it)->listOfKnownEffects();
441 }
442 return result;
443}
444
445bool EffectLoader::loadEffect(const QString &name)
446{
447 for (auto it = m_loaders.constBegin(); it != m_loaders.constEnd(); ++it) {
448 if ((*it)->loadEffect(name)) {
449 return true;
450 }
451 }
452 return false;
453}
454
456{
457 for (auto it = m_loaders.constBegin(); it != m_loaders.constEnd(); ++it) {
458 (*it)->queryAndLoadAll();
459 }
460}
461
462void EffectLoader::setConfig(KSharedConfig::Ptr config)
463{
465 for (auto it = m_loaders.constBegin(); it != m_loaders.constEnd(); ++it) {
466 (*it)->setConfig(config);
467 }
468}
469
471{
472 for (auto it = m_loaders.constBegin(); it != m_loaders.constEnd(); ++it) {
473 (*it)->clear();
474 }
475}
476
477} // namespace KWin
478
479#include "moc_effectloader.cpp"
Interface to describe how an effect loader has to function.
void effectLoaded(KWin::Effect *effect, const QString &name)
The loader emits this signal when it successfully loaded an effect.
AbstractEffectLoader(QObject *parent=nullptr)
LoadEffectFlags readConfig(const QString &effectName, bool defaultValue) const
Checks the configuration for the Effect identified by effectName.
virtual void setConfig(KSharedConfig::Ptr config)
The KSharedConfig this EffectLoader should operate on.
Base class for all KWin effects.
Definition effect.h:535
void clear() override
Clears the load queue, that is all scheduled Effects are discarded from loading.
QStringList listOfKnownEffects() const override
All the Effects this loader knows of.
void setConfig(KSharedConfig::Ptr config) override
The KSharedConfig this EffectLoader should operate on.
bool hasEffect(const QString &name) const override
Whether this Effect Loader can load the Effect with the given name.
EffectLoader(QObject *parent=nullptr)
bool isEffectSupported(const QString &name) const override
Whether the Effect with the given name is supported by the compositing backend.
void queryAndLoadAll() override
The Effect Loader should query its store for all available effects and try to load them.
bool loadEffect(const QString &name) override
Synchronous loading of the Effect with the given name.
bool makeOpenGLContextCurrent()
Makes the OpenGL compositing context current.
void setPluginSubDirectory(const QString &directory)
bool isEffectSupported(const QString &name) const override
Whether the Effect with the given name is supported by the compositing backend.
bool loadEffect(const QString &name) override
Synchronous loading of the Effect with the given name.
void clear() override
Clears the load queue, that is all scheduled Effects are discarded from loading.
bool hasEffect(const QString &name) const override
Whether this Effect Loader can load the Effect with the given name.
void queryAndLoadAll() override
The Effect Loader should query its store for all available effects and try to load them.
QStringList listOfKnownEffects() const override
All the Effects this loader knows of.
PluginEffectLoader(QObject *parent=nullptr)
static ScriptedEffect * create(const QString &effectName, const QString &pathToScript, int chainPosition, const QString &exclusiveCategory)
Can load scripted Effects.
bool hasEffect(const QString &name) const override
Whether this Effect Loader can load the Effect with the given name.
bool loadEffect(const QString &name) override
Synchronous loading of the Effect with the given name.
bool isEffectSupported(const QString &name) const override
Whether the Effect with the given name is supported by the compositing backend.
void clear() override
Clears the load queue, that is all scheduled Effects are discarded from loading.
void queryAndLoadAll() override
The Effect Loader should query its store for all available effects and try to load them.
ScriptedEffectLoader(QObject *parent=nullptr)
QStringList listOfKnownEffects() const override
All the Effects this loader knows of.
static Scripting * self()
Definition scripting.h:393
QQmlEngine * qmlEngine() const
Definition scripting.h:368
virtual bool isSupported() const
Definition effect.cpp:563
#define EffectPluginFactory_iid
Definition effect.h:974
virtual KWin::Effect * createEffect() const =0
virtual bool enabledByDefault() const
Definition effect.cpp:558
@ CheckDefaultFunction
The Check Default Function needs to be invoked if the Effect provides it.
@ Load
Effect should be loaded.
EffectsHandler * effects
#define PluginFactory_iid
Definition plugin.h:18