KWin
Loading...
Searching...
No Matches
scriptedeffect.cpp
Go to the documentation of this file.
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 "opengl/glshader.h"
14#include "scripting_logging.h"
15#include "workspace_wrapper.h"
16
17#include "core/output.h"
19#include "input.h"
20#include "screenedge.h"
21#include "workspace.h"
22// KDE
23#include <KConfigGroup>
24#include <KGlobalAccel>
25#include <KPluginMetaData>
26#include <kconfigloader.h>
27// Qt
28#include <QAction>
29#include <QFile>
30#include <QList>
31#include <QQmlEngine>
32#include <QStandardPaths>
33
34#include <optional>
35
36Q_DECLARE_METATYPE(KSharedConfigPtr)
37
38namespace KWin
39{
40
42{
43 enum {
44 Type = 1 << 0,
45 Curve = 1 << 1,
46 Delay = 1 << 2,
47 Duration = 1 << 3,
48 FullScreen = 1 << 4,
49 KeepAlive = 1 << 5,
50 FrozenTime = 1 << 6
51 };
53 QEasingCurve::Type curve;
54 QJSValue from;
55 QJSValue to;
56 int delay;
57 qint64 frozenTime;
59 uint set;
63 std::optional<uint> shader;
64};
65
67{
68 AnimationSettings settings;
69 settings.set = 0;
70 settings.metaData = 0;
71
72 settings.to = object.property(QStringLiteral("to"));
73 settings.from = object.property(QStringLiteral("from"));
74
75 const QJSValue duration = object.property(QStringLiteral("duration"));
76 if (duration.isNumber()) {
77 settings.duration = duration.toUInt();
79 } else {
80 settings.duration = 0;
81 }
82
83 const QJSValue delay = object.property(QStringLiteral("delay"));
84 if (delay.isNumber()) {
85 settings.delay = delay.toInt();
86 settings.set |= AnimationSettings::Delay;
87 } else {
88 settings.delay = 0;
89 }
90
91 const QJSValue curve = object.property(QStringLiteral("curve"));
92 if (curve.isNumber()) {
93 settings.curve = static_cast<QEasingCurve::Type>(curve.toInt());
94 settings.set |= AnimationSettings::Curve;
95 } else {
96 settings.curve = QEasingCurve::Linear;
97 }
98
99 const QJSValue type = object.property(QStringLiteral("type"));
100 if (type.isNumber()) {
101 settings.type = static_cast<AnimationEffect::Attribute>(type.toInt());
102 settings.set |= AnimationSettings::Type;
103 } else {
104 settings.type = static_cast<AnimationEffect::Attribute>(-1);
105 }
106
107 const QJSValue isFullScreen = object.property(QStringLiteral("fullScreen"));
108 if (isFullScreen.isBool()) {
109 settings.fullScreenEffect = isFullScreen.toBool();
111 } else {
112 settings.fullScreenEffect = false;
113 }
114
115 const QJSValue keepAlive = object.property(QStringLiteral("keepAlive"));
116 if (keepAlive.isBool()) {
117 settings.keepAlive = keepAlive.toBool();
119 } else {
120 settings.keepAlive = true;
121 }
122
123 const QJSValue frozenTime = object.property(QStringLiteral("frozenTime"));
124 if (frozenTime.isNumber()) {
125 settings.frozenTime = frozenTime.toInt();
127 } else {
128 settings.frozenTime = -1;
129 }
130
131 if (const auto shader = object.property(QStringLiteral("fragmentShader")); shader.isNumber()) {
132 settings.shader = shader.toUInt();
133 }
134
135 return settings;
136}
137
138static KWin::FPx2 fpx2FromScriptValue(const QJSValue &value)
139{
140 if (value.isNull()) {
141 return FPx2();
142 }
143 if (value.isNumber()) {
144 return FPx2(value.toNumber());
145 }
146 if (value.isObject()) {
147 const QJSValue value1 = value.property(QStringLiteral("value1"));
148 const QJSValue value2 = value.property(QStringLiteral("value2"));
149 if (!value1.isNumber() || !value2.isNumber()) {
150 qCDebug(KWIN_SCRIPTING) << "Cannot cast scripted FPx2 to C++";
151 return FPx2();
152 }
153 return FPx2(value1.toNumber(), value2.toNumber());
154 }
155 return FPx2();
156}
157
158ScriptedEffect *ScriptedEffect::create(const KPluginMetaData &effect)
159{
160 const QString name = effect.pluginId();
161 const QString scriptFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
162 QLatin1String("kwin/effects/") + name + QLatin1String("/contents/code/main.js"));
163 if (scriptFile.isEmpty()) {
164 qCDebug(KWIN_SCRIPTING) << "Could not locate effect script" << name;
165 return nullptr;
166 }
167
168 return ScriptedEffect::create(name, scriptFile, effect.value(QStringLiteral("X-KDE-Ordering"), 0), effect.value(QStringLiteral("X-KWin-Exclusive-Category")));
169}
170
171ScriptedEffect *ScriptedEffect::create(const QString &effectName, const QString &pathToScript, int chainPosition, const QString &exclusiveCategory)
172{
173 ScriptedEffect *effect = new ScriptedEffect();
174 effect->m_exclusiveCategory = exclusiveCategory;
175 if (!effect->init(effectName, pathToScript)) {
176 delete effect;
177 return nullptr;
178 }
179 effect->m_chainPosition = chainPosition;
180
181 return effect;
182}
183
188
191 , m_engine(new QJSEngine(this))
192 , m_scriptFile(QString())
193 , m_config(nullptr)
194 , m_chainPosition(0)
195{
196 Q_ASSERT(effects);
198 Effect *fullScreenEffect = effects->activeFullScreenEffect();
199 if (fullScreenEffect == m_activeFullScreenEffect) {
200 return;
201 }
202 if (m_activeFullScreenEffect == this || fullScreenEffect == this) {
204 }
205 m_activeFullScreenEffect = fullScreenEffect;
206 });
207}
208
210
211bool ScriptedEffect::init(const QString &effectName, const QString &pathToScript)
212{
213 qRegisterMetaType<QJSValueList>();
214 qRegisterMetaType<QList<KWin::EffectWindow *>>();
215
216 QFile scriptFile(pathToScript);
217 if (!scriptFile.open(QIODevice::ReadOnly)) {
218 qCDebug(KWIN_SCRIPTING) << "Could not open script file: " << pathToScript;
219 return false;
220 }
221 m_effectName = effectName;
222 m_scriptFile = pathToScript;
223
224 // does the effect contain an KConfigXT file?
225 const QString kconfigXTFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kwin/effects/") + m_effectName + QLatin1String("/contents/config/main.xml"));
226 if (!kconfigXTFile.isNull()) {
227 KConfigGroup cg = QCoreApplication::instance()->property("config").value<KSharedConfigPtr>()->group(QStringLiteral("Effect-%1").arg(m_effectName));
228 QFile xmlFile(kconfigXTFile);
229 m_config = new KConfigLoader(cg, &xmlFile, this);
230 m_config->load();
231 }
232
233 m_engine->installExtensions(QJSEngine::ConsoleExtension);
234
235 QJSValue globalObject = m_engine->globalObject();
236
237 QJSValue effectsObject = m_engine->newQObject(effects);
238 QQmlEngine::setObjectOwnership(effects, QQmlEngine::CppOwnership);
239 globalObject.setProperty(QStringLiteral("effects"), effectsObject);
240
241 QJSValue selfObject = m_engine->newQObject(this);
242 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
243 globalObject.setProperty(QStringLiteral("effect"), selfObject);
244
245 globalObject.setProperty(QStringLiteral("Effect"),
246 m_engine->newQMetaObject(&ScriptedEffect::staticMetaObject));
247 globalObject.setProperty(QStringLiteral("KWin"),
248 m_engine->newQMetaObject(&QtScriptWorkspaceWrapper::staticMetaObject));
249 globalObject.setProperty(QStringLiteral("Globals"),
250 m_engine->newQMetaObject(&KWin::staticMetaObject));
251 globalObject.setProperty(QStringLiteral("QEasingCurve"),
252 m_engine->newQMetaObject(&QEasingCurve::staticMetaObject));
253
254 static const QStringList globalProperties{
255 QStringLiteral("animationTime"),
256 QStringLiteral("displayWidth"),
257 QStringLiteral("displayHeight"),
258
259 QStringLiteral("registerShortcut"),
260 QStringLiteral("registerScreenEdge"),
261 QStringLiteral("registerRealtimeScreenEdge"),
262 QStringLiteral("registerTouchScreenEdge"),
263 QStringLiteral("unregisterScreenEdge"),
264 QStringLiteral("unregisterTouchScreenEdge"),
265
266 QStringLiteral("animate"),
267 QStringLiteral("set"),
268 QStringLiteral("retarget"),
269 QStringLiteral("freezeInTime"),
270 QStringLiteral("redirect"),
271 QStringLiteral("complete"),
272 QStringLiteral("cancel"),
273 QStringLiteral("addShader"),
274 QStringLiteral("setUniform"),
275 };
276
277 for (const QString &propertyName : globalProperties) {
278 globalObject.setProperty(propertyName, selfObject.property(propertyName));
279 }
280
281 const QJSValue result = m_engine->evaluate(QString::fromUtf8(scriptFile.readAll()));
282
283 if (result.isError()) {
284 qCWarning(KWIN_SCRIPTING, "%s:%d: error: %s", qPrintable(scriptFile.fileName()),
285 result.property(QStringLiteral("lineNumber")).toInt(),
286 qPrintable(result.property(QStringLiteral("message")).toString()));
287 return false;
288 }
289
290 return true;
291}
292
294{
296 Q_EMIT animationEnded(w, 0);
297}
298
300{
301 return m_effectName;
302}
303
305{
306 return effects->activeFullScreenEffect() == this;
307}
308
309QList<int> ScriptedEffect::touchEdgesForAction(const QString &action) const
310{
311 QList<int> ret;
312 if (m_exclusiveCategory == QStringLiteral("show-desktop") && action == QStringLiteral("show-desktop")) {
313 for (const auto b : {ElectricTop, ElectricRight, ElectricBottom, ElectricLeft}) {
315 ret.append(b);
316 }
317 }
318 return ret;
319 } else {
320 if (!m_config) {
321 return ret;
322 }
323 return m_config->property(QStringLiteral("TouchBorderActivate") + action).value<QList<int>>();
324 }
325}
326
327QJSValue ScriptedEffect::animate_helper(const QJSValue &object, AnimationType animationType)
328{
329 QJSValue windowProperty = object.property(QStringLiteral("window"));
330 if (!windowProperty.isObject()) {
331 m_engine->throwError(QStringLiteral("Window property missing in animation options"));
332 return QJSValue();
333 }
334
335 EffectWindow *window = qobject_cast<EffectWindow *>(windowProperty.toQObject());
336 if (!window) {
337 m_engine->throwError(QStringLiteral("Window property references invalid window"));
338 return QJSValue();
339 }
340
341 QList<AnimationSettings> settings{animationSettingsFromObject(object)}; // global
342
343 QJSValue animations = object.property(QStringLiteral("animations")); // array
344 if (!animations.isUndefined()) {
345 if (!animations.isArray()) {
346 m_engine->throwError(QStringLiteral("Animations provided but not an array"));
347 return QJSValue();
348 }
349
350 const int length = static_cast<int>(animations.property(QStringLiteral("length")).toInt());
351 for (int i = 0; i < length; ++i) {
352 QJSValue value = animations.property(QString::number(i));
353 if (value.isObject()) {
354 AnimationSettings s = animationSettingsFromObject(value);
355 const uint set = s.set | settings.at(0).set;
356 // Catch show stoppers (incompletable animation)
357 if (!(set & AnimationSettings::Type)) {
358 m_engine->throwError(QStringLiteral("Type property missing in animation options"));
359 return QJSValue();
360 }
362 m_engine->throwError(QStringLiteral("Duration property missing in animation options"));
363 return QJSValue();
364 }
365 // Complete local animations from global settings
366 if (!(s.set & AnimationSettings::Duration)) {
367 s.duration = settings.at(0).duration;
368 }
369 if (!(s.set & AnimationSettings::Curve)) {
370 s.curve = settings.at(0).curve;
371 }
372 if (!(s.set & AnimationSettings::Delay)) {
373 s.delay = settings.at(0).delay;
374 }
375 if (!(s.set & AnimationSettings::FullScreen)) {
376 s.fullScreenEffect = settings.at(0).fullScreenEffect;
377 }
378 if (!(s.set & AnimationSettings::KeepAlive)) {
379 s.keepAlive = settings.at(0).keepAlive;
380 }
381 if (!s.shader.has_value()) {
382 s.shader = settings.at(0).shader;
383 }
384
385 s.metaData = 0;
386 typedef QMap<AnimationEffect::MetaType, QString> MetaTypeMap;
387 static MetaTypeMap metaTypes({{AnimationEffect::SourceAnchor, QStringLiteral("sourceAnchor")},
388 {AnimationEffect::TargetAnchor, QStringLiteral("targetAnchor")},
389 {AnimationEffect::RelativeSourceX, QStringLiteral("relativeSourceX")},
390 {AnimationEffect::RelativeSourceY, QStringLiteral("relativeSourceY")},
391 {AnimationEffect::RelativeTargetX, QStringLiteral("relativeTargetX")},
392 {AnimationEffect::RelativeTargetY, QStringLiteral("relativeTargetY")},
393 {AnimationEffect::Axis, QStringLiteral("axis")}});
394
395 for (auto it = metaTypes.constBegin(),
396 end = metaTypes.constEnd();
397 it != end; ++it) {
398 QJSValue metaVal = value.property(*it);
399 if (metaVal.isNumber()) {
400 AnimationEffect::setMetaData(it.key(), metaVal.toInt(), s.metaData);
401 }
402 }
403 if (s.type == ShaderUniform && s.shader) {
404 auto uniformProperty = value.property(QStringLiteral("uniform")).toString();
405 auto shader = findShader(s.shader.value());
406 if (!shader) {
407 m_engine->throwError(QStringLiteral("Shader for given shaderId not found"));
408 return {};
409 }
411 m_engine->throwError(QStringLiteral("Failed to make OpenGL context current"));
412 return {};
413 }
414 ShaderBinder binder{shader};
415 s.metaData = shader->uniformLocation(uniformProperty.toUtf8().constData());
416 }
417
418 settings << s;
419 }
420 }
421 }
422
423 if (settings.count() == 1) {
424 const uint set = settings.at(0).set;
425 if (!(set & AnimationSettings::Type)) {
426 m_engine->throwError(QStringLiteral("Type property missing in animation options"));
427 return QJSValue();
428 }
430 m_engine->throwError(QStringLiteral("Duration property missing in animation options"));
431 return QJSValue();
432 }
433 } else if (!(settings.at(0).set & AnimationSettings::Type)) { // invalid global
434 settings.removeAt(0); // -> get rid of it, only used to complete the others
435 }
436
437 if (settings.isEmpty()) {
438 m_engine->throwError(QStringLiteral("No animations provided"));
439 return QJSValue();
440 }
441
442 QJSValue array = m_engine->newArray(settings.length());
443 for (int i = 0; i < settings.count(); i++) {
444 const AnimationSettings &setting = settings[i];
445 int animationId;
446 if (animationType == AnimationType::Set) {
447 animationId = set(window,
448 setting.type,
449 setting.duration,
450 setting.to,
451 setting.from,
452 setting.metaData,
453 setting.curve,
454 setting.delay,
455 setting.fullScreenEffect,
456 setting.keepAlive,
457 setting.shader ? setting.shader.value() : 0u);
458 if (setting.frozenTime >= 0) {
459 freezeInTime(animationId, setting.frozenTime);
460 }
461 } else {
462 animationId = animate(window,
463 setting.type,
464 setting.duration,
465 setting.to,
466 setting.from,
467 setting.metaData,
468 setting.curve,
469 setting.delay,
470 setting.fullScreenEffect,
471 setting.keepAlive,
472 setting.shader ? setting.shader.value() : 0u);
473 if (setting.frozenTime >= 0) {
474 freezeInTime(animationId, setting.frozenTime);
475 }
476 }
477 array.setProperty(i, animationId);
478 }
479
480 return array;
481}
482
484 int ms, const QJSValue &to, const QJSValue &from, uint metaData, int curve,
485 int delay, bool fullScreen, bool keepAlive, uint shaderId)
486{
487 QEasingCurve qec;
488 if (curve < QEasingCurve::Custom) {
489 qec.setType(static_cast<QEasingCurve::Type>(curve));
490 } else if (curve == GaussianCurve) {
491 qec.setCustomType(qecGaussian);
492 }
493 return AnimationEffect::animate(window, attribute, metaData, ms, fpx2FromScriptValue(to), qec,
494 delay, fpx2FromScriptValue(from), fullScreen, keepAlive, findShader(shaderId));
495}
496
497QJSValue ScriptedEffect::animate(const QJSValue &object)
498{
499 return animate_helper(object, AnimationType::Animate);
500}
501
503 int ms, const QJSValue &to, const QJSValue &from, uint metaData, int curve,
504 int delay, bool fullScreen, bool keepAlive, uint shaderId)
505{
506 QEasingCurve qec;
507 if (curve < QEasingCurve::Custom) {
508 qec.setType(static_cast<QEasingCurve::Type>(curve));
509 } else if (curve == GaussianCurve) {
510 qec.setCustomType(qecGaussian);
511 }
512 return AnimationEffect::set(window, attribute, metaData, ms, fpx2FromScriptValue(to), qec,
513 delay, fpx2FromScriptValue(from), fullScreen, keepAlive, findShader(shaderId));
514}
515
516QJSValue ScriptedEffect::set(const QJSValue &object)
517{
518 return animate_helper(object, AnimationType::Set);
519}
520
521bool ScriptedEffect::retarget(quint64 animationId, const QJSValue &newTarget, int newRemainingTime)
522{
523 return AnimationEffect::retarget(animationId, fpx2FromScriptValue(newTarget), newRemainingTime);
524}
525
526bool ScriptedEffect::retarget(const QList<quint64> &animationIds, const QJSValue &newTarget, int newRemainingTime)
527{
528 return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
529 return retarget(animationId, newTarget, newRemainingTime);
530 });
531}
532
533bool ScriptedEffect::freezeInTime(quint64 animationId, qint64 frozenTime)
534{
535 return AnimationEffect::freezeInTime(animationId, frozenTime);
536}
537
538bool ScriptedEffect::freezeInTime(const QList<quint64> &animationIds, qint64 frozenTime)
539{
540 return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
541 return AnimationEffect::freezeInTime(animationId, frozenTime);
542 });
543}
544
545bool ScriptedEffect::redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags)
546{
547 return AnimationEffect::redirect(animationId, direction, terminationFlags);
548}
549
550bool ScriptedEffect::redirect(const QList<quint64> &animationIds, Direction direction, TerminationFlags terminationFlags)
551{
552 return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
553 return redirect(animationId, direction, terminationFlags);
554 });
555}
556
557bool ScriptedEffect::complete(quint64 animationId)
558{
559 return AnimationEffect::complete(animationId);
560}
561
562bool ScriptedEffect::complete(const QList<quint64> &animationIds)
563{
564 return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
565 return complete(animationId);
566 });
567}
568
569bool ScriptedEffect::cancel(quint64 animationId)
570{
571 return AnimationEffect::cancel(animationId);
572}
573
574bool ScriptedEffect::cancel(const QList<quint64> &animationIds)
575{
576 bool ret = false;
577 for (const quint64 &animationId : animationIds) {
578 ret |= cancel(animationId);
579 }
580 return ret;
581}
582
584{
585 void *e = w->data(static_cast<KWin::DataRole>(grabRole)).value<void *>();
586 if (e) {
587 return e != this;
588 } else {
589 return false;
590 }
591}
592
593bool ScriptedEffect::grab(EffectWindow *w, DataRole grabRole, bool force)
594{
595 void *grabber = w->data(grabRole).value<void *>();
596
597 if (grabber == this) {
598 return true;
599 }
600
601 if (grabber != nullptr && grabber != this && !force) {
602 return false;
603 }
604
605 w->setData(grabRole, QVariant::fromValue(static_cast<void *>(this)));
606
607 return true;
608}
609
611{
612 void *grabber = w->data(grabRole).value<void *>();
613
614 if (grabber == nullptr) {
615 return true;
616 }
617
618 if (grabber != this) {
619 return false;
620 }
621
622 w->setData(grabRole, QVariant());
623
624 return true;
625}
626
627void ScriptedEffect::reconfigure(ReconfigureFlags flags)
628{
630 if (m_config) {
631 m_config->read();
632 }
633 Q_EMIT configChanged();
634}
635
636void ScriptedEffect::registerShortcut(const QString &objectName, const QString &text,
637 const QString &keySequence, const QJSValue &callback)
638{
639 if (!callback.isCallable()) {
640 m_engine->throwError(QStringLiteral("Shortcut handler must be callable"));
641 return;
642 }
643 QAction *action = new QAction(this);
644 action->setObjectName(objectName);
645 action->setText(text);
646 const QKeySequence shortcut = QKeySequence(keySequence);
647 KGlobalAccel::self()->setShortcut(action, QList<QKeySequence>() << shortcut);
648 connect(action, &QAction::triggered, this, [this, action, callback]() {
649 QJSValue actionObject = m_engine->newQObject(action);
650 QQmlEngine::setObjectOwnership(action, QQmlEngine::CppOwnership);
651 QJSValue(callback).call(QJSValueList{actionObject});
652 });
653}
654
656{
657 auto it = screenEdgeCallbacks().constFind(edge);
658 if (it != screenEdgeCallbacks().constEnd()) {
659 for (const QJSValue &callback : it.value()) {
660 QJSValue(callback).call();
661 }
662 }
663 return true;
664}
665
666QJSValue ScriptedEffect::readConfig(const QString &key, const QJSValue &defaultValue)
667{
668 if (!m_config) {
669 return defaultValue;
670 }
671 return m_engine->toScriptValue(m_config->property(key));
672}
673
675{
676 return workspace()->geometry().width();
677}
678
680{
681 return workspace()->geometry().height();
682}
683
684int ScriptedEffect::animationTime(int defaultTime) const
685{
686 return Effect::animationTime(defaultTime);
687}
688
689bool ScriptedEffect::registerScreenEdge(int edge, const QJSValue &callback)
690{
691 if (!callback.isCallable()) {
692 m_engine->throwError(QStringLiteral("Screen edge handler must be callable"));
693 return false;
694 }
695 auto it = screenEdgeCallbacks().find(edge);
696 if (it == screenEdgeCallbacks().end()) {
697 // not yet registered
698 workspace()->screenEdges()->reserve(static_cast<KWin::ElectricBorder>(edge), this, "borderActivated");
699 screenEdgeCallbacks().insert(edge, QJSValueList{callback});
700 } else {
701 it->append(callback);
702 }
703 return true;
704}
705
706bool ScriptedEffect::registerRealtimeScreenEdge(int edge, const QJSValue &callback)
707{
708 if (!callback.isCallable()) {
709 m_engine->throwError(QStringLiteral("Screen edge handler must be callable"));
710 return false;
711 }
712 auto it = realtimeScreenEdgeCallbacks().find(edge);
713 if (it == realtimeScreenEdgeCallbacks().end()) {
714 // not yet registered
715 realtimeScreenEdgeCallbacks().insert(edge, QJSValueList{callback});
716 auto *triggerAction = new QAction(this);
717 connect(triggerAction, &QAction::triggered, this, [this, edge]() {
718 auto it = realtimeScreenEdgeCallbacks().constFind(edge);
719 if (it != realtimeScreenEdgeCallbacks().constEnd()) {
720 for (const QJSValue &callback : it.value()) {
721 QJSValue(callback).call({edge});
722 }
723 }
724 });
725 effects->registerRealtimeTouchBorder(static_cast<KWin::ElectricBorder>(edge), triggerAction, [this](ElectricBorder border, const QPointF &deltaProgress, Output *screen) {
726 auto it = realtimeScreenEdgeCallbacks().constFind(border);
727 if (it != realtimeScreenEdgeCallbacks().constEnd()) {
728 for (const QJSValue &callback : it.value()) {
729 QJSValue delta = m_engine->newObject();
730 delta.setProperty("width", deltaProgress.x());
731 delta.setProperty("height", deltaProgress.y());
732
733 QJSValue(callback).call({border, QJSValue(delta), m_engine->newQObject(screen)});
734 }
735 }
736 });
737 } else {
738 it->append(callback);
739 }
740 return true;
741}
742
744{
745 auto it = screenEdgeCallbacks().find(edge);
746 if (it == screenEdgeCallbacks().end()) {
747 // not previously registered
748 return false;
749 }
750 workspace()->screenEdges()->unreserve(static_cast<KWin::ElectricBorder>(edge), this);
751 screenEdgeCallbacks().erase(it);
752 return true;
753}
754
755bool ScriptedEffect::registerTouchScreenEdge(int edge, const QJSValue &callback)
756{
757 if (m_touchScreenEdgeCallbacks.constFind(edge) != m_touchScreenEdgeCallbacks.constEnd()) {
758 return false;
759 }
760 if (!callback.isCallable()) {
761 m_engine->throwError(QStringLiteral("Touch screen edge handler must be callable"));
762 return false;
763 }
764 QAction *action = new QAction(this);
765 connect(action, &QAction::triggered, this, [callback]() {
766 QJSValue(callback).call();
767 });
769 m_touchScreenEdgeCallbacks.insert(edge, action);
770 return true;
771}
772
774{
775 auto it = m_touchScreenEdgeCallbacks.find(edge);
776 if (it == m_touchScreenEdgeCallbacks.end()) {
777 return false;
778 }
779 delete it.value();
780 m_touchScreenEdgeCallbacks.erase(it);
781 return true;
782}
783
784QJSEngine *ScriptedEffect::engine() const
785{
786 return m_engine;
787}
788
789uint ScriptedEffect::addFragmentShader(ShaderTrait traits, const QString &fragmentShaderFile)
790{
792 m_engine->throwError(QStringLiteral("Failed to make OpenGL context current"));
793 return 0;
794 }
795 const QString shaderDir{QLatin1String("kwin/effects/") + m_effectName + QLatin1String("/contents/shaders/")};
796 const QString fragment = fragmentShaderFile.isEmpty() ? QString{} : QStandardPaths::locate(QStandardPaths::GenericDataLocation, shaderDir + fragmentShaderFile);
797
798 auto shader = ShaderManager::instance()->generateShaderFromFile(static_cast<KWin::ShaderTraits>(int(traits)), {}, fragment);
799 if (!shader->isValid()) {
800 m_engine->throwError(QStringLiteral("Shader failed to load"));
801 // 0 is never a valid shader identifier, it's ensured the first shader gets id 1
802 return 0;
803 }
804
805 const uint shaderId{m_nextShaderId};
806 m_nextShaderId++;
807 m_shaders[shaderId] = std::move(shader);
808 return shaderId;
809}
810
811GLShader *ScriptedEffect::findShader(uint shaderId) const
812{
813 if (auto it = m_shaders.find(shaderId); it != m_shaders.end()) {
814 return it->second.get();
815 }
816 return nullptr;
817}
818
819void ScriptedEffect::setUniform(uint shaderId, const QString &name, const QJSValue &value)
820{
821 auto shader = findShader(shaderId);
822 if (!shader) {
823 m_engine->throwError(QStringLiteral("Shader for given shaderId not found"));
824 return;
825 }
827 m_engine->throwError(QStringLiteral("Failed to make OpenGL context current"));
828 return;
829 }
830 auto setColorUniform = [this, shader, name](const QColor &color) {
831 if (!color.isValid()) {
832 return;
833 }
834 if (!shader->setUniform(name.toUtf8().constData(), color)) {
835 m_engine->throwError(QStringLiteral("Failed to set uniform ") + name);
836 }
837 };
838 ShaderBinder binder{shader};
839 if (value.isString()) {
840 setColorUniform(value.toString());
841 } else if (value.isNumber()) {
842 if (!shader->setUniform(name.toUtf8().constData(), float(value.toNumber()))) {
843 m_engine->throwError(QStringLiteral("Failed to set uniform ") + name);
844 }
845 } else if (value.isArray()) {
846 const auto length = value.property(QStringLiteral("length")).toInt();
847 if (length == 2) {
848 if (!shader->setUniform(name.toUtf8().constData(), QVector2D{float(value.property(0).toNumber()), float(value.property(1).toNumber())})) {
849 m_engine->throwError(QStringLiteral("Failed to set uniform ") + name);
850 }
851 } else if (length == 3) {
852 if (!shader->setUniform(name.toUtf8().constData(), QVector3D{float(value.property(0).toNumber()), float(value.property(1).toNumber()), float(value.property(2).toNumber())})) {
853 m_engine->throwError(QStringLiteral("Failed to set uniform ") + name);
854 }
855 } else if (length == 4) {
856 if (!shader->setUniform(name.toUtf8().constData(), QVector4D{float(value.property(0).toNumber()), float(value.property(1).toNumber()), float(value.property(2).toNumber()), float(value.property(3).toNumber())})) {
857 m_engine->throwError(QStringLiteral("Failed to set uniform ") + name);
858 }
859 } else {
860 m_engine->throwError(QStringLiteral("Invalid number of elements in array"));
861 }
862 } else if (value.isVariant()) {
863 const auto variant = value.toVariant();
864 setColorUniform(variant.value<QColor>());
865 } else {
866 m_engine->throwError(QStringLiteral("Invalid value provided for uniform"));
867 }
868}
869
870} // namespace
871
872#include "moc_scriptedeffect.cpp"
quint64 set(EffectWindow *w, Attribute a, uint meta, int ms, const FPx2 &to, const QEasingCurve &curve=QEasingCurve(), int delay=0, const FPx2 &from=FPx2(), bool fullScreen=false, bool keepAlive=true, GLShader *shader=nullptr)
bool redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags=TerminateAtSource)
bool complete(quint64 animationId)
virtual void animationEnded(EffectWindow *w, Attribute a, uint meta)
static void setMetaData(MetaType type, uint value, uint &meta)
bool retarget(quint64 animationId, FPx2 newTarget, int newRemainingTime=-1)
bool cancel(quint64 animationId)
static int metaData(MetaType type, uint meta)
bool freezeInTime(quint64 animationId, qint64 frozenTime)
quint64 animate(EffectWindow *w, Attribute a, uint meta, int ms, const FPx2 &to, const QEasingCurve &curve=QEasingCurve(), int delay=0, const FPx2 &from=FPx2(), bool fullScreen=false, bool keepAlive=true, GLShader *shader=nullptr)
static qreal qecGaussian(qreal progress)
Base class for all KWin effects.
Definition effect.h:535
Representation of a window used by/for Effect classes.
Q_SCRIPTABLE void setData(int role, const QVariant &data)
Q_SCRIPTABLE QVariant data(int role) const
bool animationsSupported() const
bool makeOpenGLContextCurrent()
Makes the OpenGL compositing context current.
void registerRealtimeTouchBorder(ElectricBorder border, QAction *action, TouchBorderCallback progressCallback)
Effect * activeFullScreenEffect() const
void activeFullScreenEffectChanged()
void reserve(ElectricBorder border, QObject *object, const char *callback)
void unreserve(ElectricBorder border, QObject *object)
ElectricBorderAction actionForTouchBorder(ElectricBorder border) const
void reserveTouch(ElectricBorder border, QAction *action, TouchCallback::CallbackFunction callback=nullptr)
Q_SCRIPTABLE int displayHeight() const
Q_SCRIPTABLE bool registerRealtimeScreenEdge(int edge, const QJSValue &callback)
Q_SCRIPTABLE QList< int > touchEdgesForAction(const QString &action) const
~ScriptedEffect() override
QHash< int, QJSValueList > & realtimeScreenEdgeCallbacks()
Q_SCRIPTABLE bool unregisterScreenEdge(int edge)
Q_SCRIPTABLE bool registerScreenEdge(int edge, const QJSValue &callback)
bool init(const QString &effectName, const QString &pathToScript)
Q_SCRIPTABLE uint addFragmentShader(ShaderTrait traits, const QString &fragmentShaderFile={})
Q_SCRIPTABLE bool retarget(quint64 animationId, const QJSValue &newTarget, int newRemainingTime=-1)
Q_SCRIPTABLE bool complete(quint64 animationId)
void animationEnded(KWin::EffectWindow *w, quint64 animationId)
Q_SCRIPTABLE int animationTime(int defaultTime) const
Q_SCRIPTABLE int displayWidth() const
Q_SCRIPTABLE void setUniform(uint shaderId, const QString &name, const QJSValue &value)
void isActiveFullScreenEffectChanged()
QJSEngine * engine() const
Q_SCRIPTABLE bool ungrab(KWin::EffectWindow *w, DataRole grabRole)
Q_SCRIPTABLE bool registerTouchScreenEdge(int edge, const QJSValue &callback)
Q_SCRIPTABLE quint64 animate(KWin::EffectWindow *window, Attribute attribute, int ms, const QJSValue &to, const QJSValue &from=QJSValue(), uint metaData=0, int curve=QEasingCurve::Linear, int delay=0, bool fullScreen=false, bool keepAlive=true, uint shaderId=0)
Q_SCRIPTABLE bool grab(KWin::EffectWindow *w, DataRole grabRole, bool force=false)
void reconfigure(ReconfigureFlags flags) override
Q_SCRIPTABLE bool redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags=TerminateAtSource)
static ScriptedEffect * create(const QString &effectName, const QString &pathToScript, int chainPosition, const QString &exclusiveCategory)
Q_SCRIPTABLE bool cancel(quint64 animationId)
const QString & scriptFile() const
Q_SCRIPTABLE bool unregisterTouchScreenEdge(int edge)
bool borderActivated(ElectricBorder border) override
Q_SCRIPTABLE bool isGrabbed(KWin::EffectWindow *w, DataRole grabRole)
Q_SCRIPTABLE QJSValue readConfig(const QString &key, const QJSValue &defaultValue=QJSValue())
Q_SCRIPTABLE void registerShortcut(const QString &objectName, const QString &text, const QString &keySequence, const QJSValue &callback)
Q_SCRIPTABLE quint64 set(KWin::EffectWindow *window, Attribute attribute, int ms, const QJSValue &to, const QJSValue &from=QJSValue(), uint metaData=0, int curve=QEasingCurve::Linear, int delay=0, bool fullScreen=false, bool keepAlive=true, uint shaderId=0)
QHash< int, QJSValueList > & screenEdgeCallbacks()
Q_SCRIPTABLE bool freezeInTime(quint64 animationId, qint64 frozenTime)
std::unique_ptr< GLShader > generateShaderFromFile(ShaderTraits traits, const QString &vertexFile=QString(), const QString &fragmentFile=QString())
static ShaderManager * instance()
ScreenEdges * screenEdges() const
QRect geometry() const
virtual void reconfigure(ReconfigureFlags flags)
Definition effect.cpp:395
static double animationTime(const KConfigGroup &cfg, const QString &key, int defaultTime)
Definition effect.cpp:483
Q_DECLARE_METATYPE(KWin::SwitchEvent::State)
Session::Type type
Definition session.cpp:17
ElectricBorder
Definition globals.h:60
@ ElectricBottom
Definition globals.h:65
@ ElectricTop
Definition globals.h:61
@ ElectricRight
Definition globals.h:63
@ ElectricLeft
Definition globals.h:67
Workspace * workspace()
Definition workspace.h:830
@ ElectricActionShowDesktop
Definition globals.h:80
AnimationSettings animationSettingsFromObject(const QJSValue &object)
EffectsHandler * effects
std::optional< uint > shader
AnimationEffect::Attribute type
QEasingCurve::Type curve