scripting: Port Script to QJSEngine

QtScript is not well maintained and deprecated in favor of QJSEngine.
icc-effect-5.26.4
Vlad Zahorodnii 2021-01-24 13:56:42 +02:00
parent 28d2650f24
commit 566d4aa27b
12 changed files with 500 additions and 885 deletions

View File

@ -1 +1 @@
registerScreenEdge(readConfig("Edge", 1), function() { workspace.slotToggleShowDesktop(); });
registerScreenEdge(readConfig("Edge", 1), () => workspace.slotToggleShowDesktop());

View File

@ -1,9 +1,9 @@
function init() {
var edge = readConfig("Edge", 1);
let edge = readConfig("Edge", 1);
if (readConfig("mode", "") == "unregister") {
unregisterScreenEdge(edge);
} else {
registerScreenEdge(edge, function() { workspace.slotToggleShowDesktop(); });
registerScreenEdge(edge, () => workspace.slotToggleShowDesktop());
}
}
options.configChanged.connect(init);

View File

@ -1 +1 @@
registerTouchScreenEdge(readConfig("Edge", 1), function() { workspace.slotToggleShowDesktop(); });
registerTouchScreenEdge(readConfig("Edge", 1), () => workspace.slotToggleShowDesktop());

View File

@ -106,7 +106,6 @@ set(kwin_SRCS
scripting/scripting_logging.cpp
scripting/scripting_model.cpp
scripting/scriptingutils.cpp
scripting/timer.cpp
scripting/workspace_wrapper.cpp
shadow.cpp
sm.cpp

View File

@ -7,6 +7,7 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "dbuscall.h"
#include "scriptingutils.h"
#include <QDBusConnection>
#include <QDBusMessage>
@ -35,7 +36,11 @@ void DBusCall::call()
emit failed();
return;
}
emit finished(watcher->reply().arguments());
QVariantList reply = watcher->reply().arguments();
std::for_each(reply.begin(), reply.end(), [](QVariant &variant) {
variant = dbusToVariant(variant);
});
emit finished(reply);
});
}

View File

@ -8,8 +8,8 @@
*/
#include "meta.h"
#include "x11client.h"
#include <QRect>
#include <QtScript/QScriptEngine>
using namespace KWin::MetaScripting;
@ -85,161 +85,12 @@ void Rect::fromScriptValue(const QScriptValue& obj, QRect &rect)
}
// End of meta for QRect object
QScriptValue AbstractClient::toScriptValue(QScriptEngine *engine, const KAbstractClientRef &client)
{
return engine->newQObject(client, QScriptEngine::QtOwnership,
QScriptEngine::ExcludeChildObjects |
QScriptEngine::ExcludeDeleteLater |
QScriptEngine::PreferExistingWrapperObject |
QScriptEngine::AutoCreateDynamicProperties);
}
void AbstractClient::fromScriptValue(const QScriptValue &value, KWin::AbstractClient *&client)
{
client = qobject_cast<KWin::AbstractClient *>(value.toQObject());
}
QScriptValue X11Client::toScriptValue(QScriptEngine *eng, const KClientRef &client)
{
return eng->newQObject(client, QScriptEngine::QtOwnership,
QScriptEngine::ExcludeChildObjects |
QScriptEngine::ExcludeDeleteLater |
QScriptEngine::PreferExistingWrapperObject |
QScriptEngine::AutoCreateDynamicProperties);
}
void X11Client::fromScriptValue(const QScriptValue &value, KWin::X11Client *&client)
{
client = qobject_cast<KWin::X11Client *>(value.toQObject());
}
QScriptValue Toplevel::toScriptValue(QScriptEngine *eng, const KToplevelRef &client)
{
return eng->newQObject(client, QScriptEngine::QtOwnership,
QScriptEngine::ExcludeChildObjects |
QScriptEngine::ExcludeDeleteLater |
QScriptEngine::PreferExistingWrapperObject |
QScriptEngine::AutoCreateDynamicProperties);
}
void Toplevel::fromScriptValue(const QScriptValue &value, KToplevelRef &client)
{
client = qobject_cast<KWin::Toplevel*>(value.toQObject());
}
// 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);
qScriptRegisterMetaType<KAbstractClientRef>(eng, AbstractClient::toScriptValue, AbstractClient::fromScriptValue);
qScriptRegisterMetaType<KClientRef>(eng, X11Client::toScriptValue, X11Client::fromScriptValue);
qScriptRegisterMetaType<KToplevelRef>(eng, Toplevel::toScriptValue, Toplevel::fromScriptValue);
qScriptRegisterSequenceMetaType<QStringList>(eng);
qScriptRegisterSequenceMetaType< QList<KWin::AbstractClient*> >(eng);
qScriptRegisterSequenceMetaType< QList<KWin::X11Client *> >(eng);
}
QScriptValue KWin::MetaScripting::configExists(QScriptContext* ctx, QScriptEngine* eng)
{
QHash<QString, QVariant> scriptConfig = (((ctx->thisObject()).data()).toVariant()).toHash();
QVariant val = scriptConfig.value((ctx->argument(0)).toString(), QVariant());
return eng->toScriptValue<bool>(val.isValid());
}
QScriptValue KWin::MetaScripting::getConfigValue(QScriptContext* ctx, QScriptEngine* eng)
{
int num = ctx->argumentCount();
QHash<QString, QVariant> scriptConfig = (((ctx->thisObject()).data()).toVariant()).toHash();
/*
* Handle config.get() separately. Compute and return immediately.
*/
if (num == 0) {
QHash<QString, QVariant>::const_iterator i;
QScriptValue ret = eng->newArray();
for (i = scriptConfig.constBegin(); i != scriptConfig.constEnd(); ++i) {
ret.setProperty(i.key(), eng->newVariant(i.value()));
}
return ret;
}
if ((num == 1) && !((ctx->argument(0)).isArray())) {
QVariant val = scriptConfig.value((ctx->argument(0)).toString(), QVariant());
if (val.isValid()) {
return eng->newVariant(val);
} else {
return QScriptValue();
}
} else {
QScriptValue ret = eng->newArray();
int j = 0;
if ((ctx->argument(0)).isArray()) {
bool simple = (num == 1) ? 0 : (ctx->argument(1)).toBool();
QScriptValue array = (ctx->argument(0));
int len = (array.property(QStringLiteral("length")).isValid()) ? array.property(QStringLiteral("length")).toNumber() : 0;
for (int i = 0; i < len; i++) {
QVariant val = scriptConfig.value(array.property(i).toString(), QVariant());
if (val.isValid()) {
if (simple) {
ret.setProperty(j, eng->newVariant(val));
} else {
ret.setProperty(array.property(i).toString(), eng->newVariant(val));
}
j++;
}
}
} else {
for (int i = 0; i < num; i++) {
QVariant val = scriptConfig.value((ctx->argument(i)).toString(), QVariant());
if (val.isValid()) {
ret.setProperty((ctx->argument(i)).toString(), eng->newVariant(val));
j = 1;
}
}
}
if (j == 0) {
return QScriptValue();
} else {
return ret;
}
}
}
void KWin::MetaScripting::supplyConfig(QScriptEngine* eng, const QVariant& scriptConfig)
{
QScriptValue configObject = eng->newObject();
configObject.setData(eng->newVariant(scriptConfig));
configObject.setProperty(QStringLiteral("get"), eng->newFunction(getConfigValue, 0), QScriptValue::Undeletable);
configObject.setProperty(QStringLiteral("exists"), eng->newFunction(configExists, 0), QScriptValue::Undeletable);
configObject.setProperty(QStringLiteral("loaded"), ((scriptConfig.toHash().empty()) ? eng->newVariant((bool)0) : eng->newVariant((bool)1)), QScriptValue::Undeletable);
(eng->globalObject()).setProperty(QStringLiteral("config"), configObject);
}
void KWin::MetaScripting::supplyConfig(QScriptEngine* eng)
{
KWin::MetaScripting::supplyConfig(eng, QVariant(QHash<QString, QVariant>()));
}
void KWin::MetaScripting::valueMerge(QScriptValue& first, QScriptValue second)
{
QScriptValueIterator value_it(second);
while (value_it.hasNext()) {
value_it.next();
first.setProperty(value_it.name(), value_it.value());
}
}

