diff --git a/CMakeLists.txt b/CMakeLists.txt
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -457,8 +457,8 @@
scripting/meta.cpp
scripting/scriptedeffect.cpp
scripting/scriptingutils.cpp
- scripting/timer.cpp
scripting/scripting_model.cpp
+ scripting/timer_wrapper.cpp
scripting/dbuscall.cpp
scripting/screenedgeitem.cpp
scripting/scripting_logging.cpp
diff --git a/autotests/integration/scripting/scripts/screenedge.js b/autotests/integration/scripting/scripts/screenedge.js
--- a/autotests/integration/scripting/scripts/screenedge.js
+++ b/autotests/integration/scripting/scripts/screenedge.js
@@ -1 +1 @@
-registerScreenEdge(readConfig("Edge", 1), function() { workspace.slotToggleShowDesktop(); });
+registerScreenEdge(readConfig("Edge", 1), () => workspace.slotToggleShowDesktop());
diff --git a/autotests/integration/scripting/scripts/screenedgeunregister.js b/autotests/integration/scripting/scripts/screenedgeunregister.js
--- a/autotests/integration/scripting/scripts/screenedgeunregister.js
+++ b/autotests/integration/scripting/scripts/screenedgeunregister.js
@@ -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);
diff --git a/autotests/integration/scripting/scripts/touchScreenedge.js b/autotests/integration/scripting/scripts/touchScreenedge.js
--- a/autotests/integration/scripting/scripts/touchScreenedge.js
+++ b/autotests/integration/scripting/scripts/touchScreenedge.js
@@ -1 +1 @@
-registerTouchScreenEdge(readConfig("Edge", 1), function() { workspace.slotToggleShowDesktop(); });
+registerTouchScreenEdge(readConfig("Edge", 1), () => workspace.slotToggleShowDesktop());
diff --git a/scripting/dbuscall.cpp b/scripting/dbuscall.cpp
--- a/scripting/dbuscall.cpp
+++ b/scripting/dbuscall.cpp
@@ -18,6 +18,7 @@
along with this program. If not, see .
*********************************************************************/
#include "dbuscall.h"
+#include "scriptingutils.h"
#include
#include
@@ -46,7 +47,11 @@
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);
});
}
diff --git a/scripting/meta.h b/scripting/meta.h
--- a/scripting/meta.h
+++ b/scripting/meta.h
@@ -29,14 +29,6 @@
class QScriptContext;
class QSize;
-namespace KWin {
-class Client;
-class Toplevel;
-}
-
-typedef KWin::Client* KClientRef;
-typedef KWin::Toplevel* KToplevelRef;
-
namespace KWin
{
namespace MetaScripting
@@ -75,53 +67,12 @@
void fromScriptValue(const QScriptValue&, QRect&);
}
-namespace Client
-{
-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
diff --git a/scripting/meta.cpp b/scripting/meta.cpp
--- a/scripting/meta.cpp
+++ b/scripting/meta.cpp
@@ -19,8 +19,8 @@
*********************************************************************/
#include "meta.h"
-#include "client.h"
+#include
#include
using namespace KWin::MetaScripting;
@@ -96,145 +96,12 @@
}
// End of meta for QRect object
-QScriptValue Client::toScriptValue(QScriptEngine *eng, const KClientRef &client)
-{
- return eng->newQObject(client, QScriptEngine::QtOwnership,
- QScriptEngine::ExcludeChildObjects |
- QScriptEngine::ExcludeDeleteLater |
- QScriptEngine::PreferExistingWrapperObject |
- QScriptEngine::AutoCreateDynamicProperties);
-}
-
-void Client::fromScriptValue(const QScriptValue &value, KWin::Client* &client)
-{
- client = qobject_cast(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(value.toQObject());
-}
-
// Other helper functions
void KWin::MetaScripting::registration(QScriptEngine* eng)
{
qScriptRegisterMetaType(eng, Point::toScriptValue, Point::fromScriptValue);
qScriptRegisterMetaType(eng, Size::toScriptValue, Size::fromScriptValue);
qScriptRegisterMetaType(eng, Rect::toScriptValue, Rect::fromScriptValue);
- qScriptRegisterMetaType(eng, Client::toScriptValue, Client::fromScriptValue);
- qScriptRegisterMetaType(eng, Toplevel::toScriptValue, Toplevel::fromScriptValue);
qScriptRegisterSequenceMetaType(eng);
- qScriptRegisterSequenceMetaType< QList >(eng);
-}
-
-QScriptValue KWin::MetaScripting::configExists(QScriptContext* ctx, QScriptEngine* eng)
-{
- QHash scriptConfig = (((ctx->thisObject()).data()).toVariant()).toHash();
- QVariant val = scriptConfig.value((ctx->argument(0)).toString(), QVariant());
-
- return eng->toScriptValue(val.isValid());
-}
-
-QScriptValue KWin::MetaScripting::getConfigValue(QScriptContext* ctx, QScriptEngine* eng)
-{
- int num = ctx->argumentCount();
- QHash scriptConfig = (((ctx->thisObject()).data()).toVariant()).toHash();
-
- /*
- * Handle config.get() separately. Compute and return immediately.
- **/
- if (num == 0) {
- QHash::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()));
-}
-
-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());
- }
}
diff --git a/scripting/scripting.h b/scripting/scripting.h
--- a/scripting/scripting.h
+++ b/scripting/scripting.h
@@ -4,6 +4,7 @@
Copyright (C) 2010 Rohan Prabhu
Copyright (C) 2011 Martin Gräßlin
+Copyright (C) 2019 Vlad Zagorodniy
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -27,7 +28,7 @@
#include
#include
#include
-#include
+#include
#include
#include
@@ -37,12 +38,8 @@
class QQmlContext;
class QQmlEngine;
class QAction;
-class QDBusPendingCallWatcher;
-class QGraphicsScene;
class QMenu;
class QMutex;
-class QScriptEngine;
-class QScriptValue;
class QQuickWindow;
class KConfigGroup;
@@ -53,7 +50,6 @@
{
class AbstractClient;
class Client;
-class ScriptUnloaderAgent;
class QtScriptWorkspaceWrapper;
class KWIN_EXPORT AbstractScript : public QObject
@@ -69,8 +65,61 @@
return m_pluginName;
}
- void printMessage(const QString &message);
- void registerShortcut(QAction *a, QScriptValue callback);
+ KConfigGroup config() const;
+
+public Q_SLOTS:
+ void stop();
+ virtual void run() = 0;
+
+Q_SIGNALS:
+ 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;
+ }
+
+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);
+
/**
* @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
@@ -80,7 +129,8 @@
* @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.
*
@@ -123,62 +173,38 @@
* @return QList< QAction* > List of QActions obtained from asking the registered callbacks
* @see registerUseractionsMenuCallback
**/
- QList actionsForUserActionMenu(AbstractClient *c, QMenu *parent);
-
- KConfigGroup config() const;
- const QHash &shortcutCallbacks() const {
- return m_shortcutCallbacks;
- }
- QHash > &screenEdgeCallbacks() {
- return m_screenEdgeCallbacks;
- }
-
- int registerCallback(QScriptValue value);
+ QList actionsForUserActionMenu(AbstractClient *client, QMenu *parent);
public Q_SLOTS:
- Q_SCRIPTABLE void stop();
- Q_SCRIPTABLE virtual void run() = 0;
- void slotPendingDBusCall(QDBusPendingCallWatcher *watcher);
+ void run();
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);
-
-Q_SIGNALS:
- Q_SCRIPTABLE void print(const QString &text);
- void runningChanged(bool);
+ void slotScriptLoadedFromFile();
-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.
*
* @param value The ScriptValue describing either a menu or action
* @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.
@@ -193,84 +219,24 @@
* @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.
*
* @param title The title of the Menu.
* @param items JavaScript Array containing Menu items.
* @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 m_shortcutCallbacks;
- QHash > m_screenEdgeCallbacks;
- QHash m_callbacks;
- /**
- * @brief List of registered functions to call when the UserActionsMenu is about to show
- * to add further entries.
- **/
- QList m_userActionsMenuCallbacks;
-};
-
-class Script : public AbstractScript, QDBusContext
-{
- Q_OBJECT
- Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.Scripting")
-public:
+ QAction *createMenu(const QString &title, const QJSValue &items, QMenu *parent);
- Script(int id, QString scriptName, QString pluginName, QObject *parent = nullptr);
- virtual ~Script();
- QScriptEngine *engine() {
- return m_engine;
- }
-
- bool registerTouchScreenCallback(int edge, QScriptValue callback);
- bool unregisterTouchScreenCallback(int edge);
-
-public Q_SLOTS:
- Q_SCRIPTABLE void run();
-
-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 m_agent;
- QHash m_touchScreenEdgeCallbacks;
-};
-
-class ScriptUnloaderAgent : public QScriptEngineAgent
-{
-public:
- explicit ScriptUnloaderAgent(Script *script);
- virtual void scriptUnload(qint64 id);
-
-private:
- Script *m_script;
+ QHash m_screenEdgeCallbacks;
+ QHash m_touchScreenEdgeCallbacks;
+ QJSValueList m_userActionsMenuCallbacks;
};
class DeclarativeScript : public AbstractScript
diff --git a/scripting/scripting.cpp b/scripting/scripting.cpp
--- a/scripting/scripting.cpp
+++ b/scripting/scripting.cpp
@@ -4,6 +4,7 @@
Copyright (C) 2010 Rohan Prabhu
Copyright (C) 2011 Martin Gräßlin
+Copyright (C) 2019 Vlad Zagorodniy
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -22,12 +23,12 @@
#include "scripting.h"
// own
#include "dbuscall.h"
-#include "meta.h"
#include "scriptingutils.h"
#include "workspace_wrapper.h"
#include "screenedgeitem.h"
#include "scripting_model.h"
#include "scripting_logging.h"
+#include "timer_wrapper.h"
#include "../client.h"
#include "../thumbnailitem.h"
#include "../options.h"
@@ -47,186 +48,9 @@
#include
#include
#include
-#include
-#include
#include
#include
-QScriptValue kwinScriptPrint(QScriptContext *context, QScriptEngine *engine)
-{
- KWin::AbstractScript *script = qobject_cast(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::Client *client = qscriptvalue_cast(argument)) {
- client->print(stream);
- } else {
- stream << argument.toString();
- }
- }
- script->printMessage(result);
-
- return engine->undefinedValue();
-}
-
-QScriptValue kwinScriptReadConfig(QScriptContext *context, QScriptEngine *engine)
-{
- KWin::AbstractScript *script = qobject_cast(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));
-}
-
-QScriptValue kwinScriptGlobalShortcut(QScriptContext *context, QScriptEngine *engine)
-{
- return KWin::globalShortcut(context, engine);
-}
-
-QScriptValue kwinAssertTrue(QScriptContext *context, QScriptEngine *engine)
-{
- return KWin::scriptingAssert(context, engine, 1, 2, true);
-}
-
-QScriptValue kwinAssertFalse(QScriptContext *context, QScriptEngine *engine)
-{
- return KWin::scriptingAssert(context, engine, 1, 2, false);
-}
-
-QScriptValue kwinAssertEquals(QScriptContext *context, QScriptEngine *engine)
-{
- return KWin::scriptingAssert(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(context, engine);
-}
-
-QScriptValue kwinUnregisterScreenEdge(QScriptContext *context, QScriptEngine *engine)
-{
- return KWin::unregisterScreenEdge(context, engine);
-}
-
-QScriptValue kwinRegisterTouchScreenEdge(QScriptContext *context, QScriptEngine *engine)
-{
- return KWin::registerTouchScreenEdge(context, engine);
-}
-
-QScriptValue kwinUnregisterTouchScreenEdge(QScriptContext *context, QScriptEngine *engine)
-{
- return KWin::unregisterTouchScreenEdge(context, engine);
-}
-
-QScriptValue kwinRegisterUserActionsMenu(QScriptContext *context, QScriptEngine *engine)
-{
- return KWin::registerUserActionsMenu(context, engine);
-}
-
-QScriptValue kwinCallDBus(QScriptContext *context, QScriptEngine *engine)
-{
- KWin::AbstractScript *script = qobject_cast(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(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; iargument(i).isArray()) {
- QStringList stringArray = engine->fromScriptValue(context->argument(i));
- arguments << qVariantFromValue(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, SIGNAL(finished(QDBusPendingCallWatcher*)), script, SLOT(slotPendingDBusCall(QDBusPendingCallWatcher*)));
- }
- return engine->undefinedValue();
-}
-
KWin::AbstractScript::AbstractScript(int id, QString scriptName, QString pluginName, QObject *parent)
: QObject(parent)
, m_scriptId(id)
@@ -253,204 +77,10 @@
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, SIGNAL(triggered(bool)), SLOT(globalShortcutTriggered()));
-}
-
-void KWin::AbstractScript::globalShortcutTriggered()
-{
- callGlobalShortcutCallback(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 returnActions;
- for (QList::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, SIGNAL(triggered(bool)), SLOT(globalShortcutTriggered()));
- connect(action, SIGNAL(destroyed(QObject*)), SLOT(actionDestroyed(QObject*)));
- return action;
-}
-
-QAction *KWin::AbstractScript::createMenu(const QString &title, QScriptValue &items, QMenu *parent)
-{
- QMenu *menu = new QMenu(title, parent);
- const int length = static_cast(items.property(QStringLiteral("length")).toInteger());
- for (int i=0; iaddAction(a);
- }
- }
- }
- return menu->menuAction();
-}
-
-void KWin::AbstractScript::actionDestroyed(QObject *object)
-{
- // TODO: Qt 5 - change to lambda function
- m_shortcutCallbacks.remove(static_cast(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))
{
QDBusConnection::sessionBus().registerObject(QLatin1Char('/') + QString::number(scriptId()), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportScriptableInvokables);
}
@@ -508,18 +138,74 @@
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, SIGNAL(signalHandlerException(QScriptValue)), this, SLOT(sigException(QScriptValue)));
- KWin::MetaScripting::supplyConfig(m_engine);
- installScriptFunctions(m_engine);
-
- QScriptValue ret = m_engine->evaluate(QString::fromUtf8(watcher->result()));
-
- if (ret.isError()) {
- sigException(ret);
+ // Install console functions (e.g. console.assert(), console.log(), etc).
+ m_engine->installExtensions(QJSEngine::ConsoleExtension);
+
+ // Make the timer visible to QJSEngine.
+ QJSValue timerMetaObject = m_engine->newQMetaObject(&TimerWrapper::staticMetaObject);
+ m_engine->globalObject().setProperty("QTimer", timerMetaObject);
+ m_engine->globalObject().setProperty("Timer", timerMetaObject);
+
+ // 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);
+ m_engine->globalObject().setProperty(QStringLiteral("readConfig"), self.property(QStringLiteral("readConfig")));
+ m_engine->globalObject().setProperty(QStringLiteral("callDBus"), self.property(QStringLiteral("callDBus")));
+ m_engine->globalObject().setProperty(QStringLiteral("registerShortcut"), self.property(QStringLiteral("registerShortcut")));
+ m_engine->globalObject().setProperty(QStringLiteral("registerScreenEdge"), self.property(QStringLiteral("registerScreenEdge")));
+ m_engine->globalObject().setProperty(QStringLiteral("unregisterScreenEdge"), self.property(QStringLiteral("unregisterScreenEdge")));
+ m_engine->globalObject().setProperty(QStringLiteral("registerTouchScreenEdge"), self.property(QStringLiteral("registerTouchScreenEdge")));
+ m_engine->globalObject().setProperty(QStringLiteral("unregisterTouchScreenEdge"), self.property(QStringLiteral("unregisterTouchScreenEdge")));
+ m_engine->globalObject().setProperty(QStringLiteral("registerUserActionsMenu"), self.property(QStringLiteral("registerUserActionsMenu")));
+
+ // 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(
+ "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()) {
+ const QString message = fileName() + QLatin1String(":")
+ + result.property(QStringLiteral("lineNumber")).toString()
+ + QLatin1String(": error: ")
+ + result.property(QStringLiteral("message")).toString();
+ qCWarning(KWIN_SCRIPTING) << qPrintable(message);
deleteLater();
}
@@ -534,63 +220,277 @@
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) << "-----------------";
+ return config().readEntry(key, defaultValue);
+}
- QScriptValueIterator iter(ret);
- while (iter.hasNext()) {
- iter.next();
- qCDebug(KWIN_SCRIPTING) << " " << iter.name() << ": " << iter.value().toString();
- }
+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)
+{
+ 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;
}
- emit printError(exception.toString());
- stop();
+
+ QJSValue callback;
+ if (!jsArguments.isEmpty() && jsArguments.last().isCallable()) {
+ callback = jsArguments.takeLast();
+ }
+
+ QVariantList dbusArguments;
+ dbusArguments.reserve(jsArguments.count());
+ for (const QJSValue &jsArgument : 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::registerTouchScreenCallback(int edge, QScriptValue callback)
+bool KWin::Script::registerShortcut(const QString &objectName, const QString &text, const QString &keySequence, const QJSValue &callback)
{
- if (m_touchScreenEdgeCallbacks.constFind(edge) != m_touchScreenEdgeCallbacks.constEnd()) {
+ if (!callback.isCallable()) {
return false;
}
+
QAction *action = new QAction(this);
+ action->setObjectName(objectName);
+ action->setText(text);
+
+ const QKeySequence shortcut = keySequence;
+ KGlobalAccel::self()->setShortcut(action, { shortcut });
+ input()->registerShortcut(shortcut, action);
+
connect(action, &QAction::triggered, this,
- [callback] {
- QScriptValue invoke(callback);
- invoke.call();
+ [this, action, callback] {
+ QJSValue(callback).call({ m_engine->toScriptValue(action) });
}
);
+
+ return true;
+}
+
+bool KWin::Script::registerScreenEdge(int edge, const QJSValue &callback)
+{
+ if (!callback.isCallable()) {
+ return false;
+ }
+
+ QJSValueList &callbacks = m_screenEdgeCallbacks[edge];
+ if (callbacks.isEmpty()) {
+ ScreenEdges::self()->reserve(static_cast(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(edge), this);
+ m_screenEdgeCallbacks.erase(it);
+
+ return true;
+}
+
+bool KWin::Script::registerTouchScreenEdge(int edge, const QJSValue &callback)
+{
+ if (!callback.isCallable()) {
+ 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::unregisterTouchScreenCallback(int edge)
+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;
+}
+
+void KWin::Script::registerUserActionsMenu(const QJSValue &callback)
+{
+ if (!callback.isCallable()) {
+ return;
+ }
+ m_userActionsMenuCallbacks.append(callback);
+}
+
+QList KWin::Script::actionsForUserActionMenu(KWin::AbstractClient *client, QMenu *parent)
+{
+ QList 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;
}
-KWin::ScriptUnloaderAgent::ScriptUnloaderAgent(KWin::Script *script)
- : QScriptEngineAgent(script->engine())
- , m_script(script)
+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)
{
- script->engine()->setAgent(this);
+ 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;
}
-void KWin::ScriptUnloaderAgent::scriptUnload(qint64 id)
+QAction *KWin::Script::createMenu(const QString &title, const QJSValue &items, QMenu *parent)
{
- Q_UNUSED(id)
- m_script->stop();
+ 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)
@@ -890,8 +790,11 @@
QList< QAction * > KWin::Scripting::actionsForUserActionMenu(KWin::AbstractClient *c, QMenu *parent)
{
QList 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