From 99b33e7428d14d9b24689e8cfb23092bf18820c8 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Wed, 3 Oct 2018 03:11:59 +0300 Subject: [PATCH] [libkwineffects] Expose getting/setting activeFullScript to scripted effects Summary: Getter is exposed as a property on scripted effect in a way that hides pointers from the scripting side. Setter is implicitly handled as a property of newly created animations and holds the activeFullScreenEffect whilst any of them are active. Like existing effects it remains up to the effect author to avoid the problems of multiple full screen effects. The RAII lock pattern is somewhat overkill currently, but it's the direction I hope we can take EffectsHandler in next API break. BUG: 396790 -- This patch is against the QJSEngine port, though it's not conceptually a requirement. Test Plan: Unit test Reviewers: #kwin, zzag Reviewed By: #kwin, zzag Subscribers: zzag, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D14688 --- .../effects/scripted_effects_test.cpp | 70 +++++++++++++++++++ .../effects/scripts/fullScreenEffectTest.js | 8 +++ .../scripts/fullScreenEffectTestMulti.js | 22 ++++++ autotests/mock_effectshandler.h | 3 + effects.cpp | 9 +++ effects.h | 1 + libkwineffects/anidata.cpp | 4 +- libkwineffects/anidata_p.h | 3 +- libkwineffects/kwinanimationeffect.cpp | 28 ++++++-- libkwineffects/kwinanimationeffect.h | 27 +++++-- libkwineffects/kwineffects.h | 16 +++++ scripting/scriptedeffect.cpp | 38 ++++++++-- scripting/scriptedeffect.h | 12 +++- 13 files changed, 221 insertions(+), 20 deletions(-) create mode 100644 autotests/integration/effects/scripts/fullScreenEffectTest.js create mode 100644 autotests/integration/effects/scripts/fullScreenEffectTestMulti.js diff --git a/autotests/integration/effects/scripted_effects_test.cpp b/autotests/integration/effects/scripted_effects_test.cpp index 45083c51d7..97a9313a23 100644 --- a/autotests/integration/effects/scripted_effects_test.cpp +++ b/autotests/integration/effects/scripted_effects_test.cpp @@ -66,6 +66,8 @@ private Q_SLOTS: void testAnimations(); void testScreenEdge(); void testScreenEdgeTouch(); + void testFullScreenEffect_data(); + void testFullScreenEffect(); private: ScriptedEffect *loadEffect(const QString &name); }; @@ -172,6 +174,7 @@ void ScriptedEffectsTest::cleanup() e->unloadEffect(effect); QVERIFY(!e->isEffectLoaded(effect)); } + KWin::VirtualDesktopManager::self()->setCurrent(1); } void ScriptedEffectsTest::testEffectsHandler() @@ -351,5 +354,72 @@ void ScriptedEffectsTest::testScreenEdgeTouch() 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 index 0000000000..014ef47c81 --- /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.cpp b/libkwineffects/kwinanimationeffect.cpp index 092eb732aa..5fc1d03f90 100644 --- a/libkwineffects/kwinanimationeffect.cpp +++ b/libkwineffects/kwinanimationeffect.cpp @@ -45,9 +45,10 @@ public: } 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 @@ 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 ) +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 @@ quint64 AnimationEffect::p_animate( EffectWindow *w, Attribute a, uint meta, int 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 @@ AnimationEffect::AniMap AnimationEffect::state() const return d->m_animations; } +FullScreenEffectLock::FullScreenEffectLock(Effect *effect) +{ + effects->setActiveFullScreenEffect(effect); +} + +FullScreenEffectLock::~FullScreenEffectLock() +{ + effects->setActiveFullScreenEffect(nullptr); +} #include "moc_kwinanimationeffect.cpp" diff --git a/libkwineffects/kwinanimationeffect.h b/libkwineffects/kwinanimationeffect.h index deef3c2027..4736dc5be9 100644 --- a/libkwineffects/kwinanimationeffect.h +++ b/libkwineffects/kwinanimationeffect.h @@ -88,6 +88,20 @@ private: 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 @@ protected: * @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 @@ 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 ); + 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/kwineffects.h b/libkwineffects/kwineffects.h index a439132ccc..9e69106822 100644 --- a/libkwineffects/kwineffects.h +++ b/libkwineffects/kwineffects.h @@ -825,6 +825,7 @@ class KWINEFFECTS_EXPORT EffectsHandler : public QObject 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 @@ public: **/ 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 @@ Q_SIGNALS: **/ 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.cpp b/scripting/scriptedeffect.cpp index ca32b1cacb..99f7123f0b 100644 --- a/scripting/scriptedeffect.cpp +++ b/scripting/scriptedeffect.cpp @@ -103,7 +103,7 @@ QScriptValue kwinUnregisterTouchScreenEdge(QScriptContext *context, QScriptEngin } 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; @@ -112,6 +112,7 @@ struct AnimationSettings { uint duration; uint set; uint metaData; + bool fullScreenEffect; }; AnimationSettings animationSettingsFromObject(QScriptValue &object) @@ -155,6 +156,14 @@ AnimationSettings animationSettingsFromObject(QScriptValue &object) 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 @@ QScriptValue kwinEffectAnimate(QScriptContext *context, QScriptEngine *engine) setting.from, setting.metaData, setting.curve, - setting.delay)); + setting.delay, + setting.fullScreenEffect)); ++i; } return array; @@ -475,7 +485,18 @@ ScriptedEffect::ScriptedEffect() , 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 @@ void ScriptedEffect::animationEnded(KWin::EffectWindow *w, Attribute a, uint met 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 @@ 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) +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) diff --git a/scripting/scriptedeffect.h b/scripting/scriptedeffect.h index 4f6674218a..8a30d0cded 100644 --- a/scripting/scriptedeffect.h +++ b/scripting/scriptedeffect.h @@ -38,6 +38,10 @@ class KWIN_EXPORT ScriptedEffect : public KWin::AnimationEffect 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 @@ public: 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 @@ Q_SIGNALS: **/ void configChanged(); void animationEnded(KWin::EffectWindow *w, quint64 animationId); + void isActiveFullScreenEffectChanged(); protected: ScriptedEffect(); @@ -127,6 +134,7 @@ private: KConfigLoader *m_config; int m_chainPosition; QHash m_touchScreenEdgeCallbacks; + Effect *m_activeFullScreenEffect = nullptr; }; }