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 @@ -65,6 +65,8 @@ void testAnimations(); void testScreenEdge(); void testScreenEdgeTouch(); + void testFullScreenEffect_data(); + void testFullScreenEffect(); private: ScriptedEffect *loadEffect(const QString &name); }; @@ -162,14 +164,15 @@ e->unloadEffect(effect); QVERIFY(!e->isEffectLoaded(effect)); } + KWin::VirtualDesktopManager::self()->setCurrent(1); } void ScriptedEffectsTest::testEffectsHandler() { // this triggers and tests some of the signals in EffectHandler, which is exposed to JS as context property "effects" auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); - auto waitFor = [&effectOutputSpy, this](const QString &expected) { + auto waitFor = [&effectOutputSpy](const QString &expected) { QVERIFY(effectOutputSpy.count() == 1 || effectOutputSpy.wait()); QCOMPARE(effectOutputSpy.last().first(), expected); effectOutputSpy.clear(); @@ -338,5 +341,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 @@ -164,15 +177,22 @@ * @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); } + { return p_animate(w, a, meta, ms, to, curve, delay, from, false, false); } + + //TODO next ABI break, merge with above with an argument + quint64 animateFullScreen( 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, true); } /** * 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); } + { return p_animate(w, a, meta, ms, to, curve, delay, from, true, false); } + + quint64 setFullScreen( 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, true); } /** * this allows to alter the target (but not type or curve) of a running animation @@ -211,7 +231,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 @@ -41,7 +41,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. @@ -109,11 +117,14 @@ QHash > &screenEdgeCallbacks() { return m_screenEdgeCallbacks; } + + ActiveFullScreenEffectState fullScreenEffectState() const; + public Q_SLOTS: - int animate(KWin::EffectWindow *w, Attribute a, int ms, const QJSValue &to, const QJSValue &from = QJSValue(), uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0); + int animate(KWin::EffectWindow *w, Attribute a, int ms, const QJSValue &to, const QJSValue &from = QJSValue(), uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0, bool fullScreen = false); QJSValue animate(const QJSValue &object); - int set(KWin::EffectWindow *w, Attribute a, int ms, const QJSValue &to, const QJSValue &from = QJSValue(), uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0); + int set(KWin::EffectWindow *w, Attribute a, int ms, const QJSValue &to, const QJSValue &from = QJSValue(), uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0, bool fullScreen = false); QJSValue set(const QJSValue &object); bool retarget(int animationId, const QJSValue &newTarget, int newRemainingTime = -1); @@ -130,6 +141,7 @@ **/ void configChanged(); void animationEnded(KWin::EffectWindow *w, int animationId); + void activeFullScreenEffectChanged(); protected: ScriptedEffect(); diff --git a/scripting/scriptedeffect.cpp b/scripting/scriptedeffect.cpp --- a/scripting/scriptedeffect.cpp +++ b/scripting/scriptedeffect.cpp @@ -43,15 +43,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; QJSValue from; //should be a KFx2 QJSValue to; int delay; uint duration; uint set; uint metaData; + bool fullScreenEffect; }; AnimationSettings animationSettingsFromObject(const QJSValue &object) @@ -94,6 +95,12 @@ } else { settings.type = static_cast(-1); } + + QJSValue isFullscreen = object.property(QStringLiteral("fullScreen")); + if (isFullscreen.isBool()) { + settings.fullScreenEffect = false; + settings.set |= AnimationSettings::Fullscreen; + } return settings; } @@ -157,6 +164,8 @@ , m_config(nullptr) , m_chainPosition(0) { + Q_ASSERT(effects); + connect(effects, &EffectsHandler::activeFullScreenEffectChanged, this, &ScriptedEffect::activeFullScreenEffectChanged); } ScriptedEffect::~ScriptedEffect() @@ -246,6 +255,18 @@ } } +ScriptedEffect::ActiveFullScreenEffectState ScriptedEffect::fullScreenEffectState() const +{ + const auto *activeEffect = effects->activeFullScreenEffect(); + if (!activeEffect) { + return ScriptedEffect::InactiveFullScreenEffect; + } + if (activeEffect == this) { + return ScriptedEffect::OwnedFullScreenEffect; + } + return ScriptedEffect::ActiveFullScreenEffect; +} + int ScriptedEffect::displayHeight() const { return screens()->displaySize().height(); @@ -267,24 +288,33 @@ emit animationEnded(w, 0); } -int ScriptedEffect::animate(KWin::EffectWindow* w, KWin::AnimationEffect::Attribute a, int ms, const QJSValue &to, const QJSValue &from, uint metaData, int curve, int delay) +int ScriptedEffect::animate(KWin::EffectWindow* w, KWin::AnimationEffect::Attribute a, int ms, const QJSValue &to, const QJSValue &from, uint metaData, int curve, int delay, bool fullScreen) { QEasingCurve qec; if (curve < QEasingCurve::Custom) qec.setType(static_cast(curve)); else if (static_cast(curve) == static_cast(GaussianCurve)) qec.setCustomType(qecGaussian); - return AnimationEffect::animate(w, a, metaData, ms, fpx2FromScriptValue(to), qec, delay, fpx2FromScriptValue(from)); + + if (fullScreen) { + return AnimationEffect::animateFullScreen(w, a, metaData, ms, fpx2FromScriptValue(to), qec, delay, fpx2FromScriptValue(from)); + } else { + return AnimationEffect::animate(w, a, metaData, ms, fpx2FromScriptValue(to), qec, delay, fpx2FromScriptValue(from)); + } } -int ScriptedEffect::set(KWin::EffectWindow* w, KWin::AnimationEffect::Attribute a, int ms, const QJSValue &to, const QJSValue &from, uint metaData, int curve, int delay) +int ScriptedEffect::set(KWin::EffectWindow* w, KWin::AnimationEffect::Attribute a, int ms, const QJSValue &to, const QJSValue &from, uint metaData, int curve, int delay, bool fullScreen) { QEasingCurve qec; if (curve < QEasingCurve::Custom) qec.setType(static_cast(curve)); else if (static_cast(curve) == static_cast(GaussianCurve)) qec.setCustomType(qecGaussian); - return AnimationEffect::set(w, a, metaData, ms, fpx2FromScriptValue(to), qec, delay, fpx2FromScriptValue(from)); + if (fullScreen) { + return AnimationEffect::setFullScreen(w, a, metaData, ms, fpx2FromScriptValue(to), qec, delay, fpx2FromScriptValue(from)); + } else { + return AnimationEffect::set(w, a, metaData, ms, fpx2FromScriptValue(to), qec, delay, fpx2FromScriptValue(from)); + } } bool ScriptedEffect::retarget(int animationId, const QJSValue &newTarget, int newRemainingTime) @@ -411,16 +441,19 @@ setting.from, setting.metaData, setting.curve, - setting.delay); + setting.delay, + setting.fullScreenEffect); + } else { animationId = animate(window, setting.type, setting.duration, setting.to, setting.from, setting.metaData, setting.curve, - setting.delay); + setting.delay, + setting.fullScreenEffect); } array.setProperty(i, animationId); }