View File

@ -15,19 +15,8 @@
// forward declarations
class QPoint;
class QRect;
class QScriptContext;
class QSize;
namespace KWin {
class AbstractClient;
class Toplevel;
class X11Client;
}
typedef KWin::AbstractClient *KAbstractClientRef;
typedef KWin::X11Client *KClientRef;
typedef KWin::Toplevel* KToplevelRef;
namespace KWin
{
namespace MetaScripting
@ -66,59 +55,12 @@ QScriptValue toScriptValue(QScriptEngine*, const QRect&);
void fromScriptValue(const QScriptValue&, QRect&);
}
namespace AbstractClient
{
QScriptValue toScriptValue(QScriptEngine *engine, const KAbstractClientRef &client);
void fromScriptValue(const QScriptValue &value, KAbstractClientRef &client);
}
namespace X11Client
{
QScriptValue toScriptValue(QScriptEngine *eng, const KClientRef &client);
void fromScriptValue(const QScriptValue &value, KClientRef& client);
}
namespace Toplevel
{
QScriptValue toScriptValue(QScriptEngine *eng, const KToplevelRef &client);
void fromScriptValue(const QScriptValue &value, KToplevelRef& client);
}
/**
* Merges the second QScriptValue in the first one.
*/
void valueMerge(QScriptValue&, QScriptValue);
/**
* Registers all the meta conversion to the provided QScriptEngine
*/
void registration(QScriptEngine* eng);
/**
* Functions for the JS function objects, config.exists and config.get.
* Read scripting/IMPLIST for details on how they work
*/
QScriptValue configExists(QScriptContext*, QScriptEngine*);
QScriptValue getConfigValue(QScriptContext*, QScriptEngine*);
/**
* Provide a config object to the given QScriptEngine depending
* on the keys provided in the QVariant. The provided QVariant
* MUST returns (true) on isHash()
*/
void supplyConfig(QScriptEngine*, const QVariant&);
/**
* For engines whose scripts have no associated configuration.
*/
void supplyConfig(QScriptEngine*);
}
}
/**
* Code linked from plasma for QTimer.
*/
QScriptValue constructTimerClass(QScriptEngine *eng);
#endif

View File

