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