From 0fd9a1eeee1da1bf6dc94b4450c4be154643852c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Sat, 22 Mar 2014 09:48:07 +0100 Subject: [PATCH] [kwin] Introduce a new Effect Loading mechanism Effect loading gets split by the kind of effects KWin supports: * Built-In Effects * Scripted Effects * Binary Plugin Effects For this a new AbstractEffectLoader is added which will have several sub-classes: * BuiltInEffectLoader * ScriptedEffectLoader * PluginEffectLoader * EffectLoader The EffectLoader will be what the EffectsHandlerImpl is using and it just delegates to the three other types of loaders. Thus the handler doesn't need to care about the different kinds of effects. The loading is supposed to be completely async and the EffectLoader emits a signal whenever an Effect got loaded. The EffectsHandlerImpl is supposed to connect to this signal and insert it into its own Effect management. Unloading is not performed by the loader, but by the EffectsHandler. There is one important change which needs to be implemented: the ordering cannot be provided by the loader and thus needs to be added to the Effects directly. So far only the BuiltInEffectsLoader is implemented. It's not yet integrated into the EffectsHandlerImpl, but a unit test is added which tries to perform the various operations provided by the loader and the BuiltInEffects. The test should cover all cases except the Check Default functionality which is only used by Blur and Contrast effects. This cannot be mocked yet as the GLPlatform doesn't allow mocking yet. --- CMakeLists.txt | 1 + autotests/CMakeLists.txt | 20 + autotests/mock_effectshandler.cpp | 25 ++ autotests/mock_effectshandler.h | 221 ++++++++++ autotests/test_builtin_effectloader.cpp | 540 ++++++++++++++++++++++++ effectloader.cpp | 185 ++++++++ effectloader.h | 289 +++++++++++++ 7 files changed, 1281 insertions(+) create mode 100644 autotests/mock_effectshandler.cpp create mode 100644 autotests/mock_effectshandler.h create mode 100644 autotests/test_builtin_effectloader.cpp create mode 100644 effectloader.cpp create mode 100644 effectloader.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c0d6345c7c..99542afe83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -290,6 +290,7 @@ set(kwin_KDEINIT_SRCS lanczosfilter.cpp deleted.cpp effects.cpp + effectloader.cpp compositingprefs.cpp paintredirector.cpp virtualdesktops.cpp diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 038a26d0d9..1cb80b9b86 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,3 +1,4 @@ +add_definitions(-DKWIN_UNIT_TEST) ######################################################## # Test ScreenPaintData ######################################################## @@ -98,3 +99,22 @@ target_link_libraries( testXcbWindow ) add_test(kwin-testXcbWindow testXcbWindow) ecm_mark_as_test(testXcbWindow) + +######################################################## +# Test BuiltInEffectLoader +######################################################## +set( testBuiltInEffectLoader_SRCS + test_builtin_effectloader.cpp + mock_effectshandler.cpp + ../effectloader.cpp +) +add_executable( testBuiltInEffectLoader ${testBuiltInEffectLoader_SRCS}) + +target_link_libraries(testBuiltInEffectLoader + Qt5::Test + kwineffects + kwin4_effect_builtins +) + +add_test(kwin-testBuiltInEffectLoader testBuiltInEffectLoader) +ecm_mark_as_test(testBuiltInEffectLoader) diff --git a/autotests/mock_effectshandler.cpp b/autotests/mock_effectshandler.cpp new file mode 100644 index 0000000000..8f3769c80f --- /dev/null +++ b/autotests/mock_effectshandler.cpp @@ -0,0 +1,25 @@ +/******************************************************************** +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 "mock_effectshandler.h" + +MockEffectsHandler::MockEffectsHandler(KWin::CompositingType type) + : EffectsHandler(type) +{ +} diff --git a/autotests/mock_effectshandler.h b/autotests/mock_effectshandler.h new file mode 100644 index 0000000000..4ef767dfa5 --- /dev/null +++ b/autotests/mock_effectshandler.h @@ -0,0 +1,221 @@ +/******************************************************************** +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 . +*********************************************************************/ +#ifndef MOCK_EFFECTS_HANDLER_H +#define MOCK_EFFECTS_HANDLER_H + +#include +class MockEffectsHandler : public KWin::EffectsHandler +{ + Q_OBJECT +public: + explicit MockEffectsHandler(KWin::CompositingType type); + void activateWindow(KWin::EffectWindow *) override {} + KWin::Effect *activeFullScreenEffect() const { + return nullptr; + } + int activeScreen() const override { + return 0; + } + KWin::EffectWindow *activeWindow() const override { + return nullptr; + } + void addRepaint(const QRect &) override {} + void addRepaint(const QRegion &) override {} + void addRepaint(int, int, int, int) override {} + void addRepaintFull() override {} + double animationTimeFactor() const override { + return 0; + } + xcb_atom_t announceSupportProperty(const QByteArray &, KWin::Effect *) override { + return XCB_ATOM_NONE; + } + void buildQuads(KWin::EffectWindow *, KWin::WindowQuadList &) override {} + QRect clientArea(KWin::clientAreaOption, const QPoint &, int) const override { + return QRect(); + } + QRect clientArea(KWin::clientAreaOption, const KWin::EffectWindow *) const override { + return QRect(); + } + QRect clientArea(KWin::clientAreaOption, int, int) const override { + return QRect(); + } + void closeTabBox() override {} + QString currentActivity() const override { + return QString(); + } + int currentDesktop() const override { + return 0; + } + int currentTabBoxDesktop() const override { + return 0; + } + QList< int > currentTabBoxDesktopList() const override { + return QList(); + } + KWin::EffectWindow *currentTabBoxWindow() const override { + return nullptr; + } + KWin::EffectWindowList currentTabBoxWindowList() const override { + return KWin::EffectWindowList(); + } + QPoint cursorPos() const override { + return QPoint(); + } + bool decorationsHaveAlpha() const override { + return false; + } + bool decorationSupportsBlurBehind() const override { + return false; + } + void defineCursor(Qt::CursorShape) override {} + void deleteRootProperty(long int) const override {} + int desktopAbove(int, bool) const override { + return 0; + } + int desktopAtCoords(QPoint) const override { + return 0; + } + int desktopBelow(int, bool) const override { + return 0; + } + QPoint desktopCoords(int) const override { + return QPoint(); + } + QPoint desktopGridCoords(int) const override { + return QPoint(); + } + int desktopGridHeight() const override { + return 0; + } + QSize desktopGridSize() const override { + return QSize(); + } + int desktopGridWidth() const override { + return 0; + } + QString desktopName(int) const override { + return QString(); + } + int desktopToLeft(int, bool) const override { + return 0; + } + int desktopToRight(int, bool) const override { + return 0; + } + void doneOpenGLContextCurrent() override {} + void drawWindow(KWin::EffectWindow *, int, QRegion, KWin::WindowPaintData &) override {} + KWin::EffectFrame *effectFrame(KWin::EffectFrameStyle, bool, const QPoint &, Qt::Alignment) const override { + return nullptr; + } + KWin::EffectWindow *findWindow(WId) const override { + return nullptr; + } + void *getProxy(QString) override { + return nullptr; + } + bool grabKeyboard(KWin::Effect *) override { + return nullptr; + } + bool hasDecorationShadows() const override { + return false; + } + bool isScreenLocked() const override { + return false; + } + QVariant kwinOption(KWin::KWinOption) override { + return QVariant(); + } + bool makeOpenGLContextCurrent() override { + return false; + } + void moveWindow(KWin::EffectWindow *, const QPoint &, bool, double) override {} + KWin::WindowQuadType newWindowQuadType() override { + return KWin::WindowQuadError; + } + int numberOfDesktops() const override { + return 0; + } + int numScreens() const override { + return 0; + } + bool optionRollOverDesktops() const override { + return false; + } + void paintEffectFrame(KWin::EffectFrame *, QRegion, double, double) override {} + void paintScreen(int, QRegion, KWin::ScreenPaintData &) override {} + void paintWindow(KWin::EffectWindow *, int, QRegion, KWin::WindowPaintData &) override {} + void postPaintScreen() override {} + void postPaintWindow(KWin::EffectWindow *) override {} + void prePaintScreen(KWin::ScreenPrePaintData &, int) override {} + void prePaintWindow(KWin::EffectWindow *, KWin::WindowPrePaintData &, int) override {} + QByteArray readRootProperty(long int, long int, int) const override { + return QByteArray(); + } + void reconfigure() override {} + void refTabBox() override {} + void registerAxisShortcut(Qt::KeyboardModifiers, KWin::PointerAxisDirection, QAction *) override {} + void registerGlobalShortcut(const QKeySequence &, QAction *) override {} + void registerPointerShortcut(Qt::KeyboardModifiers, Qt::MouseButton, QAction *) override {} + void registerPropertyType(long int, bool) override {} + void reloadEffect(KWin::Effect *) override {} + void removeSupportProperty(const QByteArray &, KWin::Effect *) override {} + void reserveElectricBorder(KWin::ElectricBorder, KWin::Effect *) override {} + QPainter *scenePainter() override { + return nullptr; + } + int screenNumber(const QPoint &) const override { + return 0; + } + void setActiveFullScreenEffect(KWin::Effect *) override {} + void setCurrentDesktop(int) override {} + void setElevatedWindow(KWin::EffectWindow *, bool) override {} + void setNumberOfDesktops(int) override {} + void setShowingDesktop(bool) override {} + void setTabBoxDesktop(int) override {} + void setTabBoxWindow(KWin::EffectWindow*) override {} + KWin::EffectWindowList stackingOrder() const override { + return KWin::EffectWindowList(); + } + void startMouseInterception(KWin::Effect *, Qt::CursorShape) override {} + void startMousePolling() override {} + void stopMouseInterception(KWin::Effect *) override {} + void stopMousePolling() override {} + void ungrabKeyboard() override {} + void unrefTabBox() override {} + void unreserveElectricBorder(KWin::ElectricBorder, KWin::Effect *) override {} + QRect virtualScreenGeometry() const override { + return QRect(); + } + QSize virtualScreenSize() const override { + return QSize(); + } + void windowToDesktop(KWin::EffectWindow *, int) override {} + void windowToScreen(KWin::EffectWindow *, int) override {} + int workspaceHeight() const override { + return 0; + } + int workspaceWidth() const override { + return 0; + } + long unsigned int xrenderBufferPicture() override { + return 0; + } +}; +#endif diff --git a/autotests/test_builtin_effectloader.cpp b/autotests/test_builtin_effectloader.cpp new file mode 100644 index 0000000000..ad70781ecf --- /dev/null +++ b/autotests/test_builtin_effectloader.cpp @@ -0,0 +1,540 @@ +/******************************************************************** +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 "../effects/effect_builtins.h" +#include "mock_effectshandler.h" +// KDE +#include +#include +// Qt +#include +#include +Q_DECLARE_METATYPE(KWin::CompositingType) +Q_DECLARE_METATYPE(KWin::LoadEffectFlag) +Q_DECLARE_METATYPE(KWin::LoadEffectFlags) +Q_DECLARE_METATYPE(KWin::BuiltInEffect) +Q_DECLARE_METATYPE(KWin::Effect*) + +class TestBuiltInEffectLoader : 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 testLoadBuiltInEffect_data(); + void testLoadBuiltInEffect(); + void testLoadAllEffects(); +}; + +void TestBuiltInEffectLoader::testHasEffect_data() +{ + QTest::addColumn("name"); + QTest::addColumn("expected"); + + QTest::newRow("blur") << QStringLiteral("blur") << true; + QTest::newRow("with kwin4_effect_ prefix") << QStringLiteral("kwin4_effect_blur") << true; + QTest::newRow("case sensitive") << QStringLiteral("BlUR") << true; + QTest::newRow("Contrast") << QStringLiteral("contrast") << true; + QTest::newRow("CoverSwitch") << QStringLiteral("coverswitch") << true; + QTest::newRow("Cube") << QStringLiteral("cube") << true; + QTest::newRow("CubeSlide") << QStringLiteral("cubeslide") << true; + QTest::newRow("Dashboard") << QStringLiteral("dashboard") << true; + QTest::newRow("DesktopGrid") << QStringLiteral("desktopgrid") << true; + QTest::newRow("DimInactive") << QStringLiteral("diminactive") << true; + QTest::newRow("DimScreen") << QStringLiteral("dimscreen") << true; + QTest::newRow("FallApart") << QStringLiteral("fallapart") << true; + QTest::newRow("FlipSwitch") << QStringLiteral("flipswitch") << true; + QTest::newRow("Glide") << QStringLiteral("glide") << true; + QTest::newRow("HighlightWindow") << QStringLiteral("highlightwindow") << true; + QTest::newRow("Invert") << QStringLiteral("invert") << true; + QTest::newRow("Kscreen") << QStringLiteral("kscreen") << true; + QTest::newRow("Logout") << QStringLiteral("logout") << true; + QTest::newRow("LookingGlass") << QStringLiteral("lookingglass") << true; + QTest::newRow("MagicLamp") << QStringLiteral("magiclamp") << true; + QTest::newRow("Magnifier") << QStringLiteral("magnifier") << true; + QTest::newRow("MinimizeAnimation") << QStringLiteral("minimizeanimation") << true; + QTest::newRow("MouseClick") << QStringLiteral("mouseclick") << true; + QTest::newRow("MouseMark") << QStringLiteral("mousemark") << true; + QTest::newRow("PresentWindows") << QStringLiteral("presentwindows") << true; + QTest::newRow("Resize") << QStringLiteral("resize") << true; + QTest::newRow("ScreenEdge") << QStringLiteral("screenedge") << true; + QTest::newRow("ScreenShot") << QStringLiteral("screenshot") << true; + QTest::newRow("Sheet") << QStringLiteral("sheet") << true; + QTest::newRow("ShowFps") << QStringLiteral("showfps") << true; + QTest::newRow("ShowPaint") << QStringLiteral("showpaint") << true; + QTest::newRow("Slide") << QStringLiteral("slide") << true; + QTest::newRow("SlideBack") << QStringLiteral("slideback") << true; + QTest::newRow("SlidingPopups") << QStringLiteral("slidingpopups") << true; + QTest::newRow("SnapHelper") << QStringLiteral("snaphelper") << true; + QTest::newRow("StartupFeedback") << QStringLiteral("startupfeedback") << true; + QTest::newRow("ThumbnailAside") << QStringLiteral("thumbnailaside") << true; + QTest::newRow("TrackMouse") << QStringLiteral("trackmouse") << true; + QTest::newRow("WindowGeometry") << QStringLiteral("windowgeometry") << true; + QTest::newRow("WobblyWindows") << QStringLiteral("wobblywindows") << true; + QTest::newRow("Zoom") << QStringLiteral("zoom") << true; + QTest::newRow("Non Existing") << QStringLiteral("InvalidName") << false; + QTest::newRow("Fade - Scripted") << QStringLiteral("fade") << false; + QTest::newRow("Fade - Scripted + kwin4_effect") << QStringLiteral("kwin4_effect_fade") << false; +} + +void TestBuiltInEffectLoader::testHasEffect() +{ + QFETCH(QString, name); + QFETCH(bool, expected); + + KWin::BuiltInEffectLoader loader; + QCOMPARE(loader.hasEffect(name), expected); +} + +void TestBuiltInEffectLoader::testKnownEffects() +{ + QStringList expectedEffects; + expectedEffects << QStringLiteral("blur") + << QStringLiteral("contrast") + << QStringLiteral("coverswitch") + << QStringLiteral("cube") + << QStringLiteral("cubeslide") + << QStringLiteral("dashboard") + << QStringLiteral("desktopgrid") + << QStringLiteral("diminactive") + << QStringLiteral("dimscreen") + << QStringLiteral("fallapart") + << QStringLiteral("flipswitch") + << QStringLiteral("glide") + << QStringLiteral("highlightwindow") + << QStringLiteral("invert") + << QStringLiteral("kscreen") + << QStringLiteral("logout") + << QStringLiteral("lookingglass") + << QStringLiteral("magiclamp") + << QStringLiteral("magnifier") + << QStringLiteral("minimizeanimation") + << QStringLiteral("mouseclick") + << QStringLiteral("mousemark") + << QStringLiteral("presentwindows") + << QStringLiteral("resize") + << QStringLiteral("screenedge") + << QStringLiteral("screenshot") + << QStringLiteral("sheet") + << QStringLiteral("showfps") + << QStringLiteral("showpaint") + << QStringLiteral("slide") + << QStringLiteral("slideback") + << QStringLiteral("slidingpopups") + << QStringLiteral("snaphelper") + << QStringLiteral("startupfeedback") + << QStringLiteral("thumbnailaside") + << QStringLiteral("trackmouse") + << QStringLiteral("windowgeometry") + << QStringLiteral("wobblywindows") + << QStringLiteral("zoom"); + + KWin::BuiltInEffectLoader loader; + QStringList result = loader.listOfKnownEffects(); + QCOMPARE(result.size(), expectedEffects.size()); + qSort(result); + for (int i = 0; i < expectedEffects.size(); ++i) { + QCOMPARE(result.at(i), expectedEffects.at(i)); + } +} + +void TestBuiltInEffectLoader::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("blur") << QStringLiteral("blur") << false << xc; + // fails for GL as it does proper tests on what's supported and doesn't just check whether it's GL + QTest::newRow("blur-GL") << QStringLiteral("blur") << false << oc; + QTest::newRow("Contrast") << QStringLiteral("contrast") << false << xc; + // fails for GL as it does proper tests on what's supported and doesn't just check whether it's GL + QTest::newRow("Contrast-GL") << QStringLiteral("contrast") << false << oc; + QTest::newRow("CoverSwitch") << QStringLiteral("coverswitch") << false << xc; + QTest::newRow("CoverSwitch-GL") << QStringLiteral("coverswitch") << true << oc; + QTest::newRow("Cube") << QStringLiteral("cube") << false << xc; + QTest::newRow("Cube-GL") << QStringLiteral("cube") << true << oc; + QTest::newRow("CubeSlide") << QStringLiteral("cubeslide") << false << xc; + QTest::newRow("CubeSlide-GL") << QStringLiteral("cubeslide") << true << oc; + QTest::newRow("Dashboard") << QStringLiteral("dashboard") << true << xc; + QTest::newRow("DesktopGrid") << QStringLiteral("desktopgrid") << true << xc; + QTest::newRow("DimInactive") << QStringLiteral("diminactive") << true << xc; + QTest::newRow("DimScreen") << QStringLiteral("dimscreen") << true << xc; + QTest::newRow("FallApart") << QStringLiteral("fallapart") << false << xc; + QTest::newRow("FallApart-GL") << QStringLiteral("fallapart") << true << oc; + QTest::newRow("FlipSwitch") << QStringLiteral("flipswitch") << false << xc; + QTest::newRow("FlipSwitch-GL") << QStringLiteral("flipswitch") << true << oc; + QTest::newRow("Glide") << QStringLiteral("glide") << false << xc; + QTest::newRow("Glide-GL") << QStringLiteral("glide") << true << oc; + QTest::newRow("HighlightWindow") << QStringLiteral("highlightwindow") << true << xc; + QTest::newRow("Invert") << QStringLiteral("invert") << false << xc; + QTest::newRow("Invert-GL") << QStringLiteral("invert") << true << oc; + QTest::newRow("Kscreen") << QStringLiteral("kscreen") << true << xc; + QTest::newRow("Logout") << QStringLiteral("logout") << true << xc; + QTest::newRow("LookingGlass") << QStringLiteral("lookingglass") << false << xc; + QTest::newRow("LookingGlass-GL") << QStringLiteral("lookingglass") << true << oc; + QTest::newRow("MagicLamp") << QStringLiteral("magiclamp") << false << xc; + QTest::newRow("MagicLamp-GL") << QStringLiteral("magiclamp") << true << oc; + QTest::newRow("Magnifier") << QStringLiteral("magnifier") << true << xc; + QTest::newRow("MinimizeAnimation") << QStringLiteral("minimizeanimation") << true << xc; + QTest::newRow("MouseClick") << QStringLiteral("mouseclick") << true << xc; + QTest::newRow("MouseMark") << QStringLiteral("mousemark") << true << xc; + QTest::newRow("PresentWindows") << QStringLiteral("presentwindows") << true << xc; + QTest::newRow("Resize") << QStringLiteral("resize") << true << xc; + QTest::newRow("ScreenEdge") << QStringLiteral("screenedge") << true << xc; + QTest::newRow("ScreenShot") << QStringLiteral("screenshot") << true << xc; + QTest::newRow("Sheet") << QStringLiteral("sheet") << false << xc; + QTest::newRow("Sheet-GL") << QStringLiteral("sheet") << true << oc; + QTest::newRow("ShowFps") << QStringLiteral("showfps") << true << xc; + QTest::newRow("ShowPaint") << QStringLiteral("showpaint") << true << xc; + QTest::newRow("Slide") << QStringLiteral("slide") << true << xc; + QTest::newRow("SlideBack") << QStringLiteral("slideback") << true << xc; + QTest::newRow("SlidingPopups") << QStringLiteral("slidingpopups") << true << xc; + QTest::newRow("SnapHelper") << QStringLiteral("snaphelper") << true << xc; + QTest::newRow("StartupFeedback") << QStringLiteral("startupfeedback") << false << xc; + QTest::newRow("StartupFeedback-GL") << QStringLiteral("startupfeedback") << true << oc; + QTest::newRow("ThumbnailAside") << QStringLiteral("thumbnailaside") << true << xc; + QTest::newRow("TrackMouse") << QStringLiteral("trackmouse") << true << xc; + QTest::newRow("WindowGeometry") << QStringLiteral("windowgeometry") << true << xc; + QTest::newRow("WobblyWindows") << QStringLiteral("wobblywindows") << false << xc; + QTest::newRow("WobblyWindows-GL") << QStringLiteral("wobblywindows") << true << oc; + QTest::newRow("Zoom") << QStringLiteral("zoom") << true << xc; + QTest::newRow("Non Existing") << QStringLiteral("InvalidName") << false << xc; + QTest::newRow("Fade - Scripted") << QStringLiteral("fade") << false << xc; + QTest::newRow("Fade - Scripted + kwin4_effect") << QStringLiteral("kwin4_effect_fade") << false << xc; +} + +void TestBuiltInEffectLoader::testSupported() +{ + QFETCH(QString, name); + QFETCH(bool, expected); + QFETCH(KWin::CompositingType, type); + + MockEffectsHandler mockHandler(type); + KWin::BuiltInEffectLoader loader; + QCOMPARE(loader.isEffectSupported(name), expected); +} + +void TestBuiltInEffectLoader::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("blur") << QStringLiteral("blur") << false << xc; + // fails for GL as it does proper tests on what's supported and doesn't just check whether it's GL + QTest::newRow("blur-GL") << QStringLiteral("blur") << false << oc; + QTest::newRow("Contrast") << QStringLiteral("contrast") << false << xc; + // fails for GL as it does proper tests on what's supported and doesn't just check whether it's GL + QTest::newRow("Contrast-GL") << QStringLiteral("contrast") << false << oc; + QTest::newRow("CoverSwitch") << QStringLiteral("coverswitch") << false << xc; + // TODO: needs GL mocking +// QTest::newRow("CoverSwitch-GL") << QStringLiteral("coverswitch") << true << oc; + QTest::newRow("Cube") << QStringLiteral("cube") << false << xc; + // TODO: needs GL mocking +// QTest::newRow("Cube-GL") << QStringLiteral("cube") << true << oc; + QTest::newRow("CubeSlide") << QStringLiteral("cubeslide") << false << xc; + QTest::newRow("CubeSlide-GL") << QStringLiteral("cubeslide") << true << oc; + QTest::newRow("Dashboard") << QStringLiteral("dashboard") << true << xc; + QTest::newRow("DesktopGrid") << QStringLiteral("desktopgrid") << true << xc; + QTest::newRow("DimInactive") << QStringLiteral("diminactive") << true << xc; + QTest::newRow("DimScreen") << QStringLiteral("dimScreen") << true << xc; + QTest::newRow("FallApart") << QStringLiteral("fallapart") << false << xc; + QTest::newRow("FallApart-GL") << QStringLiteral("fallapart") << true << oc; + QTest::newRow("FlipSwitch") << QStringLiteral("flipswitch") << false << xc; + QTest::newRow("FlipSwitch-GL") << QStringLiteral("flipswitch") << true << oc; + QTest::newRow("Glide") << QStringLiteral("glide") << false << xc; + QTest::newRow("Glide-GL") << QStringLiteral("glide") << true << oc; + QTest::newRow("HighlightWindow") << QStringLiteral("highlightwindow") << true << xc; + QTest::newRow("Invert") << QStringLiteral("invert") << false << xc; + QTest::newRow("Invert-GL") << QStringLiteral("invert") << true << oc; + QTest::newRow("Kscreen") << QStringLiteral("kscreen") << true << xc; + QTest::newRow("Logout") << QStringLiteral("logout") << true << xc; + QTest::newRow("LookingGlass") << QStringLiteral("lookingglass") << false << xc; + QTest::newRow("LookingGlass-GL") << QStringLiteral("lookingglass") << true << oc; + QTest::newRow("MagicLamp") << QStringLiteral("magiclamp") << false << xc; + QTest::newRow("MagicLamp-GL") << QStringLiteral("magiclamp") << true << oc; + QTest::newRow("Magnifier") << QStringLiteral("magnifier") << true << xc; + QTest::newRow("MinimizeAnimation") << QStringLiteral("minimizeanimation") << true << xc; + QTest::newRow("MouseClick") << QStringLiteral("mouseclick") << true << xc; + QTest::newRow("MouseMark") << QStringLiteral("mousemark") << true << xc; + QTest::newRow("PresentWindows") << QStringLiteral("presentwindows") << true << xc; + QTest::newRow("Resize") << QStringLiteral("resize") << true << xc; + QTest::newRow("ScreenEdge") << QStringLiteral("screenedge") << true << xc; + QTest::newRow("ScreenShot") << QStringLiteral("screenshot") << true << xc; + QTest::newRow("Sheet") << QStringLiteral("sheet") << false << xc; + QTest::newRow("Sheet-GL") << QStringLiteral("sheet") << true << oc; + // TODO: Accesses EffectFrame and crashes +// QTest::newRow("ShowFps") << QStringLiteral("showfps") << true << xc; + QTest::newRow("ShowPaint") << QStringLiteral("showpaint") << true << xc; + QTest::newRow("Slide") << QStringLiteral("slide") << true << xc; + QTest::newRow("SlideBack") << QStringLiteral("slideback") << true << xc; + QTest::newRow("SlidingPopups") << QStringLiteral("slidingpopups") << true << xc; + QTest::newRow("SnapHelper") << QStringLiteral("snaphelper") << true << xc; + QTest::newRow("StartupFeedback") << QStringLiteral("startupfeedback") << false << xc; + QTest::newRow("StartupFeedback-GL") << QStringLiteral("startupfeedback") << true << oc; + QTest::newRow("ThumbnailAside") << QStringLiteral("thumbnailaside") << true << xc; + QTest::newRow("TrackMouse") << QStringLiteral("trackmouse") << true << xc; + // TODO: Accesses EffectFrame and crashes +// QTest::newRow("WindowGeometry") << QStringLiteral("windowgeometry") << true << xc; + QTest::newRow("WobblyWindows") << QStringLiteral("wobblywindows") << false << xc; + QTest::newRow("WobblyWindows-GL") << QStringLiteral("wobblywindows") << true << oc; + QTest::newRow("Zoom") << QStringLiteral("kwin4_effect_zoom") << true << xc; + QTest::newRow("Non Existing") << QStringLiteral("InvalidName") << false << xc; + QTest::newRow("Fade - Scripted") << QStringLiteral("fade") << false << xc; + QTest::newRow("Fade - Scripted + kwin4_effect") << QStringLiteral("kwin4_effect_fade") << false << xc; +} + +void TestBuiltInEffectLoader::testLoadEffect() +{ + QFETCH(QString, name); + QFETCH(bool, expected); + QFETCH(KWin::CompositingType, type); + + MockEffectsHandler mockHandler(type); + KWin::BuiltInEffectLoader 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::BuiltInEffectLoader::effectLoaded, + [&name](KWin::Effect *effect, const QString &effectName) { + QCOMPARE(effectName, name); + 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); + } + 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); + } +} + +void TestBuiltInEffectLoader::testLoadBuiltInEffect_data() +{ + // TODO: this test cannot yet test the checkEnabledByDefault functionality as that requires + // mocking enough of GL to get the blur effect to think it's supported and enabled by default + QTest::addColumn("effect"); + QTest::addColumn("name"); + QTest::addColumn("expected"); + QTest::addColumn("type"); + QTest::addColumn("loadFlags"); + + 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("blur") << KWin::BuiltInEffect::Blur << QStringLiteral("blur") << false << oc << checkDefault; + // enabled by default + QTest::newRow("HighlightWindow") << KWin::BuiltInEffect::HighlightWindow << QStringLiteral("highlightwindow") << true << xc << checkDefault; + // supported but not enabled by default + QTest::newRow("LookingGlass-GL") << KWin::BuiltInEffect::LookingGlass << QStringLiteral("lookingglass") << true << oc << checkDefault; + // not enabled by default + QTest::newRow("MouseClick") << KWin::BuiltInEffect::MouseClick << QStringLiteral("mouseclick") << true << xc << checkDefault; + // Force an Effect which will load + QTest::newRow("MouseClick-Force") << KWin::BuiltInEffect::MouseClick << QStringLiteral("mouseclick") << true << xc << forceFlags; + // Force an Effect which is not supported + QTest::newRow("LookingGlass-Force") << KWin::BuiltInEffect::LookingGlass << QStringLiteral("lookingglass") << false << xc << forceFlags; + // Force the Effect as supported + QTest::newRow("LookingGlass-Force-GL") << KWin::BuiltInEffect::LookingGlass << QStringLiteral("lookingglass") << true << oc << forceFlags; + // Enforce no load of effect which is enabled by default + QTest::newRow("HighlightWindow-DontLoad") << KWin::BuiltInEffect::HighlightWindow << QStringLiteral("highlightwindow") << false << xc << dontLoadFlags; + // Enforce no load of effect which is not enabled by default, but enforced + QTest::newRow("MouseClick-DontLoad") << KWin::BuiltInEffect::MouseClick << QStringLiteral("mouseclick") << false << xc << dontLoadFlags; +} + +void TestBuiltInEffectLoader::testLoadBuiltInEffect() +{ + QFETCH(KWin::BuiltInEffect, effect); + QFETCH(QString, name); + QFETCH(bool, expected); + QFETCH(KWin::CompositingType, type); + QFETCH(KWin::LoadEffectFlags, loadFlags); + + MockEffectsHandler mockHandler(type); + KWin::BuiltInEffectLoader 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::BuiltInEffectLoader::effectLoaded, + [&name](KWin::Effect *effect, const QString &effectName) { + QCOMPARE(effectName, name); + effect->deleteLater(); + } + ); + // try to load the Effect + QCOMPARE(loader.loadEffect(effect, loadFlags), expected); + // loading again should fail + QVERIFY(!loader.loadEffect(effect, 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(effect, 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 TestBuiltInEffectLoader::testLoadAllEffects() +{ + MockEffectsHandler mockHandler(KWin::XRenderCompositing); + KWin::BuiltInEffectLoader loader; + + KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + + // TODO: remove once the KCM doesn't use this prefix + 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("dashboardEnabled"), false); + plugins.writeEntry(kwin4 + QStringLiteral("desktopgridEnabled"), false); + plugins.writeEntry(kwin4 + QStringLiteral("highlightwindowEnabled"), false); + plugins.writeEntry(kwin4 + QStringLiteral("kscreenEnabled"), false); + plugins.writeEntry(kwin4 + QStringLiteral("logoutEnabled"), false); + plugins.writeEntry(kwin4 + QStringLiteral("minimizeanimationEnabled"), false); + plugins.writeEntry(kwin4 + QStringLiteral("presentwindowsEnabled"), false); + plugins.writeEntry(kwin4 + QStringLiteral("screenedgeEnabled"), false); + plugins.writeEntry(kwin4 + QStringLiteral("screenshotEnabled"), false); + plugins.writeEntry(kwin4 + QStringLiteral("slideEnabled"), false); + plugins.writeEntry(kwin4 + QStringLiteral("slidingpopupsEnabled"), false); + plugins.writeEntry(kwin4 + QStringLiteral("startupfeedbackEnabled"), false); + plugins.writeEntry(kwin4 + QStringLiteral("zoomEnabled"), false); + // enable lookingglass as it's not supported + plugins.writeEntry(kwin4 + QStringLiteral("lookingglassEnabled"), true); + 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::BuiltInEffectLoader::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 + QVERIFY(!spy.wait(10)); + + // now let's prepare a config which has one effect explicitly enabled + plugins.writeEntry(kwin4 + QStringLiteral("mouseclickEnabled"), true); + plugins.sync(); + + loader.queryAndLoadAll(); + // should load one effect in first go + QVERIFY(spy.wait(10)); + // 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("mouseclick")); + spy.clear(); + + // let's delete one of the default entries + plugins.deleteEntry(kwin4 + QStringLiteral("kscreenEnabled")); + 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), QStringLiteral("kscreen")); + QCOMPARE(loadedEffects.at(1), QStringLiteral("mouseclick")); +} + +QTEST_MAIN(TestBuiltInEffectLoader) +#include "test_builtin_effectloader.moc" diff --git a/effectloader.cpp b/effectloader.cpp new file mode 100644 index 0000000000..afadd9d42e --- /dev/null +++ b/effectloader.cpp @@ -0,0 +1,185 @@ +/******************************************************************** +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 . +*********************************************************************/ +// own +#include "effectloader.h" +// KWin +#include +#include "effects/effect_builtins.h" +// KDE +#include +// Qt +#include +#include +#include + +namespace KWin +{ + +AbstractEffectLoader::AbstractEffectLoader(QObject *parent) + : QObject(parent) +{ +} + +AbstractEffectLoader::~AbstractEffectLoader() +{ +} + +void AbstractEffectLoader::setConfig(KSharedConfig::Ptr config) +{ + m_config = config; +} + +LoadEffectFlags AbstractEffectLoader::readConfig(const QString &effectName, bool defaultValue) const +{ + Q_ASSERT(m_config); + KConfigGroup plugins(m_config, QStringLiteral("Plugins")); + + const QString key = effectName + QStringLiteral("Enabled"); + + // do we have a key for the effect? + if (plugins.hasKey(key)) { + // we have a key in the config, so read the enabled state + const bool load = plugins.readEntry(key, defaultValue); + return load ? LoadEffectFlags(LoadEffectFlag::Load) : LoadEffectFlags(); + } + // we don't have a key, so we just use the enabled by default value + if (defaultValue) { + return LoadEffectFlag::Load | LoadEffectFlag::CheckDefaultFunction; + } + return LoadEffectFlags(); +} + +BuiltInEffectLoader::BuiltInEffectLoader(QObject *parent) + : AbstractEffectLoader(parent) + , m_queue(new EffectLoadQueue(this)) +{ +} + +BuiltInEffectLoader::~BuiltInEffectLoader() +{ +} + +bool BuiltInEffectLoader::hasEffect(const QString &name) const +{ + return BuiltInEffects::available(internalName(name)); +} + +bool BuiltInEffectLoader::isEffectSupported(const QString &name) const +{ + return BuiltInEffects::supported(internalName(name)); +} + +QStringList BuiltInEffectLoader::listOfKnownEffects() const +{ + const QList availableEffects = BuiltInEffects::availableEffectNames(); + QStringList result; + for (const QByteArray name : availableEffects) { + result << QString::fromUtf8(name); + } + return result; +} + +bool BuiltInEffectLoader::loadEffect(const QString &name) +{ + return loadEffect(name, BuiltInEffects::builtInForName(internalName(name)), LoadEffectFlag::Load); +} + +void BuiltInEffectLoader::queryAndLoadAll() +{ + const QList effects = BuiltInEffects::availableEffects(); + for (BuiltInEffect effect : effects) { + // check whether it is already loaded + if (m_loadedEffects.contains(effect)) { + continue; + } + // as long as the KCM uses kwin4_effect_ we need to add it, TODO remove + const QString key = QStringLiteral("kwin4_effect_") + + QString::fromUtf8(BuiltInEffects::nameForEffect(effect)); + const LoadEffectFlags flags = readConfig(key, BuiltInEffects::enabledByDefault(effect)); + if (flags.testFlag(LoadEffectFlag::Load)) { + m_queue->enqueue(qMakePair(effect, flags)); + } + } +} + +bool BuiltInEffectLoader::loadEffect(BuiltInEffect effect, LoadEffectFlags flags) +{ + return loadEffect(QString::fromUtf8(BuiltInEffects::nameForEffect(effect)), effect, flags); +} + +bool BuiltInEffectLoader::loadEffect(const QString &name, BuiltInEffect effect, LoadEffectFlags flags) +{ + if (effect == BuiltInEffect::Invalid) { + return false; + } + if (!flags.testFlag(LoadEffectFlag::Load)) { + qDebug() << "Loading flags disable effect: " << name; + return false; + } + // check that it is not already loaded + if (m_loadedEffects.contains(effect)) { + return false; + } + + // supported might need a context +#ifndef KWIN_UNIT_TEST + effects->makeOpenGLContextCurrent(); +#endif + if (!BuiltInEffects::supported(effect)) { + qDebug() << "Effect is not supported: " << name; + return false; + } + + if (flags.testFlag(LoadEffectFlag::CheckDefaultFunction)) { + if (!BuiltInEffects::checkEnabledByDefault(effect)) { + qDebug() << "Enabled by default function disables effect: " << name; + return false; + } + } + + // ok, now we can try to create the Effect + Effect *e = BuiltInEffects::create(effect); + if (!e) { + qDebug() << "Failed to create effect: " << name; + return false; + } + // insert in our loaded effects + m_loadedEffects.insert(effect, e); + connect(e, &Effect::destroyed, this, + [this, effect]() { + m_loadedEffects.remove(effect); + } + ); + qDebug() << "Successfully loaded built-in effect: " << name; + emit effectLoaded(e, name); + return true; +} + +QByteArray BuiltInEffectLoader::internalName(const QString& name) const +{ + QString internalName = name.toLower(); + // as long as the KCM uses kwin4_effect_ we need to add it, TODO remove + if (internalName.startsWith(QStringLiteral("kwin4_effect_"))) { + internalName = internalName.mid(13); + } + return internalName.toUtf8(); +} + +} // namespace KWin diff --git a/effectloader.h b/effectloader.h new file mode 100644 index 0000000000..2a4a2db172 --- /dev/null +++ b/effectloader.h @@ -0,0 +1,289 @@ +/******************************************************************** +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 . +*********************************************************************/ +#ifndef KWIN_EFFECT_LOADER_H +#define KWIN_EFFECT_LOADER_H +// KDE +#include +// Qt +#include +#include +#include +#include +#include + +namespace KWin +{ +class Effect; +enum class BuiltInEffect; + +/** + * @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 AbstractEffectLoader : public QObject +{ + Q_OBJECT +public: + virtual ~AbstractEffectLoader(); + + /** + * @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; + +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 effecName 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; +}; + +/** + * @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; +}; + +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(); + } +protected: + void dequeue() override + { + Q_ASSERT(!m_queue.isEmpty()); + 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, "dequeue", Qt::QueuedConnection); + } + Loader *m_effectLoader; + bool m_dequeueScheduled; + QQueue> m_queue; +}; + +/** + * @brief Can load the Built-In-Effects + * + */ +class BuiltInEffectLoader : public AbstractEffectLoader +{ + Q_OBJECT +public: + explicit BuiltInEffectLoader(QObject *parent = nullptr); + ~BuiltInEffectLoader() 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(BuiltInEffect effect, LoadEffectFlags flags); + +private: + bool loadEffect(const QString &name, BuiltInEffect effect, LoadEffectFlags flags); + QByteArray internalName(const QString &name) const; + EffectLoadQueue *m_queue; + QMap m_loadedEffects; +}; + +} +Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::LoadEffectFlags) + +#endif