1 /*
2     KWin - the KDE window manager
3     This file is part of the KDE project.
4 
5     SPDX-FileCopyrightText: 2012 Martin Gräßlin <mgraesslin@kde.org>
6     SPDX-FileCopyrightText: 2018 David Edmundson <davidedmundson@kde.org>
7 
8     SPDX-License-Identifier: GPL-2.0-or-later
9 */
10 
11 #include "scriptedeffect.h"
12 #include "scriptingutils.h"
13 #include "workspace_wrapper.h"
14 #include "scripting_logging.h"
15 
16 #include "input.h"
17 #include "screenedge.h"
18 #include "screens.h"
19 // KDE
20 #include <KConfigGroup>
21 #include <kconfigloader.h>
22 #include <KGlobalAccel>
23 #include <KPluginMetaData>
24 // Qt
25 #include <QAction>
26 #include <QFile>
27 #include <QQmlEngine>
28 #include <QStandardPaths>
29 
30 Q_DECLARE_METATYPE(KSharedConfigPtr)
31 
32 namespace KWin
33 {
34 
35 struct AnimationSettings {
36     enum {
37         Type       = 1<<0,
38         Curve      = 1<<1,
39         Delay      = 1<<2,
40         Duration   = 1<<3,
41         FullScreen = 1<<4,
42         KeepAlive  = 1<<5
43     };
44     AnimationEffect::Attribute type;
45     QEasingCurve::Type curve;
46     QJSValue from;
47     QJSValue to;
48     int delay;
49     uint duration;
50     uint set;
51     uint metaData;
52     bool fullScreenEffect;
53     bool keepAlive;
54 };
55 
animationSettingsFromObject(const QJSValue & object)56 AnimationSettings animationSettingsFromObject(const QJSValue &object)
57 {
58     AnimationSettings settings;
59     settings.set = 0;
60     settings.metaData = 0;
61 
62     settings.to = object.property(QStringLiteral("to"));
63     settings.from = object.property(QStringLiteral("from"));
64 
65     const QJSValue duration = object.property(QStringLiteral("duration"));
66     if (duration.isNumber()) {
67         settings.duration = duration.toUInt();
68         settings.set |= AnimationSettings::Duration;
69     } else {
70         settings.duration = 0;
71     }
72 
73     const QJSValue delay = object.property(QStringLiteral("delay"));
74     if (delay.isNumber()) {
75         settings.delay = delay.toInt();
76         settings.set |= AnimationSettings::Delay;
77     } else {
78         settings.delay = 0;
79     }
80 
81     const QJSValue curve = object.property(QStringLiteral("curve"));
82     if (curve.isNumber()) {
83         settings.curve = static_cast<QEasingCurve::Type>(curve.toInt());
84         settings.set |= AnimationSettings::Curve;
85     } else {
86         settings.curve = QEasingCurve::Linear;
87     }
88 
89     const QJSValue type = object.property(QStringLiteral("type"));
90     if (type.isNumber()) {
91         settings.type = static_cast<AnimationEffect::Attribute>(type.toInt());
92         settings.set |= AnimationSettings::Type;
93     } else {
94         settings.type = static_cast<AnimationEffect::Attribute>(-1);
95     }
96 
97     const QJSValue isFullScreen = object.property(QStringLiteral("fullScreen"));
98     if (isFullScreen.isBool()) {
99         settings.fullScreenEffect = isFullScreen.toBool();
100         settings.set |= AnimationSettings::FullScreen;
101     } else {
102         settings.fullScreenEffect = false;
103     }
104 
105     const QJSValue keepAlive = object.property(QStringLiteral("keepAlive"));
106     if (keepAlive.isBool()) {
107         settings.keepAlive = keepAlive.toBool();
108         settings.set |= AnimationSettings::KeepAlive;
109     } else {
110         settings.keepAlive = true;
111     }
112 
113     return settings;
114 }
115 
fpx2FromScriptValue(const QJSValue & value)116 static KWin::FPx2 fpx2FromScriptValue(const QJSValue &value)
117 {
118     if (value.isNull()) {
119         return FPx2();
120     }
121     if (value.isNumber()) {
122         return FPx2(value.toNumber());
123     }
124     if (value.isObject()) {
125         const QJSValue value1 = value.property(QStringLiteral("value1"));
126         const QJSValue value2 = value.property(QStringLiteral("value2"));
127         if (!value1.isNumber() || !value2.isNumber()) {
128             qCDebug(KWIN_SCRIPTING) << "Cannot cast scripted FPx2 to C++";
129             return FPx2();
130         }
131         return FPx2(value1.toNumber(), value2.toNumber());
132     }
133     return FPx2();
134 }
135 
create(const KPluginMetaData & effect)136 ScriptedEffect *ScriptedEffect::create(const KPluginMetaData &effect)
137 {
138     const QString name = effect.pluginId();
139     const QString scriptName = effect.value(QStringLiteral("X-Plasma-MainScript"));
140     if (scriptName.isEmpty()) {
141         qCDebug(KWIN_SCRIPTING) << "X-Plasma-MainScript not set";
142         return nullptr;
143     }
144     const QString scriptFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
145                                                       QLatin1String(KWIN_NAME "/effects/") + name + QLatin1String("/contents/") + scriptName);
146     if (scriptFile.isNull()) {
147         qCDebug(KWIN_SCRIPTING) << "Could not locate the effect script";
148         return nullptr;
149     }
150     return ScriptedEffect::create(name, scriptFile, effect.value(QStringLiteral("X-KDE-Ordering")).toInt());
151 }
152 
create(const QString & effectName,const QString & pathToScript,int chainPosition)153 ScriptedEffect *ScriptedEffect::create(const QString& effectName, const QString& pathToScript, int chainPosition)
154 {
155     ScriptedEffect *effect = new ScriptedEffect();
156     if (!effect->init(effectName, pathToScript)) {
157         delete effect;
158         return nullptr;
159     }
160     effect->m_chainPosition = chainPosition;
161     return effect;
162 }
163 
supported()164 bool ScriptedEffect::supported()
165 {
166     return effects->animationsSupported();
167 }
168 
ScriptedEffect()169 ScriptedEffect::ScriptedEffect()
170     : AnimationEffect()
171     , m_engine(new QJSEngine(this))
172     , m_scriptFile(QString())
173     , m_config(nullptr)
174     , m_chainPosition(0)
175 {
176     Q_ASSERT(effects);
177     connect(effects, &EffectsHandler::activeFullScreenEffectChanged, this, [this]() {
178         Effect* fullScreenEffect = effects->activeFullScreenEffect();
179         if (fullScreenEffect == m_activeFullScreenEffect) {
180             return;
181         }
182         if (m_activeFullScreenEffect == this || fullScreenEffect == this) {
183             Q_EMIT isActiveFullScreenEffectChanged();
184         }
185         m_activeFullScreenEffect = fullScreenEffect;
186     });
187 }
188 
~ScriptedEffect()189 ScriptedEffect::~ScriptedEffect()
190 {
191 }
192 
init(const QString & effectName,const QString & pathToScript)193 bool ScriptedEffect::init(const QString &effectName, const QString &pathToScript)
194 {
195     qRegisterMetaType<QJSValueList>();
196     qRegisterMetaType<EffectWindowList>();
197 
198     QFile scriptFile(pathToScript);
199     if (!scriptFile.open(QIODevice::ReadOnly)) {
200         qCDebug(KWIN_SCRIPTING) << "Could not open script file: " << pathToScript;
201         return false;
202     }
203     m_effectName = effectName;
204     m_scriptFile = pathToScript;
205 
206     // does the effect contain an KConfigXT file?
207     const QString kconfigXTFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String(KWIN_NAME "/effects/") + m_effectName + QLatin1String("/contents/config/main.xml"));
208     if (!kconfigXTFile.isNull()) {
209         KConfigGroup cg = QCoreApplication::instance()->property("config").value<KSharedConfigPtr>()->group(QStringLiteral("Effect-%1").arg(m_effectName));
210         QFile xmlFile(kconfigXTFile);
211         m_config = new KConfigLoader(cg, &xmlFile, this);
212         m_config->load();
213     }
214 
215     m_engine->installExtensions(QJSEngine::ConsoleExtension);
216 
217     QJSValue globalObject = m_engine->globalObject();
218 
219     QJSValue effectsObject = m_engine->newQObject(effects);
220     QQmlEngine::setObjectOwnership(effects, QQmlEngine::CppOwnership);
221     globalObject.setProperty(QStringLiteral("effects"), effectsObject);
222 
223     QJSValue selfObject = m_engine->newQObject(this);
224     QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
225     globalObject.setProperty(QStringLiteral("effect"), selfObject);
226 
227     // desktopChanged is overloaded, which is problematic. Old code exposed the signal also
228     // with parameters. QJSEngine does not so we have to fake it.
229     effectsObject.setProperty(QStringLiteral("desktopChanged(int,int)"),
230                               effectsObject.property(QStringLiteral("desktopChangedLegacy")));
231     effectsObject.setProperty(QStringLiteral("desktopChanged(int,int,KWin::EffectWindow*)"),
232                               effectsObject.property(QStringLiteral("desktopChanged")));
233 
234     globalObject.setProperty(QStringLiteral("Effect"),
235                              m_engine->newQMetaObject(&ScriptedEffect::staticMetaObject));
236 #ifndef KWIN_UNIT_TEST
237     globalObject.setProperty(QStringLiteral("KWin"),
238                              m_engine->newQMetaObject(&QtScriptWorkspaceWrapper::staticMetaObject));
239 #endif
240     globalObject.setProperty(QStringLiteral("Globals"),
241                              m_engine->newQMetaObject(&KWin::staticMetaObject));
242     globalObject.setProperty(QStringLiteral("QEasingCurve"),
243                              m_engine->newQMetaObject(&QEasingCurve::staticMetaObject));
244 
245     static const QStringList globalProperties {
246         QStringLiteral("animationTime"),
247         QStringLiteral("displayWidth"),
248         QStringLiteral("displayHeight"),
249 
250         QStringLiteral("registerShortcut"),
251         QStringLiteral("registerScreenEdge"),
252         QStringLiteral("registerTouchScreenEdge"),
253         QStringLiteral("unregisterScreenEdge"),
254         QStringLiteral("unregisterTouchScreenEdge"),
255 
256         QStringLiteral("animate"),
257         QStringLiteral("set"),
258         QStringLiteral("retarget"),
259         QStringLiteral("redirect"),
260         QStringLiteral("complete"),
261         QStringLiteral("cancel"),
262     };
263 
264     for (const QString &propertyName : globalProperties) {
265         globalObject.setProperty(propertyName, selfObject.property(propertyName));
266     }
267 
268     const QJSValue result = m_engine->evaluate(QString::fromUtf8(scriptFile.readAll()));
269 
270     if (result.isError()) {
271         qCWarning(KWIN_SCRIPTING, "%s:%d: error: %s", qPrintable(scriptFile.fileName()),
272                   result.property(QStringLiteral("lineNumber")).toInt(),
273                   qPrintable(result.property(QStringLiteral("message")).toString()));
274         return false;
275     }
276 
277     return true;
278 }
279 
animationEnded(KWin::EffectWindow * w,Attribute a,uint meta)280 void ScriptedEffect::animationEnded(KWin::EffectWindow *w, Attribute a, uint meta)
281 {
282     AnimationEffect::animationEnded(w, a, meta);
283     Q_EMIT animationEnded(w, 0);
284 }
285 
pluginId() const286 QString ScriptedEffect::pluginId() const
287 {
288     return m_effectName;
289 }
290 
isActiveFullScreenEffect() const291 bool ScriptedEffect::isActiveFullScreenEffect() const
292 {
293     return effects->activeFullScreenEffect() == this;
294 }
295 
animate_helper(const QJSValue & object,AnimationType animationType)296 QJSValue ScriptedEffect::animate_helper(const QJSValue &object, AnimationType animationType)
297 {
298     QJSValue windowProperty = object.property(QStringLiteral("window"));
299     if (!windowProperty.isObject()) {
300         m_engine->throwError(QStringLiteral("Window property missing in animation options"));
301         return QJSValue();
302     }
303 
304     EffectWindow *window = qobject_cast<EffectWindow *>(windowProperty.toQObject());
305     if (!window) {
306         m_engine->throwError(QStringLiteral("Window property references invalid window"));
307         return QJSValue();
308     }
309 
310     QVector<AnimationSettings> settings{animationSettingsFromObject(object)}; // global
311 
312     QJSValue animations = object.property(QStringLiteral("animations")); // array
313     if (!animations.isUndefined()) {
314         if (!animations.isArray()) {
315             m_engine->throwError(QStringLiteral("Animations provided but not an array"));
316             return QJSValue();
317         }
318 
319         const int length = static_cast<int>(animations.property(QStringLiteral("length")).toInt());
320         for (int i = 0; i < length; ++i) {
321             QJSValue value = animations.property(QString::number(i));
322             if (value.isObject()) {
323                 AnimationSettings s = animationSettingsFromObject(value);
324                 const uint set = s.set | settings.at(0).set;
325                 // Catch show stoppers (incompletable animation)
326                 if (!(set & AnimationSettings::Type)) {
327                     m_engine->throwError(QStringLiteral("Type property missing in animation options"));
328                     return QJSValue();
329                 }
330                 if (!(set & AnimationSettings::Duration)) {
331                     m_engine->throwError(QStringLiteral("Duration property missing in animation options"));
332                     return QJSValue();
333                 }
334                 // Complete local animations from global settings
335                 if (!(s.set & AnimationSettings::Duration)) {
336                     s.duration = settings.at(0).duration;
337                 }
338                 if (!(s.set & AnimationSettings::Curve)) {
339                     s.curve = settings.at(0).curve;
340                 }
341                 if (!(s.set & AnimationSettings::Delay)) {
342                     s.delay = settings.at(0).delay;
343                 }
344                 if (!(s.set & AnimationSettings::FullScreen)) {
345                     s.fullScreenEffect = settings.at(0).fullScreenEffect;
346                 }
347                 if (!(s.set & AnimationSettings::KeepAlive)) {
348                     s.keepAlive = settings.at(0).keepAlive;
349                 }
350 
351                 s.metaData = 0;
352                 typedef QMap<AnimationEffect::MetaType, QString> MetaTypeMap;
353                 static MetaTypeMap metaTypes({
354                     {AnimationEffect::SourceAnchor, QStringLiteral("sourceAnchor")},
355                     {AnimationEffect::TargetAnchor, QStringLiteral("targetAnchor")},
356                     {AnimationEffect::RelativeSourceX, QStringLiteral("relativeSourceX")},
357                     {AnimationEffect::RelativeSourceY, QStringLiteral("relativeSourceY")},
358                     {AnimationEffect::RelativeTargetX, QStringLiteral("relativeTargetX")},
359                     {AnimationEffect::RelativeTargetY, QStringLiteral("relativeTargetY")},
360                     {AnimationEffect::Axis, QStringLiteral("axis")}
361                 });
362 
363                 for (auto it = metaTypes.constBegin(),
364                      end = metaTypes.constEnd(); it != end; ++it) {
365                     QJSValue metaVal = value.property(*it);
366                     if (metaVal.isNumber()) {
367                         AnimationEffect::setMetaData(it.key(), metaVal.toInt(), s.metaData);
368                     }
369                 }
370 
371                 settings << s;
372             }
373         }
374     }
375 
376     if (settings.count() == 1) {
377         const uint set = settings.at(0).set;
378         if (!(set & AnimationSettings::Type)) {
379             m_engine->throwError(QStringLiteral("Type property missing in animation options"));
380             return QJSValue();
381         }
382         if (!(set & AnimationSettings::Duration)) {
383             m_engine->throwError(QStringLiteral("Duration property missing in animation options"));
384             return QJSValue();
385         }
386     } else if (!(settings.at(0).set & AnimationSettings::Type)) { // invalid global
387         settings.removeAt(0); // -> get rid of it, only used to complete the others
388     }
389 
390     if (settings.isEmpty()) {
391         m_engine->throwError(QStringLiteral("No animations provided"));
392         return QJSValue();
393     }
394 
395     QJSValue array = m_engine->newArray(settings.length());
396     for (int i = 0; i < settings.count(); i++) {
397         const AnimationSettings &setting = settings[i];
398         int animationId;
399         if (animationType == AnimationType::Set) {
400             animationId = set(window,
401                               setting.type,
402                               setting.duration,
403                               setting.to,
404                               setting.from,
405                               setting.metaData,
406                               setting.curve,
407                               setting.delay,
408                               setting.fullScreenEffect,
409                               setting.keepAlive);
410         } else {
411             animationId = animate(window,
412                                   setting.type,
413                                   setting.duration,
414                                   setting.to,
415                                   setting.from,
416                                   setting.metaData,
417                                   setting.curve,
418                                   setting.delay,
419                                   setting.fullScreenEffect,
420                                   setting.keepAlive);
421         }
422         array.setProperty(i, animationId);
423     }
424 
425     return array;
426 }
427 
animate(KWin::EffectWindow * window,KWin::AnimationEffect::Attribute attribute,int ms,const QJSValue & to,const QJSValue & from,uint metaData,int curve,int delay,bool fullScreen,bool keepAlive)428 quint64 ScriptedEffect::animate(KWin::EffectWindow *window, KWin::AnimationEffect::Attribute attribute,
429                                 int ms, const QJSValue &to, const QJSValue &from, uint metaData, int curve,
430                                 int delay, bool fullScreen, bool keepAlive)
431 {
432     QEasingCurve qec;
433     if (curve < QEasingCurve::Custom)
434         qec.setType(static_cast<QEasingCurve::Type>(curve));
435     else if (curve == GaussianCurve)
436         qec.setCustomType(qecGaussian);
437     return AnimationEffect::animate(window, attribute, metaData, ms, fpx2FromScriptValue(to), qec,
438                                     delay, fpx2FromScriptValue(from), fullScreen, keepAlive);
439 }
440 
animate(const QJSValue & object)441 QJSValue ScriptedEffect::animate(const QJSValue &object)
442 {
443     return animate_helper(object, AnimationType::Animate);
444 }
445 
set(KWin::EffectWindow * window,KWin::AnimationEffect::Attribute attribute,int ms,const QJSValue & to,const QJSValue & from,uint metaData,int curve,int delay,bool fullScreen,bool keepAlive)446 quint64 ScriptedEffect::set(KWin::EffectWindow *window, KWin::AnimationEffect::Attribute attribute,
447                             int ms, const QJSValue &to, const QJSValue &from, uint metaData, int curve,
448                             int delay, bool fullScreen, bool keepAlive)
449 {
450     QEasingCurve qec;
451     if (curve < QEasingCurve::Custom)
452         qec.setType(static_cast<QEasingCurve::Type>(curve));
453     else if (curve == GaussianCurve)
454         qec.setCustomType(qecGaussian);
455     return AnimationEffect::set(window, attribute, metaData, ms, fpx2FromScriptValue(to), qec,
456                                 delay, fpx2FromScriptValue(from), fullScreen, keepAlive);
457 }
458 
set(const QJSValue & object)459 QJSValue ScriptedEffect::set(const QJSValue &object)
460 {
461     return animate_helper(object, AnimationType::Set);
462 }
463 
retarget(quint64 animationId,const QJSValue & newTarget,int newRemainingTime)464 bool ScriptedEffect::retarget(quint64 animationId, const QJSValue &newTarget, int newRemainingTime)
465 {
466     return AnimationEffect::retarget(animationId, fpx2FromScriptValue(newTarget), newRemainingTime);
467 }
468 
retarget(const QList<quint64> & animationIds,const QJSValue & newTarget,int newRemainingTime)469 bool ScriptedEffect::retarget(const QList<quint64> &animationIds, const QJSValue &newTarget, int newRemainingTime)
470 {
471     return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
472         return retarget(animationId, newTarget, newRemainingTime);
473     });
474 }
475 
redirect(quint64 animationId,Direction direction,TerminationFlags terminationFlags)476 bool ScriptedEffect::redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags)
477 {
478     return AnimationEffect::redirect(animationId, direction, terminationFlags);
479 }
480 
redirect(const QList<quint64> & animationIds,Direction direction,TerminationFlags terminationFlags)481 bool ScriptedEffect::redirect(const QList<quint64> &animationIds, Direction direction, TerminationFlags terminationFlags)
482 {
483     return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
484         return redirect(animationId, direction, terminationFlags);
485     });
486 }
487 
complete(quint64 animationId)488 bool ScriptedEffect::complete(quint64 animationId)
489 {
490     return AnimationEffect::complete(animationId);
491 }
492 
complete(const QList<quint64> & animationIds)493 bool ScriptedEffect::complete(const QList<quint64> &animationIds)
494 {
495     return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
496         return complete(animationId);
497     });
498 }
499 
cancel(quint64 animationId)500 bool ScriptedEffect::cancel(quint64 animationId)
501 {
502     return AnimationEffect::cancel(animationId);
503 }
504 
cancel(const QList<quint64> & animationIds)505 bool ScriptedEffect::cancel(const QList<quint64> &animationIds)
506 {
507     bool ret = false;
508     for (const quint64 &animationId : animationIds) {
509         ret |= cancel(animationId);
510     }
511     return ret;
512 }
513 
isGrabbed(EffectWindow * w,ScriptedEffect::DataRole grabRole)514 bool ScriptedEffect::isGrabbed(EffectWindow* w, ScriptedEffect::DataRole grabRole)
515 {
516     void *e = w->data(static_cast<KWin::DataRole>(grabRole)).value<void*>();
517     if (e) {
518         return e != this;
519     } else {
520         return false;
521     }
522 }
523 
grab(EffectWindow * w,DataRole grabRole,bool force)524 bool ScriptedEffect::grab(EffectWindow *w, DataRole grabRole, bool force)
525 {
526     void *grabber = w->data(grabRole).value<void *>();
527 
528     if (grabber == this) {
529         return true;
530     }
531 
532     if (grabber != nullptr && grabber != this && !force) {
533         return false;
534     }
535 
536     w->setData(grabRole, QVariant::fromValue(static_cast<void *>(this)));
537 
538     return true;
539 }
540 
ungrab(EffectWindow * w,DataRole grabRole)541 bool ScriptedEffect::ungrab(EffectWindow *w, DataRole grabRole)
542 {
543     void *grabber = w->data(grabRole).value<void *>();
544 
545     if (grabber == nullptr) {
546         return true;
547     }
548 
549     if (grabber != this) {
550         return false;
551     }
552 
553     w->setData(grabRole, QVariant());
554 
555     return true;
556 }
557 
reconfigure(ReconfigureFlags flags)558 void ScriptedEffect::reconfigure(ReconfigureFlags flags)
559 {
560     AnimationEffect::reconfigure(flags);
561     if (m_config) {
562         m_config->read();
563     }
564     Q_EMIT configChanged();
565 }
566 
registerShortcut(const QString & objectName,const QString & text,const QString & keySequence,const QJSValue & callback)567 void ScriptedEffect::registerShortcut(const QString &objectName, const QString &text,
568                                       const QString &keySequence, const QJSValue &callback)
569 {
570     if (!callback.isCallable()) {
571         m_engine->throwError(QStringLiteral("Shortcut handler must be callable"));
572         return;
573     }
574     QAction *action = new QAction(this);
575     action->setObjectName(objectName);
576     action->setText(text);
577     const QKeySequence shortcut = QKeySequence(keySequence);
578     KGlobalAccel::self()->setShortcut(action, QList<QKeySequence>() << shortcut);
579     input()->registerShortcut(shortcut, action);
580     connect(action, &QAction::triggered, this, [this, action, callback]() {
581         QJSValue actionObject = m_engine->newQObject(action);
582         QQmlEngine::setObjectOwnership(action, QQmlEngine::CppOwnership);
583         QJSValue(callback).call(QJSValueList{actionObject});
584     });
585 }
586 
borderActivated(ElectricBorder edge)587 bool ScriptedEffect::borderActivated(ElectricBorder edge)
588 {
589     auto it = screenEdgeCallbacks().constFind(edge);
590     if (it != screenEdgeCallbacks().constEnd()) {
591         for (const QJSValue &callback : it.value()) {
592             QJSValue(callback).call();
593         }
594     }
595     return true;
596 }
597 
readConfig(const QString & key,const QJSValue & defaultValue)598 QJSValue ScriptedEffect::readConfig(const QString &key, const QJSValue &defaultValue)
599 {
600     if (!m_config) {
601         return defaultValue;
602     }
603     return m_engine->toScriptValue(m_config->property(key));
604 }
605 
displayWidth() const606 int ScriptedEffect::displayWidth() const
607 {
608     return screens()->displaySize().width();
609 }
610 
displayHeight() const611 int ScriptedEffect::displayHeight() const
612 {
613     return screens()->displaySize().height();
614 }
615 
animationTime(int defaultTime) const616 int ScriptedEffect::animationTime(int defaultTime) const
617 {
618     return Effect::animationTime(defaultTime);
619 }
620 
registerScreenEdge(int edge,const QJSValue & callback)621 bool ScriptedEffect::registerScreenEdge(int edge, const QJSValue &callback)
622 {
623     if (!callback.isCallable()) {
624         m_engine->throwError(QStringLiteral("Screen edge handler must be callable"));
625         return false;
626     }
627     auto it = screenEdgeCallbacks().find(edge);
628     if (it == screenEdgeCallbacks().end()) {
629         // not yet registered
630         ScreenEdges::self()->reserve(static_cast<KWin::ElectricBorder>(edge), this, "borderActivated");
631         screenEdgeCallbacks().insert(edge, QJSValueList{callback});
632     } else {
633         it->append(callback);
634     }
635     return true;
636 }
637 
unregisterScreenEdge(int edge)638 bool ScriptedEffect::unregisterScreenEdge(int edge)
639 {
640     auto it = screenEdgeCallbacks().find(edge);
641     if (it == screenEdgeCallbacks().end()) {
642         //not previously registered
643         return false;
644     }
645     ScreenEdges::self()->unreserve(static_cast<KWin::ElectricBorder>(edge), this);
646     screenEdgeCallbacks().erase(it);
647     return true;
648 }
649 
registerTouchScreenEdge(int edge,const QJSValue & callback)650 bool ScriptedEffect::registerTouchScreenEdge(int edge, const QJSValue &callback)
651 {
652     if (m_touchScreenEdgeCallbacks.constFind(edge) != m_touchScreenEdgeCallbacks.constEnd()) {
653         return false;
654     }
655     if (!callback.isCallable()) {
656         m_engine->throwError(QStringLiteral("Touch screen edge handler must be callable"));
657         return false;
658     }
659     QAction *action = new QAction(this);
660     connect(action, &QAction::triggered, this, [callback]() {
661         QJSValue(callback).call();
662     });
663     ScreenEdges::self()->reserveTouch(KWin::ElectricBorder(edge), action);
664     m_touchScreenEdgeCallbacks.insert(edge, action);
665     return true;
666 }
667 
unregisterTouchScreenEdge(int edge)668 bool ScriptedEffect::unregisterTouchScreenEdge(int edge)
669 {
670     auto it = m_touchScreenEdgeCallbacks.find(edge);
671     if (it == m_touchScreenEdgeCallbacks.end()) {
672         return false;
673     }
674     delete it.value();
675     m_touchScreenEdgeCallbacks.erase(it);
676     return true;
677 }
678 
engine() const679 QJSEngine *ScriptedEffect::engine() const
680 {
681     return m_engine;
682 }
683 
684 } // namespace
685