@ -4,6 +4,7 @@
SPDX-FileCopyrightText: 2010 Rohan Prabhu <rohan@rohanprabhu.com>
SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
@ -11,7 +12,6 @@
#include "scripting.h"
// own
#include "dbuscall.h"
#include "meta.h"
#include "scriptingutils.h"
#include "workspace_wrapper.h"
#include "screenedgeitem.h"
@ -36,184 +36,27 @@
#include <QQmlContext>
#include <QQmlEngine>
#include <QQmlExpression>
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptValue>
#include <QStandardPaths>
#include <QQuickWindow>
QScriptValue kwinScriptPrint(QScriptContext *context, QScriptEngine *engine)
static QRect scriptValueToRect(const QJSValue &value)
{
KWin::AbstractScript *script = qobject_cast<KWin::Script*>(context->callee().data().toQObject());
if (!script) {
return engine->undefinedValue();
}
QString result;
QTextStream stream(&result);
for (int i = 0; i < context->argumentCount(); ++i) {
if (i > 0) {
stream << " ";
}
QScriptValue argument = context->argument(i);
if (KWin::AbstractClient *client = qscriptvalue_cast<KWin::AbstractClient *>(argument)) {
stream << client;
} else {
stream << argument.toString();
}
}
script->printMessage(result);
return engine->undefinedValue();
return QRect(value.property(QStringLiteral("x")).toInt(),
value.property(QStringLiteral("y")).toInt(),
value.property(QStringLiteral("width")).toInt(),
value.property(QStringLiteral("height")).toInt());
}
QScriptValue kwinScriptReadConfig(QScriptContext *context, QScriptEngine *engine)
static QPoint scriptValueToPoint(const QJSValue &value)
{
KWin::AbstractScript *script = qobject_cast<KWin::AbstractScript*>(context->callee().data().toQObject());
if (!script) {
return engine->undefinedValue();
}
if (context->argumentCount() < 1 || context->argumentCount() > 2) {
qCDebug(KWIN_SCRIPTING) << "Incorrect number of arguments";
return engine->undefinedValue();
}
const QString key = context->argument(0).toString();
QVariant defaultValue;
if (context->argumentCount() == 2) {
defaultValue = context->argument(1).toVariant();
}
return engine->newVariant(script->config().readEntry(key, defaultValue));
return QPoint(value.property(QStringLiteral("x")).toInt(),
value.property(QStringLiteral("y")).toInt());
}
QScriptValue kwinScriptGlobalShortcut(QScriptContext *context, QScriptEngine *engine)
static QSize scriptValueToSize(const QJSValue &value)
{
return KWin::globalShortcut<KWin::AbstractScript*>(context, engine);
}
QScriptValue kwinAssertTrue(QScriptContext *context, QScriptEngine *engine)
{
return KWin::scriptingAssert<bool>(context, engine, 1, 2, true);
}
QScriptValue kwinAssertFalse(QScriptContext *context, QScriptEngine *engine)
{
return KWin::scriptingAssert<bool>(context, engine, 1, 2, false);
}
QScriptValue kwinAssertEquals(QScriptContext *context, QScriptEngine *engine)
{
return KWin::scriptingAssert<QVariant>(context, engine, 2, 3);
}
QScriptValue kwinAssertNull(QScriptContext *context, QScriptEngine *engine)
{
if (!KWin::validateParameters(context, 1, 2)) {
return engine->undefinedValue();
}
if (!context->argument(0).isNull()) {
if (context->argumentCount() == 2) {
context->throwError(QScriptContext::UnknownError, context->argument(1).toString());
} else {
context->throwError(QScriptContext::UnknownError,
i18nc("Assertion failed in KWin script with given value",
"Assertion failed: %1 is not null", context->argument(0).toString()));
}
return engine->undefinedValue();
}
return true;
}
QScriptValue kwinAssertNotNull(QScriptContext *context, QScriptEngine *engine)
{
if (!KWin::validateParameters(context, 1, 2)) {
return engine->undefinedValue();
}
if (context->argument(0).isNull()) {
if (context->argumentCount() == 2) {
context->throwError(QScriptContext::UnknownError, context->argument(1).toString());
} else {
context->throwError(QScriptContext::UnknownError,
i18nc("Assertion failed in KWin script",
"Assertion failed: argument is null"));
}
return engine->undefinedValue();
}
return true;
}
QScriptValue kwinRegisterScreenEdge(QScriptContext *context, QScriptEngine *engine)
{
return KWin::registerScreenEdge<KWin::AbstractScript*>(context, engine);
}
QScriptValue kwinUnregisterScreenEdge(QScriptContext *context, QScriptEngine *engine)
{
return KWin::unregisterScreenEdge<KWin::AbstractScript*>(context, engine);
}
QScriptValue kwinRegisterTouchScreenEdge(QScriptContext *context, QScriptEngine *engine)
{
return KWin::registerTouchScreenEdge<KWin::Script*>(context, engine);
}
QScriptValue kwinUnregisterTouchScreenEdge(QScriptContext *context, QScriptEngine *engine)
{
return KWin::unregisterTouchScreenEdge<KWin::Script*>(context, engine);
}
QScriptValue kwinRegisterUserActionsMenu(QScriptContext *context, QScriptEngine *engine)
{
return KWin::registerUserActionsMenu<KWin::AbstractScript*>(context, engine);
}
QScriptValue kwinCallDBus(QScriptContext *context, QScriptEngine *engine)
{
KWin::AbstractScript *script = qobject_cast<KWin::AbstractScript*>(context->callee().data().toQObject());
if (!script) {
context->throwError(QScriptContext::UnknownError, QStringLiteral("Internal Error: script not registered"));
return engine->undefinedValue();
}
if (context->argumentCount() < 4) {
context->throwError(QScriptContext::SyntaxError,
i18nc("Error in KWin Script",
"Invalid number of arguments. At least service, path, interface and method need to be provided"));
return engine->undefinedValue();
}
if (!KWin::validateArgumentType<QString, QString, QString, QString>(context)) {
context->throwError(QScriptContext::SyntaxError,
i18nc("Error in KWin Script",
"Invalid type. Service, path, interface and method need to be string values"));
return engine->undefinedValue();
}
const QString service = context->argument(0).toString();
const QString path = context->argument(1).toString();
const QString interface = context->argument(2).toString();
const QString method = context->argument(3).toString();
int argumentsCount = context->argumentCount();
if (context->argument(argumentsCount-1).isFunction()) {
--argumentsCount;
}
QDBusMessage msg = QDBusMessage::createMethodCall(service, path, interface, method);
QVariantList arguments;
for (int i=4; i<argumentsCount; ++i) {
if (context->argument(i).isArray()) {
QStringList stringArray = engine->fromScriptValue<QStringList>(context->argument(i));
arguments << QVariant::fromValue(stringArray);
} else {
arguments << context->argument(i).toVariant();
}
}
if (!arguments.isEmpty()) {
msg.setArguments(arguments);
}
if (argumentsCount == context->argumentCount()) {
// no callback, just fire and forget
QDBusConnection::sessionBus().asyncCall(msg);
} else {
// with a callback
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(msg), script);
watcher->setProperty("callback", script->registerCallback(context->argument(context->argumentCount()-1)));
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, script, &KWin::AbstractScript::slotPendingDBusCall);
}
return engine->undefinedValue();
return QSize(value.property(QStringLiteral("width")).toInt(),
value.property(QStringLiteral("height")).toInt());
}
KWin::AbstractScript::AbstractScript(int id, QString scriptName, QString pluginName, QObject *parent)
@ -242,205 +85,24 @@ void KWin::AbstractScript::stop()
deleteLater();
}
void KWin::AbstractScript::printMessage(const QString &message)
{
qCDebug(KWIN_SCRIPTING) << fileName() << ":" << message;
emit print(message);
}
void KWin::AbstractScript::registerShortcut(QAction *a, QScriptValue callback)
{
m_shortcutCallbacks.insert(a, callback);
connect(a, &QAction::triggered, this, &AbstractScript::globalShortcutTriggered);
}
void KWin::AbstractScript::globalShortcutTriggered()
{
callGlobalShortcutCallback<KWin::AbstractScript*>(this, sender());
}
bool KWin::AbstractScript::borderActivated(KWin::ElectricBorder edge)
{
screenEdgeActivated(this, edge);
return true;
}
void KWin::Script::installScriptFunctions(QScriptEngine* engine)
{
// add our print
QScriptValue printFunc = engine->newFunction(kwinScriptPrint);
printFunc.setData(engine->newQObject(this));
engine->globalObject().setProperty(QStringLiteral("print"), printFunc);
// add read config
QScriptValue configFunc = engine->newFunction(kwinScriptReadConfig);
configFunc.setData(engine->newQObject(this));
engine->globalObject().setProperty(QStringLiteral("readConfig"), configFunc);
QScriptValue dbusCallFunc = engine->newFunction(kwinCallDBus);
dbusCallFunc.setData(engine->newQObject(this));
engine->globalObject().setProperty(QStringLiteral("callDBus"), dbusCallFunc);
// add global Shortcut
registerGlobalShortcutFunction(this, engine, kwinScriptGlobalShortcut);
// add screen edge
registerScreenEdgeFunction(this, engine, kwinRegisterScreenEdge);
unregisterScreenEdgeFunction(this, engine, kwinUnregisterScreenEdge);
registerTouchScreenEdgeFunction(this, engine, kwinRegisterTouchScreenEdge);
unregisterTouchScreenEdgeFunction(this, engine, kwinUnregisterTouchScreenEdge);
// add user actions menu register function
registerUserActionsMenuFunction(this, engine, kwinRegisterUserActionsMenu);
// add assertions
QScriptValue assertTrueFunc = engine->newFunction(kwinAssertTrue);
engine->globalObject().setProperty(QStringLiteral("assertTrue"), assertTrueFunc);
engine->globalObject().setProperty(QStringLiteral("assert"), assertTrueFunc);
QScriptValue assertFalseFunc = engine->newFunction(kwinAssertFalse);
engine->globalObject().setProperty(QStringLiteral("assertFalse"), assertFalseFunc);
QScriptValue assertEqualsFunc = engine->newFunction(kwinAssertEquals);
engine->globalObject().setProperty(QStringLiteral("assertEquals"), assertEqualsFunc);
QScriptValue assertNullFunc = engine->newFunction(kwinAssertNull);
engine->globalObject().setProperty(QStringLiteral("assertNull"), assertNullFunc);
engine->globalObject().setProperty(QStringLiteral("assertEquals"), assertEqualsFunc);
QScriptValue assertNotNullFunc = engine->newFunction(kwinAssertNotNull);
engine->globalObject().setProperty(QStringLiteral("assertNotNull"), assertNotNullFunc);
// global properties
engine->globalObject().setProperty(QStringLiteral("KWin"), engine->newQMetaObject(&QtScriptWorkspaceWrapper::staticMetaObject));
QScriptValue workspace = engine->newQObject(Scripting::self()->workspaceWrapper(), QScriptEngine::QtOwnership,
QScriptEngine::ExcludeDeleteLater);
engine->globalObject().setProperty(QStringLiteral("workspace"), workspace, QScriptValue::Undeletable);
// install meta functions
KWin::MetaScripting::registration(engine);
}
int KWin::AbstractScript::registerCallback(QScriptValue value)
{
int id = m_callbacks.size();
m_callbacks.insert(id, value);
return id;
}
void KWin::AbstractScript::slotPendingDBusCall(QDBusPendingCallWatcher* watcher)
{
if (watcher->isError()) {
qCDebug(KWIN_SCRIPTING) << "Received D-Bus message is error";
watcher->deleteLater();
return;
}
const int id = watcher->property("callback").toInt();
QDBusMessage reply = watcher->reply();
QScriptValue callback (m_callbacks.value(id));
QScriptValueList arguments;
foreach (const QVariant &argument, reply.arguments()) {
arguments << callback.engine()->newVariant(argument);
}
callback.call(QScriptValue(), arguments);
m_callbacks.remove(id);
watcher->deleteLater();
}
void KWin::AbstractScript::registerUseractionsMenuCallback(QScriptValue callback)
{
m_userActionsMenuCallbacks.append(callback);
}
QList< QAction * > KWin::AbstractScript::actionsForUserActionMenu(KWin::AbstractClient *c, QMenu *parent)
{
QList<QAction*> returnActions;
for (QList<QScriptValue>::const_iterator it = m_userActionsMenuCallbacks.constBegin(); it != m_userActionsMenuCallbacks.constEnd(); ++it) {
QScriptValue callback(*it);
QScriptValueList arguments;
arguments << callback.engine()->newQObject(c);
QScriptValue actions = callback.call(QScriptValue(), arguments);
if (!actions.isValid() || actions.isUndefined() || actions.isNull()) {
// script does not want to handle this Client
continue;
}
if (actions.isObject()) {
QAction *a = scriptValueToAction(actions, parent);
if (a) {
returnActions << a;
}
}
}
return returnActions;
}
QAction *KWin::AbstractScript::scriptValueToAction(QScriptValue &value, QMenu *parent)
{
QScriptValue titleValue = value.property(QStringLiteral("text"));
QScriptValue checkableValue = value.property(QStringLiteral("checkable"));
QScriptValue checkedValue = value.property(QStringLiteral("checked"));
QScriptValue itemsValue = value.property(QStringLiteral("items"));
QScriptValue triggeredValue = value.property(QStringLiteral("triggered"));
if (!titleValue.isValid()) {
// title not specified - does not make any sense to include
return nullptr;
}
const QString title = titleValue.toString();
const bool checkable = checkableValue.isValid() && checkableValue.toBool();
const bool checked = checkable && checkedValue.isValid() && checkedValue.toBool();
// either a menu or a menu item
if (itemsValue.isValid()) {
if (!itemsValue.isArray()) {
// not an array, so cannot be a menu
return nullptr;
}
QScriptValue lengthValue = itemsValue.property(QStringLiteral("length"));
if (!lengthValue.isValid() || !lengthValue.isNumber() || lengthValue.toInteger() == 0) {
// length property missing
return nullptr;
}
return createMenu(title, itemsValue, parent);
} else if (triggeredValue.isValid()) {
// normal item
return createAction(title, checkable, checked, triggeredValue, parent);
}
return nullptr;
}
QAction *KWin::AbstractScript::createAction(const QString &title, bool checkable, bool checked, QScriptValue &callback, QMenu *parent)
{
QAction *action = new QAction(title, parent);
action->setCheckable(checkable);
action->setChecked(checked);
// TODO: rename m_shortcutCallbacks
m_shortcutCallbacks.insert(action, callback);
connect(action, &QAction::triggered, this, &AbstractScript::globalShortcutTriggered);
connect(action, &QObject::destroyed, this, &AbstractScript::actionDestroyed);
return action;
}
QAction *KWin::AbstractScript::createMenu(const QString &title, QScriptValue &items, QMenu *parent)
{
QMenu *menu = new QMenu(title, parent);
const int length = static_cast<int>(items.property(QStringLiteral("length")).toInteger());
for (int i=0; i<length; ++i) {
QScriptValue value = items.property(QString::number(i));
if (!value.isValid()) {
continue;
}
if (value.isObject()) {
QAction *a = scriptValueToAction(value, menu);
if (a) {
menu->addAction(a);
}
}
}
return menu->menuAction();
}
void KWin::AbstractScript::actionDestroyed(QObject *object)
{
// TODO: Qt 5 - change to lambda function
m_shortcutCallbacks.remove(static_cast<QAction*>(object));
}
KWin::Script::Script(int id, QString scriptName, QString pluginName, QObject* parent)
: AbstractScript(id, scriptName, pluginName, parent)
, m_engine(new QScriptEngine(this))
, m_engine(new QJSEngine(this))
, m_starting(false)
, m_agent(new ScriptUnloaderAgent(this))
{
// TODO: Remove in kwin 6. We have these converters only for compatibility reasons.
if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QRect>()) {
QMetaType::registerConverter<QJSValue, QRect>(scriptValueToRect);
}
if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QPoint>()) {
QMetaType::registerConverter<QJSValue, QPoint>(scriptValueToPoint);
}
if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QSize>()) {
QMetaType::registerConverter<QJSValue, QSize>(scriptValueToSize);
}
qRegisterMetaType<QList<KWin::AbstractClient *>>();
QDBusConnection::sessionBus().registerObject(QLatin1Char('/') + QString::number(scriptId()), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportScriptableInvokables);
}
@ -497,18 +159,74 @@ void KWin::Script::slotScriptLoadedFromFile()
return;
}
QScriptValue optionsValue = m_engine->newQObject(options, QScriptEngine::QtOwnership,
QScriptEngine::ExcludeSuperClassContents | QScriptEngine::ExcludeDeleteLater);
m_engine->globalObject().setProperty(QStringLiteral("options"), optionsValue, QScriptValue::Undeletable);
m_engine->globalObject().setProperty(QStringLiteral("QTimer"), constructTimerClass(m_engine));
QObject::connect(m_engine, &QScriptEngine::signalHandlerException, this, &Script::sigException);
KWin::MetaScripting::supplyConfig(m_engine);
installScriptFunctions(m_engine);
// Install console functions (e.g. console.assert(), console.log(), etc).
m_engine->installExtensions(QJSEngine::ConsoleExtension);
QScriptValue ret = m_engine->evaluate(QString::fromUtf8(watcher->result()));
// Make the timer visible to QJSEngine.
QJSValue timerMetaObject = m_engine->newQMetaObject(&QTimer::staticMetaObject);
m_engine->globalObject().setProperty("QTimer", timerMetaObject);
if (ret.isError()) {
sigException(ret);
// Expose enums.
m_engine->globalObject().setProperty(QStringLiteral("KWin"), m_engine->newQMetaObject(&QtScriptWorkspaceWrapper::staticMetaObject));
// Make the options object visible to QJSEngine.
QJSValue optionsObject = m_engine->newQObject(options);
QQmlEngine::setObjectOwnership(options, QQmlEngine::CppOwnership);
m_engine->globalObject().setProperty(QStringLiteral("options"), optionsObject);
// Make the workspace visible to QJSEngine.
QJSValue workspaceObject = m_engine->newQObject(Scripting::self()->workspaceWrapper());
QQmlEngine::setObjectOwnership(Scripting::self()->workspaceWrapper(), QQmlEngine::CppOwnership);
m_engine->globalObject().setProperty(QStringLiteral("workspace"), workspaceObject);
QJSValue self = m_engine->newQObject(this);
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
static const QStringList globalProperties {
QStringLiteral("readConfig"),
QStringLiteral("callDBus"),
QStringLiteral("registerShortcut"),
QStringLiteral("registerScreenEdge"),
QStringLiteral("unregisterScreenEdge"),
QStringLiteral("registerTouchScreenEdge"),
QStringLiteral("unregisterTouchScreenEdge"),
QStringLiteral("registerUserActionsMenu"),
};
for (const QString &propertyName : globalProperties) {
m_engine->globalObject().setProperty(propertyName, self.property(propertyName));
}
// Inject assertion functions. It would be better to create a module with all
// this assert functions or just deprecate them in favor of console.assert().
QJSValue result = m_engine->evaluate(QStringLiteral(R"(
function assert(condition, message) {
console.assert(condition, message || 'Assertion failed');
}
function assertTrue(condition, message) {
console.assert(condition, message || 'Assertion failed');
}
function assertFalse(condition, message) {
console.assert(!condition, message || 'Assertion failed');
}
function assertNull(value, message) {
console.assert(value === null, message || 'Assertion failed');
}
function assertNotNull(value, message) {
console.assert(value !== null, message || 'Assertion failed');
}
function assertEquals(expected, actual, message) {
console.assert(expected === actual, message || 'Assertion failed');
}
)"));
Q_ASSERT(!result.isError());
result = m_engine->evaluate(QString::fromUtf8(watcher->result()), fileName());
if (result.isError()) {
qCWarning(KWIN_SCRIPTING, "%s:%d: error: %s", qPrintable(fileName()),
result.property(QStringLiteral("lineNumber")).toInt(),
qPrintable(result.property(QStringLiteral("message")).toString()));
deleteLater();
}
@ -523,63 +241,274 @@ void KWin::Script::slotScriptLoadedFromFile()
m_starting = false;
}
void KWin::Script::sigException(const QScriptValue& exception)
QVariant KWin::Script::readConfig(const QString &key, const QVariant &defaultValue)
{
QScriptValue ret = exception;
if (ret.isError()) {
qCDebug(KWIN_SCRIPTING) << "defaultscript encountered an error at [Line " << m_engine->uncaughtExceptionLineNumber() << "]";
qCDebug(KWIN_SCRIPTING) << "Message: " << ret.toString();
qCDebug(KWIN_SCRIPTING) << "-----------------";
QScriptValueIterator iter(ret);
while (iter.hasNext()) {
iter.next();
qCDebug(KWIN_SCRIPTING) << " " << iter.name() << ": " << iter.value().toString();
}
}
emit printError(exception.toString());
stop();
return config().readEntry(key, defaultValue);
}
bool KWin::Script::registerTouchScreenCallback(int edge, QScriptValue callback)
void KWin::Script::callDBus(const QString &service, const QString &path, const QString &interface,
const QString &method, const QJSValue &arg1, const QJSValue &arg2,
const QJSValue &arg3, const QJSValue &arg4, const QJSValue &arg5,
const QJSValue &arg6, const QJSValue &arg7, const QJSValue &arg8,
const QJSValue &arg9)
{
if (m_touchScreenEdgeCallbacks.constFind(edge) != m_touchScreenEdgeCallbacks.constEnd()) {
QJSValueList jsArguments;
jsArguments.reserve(9);
if (!arg1.isUndefined()) {
jsArguments << arg1;
}
if (!arg2.isUndefined()) {
jsArguments << arg2;
}
if (!arg3.isUndefined()) {
jsArguments << arg3;
}
if (!arg4.isUndefined()) {
jsArguments << arg4;
}
if (!arg5.isUndefined()) {
jsArguments << arg5;
}
if (!arg6.isUndefined()) {
jsArguments << arg6;
}
if (!arg7.isUndefined()) {
jsArguments << arg7;
}
if (!arg8.isUndefined()) {
jsArguments << arg8;
}
if (!arg9.isUndefined()) {
jsArguments << arg9;
}
QJSValue callback;
if (!jsArguments.isEmpty() && jsArguments.last().isCallable()) {
callback = jsArguments.takeLast();
}
QVariantList dbusArguments;
dbusArguments.reserve(jsArguments.count());
for (const QJSValue &jsArgument : qAsConst(jsArguments)) {
dbusArguments << jsArgument.toVariant();
}
QDBusMessage message = QDBusMessage::createMethodCall(service, path, interface, method);
message.setArguments(dbusArguments);
const QDBusPendingCall call = QDBusConnection::sessionBus().asyncCall(message);
if (callback.isUndefined()) {
return;
}
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, callback](QDBusPendingCallWatcher *self) {
self->deleteLater();
if (self->isError()) {
qCDebug(KWIN_SCRIPTING) << "Received D-Bus message is error";
return;
}
QJSValueList arguments;
const QVariantList reply = self->reply().arguments();
for (const QVariant &variant : reply) {
arguments << m_engine->toScriptValue(dbusToVariant(variant));
}
QJSValue(callback).call(arguments);
});
}
bool KWin::Script::registerShortcut(const QString &objectName, const QString &text, const QString &keySequence, const QJSValue &callback)
{
if (!callback.isCallable()) {
m_engine->throwError(QStringLiteral("Shortcut handler must be callable"));
return false;
}
QAction *action = new QAction(this);
connect(action, &QAction::triggered, this,
[callback] {
QScriptValue invoke(callback);
invoke.call();
}
);
ScreenEdges::self()->reserveTouch(KWin::ElectricBorder(edge), action);
m_touchScreenEdgeCallbacks.insert(edge, action);
action->setObjectName(objectName);
action->setText(text);
const QKeySequence shortcut = keySequence;
KGlobalAccel::self()->setShortcut(action, { shortcut });
input()->registerShortcut(shortcut, action);
connect(action, &QAction::triggered, this, [this, action, callback]() {
QJSValue(callback).call({ m_engine->toScriptValue(action) });
});
return true;
}
bool KWin::Script::unregisterTouchScreenCallback(int edge)
bool KWin::Script::registerScreenEdge(int edge, const QJSValue &callback)
{
if (!callback.isCallable()) {
m_engine->throwError(QStringLiteral("Screen edge handler must be callable"));
return false;
}
QJSValueList &callbacks = m_screenEdgeCallbacks[edge];
if (callbacks.isEmpty()) {
ScreenEdges::self()->reserve(static_cast<KWin::ElectricBorder>(edge), this, "slotBorderActivated");
}
callbacks << callback;
return true;
}
bool KWin::Script::unregisterScreenEdge(int edge)
{
auto it = m_screenEdgeCallbacks.find(edge);
if (it == m_screenEdgeCallbacks.end()) {
return false;
}
ScreenEdges::self()->unreserve(static_cast<KWin::ElectricBorder>(edge), this);
m_screenEdgeCallbacks.erase(it);
return true;
}
bool KWin::Script::registerTouchScreenEdge(int edge, const QJSValue &callback)
{
if (!callback.isCallable()) {
m_engine->throwError(QStringLiteral("Touch screen edge handler must be callable"));
return false;
}
if (m_touchScreenEdgeCallbacks.contains(edge)) {
return false;
}
QAction *action = new QAction(this);
ScreenEdges::self()->reserveTouch(KWin::ElectricBorder(edge), action);
m_touchScreenEdgeCallbacks.insert(edge, action);
connect(action, &QAction::triggered, this, [callback]() {
QJSValue(callback).call();
});
return true;
}
bool KWin::Script::unregisterTouchScreenEdge(int edge)
{
auto it = m_touchScreenEdgeCallbacks.find(edge);
if (it == m_touchScreenEdgeCallbacks.end()) {
return false;
}
delete it.value();
m_touchScreenEdgeCallbacks.erase(it);
return true;
}
KWin::ScriptUnloaderAgent::ScriptUnloaderAgent(KWin::Script *script)
: QScriptEngineAgent(script->engine())
, m_script(script)
void KWin::Script::registerUserActionsMenu(const QJSValue &callback)
{
script->engine()->setAgent(this);
if (!callback.isCallable()) {
m_engine->throwError(QStringLiteral("User action handler must be callable"));
return;
}
m_userActionsMenuCallbacks.append(callback);
}
void KWin::ScriptUnloaderAgent::scriptUnload(qint64 id)
QList<QAction *> KWin::Script::actionsForUserActionMenu(KWin::AbstractClient *client, QMenu *parent)
{
Q_UNUSED(id)
m_script->stop();
QList<QAction *> actions;
actions.reserve(m_userActionsMenuCallbacks.count());
for (QJSValue callback : m_userActionsMenuCallbacks) {
QJSValue result = callback.call({ m_engine->toScriptValue(client) });
if (result.isError()) {
continue;
}
if (!result.isObject()) {
continue;
}
if (QAction *action = scriptValueToAction(result, parent)) {
actions << action;
}
}
return actions;
}
bool KWin::Script::slotBorderActivated(ElectricBorder border)
{
const QJSValueList callbacks = m_screenEdgeCallbacks.value(border);
if (callbacks.isEmpty()) {
return false;
}
std::for_each(callbacks.begin(), callbacks.end(), [](QJSValue callback) {
callback.call();
});
return true;
}
QAction *KWin::Script::scriptValueToAction(const QJSValue &value, QMenu *parent)
{
const QString title = value.property(QStringLiteral("text")).toString();
if (title.isEmpty()) {
return nullptr;
}
// Either a menu or a menu item.
const QJSValue itemsValue = value.property(QStringLiteral("items"));
if (!itemsValue.isUndefined()) {
return createMenu(title, itemsValue, parent);
}
return createAction(title, value, parent);
}
QAction *KWin::Script::createAction(const QString &title, const QJSValue &item, QMenu *parent)
{
const QJSValue callback = item.property(QStringLiteral("triggered"));
if (!callback.isCallable()) {
return nullptr;
}
const bool checkable = item.property(QStringLiteral("checkable")).toBool();
const bool checked = item.property(QStringLiteral("checked")).toBool();
QAction *action = new QAction(title, parent);
action->setCheckable(checkable);
action->setChecked(checked);
connect(action, &QAction::triggered, this, [this, action, callback]() {
QJSValue(callback).call({ m_engine->toScriptValue(action) });
});
return action;
}
QAction *KWin::Script::createMenu(const QString &title, const QJSValue &items, QMenu *parent)
{
if (!items.isArray()) {
return nullptr;
}
const int length = items.property(QStringLiteral("length")).toInt();
if (!length) {
return nullptr;
}
QMenu *menu = new QMenu(title, parent);
for (int i = 0; i < length; ++i) {
const QJSValue value = items.property(QString::number(i));
if (!value.isObject()) {
continue;
}
if (QAction *action = scriptValueToAction(value, menu)) {
menu->addAction(action);
}
}
return menu->menuAction();
}
KWin::DeclarativeScript::DeclarativeScript(int id, QString scriptName, QString pluginName, QObject* parent)
@ -880,8 +809,11 @@ KWin::Scripting::~Scripting()
QList< QAction * > KWin::Scripting::actionsForUserActionMenu(KWin::AbstractClient *c, QMenu *parent)
{
QList<QAction*> actions;
foreach (AbstractScript *script, scripts) {
actions << script->actionsForUserActionMenu(c, parent);
for (AbstractScript *s : scripts) {
// TODO: Allow declarative scripts to add their own user actions.
if (Script *script = qobject_cast<Script *>(s)) {
actions << script->actionsForUserActionMenu(c, parent);
}
}
return actions;
}

View File

@ -4,6 +4,7 @@
SPDX-FileCopyrightText: 2010 Rohan Prabhu <rohan@rohanprabhu.com>
SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
@ -13,10 +14,9 @@
#include <kwinglobals.h>
#include <QFile>
#include <QHash>
#include <QStringList>
#include <QtScript/QScriptEngineAgent>
#include <QJSEngine>
#include <QJSValue>
#include <QDBusContext>
@ -26,12 +26,8 @@ class QQmlComponent;
class QQmlContext;
class QQmlEngine;
class QAction;
class QDBusPendingCallWatcher;
class QGraphicsScene;
class QMenu;
class QMutex;
class QScriptEngine;
class QScriptValue;
class QQuickWindow;
class KConfigGroup;
@ -41,9 +37,7 @@ typedef QList< QPair<bool, QPair<QString, QString > > > LoadScriptList;
namespace KWin
{
class AbstractClient;
class ScriptUnloaderAgent;
class QtScriptWorkspaceWrapper;
class X11Client;
class KWIN_EXPORT AbstractScript : public QObject
{
@ -51,15 +45,76 @@ class KWIN_EXPORT AbstractScript : public QObject
public:
AbstractScript(int id, QString scriptName, QString pluginName, QObject *parent = nullptr);
~AbstractScript() override;
int scriptId() const {
return m_scriptId;
}
QString fileName() const {
return m_fileName;
}
const QString &pluginName() {
return m_pluginName;
}
bool running() const {
return m_running;
}
KConfigGroup config() const;
public Q_SLOTS:
void stop();
virtual void run() = 0;
Q_SIGNALS:
void runningChanged(bool);
protected:
void setRunning(bool running) {
if (m_running == running) {
return;
}
m_running = running;
emit runningChanged(m_running);
}
private:
int m_scriptId;
QString m_fileName;
QString m_pluginName;
bool m_running;
};
class Script : public AbstractScript, QDBusContext
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.Scripting")
public:
Script(int id, QString scriptName, QString pluginName, QObject *parent = nullptr);
virtual ~Script();
Q_INVOKABLE QVariant readConfig(const QString &key, const QVariant &defaultValue = QVariant());
Q_INVOKABLE void callDBus(const QString &service, const QString &path,
const QString &interface, const QString &method,
const QJSValue &arg1 = QJSValue(),
const QJSValue &arg2 = QJSValue(),
const QJSValue &arg3 = QJSValue(),
const QJSValue &arg4 = QJSValue(),
const QJSValue &arg5 = QJSValue(),
const QJSValue &arg6 = QJSValue(),
const QJSValue &arg7 = QJSValue(),
const QJSValue &arg8 = QJSValue(),
const QJSValue &arg9 = QJSValue());
Q_INVOKABLE bool registerShortcut(const QString &objectName, const QString &text,
const QString &keySequence, const QJSValue &callback);
Q_INVOKABLE bool registerScreenEdge(int edge, const QJSValue &callback);
Q_INVOKABLE bool unregisterScreenEdge(int edge);
Q_INVOKABLE bool registerTouchScreenEdge(int edge, const QJSValue &callback);
Q_INVOKABLE bool unregisterTouchScreenEdge(int edge);
void printMessage(const QString &message);
void registerShortcut(QAction *a, QScriptValue callback);
/**
* @brief Registers the given @p callback to be invoked whenever the UserActionsMenu is about
* to be showed. In the callback the script can create a further sub menu or menu entry to be
@ -69,7 +124,8 @@ public:
* @return void
* @see actionsForUserActionMenu
*/
void registerUseractionsMenuCallback(QScriptValue callback);
Q_INVOKABLE void registerUserActionsMenu(const QJSValue &callback);
/**
* @brief Creates actions for the UserActionsMenu by invoking the registered callbacks.
*
@ -112,54 +168,29 @@ public:
* @return QList< QAction* > List of QActions obtained from asking the registered callbacks
* @see registerUseractionsMenuCallback
*/
QList<QAction*> actionsForUserActionMenu(AbstractClient *c, QMenu *parent);
KConfigGroup config() const;
const QHash<QAction*, QScriptValue> &shortcutCallbacks() const {
return m_shortcutCallbacks;
}
QHash<int, QList<QScriptValue > > &screenEdgeCallbacks() {
return m_screenEdgeCallbacks;
}
int registerCallback(QScriptValue value);
QList<QAction *> actionsForUserActionMenu(AbstractClient *client, QMenu *parent);
public Q_SLOTS:
Q_SCRIPTABLE void stop();
Q_SCRIPTABLE virtual void run() = 0;
void slotPendingDBusCall(QDBusPendingCallWatcher *watcher);
void run() override;
private Q_SLOTS:
void globalShortcutTriggered();
bool borderActivated(ElectricBorder edge);
/**
* @brief Slot invoked when a menu action is destroyed. Used to remove the action and callback
* from the map of actions.
*
* @param object The destroyed action
* Callback for when loadScriptFromFile has finished.
*/
void actionDestroyed(QObject *object);
void slotScriptLoadedFromFile();
Q_SIGNALS:
Q_SCRIPTABLE void print(const QString &text);
void runningChanged(bool);
protected:
bool running() const {
return m_running;
}
void setRunning(bool running) {
if (m_running == running) {
return;
}
m_running = running;
emit runningChanged(m_running);
}
int scriptId() const {
return m_scriptId;
}
/**
* Called when any reserve screen edge is triggered.
*/
bool slotBorderActivated(ElectricBorder border);
private:
/**
* Read the script from file into a byte array.
* If file cannot be read an empty byte array is returned.
*/
QByteArray loadScriptFromFile(const QString &fileName);
/**
* @brief Parses the @p value to either a QMenu or QAction.
*
@ -167,7 +198,8 @@ private:
* @param parent The parent to use for the created menu or action
* @return QAction* The parsed action or menu action, if parsing fails returns @c null.
*/
QAction *scriptValueToAction(QScriptValue &value, QMenu *parent);
QAction *scriptValueToAction(const QJSValue &value, QMenu *parent);
/**
* @brief Creates a new QAction from the provided data and registers it for invoking the
* @p callback when the action is triggered.
@ -182,7 +214,8 @@ private:
* @param parent The parent to be used for the new created action
* @return QAction* The created action
*/
QAction *createAction(const QString &title, bool checkable, bool checked, QScriptValue &callback, QMenu *parent);
QAction *createAction(const QString &title, const QJSValue &item, QMenu *parent);
/**
* @brief Parses the @p items and creates a QMenu from it.
*
@ -191,75 +224,14 @@ private:
* @param parent The parent to use for the new created menu
* @return QAction* The menu action for the new Menu
*/
QAction *createMenu(const QString &title, QScriptValue &items, QMenu *parent);
int m_scriptId;
QString m_fileName;
QString m_pluginName;
bool m_running;
QHash<QAction*, QScriptValue> m_shortcutCallbacks;
QHash<int, QList<QScriptValue> > m_screenEdgeCallbacks;
QHash<int, QScriptValue> m_callbacks;
/**
* @brief List of registered functions to call when the UserActionsMenu is about to show
* to add further entries.
*/
QList<QScriptValue> m_userActionsMenuCallbacks;
};
QAction *createMenu(const QString &title, const QJSValue &items, QMenu *parent);
class Script : public AbstractScript, QDBusContext
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.Scripting")
public:
Script(int id, QString scriptName, QString pluginName, QObject *parent = nullptr);
~Script() override;
QScriptEngine *engine() {
return m_engine;
}
bool registerTouchScreenCallback(int edge, QScriptValue callback);
bool unregisterTouchScreenCallback(int edge);
public Q_SLOTS:
Q_SCRIPTABLE void run() override;
Q_SIGNALS:
Q_SCRIPTABLE void printError(const QString &text);
private Q_SLOTS:
/**
* A nice clean way to handle exceptions in scripting.
* TODO: Log to file, show from notifier..
*/
void sigException(const QScriptValue &exception);
/**
* Callback for when loadScriptFromFile has finished.
*/
void slotScriptLoadedFromFile();
private:
void installScriptFunctions(QScriptEngine *engine);
/**
* Read the script from file into a byte array.
* If file cannot be read an empty byte array is returned.
*/
QByteArray loadScriptFromFile(const QString &fileName);
QScriptEngine *m_engine;
QJSEngine *m_engine;
QDBusMessage m_invocationContext;
bool m_starting;
QScopedPointer<ScriptUnloaderAgent> m_agent;
QHash<int, QAction*> m_touchScreenEdgeCallbacks;
};
class ScriptUnloaderAgent : public QScriptEngineAgent
{
public:
explicit ScriptUnloaderAgent(Script *script);
void scriptUnload(qint64 id) override;
private:
Script *m_script;
QHash<int, QJSValueList> m_screenEdgeCallbacks;
QHash<int, QAction *> m_touchScreenEdgeCallbacks;
QJSValueList m_userActionsMenuCallbacks;
};
class DeclarativeScript : public AbstractScript

