File indexing completed on 2024-05-12 05:32:10

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2012 Martin Gräßlin <mgraesslin@kde.org>
0006     SPDX-FileCopyrightText: 2018 David Edmundson <davidedmundson@kde.org>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "scriptedeffect.h"
0012 #include "opengl/glshader.h"
0013 #include "opengl/glshadermanager.h"
0014 #include "scripting_logging.h"
0015 #include "workspace_wrapper.h"
0016 
0017 #include "core/output.h"
0018 #include "effect/effecthandler.h"
0019 #include "input.h"
0020 #include "screenedge.h"
0021 #include "workspace.h"
0022 // KDE
0023 #include <KConfigGroup>
0024 #include <KGlobalAccel>
0025 #include <KPluginMetaData>
0026 #include <kconfigloader.h>
0027 // Qt
0028 #include <QAction>
0029 #include <QFile>
0030 #include <QList>
0031 #include <QQmlEngine>
0032 #include <QStandardPaths>
0033 
0034 #include <optional>
0035 
0036 Q_DECLARE_METATYPE(KSharedConfigPtr)
0037 
0038 namespace KWin
0039 {
0040 
0041 struct AnimationSettings
0042 {
0043     enum {
0044         Type = 1 << 0,
0045         Curve = 1 << 1,
0046         Delay = 1 << 2,
0047         Duration = 1 << 3,
0048         FullScreen = 1 << 4,
0049         KeepAlive = 1 << 5,
0050         FrozenTime = 1 << 6
0051     };
0052     AnimationEffect::Attribute type;
0053     QEasingCurve::Type curve;
0054     QJSValue from;
0055     QJSValue to;
0056     int delay;
0057     qint64 frozenTime;
0058     uint duration;
0059     uint set;
0060     uint metaData;
0061     bool fullScreenEffect;
0062     bool keepAlive;
0063     std::optional<uint> shader;
0064 };
0065 
0066 AnimationSettings animationSettingsFromObject(const QJSValue &object)
0067 {
0068     AnimationSettings settings;
0069     settings.set = 0;
0070     settings.metaData = 0;
0071 
0072     settings.to = object.property(QStringLiteral("to"));
0073     settings.from = object.property(QStringLiteral("from"));
0074 
0075     const QJSValue duration = object.property(QStringLiteral("duration"));
0076     if (duration.isNumber()) {
0077         settings.duration = duration.toUInt();
0078         settings.set |= AnimationSettings::Duration;
0079     } else {
0080         settings.duration = 0;
0081     }
0082 
0083     const QJSValue delay = object.property(QStringLiteral("delay"));
0084     if (delay.isNumber()) {
0085         settings.delay = delay.toInt();
0086         settings.set |= AnimationSettings::Delay;
0087     } else {
0088         settings.delay = 0;
0089     }
0090 
0091     const QJSValue curve = object.property(QStringLiteral("curve"));
0092     if (curve.isNumber()) {
0093         settings.curve = static_cast<QEasingCurve::Type>(curve.toInt());
0094         settings.set |= AnimationSettings::Curve;
0095     } else {
0096         settings.curve = QEasingCurve::Linear;
0097     }
0098 
0099     const QJSValue type = object.property(QStringLiteral("type"));
0100     if (type.isNumber()) {
0101         settings.type = static_cast<AnimationEffect::Attribute>(type.toInt());
0102         settings.set |= AnimationSettings::Type;
0103     } else {
0104         settings.type = static_cast<AnimationEffect::Attribute>(-1);
0105     }
0106 
0107     const QJSValue isFullScreen = object.property(QStringLiteral("fullScreen"));
0108     if (isFullScreen.isBool()) {
0109         settings.fullScreenEffect = isFullScreen.toBool();
0110         settings.set |= AnimationSettings::FullScreen;
0111     } else {
0112         settings.fullScreenEffect = false;
0113     }
0114 
0115     const QJSValue keepAlive = object.property(QStringLiteral("keepAlive"));
0116     if (keepAlive.isBool()) {
0117         settings.keepAlive = keepAlive.toBool();
0118         settings.set |= AnimationSettings::KeepAlive;
0119     } else {
0120         settings.keepAlive = true;
0121     }
0122 
0123     const QJSValue frozenTime = object.property(QStringLiteral("frozenTime"));
0124     if (frozenTime.isNumber()) {
0125         settings.frozenTime = frozenTime.toInt();
0126         settings.set |= AnimationSettings::FrozenTime;
0127     } else {
0128         settings.frozenTime = -1;
0129     }
0130 
0131     if (const auto shader = object.property(QStringLiteral("fragmentShader")); shader.isNumber()) {
0132         settings.shader = shader.toUInt();
0133     }
0134 
0135     return settings;
0136 }
0137 
0138 static KWin::FPx2 fpx2FromScriptValue(const QJSValue &value)
0139 {
0140     if (value.isNull()) {
0141         return FPx2();
0142     }
0143     if (value.isNumber()) {
0144         return FPx2(value.toNumber());
0145     }
0146     if (value.isObject()) {
0147         const QJSValue value1 = value.property(QStringLiteral("value1"));
0148         const QJSValue value2 = value.property(QStringLiteral("value2"));
0149         if (!value1.isNumber() || !value2.isNumber()) {
0150             qCDebug(KWIN_SCRIPTING) << "Cannot cast scripted FPx2 to C++";
0151             return FPx2();
0152         }
0153         return FPx2(value1.toNumber(), value2.toNumber());
0154     }
0155     return FPx2();
0156 }
0157 
0158 ScriptedEffect *ScriptedEffect::create(const KPluginMetaData &effect)
0159 {
0160     const QString name = effect.pluginId();
0161     const QString scriptFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
0162                                                       QLatin1String("kwin/effects/") + name + QLatin1String("/contents/code/main.js"));
0163     if (scriptFile.isEmpty()) {
0164         qCDebug(KWIN_SCRIPTING) << "Could not locate effect script" << name;
0165         return nullptr;
0166     }
0167 
0168     return ScriptedEffect::create(name, scriptFile, effect.value(QStringLiteral("X-KDE-Ordering"), 0), effect.value(QStringLiteral("X-KWin-Exclusive-Category")));
0169 }
0170 
0171 ScriptedEffect *ScriptedEffect::create(const QString &effectName, const QString &pathToScript, int chainPosition, const QString &exclusiveCategory)
0172 {
0173     ScriptedEffect *effect = new ScriptedEffect();
0174     effect->m_exclusiveCategory = exclusiveCategory;
0175     if (!effect->init(effectName, pathToScript)) {
0176         delete effect;
0177         return nullptr;
0178     }
0179     effect->m_chainPosition = chainPosition;
0180 
0181     return effect;
0182 }
0183 
0184 bool ScriptedEffect::supported()
0185 {
0186     return effects->animationsSupported();
0187 }
0188 
0189 ScriptedEffect::ScriptedEffect()
0190     : AnimationEffect()
0191     , m_engine(new QJSEngine(this))
0192     , m_scriptFile(QString())
0193     , m_config(nullptr)
0194     , m_chainPosition(0)
0195 {
0196     Q_ASSERT(effects);
0197     connect(effects, &EffectsHandler::activeFullScreenEffectChanged, this, [this]() {
0198         Effect *fullScreenEffect = effects->activeFullScreenEffect();
0199         if (fullScreenEffect == m_activeFullScreenEffect) {
0200             return;
0201         }
0202         if (m_activeFullScreenEffect == this || fullScreenEffect == this) {
0203             Q_EMIT isActiveFullScreenEffectChanged();
0204         }
0205         m_activeFullScreenEffect = fullScreenEffect;
0206     });
0207 }
0208 
0209 ScriptedEffect::~ScriptedEffect() = default;
0210 
0211 bool ScriptedEffect::init(const QString &effectName, const QString &pathToScript)
0212 {
0213     qRegisterMetaType<QJSValueList>();
0214     qRegisterMetaType<QList<KWin::EffectWindow *>>();
0215 
0216     QFile scriptFile(pathToScript);
0217     if (!scriptFile.open(QIODevice::ReadOnly)) {
0218         qCDebug(KWIN_SCRIPTING) << "Could not open script file: " << pathToScript;
0219         return false;
0220     }
0221     m_effectName = effectName;
0222     m_scriptFile = pathToScript;
0223 
0224     // does the effect contain an KConfigXT file?
0225     const QString kconfigXTFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kwin/effects/") + m_effectName + QLatin1String("/contents/config/main.xml"));
0226     if (!kconfigXTFile.isNull()) {
0227         KConfigGroup cg = QCoreApplication::instance()->property("config").value<KSharedConfigPtr>()->group(QStringLiteral("Effect-%1").arg(m_effectName));
0228         QFile xmlFile(kconfigXTFile);
0229         m_config = new KConfigLoader(cg, &xmlFile, this);
0230         m_config->load();
0231     }
0232 
0233     m_engine->installExtensions(QJSEngine::ConsoleExtension);
0234 
0235     QJSValue globalObject = m_engine->globalObject();
0236 
0237     QJSValue effectsObject = m_engine->newQObject(effects);
0238     QQmlEngine::setObjectOwnership(effects, QQmlEngine::CppOwnership);
0239     globalObject.setProperty(QStringLiteral("effects"), effectsObject);
0240 
0241     QJSValue selfObject = m_engine->newQObject(this);
0242     QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
0243     globalObject.setProperty(QStringLiteral("effect"), selfObject);
0244 
0245     globalObject.setProperty(QStringLiteral("Effect"),
0246                              m_engine->newQMetaObject(&ScriptedEffect::staticMetaObject));
0247     globalObject.setProperty(QStringLiteral("KWin"),
0248                              m_engine->newQMetaObject(&QtScriptWorkspaceWrapper::staticMetaObject));
0249     globalObject.setProperty(QStringLiteral("Globals"),
0250                              m_engine->newQMetaObject(&KWin::staticMetaObject));
0251     globalObject.setProperty(QStringLiteral("QEasingCurve"),
0252                              m_engine->newQMetaObject(&QEasingCurve::staticMetaObject));
0253 
0254     static const QStringList globalProperties{
0255         QStringLiteral("animationTime"),
0256         QStringLiteral("displayWidth"),
0257         QStringLiteral("displayHeight"),
0258 
0259         QStringLiteral("registerShortcut"),
0260         QStringLiteral("registerScreenEdge"),
0261         QStringLiteral("registerRealtimeScreenEdge"),
0262         QStringLiteral("registerTouchScreenEdge"),
0263         QStringLiteral("unregisterScreenEdge"),
0264         QStringLiteral("unregisterTouchScreenEdge"),
0265 
0266         QStringLiteral("animate"),
0267         QStringLiteral("set"),
0268         QStringLiteral("retarget"),
0269         QStringLiteral("freezeInTime"),
0270         QStringLiteral("redirect"),
0271         QStringLiteral("complete"),
0272         QStringLiteral("cancel"),
0273         QStringLiteral("addShader"),
0274         QStringLiteral("setUniform"),
0275     };
0276 
0277     for (const QString &propertyName : globalProperties) {
0278         globalObject.setProperty(propertyName, selfObject.property(propertyName));
0279     }
0280 
0281     const QJSValue result = m_engine->evaluate(QString::fromUtf8(scriptFile.readAll()));
0282 
0283     if (result.isError()) {
0284         qCWarning(KWIN_SCRIPTING, "%s:%d: error: %s", qPrintable(scriptFile.fileName()),
0285                   result.property(QStringLiteral("lineNumber")).toInt(),
0286                   qPrintable(result.property(QStringLiteral("message")).toString()));
0287         return false;
0288     }
0289 
0290     return true;
0291 }
0292 
0293 void ScriptedEffect::animationEnded(KWin::EffectWindow *w, Attribute a, uint meta)
0294 {
0295     AnimationEffect::animationEnded(w, a, meta);
0296     Q_EMIT animationEnded(w, 0);
0297 }
0298 
0299 QString ScriptedEffect::pluginId() const
0300 {
0301     return m_effectName;
0302 }
0303 
0304 bool ScriptedEffect::isActiveFullScreenEffect() const
0305 {
0306     return effects->activeFullScreenEffect() == this;
0307 }
0308 
0309 QList<int> ScriptedEffect::touchEdgesForAction(const QString &action) const
0310 {
0311     QList<int> ret;
0312     if (m_exclusiveCategory == QStringLiteral("show-desktop") && action == QStringLiteral("show-desktop")) {
0313         for (const auto b : {ElectricTop, ElectricRight, ElectricBottom, ElectricLeft}) {
0314             if (workspace()->screenEdges()->actionForTouchBorder(b) == ElectricActionShowDesktop) {
0315                 ret.append(b);
0316             }
0317         }
0318         return ret;
0319     } else {
0320         if (!m_config) {
0321             return ret;
0322         }
0323         return m_config->property(QStringLiteral("TouchBorderActivate") + action).value<QList<int>>();
0324     }
0325 }
0326 
0327 QJSValue ScriptedEffect::animate_helper(const QJSValue &object, AnimationType animationType)
0328 {
0329     QJSValue windowProperty = object.property(QStringLiteral("window"));
0330     if (!windowProperty.isObject()) {
0331         m_engine->throwError(QStringLiteral("Window property missing in animation options"));
0332         return QJSValue();
0333     }
0334 
0335     EffectWindow *window = qobject_cast<EffectWindow *>(windowProperty.toQObject());
0336     if (!window) {
0337         m_engine->throwError(QStringLiteral("Window property references invalid window"));
0338         return QJSValue();
0339     }
0340 
0341     QList<AnimationSettings> settings{animationSettingsFromObject(object)}; // global
0342 
0343     QJSValue animations = object.property(QStringLiteral("animations")); // array
0344     if (!animations.isUndefined()) {
0345         if (!animations.isArray()) {
0346             m_engine->throwError(QStringLiteral("Animations provided but not an array"));
0347             return QJSValue();
0348         }
0349 
0350         const int length = static_cast<int>(animations.property(QStringLiteral("length")).toInt());
0351         for (int i = 0; i < length; ++i) {
0352             QJSValue value = animations.property(QString::number(i));
0353             if (value.isObject()) {
0354                 AnimationSettings s = animationSettingsFromObject(value);
0355                 const uint set = s.set | settings.at(0).set;
0356                 // Catch show stoppers (incompletable animation)
0357                 if (!(set & AnimationSettings::Type)) {
0358                     m_engine->throwError(QStringLiteral("Type property missing in animation options"));
0359                     return QJSValue();
0360                 }
0361                 if (!(set & AnimationSettings::Duration)) {
0362                     m_engine->throwError(QStringLiteral("Duration property missing in animation options"));
0363                     return QJSValue();
0364                 }
0365                 // Complete local animations from global settings
0366                 if (!(s.set & AnimationSettings::Duration)) {
0367                     s.duration = settings.at(0).duration;
0368                 }
0369                 if (!(s.set & AnimationSettings::Curve)) {
0370                     s.curve = settings.at(0).curve;
0371                 }
0372                 if (!(s.set & AnimationSettings::Delay)) {
0373                     s.delay = settings.at(0).delay;
0374                 }
0375                 if (!(s.set & AnimationSettings::FullScreen)) {
0376                     s.fullScreenEffect = settings.at(0).fullScreenEffect;
0377                 }
0378                 if (!(s.set & AnimationSettings::KeepAlive)) {
0379                     s.keepAlive = settings.at(0).keepAlive;
0380                 }
0381                 if (!s.shader.has_value()) {
0382                     s.shader = settings.at(0).shader;
0383                 }
0384 
0385                 s.metaData = 0;
0386                 typedef QMap<AnimationEffect::MetaType, QString> MetaTypeMap;
0387                 static MetaTypeMap metaTypes({{AnimationEffect::SourceAnchor, QStringLiteral("sourceAnchor")},
0388                                               {AnimationEffect::TargetAnchor, QStringLiteral("targetAnchor")},
0389                                               {AnimationEffect::RelativeSourceX, QStringLiteral("relativeSourceX")},
0390                                               {AnimationEffect::RelativeSourceY, QStringLiteral("relativeSourceY")},
0391                                               {AnimationEffect::RelativeTargetX, QStringLiteral("relativeTargetX")},
0392                                               {AnimationEffect::RelativeTargetY, QStringLiteral("relativeTargetY")},
0393                                               {AnimationEffect::Axis, QStringLiteral("axis")}});
0394 
0395                 for (auto it = metaTypes.constBegin(),
0396                           end = metaTypes.constEnd();
0397                      it != end; ++it) {
0398                     QJSValue metaVal = value.property(*it);
0399                     if (metaVal.isNumber()) {
0400                         AnimationEffect::setMetaData(it.key(), metaVal.toInt(), s.metaData);
0401                     }
0402                 }
0403                 if (s.type == ShaderUniform && s.shader) {
0404                     auto uniformProperty = value.property(QStringLiteral("uniform")).toString();
0405                     auto shader = findShader(s.shader.value());
0406                     if (!shader) {
0407                         m_engine->throwError(QStringLiteral("Shader for given shaderId not found"));
0408                         return {};
0409                     }
0410                     if (!effects->makeOpenGLContextCurrent()) {
0411                         m_engine->throwError(QStringLiteral("Failed to make OpenGL context current"));
0412                         return {};
0413                     }
0414                     ShaderBinder binder{shader};
0415                     s.metaData = shader->uniformLocation(uniformProperty.toUtf8().constData());
0416                 }
0417 
0418                 settings << s;
0419             }
0420         }
0421     }
0422 
0423     if (settings.count() == 1) {
0424         const uint set = settings.at(0).set;
0425         if (!(set & AnimationSettings::Type)) {
0426             m_engine->throwError(QStringLiteral("Type property missing in animation options"));
0427             return QJSValue();
0428         }
0429         if (!(set & AnimationSettings::Duration)) {
0430             m_engine->throwError(QStringLiteral("Duration property missing in animation options"));
0431             return QJSValue();
0432         }
0433     } else if (!(settings.at(0).set & AnimationSettings::Type)) { // invalid global
0434         settings.removeAt(0); // -> get rid of it, only used to complete the others
0435     }
0436 
0437     if (settings.isEmpty()) {
0438         m_engine->throwError(QStringLiteral("No animations provided"));
0439         return QJSValue();
0440     }
0441 
0442     QJSValue array = m_engine->newArray(settings.length());
0443     for (int i = 0; i < settings.count(); i++) {
0444         const AnimationSettings &setting = settings[i];
0445         int animationId;
0446         if (animationType == AnimationType::Set) {
0447             animationId = set(window,
0448                               setting.type,
0449                               setting.duration,
0450                               setting.to,
0451                               setting.from,
0452                               setting.metaData,
0453                               setting.curve,
0454                               setting.delay,
0455                               setting.fullScreenEffect,
0456                               setting.keepAlive,
0457                               setting.shader ? setting.shader.value() : 0u);
0458             if (setting.frozenTime >= 0) {
0459                 freezeInTime(animationId, setting.frozenTime);
0460             }
0461         } else {
0462             animationId = animate(window,
0463                                   setting.type,
0464                                   setting.duration,
0465                                   setting.to,
0466                                   setting.from,
0467                                   setting.metaData,
0468                                   setting.curve,
0469                                   setting.delay,
0470                                   setting.fullScreenEffect,
0471                                   setting.keepAlive,
0472                                   setting.shader ? setting.shader.value() : 0u);
0473             if (setting.frozenTime >= 0) {
0474                 freezeInTime(animationId, setting.frozenTime);
0475             }
0476         }
0477         array.setProperty(i, animationId);
0478     }
0479 
0480     return array;
0481 }
0482 
0483 quint64 ScriptedEffect::animate(KWin::EffectWindow *window, KWin::AnimationEffect::Attribute attribute,
0484                                 int ms, const QJSValue &to, const QJSValue &from, uint metaData, int curve,
0485                                 int delay, bool fullScreen, bool keepAlive, uint shaderId)
0486 {
0487     QEasingCurve qec;
0488     if (curve < QEasingCurve::Custom) {
0489         qec.setType(static_cast<QEasingCurve::Type>(curve));
0490     } else if (curve == GaussianCurve) {
0491         qec.setCustomType(qecGaussian);
0492     }
0493     return AnimationEffect::animate(window, attribute, metaData, ms, fpx2FromScriptValue(to), qec,
0494                                     delay, fpx2FromScriptValue(from), fullScreen, keepAlive, findShader(shaderId));
0495 }
0496 
0497 QJSValue ScriptedEffect::animate(const QJSValue &object)
0498 {
0499     return animate_helper(object, AnimationType::Animate);
0500 }
0501 
0502 quint64 ScriptedEffect::set(KWin::EffectWindow *window, KWin::AnimationEffect::Attribute attribute,
0503                             int ms, const QJSValue &to, const QJSValue &from, uint metaData, int curve,
0504                             int delay, bool fullScreen, bool keepAlive, uint shaderId)
0505 {
0506     QEasingCurve qec;
0507     if (curve < QEasingCurve::Custom) {
0508         qec.setType(static_cast<QEasingCurve::Type>(curve));
0509     } else if (curve == GaussianCurve) {
0510         qec.setCustomType(qecGaussian);
0511     }
0512     return AnimationEffect::set(window, attribute, metaData, ms, fpx2FromScriptValue(to), qec,
0513                                 delay, fpx2FromScriptValue(from), fullScreen, keepAlive, findShader(shaderId));
0514 }
0515 
0516 QJSValue ScriptedEffect::set(const QJSValue &object)
0517 {
0518     return animate_helper(object, AnimationType::Set);
0519 }
0520 
0521 bool ScriptedEffect::retarget(quint64 animationId, const QJSValue &newTarget, int newRemainingTime)
0522 {
0523     return AnimationEffect::retarget(animationId, fpx2FromScriptValue(newTarget), newRemainingTime);
0524 }
0525 
0526 bool ScriptedEffect::retarget(const QList<quint64> &animationIds, const QJSValue &newTarget, int newRemainingTime)
0527 {
0528     return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
0529         return retarget(animationId, newTarget, newRemainingTime);
0530     });
0531 }
0532 
0533 bool ScriptedEffect::freezeInTime(quint64 animationId, qint64 frozenTime)
0534 {
0535     return AnimationEffect::freezeInTime(animationId, frozenTime);
0536 }
0537 
0538 bool ScriptedEffect::freezeInTime(const QList<quint64> &animationIds, qint64 frozenTime)
0539 {
0540     return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
0541         return AnimationEffect::freezeInTime(animationId, frozenTime);
0542     });
0543 }
0544 
0545 bool ScriptedEffect::redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags)
0546 {
0547     return AnimationEffect::redirect(animationId, direction, terminationFlags);
0548 }
0549 
0550 bool ScriptedEffect::redirect(const QList<quint64> &animationIds, Direction direction, TerminationFlags terminationFlags)
0551 {
0552     return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
0553         return redirect(animationId, direction, terminationFlags);
0554     });
0555 }
0556 
0557 bool ScriptedEffect::complete(quint64 animationId)
0558 {
0559     return AnimationEffect::complete(animationId);
0560 }
0561 
0562 bool ScriptedEffect::complete(const QList<quint64> &animationIds)
0563 {
0564     return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
0565         return complete(animationId);
0566     });
0567 }
0568 
0569 bool ScriptedEffect::cancel(quint64 animationId)
0570 {
0571     return AnimationEffect::cancel(animationId);
0572 }
0573 
0574 bool ScriptedEffect::cancel(const QList<quint64> &animationIds)
0575 {
0576     bool ret = false;
0577     for (const quint64 &animationId : animationIds) {
0578         ret |= cancel(animationId);
0579     }
0580     return ret;
0581 }
0582 
0583 bool ScriptedEffect::isGrabbed(EffectWindow *w, ScriptedEffect::DataRole grabRole)
0584 {
0585     void *e = w->data(static_cast<KWin::DataRole>(grabRole)).value<void *>();
0586     if (e) {
0587         return e != this;
0588     } else {
0589         return false;
0590     }
0591 }
0592 
0593 bool ScriptedEffect::grab(EffectWindow *w, DataRole grabRole, bool force)
0594 {
0595     void *grabber = w->data(grabRole).value<void *>();
0596 
0597     if (grabber == this) {
0598         return true;
0599     }
0600 
0601     if (grabber != nullptr && grabber != this && !force) {
0602         return false;
0603     }
0604 
0605     w->setData(grabRole, QVariant::fromValue(static_cast<void *>(this)));
0606 
0607     return true;
0608 }
0609 
0610 bool ScriptedEffect::ungrab(EffectWindow *w, DataRole grabRole)
0611 {
0612     void *grabber = w->data(grabRole).value<void *>();
0613 
0614     if (grabber == nullptr) {
0615         return true;
0616     }
0617 
0618     if (grabber != this) {
0619         return false;
0620     }
0621 
0622     w->setData(grabRole, QVariant());
0623 
0624     return true;
0625 }
0626 
0627 void ScriptedEffect::reconfigure(ReconfigureFlags flags)
0628 {
0629     AnimationEffect::reconfigure(flags);
0630     if (m_config) {
0631         m_config->read();
0632     }
0633     Q_EMIT configChanged();
0634 }
0635 
0636 void ScriptedEffect::registerShortcut(const QString &objectName, const QString &text,
0637                                       const QString &keySequence, const QJSValue &callback)
0638 {
0639     if (!callback.isCallable()) {
0640         m_engine->throwError(QStringLiteral("Shortcut handler must be callable"));
0641         return;
0642     }
0643     QAction *action = new QAction(this);
0644     action->setObjectName(objectName);
0645     action->setText(text);
0646     const QKeySequence shortcut = QKeySequence(keySequence);
0647     KGlobalAccel::self()->setShortcut(action, QList<QKeySequence>() << shortcut);
0648     connect(action, &QAction::triggered, this, [this, action, callback]() {
0649         QJSValue actionObject = m_engine->newQObject(action);
0650         QQmlEngine::setObjectOwnership(action, QQmlEngine::CppOwnership);
0651         QJSValue(callback).call(QJSValueList{actionObject});
0652     });
0653 }
0654 
0655 bool ScriptedEffect::borderActivated(ElectricBorder edge)
0656 {
0657     auto it = screenEdgeCallbacks().constFind(edge);
0658     if (it != screenEdgeCallbacks().constEnd()) {
0659         for (const QJSValue &callback : it.value()) {
0660             QJSValue(callback).call();
0661         }
0662     }
0663     return true;
0664 }
0665 
0666 QJSValue ScriptedEffect::readConfig(const QString &key, const QJSValue &defaultValue)
0667 {
0668     if (!m_config) {
0669         return defaultValue;
0670     }
0671     return m_engine->toScriptValue(m_config->property(key));
0672 }
0673 
0674 int ScriptedEffect::displayWidth() const
0675 {
0676     return workspace()->geometry().width();
0677 }
0678 
0679 int ScriptedEffect::displayHeight() const
0680 {
0681     return workspace()->geometry().height();
0682 }
0683 
0684 int ScriptedEffect::animationTime(int defaultTime) const
0685 {
0686     return Effect::animationTime(defaultTime);
0687 }
0688 
0689 bool ScriptedEffect::registerScreenEdge(int edge, const QJSValue &callback)
0690 {
0691     if (!callback.isCallable()) {
0692         m_engine->throwError(QStringLiteral("Screen edge handler must be callable"));
0693         return false;
0694     }
0695     auto it = screenEdgeCallbacks().find(edge);
0696     if (it == screenEdgeCallbacks().end()) {
0697         // not yet registered
0698         workspace()->screenEdges()->reserve(static_cast<KWin::ElectricBorder>(edge), this, "borderActivated");
0699         screenEdgeCallbacks().insert(edge, QJSValueList{callback});
0700     } else {
0701         it->append(callback);
0702     }
0703     return true;
0704 }
0705 
0706 bool ScriptedEffect::registerRealtimeScreenEdge(int edge, const QJSValue &callback)
0707 {
0708     if (!callback.isCallable()) {
0709         m_engine->throwError(QStringLiteral("Screen edge handler must be callable"));
0710         return false;
0711     }
0712     auto it = realtimeScreenEdgeCallbacks().find(edge);
0713     if (it == realtimeScreenEdgeCallbacks().end()) {
0714         // not yet registered
0715         realtimeScreenEdgeCallbacks().insert(edge, QJSValueList{callback});
0716         auto *triggerAction = new QAction(this);
0717         connect(triggerAction, &QAction::triggered, this, [this, edge]() {
0718             auto it = realtimeScreenEdgeCallbacks().constFind(edge);
0719             if (it != realtimeScreenEdgeCallbacks().constEnd()) {
0720                 for (const QJSValue &callback : it.value()) {
0721                     QJSValue(callback).call({edge});
0722                 }
0723             }
0724         });
0725         effects->registerRealtimeTouchBorder(static_cast<KWin::ElectricBorder>(edge), triggerAction, [this](ElectricBorder border, const QPointF &deltaProgress, Output *screen) {
0726             auto it = realtimeScreenEdgeCallbacks().constFind(border);
0727             if (it != realtimeScreenEdgeCallbacks().constEnd()) {
0728                 for (const QJSValue &callback : it.value()) {
0729                     QJSValue delta = m_engine->newObject();
0730                     delta.setProperty("width", deltaProgress.x());
0731                     delta.setProperty("height", deltaProgress.y());
0732 
0733                     QJSValue(callback).call({border, QJSValue(delta), m_engine->newQObject(screen)});
0734                 }
0735             }
0736         });
0737     } else {
0738         it->append(callback);
0739     }
0740     return true;
0741 }
0742 
0743 bool ScriptedEffect::unregisterScreenEdge(int edge)
0744 {
0745     auto it = screenEdgeCallbacks().find(edge);
0746     if (it == screenEdgeCallbacks().end()) {
0747         // not previously registered
0748         return false;
0749     }
0750     workspace()->screenEdges()->unreserve(static_cast<KWin::ElectricBorder>(edge), this);
0751     screenEdgeCallbacks().erase(it);
0752     return true;
0753 }
0754 
0755 bool ScriptedEffect::registerTouchScreenEdge(int edge, const QJSValue &callback)
0756 {
0757     if (m_touchScreenEdgeCallbacks.constFind(edge) != m_touchScreenEdgeCallbacks.constEnd()) {
0758         return false;
0759     }
0760     if (!callback.isCallable()) {
0761         m_engine->throwError(QStringLiteral("Touch screen edge handler must be callable"));
0762         return false;
0763     }
0764     QAction *action = new QAction(this);
0765     connect(action, &QAction::triggered, this, [callback]() {
0766         QJSValue(callback).call();
0767     });
0768     workspace()->screenEdges()->reserveTouch(KWin::ElectricBorder(edge), action);
0769     m_touchScreenEdgeCallbacks.insert(edge, action);
0770     return true;
0771 }
0772 
0773 bool ScriptedEffect::unregisterTouchScreenEdge(int edge)
0774 {
0775     auto it = m_touchScreenEdgeCallbacks.find(edge);
0776     if (it == m_touchScreenEdgeCallbacks.end()) {
0777         return false;
0778     }
0779     delete it.value();
0780     m_touchScreenEdgeCallbacks.erase(it);
0781     return true;
0782 }
0783 
0784 QJSEngine *ScriptedEffect::engine() const
0785 {
0786     return m_engine;
0787 }
0788 
0789 uint ScriptedEffect::addFragmentShader(ShaderTrait traits, const QString &fragmentShaderFile)
0790 {
0791     if (!effects->makeOpenGLContextCurrent()) {
0792         m_engine->throwError(QStringLiteral("Failed to make OpenGL context current"));
0793         return 0;
0794     }
0795     const QString shaderDir{QLatin1String("kwin/effects/") + m_effectName + QLatin1String("/contents/shaders/")};
0796     const QString fragment = fragmentShaderFile.isEmpty() ? QString{} : QStandardPaths::locate(QStandardPaths::GenericDataLocation, shaderDir + fragmentShaderFile);
0797 
0798     auto shader = ShaderManager::instance()->generateShaderFromFile(static_cast<KWin::ShaderTraits>(int(traits)), {}, fragment);
0799     if (!shader->isValid()) {
0800         m_engine->throwError(QStringLiteral("Shader failed to load"));
0801         // 0 is never a valid shader identifier, it's ensured the first shader gets id 1
0802         return 0;
0803     }
0804 
0805     const uint shaderId{m_nextShaderId};
0806     m_nextShaderId++;
0807     m_shaders[shaderId] = std::move(shader);
0808     return shaderId;
0809 }
0810 
0811 GLShader *ScriptedEffect::findShader(uint shaderId) const
0812 {
0813     if (auto it = m_shaders.find(shaderId); it != m_shaders.end()) {
0814         return it->second.get();
0815     }
0816     return nullptr;
0817 }
0818 
0819 void ScriptedEffect::setUniform(uint shaderId, const QString &name, const QJSValue &value)
0820 {
0821     auto shader = findShader(shaderId);
0822     if (!shader) {
0823         m_engine->throwError(QStringLiteral("Shader for given shaderId not found"));
0824         return;
0825     }
0826     if (!effects->makeOpenGLContextCurrent()) {
0827         m_engine->throwError(QStringLiteral("Failed to make OpenGL context current"));
0828         return;
0829     }
0830     auto setColorUniform = [this, shader, name](const QColor &color) {
0831         if (!color.isValid()) {
0832             return;
0833         }
0834         if (!shader->setUniform(name.toUtf8().constData(), color)) {
0835             m_engine->throwError(QStringLiteral("Failed to set uniform ") + name);
0836         }
0837     };
0838     ShaderBinder binder{shader};
0839     if (value.isString()) {
0840         setColorUniform(value.toString());
0841     } else if (value.isNumber()) {
0842         if (!shader->setUniform(name.toUtf8().constData(), float(value.toNumber()))) {
0843             m_engine->throwError(QStringLiteral("Failed to set uniform ") + name);
0844         }
0845     } else if (value.isArray()) {
0846         const auto length = value.property(QStringLiteral("length")).toInt();
0847         if (length == 2) {
0848             if (!shader->setUniform(name.toUtf8().constData(), QVector2D{float(value.property(0).toNumber()), float(value.property(1).toNumber())})) {
0849                 m_engine->throwError(QStringLiteral("Failed to set uniform ") + name);
0850             }
0851         } else if (length == 3) {
0852             if (!shader->setUniform(name.toUtf8().constData(), QVector3D{float(value.property(0).toNumber()), float(value.property(1).toNumber()), float(value.property(2).toNumber())})) {
0853                 m_engine->throwError(QStringLiteral("Failed to set uniform ") + name);
0854             }
0855         } else if (length == 4) {
0856             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())})) {
0857                 m_engine->throwError(QStringLiteral("Failed to set uniform ") + name);
0858             }
0859         } else {
0860             m_engine->throwError(QStringLiteral("Invalid number of elements in array"));
0861         }
0862     } else if (value.isVariant()) {
0863         const auto variant = value.toVariant();
0864         setColorUniform(variant.value<QColor>());
0865     } else {
0866         m_engine->throwError(QStringLiteral("Invalid value provided for uniform"));
0867     }
0868 }
0869 
0870 } // namespace
0871 
0872 #include "moc_scriptedeffect.cpp"