diff --git a/autotests/integration/effects/scripted_effects_test.cpp b/autotests/integration/effects/scripted_effects_test.cpp
index 4e477b0aaf..ab7667bcbf 100644
--- a/autotests/integration/effects/scripted_effects_test.cpp
+++ b/autotests/integration/effects/scripted_effects_test.cpp
@@ -23,7 +23,7 @@ along with this program. If not, see .
#include "composite.h"
#include "cursor.h"
-#include "cursor.h"
+#include "deleted.h"
#include "effect_builtins.h"
#include "effectloader.h"
#include "effects.h"
@@ -68,6 +68,9 @@ private Q_SLOTS:
void testScreenEdgeTouch();
void testFullScreenEffect_data();
void testFullScreenEffect();
+ void testKeepAlive_data();
+ void testKeepAlive();
+
private:
ScriptedEffect *loadEffect(const QString &name);
};
@@ -132,6 +135,7 @@ void ScriptedEffectsTest::initTestCase()
{
qRegisterMetaType();
qRegisterMetaType();
+ qRegisterMetaType();
qRegisterMetaType();
QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated);
QVERIFY(workspaceCreatedSpy.isValid());
@@ -421,6 +425,62 @@ void ScriptedEffectsTest::testFullScreenEffect()
QCOMPARE(effects->activeFullScreenEffect(), nullptr);
}
+void ScriptedEffectsTest::testKeepAlive_data()
+{
+ QTest::addColumn("file");
+ QTest::addColumn("keepAlive");
+
+ QTest::newRow("keep") << "keepAliveTest" << true;
+ QTest::newRow("don't keep") << "keepAliveTestDontKeep" << false;
+}
+
+void ScriptedEffectsTest::testKeepAlive()
+{
+ // this test checks whether closed windows are kept alive
+ // when keepAlive property is set to true(false)
+
+ QFETCH(QString, file);
+ QFETCH(bool, keepAlive);
+
+ auto *effect = new ScriptedEffectWithDebugSpy;
+ QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput);
+ QVERIFY(effectOutputSpy.isValid());
+ QVERIFY(effect->load(file));
+
+ // create a window
+ using namespace KWayland::Client;
+ auto *surface = Test::createSurface(Test::waylandCompositor());
+ QVERIFY(surface);
+ auto *shellSurface = Test::createXdgShellV6Surface(surface, surface);
+ QVERIFY(shellSurface);
+ auto *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue);
+ QVERIFY(c);
+ QCOMPARE(workspace()->activeClient(), c);
+
+ // no active animations at the beginning
+ QCOMPARE(effect->state().count(), 0);
+
+ // trigger windowClosed signal
+ surface->deleteLater();
+ QVERIFY(effectOutputSpy.count() == 1 || effectOutputSpy.wait());
+
+ if (keepAlive) {
+ QCOMPARE(effect->state().count(), 1);
+
+ QTest::qWait(500);
+ QCOMPARE(effect->state().count(), 1);
+
+ QTest::qWait(500 + 100); // 100ms is extra safety margin
+ QCOMPARE(effect->state().count(), 0);
+ } else {
+ // the test effect doesn't keep the window alive, so it should be
+ // removed immediately
+ QSignalSpy deletedRemovedSpy(workspace(), &Workspace::deletedRemoved);
+ QVERIFY(deletedRemovedSpy.isValid());
+ QVERIFY(deletedRemovedSpy.count() == 1 || deletedRemovedSpy.wait(100)); // 100ms is less than duration of the animation
+ QCOMPARE(effect->state().count(), 0);
+ }
+}
WAYLANDTEST_MAIN(ScriptedEffectsTest)
#include "scripted_effects_test.moc"
diff --git a/autotests/integration/effects/scripts/keepAliveTest.js b/autotests/integration/effects/scripts/keepAliveTest.js
new file mode 100644
index 0000000000..cf3732dc5b
--- /dev/null
+++ b/autotests/integration/effects/scripts/keepAliveTest.js
@@ -0,0 +1,13 @@
+"use strict";
+
+effects.windowClosed.connect(function (window) {
+ animate({
+ window: window,
+ type: Effect.Scale,
+ duration: 1000,
+ from: 1.0,
+ to: 0.0
+ // by default, keepAlive is set to true
+ });
+ sendTestResponse("triggered");
+});
diff --git a/autotests/integration/effects/scripts/keepAliveTestDontKeep.js b/autotests/integration/effects/scripts/keepAliveTestDontKeep.js
new file mode 100644
index 0000000000..72bdc5d669
--- /dev/null
+++ b/autotests/integration/effects/scripts/keepAliveTestDontKeep.js
@@ -0,0 +1,13 @@
+"use strict";
+
+effects.windowClosed.connect(function (window) {
+ animate({
+ window: window,
+ type: Effect.Scale,
+ duration: 1000,
+ from: 1.0,
+ to: 0.0,
+ keepAlive: false
+ });
+ sendTestResponse("triggered");
+});
diff --git a/effects/dialogparent/package/contents/code/main.js b/effects/dialogparent/package/contents/code/main.js
index 2ca3c0e19b..9bf63de0d6 100644
--- a/effects/dialogparent/package/contents/code/main.js
+++ b/effects/dialogparent/package/contents/code/main.js
@@ -77,6 +77,7 @@ var dialogParentEffect = {
animate({
window: w,
duration: dialogParentEffect.duration,
+ keepAlive: false,
animations: [{
type: Effect.Saturation,
from: 0.4,
diff --git a/libkwineffects/anidata.cpp b/libkwineffects/anidata.cpp
index 443b67197c..cd9f5c249f 100644
--- a/libkwineffects/anidata.cpp
+++ b/libkwineffects/anidata.cpp
@@ -3,6 +3,7 @@
This file is part of the KDE project.
Copyright (C) 2011 Thomas Lübking
+Copyright (C) 2018 Vlad Zagorodniy
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -41,6 +42,17 @@ FullScreenEffectLock::~FullScreenEffectLock()
effects->setActiveFullScreenEffect(nullptr);
}
+KeepAliveLock::KeepAliveLock(EffectWindow *w)
+ : m_window(w)
+{
+ m_window->refWindow();
+}
+
+KeepAliveLock::~KeepAliveLock()
+{
+ m_window->unrefWindow();
+}
+
AniData::AniData()
: attribute(AnimationEffect::Opacity)
, customCurve(0) // Linear
@@ -51,12 +63,13 @@ AniData::AniData()
, windowType((NET::WindowTypeMask)0)
, waitAtSource(false)
, keepAtTarget(false)
+ , keepAlive(true)
{
}
AniData::AniData(AnimationEffect::Attribute a, int meta_, int ms, const FPx2 &to_,
QEasingCurve curve_, int delay, const FPx2 &from_, bool waitAtSource_, bool keepAtTarget_,
- FullScreenEffectLockPtr fullScreenEffectLock_)
+ FullScreenEffectLockPtr fullScreenEffectLock_, bool keepAlive)
: attribute(a)
, curve(curve_)
, from(from_)
@@ -69,6 +82,7 @@ AniData::AniData(AnimationEffect::Attribute a, int meta_, int ms, const FPx2 &to
, fullScreenEffectLock(fullScreenEffectLock_)
, waitAtSource(waitAtSource_)
, keepAtTarget(keepAtTarget_)
+ , keepAlive(keepAlive)
{
}
diff --git a/libkwineffects/anidata_p.h b/libkwineffects/anidata_p.h
index eafd6f0fb3..95e92c1c13 100644
--- a/libkwineffects/anidata_p.h
+++ b/libkwineffects/anidata_p.h
@@ -3,6 +3,7 @@
This file is part of the KDE project.
Copyright (C) 2011 Thomas Lübking
+Copyright (C) 2018 Vlad Zagorodniy
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -40,11 +41,28 @@ private:
};
typedef QSharedPointer FullScreenEffectLockPtr;
+/**
+ * Keeps windows alive during animation after they got closed
+ **/
+class KeepAliveLock
+{
+public:
+ KeepAliveLock(EffectWindow *w);
+ ~KeepAliveLock();
+
+private:
+ EffectWindow *m_window;
+ Q_DISABLE_COPY(KeepAliveLock)
+};
+typedef QSharedPointer KeepAliveLockPtr;
+
class KWINEFFECTS_EXPORT AniData {
public:
AniData();
AniData(AnimationEffect::Attribute a, int meta, int ms, const FPx2 &to,
- QEasingCurve curve, int delay, const FPx2 &from, bool waitAtSource, bool keepAtTarget = false, FullScreenEffectLockPtr=FullScreenEffectLockPtr());
+ QEasingCurve curve, int delay, const FPx2 &from, bool waitAtSource,
+ bool keepAtTarget = false, FullScreenEffectLockPtr=FullScreenEffectLockPtr(),
+ bool keepAlive = true);
inline void addTime(int t) { time += t; }
inline bool isOneDimensional() const {
return from[0] == from[1] && to[0] == to[1];
@@ -62,6 +80,8 @@ public:
NET::WindowTypeMask windowType;
QSharedPointer fullScreenEffectLock;
bool waitAtSource, keepAtTarget;
+ bool keepAlive;
+ KeepAliveLockPtr keepAliveLock;
};
} // namespace
diff --git a/libkwineffects/kwinanimationeffect.cpp b/libkwineffects/kwinanimationeffect.cpp
index 5f0d8dc373..144996f227 100644
--- a/libkwineffects/kwinanimationeffect.cpp
+++ b/libkwineffects/kwinanimationeffect.cpp
@@ -44,7 +44,6 @@ public:
m_justEndedAnimation = 0;
}
AnimationEffect::AniMap m_animations;
- EffectWindowList m_zombies;
static quint64 m_animCounter;
quint64 m_justEndedAnimation; // protect against cancel
QWeakPointer m_fullScreenEffectLock;
@@ -217,7 +216,7 @@ void AnimationEffect::validate(Attribute a, uint &meta, FPx2 *from, FPx2 *to, co
}
}
-quint64 AnimationEffect::p_animate( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve, int delay, FPx2 from, bool keepAtTarget, bool fullScreenEffect)
+quint64 AnimationEffect::p_animate( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve, int delay, FPx2 from, bool keepAtTarget, bool fullScreenEffect, bool keepAlive)
{
const bool waitAtSource = from.isValid();
validate(a, meta, &from, &to, w);
@@ -248,7 +247,7 @@ quint64 AnimationEffect::p_animate( EffectWindow *w, Attribute a, uint meta, int
fullscreen = d->m_fullScreenEffectLock.toStrongRef();
}
}
- it->first.append(AniData(a, meta, ms, to, curve, delay, from, waitAtSource, keepAtTarget, fullscreen));
+ it->first.append(AniData(a, meta, ms, to, curve, delay, from, waitAtSource, keepAtTarget, fullscreen, keepAlive));
quint64 ret_id = ++d->m_animCounter;
it->first.last().id = ret_id;
it->second = QRect();
@@ -298,11 +297,6 @@ bool AnimationEffect::cancel(quint64 animationId)
if (anim->id == animationId) {
entry->first.erase(anim); // remove the animation
if (entry->first.isEmpty()) { // no other animations on the window, release it.
- const int i = d->m_zombies.indexOf(entry.key());
- if ( i > -1 ) {
- d->m_zombies.removeAt( i );
- entry.key()->unrefWindow();
- }
d->m_animations.erase(entry);
}
if (d->m_animations.isEmpty())
@@ -378,11 +372,6 @@ void AnimationEffect::prePaintScreen( ScreenPrePaintData& data, int time )
}
}
if (entry->first.isEmpty()) {
- const int i = d->m_zombies.indexOf(entry.key());
- if ( i > -1 ) {
- d->m_zombies.removeAt( i );
- entry.key()->unrefWindow();
- }
data.paint |= entry->second;
// d->m_damageDirty = true; // TODO likely no longer required
entry = d->m_animations.erase(entry);
@@ -397,11 +386,6 @@ void AnimationEffect::prePaintScreen( ScreenPrePaintData& data, int time )
// janitorial...
if (d->m_animations.isEmpty()) {
disconnectGeometryChanges();
- if (!d->m_zombies.isEmpty()) { // this is actually not supposed to happen
- foreach (EffectWindow *w, d->m_zombies)
- w->unrefWindow();
- d->m_zombies.clear();
- }
}
effects->prePaintScreen(data, time);
@@ -924,16 +908,33 @@ void AnimationEffect::_expandedGeometryChanged(KWin::EffectWindow *w, const QRec
void AnimationEffect::_windowClosed( EffectWindow* w )
{
Q_D(AnimationEffect);
- if (d->m_animations.contains(w) && !d->m_zombies.contains(w)) {
- w->refWindow();
- d->m_zombies << w;
+
+ auto it = d->m_animations.find(w);
+ if (it == d->m_animations.end()) {
+ return;
+ }
+
+ KeepAliveLockPtr keepAliveLock;
+
+ QList &animations = (*it).first;
+ for (auto animationIt = animations.begin();
+ animationIt != animations.end();
+ ++animationIt) {
+ if (!(*animationIt).keepAlive) {
+ continue;
+ }
+
+ if (keepAliveLock.isNull()) {
+ keepAliveLock = KeepAliveLockPtr::create(w);
+ }
+
+ (*animationIt).keepAliveLock = keepAliveLock;
}
}
void AnimationEffect::_windowDeleted( EffectWindow* w )
{
Q_D(AnimationEffect);
- d->m_zombies.removeAll( w ); // TODO this line is a workaround for a bug in KWin 4.8.0 & 4.8.1
d->m_animations.remove( w );
}
diff --git a/libkwineffects/kwinanimationeffect.h b/libkwineffects/kwinanimationeffect.h
index ce46f34493..a71bf1908b 100644
--- a/libkwineffects/kwinanimationeffect.h
+++ b/libkwineffects/kwinanimationeffect.h
@@ -162,18 +162,19 @@ protected:
* @param delay - When the animation will start compared to "now" (the window will remain at the "from" position until then)
* @param from - The starting value, the default is invalid, ie. the attribute for the window is not transformed in the beginning
* @param fullScreen - Sets this effect as the active full screen effect for the duration of the animation
+ * @param keepAlive - Whether closed windows should be kept alive during animation
* @return an ID that you can use to cancel a running animation
*/
- quint64 animate( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve = QEasingCurve(), int delay = 0, FPx2 from = FPx2(), bool fullScreen = false)
- { return p_animate(w, a, meta, ms, to, curve, delay, from, false, fullScreen); }
+ quint64 animate( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve = QEasingCurve(), int delay = 0, FPx2 from = FPx2(), bool fullScreen = false, bool keepAlive = true)
+ { return p_animate(w, a, meta, ms, to, curve, delay, from, false, fullScreen, keepAlive); }
/**
* Equal to ::animate() with one important difference:
* The target value for the attribute is kept until you ::cancel() this animation
* @return an ID that you need to use to cancel this manipulation
*/
- quint64 set( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve = QEasingCurve(), int delay = 0, FPx2 from = FPx2(), bool fullScreen = false)
- { return p_animate(w, a, meta, ms, to, curve, delay, from, true, fullScreen); }
+ quint64 set( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve = QEasingCurve(), int delay = 0, FPx2 from = FPx2(), bool fullScreen = false, bool keepAlive = true)
+ { return p_animate(w, a, meta, ms, to, curve, delay, from, true, fullScreen, keepAlive); }
/**
* this allows to alter the target (but not type or curve) of a running animation
@@ -212,7 +213,7 @@ protected:
AniMap state() const;
private:
- quint64 p_animate(EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve, int delay, FPx2 from, bool keepAtTarget, bool fullScreenEffect);
+ quint64 p_animate(EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve, int delay, FPx2 from, bool keepAtTarget, bool fullScreenEffect, bool keepAlive);
QRect clipRect(const QRect &windowRect, const AniData&) const;
void clipWindow(const EffectWindow *, const AniData &, WindowQuadList &) const;
float interpolated( const AniData&, int i = 0 ) const;
diff --git a/scripting/scriptedeffect.cpp b/scripting/scriptedeffect.cpp
index d3837ea32b..730146e56a 100644
--- a/scripting/scriptedeffect.cpp
+++ b/scripting/scriptedeffect.cpp
@@ -103,7 +103,14 @@ QScriptValue kwinUnregisterTouchScreenEdge(QScriptContext *context, QScriptEngin
}
struct AnimationSettings {
- enum { Type = 1<<0, Curve = 1<<1, Delay = 1<<2, Duration = 1<<3, FullScreen = 1<<4 };
+ enum {
+ Type = 1<<0,
+ Curve = 1<<1,
+ Delay = 1<<2,
+ Duration = 1<<3,
+ FullScreen = 1<<4,
+ KeepAlive = 1<<5
+ };
AnimationEffect::Attribute type;
QEasingCurve::Type curve;
FPx2 from;
@@ -113,6 +120,7 @@ struct AnimationSettings {
uint set;
uint metaData;
bool fullScreenEffect;
+ bool keepAlive;
};
AnimationSettings animationSettingsFromObject(QScriptValue &object)
@@ -164,6 +172,14 @@ AnimationSettings animationSettingsFromObject(QScriptValue &object)
settings.fullScreenEffect = false;
}
+ QScriptValue keepAlive = object.property(QStringLiteral("keepAlive"));
+ if (keepAlive.isValid() && keepAlive.isBool()) {
+ settings.keepAlive = keepAlive.toBool();
+ settings.set |= AnimationSettings::KeepAlive;
+ } else {
+ settings.keepAlive = true;
+ }
+
return settings;
}
@@ -230,6 +246,9 @@ QList animationSettings(QScriptContext *context, ScriptedEffe
if (!(s.set & AnimationSettings::FullScreen)) {
s.fullScreenEffect = settings.at(0).fullScreenEffect;
}
+ if (!(s.set & AnimationSettings::KeepAlive)) {
+ s.keepAlive = settings.at(0).keepAlive;
+ }
s.metaData = 0;
typedef QMap MetaTypeMap;
@@ -298,7 +317,8 @@ QScriptValue kwinEffectAnimate(QScriptContext *context, QScriptEngine *engine)
setting.metaData,
setting.curve,
setting.delay,
- setting.fullScreenEffect));
+ setting.fullScreenEffect,
+ setting.keepAlive));
++i;
}
return array;
@@ -328,7 +348,9 @@ QScriptValue kwinEffectSet(QScriptContext *context, QScriptEngine *engine)
setting.from,
setting.metaData,
setting.curve,
- setting.delay));
+ setting.delay,
+ setting.fullScreenEffect,
+ setting.keepAlive));
}
return engine->newVariant(animIds);
@@ -610,24 +632,24 @@ void ScriptedEffect::signalHandlerException(const QScriptValue &value)
}
}
-quint64 ScriptedEffect::animate(KWin::EffectWindow* w, KWin::AnimationEffect::Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from, uint metaData, int curve, int delay, bool fullScreen)
+quint64 ScriptedEffect::animate(KWin::EffectWindow* w, KWin::AnimationEffect::Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from, uint metaData, int curve, int delay, bool fullScreen, bool keepAlive)
{
QEasingCurve qec;
if (curve < QEasingCurve::Custom)
qec.setType(static_cast(curve));
else if (curve == GaussianCurve)
qec.setCustomType(qecGaussian);
- return AnimationEffect::animate(w, a, metaData, ms, to, qec, delay, from, fullScreen);
+ return AnimationEffect::animate(w, a, metaData, ms, to, qec, delay, from, fullScreen, keepAlive);
}
-quint64 ScriptedEffect::set(KWin::EffectWindow* w, KWin::AnimationEffect::Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from, uint metaData, int curve, int delay, bool fullScreen)
+quint64 ScriptedEffect::set(KWin::EffectWindow* w, KWin::AnimationEffect::Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from, uint metaData, int curve, int delay, bool fullScreen, bool keepAlive)
{
QEasingCurve qec;
if (curve < QEasingCurve::Custom)
qec.setType(static_cast(curve));
else if (curve == GaussianCurve)
qec.setCustomType(qecGaussian);
- return AnimationEffect::set(w, a, metaData, ms, to, qec, delay, from, fullScreen);
+ return AnimationEffect::set(w, a, metaData, ms, to, qec, delay, from, fullScreen, keepAlive);
}
bool ScriptedEffect::retarget(quint64 animationId, KWin::FPx2 newTarget, int newRemainingTime)
diff --git a/scripting/scriptedeffect.h b/scripting/scriptedeffect.h
index 8a30d0cded..0b667bb1b7 100644
--- a/scripting/scriptedeffect.h
+++ b/scripting/scriptedeffect.h
@@ -102,8 +102,8 @@ public:
public Q_SLOTS:
//curve should be of type QEasingCurve::type or ScriptedEffect::EasingCurve
- quint64 animate(KWin::EffectWindow *w, Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from = KWin::FPx2(), uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0, bool fullScreen = false);
- quint64 set(KWin::EffectWindow *w, Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from = KWin::FPx2(), uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0, bool fullScreen = false);
+ quint64 animate(KWin::EffectWindow *w, Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from = KWin::FPx2(), uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0, bool fullScreen = false, bool keepAlive = true);
+ quint64 set(KWin::EffectWindow *w, Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from = KWin::FPx2(), uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0, bool fullScreen = false, bool keepAlive = true);
bool retarget(quint64 animationId, KWin::FPx2 newTarget, int newRemainingTime = -1);
bool cancel(quint64 animationId) { return AnimationEffect::cancel(animationId); }
virtual bool borderActivated(ElectricBorder border);