kwin/src/effect/effectloader.h
2023-11-20 11:32:43 +00:00

346 lines
12 KiB
C++

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <kwin_export.h>
// KDE
#include <KPluginMetaData>
#include <KSharedConfig>
// Qt
#include <QFlags>
#include <QMap>
#include <QObject>
#include <QPair>
#include <QQueue>
#include <QStaticPlugin>
namespace KWin
{
class Effect;
class EffectPluginFactory;
/**
* @brief Flags defining how a Loader should load an Effect.
*
* These Flags are only used internally when querying the configuration on whether
* an Effect should be loaded.
*
* @see AbstractEffectLoader::readConfig()
*/
enum class LoadEffectFlag {
Load = 1 << 0, ///< Effect should be loaded
CheckDefaultFunction = 1 << 2 ///< The Check Default Function needs to be invoked if the Effect provides it
};
Q_DECLARE_FLAGS(LoadEffectFlags, LoadEffectFlag)
/**
* @brief Interface to describe how an effect loader has to function.
*
* The AbstractEffectLoader specifies the methods a concrete loader has to implement and how
* those methods are expected to perform. Also it provides an interface to the outside world
* (that is EffectsHandler).
*
* The abstraction is used because there are multiple types of Effects which need to be loaded:
* @li Built-In Effects
* @li Scripted Effects
* @li Binary Plugin Effects
*
* Serving all of them with one Effect Loader is rather complex given that different stores need
* to be queried at the same time. Thus the idea is to have one implementation per type and one
* implementation which makes use of all of them and combines the loading.
*/
class KWIN_EXPORT AbstractEffectLoader : public QObject
{
Q_OBJECT
public:
~AbstractEffectLoader() override;
/**
* @brief The KSharedConfig this EffectLoader should operate on.
*
* Important: a valid KSharedConfig must be provided before trying to load any effects!
*
* @param config
* @internal
*/
virtual void setConfig(KSharedConfig::Ptr config);
/**
* @brief Whether this Effect Loader can load the Effect with the given @p name.
*
* The Effect Loader determines whether it knows or can find an Effect called @p name,
* and thus whether it can attempt to load the Effect.
*
* @param name The name of the Effect to look for.
* @return bool @c true if the Effect Loader knows this effect, false otherwise
*/
virtual bool hasEffect(const QString &name) const = 0;
/**
* @brief All the Effects this loader knows of.
*
* The implementation should re-query its store whenever this method is invoked.
* It's possible that the store of effects changed (e.g. a new one got installed)
*
* @return QStringList The internal names of the known Effects
*/
virtual QStringList listOfKnownEffects() const = 0;
/**
* @brief Synchronous loading of the Effect with the given @p name.
*
* Loads the Effect without checking any configuration value or any enabled by default
* function provided by the Effect.
*
* The loader is expected to apply the following checks:
* If the Effect is already loaded, the Effect should not get loaded again. Thus the loader
* is expected to track which Effects it has loaded, and which of those have been destroyed.
* The loader should check whether the Effect is supported. If the Effect indicates it is
* not supported, it should not get loaded.
*
* If the Effect loaded successfully the signal effectLoaded(KWin::Effect*,const QString&)
* must be emitted. Otherwise the user of the loader is not able to get the loaded Effect.
* It's not returning the Effect as queryAndLoadAll() is working async and thus the users
* of the loader are expected to be prepared for async loading.
*
* @param name The internal name of the Effect which should be loaded
* @return bool @c true if the effect could be loaded, @c false in error case
* @see queryAndLoadAll()
* @see effectLoaded(KWin::Effect*,const QString&)
*/
virtual bool loadEffect(const QString &name) = 0;
/**
* @brief The Effect Loader should query its store for all available effects and try to load them.
*
* The Effect Loader is supposed to perform this operation in a highly async way. If there is
* IO which needs to be performed this should be done in a background thread and a queue should
* be used to load the effects. The loader should make sure to not load more than one Effect
* in one event cycle. Loading the Effect has to be performed in the Compositor thread and
* thus blocks the Compositor. Therefore after loading one Effect all events should get
* processed first, so that the Compositor can perform a painting pass if needed. To simplify
* this operation one can use the EffectLoadQueue. This requires to add another loadEffect
* method with the custom loader specific type to refer to an Effect and LoadEffectFlags.
*
* The LoadEffectFlags have to be determined by querying the configuration with readConfig().
* If the Load flag is set the loading can proceed and all the checks from
* loadEffect(const QString &) have to be applied.
* In addition if the CheckDefaultFunction flag is set and the Effect provides such a method,
* it should be queried to determine whether the Effect is enabled by default. If such a method
* returns @c false the Effect should not get loaded. If the Effect does not provide a way to
* query whether it's enabled by default at runtime the flag can get ignored.
*
* If the Effect loaded successfully the signal effectLoaded(KWin::Effect*,const QString&)
* must be emitted.
*
* @see loadEffect(const QString &)
* @see effectLoaded(KWin::Effect*,const QString&)
*/
virtual void queryAndLoadAll() = 0;
/**
* @brief Whether the Effect with the given @p name is supported by the compositing backend.
*
* @param name The name of the Effect to check.
* @return bool @c true if it is supported, @c false otherwise
*/
virtual bool isEffectSupported(const QString &name) const = 0;
/**
* @brief Clears the load queue, that is all scheduled Effects are discarded from loading.
*/
virtual void clear() = 0;
Q_SIGNALS:
/**
* @brief The loader emits this signal when it successfully loaded an effect.
*
* @param effect The created Effect
* @param name The internal name of the loaded Effect
* @return void
*/
void effectLoaded(KWin::Effect *effect, const QString &name);
protected:
explicit AbstractEffectLoader(QObject *parent = nullptr);
/**
* @brief Checks the configuration for the Effect identified by @p effectName.
*
* For each Effect there could be a key called "<effectName>Enabled". If there is such a key
* the returned flags will contain Load in case it's @c true. If the key does not exist the
* @p defaultValue determines whether the Effect should be loaded. A value of @c true means
* that Load | CheckDefaultFunction is returned, in case of @c false no Load flags are returned.
*
* @param effectName The name of the Effect to look for in the configuration
* @param defaultValue Whether the Effect is enabled by default or not.
* @returns Flags indicating whether the Effect should be loaded and how it should be loaded
*/
LoadEffectFlags readConfig(const QString &effectName, bool defaultValue) const;
private:
KSharedConfig::Ptr m_config;
};
template<typename Loader, typename QueueType>
class EffectLoadQueue;
/**
* @brief Helper class to queue the loading of Effects.
*
* Loading an Effect has to be done in the compositor thread and thus the Compositor is blocked
* while the Effect loads. To not block the compositor for several frames the loading of all
* Effects need to be queued. By invoking the slot dequeue() through a QueuedConnection the queue
* can ensure that events are processed between the loading of two Effects and thus the compositor
* doesn't block.
*
* As it needs to be a slot, the queue must subclass QObject, but it also needs to be templated as
* the information to load an Effect is specific to the Effect Loader. Thus there is the
* AbstractEffectLoadQueue providing the slots as pure virtual functions and the templated
* EffectLoadQueue inheriting from AbstractEffectLoadQueue.
*
* The queue operates like a normal queue providing enqueue and a scheduleDequeue instead of dequeue.
*
*/
class AbstractEffectLoadQueue : public QObject
{
Q_OBJECT
public:
explicit AbstractEffectLoadQueue(QObject *parent = nullptr)
: QObject(parent)
{
}
protected Q_SLOTS:
virtual void dequeue() = 0;
private:
template<typename Loader, typename QueueType>
friend class EffectLoadQueue;
};
template<typename Loader, typename QueueType>
class EffectLoadQueue : public AbstractEffectLoadQueue
{
public:
explicit EffectLoadQueue(Loader *parent)
: AbstractEffectLoadQueue(parent)
, m_effectLoader(parent)
, m_dequeueScheduled(false)
{
}
void enqueue(const QPair<QueueType, LoadEffectFlags> value)
{
m_queue.enqueue(value);
scheduleDequeue();
}
void clear()
{
m_queue.clear();
m_dequeueScheduled = false;
}
protected:
void dequeue() override
{
if (m_queue.isEmpty()) {
return;
}
m_dequeueScheduled = false;
const auto pair = m_queue.dequeue();
m_effectLoader->loadEffect(pair.first, pair.second);
scheduleDequeue();
}
private:
void scheduleDequeue()
{
if (m_queue.isEmpty() || m_dequeueScheduled) {
return;
}
m_dequeueScheduled = true;
QMetaObject::invokeMethod(this, &AbstractEffectLoadQueue::dequeue, Qt::QueuedConnection);
}
Loader *m_effectLoader;
bool m_dequeueScheduled;
QQueue<QPair<QueueType, LoadEffectFlags>> m_queue;
};
/**
* @brief Can load scripted Effects
*/
class KWIN_EXPORT ScriptedEffectLoader : public AbstractEffectLoader
{
Q_OBJECT
public:
explicit ScriptedEffectLoader(QObject *parent = nullptr);
~ScriptedEffectLoader() override;
bool hasEffect(const QString &name) const override;
bool isEffectSupported(const QString &name) const override;
QStringList listOfKnownEffects() const override;
void clear() override;
void queryAndLoadAll() override;
bool loadEffect(const QString &name) override;
bool loadEffect(const KPluginMetaData &effect, LoadEffectFlags flags);
private:
QList<KPluginMetaData> findAllEffects() const;
KPluginMetaData findEffect(const QString &name) const;
bool loadJavascriptEffect(const KPluginMetaData &effect);
bool loadDeclarativeEffect(const KPluginMetaData &effect);
QStringList m_loadedEffects;
EffectLoadQueue<ScriptedEffectLoader, KPluginMetaData> *m_queue;
QMetaObject::Connection m_queryConnection;
};
class PluginEffectLoader : public AbstractEffectLoader
{
Q_OBJECT
public:
explicit PluginEffectLoader(QObject *parent = nullptr);
~PluginEffectLoader() override;
bool hasEffect(const QString &name) const override;
bool isEffectSupported(const QString &name) const override;
QStringList listOfKnownEffects() const override;
void clear() override;
void queryAndLoadAll() override;
bool loadEffect(const QString &name) override;
bool loadEffect(const KPluginMetaData &info, LoadEffectFlags flags);
void setPluginSubDirectory(const QString &directory);
private:
QList<KPluginMetaData> findAllEffects() const;
KPluginMetaData findEffect(const QString &name) const;
EffectPluginFactory *factory(const KPluginMetaData &info) const;
QStringList m_loadedEffects;
QString m_pluginSubDirectory;
};
class KWIN_EXPORT EffectLoader : public AbstractEffectLoader
{
Q_OBJECT
public:
explicit EffectLoader(QObject *parent = nullptr);
~EffectLoader() override;
bool hasEffect(const QString &name) const override;
bool isEffectSupported(const QString &name) const override;
QStringList listOfKnownEffects() const override;
bool loadEffect(const QString &name) override;
void queryAndLoadAll() override;
void setConfig(KSharedConfig::Ptr config) override;
void clear() override;
private:
QList<AbstractEffectLoader *> m_loaders;
};
}
Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::LoadEffectFlags)