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