File indexing completed on 2024-11-10 04:57:18
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"