[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
icc-effect-5.17.5
David Edmundson 2018-10-03 03:11:59 +03:00
parent 21c5b95529
commit 99b33e7428
13 changed files with 221 additions and 20 deletions

View File

@ -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<QString>("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"

View File

@ -0,0 +1,8 @@
effects['desktopChanged(int,int)'].connect(function(old, current) {
var stackingOrder = effects.stackingOrder;
for (var i=0; i<stackingOrder.length; i++) {
var w = stackingOrder[i];
//1 second long random animation. Final param indicates fullscreen
w.anim1 = effect.animate(w, Effect.Scale, 1000, 1.4, 0.2, 0, QEasingCurve.Linear, 0, true);
}
});

View File

@ -0,0 +1,22 @@
effects['desktopChanged(int,int)'].connect(function(old, current) {
var stackingOrder = effects.stackingOrder;
for (var i=0; i<stackingOrder.length; i++) {
var w = stackingOrder[i];
//1 second long random animation, marked as fullscreen
w.anim1 = animate({
window: w,
duration: 1000,
animations: [{
type: Effect.Scale,
curve: Effect.GaussianCurve,
to: 1.4,
fullScreen: true
}, {
type: Effect.Opacity,
curve: Effect.GaussianCurve,
to: 0.0,
fullScreen: true
}]
});
}
});

View File

@ -32,6 +32,9 @@ public:
KWin::Effect *activeFullScreenEffect() const {
return nullptr;
}
bool hasActiveFullScreenEffect() const override {
return false;
}
int activeScreen() const override {
return 0;
}

View File

@ -623,8 +623,12 @@ void EffectsHandlerImpl::setActiveFullScreenEffect(Effect* e)
if (fullscreen_effect == e) {
return;
}
const bool activeChanged = (e == nullptr || fullscreen_effect == nullptr);
fullscreen_effect = e;
emit activeFullScreenEffectChanged();
if (activeChanged) {
emit hasActiveFullScreenEffectChanged();
}
}
Effect* EffectsHandlerImpl::activeFullScreenEffect() const
@ -632,6 +636,11 @@ Effect* EffectsHandlerImpl::activeFullScreenEffect() const
return fullscreen_effect;
}
bool EffectsHandlerImpl::hasActiveFullScreenEffect() const
{
return fullscreen_effect;
}
bool EffectsHandlerImpl::grabKeyboard(Effect* effect)
{
if (keyboard_grab_effect != NULL)

View File

@ -149,6 +149,7 @@ public:
void setActiveFullScreenEffect(Effect* e) override;
Effect* activeFullScreenEffect() const override;
bool hasActiveFullScreenEffect() const override;
void addRepaintFull() override;
void addRepaint(const QRect& r) override;

View File

@ -45,7 +45,8 @@ AniData::AniData()
}
AniData::AniData(AnimationEffect::Attribute a, int meta_, int ms, const FPx2 &to_,
QEasingCurve curve_, int delay, const FPx2 &from_, bool waitAtSource_, bool keepAtTarget_ )
QEasingCurve curve_, int delay, const FPx2 &from_, bool waitAtSource_, bool keepAtTarget_,
FullScreenEffectLockPtr fullScreenEffectLock_)
: attribute(a)
, curve(curve_)
, from(from_)
@ -55,6 +56,7 @@ AniData::AniData(AnimationEffect::Attribute a, int meta_, int ms, const FPx2 &to
, meta(meta_)
, startTime(AnimationEffect::clock() + delay)
, windowType((NET::WindowTypeMask)0)
, fullScreenEffectLock(fullScreenEffectLock_)
, waitAtSource(waitAtSource_)
, keepAtTarget(keepAtTarget_)
{

View File

@ -31,7 +31,7 @@ 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);
QEasingCurve curve, int delay, const FPx2 &from, bool waitAtSource, bool keepAtTarget = false, FullScreenEffectLockPtr=FullScreenEffectLockPtr());
explicit AniData(const QString &str);
inline void addTime(int t) { time += t; }
inline bool isOneDimensional() const {
@ -50,6 +50,7 @@ public:
uint meta;
qint64 startTime;
NET::WindowTypeMask windowType;
QSharedPointer<FullScreenEffectLock> fullScreenEffectLock;
bool waitAtSource, keepAtTarget;
};

View File

@ -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<FullScreenEffectLock> 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<QList<AniData>, QRect>(QList<AniData>(), 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"

View File

@ -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<FullScreenEffectLock> 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;

View File

@ -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;

View File

@ -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<AnimationEffect::Attribute>(-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<QEasingCurve::Type>(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<QEasingCurve::Type>(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)

View File

@ -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<int, QAction*> m_touchScreenEdgeCallbacks;
Effect *m_activeFullScreenEffect = nullptr;
};
}