[scripting] Allow effects to grab windows

Summary:
Some JavaScript based effects need to grab particular windows in order
to avoid conflicts with other effects.

Example usage:

```lang=js
effects.windowAdded.connect(function (window) {
    if (effect.grab(window, Effect.WindowAddedGrabRole)) {
        window.coolWindowTypeAnimation = animate({
            ...
        });
    }
});
```

Reviewers: #kwin, davidedmundson

Reviewed By: #kwin, davidedmundson

Subscribers: romangg, graesslin, davidedmundson, kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D13153
This commit is contained in:
Vlad Zagorodniy 2018-10-27 23:13:53 +03:00
parent 6c5d7ef2ad
commit 8d0554e45a
9 changed files with 247 additions and 0 deletions

View file

@ -70,6 +70,10 @@ private Q_SLOTS:
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::testKeepAlive()
}
}
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<void *>(), 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<void *>(), 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<void *>(), 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<void *>(), 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<void *>(), nullptr);
}
WAYLANDTEST_MAIN(ScriptedEffectsTest)
#include "scripted_effects_test.moc"

View file

@ -0,0 +1,7 @@
effects.windowAdded.connect(function (window) {
if (effect.grab(window, Effect.WindowAddedGrabRole)) {
sendTestResponse('ok');
} else {
sendTestResponse('fail');
}
});

View file

@ -0,0 +1,7 @@
effects.windowAdded.connect(function (window) {
if (effect.grab(window, Effect.WindowAddedGrabRole, true)) {
sendTestResponse('ok');
} else {
sendTestResponse('fail');
}
});

View file

@ -0,0 +1,7 @@
effects.windowAdded.connect(function (window) {
if (effect.grab(window, Effect.WindowAddedGrabRole)) {
sendTestResponse('ok');
} else {
sendTestResponse('fail');
}
});

View file

@ -0,0 +1,7 @@
effects.windowAdded.connect(function (window) {
if (effect.grab(window, Effect.WindowAddedGrabRole)) {
sendTestResponse('ok');
} else {
sendTestResponse('fail');
}
});

View file

@ -0,0 +1,7 @@
effects.windowAdded.connect(function (window) {
if (effect.grab(window, Effect.WindowAddedGrabRole)) {
sendTestResponse('ok');
} else {
sendTestResponse('fail');
}
});

View file

@ -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');
}
});

View file

@ -667,6 +667,40 @@ bool ScriptedEffect::isGrabbed(EffectWindow* w, ScriptedEffect::DataRole grabRol
}
}
bool ScriptedEffect::grab(EffectWindow *w, DataRole grabRole, bool force)
{
void *grabber = w->data(grabRole).value<void *>();
if (grabber == this) {
return true;
}
if (grabber != nullptr && grabber != this && !force) {
return false;
}
w->setData(grabRole, QVariant::fromValue(static_cast<void *>(this)));
return true;
}
bool ScriptedEffect::ungrab(EffectWindow *w, DataRole grabRole)
{
void *grabber = w->data(grabRole).value<void *>();
if (grabber == nullptr) {
return true;
}
if (grabber != this) {
return false;
}
w->setData(grabRole, QVariant());
return true;
}
void ScriptedEffect::reconfigure(ReconfigureFlags flags)
{
AnimationEffect::reconfigure(flags);

View file

@ -80,6 +80,29 @@ public:
* @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