diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index ce9ccafa5d..475a7a5f90 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -146,3 +146,37 @@ target_link_libraries(testScriptedEffectLoader add_test(kwin-testScriptedEffectLoader testScriptedEffectLoader) ecm_mark_as_test(testScriptedEffectLoader) + +######################################################## +# Test PluginEffectLoader +######################################################## +set( testPluginEffectLoader_SRCS + test_plugin_effectloader.cpp + mock_effectshandler.cpp + ../effectloader.cpp +) +add_executable( testPluginEffectLoader ${testPluginEffectLoader_SRCS}) + +target_link_libraries(testPluginEffectLoader + Qt5::Concurrent + Qt5::Test + kwineffects + kwin4_effect_builtins +) + +add_test(kwin-testPluginEffectLoader testPluginEffectLoader) +ecm_mark_as_test(testPluginEffectLoader) + +######################################################## +# FakeEffectPlugin +######################################################## +add_library(fakeeffectplugin MODULE fakeeffectplugin.cpp) +set_target_properties(fakeeffectplugin PROPERTIES PREFIX "") +target_link_libraries(fakeeffectplugin kwineffects) + +######################################################## +# FakeEffectPlugin-Version +######################################################## +add_library(effectversionplugin MODULE fakeeffectplugin_version.cpp) +set_target_properties(effectversionplugin PROPERTIES PREFIX "") +target_link_libraries(effectversionplugin kwineffects) diff --git a/autotests/fakeeffectplugin.cpp b/autotests/fakeeffectplugin.cpp new file mode 100644 index 0000000000..73cc7e4bc8 --- /dev/null +++ b/autotests/fakeeffectplugin.cpp @@ -0,0 +1,49 @@ +/******************************************************************** +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 + +namespace KWin +{ + +class FakeEffect : public Effect +{ + Q_OBJECT +public: + FakeEffect() {} + virtual ~FakeEffect() {} + + static bool supported() { + return effects->isOpenGLCompositing(); + } + + static bool enabledByDefault() { + return effects->property("testEnabledByDefault").toBool(); + } +}; + +} // namespace + +KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED( FakeEffectPluginFactory, + KWin::FakeEffect, + "fakeeffectplugin.json", + return KWin::FakeEffect::supported();, + return KWin::FakeEffect::enabledByDefault();) + +#include "fakeeffectplugin.moc" diff --git a/autotests/fakeeffectplugin.json b/autotests/fakeeffectplugin.json new file mode 100644 index 0000000000..a0facd351b --- /dev/null +++ b/autotests/fakeeffectplugin.json @@ -0,0 +1,9 @@ +{ + "Type": "Service", + "X-KDE-Library": "fakeeffectplugin", + "X-KDE-PluginInfo-EnabledByDefault": true, + "X-KDE-PluginInfo-Name": "fakeeffectplugin", + "X-KDE-ServiceTypes": [ + "KWin/Effect" + ] +} diff --git a/autotests/fakeeffectplugin_version.cpp b/autotests/fakeeffectplugin_version.cpp new file mode 100644 index 0000000000..1d1f5bcc03 --- /dev/null +++ b/autotests/fakeeffectplugin_version.cpp @@ -0,0 +1,50 @@ +/******************************************************************** +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 + +namespace KWin +{ + +class FakeVersionEffect : public Effect +{ + Q_OBJECT +public: + FakeVersionEffect() {} + virtual ~FakeVersionEffect() {} +}; + +} // namespace + +class FakeEffectPluginFactory : public KWin::EffectPluginFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID KPluginFactory_iid FILE "fakeeffectplugin_version.json") + Q_INTERFACES(KPluginFactory) +public: + explicit FakeEffectPluginFactory(const char *a = 0, QObject *b = 0) : KWin::EffectPluginFactory(a, b) {} + ~FakeEffectPluginFactory() {} + KWin::Effect *createEffect() const override { + return new KWin::FakeVersionEffect(); + } +}; +K_EXPORT_PLUGIN_VERSION(quint32(KWIN_EFFECT_API_VERSION) - 1) + + +#include "fakeeffectplugin_version.moc" diff --git a/autotests/fakeeffectplugin_version.json b/autotests/fakeeffectplugin_version.json new file mode 100644 index 0000000000..a0a6aa709d --- /dev/null +++ b/autotests/fakeeffectplugin_version.json @@ -0,0 +1,9 @@ +{ + "Type": "Service", + "X-KDE-Library": "effectversionplugin", + "X-KDE-PluginInfo-EnabledByDefault": true, + "X-KDE-PluginInfo-Name": "effectversion", + "X-KDE-ServiceTypes": [ + "KWin/Effect" + ] +} diff --git a/autotests/test_plugin_effectloader.cpp b/autotests/test_plugin_effectloader.cpp new file mode 100644 index 0000000000..4340e36c5e --- /dev/null +++ b/autotests/test_plugin_effectloader.cpp @@ -0,0 +1,382 @@ +/******************************************************************** +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 ScriptedEffect::create +// KDE +#include +#include +#include +// Qt +#include +#include +Q_DECLARE_METATYPE(KWin::CompositingType) +Q_DECLARE_METATYPE(KWin::LoadEffectFlag) +Q_DECLARE_METATYPE(KWin::LoadEffectFlags) +Q_DECLARE_METATYPE(KWin::Effect*) + +namespace KWin +{ + +ScriptedEffect *ScriptedEffect::create(KService::Ptr) +{ + return nullptr; +} + +} + +class TestPluginEffectLoader : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testHasEffect_data(); + void testHasEffect(); + void testKnownEffects(); + void testSupported_data(); + void testSupported(); + void testLoadEffect_data(); + void testLoadEffect(); + void testLoadPluginEffect_data(); + void testLoadPluginEffect(); + void testLoadAllEffects(); +}; + +void TestPluginEffectLoader::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; + // all the scripted effects should fail + QTest::newRow("Fade") << QStringLiteral("kwin4_effect_fade") << false; + QTest::newRow("FadeDesktop") << QStringLiteral("kwin4_effect_fadedesktop") << false; + QTest::newRow("DialogParent") << QStringLiteral("kwin4_effect_dialogparent") << false; + QTest::newRow("Login") << QStringLiteral("kwin4_effect_login") << false; + QTest::newRow("Maximize") << QStringLiteral("kwin4_effect_maximize") << false; + QTest::newRow("ScaleIn") << QStringLiteral("kwin4_effect_scalein") << false; + QTest::newRow("Translucency") << QStringLiteral("kwin4_effect_translucency") << false; + // and the fake effects we use here + QTest::newRow("fakeeffectplugin") << QStringLiteral("fakeeffectplugin") << true; + QTest::newRow("fakeeffectplugin CS") << QStringLiteral("fakeEffectPlugin") << true; + QTest::newRow("effectversion") << QStringLiteral("effectversion") << true; +} + +void TestPluginEffectLoader::testHasEffect() +{ + QFETCH(QString, name); + QFETCH(bool, expected); + + KWin::PluginEffectLoader loader; + loader.setPluginSubDirectory(QString()); + QCOMPARE(loader.hasEffect(name), expected); +} + +void TestPluginEffectLoader::testKnownEffects() +{ + QStringList expectedEffects; + expectedEffects << QStringLiteral("fakeeffectplugin") << QStringLiteral("effectversion"); + + KWin::PluginEffectLoader loader; + loader.setPluginSubDirectory(QString()); + 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 TestPluginEffectLoader::testSupported_data() +{ + QTest::addColumn("name"); + QTest::addColumn("expected"); + QTest::addColumn("type"); + + const KWin::CompositingType xc = KWin::XRenderCompositing; + const KWin::CompositingType oc = KWin::OpenGL2Compositing; + + QTest::newRow("invalid") << QStringLiteral("blur") << false << xc; + QTest::newRow("fake - xrender") << QStringLiteral("fakeeffectplugin") << false << xc; + QTest::newRow("fake - opengl") << QStringLiteral("fakeeffectplugin") << true << oc; + QTest::newRow("fake - CS") << QStringLiteral("fakeEffectPlugin") << true << oc; + QTest::newRow("version") << QStringLiteral("effectversion") << false << xc; +} + +void TestPluginEffectLoader::testSupported() +{ + QFETCH(QString, name); + QFETCH(bool, expected); + QFETCH(KWin::CompositingType, type); + + MockEffectsHandler mockHandler(type); + KWin::PluginEffectLoader loader; + loader.setPluginSubDirectory(QString()); + QCOMPARE(loader.isEffectSupported(name), expected); +} + +void TestPluginEffectLoader::testLoadEffect_data() +{ + QTest::addColumn("name"); + QTest::addColumn("expected"); + QTest::addColumn("type"); + + const KWin::CompositingType xc = KWin::XRenderCompositing; + const KWin::CompositingType oc = KWin::OpenGL2Compositing; + + QTest::newRow("invalid") << QStringLiteral("slide") << false << xc; + QTest::newRow("fake - xrender") << QStringLiteral("fakeeffectplugin") << false << xc; + QTest::newRow("fake - opengl") << QStringLiteral("fakeeffectplugin") << true << oc; + QTest::newRow("fake - CS") << QStringLiteral("fakeEffectPlugin") << true << oc; + QTest::newRow("version") << QStringLiteral("effectversion") << false << xc; +} + +void TestPluginEffectLoader::testLoadEffect() +{ + QFETCH(QString, name); + QFETCH(bool, expected); + QFETCH(KWin::CompositingType, type); + + MockEffectsHandler mockHandler(type); + KWin::PluginEffectLoader loader; + loader.setPluginSubDirectory(QString()); + 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::PluginEffectLoader::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 TestPluginEffectLoader::testLoadPluginEffect_data() +{ + QTest::addColumn("name"); + QTest::addColumn("expected"); + QTest::addColumn("type"); + QTest::addColumn("loadFlags"); + QTest::addColumn("enabledByDefault"); + + const KWin::CompositingType xc = KWin::XRenderCompositing; + const KWin::CompositingType oc = KWin::OpenGL2Compositing; + + 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, but not supported + QTest::newRow("fakeeffectplugin") << QStringLiteral("fakeeffectplugin") << false << xc << checkDefault << false; + // enabled by default, check default false + QTest::newRow("supported, check default error") << QStringLiteral("fakeeffectplugin") << false << oc << checkDefault << false; + // enabled by default, check default true + QTest::newRow("supported, check default") << QStringLiteral("fakeeffectplugin") << true << oc << checkDefault << true; + // enabled by default, check default false + QTest::newRow("supported, check default error, forced") << QStringLiteral("fakeeffectplugin") << true << oc << forceFlags << false; + // enabled by default, check default true + QTest::newRow("supported, check default, don't load") << QStringLiteral("fakeeffectplugin") << false << oc << dontLoadFlags << true; + // incorrect version + QTest::newRow("Version") << QStringLiteral("effectversion") << false << xc << forceFlags << true; +} + +void TestPluginEffectLoader::testLoadPluginEffect() +{ + QFETCH(QString, name); + QFETCH(bool, expected); + QFETCH(KWin::CompositingType, type); + QFETCH(KWin::LoadEffectFlags, loadFlags); + QFETCH(bool, enabledByDefault); + + MockEffectsHandler mockHandler(type); + mockHandler.setProperty("testEnabledByDefault", enabledByDefault); + KWin::PluginEffectLoader loader; + loader.setPluginSubDirectory(QString()); + KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + loader.setConfig(config); + + const auto plugins = KPluginTrader::self()->query(QString(), + QStringLiteral("KWin/Effect"), + QStringLiteral("[X-KDE-PluginInfo-Name] == '%1'").arg(name)); + QCOMPARE(plugins.size(), 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::PluginEffectLoader::effectLoaded, + [&name](KWin::Effect *effect, const QString &effectName) { + QCOMPARE(effectName, name); + effect->deleteLater(); + } + ); + // try to load the Effect + QCOMPARE(loader.loadEffect(plugins.first(), loadFlags), expected); + // loading again should fail + QVERIFY(!loader.loadEffect(plugins.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); + } + 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(plugins.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); + } +} + +void TestPluginEffectLoader::testLoadAllEffects() +{ + MockEffectsHandler mockHandler(KWin::OpenGL2Compositing); + mockHandler.setProperty("testEnabledByDefault", true); + KWin::PluginEffectLoader loader; + loader.setPluginSubDirectory(QString()); + + KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + + // prepare the configuration to hard enable/disable the effects we want to load + KConfigGroup plugins = config->group("Plugins"); + plugins.writeEntry(QStringLiteral("fakeeffectpluginEnabled"), 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::PluginEffectLoader::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(QStringLiteral("fakeeffectpluginEnabled"), 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(), QStringLiteral("fakeeffectplugin")); + spy.clear(); +} + +QTEST_MAIN(TestPluginEffectLoader) +#include "test_plugin_effectloader.moc" diff --git a/effectloader.cpp b/effectloader.cpp index 328524209c..5025cf25d2 100644 --- a/effectloader.cpp +++ b/effectloader.cpp @@ -26,6 +26,7 @@ along with this program. If not, see . #include "scripting/scriptedeffect.h" // KDE #include +#include #include // Qt #include @@ -296,4 +297,160 @@ KService::Ptr ScriptedEffectLoader::findEffect(const QString &name) const return KService::Ptr(); } + +PluginEffectLoader::PluginEffectLoader(QObject *parent) + : AbstractEffectLoader(parent) + , m_queue(new EffectLoadQueue< PluginEffectLoader, KPluginInfo>(this)) + , m_pluginSubDirectory(QStringLiteral("kwin/effects/plugins/")) +{ +} + +PluginEffectLoader::~PluginEffectLoader() +{ +} + +bool PluginEffectLoader::hasEffect(const QString &name) const +{ + KPluginInfo info = findEffect(name); + return info.isValid(); +} + +KPluginInfo PluginEffectLoader::findEffect(const QString &name) const +{ + const QString constraint = QStringLiteral("[%1] == '%2'").arg(s_nameProperty).arg(name.toLower()); + KPluginInfo::List plugins = KPluginTrader::self()->query(m_pluginSubDirectory, s_serviceType, constraint); + if (plugins.isEmpty()) { + return KPluginInfo(); + } + return plugins.first(); +} + +bool PluginEffectLoader::isEffectSupported(const QString &name) const +{ + if (EffectPluginFactory *effectFactory = factory(findEffect(name))) { + return effectFactory->isSupported(); + } + return false; +} + +EffectPluginFactory *PluginEffectLoader::factory(const KPluginInfo &info) const +{ + if (!info.isValid()) { + return nullptr; + } + KPluginLoader loader(info.libraryPath()); + if (loader.pluginVersion() != KWIN_EFFECT_API_VERSION) { + qDebug() << info.pluginName() << " has not matching plugin version, expected " << KWIN_EFFECT_API_VERSION << "got " << loader.pluginVersion(); + return nullptr; + } + KPluginFactory *factory = loader.factory(); + if (!factory) { + qDebug() << "Did not get KPluginFactory for " << info.pluginName(); + return nullptr; + } + return dynamic_cast< EffectPluginFactory* >(factory); +} + +QStringList PluginEffectLoader::listOfKnownEffects() const +{ + const KPluginInfo::List plugins = findAllEffects(); + QStringList result; + for (const KPluginInfo &plugin : plugins) { + result << plugin.pluginName(); + } + return result; +} + +bool PluginEffectLoader::loadEffect(const QString &name) +{ + KPluginInfo info = findEffect(name); + if (!info.isValid()) { + return false; + } + return loadEffect(info, LoadEffectFlag::Load); +} + +bool PluginEffectLoader::loadEffect(const KPluginInfo &info, LoadEffectFlags flags) +{ + if (!info.isValid()) { + qDebug() << "Plugin info is not valid"; + return false; + } + const QString name = info.pluginName(); + if (!flags.testFlag(LoadEffectFlag::Load)) { + qDebug() << "Loading flags disable effect: " << name; + return false; + } + if (m_loadedEffects.contains(name)) { + qDebug() << name << " already loaded"; + return false; + } + EffectPluginFactory *effectFactory = factory(info); + if (!effectFactory) { + qDebug() << "Couldn't get an EffectPluginFactory for: " << name; + return false; + } + +#ifndef KWIN_UNIT_TEST + effects->makeOpenGLContextCurrent(); +#endif + if (!effectFactory->isSupported()) { + qDebug() << "Effect is not supported: " << name; + return false; + } + + if (flags.testFlag(LoadEffectFlag::CheckDefaultFunction)) { + if (!effectFactory->enabledByDefault()) { + qDebug() << "Enabled by default function disables effect: " << name; + return false; + } + } + + // ok, now we can try to create the Effect + Effect *e = effectFactory->createEffect(); + if (!e) { + qDebug() << "Failed to create effect: " << name; + return false; + } + // insert in our loaded effects + m_loadedEffects << name; + connect(e, &Effect::destroyed, this, + [this, name]() { + m_loadedEffects.removeAll(name); + } + ); + qDebug() << "Successfully loaded plugin effect: " << name; + emit effectLoaded(e, name); + return true; +} + +void PluginEffectLoader::queryAndLoadAll() +{ + // perform querying for the services in a thread + QFutureWatcher *watcher = new QFutureWatcher(this); + connect(watcher, &QFutureWatcher::finished, this, + [this, watcher]() { + const KPluginInfo::List effects = watcher->result(); + for (const KPluginInfo &effect : effects) { + const LoadEffectFlags flags = readConfig(effect.pluginName(), effect.isPluginEnabledByDefault()); + if (flags.testFlag(LoadEffectFlag::Load)) { + m_queue->enqueue(qMakePair(effect, flags)); + } + } + watcher->deleteLater(); + }, + Qt::QueuedConnection); + watcher->setFuture(QtConcurrent::run(this, &PluginEffectLoader::findAllEffects)); +} + +KPluginInfo::List PluginEffectLoader::findAllEffects() const +{ + return KPluginTrader::self()->query(m_pluginSubDirectory, s_serviceType); +} + +void PluginEffectLoader::setPluginSubDirectory(const QString &directory) +{ + m_pluginSubDirectory = directory; +} + } // namespace KWin diff --git a/effectloader.h b/effectloader.h index dd13a0e9d3..a820816b1f 100644 --- a/effectloader.h +++ b/effectloader.h @@ -29,9 +29,12 @@ along with this program. If not, see . #include #include +class KPluginInfo; + namespace KWin { class Effect; +class EffectPluginFactory; enum class BuiltInEffect; /** @@ -310,6 +313,32 @@ private: EffectLoadQueue< ScriptedEffectLoader, KService::Ptr > *m_queue; }; +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 queryAndLoadAll() override; + bool loadEffect(const QString &name) override; + bool loadEffect(const KPluginInfo &info, LoadEffectFlags flags); + + void setPluginSubDirectory(const QString &directory); + +private: + QList findAllEffects() const; + KPluginInfo findEffect(const QString &name) const; + EffectPluginFactory *factory(const KPluginInfo &info) const; + QStringList m_loadedEffects; + EffectLoadQueue< PluginEffectLoader, KPluginInfo> *m_queue; + QString m_pluginSubDirectory; +}; + } Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::LoadEffectFlags)