View File

@ -8,6 +8,9 @@
*/
#include "scriptingutils.h"
#include <QDBusObjectPath>
#include <QDBusSignature>
namespace KWin
{
bool validateParameters(QScriptContext *context, int min, int max)
@ -32,4 +35,58 @@ bool validateArgumentType<QVariant>(QScriptContext *context, int argument)
return result;
}
QVariant dbusToVariant(const QVariant &variant)
{
if (variant.canConvert<QDBusArgument>()) {
const QDBusArgument argument = variant.value<QDBusArgument>();
switch (argument.currentType()) {
case QDBusArgument::BasicType:
return dbusToVariant(argument.asVariant());
case QDBusArgument::VariantType:
return dbusToVariant(argument.asVariant().value<QDBusVariant>().variant());
case QDBusArgument::ArrayType: {
QVariantList array;
argument.beginArray();
while (!argument.atEnd()) {
const QVariant value = argument.asVariant();
array.append(dbusToVariant(value));
}
argument.endArray();
return array; }
case QDBusArgument::StructureType: {
QVariantList structure;
argument.beginStructure();
while (!argument.atEnd()) {
const QVariant value = argument.asVariant();
structure.append(dbusToVariant(value));
}
argument.endStructure();
return structure; }
case QDBusArgument::MapType: {
QVariantMap map;
argument.beginMap();
while (!argument.atEnd()) {
argument.beginMapEntry();
const QVariant key = argument.asVariant();
const QVariant value = argument.asVariant();
argument.endMapEntry();
map.insert(key.toString(), dbusToVariant(value));
}
argument.endMap();
return map; }
default:
qCWarning(KWIN_SCRIPTING) << "Couldn't unwrap QDBusArgument of type" << argument.currentType();
return variant;
}
} else if (variant.canConvert<QDBusObjectPath>()) {
return variant.value<QDBusObjectPath>().path();
} else if (variant.canConvert<QDBusSignature>()) {
return variant.value<QDBusSignature>().signature();
} else if (variant.canConvert<QDBusVariant>()) {
return dbusToVariant(variant.value<QDBusVariant>().variant());
}
return variant;
}
}

