23#include <KConfigGroup>
24#include <KGlobalAccel>
25#include <KPluginMetaData>
26#include <kconfigloader.h>
32#include <QStandardPaths>
72 settings.
to =
object.property(QStringLiteral(
"to"));
73 settings.
from =
object.property(QStringLiteral(
"from"));
75 const QJSValue duration =
object.property(QStringLiteral(
"duration"));
76 if (duration.isNumber()) {
77 settings.
duration = duration.toUInt();
83 const QJSValue delay =
object.property(QStringLiteral(
"delay"));
84 if (delay.isNumber()) {
85 settings.
delay = delay.toInt();
91 const QJSValue curve =
object.property(QStringLiteral(
"curve"));
92 if (curve.isNumber()) {
93 settings.
curve =
static_cast<QEasingCurve::Type
>(curve.toInt());
96 settings.
curve = QEasingCurve::Linear;
99 const QJSValue
type =
object.property(QStringLiteral(
"type"));
100 if (
type.isNumber()) {
107 const QJSValue isFullScreen =
object.property(QStringLiteral(
"fullScreen"));
108 if (isFullScreen.isBool()) {
115 const QJSValue keepAlive =
object.property(QStringLiteral(
"keepAlive"));
116 if (keepAlive.isBool()) {
123 const QJSValue frozenTime =
object.property(QStringLiteral(
"frozenTime"));
124 if (frozenTime.isNumber()) {
131 if (
const auto shader =
object.property(QStringLiteral(
"fragmentShader")); shader.isNumber()) {
132 settings.
shader = shader.toUInt();
138static KWin::FPx2 fpx2FromScriptValue(
const QJSValue &value)
140 if (value.isNull()) {
143 if (value.isNumber()) {
144 return FPx2(value.toNumber());
146 if (value.isObject()) {
147 const QJSValue value1 = value.property(QStringLiteral(
"value1"));
148 const QJSValue value2 = value.property(QStringLiteral(
"value2"));
149 if (!value1.isNumber() || !value2.isNumber()) {
150 qCDebug(KWIN_SCRIPTING) <<
"Cannot cast scripted FPx2 to C++";
153 return FPx2(value1.toNumber(), value2.toNumber());
160 const QString name = effect.
pluginId();
161 const QString
scriptFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
162 QLatin1String(
"kwin/effects/") + name + QLatin1String(
"/contents/code/main.js"));
164 qCDebug(KWIN_SCRIPTING) <<
"Could not locate effect script" << name;
168 return ScriptedEffect::create(name,
scriptFile, effect.value(QStringLiteral(
"X-KDE-Ordering"), 0), effect.value(QStringLiteral(
"X-KWin-Exclusive-Category")));
174 effect->m_exclusiveCategory = exclusiveCategory;
175 if (!effect->
init(effectName, pathToScript)) {
179 effect->m_chainPosition = chainPosition;
191 , m_engine(new QJSEngine(this))
192 , m_scriptFile(QString())
199 if (fullScreenEffect == m_activeFullScreenEffect) {
202 if (m_activeFullScreenEffect ==
this || fullScreenEffect ==
this) {
205 m_activeFullScreenEffect = fullScreenEffect;
211bool ScriptedEffect::init(
const QString &effectName,
const QString &pathToScript)
213 qRegisterMetaType<QJSValueList>();
214 qRegisterMetaType<QList<KWin::EffectWindow *>>();
218 qCDebug(KWIN_SCRIPTING) <<
"Could not open script file: " << pathToScript;
221 m_effectName = effectName;
222 m_scriptFile = pathToScript;
225 const QString kconfigXTFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String(
"kwin/effects/") + m_effectName + QLatin1String(
"/contents/config/main.xml"));
226 if (!kconfigXTFile.isNull()) {
227 KConfigGroup cg = QCoreApplication::instance()->property(
"config").value<KSharedConfigPtr>()->group(QStringLiteral(
"Effect-%1").arg(m_effectName));
228 QFile xmlFile(kconfigXTFile);
229 m_config =
new KConfigLoader(cg, &xmlFile,
this);
233 m_engine->installExtensions(QJSEngine::ConsoleExtension);
235 QJSValue globalObject = m_engine->globalObject();
237 QJSValue effectsObject = m_engine->newQObject(
effects);
238 QQmlEngine::setObjectOwnership(
effects, QQmlEngine::CppOwnership);
239 globalObject.setProperty(QStringLiteral(
"effects"), effectsObject);
241 QJSValue selfObject = m_engine->newQObject(
this);
242 QQmlEngine::setObjectOwnership(
this, QQmlEngine::CppOwnership);
243 globalObject.setProperty(QStringLiteral(
"effect"), selfObject);
245 globalObject.setProperty(QStringLiteral(
"Effect"),
246 m_engine->newQMetaObject(&ScriptedEffect::staticMetaObject));
247 globalObject.setProperty(QStringLiteral(
"KWin"),
248 m_engine->newQMetaObject(&QtScriptWorkspaceWrapper::staticMetaObject));
249 globalObject.setProperty(QStringLiteral(
"Globals"),
250 m_engine->newQMetaObject(&KWin::staticMetaObject));
251 globalObject.setProperty(QStringLiteral(
"QEasingCurve"),
252 m_engine->newQMetaObject(&QEasingCurve::staticMetaObject));
254 static const QStringList globalProperties{
255 QStringLiteral(
"animationTime"),
256 QStringLiteral(
"displayWidth"),
257 QStringLiteral(
"displayHeight"),
259 QStringLiteral(
"registerShortcut"),
260 QStringLiteral(
"registerScreenEdge"),
261 QStringLiteral(
"registerRealtimeScreenEdge"),
262 QStringLiteral(
"registerTouchScreenEdge"),
263 QStringLiteral(
"unregisterScreenEdge"),
264 QStringLiteral(
"unregisterTouchScreenEdge"),
266 QStringLiteral(
"animate"),
267 QStringLiteral(
"set"),
268 QStringLiteral(
"retarget"),
269 QStringLiteral(
"freezeInTime"),
270 QStringLiteral(
"redirect"),
271 QStringLiteral(
"complete"),
272 QStringLiteral(
"cancel"),
273 QStringLiteral(
"addShader"),
274 QStringLiteral(
"setUniform"),
277 for (
const QString &propertyName : globalProperties) {
278 globalObject.setProperty(propertyName, selfObject.property(propertyName));
281 const QJSValue result = m_engine->evaluate(QString::fromUtf8(
scriptFile.readAll()));
283 if (result.isError()) {
284 qCWarning(KWIN_SCRIPTING,
"%s:%d: error: %s", qPrintable(
scriptFile.fileName()),
285 result.property(QStringLiteral(
"lineNumber")).toInt(),
286 qPrintable(result.property(QStringLiteral(
"message")).toString()));
312 if (m_exclusiveCategory == QStringLiteral(
"show-desktop") && action == QStringLiteral(
"show-desktop")) {
323 return m_config->property(QStringLiteral(
"TouchBorderActivate") + action).value<QList<int>>();
327QJSValue ScriptedEffect::animate_helper(
const QJSValue &
object, AnimationType animationType)
329 QJSValue windowProperty =
object.property(QStringLiteral(
"window"));
330 if (!windowProperty.isObject()) {
331 m_engine->throwError(QStringLiteral(
"Window property missing in animation options"));
335 EffectWindow *window = qobject_cast<EffectWindow *>(windowProperty.toQObject());
337 m_engine->throwError(QStringLiteral(
"Window property references invalid window"));
343 QJSValue animations =
object.property(QStringLiteral(
"animations"));
344 if (!animations.isUndefined()) {
345 if (!animations.isArray()) {
346 m_engine->throwError(QStringLiteral(
"Animations provided but not an array"));
350 const int length =
static_cast<int>(animations.property(QStringLiteral(
"length")).toInt());
351 for (
int i = 0; i < length; ++i) {
352 QJSValue value = animations.property(QString::number(i));
353 if (value.isObject()) {
355 const uint
set = s.set | settings.at(0).set;
358 m_engine->throwError(QStringLiteral(
"Type property missing in animation options"));
362 m_engine->throwError(QStringLiteral(
"Duration property missing in animation options"));
367 s.duration = settings.at(0).duration;
370 s.curve = settings.at(0).curve;
373 s.delay = settings.at(0).delay;
376 s.fullScreenEffect = settings.at(0).fullScreenEffect;
379 s.keepAlive = settings.at(0).keepAlive;
381 if (!s.shader.has_value()) {
382 s.shader = settings.at(0).shader;
386 typedef QMap<AnimationEffect::MetaType, QString> MetaTypeMap;
395 for (
auto it = metaTypes.constBegin(),
396 end = metaTypes.constEnd();
398 QJSValue metaVal = value.property(*it);
399 if (metaVal.isNumber()) {
404 auto uniformProperty = value.property(QStringLiteral(
"uniform")).toString();
405 auto shader = findShader(s.shader.value());
407 m_engine->throwError(QStringLiteral(
"Shader for given shaderId not found"));
411 m_engine->throwError(QStringLiteral(
"Failed to make OpenGL context current"));
414 ShaderBinder binder{shader};
415 s.metaData = shader->uniformLocation(uniformProperty.toUtf8().constData());
423 if (settings.count() == 1) {
424 const uint
set = settings.at(0).set;
426 m_engine->throwError(QStringLiteral(
"Type property missing in animation options"));
430 m_engine->throwError(QStringLiteral(
"Duration property missing in animation options"));
434 settings.removeAt(0);
437 if (settings.isEmpty()) {
438 m_engine->throwError(QStringLiteral(
"No animations provided"));
442 QJSValue array = m_engine->newArray(settings.length());
443 for (
int i = 0; i < settings.count(); i++) {
444 const AnimationSettings &setting = settings[i];
446 if (animationType == AnimationType::Set) {
447 animationId =
set(window,
455 setting.fullScreenEffect,
457 setting.shader ? setting.shader.value() : 0u);
458 if (setting.frozenTime >= 0) {
470 setting.fullScreenEffect,
472 setting.shader ? setting.shader.value() : 0u);
473 if (setting.frozenTime >= 0) {
477 array.setProperty(i, animationId);
484 int ms,
const QJSValue &to,
const QJSValue &from, uint metaData,
int curve,
485 int delay,
bool fullScreen,
bool keepAlive, uint shaderId)
488 if (curve < QEasingCurve::Custom) {
489 qec.setType(
static_cast<QEasingCurve::Type
>(curve));
494 delay, fpx2FromScriptValue(from), fullScreen, keepAlive, findShader(shaderId));
499 return animate_helper(
object, AnimationType::Animate);
503 int ms,
const QJSValue &to,
const QJSValue &from, uint metaData,
int curve,
504 int delay,
bool fullScreen,
bool keepAlive, uint shaderId)
507 if (curve < QEasingCurve::Custom) {
508 qec.setType(
static_cast<QEasingCurve::Type
>(curve));
513 delay, fpx2FromScriptValue(from), fullScreen, keepAlive, findShader(shaderId));
518 return animate_helper(
object, AnimationType::Set);
528 return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
529 return retarget(animationId, newTarget, newRemainingTime);
540 return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
541 return AnimationEffect::freezeInTime(animationId, frozenTime);
552 return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
553 return redirect(animationId, direction, terminationFlags);
564 return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
565 return complete(animationId);
577 for (
const quint64 &animationId : animationIds) {
578 ret |=
cancel(animationId);
595 void *grabber = w->
data(grabRole).value<
void *>();
597 if (grabber ==
this) {
601 if (grabber !=
nullptr && grabber !=
this && !force) {
605 w->
setData(grabRole, QVariant::fromValue(
static_cast<void *
>(
this)));
612 void *grabber = w->
data(grabRole).value<
void *>();
614 if (grabber ==
nullptr) {
618 if (grabber !=
this) {
622 w->
setData(grabRole, QVariant());
637 const QString &keySequence,
const QJSValue &callback)
639 if (!callback.isCallable()) {
640 m_engine->throwError(QStringLiteral(
"Shortcut handler must be callable"));
643 QAction *action =
new QAction(
this);
644 action->setObjectName(objectName);
645 action->setText(text);
646 const QKeySequence shortcut = QKeySequence(keySequence);
647 KGlobalAccel::self()->setShortcut(action, QList<QKeySequence>() << shortcut);
648 connect(action, &QAction::triggered,
this, [
this, action, callback]() {
649 QJSValue actionObject = m_engine->newQObject(action);
650 QQmlEngine::setObjectOwnership(action, QQmlEngine::CppOwnership);
651 QJSValue(callback).call(QJSValueList{actionObject});
659 for (
const QJSValue &callback : it.value()) {
660 QJSValue(callback).call();
671 return m_engine->toScriptValue(m_config->property(key));
691 if (!callback.isCallable()) {
692 m_engine->throwError(QStringLiteral(
"Screen edge handler must be callable"));
701 it->append(callback);
708 if (!callback.isCallable()) {
709 m_engine->throwError(QStringLiteral(
"Screen edge handler must be callable"));
716 auto *triggerAction =
new QAction(
this);
717 connect(triggerAction, &QAction::triggered,
this, [
this, edge]() {
720 for (
const QJSValue &callback : it.value()) {
721 QJSValue(callback).call({edge});
728 for (
const QJSValue &callback : it.value()) {
729 QJSValue delta = m_engine->newObject();
730 delta.setProperty(
"width", deltaProgress.x());
731 delta.setProperty(
"height", deltaProgress.y());
733 QJSValue(callback).call({border, QJSValue(delta), m_engine->newQObject(screen)});
738 it->append(callback);
757 if (m_touchScreenEdgeCallbacks.constFind(edge) != m_touchScreenEdgeCallbacks.constEnd()) {
760 if (!callback.isCallable()) {
761 m_engine->throwError(QStringLiteral(
"Touch screen edge handler must be callable"));
764 QAction *action =
new QAction(
this);
765 connect(action, &QAction::triggered,
this, [callback]() {
766 QJSValue(callback).call();
769 m_touchScreenEdgeCallbacks.insert(edge, action);
775 auto it = m_touchScreenEdgeCallbacks.find(edge);
776 if (it == m_touchScreenEdgeCallbacks.end()) {
780 m_touchScreenEdgeCallbacks.erase(it);
792 m_engine->throwError(QStringLiteral(
"Failed to make OpenGL context current"));
795 const QString shaderDir{QLatin1String(
"kwin/effects/") + m_effectName + QLatin1String(
"/contents/shaders/")};
796 const QString fragment = fragmentShaderFile.isEmpty() ? QString{} : QStandardPaths::locate(QStandardPaths::GenericDataLocation, shaderDir + fragmentShaderFile);
799 if (!shader->isValid()) {
800 m_engine->throwError(QStringLiteral(
"Shader failed to load"));
805 const uint shaderId{m_nextShaderId};
807 m_shaders[shaderId] = std::move(shader);
811GLShader *ScriptedEffect::findShader(uint shaderId)
const
813 if (
auto it = m_shaders.find(shaderId); it != m_shaders.end()) {
814 return it->second.get();
821 auto shader = findShader(shaderId);
823 m_engine->throwError(QStringLiteral(
"Shader for given shaderId not found"));
827 m_engine->throwError(QStringLiteral(
"Failed to make OpenGL context current"));
830 auto setColorUniform = [
this, shader, name](
const QColor &color) {
831 if (!color.isValid()) {
834 if (!shader->setUniform(name.toUtf8().constData(), color)) {
835 m_engine->throwError(QStringLiteral(
"Failed to set uniform ") + name);
839 if (value.isString()) {
840 setColorUniform(value.toString());
841 }
else if (value.isNumber()) {
842 if (!shader->setUniform(name.toUtf8().constData(),
float(value.toNumber()))) {
843 m_engine->throwError(QStringLiteral(
"Failed to set uniform ") + name);
845 }
else if (value.isArray()) {
846 const auto length = value.property(QStringLiteral(
"length")).toInt();
848 if (!shader->setUniform(name.toUtf8().constData(), QVector2D{float(value.property(0).toNumber()), float(value.property(1).toNumber())})) {
849 m_engine->throwError(QStringLiteral(
"Failed to set uniform ") + name);
851 }
else if (length == 3) {
852 if (!shader->setUniform(name.toUtf8().constData(), QVector3D{float(value.property(0).toNumber()), float(value.property(1).toNumber()), float(value.property(2).toNumber())})) {
853 m_engine->throwError(QStringLiteral(
"Failed to set uniform ") + name);
855 }
else if (length == 4) {
856 if (!shader->setUniform(name.toUtf8().constData(), QVector4D{float(value.property(0).toNumber()), float(value.property(1).toNumber()), float(value.property(2).toNumber()), float(value.property(3).toNumber())})) {
857 m_engine->throwError(QStringLiteral(
"Failed to set uniform ") + name);
860 m_engine->throwError(QStringLiteral(
"Invalid number of elements in array"));
862 }
else if (value.isVariant()) {
863 const auto variant = value.toVariant();
864 setColorUniform(variant.value<QColor>());
866 m_engine->throwError(QStringLiteral(
"Invalid value provided for uniform"));
872#include "moc_scriptedeffect.cpp"
quint64 set(EffectWindow *w, Attribute a, uint meta, int ms, const FPx2 &to, const QEasingCurve &curve=QEasingCurve(), int delay=0, const FPx2 &from=FPx2(), bool fullScreen=false, bool keepAlive=true, GLShader *shader=nullptr)
bool redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags=TerminateAtSource)
bool complete(quint64 animationId)
virtual void animationEnded(EffectWindow *w, Attribute a, uint meta)
static void setMetaData(MetaType type, uint value, uint &meta)
bool retarget(quint64 animationId, FPx2 newTarget, int newRemainingTime=-1)
bool cancel(quint64 animationId)
static int metaData(MetaType type, uint meta)
bool freezeInTime(quint64 animationId, qint64 frozenTime)
quint64 animate(EffectWindow *w, Attribute a, uint meta, int ms, const FPx2 &to, const QEasingCurve &curve=QEasingCurve(), int delay=0, const FPx2 &from=FPx2(), bool fullScreen=false, bool keepAlive=true, GLShader *shader=nullptr)
static qreal qecGaussian(qreal progress)
Base class for all KWin effects.
Representation of a window used by/for Effect classes.
Q_SCRIPTABLE void setData(int role, const QVariant &data)
Q_SCRIPTABLE QVariant data(int role) const
bool animationsSupported() const
bool makeOpenGLContextCurrent()
Makes the OpenGL compositing context current.
void registerRealtimeTouchBorder(ElectricBorder border, QAction *action, TouchBorderCallback progressCallback)
Effect * activeFullScreenEffect() const
void activeFullScreenEffectChanged()
void reserve(ElectricBorder border, QObject *object, const char *callback)
void unreserve(ElectricBorder border, QObject *object)
ElectricBorderAction actionForTouchBorder(ElectricBorder border) const
void reserveTouch(ElectricBorder border, QAction *action, TouchCallback::CallbackFunction callback=nullptr)
Q_SCRIPTABLE int displayHeight() const
Q_SCRIPTABLE bool registerRealtimeScreenEdge(int edge, const QJSValue &callback)
Q_SCRIPTABLE QList< int > touchEdgesForAction(const QString &action) const
~ScriptedEffect() override
QHash< int, QJSValueList > & realtimeScreenEdgeCallbacks()
Q_SCRIPTABLE bool unregisterScreenEdge(int edge)
Q_SCRIPTABLE bool registerScreenEdge(int edge, const QJSValue &callback)
bool init(const QString &effectName, const QString &pathToScript)
bool isActiveFullScreenEffect
Q_SCRIPTABLE uint addFragmentShader(ShaderTrait traits, const QString &fragmentShaderFile={})
Q_SCRIPTABLE bool retarget(quint64 animationId, const QJSValue &newTarget, int newRemainingTime=-1)
Q_SCRIPTABLE bool complete(quint64 animationId)
void animationEnded(KWin::EffectWindow *w, quint64 animationId)
Q_SCRIPTABLE int animationTime(int defaultTime) const
Q_SCRIPTABLE int displayWidth() const
Q_SCRIPTABLE void setUniform(uint shaderId, const QString &name, const QJSValue &value)
void isActiveFullScreenEffectChanged()
QJSEngine * engine() const
Q_SCRIPTABLE bool ungrab(KWin::EffectWindow *w, DataRole grabRole)
Q_SCRIPTABLE bool registerTouchScreenEdge(int edge, const QJSValue &callback)
Q_SCRIPTABLE quint64 animate(KWin::EffectWindow *window, Attribute attribute, int ms, const QJSValue &to, const QJSValue &from=QJSValue(), uint metaData=0, int curve=QEasingCurve::Linear, int delay=0, bool fullScreen=false, bool keepAlive=true, uint shaderId=0)
Q_SCRIPTABLE bool grab(KWin::EffectWindow *w, DataRole grabRole, bool force=false)
void reconfigure(ReconfigureFlags flags) override
Q_SCRIPTABLE bool redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags=TerminateAtSource)
static ScriptedEffect * create(const QString &effectName, const QString &pathToScript, int chainPosition, const QString &exclusiveCategory)
Q_SCRIPTABLE bool cancel(quint64 animationId)
const QString & scriptFile() const
Q_SCRIPTABLE bool unregisterTouchScreenEdge(int edge)
bool borderActivated(ElectricBorder border) override
Q_SCRIPTABLE bool isGrabbed(KWin::EffectWindow *w, DataRole grabRole)
Q_SCRIPTABLE QJSValue readConfig(const QString &key, const QJSValue &defaultValue=QJSValue())
Q_SCRIPTABLE void registerShortcut(const QString &objectName, const QString &text, const QString &keySequence, const QJSValue &callback)
Q_SCRIPTABLE quint64 set(KWin::EffectWindow *window, Attribute attribute, int ms, const QJSValue &to, const QJSValue &from=QJSValue(), uint metaData=0, int curve=QEasingCurve::Linear, int delay=0, bool fullScreen=false, bool keepAlive=true, uint shaderId=0)
QHash< int, QJSValueList > & screenEdgeCallbacks()
Q_SCRIPTABLE bool freezeInTime(quint64 animationId, qint64 frozenTime)
std::unique_ptr< GLShader > generateShaderFromFile(ShaderTraits traits, const QString &vertexFile=QString(), const QString &fragmentFile=QString())
static ShaderManager * instance()
ScreenEdges * screenEdges() const
virtual void reconfigure(ReconfigureFlags flags)
static double animationTime(const KConfigGroup &cfg, const QString &key, int defaultTime)
@ ElectricActionShowDesktop
AnimationSettings animationSettingsFromObject(const QJSValue &object)
std::optional< uint > shader
AnimationEffect::Attribute type