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,72 @@ 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); + QSignalSpy fullScreenEffectActiveSpy(effects, &EffectsHandler::hasActiveFullScreenEffectChanged); + QSignalSpy isActiveFullScreenEffectSpy(effectMain, &ScriptedEffect::isActiveFullScreenEffectChanged); + + 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")); + QSignalSpy isActiveFullScreenEffectSpyOther(effectOther, &ScriptedEffect::isActiveFullScreenEffectChanged); + + 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(effects->hasActiveFullScreenEffect(), false); + QCOMPARE(effectMain->isActiveFullScreenEffect(), false); + + //trigger animation + KWin::VirtualDesktopManager::self()->setCurrent(2); + + QCOMPARE(effects->activeFullScreenEffect(), effectMain); + QCOMPARE(effects->hasActiveFullScreenEffect(), true); + QCOMPARE(fullScreenEffectActiveSpy.count(), 1); + + QCOMPARE(effectMain->isActiveFullScreenEffect(), true); + QCOMPARE(isActiveFullScreenEffectSpy.count(), 1); + + QCOMPARE(effectOther->isActiveFullScreenEffect(), false); + QCOMPARE(isActiveFullScreenEffectSpyOther.count(), 0); + + //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,20 @@ bool valid; }; +/** + * Wraps effects->setActiveFullScreenEffect for the duration of it's lifespan + */ +class FullScreenEffectLock +{ +public: + FullScreenEffectLock(Effect *effect); + ~FullScreenEffectLock(); +private: + Q_DISABLE_COPY(FullScreenEffectLock) + void *d; //unused currently +}; +typedef QSharedPointer FullScreenEffectLockPtr; + class AniData; class AnimationEffectPrivate; class KWINEFFECTS_EXPORT AnimationEffect : public Effect @@ -160,19 +174,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 +226,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::create(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/libkwineffects/kwineffects.h b/libkwineffects/kwineffects.h --- a/libkwineffects/kwineffects.h +++ b/libkwineffects/kwineffects.h @@ -825,6 +825,7 @@ Q_PROPERTY(QPoint cursorPos READ cursorPos) Q_PROPERTY(QSize virtualScreenSize READ virtualScreenSize NOTIFY virtualScreenSizeChanged) Q_PROPERTY(QRect virtualScreenGeometry READ virtualScreenGeometry NOTIFY virtualScreenGeometryChanged) + Q_PROPERTY(bool hasActiveFullscreenEffect READ hasActiveFullScreenEffect NOTIFY hasActiveFullScreenEffectChanged) friend class Effect; public: explicit EffectsHandler(CompositingType type); @@ -1322,6 +1323,11 @@ **/ virtual KSharedConfigPtr inputConfig() const = 0; + /** + * Returns if activeFullScreenEffect is set + */ + virtual bool hasActiveFullScreenEffect() const = 0; + Q_SIGNALS: /** * Signal emitted when the current desktop changed. @@ -1713,6 +1719,16 @@ **/ void activeFullScreenEffectChanged(); + /** + * This signal is emitted when active fullscreen effect changed to being + * set or unset + * + * @see activeFullScreenEffect + * @see setActiveFullScreenEffect + * @since 5.15 + **/ + void hasActiveFullScreenEffectChanged(); + protected: QVector< EffectPair > loaded_effects; //QHash< QString, EffectFactory* > effect_factories; diff --git a/scripting/scriptedeffect.h b/scripting/scriptedeffect.h --- a/scripting/scriptedeffect.h +++ b/scripting/scriptedeffect.h @@ -38,6 +38,10 @@ Q_ENUMS(Anchor) Q_ENUMS(MetaType) Q_ENUMS(EasingCurve) + /** + * True if we are the active fullscreen effect + */ + Q_PROPERTY(bool isActiveFullScreenEffect READ isActiveFullScreenEffect NOTIFY isActiveFullScreenEffectChanged) public: // copied from kwineffects.h enum DataRole { @@ -91,13 +95,15 @@ return m_screenEdgeCallbacks; } + bool isActiveFullScreenEffect() 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); @@ -108,6 +114,7 @@ **/ void configChanged(); void animationEnded(KWin::EffectWindow *w, quint64 animationId); + void isActiveFullScreenEffectChanged(); protected: ScriptedEffect(); @@ -127,6 +134,7 @@ KConfigLoader *m_config; int m_chainPosition; QHash m_touchScreenEdgeCallbacks; + Effect *m_activeFullScreenEffect = nullptr; }; } 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,18 @@ , m_config(nullptr) , m_chainPosition(0) { + Q_ASSERT(effects); connect(m_engine, SIGNAL(signalHandlerException(QScriptValue)), SLOT(signalHandlerException(QScriptValue))); + connect(effects, &EffectsHandler::activeFullScreenEffectChanged, this, [this]() { + Effect* fullScreenEffect = effects->activeFullScreenEffect(); + if (fullScreenEffect == m_activeFullScreenEffect) { + return; + } + if (m_activeFullScreenEffect == this || fullScreenEffect == this) { + emit isActiveFullScreenEffectChanged(); + } + m_activeFullScreenEffect = fullScreenEffect; + }); } ScriptedEffect::~ScriptedEffect() @@ -567,6 +588,11 @@ emit animationEnded(w, 0); } +bool ScriptedEffect::isActiveFullScreenEffect() const +{ + return effects->activeFullScreenEffect() == this; +} + void ScriptedEffect::signalHandlerException(const QScriptValue &value) { if (value.isError()) { @@ -581,24 +607,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)