View File

@ -159,31 +159,6 @@ QScriptValue registerScreenEdge(QScriptContext *context, QScriptEngine *engine)
return engine->newVariant(true);
}
template<class T>
QScriptValue unregisterScreenEdge(QScriptContext *context, QScriptEngine *engine)
{
T script = qobject_cast<T>(context->callee().data().toQObject());
if (!script) {
return engine->undefinedValue();
}
if (!validateParameters(context, 1, 1)) {
return engine->undefinedValue();
}
if (!validateArgumentType<int>(context)) {
return engine->undefinedValue();
}
const int edge = context->argument(0).toVariant().toInt();
QHash<int, QList<QScriptValue> >::iterator it = script->screenEdgeCallbacks().find(edge);
if (it == script->screenEdgeCallbacks().end()) {
//not previously registered
return engine->newVariant(false);
}
ScreenEdges::self()->unreserve(static_cast<KWin::ElectricBorder>(edge), script);
script->screenEdgeCallbacks().erase(it);
return engine->newVariant(true);
}
template<class T>
QScriptValue registerTouchScreenEdge(QScriptContext *context, QScriptEngine *engine)
{
@ -224,25 +199,6 @@ QScriptValue unregisterTouchScreenEdge(QScriptContext *context, QScriptEngine *e
return engine->newVariant(ret);
}
template<class T>
QScriptValue registerUserActionsMenu(QScriptContext *context, QScriptEngine *engine)
{
T script = qobject_cast<T>(context->callee().data().toQObject());
if (!script) {
return engine->undefinedValue();
}
if (!validateParameters(context, 1, 1)) {
return engine->undefinedValue();
}
if (!context->argument(0).isFunction()) {
context->throwError(QScriptContext::SyntaxError, i18nc("KWin Scripting error thrown due to incorrect argument",
"Argument for registerUserActionsMenu needs to be a callback"));
return engine->undefinedValue();
}
script->registerUseractionsMenuCallback(context->argument(0));
return engine->newVariant(true);
}
template<class T>
void screenEdgeActivated(T *script, int edge)
{
@ -255,62 +211,6 @@ void screenEdgeActivated(T *script, int edge)
}
}
template<class T>
QScriptValue scriptingAssert(QScriptContext *context, QScriptEngine *engine, int min, int max, T defaultVal = T())
{
if (!validateParameters(context, min, max)) {
return engine->undefinedValue();
}
switch (context->argumentCount()) {
case 1:
if (!validateArgumentType<T>(context)) {
return engine->undefinedValue();
}
break;
case 2:
if (max == 2) {
if (!validateArgumentType<T, QString>(context)) {
return engine->undefinedValue();
}
} else {
if (!validateArgumentType<T, T>(context)) {
return engine->undefinedValue();
}
}
break;
case 3:
if (!validateArgumentType<T, T, QString>(context)) {
return engine->undefinedValue();
}
break;
}
if (max == 2) {
if (context->argument(0).toVariant().value<T>() != defaultVal) {
if (context->argumentCount() == max) {
context->throwError(QScriptContext::UnknownError, context->argument(max - 1).toString());
} else {
context->throwError(QScriptContext::UnknownError,
i18nc("Assertion failed in KWin script with given value",
"Assertion failed: %1", context->argument(0).toString()));
}
return engine->undefinedValue();
}
} else {
if (context->argument(0).toVariant().value<T>() != context->argument(1).toVariant().value<T>()) {
if (context->argumentCount() == max) {
context->throwError(QScriptContext::UnknownError, context->argument(max - 1).toString());
} else {
context->throwError(QScriptContext::UnknownError,
i18nc("Assertion failed in KWin script with expected value and actual value",
"Assertion failed: Expected %1, got %2",
context->argument(0).toString(), context->argument(1).toString()));
}
return engine->undefinedValue();
}
}
return engine->newVariant(true);
}
inline void registerGlobalShortcutFunction(QObject *parent, QScriptEngine *engine, QScriptEngine::FunctionSignature function)
{
QScriptValue shortcutFunc = engine->newFunction(function);
@ -325,13 +225,6 @@ inline void registerScreenEdgeFunction(QObject *parent, QScriptEngine *engine, Q
engine->globalObject().setProperty(QStringLiteral("registerScreenEdge"), shortcutFunc);
}
inline void unregisterScreenEdgeFunction(QObject *parent, QScriptEngine *engine, QScriptEngine::FunctionSignature function)
{
QScriptValue shortcutFunc = engine->newFunction(function);
shortcutFunc.setData(engine->newQObject(parent));
engine->globalObject().setProperty(QStringLiteral("unregisterScreenEdge"), shortcutFunc);
}
inline void registerTouchScreenEdgeFunction(QObject *parent, QScriptEngine *engine, QScriptEngine::FunctionSignature function)
{
QScriptValue touchScreenFunc = engine->newFunction(function);
@ -346,12 +239,7 @@ inline void unregisterTouchScreenEdgeFunction(QObject *parent, QScriptEngine *en
engine->globalObject().setProperty(QStringLiteral("unregisterTouchScreenEdge"), touchScreenFunc);
}
inline void registerUserActionsMenuFunction(QObject *parent, QScriptEngine *engine, QScriptEngine::FunctionSignature function)
{
QScriptValue shortcutFunc = engine->newFunction(function);
shortcutFunc.setData(engine->newQObject(parent));
engine->globalObject().setProperty(QStringLiteral("registerUserActionsMenu"), shortcutFunc);
}
QVariant dbusToVariant(const QVariant &variant);
} // namespace KWin

View File

@ -1,31 +0,0 @@
/*
SPDX-FileCopyrightText: 2007 Richard J. Moore <rich@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include <QtScript/QScriptValue>
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptContext>
#include <QtScript/QScriptable>
#include <QTimer>
Q_DECLARE_METATYPE(QTimer*)
static QScriptValue newTimer(QScriptEngine *eng, QTimer *timer)
{
return eng->newQObject(timer, QScriptEngine::AutoOwnership);
}
static QScriptValue ctor(QScriptContext *ctx, QScriptEngine *eng)
{
return newTimer(eng, new QTimer(qscriptvalue_cast<QObject*>(ctx->argument(0))));
}
QScriptValue constructTimerClass(QScriptEngine *eng)
{
QScriptValue proto = newTimer(eng, new QTimer());
eng->setDefaultPrototype(qMetaTypeId<QTimer*>(), proto);
return eng->newFunction(ctor, proto);
}