diff --git a/autotests/integration/effects/scripted_effects_test.cpp b/autotests/integration/effects/scripted_effects_test.cpp --- a/autotests/integration/effects/scripted_effects_test.cpp +++ b/autotests/integration/effects/scripted_effects_test.cpp @@ -70,6 +70,10 @@ void testFullScreenEffect(); void testKeepAlive_data(); void testKeepAlive(); + void testGrab(); + void testGrabAlreadyGrabbedWindow(); + void testGrabAlreadyGrabbedWindowForced(); + void testUngrab(); private: ScriptedEffect *loadEffect(const QString &name); @@ -482,5 +486,141 @@ } } +void ScriptedEffectsTest::testGrab() +{ + // this test verifies that scripted effects can grab windows that are + // not already grabbed + + // load the test effect + auto effect = new ScriptedEffectWithDebugSpy; + QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(effectOutputSpy.isValid()); + QVERIFY(effect->load(QStringLiteral("grabTest"))); + + // create test client + using namespace KWayland::Client; + Surface *surface = Test::createSurface(Test::waylandCompositor()); + QVERIFY(surface); + XdgShellSurface *shellSurface = Test::createXdgShellStableSurface(surface, surface); + QVERIFY(shellSurface); + ShellClient *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(workspace()->activeClient(), c); + + // the test effect should grab the test client successfully + QCOMPARE(effectOutputSpy.count(), 1); + QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); + QCOMPARE(c->effectWindow()->data(WindowAddedGrabRole).value(), effect); +} + +void ScriptedEffectsTest::testGrabAlreadyGrabbedWindow() +{ + // this test verifies that scripted effects cannot grab already grabbed + // windows (unless force is set to true of course) + + // load effect that will hold the window grab + auto owner = new ScriptedEffectWithDebugSpy; + QSignalSpy ownerOutputSpy(owner, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(ownerOutputSpy.isValid()); + QVERIFY(owner->load(QStringLiteral("grabAlreadyGrabbedWindowTest_owner"))); + + // load effect that will try to grab already grabbed window + auto grabber = new ScriptedEffectWithDebugSpy; + QSignalSpy grabberOutputSpy(grabber, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(grabberOutputSpy.isValid()); + QVERIFY(grabber->load(QStringLiteral("grabAlreadyGrabbedWindowTest_grabber"))); + + // create test client + using namespace KWayland::Client; + Surface *surface = Test::createSurface(Test::waylandCompositor()); + QVERIFY(surface); + XdgShellSurface *shellSurface = Test::createXdgShellStableSurface(surface, surface); + QVERIFY(shellSurface); + ShellClient *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(workspace()->activeClient(), c); + + // effect that initially held the grab should still hold the grab + QCOMPARE(ownerOutputSpy.count(), 1); + QCOMPARE(ownerOutputSpy.first().first(), QStringLiteral("ok")); + QCOMPARE(c->effectWindow()->data(WindowAddedGrabRole).value(), owner); + + // effect that tried to grab already grabbed window should fail miserably + QCOMPARE(grabberOutputSpy.count(), 1); + QCOMPARE(grabberOutputSpy.first().first(), QStringLiteral("fail")); +} + +void ScriptedEffectsTest::testGrabAlreadyGrabbedWindowForced() +{ + // this test verifies that scripted effects can steal window grabs when + // they forcefully try to grab windows + + // load effect that initially will be holding the window grab + auto owner = new ScriptedEffectWithDebugSpy; + QSignalSpy ownerOutputSpy(owner, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(ownerOutputSpy.isValid()); + QVERIFY(owner->load(QStringLiteral("grabAlreadyGrabbedWindowForcedTest_owner"))); + + // load effect that will try to steal the window grab + auto thief = new ScriptedEffectWithDebugSpy; + QSignalSpy thiefOutputSpy(thief, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(thiefOutputSpy.isValid()); + QVERIFY(thief->load(QStringLiteral("grabAlreadyGrabbedWindowForcedTest_thief"))); + + // create test client + using namespace KWayland::Client; + Surface *surface = Test::createSurface(Test::waylandCompositor()); + QVERIFY(surface); + XdgShellSurface *shellSurface = Test::createXdgShellStableSurface(surface, surface); + QVERIFY(shellSurface); + ShellClient *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(workspace()->activeClient(), c); + + // verify that the owner in fact held the grab + QCOMPARE(ownerOutputSpy.count(), 1); + QCOMPARE(ownerOutputSpy.first().first(), QStringLiteral("ok")); + + // effect that grabbed the test client forcefully should now hold the grab + QCOMPARE(thiefOutputSpy.count(), 1); + QCOMPARE(thiefOutputSpy.first().first(), QStringLiteral("ok")); + QCOMPARE(c->effectWindow()->data(WindowAddedGrabRole).value(), thief); +} + +void ScriptedEffectsTest::testUngrab() +{ + // this test verifies that scripted effects can ungrab windows that they + // are previously grabbed + + // load the test effect + auto effect = new ScriptedEffectWithDebugSpy; + QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(effectOutputSpy.isValid()); + QVERIFY(effect->load(QStringLiteral("ungrabTest"))); + + // create test client + using namespace KWayland::Client; + Surface *surface = Test::createSurface(Test::waylandCompositor()); + QVERIFY(surface); + XdgShellSurface *shellSurface = Test::createXdgShellStableSurface(surface, surface); + QVERIFY(shellSurface); + ShellClient *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(workspace()->activeClient(), c); + + // the test effect should grab the test client successfully + QCOMPARE(effectOutputSpy.count(), 1); + QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); + QCOMPARE(c->effectWindow()->data(WindowAddedGrabRole).value(), effect); + + // when the test effect sees that a window was minimized, it will try to ungrab it + effectOutputSpy.clear(); + c->setMinimized(true); + + QCOMPARE(effectOutputSpy.count(), 1); + QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); + QCOMPARE(c->effectWindow()->data(WindowAddedGrabRole).value(), nullptr); +} + WAYLANDTEST_MAIN(ScriptedEffectsTest) #include "scripted_effects_test.moc" diff --git a/autotests/integration/effects/scripts/grabAlreadyGrabbedWindowForcedTest_owner.js b/autotests/integration/effects/scripts/grabAlreadyGrabbedWindowForcedTest_owner.js new file mode 100644 --- /dev/null +++ b/autotests/integration/effects/scripts/grabAlreadyGrabbedWindowForcedTest_owner.js @@ -0,0 +1,7 @@ +effects.windowAdded.connect(function (window) { + if (effect.grab(window, Effect.WindowAddedGrabRole)) { + sendTestResponse('ok'); + } else { + sendTestResponse('fail'); + } +}); diff --git a/autotests/integration/effects/scripts/grabAlreadyGrabbedWindowForcedTest_thief.js b/autotests/integration/effects/scripts/grabAlreadyGrabbedWindowForcedTest_thief.js new file mode 100644 --- /dev/null +++ b/autotests/integration/effects/scripts/grabAlreadyGrabbedWindowForcedTest_thief.js @@ -0,0 +1,7 @@ +effects.windowAdded.connect(function (window) { + if (effect.grab(window, Effect.WindowAddedGrabRole, true)) { + sendTestResponse('ok'); + } else { + sendTestResponse('fail'); + } +}); diff --git a/autotests/integration/effects/scripts/grabAlreadyGrabbedWindowTest_grabber.js b/autotests/integration/effects/scripts/grabAlreadyGrabbedWindowTest_grabber.js new file mode 100644 --- /dev/null +++ b/autotests/integration/effects/scripts/grabAlreadyGrabbedWindowTest_grabber.js @@ -0,0 +1,7 @@ +effects.windowAdded.connect(function (window) { + if (effect.grab(window, Effect.WindowAddedGrabRole)) { + sendTestResponse('ok'); + } else { + sendTestResponse('fail'); + } +}); diff --git a/autotests/integration/effects/scripts/grabAlreadyGrabbedWindowTest_owner.js b/autotests/integration/effects/scripts/grabAlreadyGrabbedWindowTest_owner.js new file mode 100644 --- /dev/null +++ b/autotests/integration/effects/scripts/grabAlreadyGrabbedWindowTest_owner.js @@ -0,0 +1,7 @@ +effects.windowAdded.connect(function (window) { + if (effect.grab(window, Effect.WindowAddedGrabRole)) { + sendTestResponse('ok'); + } else { + sendTestResponse('fail'); + } +}); diff --git a/autotests/integration/effects/scripts/grabTest.js b/autotests/integration/effects/scripts/grabTest.js new file mode 100644 --- /dev/null +++ b/autotests/integration/effects/scripts/grabTest.js @@ -0,0 +1,7 @@ +effects.windowAdded.connect(function (window) { + if (effect.grab(window, Effect.WindowAddedGrabRole)) { + sendTestResponse('ok'); + } else { + sendTestResponse('fail'); + } +}); diff --git a/autotests/integration/effects/scripts/ungrabTest.js b/autotests/integration/effects/scripts/ungrabTest.js new file mode 100644 --- /dev/null +++ b/autotests/integration/effects/scripts/ungrabTest.js @@ -0,0 +1,15 @@ +effects.windowAdded.connect(function (window) { + if (effect.grab(window, Effect.WindowAddedGrabRole)) { + sendTestResponse('ok'); + } else { + sendTestResponse('fail'); + } +}); + +effects.windowMinimized.connect(function (window) { + if (effect.ungrab(window, Effect.WindowAddedGrabRole)) { + sendTestResponse('ok'); + } else { + sendTestResponse('fail'); + } +}); diff --git a/scripting/scriptedeffect.h b/scripting/scriptedeffect.h --- a/scripting/scriptedeffect.h +++ b/scripting/scriptedeffect.h @@ -80,6 +80,29 @@ * @returns @c true if another window has grabbed the effect, @c false otherwise **/ Q_SCRIPTABLE bool isGrabbed(KWin::EffectWindow *w, DataRole grabRole); + + /** + * Grabs the window with the specified role. + * + * @param w The window. + * @param grabRole The grab role. + * @param force By default, if the window is already grabbed by another effect, + * then that window won't be grabbed by effect that called this method. If you + * would like to grab a window even if it's grabbed by another effect, then + * pass @c true. + * @returns @c true if the window was grabbed successfully, otherwise @c false. + **/ + Q_SCRIPTABLE bool grab(KWin::EffectWindow *w, DataRole grabRole, bool force = false); + + /** + * Ungrabs the window with the specified role. + * + * @param w The window. + * @param grabRole The grab role. + * @returns @c true if the window was ungrabbed successfully, otherwise @c false. + **/ + Q_SCRIPTABLE bool ungrab(KWin::EffectWindow *w, DataRole grabRole); + /** * Reads the value from the configuration data for the given key. * @param key The key to search for diff --git a/scripting/scriptedeffect.cpp b/scripting/scriptedeffect.cpp --- a/scripting/scriptedeffect.cpp +++ b/scripting/scriptedeffect.cpp @@ -667,6 +667,40 @@ } } +bool ScriptedEffect::grab(EffectWindow *w, DataRole grabRole, bool force) +{ + void *grabber = w->data(grabRole).value(); + + if (grabber == this) { + return true; + } + + if (grabber != nullptr && grabber != this && !force) { + return false; + } + + w->setData(grabRole, QVariant::fromValue(static_cast(this))); + + return true; +} + +bool ScriptedEffect::ungrab(EffectWindow *w, DataRole grabRole) +{ + void *grabber = w->data(grabRole).value(); + + if (grabber == nullptr) { + return true; + } + + if (grabber != this) { + return false; + } + + w->setData(grabRole, QVariant()); + + return true; +} + void ScriptedEffect::reconfigure(ReconfigureFlags flags) { AnimationEffect::reconfigure(flags);