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