[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.
This commit is contained in:
Martin Gräßlin 2014-03-22 09:48:07 +01:00
parent 40bb6faa30
commit 0fd9a1eeee
7 changed files with 1281 additions and 0 deletions

View file

@ -290,6 +290,7 @@ set(kwin_KDEINIT_SRCS
lanczosfilter.cpp lanczosfilter.cpp
deleted.cpp deleted.cpp
effects.cpp effects.cpp
effectloader.cpp
compositingprefs.cpp compositingprefs.cpp
paintredirector.cpp paintredirector.cpp
virtualdesktops.cpp virtualdesktops.cpp

View file

@ -1,3 +1,4 @@
add_definitions(-DKWIN_UNIT_TEST)
######################################################## ########################################################
# Test ScreenPaintData # Test ScreenPaintData
######################################################## ########################################################
@ -98,3 +99,22 @@ target_link_libraries( testXcbWindow
) )
add_test(kwin-testXcbWindow testXcbWindow) add_test(kwin-testXcbWindow testXcbWindow)
ecm_mark_as_test(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)

View file

@ -0,0 +1,25 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2014 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "mock_effectshandler.h"
MockEffectsHandler::MockEffectsHandler(KWin::CompositingType type)
: EffectsHandler(type)
{
}

View file

@ -0,0 +1,221 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2014 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#ifndef MOCK_EFFECTS_HANDLER_H
#define MOCK_EFFECTS_HANDLER_H
#include <kwineffects.h>
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<int>();
}
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

View file

@ -0,0 +1,540 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2014 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "../effectloader.h"
#include "../effects/effect_builtins.h"
#include "mock_effectshandler.h"
// KDE
#include <KConfig>
#include <KConfigGroup>
// Qt
#include <QtTest/QtTest>
#include <QStringList>
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<QString>("name");
QTest::addColumn<bool>("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<QString>("name");
QTest::addColumn<bool>("expected");
QTest::addColumn<KWin::CompositingType>("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<QString>("name");
QTest::addColumn<bool>("expected");
QTest::addColumn<KWin::CompositingType>("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<KWin::Effect*>();
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<QVariant> 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<QVariant> 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<KWin::BuiltInEffect>("effect");
QTest::addColumn<QString>("name");
QTest::addColumn<bool>("expected");
QTest::addColumn<KWin::CompositingType>("type");
QTest::addColumn<KWin::LoadEffectFlags>("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<KWin::Effect*>();
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<QVariant> 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<QVariant> 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<KWin::Effect*>();
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<QVariant> 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"

185
effectloader.cpp Normal file
View file

@ -0,0 +1,185 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2014 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
// own
#include "effectloader.h"
// KWin
#include <kwineffects.h>
#include "effects/effect_builtins.h"
// KDE
#include <KConfigGroup>
// Qt
#include <QDebug>
#include <QMap>
#include <QStringList>
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<BuiltInEffectLoader, BuiltInEffect>(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<QByteArray> 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<BuiltInEffect> 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

289
effectloader.h Normal file
View file

@ -0,0 +1,289 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2014 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#ifndef KWIN_EFFECT_LOADER_H
#define KWIN_EFFECT_LOADER_H
// KDE
#include <KSharedConfig>
// Qt
#include <QObject>
#include <QFlags>
#include <QMap>
#include <QPair>
#include <QQueue>
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 "<effectName>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 <typename Loader, typename QueueType>
class EffectLoadQueue : public AbstractEffectLoadQueue
{
public:
explicit EffectLoadQueue(Loader *parent)
: AbstractEffectLoadQueue(parent)
, m_effectLoader(parent)
, m_dequeueScheduled(false)
{
}
void enqueue(const QPair<QueueType, LoadEffectFlags> 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<QPair<QueueType, LoadEffectFlags>> 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<BuiltInEffectLoader, BuiltInEffect> *m_queue;
QMap<BuiltInEffect, Effect*> m_loadedEffects;
};
}
Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::LoadEffectFlags)
#endif