[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/D13153icc-effect-5.17.5
parent
6c5d7ef2ad
commit
8d0554e45a
|
@ -70,6 +70,10 @@ private Q_SLOTS:
|
||||||
void testFullScreenEffect();
|
void testFullScreenEffect();
|
||||||
void testKeepAlive_data();
|
void testKeepAlive_data();
|
||||||
void testKeepAlive();
|
void testKeepAlive();
|
||||||
|
void testGrab();
|
||||||
|
void testGrabAlreadyGrabbedWindow();
|
||||||
|
void testGrabAlreadyGrabbedWindowForced();
|
||||||
|
void testUngrab();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ScriptedEffect *loadEffect(const QString &name);
|
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)
|
WAYLANDTEST_MAIN(ScriptedEffectsTest)
|
||||||
#include "scripted_effects_test.moc"
|
#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');
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
effects.windowAdded.connect(function (window) {
|
||||||
|
if (effect.grab(window, Effect.WindowAddedGrabRole)) {
|
||||||
|
sendTestResponse('ok');
|
||||||
|
} else {
|
||||||
|
sendTestResponse('fail');
|
||||||
|
}
|
||||||
|
});
|
|
@ -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)
|
void ScriptedEffect::reconfigure(ReconfigureFlags flags)
|
||||||
{
|
{
|
||||||
AnimationEffect::reconfigure(flags);
|
AnimationEffect::reconfigure(flags);
|
||||||
|
|
|
@ -80,6 +80,29 @@ public:
|
||||||
* @returns @c true if another window has grabbed the effect, @c false otherwise
|
* @returns @c true if another window has grabbed the effect, @c false otherwise
|
||||||
**/
|
**/
|
||||||
Q_SCRIPTABLE bool isGrabbed(KWin::EffectWindow *w, DataRole grabRole);
|
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.
|
* Reads the value from the configuration data for the given key.
|
||||||
* @param key The key to search for
|
* @param key The key to search for
|
||||||
|
|
Loading…
Reference in New Issue