Commit 033ad5ca authored by Marco Martin's avatar Marco Martin

Port Plasma Desktop Scripting to QJSEngine

Summary:
Port plasma desktop scripting to qjsengine, removing every trace of QScript from plasma-workspace.
The scripting is 100% API compatible.
the bindings are partly done on the javascript part as some things are only possible there and not on the QJSEngine part:
* setting AppInterface as the globalobject's __proto__ with property getters that are evaluated every time
* setting functions that act as constructors (using newQMetaObject gives way more complex and uglier c++ code in Containment, as the scriptengine is not immediately accessible)
* a QRectF wrapper which exposes all properties and methods that were exposed in the previous implementation

Test Plan:
* some manual api test from the interactive console
* tested both from the desktop console and first start with some pretty complex layouts from lnf packages (the default one, netrunner, united, elpas)
* tested every property/methos of the qrectf wrapper

Reviewers: #plasma, davidedmundson

Reviewed By: #plasma, davidedmundson

Subscribers: davidedmundson, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D13112
parent 17e26994
......@@ -8,7 +8,7 @@ set(PROJECT_VERSION_MAJOR 5)
set(QT_MIN_VERSION "5.9.0")
set(KF5_MIN_VERSION "5.45.0")
set(INSTALL_SDDM_THEME TRUE)
find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Widgets Quick QuickWidgets Concurrent Test Script Network)
find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Widgets Quick QuickWidgets Concurrent Test Network)
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR})
......
......@@ -9,9 +9,7 @@ set(scripting_SRC
scripting/applet.cpp
scripting/containment.cpp
scripting/configgroup.cpp
scripting/i18n.cpp
scripting/panel.cpp
scripting/rect.cpp
scripting/scriptengine.cpp
scripting/scriptengine_v1.cpp
scripting/widget.cpp
......@@ -60,7 +58,6 @@ target_link_libraries(plasmashell
KF5::Crash
KF5::Plasma
KF5::PlasmaQuick
Qt5::Script
KF5::Solid
KF5::Declarative
KF5::I18n
......
......@@ -59,9 +59,11 @@ int AppInterface::screenCount() const
return m_env->corona()->numScreens();
}
QRectF AppInterface::screenGeometry(int screen) const
QJSValue AppInterface::screenGeometry(int screen) const
{
return m_env->corona()->screenGeometry(screen);
QRectF rect = m_env->corona()->screenGeometry(screen);
QJSValueList args({QJSValue(rect.x()), QJSValue(rect.y()), QJSValue(rect.width()), QJSValue(rect.height())});
return m_env->globalObject().property("QRectF").callAsConstructor(args);
}
QList<int> AppInterface::activityIds() const
......
......@@ -23,6 +23,7 @@
#include <QObject>
#include <QRectF>
#include <QStringList>
#include <QJSValue>
namespace Plasma
{
......@@ -87,7 +88,7 @@ public:
bool coronaLocked() const;
public Q_SLOTS:
QRectF screenGeometry(int screen) const;
QJSValue screenGeometry(int screen) const;
void lockCorona(bool locked);
void sleep(int ms);
......
This diff is collapsed.
......@@ -212,4 +212,3 @@ void ConfigGroup::sync()
}
}
......@@ -30,6 +30,7 @@
#include <Plasma/Containment>
#include <Plasma/PluginLoader>
#include "shellcorona.h"
#include "scriptengine.h"
#include "widget.h"
......@@ -39,18 +40,26 @@ namespace WorkspaceScripting
class Containment::Private
{
public:
QPointer<ScriptEngine> engine;
QPointer<Plasma::Containment> containment;
ShellCorona *corona;
QString oldWallpaperPlugin;
QString wallpaperPlugin;
QString oldWallpaperMode;
QString wallpaperMode;
QString type;
QString plugin;
};
Containment::Containment(Plasma::Containment *containment, QObject *parent)
: Applet(parent),
Containment::Containment(Plasma::Containment *containment, ScriptEngine *engine)
: Applet(engine),
d(new Containment::Private)
{
d->engine = engine;
d->containment = containment;
d->corona = qobject_cast<ShellCorona *>(containment->corona());
setCurrentConfigGroup(QStringList());
setCurrentGlobalConfigGroup(QStringList());
if (containment) {
......@@ -73,6 +82,11 @@ Containment::~Containment()
delete d;
}
ShellCorona *Containment::corona() const
{
return d->corona;
}
int Containment::screen() const
{
if (!d->containment) {
......@@ -140,60 +154,36 @@ QList<int> Containment::widgetIds() const
return w;
}
QScriptValue Containment::widgetById(QScriptContext *context, QScriptEngine *engine)
QJSValue Containment::widgetById(const QJSValue &paramId) const
{
if (context->argumentCount() == 0) {
return context->throwError(i18n("widgetById requires an id"));
if (!paramId.isNumber()) {
return d->engine->newError(i18n("widgetById requires an id"));
}
const uint id = context->argument(0).toInt32();
Containment *c = qobject_cast<Containment*>(context->thisObject().toQObject());
if (!c) {
return engine->undefinedValue();
}
const uint id = paramId.toInt();
if (c->d->containment) {
foreach (Plasma::Applet *w, c->d->containment.data()->applets()) {
if (d->containment) {
foreach (Plasma::Applet *w, d->containment.data()->applets()) {
if (w->id() == id) {
ScriptEngine *env = ScriptEngine::envFor(engine);
return env->wrap(w);
return d->engine->wrap(w);
}
}
}
return engine->undefinedValue();
return QJSValue();
}
QScriptValue Containment::addWidget(QScriptContext *context, QScriptEngine *engine)
QJSValue Containment::addWidget(const QJSValue &v, qreal x, qreal y, qreal w, qreal h)
{
if (context->argumentCount() == 0) {
return context->throwError(i18n("addWidget requires a name of a widget or a widget object"));
if (!v.isString() && !v.isQObject()) {
return d->engine->newError(i18n("addWidget requires a name of a widget or a widget object"));
}
Containment *c = qobject_cast<Containment*>(context->thisObject().toQObject());
if (!c || !c->d->containment) {
return engine->undefinedValue();
if (!d->containment) {
return QJSValue();
}
QScriptValue v = context->argument(0);
QRectF geometry(-1, -1, -1, -1);
if (context->argumentCount() > 4) {
//The user provided a geometry as parameter
if (context->argument(1).isNumber() &&
context->argument(2).isNumber() &&
context->argument(3).isNumber() &&
context->argument(4).isNumber()) {
//Try to reconstruct a rectangle from the object hat has been passed
//It's expected a js object such as
//addWidget("org.kde.plasma.analogclock", 0, 100, 300, 400);
geometry = QRectF(context->argument(1).toNumber(),
context->argument(2).toNumber(),
context->argument(3).toNumber(),
context->argument(4).toNumber());
}
}
QRectF geometry(x, y, w, h);
Plasma::Applet *applet = nullptr;
if (v.isString()) {
......@@ -201,54 +191,48 @@ QScriptValue Containment::addWidget(QScriptContext *context, QScriptEngine *engi
QQuickItem *containmentItem = nullptr;
if (geometry.x() >= 0 && geometry.y() >= 0) {
containmentItem = c->d->containment.data()->property("_plasma_graphicObject").value<QQuickItem *>();
containmentItem = d->containment.data()->property("_plasma_graphicObject").value<QQuickItem *>();
if (containmentItem) {
QMetaObject::invokeMethod(containmentItem , "createApplet", Qt::DirectConnection, Q_RETURN_ARG(Plasma::Applet *, applet), Q_ARG(QString, v.toString()), Q_ARG(QVariantList, QVariantList()), Q_ARG(QRectF, geometry));
}
if (applet) {
ScriptEngine *env = ScriptEngine::envFor(engine);
return env->wrap(applet);
return d->engine->wrap(applet);
}
return context->throwError(i18n("Could not create the %1 widget!", v.toString()));
return d->engine->newError(i18n("Could not create the %1 widget!", v.toString()));
}
//Case in which either:
// * a geometry wasn't provided
// * containmentItem wasn't found
applet = c->d->containment.data()->createApplet(v.toString());
applet = d->containment.data()->createApplet(v.toString());
if (applet) {
ScriptEngine *env = ScriptEngine::envFor(engine);
return env->wrap(applet);
return d->engine->wrap(applet);
}
return context->throwError(i18n("Could not create the %1 widget!", v.toString()));
return d->engine->newError(i18n("Could not create the %1 widget!", v.toString()));
} else if (Widget *widget = qobject_cast<Widget*>(v.toQObject())) {
applet = widget->applet();
c->d->containment.data()->addApplet(applet);
d->containment.data()->addApplet(applet);
return v;
}
return engine->undefinedValue();
return QJSValue();
}
QScriptValue Containment::widgets(QScriptContext *context, QScriptEngine *engine)
QJSValue Containment::widgets(const QString &widgetType) const
{
Containment *c = qobject_cast<Containment*>(context->thisObject().toQObject());
if (!c || !c->d->containment) {
return engine->undefinedValue();
if (!d->containment) {
return QJSValue();
}
const QString widgetType = context->argumentCount() > 0 ? context->argument(0).toString() : QString();
QScriptValue widgets = engine->newArray();
ScriptEngine *env = ScriptEngine::envFor(engine);
QJSValue widgets = d->engine->newArray();
int count = 0;
foreach (Plasma::Applet *widget, c->d->containment.data()->applets()) {
foreach (Plasma::Applet *widget, d->containment.data()->applets()) {
if (widgetType.isEmpty() || widget->pluginMetaData().pluginId() == widgetType) {
widgets.setProperty(count, env->wrap(widget));
widgets.setProperty(count, d->engine->wrap(widget));
++count;
}
}
......
......@@ -20,8 +20,7 @@
#ifndef CONTAINMENT
#define CONTAINMENT
#include <QScriptContext>
#include <QScriptValue>
#include <QJSValue>
#include <QWeakPointer>
#include "applet.h"
......@@ -31,9 +30,13 @@ namespace Plasma
class Containment;
} // namespace Plasma
class ShellCorona;
namespace WorkspaceScripting
{
class ScriptEngine;
class Containment : public Applet
{
Q_OBJECT
......@@ -54,7 +57,7 @@ class Containment : public Applet
Q_PROPERTY(int id READ id)
public:
explicit Containment(Plasma::Containment *containment, QObject *parent = nullptr);
explicit Containment(Plasma::Containment *containment, ScriptEngine *parent);
~Containment() override;
uint id() const;
......@@ -72,22 +75,16 @@ public:
QString wallpaperMode() const;
void setWallpaperMode(const QString &wallpaperMode);
static QScriptValue widgetById(QScriptContext *context, QScriptEngine *engine);
static QScriptValue addWidget(QScriptContext *context, QScriptEngine *engine);
static QScriptValue widgets(QScriptContext *context, QScriptEngine *engine);
Q_INVOKABLE QJSValue widgetById(const QJSValue &paramId = QJSValue()) const;
Q_INVOKABLE QJSValue addWidget(const QJSValue &v = QJSValue(), qreal x = -1, qreal y = -1, qreal w = -1, qreal h = -1);
Q_INVOKABLE QJSValue widgets(const QString &widgetType = QString()) const;
public Q_SLOTS:
void remove();
void showConfigurationInterface();
// from the applet interface
QVariant readConfig(const QString &key, const QVariant &def = QString()) const override { return Applet::readConfig(key, def); }
void writeConfig(const QString &key, const QVariant &value) override { Applet::writeConfig(key, value); }
QVariant readGlobalConfig(const QString &key, const QVariant &def = QString()) const override { return Applet::readGlobalConfig(key, def); }
void writeGlobalConfig(const QString &key, const QVariant &value) override { Applet::writeGlobalConfig(key, value); }
void reloadConfig() override { Applet::reloadConfig(); }
protected:
ShellCorona *corona() const;
private:
class Private;
......
/*
* Copyright 2009 Aaron Seigo <aseigo@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "i18n.h"
#include <QScriptContext>
#include <QScriptEngine>
#include <QDebug>
#include <klocalizedstring.h>
QScriptValue jsi18n(QScriptContext *context, QScriptEngine *engine)
{
Q_UNUSED(engine)
if (context->argumentCount() < 1) {
// qDebug() << i18n("i18n() takes at least one argument");
return engine->undefinedValue();
}
KLocalizedString message = ki18n(context->argument(0).toString().toUtf8());
const int numArgs = context->argumentCount();
for (int i = 1; i < numArgs; ++i) {
message = message.subs(context->argument(i).toString());
}
return message.toString();
}
QScriptValue jsi18nc(QScriptContext *context, QScriptEngine *engine)
{
Q_UNUSED(engine)
if (context->argumentCount() < 2) {
// qDebug() << i18n("i18nc() takes at least two arguments");
return engine->undefinedValue();
}
KLocalizedString message = ki18nc(context->argument(0).toString().toUtf8(),
context->argument(1).toString().toUtf8());
const int numArgs = context->argumentCount();
for (int i = 2; i < numArgs; ++i) {
message = message.subs(context->argument(i).toString());
}
return message.toString();
}
QScriptValue jsi18np(QScriptContext *context, QScriptEngine *engine)
{
Q_UNUSED(engine)
if (context->argumentCount() < 2) {
// qDebug() << i18n("i18np() takes at least two arguments");
return engine->undefinedValue();
}
KLocalizedString message = ki18np(context->argument(0).toString().toUtf8(),
context->argument(1).toString().toUtf8());
const int numArgs = context->argumentCount();
for (int i = 2; i < numArgs; ++i) {
QScriptValue v = context->argument(i);
if (v.isNumber()) {
message = message.subs(v.toInt32());
} else {
message = message.subs(v.toString());
}
}
return message.toString();
}
QScriptValue jsi18ncp(QScriptContext *context, QScriptEngine *engine)
{
Q_UNUSED(engine)
if (context->argumentCount() < 3) {
// qDebug() << i18n("i18ncp() takes at least three arguments");
return engine->undefinedValue();
}
KLocalizedString message = ki18ncp(context->argument(0).toString().toUtf8(),
context->argument(1).toString().toUtf8(),
context->argument(2).toString().toUtf8());
const int numArgs = context->argumentCount();
for (int i = 3; i < numArgs; ++i) {
message = message.subs(context->argument(i).toString());
}
return message.toString();
}
void bindI18N(QScriptEngine *engine)
{
QScriptValue global = engine->globalObject();
global.setProperty(QStringLiteral("i18n"), engine->newFunction(jsi18n));
global.setProperty(QStringLiteral("i18nc"), engine->newFunction(jsi18nc));
global.setProperty(QStringLiteral("i18np"), engine->newFunction(jsi18np));
global.setProperty(QStringLiteral("i18ncp"), engine->newFunction(jsi18ncp));
}
/*
* Copyright 2009 Aaron Seigo <aseigo@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef JAVASCRIPTBINDI18N_H
#define JAVASCRIPTBINDI18N_H
#include <QScriptValue>
class QScriptContext;
class QScriptEngine;
QScriptValue jsi18n(QScriptContext *context, QScriptEngine *engine);
QScriptValue jsi18nc(QScriptContext *context, QScriptEngine *engine);
QScriptValue jsi18np(QScriptContext *context, QScriptEngine *engine);
QScriptValue jsi18ncp(QScriptContext *context, QScriptEngine *engine);
void bindI18N(QScriptEngine *engine);
#endif
......@@ -34,10 +34,9 @@
namespace WorkspaceScripting
{
Panel::Panel(Plasma::Containment *containment, QObject *parent)
: Containment(containment, parent)
Panel::Panel(Plasma::Containment *containment, ScriptEngine *engine)
: Containment(containment, engine)
{
m_corona = qobject_cast<ShellCorona *>(containment->corona());
}
Panel::~Panel()
......@@ -106,11 +105,11 @@ void Panel::setLocation(const QString &locationString)
PanelView *Panel::panel() const
{
Plasma::Containment *c = containment();
if (!c || !m_corona) {
if (!c || !corona()) {
return nullptr;
}
return m_corona->panelView(c);
return corona()->panelView(c);
}
......@@ -122,7 +121,7 @@ KConfigGroup Panel::panelConfig() const
return KConfigGroup();
}
QScreen *s = QGuiApplication::screens().at(screenNum);
return PanelView::panelConfig(m_corona, containment(), s);
return PanelView::panelConfig(corona(), containment(), s);
}
QString Panel::alignment() const
......
......@@ -20,8 +20,7 @@
#ifndef PANEL
#define PANEL
#include <QScriptContext>
#include <QScriptValue>
#include <QJSValue>
#include <QWeakPointer>
#include "containment.h"
......@@ -61,7 +60,7 @@ class Panel : public Containment
Q_PROPERTY(QString hiding READ hiding WRITE setHiding)
public:
explicit Panel(Plasma::Containment *containment, QObject *parent = nullptr);
explicit Panel(Plasma::Containment *containment, ScriptEngine *parent);
~Panel() override;
QString location() const;
......@@ -92,11 +91,6 @@ public Q_SLOTS:
void remove() { Containment::remove(); }
void showConfigurationInterface() { Containment::showConfigurationInterface(); }
// from the applet interface
QVariant readConfig(const QString &key, const QVariant &def = QString()) const override { return Applet::readConfig(key, def); }
void writeConfig(const QString &key, const QVariant &value) override { Applet::writeConfig(key, value); }
void reloadConfig() override { Applet::reloadConfig(); }
private:
PanelView *panel() const;
KConfigGroup panelConfig() const;
......
This diff is collapsed.
This diff is collapsed.
......@@ -20,8 +20,8 @@
#ifndef SCRIPTENGINE
#define SCRIPTENGINE
#include <QScriptEngine>
#include <QScriptValue>
#include <QJSEngine>
#include <QJSValue>
#include <QFontMetrics>
......@@ -35,13 +35,16 @@ namespace Plasma
class Containment;
} // namespace Plasma
class KLocalizedContext;
namespace WorkspaceScripting
{
class AppInterface;
class Containment;
class V1;
class ScriptEngine : public QScriptEngine
class ScriptEngine : public QJSEngine
{
Q_OBJECT
......@@ -49,16 +52,19 @@ public:
explicit ScriptEngine(Plasma::Corona *corona, QObject *parent = nullptr);
~ScriptEngine() override;
QString errorString() const;
static QStringList pendingUpdateScripts(Plasma::Corona *corona);
Plasma::Corona *corona() const;
QScriptValue wrap(Plasma::Applet *w);
virtual QScriptValue wrap(Plasma::Containment *c);
QScriptValue wrap(Containment *c);
QJSValue wrap(Plasma::Applet *w);
QJSValue wrap(Plasma::Containment *c);
virtual int defaultPanelScreen() const;
QJSValue newError(const QString &message);
static bool isPanel(const Plasma::Containment *c);
static ScriptEngine *envFor(QScriptEngine *engine);
Plasma::Containment *createContainment(const QString &type, const QString &plugin);
public Q_SLOTS:
bool evaluateScript(const QString &script, const QString &path = QString());
......@@ -71,24 +77,24 @@ private:
void setupEngine();
static QString onlyExec(const QString &commandLine);
static QScriptValue createAPIForVersion(QScriptContext *context, QScriptEngine *engine);
// Script API versions
class V1;
// helpers
QStringList availableActivities() const;
QList<Containment*> desktopsForActivity(const QString &id);
Containment *createContainment(const QString &type, const QString &plugin);
static int gridUnit();
Containment *createContainmentWrapper(const QString &type, const QString &plugin);
private Q_SLOTS:
void exception(const QScriptValue &value);
void exception(const QJSValue &value);
private:
Plasma::Corona *m_corona;
QScriptValue m_scriptSelf;
ScriptEngine::V1 *m_globalScriptEngineObject;
KLocalizedContext *m_localizedContext;
AppInterface *m_appInterface;
QJSValue m_scriptSelf;
QString m_errorString;
};
static const int PLASMA_DESKTOP_SCRIPTING_VERSION = 20;
......
This diff is collapsed.
/*
* Copyright 2009 Aaron Seigo <aseigo@kde.org>
* Copyright 2018 Marco Martin <mart@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
......@@ -20,8 +21,8 @@
#ifndef SCRIPTENGINE_V1
#define SCRIPTENGINE_V1
#include <QScriptEngine>
#include <QScriptValue>
#include <QObject>
#include <QJSValue>
#include <QFontMetrics>
......@@ -33,35 +34,49 @@
namespace WorkspaceScripting
{
class ScriptEngine::V1 {
class ScriptEngine::V1 : public QObject {
Q_OBJECT
Q_PROPERTY(int gridUnit READ gridUnit CONSTANT)
public:
static QScriptValue createActivity(QScriptContext *context, QScriptEngine *engine);
static QScriptValue setCurrentActivity(QScriptContext *context, QScriptEngine *engine);
static QScriptValue currentActivity(QScriptContext *controller, QScriptEngine *engine);
static QScriptValue activities(QScriptContext *context, QScriptEngine *engine);
static QScriptValue setActivityName(QScriptContext *context, QScriptEngine *engine);
static QScriptValue activityName(QScriptContext *context, QScriptEngine *engine);
static QScriptValue loadSerializedLayout(QScriptContext *context, QScriptEngine *engine);
static QScriptValue newPanel(QScriptContext *context, QScriptEngine *engine);
static QScriptValue desktopsForActivity(QScriptContext *context, QScriptEngine *engine);
static QScriptValue desktops(QScriptContext *context, QScriptEngine *engine);