diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index 1cb80b9b86..ce9ccafa5d 100644
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@ -111,6 +111,7 @@ set( testBuiltInEffectLoader_SRCS
add_executable( testBuiltInEffectLoader ${testBuiltInEffectLoader_SRCS})
target_link_libraries(testBuiltInEffectLoader
+ Qt5::Concurrent
Qt5::Test
kwineffects
kwin4_effect_builtins
@@ -118,3 +119,30 @@ target_link_libraries(testBuiltInEffectLoader
add_test(kwin-testBuiltInEffectLoader testBuiltInEffectLoader)
ecm_mark_as_test(testBuiltInEffectLoader)
+
+########################################################
+# Test ScriptedEffectLoader
+########################################################
+include_directories(${KWIN_SOURCE_DIR})
+set( testScriptedEffectLoader_SRCS
+ test_scripted_effectloader.cpp
+ mock_effectshandler.cpp
+ ../effectloader.cpp
+ ../scripting/scriptedeffect.cpp
+ ../scripting/scriptingutils.cpp
+)
+add_executable( testScriptedEffectLoader ${testScriptedEffectLoader_SRCS})
+
+target_link_libraries(testScriptedEffectLoader
+ Qt5::Concurrent
+ Qt5::Script
+ Qt5::Test
+ KF5::ConfigGui
+ KF5::GlobalAccel
+ KF5::I18n
+ kwineffects
+ kwin4_effect_builtins
+)
+
+add_test(kwin-testScriptedEffectLoader testScriptedEffectLoader)
+ecm_mark_as_test(testScriptedEffectLoader)
diff --git a/autotests/test_builtin_effectloader.cpp b/autotests/test_builtin_effectloader.cpp
index ad70781ecf..56156496c6 100644
--- a/autotests/test_builtin_effectloader.cpp
+++ b/autotests/test_builtin_effectloader.cpp
@@ -20,6 +20,7 @@ along with this program. If not, see .
#include "../effectloader.h"
#include "../effects/effect_builtins.h"
#include "mock_effectshandler.h"
+#include "../scripting/scriptedeffect.h" // for mocking ScriptedEffect::create
// KDE
#include
#include
@@ -32,6 +33,16 @@ Q_DECLARE_METATYPE(KWin::LoadEffectFlags)
Q_DECLARE_METATYPE(KWin::BuiltInEffect)
Q_DECLARE_METATYPE(KWin::Effect*)
+namespace KWin
+{
+
+ScriptedEffect *ScriptedEffect::create(const QString &, const QString &)
+{
+ return nullptr;
+}
+
+}
+
class TestBuiltInEffectLoader : public QObject
{
Q_OBJECT
diff --git a/autotests/test_scripted_effectloader.cpp b/autotests/test_scripted_effectloader.cpp
new file mode 100644
index 0000000000..f5921072f5
--- /dev/null
+++ b/autotests/test_scripted_effectloader.cpp
@@ -0,0 +1,383 @@
+/********************************************************************
+KWin - the KDE window manager
+This file is part of the KDE project.
+
+Copyright (C) 2014 Martin Gräßlin
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*********************************************************************/
+#include "../effectloader.h"
+#include "mock_effectshandler.h"
+#include "../scripting/scriptedeffect.h"
+// for mocking
+#include "../input.h"
+#include "../screenedge.h"
+// KDE
+#include
+#include
+#include
+// Qt
+#include
+#include
+Q_DECLARE_METATYPE(KWin::LoadEffectFlag)
+Q_DECLARE_METATYPE(KWin::LoadEffectFlags)
+Q_DECLARE_METATYPE(KWin::Effect*)
+
+namespace KWin
+{
+ScreenEdges *ScreenEdges::s_self = nullptr;
+
+void ScreenEdges::reserve(ElectricBorder, QObject *, const char *)
+{
+}
+
+InputRedirection *InputRedirection::s_self = nullptr;
+
+void InputRedirection::registerShortcut(const QKeySequence &, QAction *)
+{
+}
+
+namespace MetaScripting
+{
+void registration(QScriptEngine *)
+{
+}
+}
+
+}
+
+class TestScriptedEffectLoader : public QObject
+{
+ Q_OBJECT
+private Q_SLOTS:
+ void testHasEffect_data();
+ void testHasEffect();
+ void testKnownEffects();
+ void testLoadEffect_data();
+ void testLoadEffect();
+ void testLoadScriptedEffect_data();
+ void testLoadScriptedEffect();
+ void testLoadAllEffects();
+};
+
+void TestScriptedEffectLoader::testHasEffect_data()
+{
+ QTest::addColumn("name");
+ QTest::addColumn("expected");
+
+ // all the built-in effects should fail
+ QTest::newRow("blur") << QStringLiteral("blur") << false;
+ QTest::newRow("Contrast") << QStringLiteral("contrast") << false;
+ QTest::newRow("CoverSwitch") << QStringLiteral("coverswitch") << false;
+ QTest::newRow("Cube") << QStringLiteral("cube") << false;
+ QTest::newRow("CubeSlide") << QStringLiteral("cubeslide") << false;
+ QTest::newRow("Dashboard") << QStringLiteral("dashboard") << false;
+ QTest::newRow("DesktopGrid") << QStringLiteral("desktopgrid") << false;
+ QTest::newRow("DimInactive") << QStringLiteral("diminactive") << false;
+ QTest::newRow("DimScreen") << QStringLiteral("dimscreen") << false;
+ QTest::newRow("FallApart") << QStringLiteral("fallapart") << false;
+ QTest::newRow("FlipSwitch") << QStringLiteral("flipswitch") << false;
+ QTest::newRow("Glide") << QStringLiteral("glide") << false;
+ QTest::newRow("HighlightWindow") << QStringLiteral("highlightwindow") << false;
+ QTest::newRow("Invert") << QStringLiteral("invert") << false;
+ QTest::newRow("Kscreen") << QStringLiteral("kscreen") << false;
+ QTest::newRow("Logout") << QStringLiteral("logout") << false;
+ QTest::newRow("LookingGlass") << QStringLiteral("lookingglass") << false;
+ QTest::newRow("MagicLamp") << QStringLiteral("magiclamp") << false;
+ QTest::newRow("Magnifier") << QStringLiteral("magnifier") << false;
+ QTest::newRow("MinimizeAnimation") << QStringLiteral("minimizeanimation") << false;
+ QTest::newRow("MouseClick") << QStringLiteral("mouseclick") << false;
+ QTest::newRow("MouseMark") << QStringLiteral("mousemark") << false;
+ QTest::newRow("PresentWindows") << QStringLiteral("presentwindows") << false;
+ QTest::newRow("Resize") << QStringLiteral("resize") << false;
+ QTest::newRow("ScreenEdge") << QStringLiteral("screenedge") << false;
+ QTest::newRow("ScreenShot") << QStringLiteral("screenshot") << false;
+ QTest::newRow("Sheet") << QStringLiteral("sheet") << false;
+ QTest::newRow("ShowFps") << QStringLiteral("showfps") << false;
+ QTest::newRow("ShowPaint") << QStringLiteral("showpaint") << false;
+ QTest::newRow("Slide") << QStringLiteral("slide") << false;
+ QTest::newRow("SlideBack") << QStringLiteral("slideback") << false;
+ QTest::newRow("SlidingPopups") << QStringLiteral("slidingpopups") << false;
+ QTest::newRow("SnapHelper") << QStringLiteral("snaphelper") << false;
+ QTest::newRow("StartupFeedback") << QStringLiteral("startupfeedback") << false;
+ QTest::newRow("ThumbnailAside") << QStringLiteral("thumbnailaside") << false;
+ QTest::newRow("TrackMouse") << QStringLiteral("trackmouse") << false;
+ QTest::newRow("WindowGeometry") << QStringLiteral("windowgeometry") << false;
+ QTest::newRow("WobblyWindows") << QStringLiteral("wobblywindows") << false;
+ QTest::newRow("Zoom") << QStringLiteral("zoom") << false;
+ QTest::newRow("Non Existing") << QStringLiteral("InvalidName") << false;
+ QTest::newRow("Fade - without kwin4_effect") << QStringLiteral("fade") << false;
+ QTest::newRow("Fade + kwin4_effect") << QStringLiteral("kwin4_effect_fade") << true;
+ QTest::newRow("Fade + kwin4_effect + CS") << QStringLiteral("kwin4_eFfect_fAde") << true;
+ QTest::newRow("FadeDesktop") << QStringLiteral("kwin4_effect_fadedesktop") << true;
+ QTest::newRow("DialogParent") << QStringLiteral("kwin4_effect_dialogparent") << true;
+ QTest::newRow("Login") << QStringLiteral("kwin4_effect_login") << true;
+ QTest::newRow("Maximize") << QStringLiteral("kwin4_effect_maximize") << true;
+ QTest::newRow("ScaleIn") << QStringLiteral("kwin4_effect_scalein") << true;
+ QTest::newRow("Translucency") << QStringLiteral("kwin4_effect_translucency") << true;
+}
+
+void TestScriptedEffectLoader::testHasEffect()
+{
+ QFETCH(QString, name);
+ QFETCH(bool, expected);
+
+ KWin::ScriptedEffectLoader loader;
+ QCOMPARE(loader.hasEffect(name), expected);
+
+ // each available effect should also be supported
+ QCOMPARE(loader.isEffectSupported(name), expected);
+}
+
+void TestScriptedEffectLoader::testKnownEffects()
+{
+ QStringList expectedEffects;
+ expectedEffects << QStringLiteral("kwin4_effect_dialogparent")
+ << QStringLiteral("kwin4_effect_fade")
+ << QStringLiteral("kwin4_effect_fadedesktop")
+ << QStringLiteral("kwin4_effect_login")
+ << QStringLiteral("kwin4_effect_maximize")
+ << QStringLiteral("kwin4_effect_scalein")
+ << QStringLiteral("kwin4_effect_translucency");
+
+ KWin::ScriptedEffectLoader loader;
+ QStringList result = loader.listOfKnownEffects();
+ // at least as many effects as we expect - system running the test could have more effects
+ QVERIFY(result.size() >= expectedEffects.size());
+ for (const QString &effect : expectedEffects) {
+ QVERIFY(result.contains(effect));
+ }
+}
+
+void TestScriptedEffectLoader::testLoadEffect_data()
+{
+ QTest::addColumn("name");
+ QTest::addColumn("expected");
+
+ QTest::newRow("Non Existing") << QStringLiteral("InvalidName") << false;
+ QTest::newRow("Fade - without kwin4_effect") << QStringLiteral("fade") << false;
+ QTest::newRow("Fade + kwin4_effect") << QStringLiteral("kwin4_effect_fade") << true;
+ QTest::newRow("Fade + kwin4_effect + CS") << QStringLiteral("kwin4_eFfect_fAde") << true;
+ QTest::newRow("FadeDesktop") << QStringLiteral("kwin4_effect_fadedesktop") << true;
+ QTest::newRow("DialogParent") << QStringLiteral("kwin4_effect_dialogparent") << true;
+ QTest::newRow("Login") << QStringLiteral("kwin4_effect_login") << true;
+ QTest::newRow("Maximize") << QStringLiteral("kwin4_effect_maximize") << true;
+ QTest::newRow("ScaleIn") << QStringLiteral("kwin4_effect_scalein") << true;
+ QTest::newRow("Translucency") << QStringLiteral("kwin4_effect_translucency") << true;
+}
+
+void TestScriptedEffectLoader::testLoadEffect()
+{
+ QFETCH(QString, name);
+ QFETCH(bool, expected);
+
+ MockEffectsHandler mockHandler(KWin::XRenderCompositing);
+ KWin::ScriptedEffectLoader loader;
+ KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
+ loader.setConfig(config);
+
+ qRegisterMetaType();
+ QSignalSpy spy(&loader, SIGNAL(effectLoaded(KWin::Effect*,QString)));
+ // connect to signal to ensure that we delete the Effect again as the Effect doesn't have a parent
+ connect(&loader, &KWin::ScriptedEffectLoader::effectLoaded,
+ [&name](KWin::Effect *effect, const QString &effectName) {
+ QCOMPARE(effectName, name.toLower());
+ effect->deleteLater();
+ }
+ );
+ // try to load the Effect
+ QCOMPARE(loader.loadEffect(name), expected);
+ // loading again should fail
+ QVERIFY(!loader.loadEffect(name));
+
+ // signal spy should have got the signal if it was expected
+ QCOMPARE(spy.isEmpty(), !expected);
+ if (!spy.isEmpty()) {
+ QCOMPARE(spy.count(), 1);
+ // if we caught a signal it should have the effect name we passed in
+ QList arguments = spy.takeFirst();
+ QCOMPARE(arguments.count(), 2);
+ QCOMPARE(arguments.at(1).toString(), name.toLower());
+ }
+ spy.clear();
+ QVERIFY(spy.isEmpty());
+
+ // now if we wait for the events being processed, the effect will get deleted and it should load again
+ QTest::qWait(1);
+ QCOMPARE(loader.loadEffect(name), expected);
+ // signal spy should have got the signal if it was expected
+ QCOMPARE(spy.isEmpty(), !expected);
+ if (!spy.isEmpty()) {
+ QCOMPARE(spy.count(), 1);
+ // if we caught a signal it should have the effect name we passed in
+ QList arguments = spy.takeFirst();
+ QCOMPARE(arguments.count(), 2);
+ QCOMPARE(arguments.at(1).toString(), name.toLower());
+ }
+}
+
+void TestScriptedEffectLoader::testLoadScriptedEffect_data()
+{
+ QTest::addColumn("name");
+ QTest::addColumn("expected");
+ QTest::addColumn("loadFlags");
+
+ const KWin::LoadEffectFlags checkDefault = KWin::LoadEffectFlag::Load | KWin::LoadEffectFlag::CheckDefaultFunction;
+ const KWin::LoadEffectFlags forceFlags = KWin::LoadEffectFlag::Load;
+ const KWin::LoadEffectFlags dontLoadFlags = KWin::LoadEffectFlags();
+
+ // enabled by default
+ QTest::newRow("Fade") << QStringLiteral("kwin4_effect_fade") << true << checkDefault;
+ // not enabled by default
+ QTest::newRow("Scalein") << QStringLiteral("kwin4_effect_scalein") << true << checkDefault;
+ // Force an Effect which will load
+ QTest::newRow("Scalein-Force") << QStringLiteral("kwin4_effect_scalein") << true << forceFlags;
+ // Enforce no load of effect which is enabled by default
+ QTest::newRow("Fade-DontLoad") << QStringLiteral("kwin4_effect_fade") << false << dontLoadFlags;
+ // Enforce no load of effect which is not enabled by default, but enforced
+ QTest::newRow("Scalein-DontLoad") << QStringLiteral("kwin4_effect_scalein") << false << dontLoadFlags;
+}
+
+void TestScriptedEffectLoader::testLoadScriptedEffect()
+{
+ QFETCH(QString, name);
+ QFETCH(bool, expected);
+ QFETCH(KWin::LoadEffectFlags, loadFlags);
+
+ MockEffectsHandler mockHandler(KWin::XRenderCompositing);
+ KWin::ScriptedEffectLoader loader;
+ KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
+ loader.setConfig(config);
+
+ const auto services = KServiceTypeTrader::self()->query(QStringLiteral("KWin/Effect"),
+ QStringLiteral("[X-KDE-PluginInfo-Name] == '%1'").arg(name));
+ QCOMPARE(services.count(), 1);
+
+ qRegisterMetaType();
+ QSignalSpy spy(&loader, SIGNAL(effectLoaded(KWin::Effect*,QString)));
+ // connect to signal to ensure that we delete the Effect again as the Effect doesn't have a parent
+ connect(&loader, &KWin::ScriptedEffectLoader::effectLoaded,
+ [&name](KWin::Effect *effect, const QString &effectName) {
+ QCOMPARE(effectName, name.toLower());
+ effect->deleteLater();
+ }
+ );
+ // try to load the Effect
+ QCOMPARE(loader.loadEffect(services.first(), loadFlags), expected);
+ // loading again should fail
+ QVERIFY(!loader.loadEffect(services.first(), loadFlags));
+
+ // signal spy should have got the signal if it was expected
+ QCOMPARE(spy.isEmpty(), !expected);
+ if (!spy.isEmpty()) {
+ QCOMPARE(spy.count(), 1);
+ // if we caught a signal it should have the effect name we passed in
+ QList arguments = spy.takeFirst();
+ QCOMPARE(arguments.count(), 2);
+ QCOMPARE(arguments.at(1).toString(), name.toLower());
+ }
+ spy.clear();
+ QVERIFY(spy.isEmpty());
+
+ // now if we wait for the events being processed, the effect will get deleted and it should load again
+ QTest::qWait(1);
+ QCOMPARE(loader.loadEffect(services.first(), loadFlags), expected);
+ // signal spy should have got the signal if it was expected
+ QCOMPARE(spy.isEmpty(), !expected);
+ if (!spy.isEmpty()) {
+ QCOMPARE(spy.count(), 1);
+ // if we caught a signal it should have the effect name we passed in
+ QList arguments = spy.takeFirst();
+ QCOMPARE(arguments.count(), 2);
+ QCOMPARE(arguments.at(1).toString(), name.toLower());
+ }
+}
+
+void TestScriptedEffectLoader::testLoadAllEffects()
+{
+ MockEffectsHandler mockHandler(KWin::XRenderCompositing);
+ KWin::ScriptedEffectLoader loader;
+
+ KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
+
+ const QString kwin4 = QStringLiteral("kwin4_effect_");
+
+ // prepare the configuration to hard enable/disable the effects we want to load
+ KConfigGroup plugins = config->group("Plugins");
+ plugins.writeEntry(kwin4 + QStringLiteral("dialogparentEnabled"), false);
+ plugins.writeEntry(kwin4 + QStringLiteral("fadeEnabled"), false);
+ plugins.writeEntry(kwin4 + QStringLiteral("fadedesktopEnabled"), false);
+ plugins.writeEntry(kwin4 + QStringLiteral("loginEnabled"), false);
+ plugins.writeEntry(kwin4 + QStringLiteral("maximizeEnabled"), false);
+ plugins.writeEntry(kwin4 + QStringLiteral("minimizeanimationEnabled"), false);
+ plugins.writeEntry(kwin4 + QStringLiteral("scaleinEnabled"), false);
+ plugins.writeEntry(kwin4 + QStringLiteral("translucencyEnabled"), false);
+ plugins.sync();
+
+ loader.setConfig(config);
+
+ qRegisterMetaType();
+ QSignalSpy spy(&loader, SIGNAL(effectLoaded(KWin::Effect*,QString)));
+ // connect to signal to ensure that we delete the Effect again as the Effect doesn't have a parent
+ connect(&loader, &KWin::ScriptedEffectLoader::effectLoaded,
+ [](KWin::Effect *effect) {
+ effect->deleteLater();
+ }
+ );
+
+ // the config is prepared so that no Effect gets loaded!
+ loader.queryAndLoadAll();
+
+ // we need to wait some time because it's queued and in a thread
+ QVERIFY(!spy.wait(100));
+
+ // now let's prepare a config which has one effect explicitly enabled
+ plugins.writeEntry(kwin4 + QStringLiteral("scaleinEnabled"), true);
+ plugins.sync();
+
+ loader.queryAndLoadAll();
+ // should load one effect in first go
+ QVERIFY(spy.wait(100));
+ // and afterwards it should not load another one
+ QVERIFY(!spy.wait(10));
+
+ QCOMPARE(spy.size(), 1);
+ // if we caught a signal it should have the effect name we passed in
+ QList arguments = spy.takeFirst();
+ QCOMPARE(arguments.count(), 2);
+ QCOMPARE(arguments.at(1).toString(), kwin4 + QStringLiteral("scalein"));
+ spy.clear();
+
+ // let's delete one of the default entries
+ plugins.deleteEntry(kwin4 + QStringLiteral("fadeEnabled"));
+ plugins.sync();
+
+ QVERIFY(spy.isEmpty());
+ loader.queryAndLoadAll();
+
+ // let's use qWait as we need to wait for two signals to be emitted
+ QTest::qWait(100);
+ QCOMPARE(spy.size(), 2);
+ QStringList loadedEffects;
+ for (auto &list : spy) {
+ QCOMPARE(list.size(), 2);
+ loadedEffects << list.at(1).toString();
+ }
+ qSort(loadedEffects);
+ QCOMPARE(loadedEffects.at(0), kwin4 + QStringLiteral("fade"));
+ QCOMPARE(loadedEffects.at(1), kwin4 + QStringLiteral("scalein"));
+}
+
+QTEST_MAIN(TestScriptedEffectLoader)
+#include "test_scripted_effectloader.moc"
diff --git a/effectloader.cpp b/effectloader.cpp
index afadd9d42e..0d934808ca 100644
--- a/effectloader.cpp
+++ b/effectloader.cpp
@@ -20,12 +20,17 @@ along with this program. If not, see .
// own
#include "effectloader.h"
// KWin
+#include
#include
#include "effects/effect_builtins.h"
+#include "scripting/scriptedeffect.h"
// KDE
#include
+#include
// Qt
+#include
#include
+#include
#include
#include
@@ -182,4 +187,124 @@ QByteArray BuiltInEffectLoader::internalName(const QString& name) const
return internalName.toUtf8();
}
+static const QString s_nameProperty = QStringLiteral("X-KDE-PluginInfo-Name");
+static const QString s_jsConstraint = QStringLiteral("[X-Plasma-API] == 'javascript'");
+static const QString s_serviceType = QStringLiteral("KWin/Effect");
+
+ScriptedEffectLoader::ScriptedEffectLoader(QObject *parent)
+ : AbstractEffectLoader(parent)
+ , m_queue(new EffectLoadQueue(this))
+{
+}
+
+ScriptedEffectLoader::~ScriptedEffectLoader()
+{
+}
+
+bool ScriptedEffectLoader::hasEffect(const QString &name) const
+{
+ return findEffect(name);
+}
+
+bool ScriptedEffectLoader::isEffectSupported(const QString &name) const
+{
+ // scripted effects are in general supported
+ return hasEffect(name);
+}
+
+QStringList ScriptedEffectLoader::listOfKnownEffects() const
+{
+ const KService::List effects = findAllEffects();
+ QStringList result;
+ for (KService::Ptr service : effects) {
+ result << service->property(s_nameProperty).toString();
+ }
+ return result;
+}
+
+bool ScriptedEffectLoader::loadEffect(const QString &name)
+{
+ KService::Ptr effect = findEffect(name);
+ if (!effect) {
+ return false;
+ }
+ return loadEffect(effect, LoadEffectFlag::Load);
+}
+
+bool ScriptedEffectLoader::loadEffect(KService::Ptr effect, LoadEffectFlags flags)
+{
+ const QString name = effect->property(s_nameProperty).toString();
+ if (!flags.testFlag(LoadEffectFlag::Load)) {
+ qDebug() << "Loading flags disable effect: " << name;
+ return false;
+ }
+ if (m_loadedEffects.contains(name)) {
+ qDebug() << name << "already loaded";
+ return false;
+ }
+
+ const QString scriptName = effect->property(QStringLiteral("X-Plasma-MainScript")).toString();
+ if (scriptName.isEmpty()) {
+ qDebug() << "X-Plasma-MainScript not set";
+ return false;
+ }
+ const QString scriptFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
+ QStringLiteral(KWIN_NAME) + QStringLiteral("/effects/") + name + QStringLiteral("/contents/") + scriptName);
+ if (scriptFile.isNull()) {
+ qDebug() << "Could not locate the effect script";
+ return false;
+ }
+ ScriptedEffect *e = ScriptedEffect::create(name, scriptFile);
+ if (!e) {
+ qDebug() << "Could not initialize scripted effect: " << name;
+ return false;
+ }
+ connect(e, &ScriptedEffect::destroyed, this,
+ [this, name]() {
+ m_loadedEffects.removeAll(name);
+ }
+ );
+
+ qDebug() << "Successfully loaded scripted effect: " << name;
+ emit effectLoaded(e, name);
+ m_loadedEffects << name;
+ return true;
+}
+
+void ScriptedEffectLoader::queryAndLoadAll()
+{
+ // perform querying for the services in a thread
+ QFutureWatcher *watcher = new QFutureWatcher(this);
+ connect(watcher, &QFutureWatcher::finished, this,
+ [this, watcher]() {
+ const KService::List effects = watcher->result();
+ for (KService::Ptr effect : effects) {
+ const LoadEffectFlags flags = readConfig(effect->property(s_nameProperty).toString(),
+ effect->property(QStringLiteral("X-KDE-PluginInfo-EnabledByDefault")).toBool());
+ if (flags.testFlag(LoadEffectFlag::Load)) {
+ m_queue->enqueue(qMakePair(effect, flags));
+ }
+ }
+ watcher->deleteLater();
+ },
+ Qt::QueuedConnection);
+ watcher->setFuture(QtConcurrent::run(this, &ScriptedEffectLoader::findAllEffects));
+}
+
+KService::List ScriptedEffectLoader::findAllEffects() const
+{
+ return KServiceTypeTrader::self()->query(s_serviceType, s_jsConstraint);
+}
+
+KService::Ptr ScriptedEffectLoader::findEffect(const QString &name) const
+{
+ const QString constraint = QStringLiteral("%1 and [%2] == '%3'").arg(s_jsConstraint).arg(s_nameProperty).arg(name.toLower());
+ const KService::List services = KServiceTypeTrader::self()->query(s_serviceType,
+ constraint);
+ if (!services.isEmpty()) {
+ return services.first();
+ }
+ return KService::Ptr();
+}
+
} // namespace KWin
diff --git a/effectloader.h b/effectloader.h
index 2a4a2db172..dd13a0e9d3 100644
--- a/effectloader.h
+++ b/effectloader.h
@@ -21,6 +21,7 @@ along with this program. If not, see .
#define KWIN_EFFECT_LOADER_H
// KDE
#include
+#include
// Qt
#include
#include
@@ -283,6 +284,32 @@ private:
QMap m_loadedEffects;
};
+/**
+ * @brief Can load scripted Effects
+ *
+ */
+class 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 queryAndLoadAll() override;
+ bool loadEffect(const QString &name) override;
+ bool loadEffect(KService::Ptr effect, LoadEffectFlags flags);
+
+private:
+ KService::List findAllEffects() const;
+ KService::Ptr findEffect(const QString &name) const;
+ QStringList m_loadedEffects;
+ EffectLoadQueue< ScriptedEffectLoader, KService::Ptr > *m_queue;
+};
+
}
Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::LoadEffectFlags)
diff --git a/scripting/scriptedeffect.cpp b/scripting/scriptedeffect.cpp
index a205d1ca22..056e4e8c06 100644
--- a/scripting/scriptedeffect.cpp
+++ b/scripting/scriptedeffect.cpp
@@ -411,7 +411,9 @@ bool ScriptedEffect::init(const QString &effectName, const QString &pathToScript
QScriptValue effectsObject = m_engine->newQObject(effects, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater);
m_engine->globalObject().setProperty(QStringLiteral("effects"), effectsObject, QScriptValue::Undeletable);
m_engine->globalObject().setProperty(QStringLiteral("Effect"), m_engine->newQMetaObject(&ScriptedEffect::staticMetaObject));
+#ifndef KWIN_UNIT_TEST
m_engine->globalObject().setProperty(QStringLiteral("KWin"), m_engine->newQMetaObject(&WorkspaceWrapper::staticMetaObject));
+#endif
m_engine->globalObject().setProperty(QStringLiteral("QEasingCurve"), m_engine->newQMetaObject(&QEasingCurve::staticMetaObject));
m_engine->globalObject().setProperty(QStringLiteral("effect"), m_engine->newQObject(this, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater), QScriptValue::Undeletable);
m_engine->globalObject().setProperty(QStringLiteral("AnimationData"), m_engine->scriptValueFromQMetaObject());