From 6622c97601ab29922c1035b11ff7874d344b24a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Tue, 25 Mar 2014 11:39:07 +0100 Subject: [PATCH] [kwin] Add a PluginEffectLoader This is a specialized subclass of AbstractEffectLoader to load binary effect plugins. It used the KPluginTrader to find all candidates to load. The loader is able to detect incorrect ABI versions through the pluginVersion() and uses the methods exposed by the new KWin::EffectPluginFactory to check whether the Effect is supported and should be enabled by default. The unit test for this loader comes with two plugins: one is able to be loaded and provides a supported and enabledByDefault method which can be tweaked during the test to get all the conditions we want to test for. The second plugin uses an incorrect plugin version and thus cannot get loaded. --- autotests/CMakeLists.txt | 34 +++ autotests/fakeeffectplugin.cpp | 49 +++ autotests/fakeeffectplugin.json | 9 + autotests/fakeeffectplugin_version.cpp | 50 ++++ autotests/fakeeffectplugin_version.json | 9 + autotests/test_plugin_effectloader.cpp | 382 ++++++++++++++++++++++++ effectloader.cpp | 157 ++++++++++ effectloader.h | 29 ++ 8 files changed, 719 insertions(+) create mode 100644 autotests/fakeeffectplugin.cpp create mode 100644 autotests/fakeeffectplugin.json create mode 100644 autotests/fakeeffectplugin_version.cpp create mode 100644 autotests/fakeeffectplugin_version.json create mode 100644 autotests/test_plugin_effectloader.cpp diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index ce9ccafa5..475a7a5f9 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 000000000..73cc7e4bc --- /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 000000000..a0facd351 --- /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 000000000..1d1f5bcc0 --- /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 000000000..a0a6aa709 --- /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 000000000..4340e36c5 --- /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 328524209..5025cf25d 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 dd13a0e9d..a820816b1 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)