Commit ae6e6dc6 authored by David Edmundson's avatar David Edmundson Committed by Vlad Zahorodnii
Browse files

[scripting] Port ScriptedEffects to QJSEngine

Summary:
QScriptEngine is deprecated for years and suffers bitrot.
Plasma hit one super major bug with it in 5.11.0 and has now ported
away.

Main porting notes:
- creating low level functions no longer exists
The old global functions are exposed on the ScriptedEffect instance
and then the QJSValue wrappers of the globalObject are modified to
trampoline the methods at a wrapper level.

- We can then use QJSEngine to automatically do argument error checking
rather than unmarshalling a QJSValue manually which significantly
reduces a lot of code.

- We can't make FPX2 a native type, so these are QJSValue args and
unboxed there.

Long term I want overloads for animate that take int/QSize/QPoint which
are native JS types, but that might be an API break.

Test Plan:
Hopefully comprehensive unit test which passes
Tested fade/fadeDesktop manually.

It's a very invasive change, so I expect some things will be broke
please help test any JS effects.

Reviewers: #kwin, ma...
parent 566d4aa2
......@@ -149,6 +149,7 @@ set_target_properties(testBuiltInEffectLoader PROPERTIES COMPILE_DEFINITIONS "NO
target_link_libraries(testBuiltInEffectLoader
Qt::Concurrent
Qt5::Qml
Qt::Test
Qt::X11Extras
......@@ -184,7 +185,6 @@ add_executable(testScriptedEffectLoader ${testScriptedEffectLoader_SRCS})
target_link_libraries(testScriptedEffectLoader
Qt::Concurrent
Qt::Qml
Qt::Script
Qt::Test
Qt::X11Extras
......@@ -213,6 +213,7 @@ add_executable(testPluginEffectLoader ${testPluginEffectLoader_SRCS})
target_link_libraries(testPluginEffectLoader
Qt::Concurrent
Qt5::Qml
Qt::Test
Qt::X11Extras
......
......@@ -23,9 +23,8 @@
#include "wayland_server.h"
#include "workspace.h"
#include <QScriptContext>
#include <QScriptEngine>
#include <QScriptValue>
#include <QJSValue>
#include <QQmlEngine>
#include <KConfigGroup>
#include <KGlobalAccel>
......@@ -81,37 +80,34 @@ public:
bool load(const QString &name);
using AnimationEffect::AniMap;
using AnimationEffect::state;
Q_INVOKABLE void sendTestResponse(const QString &out); //proxies triggers out from the tests
QList<QAction*> actions(); //returns any QActions owned by the ScriptEngine
signals:
void testOutput(const QString &data);
};
QScriptValue kwinEffectScriptTestOut(QScriptContext *context, QScriptEngine *engine)
void ScriptedEffectWithDebugSpy::sendTestResponse(const QString &out)
{
auto *script = qobject_cast<ScriptedEffectWithDebugSpy*>(context->callee().data().toQObject());
QString result;
for (int i = 0; i < context->argumentCount(); ++i) {
if (i > 0) {
result.append(QLatin1Char(' '));
}
result.append(context->argument(i).toString());
}
emit script->testOutput(result);
emit testOutput(out);
}
return engine->undefinedValue();
QList<QAction *> ScriptedEffectWithDebugSpy::actions()
{
return findChildren<QAction *>(QString(), Qt::FindDirectChildrenOnly);
}
ScriptedEffectWithDebugSpy::ScriptedEffectWithDebugSpy()
: ScriptedEffect()
{
QScriptValue testHookFunc = engine()->newFunction(kwinEffectScriptTestOut);
testHookFunc.setData(engine()->newQObject(this));
engine()->globalObject().setProperty(QStringLiteral("sendTestResponse"), testHookFunc);
}
bool ScriptedEffectWithDebugSpy::load(const QString &name)
{
auto selfContext = engine()->newQObject(this);
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
const QString path = QFINDTESTDATA("./scripts/" + name + ".js");
if (!init(name, path)) {
engine()->globalObject().setProperty("sendTestResponse", selfContext.property("sendTestResponse"));
if (!init(name, path)) {
return false;
}
......@@ -244,8 +240,8 @@ void ScriptedEffectsTest::testShortcuts()
auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean
QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput);
QVERIFY(effect->load("shortcutsTest"));
QCOMPARE(effect->shortcutCallbacks().count(), 1);
QAction *action = effect->shortcutCallbacks().keys()[0];
QCOMPARE(effect->actions().count(), 1);
auto action = effect->actions()[0];
QCOMPARE(action->objectName(), "testShortcut");
QCOMPARE(action->text(), "Test Shortcut");
QCOMPARE(KGlobalAccel::self()->shortcut(action).first(), QKeySequence("Meta+Shift+Y"));
......@@ -353,8 +349,7 @@ void ScriptedEffectsTest::testScreenEdgeTouch()
auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean
QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput);
QVERIFY(effect->load("screenEdgeTouchTest"));
auto actions = effect->findChildren<QAction*>(QString(), Qt::FindDirectChildrenOnly);
actions[0]->trigger();
effect->actions()[0]->trigger();
QCOMPARE(effectOutputSpy.count(), 1);
}
......
registerScreenEdge(readConfig("Edge", 1), () => workspace.slotToggleShowDesktop());
registerScreenEdge(readConfig("Edge", 1), workspace.slotToggleShowDesktop);
function init() {
let edge = readConfig("Edge", 1);
const edge = readConfig("Edge", 1);
if (readConfig("mode", "") == "unregister") {
unregisterScreenEdge(edge);
} else {
registerScreenEdge(edge, () => workspace.slotToggleShowDesktop());
registerScreenEdge(edge, workspace.slotToggleShowDesktop);
}
}
options.configChanged.connect(init);
......
registerTouchScreenEdge(readConfig("Edge", 1), () => workspace.slotToggleShowDesktop());
registerTouchScreenEdge(readConfig("Edge", 1), workspace.slotToggleShowDesktop);
......@@ -35,6 +35,10 @@ void ScreenEdges::reserve(ElectricBorder, QObject *, const char *)
{
}
void ScreenEdges::unreserve(ElectricBorder, QObject *)
{
}
void ScreenEdges::reserveTouch(ElectricBorder, QAction *)
{
}
......@@ -45,13 +49,6 @@ void InputRedirection::registerShortcut(const QKeySequence &, QAction *)
{
}
namespace MetaScripting
{
void registration(QScriptEngine *)
{
}
}
}
class TestScriptedEffectLoader : public QObject
......
......@@ -99,7 +99,6 @@ set(kwin_SRCS
screenlockerwatcher.cpp
screens.cpp
scripting/dbuscall.cpp
scripting/meta.cpp
scripting/screenedgeitem.cpp
scripting/scriptedeffect.cpp
scripting/scripting.cpp
......
......@@ -148,8 +148,8 @@ public:
*/
TerminateAtTarget = 0x02
};
Q_FLAGS(TerminationFlag)
Q_DECLARE_FLAGS(TerminationFlags, TerminationFlag)
Q_FLAG(TerminationFlags)
/**
* Constructs AnimationEffect.
......
......@@ -741,6 +741,7 @@ EffectsHandler::EffectsHandler(CompositingType type)
if (compositing_type == NoCompositing)
return;
KWin::effects = this;
connect(this, QOverload<int, int>::of(&EffectsHandler::desktopChanged), this, &EffectsHandler::desktopChangedLegacy);
}
EffectsHandler::~EffectsHandler()
......
......@@ -811,7 +811,7 @@ class KWINEFFECTS_EXPORT EffectsHandler : public QObject
* if used manually.
*/
Q_PROPERTY(qreal animationTimeFactor READ animationTimeFactor)
Q_PROPERTY(QList< KWin::EffectWindow* > stackingOrder READ stackingOrder)
Q_PROPERTY(KWin::EffectWindowList stackingOrder READ stackingOrder)
/**
* Whether window decorations use the alpha channel.
*/
......@@ -1392,6 +1392,10 @@ Q_SIGNALS:
* @deprecated
*/
void desktopChanged(int oldDesktop, int newDesktop);
/**
* @internal
*/
void desktopChangedLegacy(int oldDesktop, int newDesktop);
/**
* Signal emitted when a window moved to another desktop
* NOTICE that this does NOT imply that the desktop has changed
......@@ -2375,7 +2379,7 @@ public:
virtual bool isModal() const = 0;
Q_SCRIPTABLE virtual KWin::EffectWindow* findModal() = 0;
Q_SCRIPTABLE virtual KWin::EffectWindow* transientFor() = 0;
Q_SCRIPTABLE virtual QList<KWin::EffectWindow*> mainWindows() const = 0;
Q_SCRIPTABLE virtual KWin::EffectWindowList mainWindows() const = 0;
/**
* Returns whether the window should be excluded from window switching effects.
......@@ -4022,7 +4026,7 @@ void Effect::initConfig()
} // namespace
Q_DECLARE_METATYPE(KWin::EffectWindow*)
Q_DECLARE_METATYPE(QList<KWin::EffectWindow*>)
Q_DECLARE_METATYPE(KWin::EffectWindowList)
Q_DECLARE_METATYPE(KWin::TimeLine)
Q_DECLARE_METATYPE(KWin::TimeLine::Direction)
......
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2010 Rohan Prabhu <rohan@rohanprabhu.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "meta.h"
#include <QRect>
#include <QtScript/QScriptEngine>
using namespace KWin::MetaScripting;
// Meta for QPoint object
QScriptValue Point::toScriptValue(QScriptEngine* eng, const QPoint& point)
{
QScriptValue temp = eng->newObject();
temp.setProperty(QStringLiteral("x"), point.x());
temp.setProperty(QStringLiteral("y"), point.y());
return temp;
}
void Point::fromScriptValue(const QScriptValue& obj, QPoint& point)
{
QScriptValue x = obj.property(QStringLiteral("x"), QScriptValue::ResolveLocal);
QScriptValue y = obj.property(QStringLiteral("y"), QScriptValue::ResolveLocal);
if (!x.isUndefined() && !y.isUndefined()) {
point.setX(x.toInt32());
point.setY(y.toInt32());
}
}
// End of meta for QPoint object
// Meta for QSize object
QScriptValue Size::toScriptValue(QScriptEngine* eng, const QSize& size)
{
QScriptValue temp = eng->newObject();
temp.setProperty(QStringLiteral("w"), size.width());
temp.setProperty(QStringLiteral("h"), size.height());
return temp;
}
void Size::fromScriptValue(const QScriptValue& obj, QSize& size)
{
QScriptValue w = obj.property(QStringLiteral("w"), QScriptValue::ResolveLocal);
QScriptValue h = obj.property(QStringLiteral("h"), QScriptValue::ResolveLocal);
if (!w.isUndefined() && !h.isUndefined()) {
size.setWidth(w.toInt32());
size.setHeight(h.toInt32());
}
}
// End of meta for QSize object
// Meta for QRect object. Just a temporary measure, hope to
// add a much better wrapping of the QRect object soon
QScriptValue Rect::toScriptValue(QScriptEngine* eng, const QRect& rect)
{
QScriptValue temp = eng->newObject();
temp.setProperty(QStringLiteral("x"), rect.x());
temp.setProperty(QStringLiteral("y"), rect.y());
temp.setProperty(QStringLiteral("width"), rect.width());
temp.setProperty(QStringLiteral("height"), rect.height());
return temp;
}
void Rect::fromScriptValue(const QScriptValue& obj, QRect &rect)
{
QScriptValue w = obj.property(QStringLiteral("width"), QScriptValue::ResolveLocal);
QScriptValue h = obj.property(QStringLiteral("height"), QScriptValue::ResolveLocal);
QScriptValue x = obj.property(QStringLiteral("x"), QScriptValue::ResolveLocal);
QScriptValue y = obj.property(QStringLiteral("y"), QScriptValue::ResolveLocal);
if (!w.isUndefined() && !h.isUndefined() && !x.isUndefined() && !y.isUndefined()) {
rect.setX(x.toInt32());
rect.setY(y.toInt32());
rect.setWidth(w.toInt32());
rect.setHeight(h.toInt32());
}
}
// End of meta for QRect object
// Other helper functions
void KWin::MetaScripting::registration(QScriptEngine* eng)
{
qScriptRegisterMetaType<QPoint>(eng, Point::toScriptValue, Point::fromScriptValue);
qScriptRegisterMetaType<QSize>(eng, Size::toScriptValue, Size::fromScriptValue);
qScriptRegisterMetaType<QRect>(eng, Rect::toScriptValue, Rect::fromScriptValue);
qScriptRegisterSequenceMetaType<QStringList>(eng);
}
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2010 Rohan Prabhu <rohan@rohanprabhu.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef KWIN_SCRIPTING_META_H
#define KWIN_SCRIPTING_META_H
#include <QScriptValueIterator>
// forward declarations
class QPoint;
class QRect;
class QSize;
namespace KWin
{
namespace MetaScripting
{
/**
* The toScriptValue and fromScriptValue functions used in qScriptRegisterMetaType.
* Conversion functions for QPoint
*/
namespace Point
{
QScriptValue toScriptValue(QScriptEngine*, const QPoint&);
void fromScriptValue(const QScriptValue&, QPoint&);
}
/**
* The toScriptValue and fromScriptValue functions used in qScriptRegisterMetaType.
* Conversion functions for QSize
*/
namespace Size
{
QScriptValue toScriptValue(QScriptEngine*, const QSize&);
void fromScriptValue(const QScriptValue&, QSize&);
}
/**
* The toScriptValue and fromScriptValue functions used in qScriptRegisterMetaType.
* Conversion functions for QRect
* TODO: QRect conversions have to be linked from plasma as they provide a lot more
* features. As for QSize and QPoint, I don't really plan any such thing.
*/
namespace Rect
{
QScriptValue toScriptValue(QScriptEngine*, const QRect&);
void fromScriptValue(const QScriptValue&, QRect&);
}
/**
* Registers all the meta conversion to the provided QScriptEngine
*/
void registration(QScriptEngine* eng);
}
}
#endif
This diff is collapsed.
......@@ -3,6 +3,7 @@
This file is part of the KDE project.
SPDX-FileCopyrightText: 2012 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2018 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
......@@ -12,10 +13,11 @@
#include <kwinanimationeffect.h>
#include <QJSEngine>
#include <QJSValue>
class KConfigLoader;
class KPluginMetaData;
class QScriptEngine;
class QScriptValue;
namespace KWin
{
......@@ -103,30 +105,55 @@ public:
* @param defaultValue The value to return if the key is not found
* @returns The config value if present
*/
Q_SCRIPTABLE QVariant readConfig(const QString &key, const QVariant defaultValue = QVariant());
void registerShortcut(QAction *a, QScriptValue callback);
const QHash<QAction*, QScriptValue> &shortcutCallbacks() const {
return m_shortcutCallbacks;
}
QHash<int, QList<QScriptValue > > &screenEdgeCallbacks() {
Q_SCRIPTABLE QJSValue readConfig(const QString &key, const QJSValue &defaultValue = QJSValue());
Q_SCRIPTABLE int displayWidth() const;
Q_SCRIPTABLE int displayHeight() const;
Q_SCRIPTABLE int animationTime(int defaultTime) const;
Q_SCRIPTABLE void registerShortcut(const QString &objectName, const QString &text,
const QString &keySequence, const QJSValue &callback);
Q_SCRIPTABLE bool registerScreenEdge(int edge, const QJSValue &callback);
Q_SCRIPTABLE bool unregisterScreenEdge(int edge);
Q_SCRIPTABLE bool registerTouchScreenEdge(int edge, const QJSValue &callback);
Q_SCRIPTABLE bool unregisterTouchScreenEdge(int edge);
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);
Q_SCRIPTABLE QJSValue animate(const QJSValue &object);
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);
Q_SCRIPTABLE QJSValue set(const QJSValue &object);
Q_SCRIPTABLE bool retarget(quint64 animationId, const QJSValue &newTarget,
int newRemainingTime = -1);
Q_SCRIPTABLE bool retarget(const QList<quint64> &animationIds, const QJSValue &newTarget,
int newRemainingTime = -1);
Q_SCRIPTABLE bool redirect(quint64 animationId, Direction direction,
TerminationFlags terminationFlags = TerminateAtSource);
Q_SCRIPTABLE bool redirect(const QList<quint64> &animationIds, Direction direction,
TerminationFlags terminationFlags = TerminateAtSource);
Q_SCRIPTABLE bool complete(quint64 animationId);
Q_SCRIPTABLE bool complete(const QList<quint64> &animationIds);
Q_SCRIPTABLE bool cancel(quint64 animationId);
Q_SCRIPTABLE bool cancel(const QList<quint64> &animationIds);
QHash<int, QJSValueList> &screenEdgeCallbacks() {
return m_screenEdgeCallbacks;
}
QString pluginId() const;
bool isActiveFullScreenEffect() const;
bool registerTouchScreenCallback(int edge, QScriptValue callback);
bool unregisterTouchScreenCallback(int edge);
public Q_SLOTS:
//curve should be of type QEasingCurve::type or ScriptedEffect::EasingCurve
quint64 animate(KWin::EffectWindow *w, Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from = KWin::FPx2(), uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0, bool fullScreen = false, bool keepAlive = true);
quint64 set(KWin::EffectWindow *w, Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from = KWin::FPx2(), uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0, bool fullScreen = false, bool keepAlive = true);
bool retarget(quint64 animationId, KWin::FPx2 newTarget, int newRemainingTime = -1);
bool redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags = TerminateAtSource);
bool complete(quint64 animationId);
bool cancel(quint64 animationId) { return AnimationEffect::cancel(animationId); }
bool borderActivated(ElectricBorder border) override;
Q_SIGNALS:
......@@ -139,19 +166,22 @@ Q_SIGNALS:
protected:
ScriptedEffect();
QScriptEngine *engine() const;
QJSEngine *engine() const;
bool init(const QString &effectName, const QString &pathToScript);
void animationEnded(KWin::EffectWindow *w, Attribute a, uint meta) override;
private Q_SLOTS:
void signalHandlerException(const QScriptValue &value);
void globalShortcutTriggered();
private:
QScriptEngine *m_engine;
enum class AnimationType {
Animate,
Set
};
QJSValue animate_helper(const QJSValue &object, AnimationType animationType);
QJSEngine *m_engine;
QString m_effectName;
QString m_scriptFile;
QHash<QAction*, QScriptValue> m_shortcutCallbacks;
QHash<int, QList<QScriptValue> > m_screenEdgeCallbacks;
QHash<int, QJSValueList> m_screenEdgeCallbacks;
KConfigLoader *m_config;
int m_chainPosition;
QHash<int, QAction*> m_touchScreenEdgeCallbacks;
......
......@@ -19,11 +19,13 @@
#include "scripting_logging.h"
#include "options.h"
#include "screenedge.h"
#include "thumbnailitem.h"
#include "workspace.h"
#include "x11client.h"
// KDE
#include <KConfigGroup>
#include <KGlobalAccel>
#include <KPackage/PackageLoader>
// Qt
#include <QDBusConnection>
......
......@@ -7,33 +7,14 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "scriptingutils.h"
#include "scripting_logging.h"
#include <QDBusArgument>
#include <QDBusObjectPath>
#include <QDBusSignature>
namespace KWin
{
bool validateParameters(QScriptContext *context, int min, int max)
{
if (context->argumentCount() < min || context->argumentCount() > max) {
context->throwError(QScriptContext::SyntaxError,
i18nc("syntax error in KWin script", "Invalid number of arguments"));
return false;
}
return true;
}
template<>
bool validateArgumentType<QVariant>(QScriptContext *context, int argument)
{
const bool result =context->argument(argument).toVariant().isValid();
if (!result) {
context->throwError(QScriptContext::TypeError,
i18nc("KWin Scripting function received incorrect value for an expected type",
"%1 is not a variant type", context->argument(argument).toString()));
}
return result;
}
QVariant dbusToVariant(const QVariant &variant)
{
......
......@@ -10,235 +10,11 @@
#ifndef KWIN_SCRIPTINGUTILS_H
#define KWIN_SCRIPTINGUTILS_H
#include "input.h"
#include "workspace.h"
#include "screenedge.h"
#include "scripting_logging.h"
#include <KGlobalAccel>
#include <KLocalizedString>
#include <QAction>
#include <QtScript/QScriptEngine>
#include <QVariant>
namespace KWin
{
/**
* Validates that argument at @p index of given @p context is of required type.
* Throws a type error in the scripting context if there is a type mismatch.
* @param context The scripting context in which the argument type needs to be validated.
* @param index The argument index to validate
* @returns @c true if the argument is of required type, @c false otherwise
*/
template<class T>
bool validateArgumentType(QScriptContext *context, int index)
{
const bool result = context->argument(index).toVariant().canConvert<T>();
if (!result) {
context->throwError(QScriptContext::TypeError,
i18nc("KWin Scripting function received incorrect value for an expected type",
"%1 is not of required type", context->argument(index).toString()));
}
return result;
}
/**
* Validates that the argument of @p context is of specified type.
* Throws a type error in the scripting context if there is a type mismatch.
* @param context The scripting context in which the argument type needs to be validated.
* @returns @c true if the argument is of required type, @c false otherwise
*/
template<class T>
bool validateArgumentType(QScriptContext *context)