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 @@ -66,6 +66,8 @@ void testAnimations(); void testScreenEdge(); void testScreenEdgeTouch(); + void testFullScreenEffect_data(); + void testFullScreenEffect(); private: ScriptedEffect *loadEffect(const QString &name); }; @@ -172,6 +174,7 @@ e->unloadEffect(effect); QVERIFY(!e->isEffectLoaded(effect)); } + KWin::VirtualDesktopManager::self()->setCurrent(1); } void ScriptedEffectsTest::testEffectsHandler() @@ -351,5 +354,61 @@ QCOMPARE(effectOutputSpy.count(), 1); } +void ScriptedEffectsTest::testFullScreenEffect_data() +{ + QTest::addColumn("file"); + + QTest::newRow("single") << "fullScreenEffectTest"; + QTest::newRow("multi") << "fullScreenEffectTestMulti"; +} + +void ScriptedEffectsTest::testFullScreenEffect() +{ + QFETCH(QString, file); + + auto *effectMain = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean + QSignalSpy effectOutputSpy(effectMain, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(effectMain->load(file)); + + //load any random effect from another test to confirm fullscreen effect state is correctly + //shown as being someone else + auto effectOther = new ScriptedEffectWithDebugSpy(); + QVERIFY(effectOther->load("screenEdgeTouchTest")); + + using namespace KWayland::Client; + auto *surface = Test::createSurface(Test::waylandCompositor()); + QVERIFY(surface); + auto *shellSurface = Test::createXdgShellV6Surface(surface, surface); + QVERIFY(shellSurface); + shellSurface->setTitle("Window 1"); + auto *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(workspace()->activeClient(), c); + + QCOMPARE(effectMain->fullScreenEffectState(), ScriptedEffect::InactiveFullScreenEffect); + + //trigger animation + KWin::VirtualDesktopManager::self()->setCurrent(2); + + QCOMPARE(effectMain->fullScreenEffectState(), ScriptedEffect::OwnedFullScreenEffect); + QCOMPARE(effects->activeFullScreenEffect(), effectMain); + QCOMPARE(effectOther->fullScreenEffectState(), ScriptedEffect::ActiveFullScreenEffect); + + //after 500ms trigger another full screen animation + QTest::qWait(500); + KWin::VirtualDesktopManager::self()->setCurrent(1); + QCOMPARE(effects->activeFullScreenEffect(), effectMain); + + //after 1000ms (+a safety margin for time based tests) we should still be the active full screen effect + //despite first animation expiring + QTest::qWait(500+100); + QCOMPARE(effects->activeFullScreenEffect(), effectMain); + + //after 1500ms (+a safetey margin) we should have no full screen effect + QTest::qWait(500+100); + QCOMPARE(effects->activeFullScreenEffect(), nullptr); +} + + WAYLANDTEST_MAIN(ScriptedEffectsTest) #include "scripted_effects_test.moc" diff --git a/autotests/integration/effects/scripts/fullScreenEffectTest.js b/autotests/integration/effects/scripts/fullScreenEffectTest.js new file mode 100644 --- /dev/null +++ b/autotests/integration/effects/scripts/fullScreenEffectTest.js @@ -0,0 +1,8 @@ +effects['desktopChanged(int,int)'].connect(function(old, current) { + var stackingOrder = effects.stackingOrder; + for (var i=0; i fullScreenEffectLock; bool waitAtSource, keepAtTarget; }; diff --git a/libkwineffects/kwinanimationeffect.h b/libkwineffects/kwinanimationeffect.h --- a/libkwineffects/kwinanimationeffect.h +++ b/libkwineffects/kwinanimationeffect.h @@ -88,6 +88,19 @@ bool valid; }; +/** + * Wraps effects->setActiveFullScreenEffect for the duration of it's lifespan + */ +class FullScreenEffectLock +{ +public: + FullScreenEffectLock(Effect *effect); + ~FullScreenEffectLock(); +private: + void *d; //unused currently +}; +typedef QSharedPointer FullScreenEffectLockPtr; + class AniData; class AnimationEffectPrivate; class KWINEFFECTS_EXPORT AnimationEffect : public Effect @@ -160,19 +173,20 @@ * @param to - The target value. FPx2 is an agnostic two component float type (like QPointF or QSizeF, but without requiring to be either and supporting an invalid state) * @param shape - How the animation progresses, eg. Linear progresses constantly while Exponential start slow and becomes very fast in the end * @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 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 * @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() ) - { return p_animate(w, a, meta, ms, to, curve, delay, from, false); } + 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); } /** * 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() ) - { return p_animate(w, a, meta, ms, to, curve, delay, from, true); } + 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); } /** * this allows to alter the target (but not type or curve) of a running animation @@ -211,7 +225,7 @@ 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 ); + quint64 p_animate(EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve, int delay, FPx2 from, bool keepAtTarget , bool fullScreenEffect); 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/libkwineffects/kwinanimationeffect.cpp b/libkwineffects/kwinanimationeffect.cpp --- a/libkwineffects/kwinanimationeffect.cpp +++ b/libkwineffects/kwinanimationeffect.cpp @@ -45,9 +45,10 @@ } AnimationEffect::AniMap m_animations; EffectWindowList m_zombies; - bool m_animated, m_damageDirty, m_needSceneRepaint, m_animationsTouched, m_isInitialized; - quint64 m_justEndedAnimation; // protect against cancel static quint64 m_animCounter; + quint64 m_justEndedAnimation; // protect against cancel + QWeakPointer m_fullScreenEffectLock; + bool m_animated, m_damageDirty, m_needSceneRepaint, m_animationsTouched, m_isInitialized; }; } @@ -216,7 +217,7 @@ } } -quint64 AnimationEffect::p_animate( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve, int delay, FPx2 from, bool keepAtTarget ) +quint64 AnimationEffect::p_animate( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve, int delay, FPx2 from, bool keepAtTarget, bool fullScreenEffect) { const bool waitAtSource = from.isValid(); validate(a, meta, &from, &to, w); @@ -237,7 +238,17 @@ AniMap::iterator it = d->m_animations.find(w); if (it == d->m_animations.end()) it = d->m_animations.insert(w, QPair, QRect>(QList(), QRect())); - it->first.append(AniData(a, meta, ms, to, curve, delay, from, waitAtSource, keepAtTarget)); + + FullScreenEffectLockPtr fullscreen; + if (fullScreenEffect) { + if (d->m_fullScreenEffectLock.isNull()) { + fullscreen = FullScreenEffectLockPtr(new FullScreenEffectLock(this)); + d->m_fullScreenEffectLock = fullscreen.toWeakRef(); + } else { + fullscreen = d->m_fullScreenEffectLock.toStrongRef(); + } + } + it->first.append(AniData(a, meta, ms, to, curve, delay, from, waitAtSource, keepAtTarget, fullscreen)); quint64 ret_id = ++d->m_animCounter; it->first.last().id = ret_id; it->second = QRect(); @@ -954,5 +965,14 @@ return d->m_animations; } +FullScreenEffectLock::FullScreenEffectLock(Effect *effect) +{ + effects->setActiveFullScreenEffect(effect); +} + +FullScreenEffectLock::~FullScreenEffectLock() +{ + effects->setActiveFullScreenEffect(nullptr); +} #include "moc_kwinanimationeffect.cpp" diff --git a/scripting/scriptedeffect.h b/scripting/scriptedeffect.h --- a/scripting/scriptedeffect.h +++ b/scripting/scriptedeffect.h @@ -38,7 +38,15 @@ Q_ENUMS(Anchor) Q_ENUMS(MetaType) Q_ENUMS(EasingCurve) + Q_ENUMS(ActiveFullScreenEffectState) + Q_PROPERTY(ActiveFullScreenEffectState fullScreenEffectState READ fullScreenEffectState NOTIFY activeFullScreenEffectChanged) public: + enum ActiveFullScreenEffectState { + InactiveFullScreenEffect, ///< ActiveFullScreenEffect is null + OwnedFullScreenEffect, ///< ActiveFullScreenEffect is us + ActiveFullScreenEffect, ///< ActiveFullScreenEffect is someone else + }; + // copied from kwineffects.h enum DataRole { // Grab roles are used to force all other animations to ignore the window. @@ -91,23 +99,27 @@ return m_screenEdgeCallbacks; } + ActiveFullScreenEffectState fullScreenEffectState() const; bool registerTouchScreenCallback(int edge, QScriptValue callback); bool unregisterTouchScreenCallback(int edge); 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); - 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); + 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); bool retarget(quint64 animationId, KWin::FPx2 newTarget, int newRemainingTime = -1); bool cancel(quint64 animationId) { return AnimationEffect::cancel(animationId); } virtual bool borderActivated(ElectricBorder border); + + Q_SIGNALS: /** * Signal emitted whenever the effect's config changed. **/ void configChanged(); void animationEnded(KWin::EffectWindow *w, quint64 animationId); + void activeFullScreenEffectChanged(); protected: ScriptedEffect(); diff --git a/scripting/scriptedeffect.cpp b/scripting/scriptedeffect.cpp --- a/scripting/scriptedeffect.cpp +++ b/scripting/scriptedeffect.cpp @@ -103,15 +103,16 @@ } struct AnimationSettings { - enum { Type = 1<<0, Curve = 1<<1, Delay = 1<<2, Duration = 1<<3 }; + enum { Type = 1<<0, Curve = 1<<1, Delay = 1<<2, Duration = 1<<3, FullScreen = 1<<4 }; AnimationEffect::Attribute type; QEasingCurve::Type curve; FPx2 from; FPx2 to; int delay; uint duration; uint set; uint metaData; + bool fullScreenEffect; }; AnimationSettings animationSettingsFromObject(QScriptValue &object) @@ -155,6 +156,14 @@ settings.type = static_cast(-1); } + QScriptValue isFullScreen = object.property(QStringLiteral("fullScreen")); + if (isFullScreen.isValid() && isFullScreen.isBool()) { + settings.fullScreenEffect = isFullScreen.toBool(); + settings.set |= AnimationSettings::FullScreen; + } else { + settings.fullScreenEffect = false; + } + return settings; } @@ -285,7 +294,8 @@ setting.from, setting.metaData, setting.curve, - setting.delay)); + setting.delay, + setting.fullScreenEffect)); ++i; } return array; @@ -475,7 +485,9 @@ , m_config(nullptr) , m_chainPosition(0) { + Q_ASSERT(effects); connect(m_engine, SIGNAL(signalHandlerException(QScriptValue)), SLOT(signalHandlerException(QScriptValue))); + connect(effects, &EffectsHandler::activeFullScreenEffectChanged, this, &ScriptedEffect::activeFullScreenEffectChanged); } ScriptedEffect::~ScriptedEffect() @@ -567,6 +579,17 @@ emit animationEnded(w, 0); } +ScriptedEffect::ActiveFullScreenEffectState ScriptedEffect::fullScreenEffectState() const +{ + const auto *activeEffect = effects->activeFullScreenEffect(); + if (!activeEffect) { + return ScriptedEffect::InactiveFullScreenEffect; + } + if (activeEffect == this) { + return ScriptedEffect::OwnedFullScreenEffect; + } + return ScriptedEffect::ActiveFullScreenEffect; +} void ScriptedEffect::signalHandlerException(const QScriptValue &value) { if (value.isError()) { @@ -581,24 +604,24 @@ } } -quint64 ScriptedEffect::animate(KWin::EffectWindow* w, KWin::AnimationEffect::Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from, uint metaData, int curve, int delay) +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) { 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); + return AnimationEffect::animate(w, a, metaData, ms, to, qec, delay, from, 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) +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) { 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); + return AnimationEffect::set(w, a, metaData, ms, to, qec, delay, from, fullScreen); } bool ScriptedEffect::retarget(quint64 animationId, KWin::FPx2 newTarget, int newRemainingTime)