16#include "virtualdesktops.h"
21#include <KConfigGroup>
22#include <KGlobalAccel>
23#include <KWayland/Client/compositor.h>
24#include <KWayland/Client/connection_thread.h>
25#include <KWayland/Client/registry.h>
26#include <KWayland/Client/slide.h>
27#include <KWayland/Client/surface.h>
33using namespace std::chrono_literals;
35static const QString s_socketName = QStringLiteral(
"wayland_test_effects_scripts-0");
45 void testEffectsHandler();
46 void testEffectsContext();
48 void testAnimations_data();
49 void testAnimations();
50 void testScreenEdge();
51 void testScreenEdgeTouch();
52 void testFullScreenEffect_data();
53 void testFullScreenEffect();
54 void testKeepAlive_data();
57 void testGrabAlreadyGrabbedWindow();
58 void testGrabAlreadyGrabbedWindowForced();
60 void testRedirect_data();
73 bool load(
const QString &name);
89 return findChildren<QAction *>(QString(), Qt::FindDirectChildrenOnly);
99 auto selfContext =
engine()->newQObject(
this);
100 QQmlEngine::setObjectOwnership(
this, QQmlEngine::CppOwnership);
101 const QString path = QFINDTESTDATA(
"./scripts/" + name +
".js");
102 engine()->globalObject().setProperty(
"sendTestResponse", selfContext.property(
"sendTestResponse"));
103 if (!
init(name, path)) {
110 auto children =
effects->children();
111 for (
auto it = children.begin(); it != children.end(); ++it) {
112 if (qstrcmp((*it)->metaObject()->className(),
"KWin::EffectLoader") != 0) {
115 QMetaObject::invokeMethod(*it,
"effectLoaded", Q_ARG(
KWin::Effect *,
this), Q_ARG(QString, name));
122void ScriptedEffectsTest::initTestCase()
125 QSKIP(
"no render node available");
128 qRegisterMetaType<KWin::Window *>();
129 qRegisterMetaType<KWin::Effect *>();
135 auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
136 KConfigGroup plugins(config, QStringLiteral(
"Plugins"));
138 for (QString name : builtinNames) {
139 plugins.writeEntry(name + QStringLiteral(
"Enabled"),
false);
143 kwinApp()->setConfig(config);
145 qputenv(
"KWIN_COMPOSE", QByteArrayLiteral(
"O2"));
146 qputenv(
"KWIN_EFFECTS_FORCE_ANIMATIONS",
"1");
148 QVERIFY(applicationStartedSpy.wait());
150 KWin::VirtualDesktopManager::self()->setCount(2);
153void ScriptedEffectsTest::init()
158void ScriptedEffectsTest::cleanup()
165 KWin::VirtualDesktopManager::self()->setCurrent(1);
168void ScriptedEffectsTest::testEffectsHandler()
173 auto waitFor = [&effectOutputSpy](
const QString &expected) {
174 QVERIFY(effectOutputSpy.count() > 0 || effectOutputSpy.wait());
175 QCOMPARE(effectOutputSpy.first().first(), expected);
176 effectOutputSpy.removeFirst();
178 QVERIFY(effect->load(
"effectsHandler"));
186 QVERIFY(shellSurface);
187 shellSurface->set_title(
"WindowA");
190 QCOMPARE(
workspace()->activeWindow(), c);
192 waitFor(
"windowAdded - WindowA");
193 waitFor(
"stackingOrder - 1 WindowA");
196 c->setMinimized(
true);
197 waitFor(
"windowMinimized - WindowA");
199 c->setMinimized(
false);
200 waitFor(
"windowUnminimized - WindowA");
203 waitFor(
"windowClosed - WindowA");
206 KWin::VirtualDesktopManager::self()->setCurrent(2);
207 waitFor(
"desktopChanged - 1 2");
210void ScriptedEffectsTest::testEffectsContext()
216 QVERIFY(effect->load(
"effectContext"));
217 QCOMPARE(effectOutputSpy[0].first(),
"1280x1024");
218 QCOMPARE(effectOutputSpy[1].first(),
"100");
219 QCOMPARE(effectOutputSpy[2].first(),
"2");
220 QCOMPARE(effectOutputSpy[3].first(),
"0");
223void ScriptedEffectsTest::testShortcuts()
225#if !KWIN_BUILD_GLOBALSHORTCUTS
226 QSKIP(
"Can't test shortcuts without shortcuts");
233 QVERIFY(effect->load(
"shortcutsTest"));
234 QCOMPARE(effect->actions().count(), 1);
235 auto action = effect->actions()[0];
236 QCOMPARE(action->objectName(),
"testShortcut");
237 QCOMPARE(action->text(),
"Test Shortcut");
238 QCOMPARE(KGlobalAccel::self()->shortcut(action).first(), QKeySequence(
"Meta+Shift+Y"));
240 QCOMPARE(effectOutputSpy[0].first(),
"shortcutTriggered");
243void ScriptedEffectsTest::testAnimations_data()
245 QTest::addColumn<QString>(
"file");
246 QTest::addColumn<int>(
"animationCount");
248 QTest::newRow(
"single") <<
"animationTest" << 1;
249 QTest::newRow(
"multi") <<
"animationTestMulti" << 2;
252void ScriptedEffectsTest::testAnimations()
258 QFETCH(QString, file);
259 QFETCH(
int, animationCount);
263 QVERIFY(effect->load(file));
269 QVERIFY(shellSurface);
270 shellSurface->set_title(
"Window 1");
273 QCOMPARE(
workspace()->activeWindow(), c);
276 const auto state = effect->state();
277 QCOMPARE(state.count(), 1);
278 QCOMPARE(state.firstKey(), c->effectWindow());
279 const auto &animationsForWindow = state.first().first;
280 QCOMPARE(animationsForWindow.count(), animationCount);
281 QCOMPARE(animationsForWindow[0].timeLine.duration(), 100ms);
282 QCOMPARE(animationsForWindow[0].to,
FPx2(1.4));
284 QCOMPARE(animationsForWindow[0].timeLine.easingCurve().type(), QEasingCurve::OutCubic);
285 QCOMPARE(animationsForWindow[0].terminationFlags,
288 if (animationCount == 2) {
289 QCOMPARE(animationsForWindow[1].timeLine.duration(), 100ms);
290 QCOMPARE(animationsForWindow[1].to,
FPx2(0.0));
292 QCOMPARE(animationsForWindow[1].terminationFlags,
296 QCOMPARE(effectOutputSpy[0].first(),
"true");
300 c->setMinimized(
true);
302 const auto state = effect->state();
303 QCOMPARE(state.count(), 1);
304 const auto &animationsForWindow = state.first().first;
305 QCOMPARE(animationsForWindow.count(), animationCount);
306 QCOMPARE(animationsForWindow[0].timeLine.duration(), 200ms);
307 QCOMPARE(animationsForWindow[0].to,
FPx2(1.5));
309 QCOMPARE(animationsForWindow[0].terminationFlags,
311 if (animationCount == 2) {
312 QCOMPARE(animationsForWindow[1].timeLine.duration(), 200ms);
313 QCOMPARE(animationsForWindow[1].to,
FPx2(1.5));
315 QCOMPARE(animationsForWindow[1].terminationFlags,
319 c->setMinimized(
false);
321 const auto state = effect->state();
322 QCOMPARE(state.count(), 0);
326void ScriptedEffectsTest::testScreenEdge()
331 QVERIFY(effect->load(
"screenEdgeTest"));
333 QCOMPARE(effectOutputSpy.count(), 1);
336void ScriptedEffectsTest::testScreenEdgeTouch()
341 QVERIFY(effect->load(
"screenEdgeTouchTest"));
342 effect->actions()[0]->trigger();
343 QCOMPARE(effectOutputSpy.count(), 1);
346void ScriptedEffectsTest::testFullScreenEffect_data()
348 QTest::addColumn<QString>(
"file");
350 QTest::newRow(
"single") <<
"fullScreenEffectTest";
351 QTest::newRow(
"multi") <<
"fullScreenEffectTestMulti";
352 QTest::newRow(
"global") <<
"fullScreenEffectTestGlobal";
355void ScriptedEffectsTest::testFullScreenEffect()
357 QFETCH(QString, file);
364 QVERIFY(effectMain->load(file));
369 QVERIFY(effectOther->load(
"screenEdgeTouchTest"));
375 QVERIFY(shellSurface);
376 shellSurface->set_title(
"Window 1");
379 QCOMPARE(
workspace()->activeWindow(), c);
382 QCOMPARE(effectMain->isActiveFullScreenEffect(),
false);
385 KWin::VirtualDesktopManager::self()->setCurrent(2);
389 QCOMPARE(fullScreenEffectActiveSpy.count(), 1);
391 QCOMPARE(effectMain->isActiveFullScreenEffect(),
true);
392 QCOMPARE(isActiveFullScreenEffectSpy.count(), 1);
394 QCOMPARE(effectOther->isActiveFullScreenEffect(),
false);
395 QCOMPARE(isActiveFullScreenEffectSpyOther.count(), 0);
399 KWin::VirtualDesktopManager::self()->setCurrent(1);
404 QTest::qWait(500 + 100);
408 QTest::qWait(500 + 100);
412void ScriptedEffectsTest::testKeepAlive_data()
414 QTest::addColumn<QString>(
"file");
415 QTest::addColumn<bool>(
"keepAlive");
417 QTest::newRow(
"keep") <<
"keepAliveTest" <<
true;
418 QTest::newRow(
"don't keep") <<
"keepAliveTestDontKeep" <<
false;
421void ScriptedEffectsTest::testKeepAlive()
426 QFETCH(QString, file);
427 QFETCH(
bool, keepAlive);
431 QVERIFY(effect->load(file));
437 QVERIFY(shellSurface);
440 QCOMPARE(
workspace()->activeWindow(), c);
443 QCOMPARE(effect->state().count(), 0);
448 QVERIFY(effectOutputSpy.count() == 1 || effectOutputSpy.wait());
451 QCOMPARE(effect->state().count(), 1);
452 QCOMPARE(deletedRemovedSpy.count(), 0);
455 QCOMPARE(effect->state().count(), 1);
456 QCOMPARE(deletedRemovedSpy.count(), 0);
458 QTest::qWait(500 + 100);
459 QCOMPARE(deletedRemovedSpy.count(), 1);
460 QCOMPARE(effect->state().count(), 0);
464 QVERIFY(deletedRemovedSpy.count() == 1 || deletedRemovedSpy.wait(100));
465 QCOMPARE(effect->state().count(), 0);
469void ScriptedEffectsTest::testGrab()
477 QVERIFY(effect->load(QStringLiteral(
"grabTest")));
483 QVERIFY(shellSurface);
486 QCOMPARE(
workspace()->activeWindow(), window);
489 QCOMPARE(effectOutputSpy.count(), 1);
490 QCOMPARE(effectOutputSpy.first().first(), QStringLiteral(
"ok"));
494void ScriptedEffectsTest::testGrabAlreadyGrabbedWindow()
502 QVERIFY(owner->load(QStringLiteral(
"grabAlreadyGrabbedWindowTest_owner")));
507 QVERIFY(grabber->load(QStringLiteral(
"grabAlreadyGrabbedWindowTest_grabber")));
513 QVERIFY(shellSurface);
516 QCOMPARE(
workspace()->activeWindow(), window);
519 QCOMPARE(ownerOutputSpy.count(), 1);
520 QCOMPARE(ownerOutputSpy.first().first(), QStringLiteral(
"ok"));
524 QCOMPARE(grabberOutputSpy.count(), 1);
525 QCOMPARE(grabberOutputSpy.first().first(), QStringLiteral(
"fail"));
528void ScriptedEffectsTest::testGrabAlreadyGrabbedWindowForced()
536 QVERIFY(owner->load(QStringLiteral(
"grabAlreadyGrabbedWindowForcedTest_owner")));
541 QVERIFY(thief->load(QStringLiteral(
"grabAlreadyGrabbedWindowForcedTest_thief")));
547 QVERIFY(shellSurface);
550 QCOMPARE(
workspace()->activeWindow(), window);
553 QCOMPARE(ownerOutputSpy.count(), 1);
554 QCOMPARE(ownerOutputSpy.first().first(), QStringLiteral(
"ok"));
557 QCOMPARE(thiefOutputSpy.count(), 1);
558 QCOMPARE(thiefOutputSpy.first().first(), QStringLiteral(
"ok"));
562void ScriptedEffectsTest::testUngrab()
570 QVERIFY(effect->load(QStringLiteral(
"ungrabTest")));
576 QVERIFY(shellSurface);
579 QCOMPARE(
workspace()->activeWindow(), window);
582 QCOMPARE(effectOutputSpy.count(), 1);
583 QCOMPARE(effectOutputSpy.first().first(), QStringLiteral(
"ok"));
587 effectOutputSpy.clear();
588 window->setMinimized(
true);
590 QCOMPARE(effectOutputSpy.count(), 1);
591 QCOMPARE(effectOutputSpy.first().first(), QStringLiteral(
"ok"));
595void ScriptedEffectsTest::testRedirect_data()
597 QTest::addColumn<QString>(
"file");
598 QTest::addColumn<bool>(
"shouldTerminate");
599 QTest::newRow(
"animate/DontTerminateAtSource") <<
"redirectAnimateDontTerminateTest" <<
false;
600 QTest::newRow(
"animate/TerminateAtSource") <<
"redirectAnimateTerminateTest" <<
true;
601 QTest::newRow(
"set/DontTerminate") <<
"redirectSetDontTerminateTest" <<
false;
602 QTest::newRow(
"set/Terminate") <<
"redirectSetTerminateTest" <<
true;
605void ScriptedEffectsTest::testRedirect()
611 QFETCH(QString, file);
612 QVERIFY(effect->load(file));
618 QVERIFY(shellSurface);
621 QCOMPARE(
workspace()->activeWindow(), window);
623 auto around = [](std::chrono::milliseconds elapsed,
624 std::chrono::milliseconds pivot,
625 std::chrono::milliseconds margin) {
626 return std::abs(elapsed.count() - pivot.count()) < margin.count();
632 const auto state = effect->state();
633 QCOMPARE(state.count(), 1);
634 QCOMPARE(state.firstKey(), window->effectWindow());
635 const QList<AniData> animations = state.first().first;
636 QCOMPARE(animations.count(), 1);
638 QVERIFY(around(animations[0].timeLine.elapsed(), 0ms, 50ms));
647 window->setMinimized(
true);
649 QCOMPARE(effectOutputSpy.count(), 1);
650 QCOMPARE(effectOutputSpy.first().first(), QStringLiteral(
"ok"));
653 const auto state = effect->state();
654 QCOMPARE(state.count(), 1);
655 QCOMPARE(state.firstKey(), window->effectWindow());
656 const QList<AniData> animations = state.first().first;
657 QCOMPARE(animations.count(), 1);
659 QVERIFY(around(animations[0].timeLine.elapsed(), 1000ms - 250ms, 50ms));
664 QTest::qWait(250 + 100);
666 QFETCH(
bool, shouldTerminate);
667 if (shouldTerminate) {
668 const auto state = effect->state();
669 QCOMPARE(state.count(), 0);
671 const auto state = effect->state();
672 QCOMPARE(state.count(), 1);
673 QCOMPARE(state.firstKey(), window->effectWindow());
674 const QList<AniData> animations = state.first().first;
675 QCOMPARE(animations.count(), 1);
677 QCOMPARE(animations[0].timeLine.elapsed(), 1000ms);
678 QCOMPARE(animations[0].timeLine.value(), 0.0);
682void ScriptedEffectsTest::testComplete()
688 QVERIFY(effect->load(QStringLiteral(
"completeTest")));
694 QVERIFY(shellSurface);
697 QCOMPARE(
workspace()->activeWindow(), window);
699 auto around = [](std::chrono::milliseconds elapsed,
700 std::chrono::milliseconds pivot,
701 std::chrono::milliseconds margin) {
702 return std::abs(elapsed.count() - pivot.count()) < margin.count();
707 const auto state = effect->state();
708 QCOMPARE(state.count(), 1);
709 QCOMPARE(state.firstKey(), window->effectWindow());
710 const QList<AniData> animations = state.first().first;
711 QCOMPARE(animations.count(), 1);
712 QVERIFY(around(animations[0].timeLine.elapsed(), 0ms, 50ms));
713 QVERIFY(!animations[0].timeLine.done());
720 const auto state = effect->state();
721 QCOMPARE(state.count(), 1);
722 QCOMPARE(state.firstKey(), window->effectWindow());
723 const QList<AniData> animations = state.first().first;
724 QCOMPARE(animations.count(), 1);
725 QVERIFY(around(animations[0].timeLine.elapsed(), 250ms, 50ms));
726 QVERIFY(!animations[0].timeLine.done());
733 window->setMinimized(
true);
735 QCOMPARE(effectOutputSpy.count(), 1);
736 QCOMPARE(effectOutputSpy.first().first(), QStringLiteral(
"ok"));
739 const auto state = effect->state();
740 QCOMPARE(state.count(), 1);
741 QCOMPARE(state.firstKey(), window->effectWindow());
742 const QList<AniData> animations = state.first().first;
743 QCOMPARE(animations.count(), 1);
744 QCOMPARE(animations[0].timeLine.elapsed(), 1000ms);
745 QVERIFY(animations[0].timeLine.done());
750#include "scripted_effects_test.moc"
QMap< EffectWindow *, QPair< QList< AniData >, QRect > > AniMap
Base class for all KWin effects.
QStringList listOfKnownEffects() const override
All the Effects this loader knows of.
bool hasActiveFullScreenEffect
QStringList loadedEffects
Q_SCRIPTABLE bool isEffectLoaded(const QString &name) const
void hasActiveFullScreenEffectChanged()
Effect * activeFullScreenEffect() const
bool init(const QString &effectName, const QString &pathToScript)
void isActiveFullScreenEffectChanged()
QJSEngine * engine() const
void deletedRemoved(KWin::Window *)
bool load(const QString &name)
ScriptedEffectWithDebugSpy()
void testOutput(const QString &data)
QList< QAction * > actions()
Q_INVOKABLE void sendTestResponse(const QString &out)
#define WAYLANDTEST_MAIN(TestObject)
Window * renderAndWaitForShown(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format=QImage::Format_ARGB32, int timeout=5000)
void destroyWaylandConnection()
void setOutputConfig(const QList< QRect > &geometries)
bool setupWaylandConnection(AdditionalWaylandInterfaces flags=AdditionalWaylandInterfaces())
bool renderNodeAvailable()
std::unique_ptr< KWayland::Client::Surface > createSurface()
XdgToplevel * createXdgToplevelSurface(KWayland::Client::Surface *surface, QObject *parent=nullptr)
WaylandServer * waylandServer()