File indexing completed on 2024-11-10 04:55:55
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2018 David Edmundson <davidedmundson@kde.org> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #include "kwin_wayland_test.h" 0011 0012 #include "effect/anidata_p.h" 0013 #include "effect/effecthandler.h" 0014 #include "effect/effectloader.h" 0015 #include "scripting/scriptedeffect.h" 0016 #include "virtualdesktops.h" 0017 #include "wayland_server.h" 0018 #include "window.h" 0019 #include "workspace.h" 0020 0021 #include <KConfigGroup> 0022 #include <KGlobalAccel> 0023 #include <KWayland/Client/compositor.h> 0024 #include <KWayland/Client/connection_thread.h> 0025 #include <KWayland/Client/registry.h> 0026 #include <KWayland/Client/slide.h> 0027 #include <KWayland/Client/surface.h> 0028 0029 #include <QJSValue> 0030 #include <QQmlEngine> 0031 0032 using namespace KWin; 0033 using namespace std::chrono_literals; 0034 0035 static const QString s_socketName = QStringLiteral("wayland_test_effects_scripts-0"); 0036 0037 class ScriptedEffectsTest : public QObject 0038 { 0039 Q_OBJECT 0040 private Q_SLOTS: 0041 void initTestCase(); 0042 void init(); 0043 void cleanup(); 0044 0045 void testEffectsHandler(); 0046 void testEffectsContext(); 0047 void testShortcuts(); 0048 void testAnimations_data(); 0049 void testAnimations(); 0050 void testScreenEdge(); 0051 void testScreenEdgeTouch(); 0052 void testFullScreenEffect_data(); 0053 void testFullScreenEffect(); 0054 void testKeepAlive_data(); 0055 void testKeepAlive(); 0056 void testGrab(); 0057 void testGrabAlreadyGrabbedWindow(); 0058 void testGrabAlreadyGrabbedWindowForced(); 0059 void testUngrab(); 0060 void testRedirect_data(); 0061 void testRedirect(); 0062 void testComplete(); 0063 0064 private: 0065 ScriptedEffect *loadEffect(const QString &name); 0066 }; 0067 0068 class ScriptedEffectWithDebugSpy : public KWin::ScriptedEffect 0069 { 0070 Q_OBJECT 0071 public: 0072 ScriptedEffectWithDebugSpy(); 0073 bool load(const QString &name); 0074 using AnimationEffect::AniMap; 0075 using AnimationEffect::state; 0076 Q_INVOKABLE void sendTestResponse(const QString &out); // proxies triggers out from the tests 0077 QList<QAction *> actions(); // returns any QActions owned by the ScriptEngine 0078 Q_SIGNALS: 0079 void testOutput(const QString &data); 0080 }; 0081 0082 void ScriptedEffectWithDebugSpy::sendTestResponse(const QString &out) 0083 { 0084 Q_EMIT testOutput(out); 0085 } 0086 0087 QList<QAction *> ScriptedEffectWithDebugSpy::actions() 0088 { 0089 return findChildren<QAction *>(QString(), Qt::FindDirectChildrenOnly); 0090 } 0091 0092 ScriptedEffectWithDebugSpy::ScriptedEffectWithDebugSpy() 0093 : ScriptedEffect() 0094 { 0095 } 0096 0097 bool ScriptedEffectWithDebugSpy::load(const QString &name) 0098 { 0099 auto selfContext = engine()->newQObject(this); 0100 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); 0101 const QString path = QFINDTESTDATA("./scripts/" + name + ".js"); 0102 engine()->globalObject().setProperty("sendTestResponse", selfContext.property("sendTestResponse")); 0103 if (!init(name, path)) { 0104 return false; 0105 } 0106 0107 // inject our newly created effect to be registered with the EffectsHandler::loaded_effects 0108 // this is private API so some horrible code is used to find the internal effectloader 0109 // and register ourselves 0110 auto children = effects->children(); 0111 for (auto it = children.begin(); it != children.end(); ++it) { 0112 if (qstrcmp((*it)->metaObject()->className(), "KWin::EffectLoader") != 0) { 0113 continue; 0114 } 0115 QMetaObject::invokeMethod(*it, "effectLoaded", Q_ARG(KWin::Effect *, this), Q_ARG(QString, name)); 0116 break; 0117 } 0118 0119 return effects->isEffectLoaded(name); 0120 } 0121 0122 void ScriptedEffectsTest::initTestCase() 0123 { 0124 if (!Test::renderNodeAvailable()) { 0125 QSKIP("no render node available"); 0126 return; 0127 } 0128 qRegisterMetaType<KWin::Window *>(); 0129 qRegisterMetaType<KWin::Effect *>(); 0130 QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); 0131 QVERIFY(waylandServer()->init(s_socketName)); 0132 Test::setOutputConfig({QRect(0, 0, 1280, 1024)}); 0133 0134 // disable all effects - we don't want to have it interact with the rendering 0135 auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); 0136 KConfigGroup plugins(config, QStringLiteral("Plugins")); 0137 const auto builtinNames = EffectLoader().listOfKnownEffects(); 0138 for (QString name : builtinNames) { 0139 plugins.writeEntry(name + QStringLiteral("Enabled"), false); 0140 } 0141 0142 config->sync(); 0143 kwinApp()->setConfig(config); 0144 0145 qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); 0146 qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", "1"); 0147 kwinApp()->start(); 0148 QVERIFY(applicationStartedSpy.wait()); 0149 0150 KWin::VirtualDesktopManager::self()->setCount(2); 0151 } 0152 0153 void ScriptedEffectsTest::init() 0154 { 0155 QVERIFY(Test::setupWaylandConnection()); 0156 } 0157 0158 void ScriptedEffectsTest::cleanup() 0159 { 0160 Test::destroyWaylandConnection(); 0161 0162 effects->unloadAllEffects(); 0163 QVERIFY(effects->loadedEffects().isEmpty()); 0164 0165 KWin::VirtualDesktopManager::self()->setCurrent(1); 0166 } 0167 0168 void ScriptedEffectsTest::testEffectsHandler() 0169 { 0170 // this triggers and tests some of the signals in EffectHandler, which is exposed to JS as context property "effects" 0171 auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean 0172 QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); 0173 auto waitFor = [&effectOutputSpy](const QString &expected) { 0174 QVERIFY(effectOutputSpy.count() > 0 || effectOutputSpy.wait()); 0175 QCOMPARE(effectOutputSpy.first().first(), expected); 0176 effectOutputSpy.removeFirst(); 0177 }; 0178 QVERIFY(effect->load("effectsHandler")); 0179 0180 // trigger windowAdded signal 0181 0182 // create a window 0183 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface(); 0184 QVERIFY(surface); 0185 auto *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); 0186 QVERIFY(shellSurface); 0187 shellSurface->set_title("WindowA"); 0188 auto *c = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0189 QVERIFY(c); 0190 QCOMPARE(workspace()->activeWindow(), c); 0191 0192 waitFor("windowAdded - WindowA"); 0193 waitFor("stackingOrder - 1 WindowA"); 0194 0195 // windowMinimsed 0196 c->setMinimized(true); 0197 waitFor("windowMinimized - WindowA"); 0198 0199 c->setMinimized(false); 0200 waitFor("windowUnminimized - WindowA"); 0201 0202 surface.reset(); 0203 waitFor("windowClosed - WindowA"); 0204 0205 // desktop management 0206 KWin::VirtualDesktopManager::self()->setCurrent(2); 0207 waitFor("desktopChanged - 1 2"); 0208 } 0209 0210 void ScriptedEffectsTest::testEffectsContext() 0211 { 0212 // this tests misc non-objects exposed to the script engine: animationTime, displaySize, use of external enums 0213 0214 auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean 0215 QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); 0216 QVERIFY(effect->load("effectContext")); 0217 QCOMPARE(effectOutputSpy[0].first(), "1280x1024"); 0218 QCOMPARE(effectOutputSpy[1].first(), "100"); 0219 QCOMPARE(effectOutputSpy[2].first(), "2"); 0220 QCOMPARE(effectOutputSpy[3].first(), "0"); 0221 } 0222 0223 void ScriptedEffectsTest::testShortcuts() 0224 { 0225 #if !KWIN_BUILD_GLOBALSHORTCUTS 0226 QSKIP("Can't test shortcuts without shortcuts"); 0227 return; 0228 #endif 0229 0230 // this tests method registerShortcut 0231 auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean 0232 QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); 0233 QVERIFY(effect->load("shortcutsTest")); 0234 QCOMPARE(effect->actions().count(), 1); 0235 auto action = effect->actions()[0]; 0236 QCOMPARE(action->objectName(), "testShortcut"); 0237 QCOMPARE(action->text(), "Test Shortcut"); 0238 QCOMPARE(KGlobalAccel::self()->shortcut(action).first(), QKeySequence("Meta+Shift+Y")); 0239 action->trigger(); 0240 QCOMPARE(effectOutputSpy[0].first(), "shortcutTriggered"); 0241 } 0242 0243 void ScriptedEffectsTest::testAnimations_data() 0244 { 0245 QTest::addColumn<QString>("file"); 0246 QTest::addColumn<int>("animationCount"); 0247 0248 QTest::newRow("single") << "animationTest" << 1; 0249 QTest::newRow("multi") << "animationTestMulti" << 2; 0250 } 0251 0252 void ScriptedEffectsTest::testAnimations() 0253 { 0254 // this tests animate/set/cancel 0255 // methods take either an int or an array, as forced in the data above 0256 // also splits animate vs effects.animate(..) 0257 0258 QFETCH(QString, file); 0259 QFETCH(int, animationCount); 0260 0261 auto *effect = new ScriptedEffectWithDebugSpy; 0262 QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); 0263 QVERIFY(effect->load(file)); 0264 0265 // animated after window added connect 0266 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface(); 0267 QVERIFY(surface); 0268 auto *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); 0269 QVERIFY(shellSurface); 0270 shellSurface->set_title("Window 1"); 0271 auto *c = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0272 QVERIFY(c); 0273 QCOMPARE(workspace()->activeWindow(), c); 0274 0275 { 0276 const auto state = effect->state(); 0277 QCOMPARE(state.count(), 1); 0278 QCOMPARE(state.firstKey(), c->effectWindow()); 0279 const auto &animationsForWindow = state.first().first; 0280 QCOMPARE(animationsForWindow.count(), animationCount); 0281 QCOMPARE(animationsForWindow[0].timeLine.duration(), 100ms); 0282 QCOMPARE(animationsForWindow[0].to, FPx2(1.4)); 0283 QCOMPARE(animationsForWindow[0].attribute, AnimationEffect::Scale); 0284 QCOMPARE(animationsForWindow[0].timeLine.easingCurve().type(), QEasingCurve::OutCubic); 0285 QCOMPARE(animationsForWindow[0].terminationFlags, 0286 AnimationEffect::TerminateAtSource | AnimationEffect::TerminateAtTarget); 0287 0288 if (animationCount == 2) { 0289 QCOMPARE(animationsForWindow[1].timeLine.duration(), 100ms); 0290 QCOMPARE(animationsForWindow[1].to, FPx2(0.0)); 0291 QCOMPARE(animationsForWindow[1].attribute, AnimationEffect::Opacity); 0292 QCOMPARE(animationsForWindow[1].terminationFlags, 0293 AnimationEffect::TerminateAtSource | AnimationEffect::TerminateAtTarget); 0294 } 0295 } 0296 QCOMPARE(effectOutputSpy[0].first(), "true"); 0297 0298 // window state changes, scale should be retargetted 0299 0300 c->setMinimized(true); 0301 { 0302 const auto state = effect->state(); 0303 QCOMPARE(state.count(), 1); 0304 const auto &animationsForWindow = state.first().first; 0305 QCOMPARE(animationsForWindow.count(), animationCount); 0306 QCOMPARE(animationsForWindow[0].timeLine.duration(), 200ms); 0307 QCOMPARE(animationsForWindow[0].to, FPx2(1.5)); 0308 QCOMPARE(animationsForWindow[0].attribute, AnimationEffect::Scale); 0309 QCOMPARE(animationsForWindow[0].terminationFlags, 0310 AnimationEffect::TerminateAtSource | AnimationEffect::TerminateAtTarget); 0311 if (animationCount == 2) { 0312 QCOMPARE(animationsForWindow[1].timeLine.duration(), 200ms); 0313 QCOMPARE(animationsForWindow[1].to, FPx2(1.5)); 0314 QCOMPARE(animationsForWindow[1].attribute, AnimationEffect::Opacity); 0315 QCOMPARE(animationsForWindow[1].terminationFlags, 0316 AnimationEffect::TerminateAtSource | AnimationEffect::TerminateAtTarget); 0317 } 0318 } 0319 c->setMinimized(false); 0320 { 0321 const auto state = effect->state(); 0322 QCOMPARE(state.count(), 0); 0323 } 0324 } 0325 0326 void ScriptedEffectsTest::testScreenEdge() 0327 { 0328 // this test checks registerScreenEdge functions 0329 auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean 0330 QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); 0331 QVERIFY(effect->load("screenEdgeTest")); 0332 effect->borderActivated(KWin::ElectricTopRight); 0333 QCOMPARE(effectOutputSpy.count(), 1); 0334 } 0335 0336 void ScriptedEffectsTest::testScreenEdgeTouch() 0337 { 0338 // this test checks registerTouchScreenEdge functions 0339 auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean 0340 QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); 0341 QVERIFY(effect->load("screenEdgeTouchTest")); 0342 effect->actions()[0]->trigger(); 0343 QCOMPARE(effectOutputSpy.count(), 1); 0344 } 0345 0346 void ScriptedEffectsTest::testFullScreenEffect_data() 0347 { 0348 QTest::addColumn<QString>("file"); 0349 0350 QTest::newRow("single") << "fullScreenEffectTest"; 0351 QTest::newRow("multi") << "fullScreenEffectTestMulti"; 0352 QTest::newRow("global") << "fullScreenEffectTestGlobal"; 0353 } 0354 0355 void ScriptedEffectsTest::testFullScreenEffect() 0356 { 0357 QFETCH(QString, file); 0358 0359 auto *effectMain = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean 0360 QSignalSpy effectOutputSpy(effectMain, &ScriptedEffectWithDebugSpy::testOutput); 0361 QSignalSpy fullScreenEffectActiveSpy(effects, &EffectsHandler::hasActiveFullScreenEffectChanged); 0362 QSignalSpy isActiveFullScreenEffectSpy(effectMain, &ScriptedEffect::isActiveFullScreenEffectChanged); 0363 0364 QVERIFY(effectMain->load(file)); 0365 0366 // load any random effect from another test to confirm fullscreen effect state is correctly 0367 // shown as being someone else 0368 auto effectOther = new ScriptedEffectWithDebugSpy(); 0369 QVERIFY(effectOther->load("screenEdgeTouchTest")); 0370 QSignalSpy isActiveFullScreenEffectSpyOther(effectOther, &ScriptedEffect::isActiveFullScreenEffectChanged); 0371 0372 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface(); 0373 QVERIFY(surface); 0374 auto *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); 0375 QVERIFY(shellSurface); 0376 shellSurface->set_title("Window 1"); 0377 auto *c = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0378 QVERIFY(c); 0379 QCOMPARE(workspace()->activeWindow(), c); 0380 0381 QCOMPARE(effects->hasActiveFullScreenEffect(), false); 0382 QCOMPARE(effectMain->isActiveFullScreenEffect(), false); 0383 0384 // trigger animation 0385 KWin::VirtualDesktopManager::self()->setCurrent(2); 0386 0387 QCOMPARE(effects->activeFullScreenEffect(), effectMain); 0388 QCOMPARE(effects->hasActiveFullScreenEffect(), true); 0389 QCOMPARE(fullScreenEffectActiveSpy.count(), 1); 0390 0391 QCOMPARE(effectMain->isActiveFullScreenEffect(), true); 0392 QCOMPARE(isActiveFullScreenEffectSpy.count(), 1); 0393 0394 QCOMPARE(effectOther->isActiveFullScreenEffect(), false); 0395 QCOMPARE(isActiveFullScreenEffectSpyOther.count(), 0); 0396 0397 // after 500ms trigger another full screen animation 0398 QTest::qWait(500); 0399 KWin::VirtualDesktopManager::self()->setCurrent(1); 0400 QCOMPARE(effects->activeFullScreenEffect(), effectMain); 0401 0402 // after 1000ms (+a safety margin for time based tests) we should still be the active full screen effect 0403 // despite first animation expiring 0404 QTest::qWait(500 + 100); 0405 QCOMPARE(effects->activeFullScreenEffect(), effectMain); 0406 0407 // after 1500ms (+a safetey margin) we should have no full screen effect 0408 QTest::qWait(500 + 100); 0409 QCOMPARE(effects->activeFullScreenEffect(), nullptr); 0410 } 0411 0412 void ScriptedEffectsTest::testKeepAlive_data() 0413 { 0414 QTest::addColumn<QString>("file"); 0415 QTest::addColumn<bool>("keepAlive"); 0416 0417 QTest::newRow("keep") << "keepAliveTest" << true; 0418 QTest::newRow("don't keep") << "keepAliveTestDontKeep" << false; 0419 } 0420 0421 void ScriptedEffectsTest::testKeepAlive() 0422 { 0423 // this test checks whether closed windows are kept alive 0424 // when keepAlive property is set to true(false) 0425 0426 QFETCH(QString, file); 0427 QFETCH(bool, keepAlive); 0428 0429 auto *effect = new ScriptedEffectWithDebugSpy; 0430 QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); 0431 QVERIFY(effect->load(file)); 0432 0433 // create a window 0434 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface(); 0435 QVERIFY(surface); 0436 auto *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); 0437 QVERIFY(shellSurface); 0438 auto *c = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0439 QVERIFY(c); 0440 QCOMPARE(workspace()->activeWindow(), c); 0441 0442 // no active animations at the beginning 0443 QCOMPARE(effect->state().count(), 0); 0444 0445 // trigger windowClosed signal 0446 QSignalSpy deletedRemovedSpy(workspace(), &Workspace::deletedRemoved); 0447 surface.reset(); 0448 QVERIFY(effectOutputSpy.count() == 1 || effectOutputSpy.wait()); 0449 0450 if (keepAlive) { 0451 QCOMPARE(effect->state().count(), 1); 0452 QCOMPARE(deletedRemovedSpy.count(), 0); 0453 0454 QTest::qWait(500); 0455 QCOMPARE(effect->state().count(), 1); 0456 QCOMPARE(deletedRemovedSpy.count(), 0); 0457 0458 QTest::qWait(500 + 100); // 100ms is extra safety margin 0459 QCOMPARE(deletedRemovedSpy.count(), 1); 0460 QCOMPARE(effect->state().count(), 0); 0461 } else { 0462 // the test effect doesn't keep the window alive, so it should be 0463 // removed immediately 0464 QVERIFY(deletedRemovedSpy.count() == 1 || deletedRemovedSpy.wait(100)); // 100ms is less than duration of the animation 0465 QCOMPARE(effect->state().count(), 0); 0466 } 0467 } 0468 0469 void ScriptedEffectsTest::testGrab() 0470 { 0471 // this test verifies that scripted effects can grab windows that are 0472 // not already grabbed 0473 0474 // load the test effect 0475 auto effect = new ScriptedEffectWithDebugSpy; 0476 QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); 0477 QVERIFY(effect->load(QStringLiteral("grabTest"))); 0478 0479 // create test window 0480 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface(); 0481 QVERIFY(surface); 0482 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); 0483 QVERIFY(shellSurface); 0484 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0485 QVERIFY(window); 0486 QCOMPARE(workspace()->activeWindow(), window); 0487 0488 // the test effect should grab the test window successfully 0489 QCOMPARE(effectOutputSpy.count(), 1); 0490 QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); 0491 QCOMPARE(window->effectWindow()->data(WindowAddedGrabRole).value<void *>(), effect); 0492 } 0493 0494 void ScriptedEffectsTest::testGrabAlreadyGrabbedWindow() 0495 { 0496 // this test verifies that scripted effects cannot grab already grabbed 0497 // windows (unless force is set to true of course) 0498 0499 // load effect that will hold the window grab 0500 auto owner = new ScriptedEffectWithDebugSpy; 0501 QSignalSpy ownerOutputSpy(owner, &ScriptedEffectWithDebugSpy::testOutput); 0502 QVERIFY(owner->load(QStringLiteral("grabAlreadyGrabbedWindowTest_owner"))); 0503 0504 // load effect that will try to grab already grabbed window 0505 auto grabber = new ScriptedEffectWithDebugSpy; 0506 QSignalSpy grabberOutputSpy(grabber, &ScriptedEffectWithDebugSpy::testOutput); 0507 QVERIFY(grabber->load(QStringLiteral("grabAlreadyGrabbedWindowTest_grabber"))); 0508 0509 // create test window 0510 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface(); 0511 QVERIFY(surface); 0512 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); 0513 QVERIFY(shellSurface); 0514 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0515 QVERIFY(window); 0516 QCOMPARE(workspace()->activeWindow(), window); 0517 0518 // effect that initially held the grab should still hold the grab 0519 QCOMPARE(ownerOutputSpy.count(), 1); 0520 QCOMPARE(ownerOutputSpy.first().first(), QStringLiteral("ok")); 0521 QCOMPARE(window->effectWindow()->data(WindowAddedGrabRole).value<void *>(), owner); 0522 0523 // effect that tried to grab already grabbed window should fail miserably 0524 QCOMPARE(grabberOutputSpy.count(), 1); 0525 QCOMPARE(grabberOutputSpy.first().first(), QStringLiteral("fail")); 0526 } 0527 0528 void ScriptedEffectsTest::testGrabAlreadyGrabbedWindowForced() 0529 { 0530 // this test verifies that scripted effects can steal window grabs when 0531 // they forcefully try to grab windows 0532 0533 // load effect that initially will be holding the window grab 0534 auto owner = new ScriptedEffectWithDebugSpy; 0535 QSignalSpy ownerOutputSpy(owner, &ScriptedEffectWithDebugSpy::testOutput); 0536 QVERIFY(owner->load(QStringLiteral("grabAlreadyGrabbedWindowForcedTest_owner"))); 0537 0538 // load effect that will try to steal the window grab 0539 auto thief = new ScriptedEffectWithDebugSpy; 0540 QSignalSpy thiefOutputSpy(thief, &ScriptedEffectWithDebugSpy::testOutput); 0541 QVERIFY(thief->load(QStringLiteral("grabAlreadyGrabbedWindowForcedTest_thief"))); 0542 0543 // create test window 0544 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface(); 0545 QVERIFY(surface); 0546 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); 0547 QVERIFY(shellSurface); 0548 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0549 QVERIFY(window); 0550 QCOMPARE(workspace()->activeWindow(), window); 0551 0552 // verify that the owner in fact held the grab 0553 QCOMPARE(ownerOutputSpy.count(), 1); 0554 QCOMPARE(ownerOutputSpy.first().first(), QStringLiteral("ok")); 0555 0556 // effect that grabbed the test window forcefully should now hold the grab 0557 QCOMPARE(thiefOutputSpy.count(), 1); 0558 QCOMPARE(thiefOutputSpy.first().first(), QStringLiteral("ok")); 0559 QCOMPARE(window->effectWindow()->data(WindowAddedGrabRole).value<void *>(), thief); 0560 } 0561 0562 void ScriptedEffectsTest::testUngrab() 0563 { 0564 // this test verifies that scripted effects can ungrab windows that they 0565 // are previously grabbed 0566 0567 // load the test effect 0568 auto effect = new ScriptedEffectWithDebugSpy; 0569 QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); 0570 QVERIFY(effect->load(QStringLiteral("ungrabTest"))); 0571 0572 // create test window 0573 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface(); 0574 QVERIFY(surface); 0575 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); 0576 QVERIFY(shellSurface); 0577 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0578 QVERIFY(window); 0579 QCOMPARE(workspace()->activeWindow(), window); 0580 0581 // the test effect should grab the test window successfully 0582 QCOMPARE(effectOutputSpy.count(), 1); 0583 QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); 0584 QCOMPARE(window->effectWindow()->data(WindowAddedGrabRole).value<void *>(), effect); 0585 0586 // when the test effect sees that a window was minimized, it will try to ungrab it 0587 effectOutputSpy.clear(); 0588 window->setMinimized(true); 0589 0590 QCOMPARE(effectOutputSpy.count(), 1); 0591 QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); 0592 QCOMPARE(window->effectWindow()->data(WindowAddedGrabRole).value<void *>(), nullptr); 0593 } 0594 0595 void ScriptedEffectsTest::testRedirect_data() 0596 { 0597 QTest::addColumn<QString>("file"); 0598 QTest::addColumn<bool>("shouldTerminate"); 0599 QTest::newRow("animate/DontTerminateAtSource") << "redirectAnimateDontTerminateTest" << false; 0600 QTest::newRow("animate/TerminateAtSource") << "redirectAnimateTerminateTest" << true; 0601 QTest::newRow("set/DontTerminate") << "redirectSetDontTerminateTest" << false; 0602 QTest::newRow("set/Terminate") << "redirectSetTerminateTest" << true; 0603 } 0604 0605 void ScriptedEffectsTest::testRedirect() 0606 { 0607 // this test verifies that redirect() works 0608 0609 // load the test effect 0610 auto effect = new ScriptedEffectWithDebugSpy; 0611 QFETCH(QString, file); 0612 QVERIFY(effect->load(file)); 0613 0614 // create test window 0615 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface(); 0616 QVERIFY(surface); 0617 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); 0618 QVERIFY(shellSurface); 0619 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0620 QVERIFY(window); 0621 QCOMPARE(workspace()->activeWindow(), window); 0622 0623 auto around = [](std::chrono::milliseconds elapsed, 0624 std::chrono::milliseconds pivot, 0625 std::chrono::milliseconds margin) { 0626 return std::abs(elapsed.count() - pivot.count()) < margin.count(); 0627 }; 0628 0629 // initially, the test animation is at the source position 0630 0631 { 0632 const auto state = effect->state(); 0633 QCOMPARE(state.count(), 1); 0634 QCOMPARE(state.firstKey(), window->effectWindow()); 0635 const QList<AniData> animations = state.first().first; 0636 QCOMPARE(animations.count(), 1); 0637 QCOMPARE(animations[0].timeLine.direction(), TimeLine::Forward); 0638 QVERIFY(around(animations[0].timeLine.elapsed(), 0ms, 50ms)); 0639 } 0640 0641 // minimize the test window after 250ms, when the test effect sees that 0642 // a window was minimized, it will try to reverse animation for it 0643 QTest::qWait(250); 0644 0645 QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); 0646 0647 window->setMinimized(true); 0648 0649 QCOMPARE(effectOutputSpy.count(), 1); 0650 QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); 0651 0652 { 0653 const auto state = effect->state(); 0654 QCOMPARE(state.count(), 1); 0655 QCOMPARE(state.firstKey(), window->effectWindow()); 0656 const QList<AniData> animations = state.first().first; 0657 QCOMPARE(animations.count(), 1); 0658 QCOMPARE(animations[0].timeLine.direction(), TimeLine::Backward); 0659 QVERIFY(around(animations[0].timeLine.elapsed(), 1000ms - 250ms, 50ms)); 0660 } 0661 0662 // wait for the animation to reach the start position, 100ms is an extra 0663 // safety margin 0664 QTest::qWait(250 + 100); 0665 0666 QFETCH(bool, shouldTerminate); 0667 if (shouldTerminate) { 0668 const auto state = effect->state(); 0669 QCOMPARE(state.count(), 0); 0670 } else { 0671 const auto state = effect->state(); 0672 QCOMPARE(state.count(), 1); 0673 QCOMPARE(state.firstKey(), window->effectWindow()); 0674 const QList<AniData> animations = state.first().first; 0675 QCOMPARE(animations.count(), 1); 0676 QCOMPARE(animations[0].timeLine.direction(), TimeLine::Backward); 0677 QCOMPARE(animations[0].timeLine.elapsed(), 1000ms); 0678 QCOMPARE(animations[0].timeLine.value(), 0.0); 0679 } 0680 } 0681 0682 void ScriptedEffectsTest::testComplete() 0683 { 0684 // this test verifies that complete works 0685 0686 // load the test effect 0687 auto effect = new ScriptedEffectWithDebugSpy; 0688 QVERIFY(effect->load(QStringLiteral("completeTest"))); 0689 0690 // create test window 0691 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface(); 0692 QVERIFY(surface); 0693 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); 0694 QVERIFY(shellSurface); 0695 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0696 QVERIFY(window); 0697 QCOMPARE(workspace()->activeWindow(), window); 0698 0699 auto around = [](std::chrono::milliseconds elapsed, 0700 std::chrono::milliseconds pivot, 0701 std::chrono::milliseconds margin) { 0702 return std::abs(elapsed.count() - pivot.count()) < margin.count(); 0703 }; 0704 0705 // initially, the test animation should be at the start position 0706 { 0707 const auto state = effect->state(); 0708 QCOMPARE(state.count(), 1); 0709 QCOMPARE(state.firstKey(), window->effectWindow()); 0710 const QList<AniData> animations = state.first().first; 0711 QCOMPARE(animations.count(), 1); 0712 QVERIFY(around(animations[0].timeLine.elapsed(), 0ms, 50ms)); 0713 QVERIFY(!animations[0].timeLine.done()); 0714 } 0715 0716 // wait for 250ms 0717 QTest::qWait(250); 0718 0719 { 0720 const auto state = effect->state(); 0721 QCOMPARE(state.count(), 1); 0722 QCOMPARE(state.firstKey(), window->effectWindow()); 0723 const QList<AniData> animations = state.first().first; 0724 QCOMPARE(animations.count(), 1); 0725 QVERIFY(around(animations[0].timeLine.elapsed(), 250ms, 50ms)); 0726 QVERIFY(!animations[0].timeLine.done()); 0727 } 0728 0729 // minimize the test window, when the test effect sees that a window was 0730 // minimized, it will try to complete animation for it 0731 QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); 0732 0733 window->setMinimized(true); 0734 0735 QCOMPARE(effectOutputSpy.count(), 1); 0736 QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); 0737 0738 { 0739 const auto state = effect->state(); 0740 QCOMPARE(state.count(), 1); 0741 QCOMPARE(state.firstKey(), window->effectWindow()); 0742 const QList<AniData> animations = state.first().first; 0743 QCOMPARE(animations.count(), 1); 0744 QCOMPARE(animations[0].timeLine.elapsed(), 1000ms); 0745 QVERIFY(animations[0].timeLine.done()); 0746 } 0747 } 0748 0749 WAYLANDTEST_MAIN(ScriptedEffectsTest) 0750 #include "scripted_effects_test.moc"