[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:
parent
6c5d7ef2ad
commit
8d0554e45a
9 changed files with 247 additions and 0 deletions
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
effects.windowAdded.connect(function (window) {
|
||||
if (effect.grab(window, Effect.WindowAddedGrabRole)) {
|
||||
sendTestResponse('ok');
|
||||
} else {
|
||||
sendTestResponse('fail');
|
||||
}
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
effects.windowAdded.connect(function (window) {
|
||||
if (effect.grab(window, Effect.WindowAddedGrabRole, true)) {
|
||||
sendTestResponse('ok');
|
||||
} else {
|
||||
sendTestResponse('fail');
|
||||
}
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
effects.windowAdded.connect(function (window) {
|
||||
if (effect.grab(window, Effect.WindowAddedGrabRole)) {
|
||||
sendTestResponse('ok');
|
||||
} else {
|
||||
sendTestResponse('fail');
|
||||
}
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
effects.windowAdded.connect(function (window) {
|
||||
if (effect.grab(window, Effect.WindowAddedGrabRole)) {
|
||||
sendTestResponse('ok');
|
||||
} else {
|
||||
sendTestResponse('fail');
|
||||
}
|
||||
});
|
7
autotests/integration/effects/scripts/grabTest.js
Normal file
7
autotests/integration/effects/scripts/grabTest.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
effects.windowAdded.connect(function (window) {
|
||||
if (effect.grab(window, Effect.WindowAddedGrabRole)) {
|
||||
sendTestResponse('ok');
|
||||
} else {
|
||||
sendTestResponse('fail');
|
||||
}
|
||||
});
|
15
autotests/integration/effects/scripts/ungrabTest.js
Normal file
15
autotests/integration/effects/scripts/ungrabTest.js
Normal 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');
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue