/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 2014 Martin Gräßlin SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef KWIN_EFFECT_LOADER_H #define KWIN_EFFECT_LOADER_H #include // KDE #include #include // Qt #include #include #include #include #include #include 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 EffectsHandlerImpl). * * 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 "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 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 friend class EffectLoadQueue; }; template class EffectLoadQueue : public AbstractEffectLoadQueue { public: explicit EffectLoadQueue(Loader *parent) : AbstractEffectLoadQueue(parent) , m_effectLoader(parent) , m_dequeueScheduled(false) { } void enqueue(const QPair 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> 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 findAllEffects() const; KPluginMetaData findEffect(const QString &name) const; 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: QVector findAllEffects() const; KPluginMetaData findEffect(const QString &name) const; EffectPluginFactory *factory(const KPluginMetaData &info) const; QStringList m_loadedEffects; EffectLoadQueue< PluginEffectLoader, KPluginMetaData> *m_queue; QString m_pluginSubDirectory; QMetaObject::Connection m_queryConnection; }; 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 m_loaders; }; } Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::LoadEffectFlags) #endif