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

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);
......
/****************************************************************************
**
** Copyright (C) 1992-2007 Trolltech ASA. All rights reserved.
**
** This file is part of the plugins of the Qt Toolkit.
**
** This file may be used under the terms of the GNU General Public
** License version 2.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file. Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** In addition, as a special exception, Trolltech gives you certain
** additional rights. These rights are described in the Trolltech GPL
** Exception version 1.0, which can be found at
** http://www.trolltech.com/products/qt/gplexception/ and in the file
** GPL_EXCEPTION.txt in this package.
**
** In addition, as a special exception, Trolltech, as the sole copyright
** holder for Qt Designer, grants users of the Qt/Eclipse Integration
** plug-in the right for the Qt/Eclipse Integration to link to
** functionality provided by Qt Designer and its related libraries.
**
** Trolltech reserves all rights not expressly granted herein.
**
** Trolltech ASA (c) 2007
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/
#ifndef QTSCRIPTEXTENSIONS_GLOBAL_H
#define QTSCRIPTEXTENSIONS_GLOBAL_H
#include <QSharedData>
#define DECLARE_SELF(Class, __fn__) \
Class* self = qscriptvalue_cast<Class*>(ctx->thisObject()); \
if (!self) { \
return ctx->throwError(QScriptContext::TypeError, \
QStringLiteral("%0.prototype.%1: this object is not a %0") \
.arg(#Class, #__fn__)); \
}
#define DECLARE_SELF2(Class, __fn__, __ret__) \
Class* self = qscriptvalue_cast<Class*>(thisObject()); \
if (!self) { \
context()->throwError(QScriptContext::TypeError, \
QStringLiteral("%0.prototype.%1: this object is not a %0") \
.arg(#Class, #__fn__)); \
return __ret__; \
}
#define ADD_METHOD(__p__, __f__) \
__p__.setProperty(#__f__, __p__.engine()->newFunction(__f__))
#define ADD_GET_METHOD(__p__, __get__) \
ADD_METHOD(__p__, __get__)
#define ADD_GET_SET_METHODS(__p__, __get__, __set__) \
do { \
ADD_METHOD(__p__, __get__); \
ADD_METHOD(__p__, __set__); \
} while (0);
#define ADD_CTOR_FUNCTION(__c__, __f__) ADD_METHOD(__c__, __f__)
#define ADD_ENUM_VALUE(__c__, __ns__, __v__) \
__c__.setProperty(#__v__, QScriptValue(__c__.engine(), __ns__::__v__))
#define BEGIN_DECLARE_METHOD(Class, __mtd__) \
static QScriptValue __mtd__(QScriptContext *ctx, QScriptEngine *eng) \
{ \
DECLARE_SELF(Class, __mtd__);
#define END_DECLARE_METHOD \
}
#define DECLARE_GET_METHOD(Class, __get__) \
BEGIN_DECLARE_METHOD(Class, __get__) { \
return qScriptValueFromValue(eng, self->__get__()); \
} END_DECLARE_METHOD
#define DECLARE_SET_METHOD(Class, T, __set__) \
BEGIN_DECLARE_METHOD(Class, __set__) { \
self->__set__(qscriptvalue_cast<T>(ctx->argument(0))); \
return eng->undefinedValue(); \
} END_DECLARE_METHOD
#define DECLARE_GET_SET_METHODS(Class, T, __get__, __set__) \
DECLARE_GET_METHOD(Class, /*T,*/ __get__) \
DECLARE_SET_METHOD(Class, T, __set__)
#define DECLARE_SIMPLE_GET_METHOD(Class, __get__) \
BEGIN_DECLARE_METHOD(Class, __get__) { \
return QScriptValue(eng, self->__get__()); \
} END_DECLARE_METHOD
#define DECLARE_SIMPLE_SET_METHOD(Class, ToType, __set__) \
BEGIN_DECLARE_METHOD(Class, __set__) { \
self->__set__(ctx->argument(0).ToType()); \
return eng->undefinedValue(); \
} END_DECLARE_METHOD
#define DECLARE_BOOLEAN_GET_METHOD(Class, __set__) \
DECLARE_SIMPLE_GET_METHOD(Class, __set__)
#define DECLARE_BOOLEAN_SET_METHOD(Class, __set__) \
DECLARE_SIMPLE_SET_METHOD(Class, toBoolean, __set__)
#define DECLARE_INT_GET_METHOD(Class, __set__) \
DECLARE_SIMPLE_GET_METHOD(Class, __set__)
#define DECLARE_INT_SET_METHOD(Class, __set__) \
DECLARE_SIMPLE_SET_METHOD(Class, toInt32, __set__)
#define DECLARE_NUMBER_GET_METHOD(Class, __set__) \
DECLARE_SIMPLE_GET_METHOD(Class, __set__)
#define DECLARE_NUMBER_SET_METHOD(Class, __set__) \
DECLARE_SIMPLE_SET_METHOD(Class, toNumber, __set__)
#define DECLARE_STRING_GET_METHOD(Class, __set__) \
DECLARE_SIMPLE_GET_METHOD(Class, __set__)
#define DECLARE_STRING_SET_METHOD(Class, __set__) \
DECLARE_SIMPLE_SET_METHOD(Class, toString, __set__)
#define DECLARE_QOBJECT_GET_METHOD(Class, __get__) \
BEGIN_DECLARE_METHOD(Class, __get__) { \
return eng->newQObject(self->__get__()); \
} END_DECLARE_METHOD
#define DECLARE_QOBJECT_SET_METHOD(Class, __set__) \
DECLARE_SIMPLE_SET_METHOD(Class, toQObject, __set__)
#define DECLARE_BOOLEAN_GET_SET_METHODS(Class, __get__, __set__) \
DECLARE_BOOLEAN_GET_METHOD(Class, __get__) \
DECLARE_BOOLEAN_SET_METHOD(Class, __set__)
#define DECLARE_NUMBER_GET_SET_METHODS(Class, __get__, __set__) \
DECLARE_NUMBER_GET_METHOD(Class, __get__) \
DECLARE_NUMBER_SET_METHOD(Class, __set__)
#define DECLARE_INT_GET_SET_METHODS(Class, __get__, __set__) \
DECLARE_INT_GET_METHOD(Class, __get__) \
DECLARE_INT_SET_METHOD(Class, __set__)
#define DECLARE_STRING_GET_SET_METHODS(Class, __get__, __set__) \
DECLARE_STRING_GET_METHOD(Class, __get__) \
DECLARE_STRING_SET_METHOD(Class, __set__)
#define DECLARE_QOBJECT_GET_SET_METHODS(Class, __get__, __set__) \
DECLARE_QOBJECT_GET_METHOD(Class, __get__) \
DECLARE_QOBJECT_SET_METHOD(Class, __set__)
#define DECLARE_VOID_METHOD(Class, __fun__) \
BEGIN_DECLARE_METHOD(Class, __fun__) { \
self->__fun__(); \
return eng->undefinedValue(); \
} END_DECLARE_METHOD
#define DECLARE_VOID_NUMBER_METHOD(Class, __fun__) \
BEGIN_DECLARE_METHOD(Class, __fun__) { \
self->__fun__(ctx->argument(0).toNumber()); \
return eng->undefinedValue(); \
} END_DECLARE_METHOD
#define DECLARE_VOID_NUMBER_NUMBER_METHOD(Class, __fun__) \
BEGIN_DECLARE_METHOD(Class, __fun__) { \
self->__fun__(ctx->argument(0).toNumber(), ctx->argument(1).toNumber()); \
return eng->undefinedValue(); \
} END_DECLARE_METHOD
#define DECLARE_VOID_QUAD_NUMBER_METHOD(Class, __fun__) \
BEGIN_DECLARE_METHOD(Class, __fun__) { \
self->__fun__(ctx->argument(0).toNumber(), ctx->argument(1).toNumber(), ctx->argument(2).toNumber(), ctx->argument(3).toNumber()); \
return eng->undefinedValue(); \
} END_DECLARE_METHOD
#define DECLARE_VOID_1ARG_METHOD(Class, ArgType, __fun__) \
BEGIN_DECLARE_METHOD(Class, __fun__) { \
self->__fun__(qscriptvalue_cast<ArgType>(ctx->argument(0))); \
return eng->undefinedValue(); \
} END_DECLARE_METHOD
#define DECLARE_BOOLEAN_1ARG_METHOD(Class, ArgType, __fun__) \
BEGIN_DECLARE_METHOD(Class, __fun__) { \
return QScriptValue(eng, self->__fun__(qscriptvalue_cast<ArgType>(ctx->argument(0)))); \
} END_DECLARE_METHOD
#define DECLARE_POINTER_METATYPE(T) \
Q_DECLARE_METATYPE(T*) \
Q_DECLARE_METATYPE(QScript::Pointer<T>::wrapped_pointer_type)
namespace QScript
{
enum {
UserOwnership = 1
};
template <typename T>
class Pointer : public QSharedData
{
public:
typedef T* pointer_type;
typedef QExplicitlySharedDataPointer<Pointer<T> > wrapped_pointer_type;
~Pointer()
{
if (!(m_flags & UserOwnership))
delete m_value;
}
operator T*()
{
return m_value;
}
operator const T*() const
{
return m_value;
}
static wrapped_pointer_type create(T *value, uint flags = 0)
{
return wrapped_pointer_type(new Pointer(value, flags));
}
static QScriptValue toScriptValue(QScriptEngine *engine, T* const &source)
{
if (!source)
return engine->nullValue();
return engine->newVariant(qVariantFromValue(source));
}
static void fromScriptValue(const QScriptValue &value, T* &target)
{
if (value.isVariant()) {
QVariant var = value.toVariant();
if (var.canConvert<T*>()) {
target = qvariant_cast<T*>(var);
} else if (var.canConvert<wrapped_pointer_type>()) {
target = qvariant_cast<wrapped_pointer_type>(var)->operator T*();
} else {
// look in prototype chain
target = 0;
int type = qMetaTypeId<T*>();
int pointerType = qMetaTypeId<wrapped_pointer_type>();
QScriptValue proto = value.prototype();
while (proto.isObject() && proto.isVariant()) {
int protoType = proto.toVariant().userType();
if ((type == protoType) || (pointerType == protoType)) {
QByteArray name = QMetaType::typeName(var.userType());
if (name.startsWith("QScript::Pointer<")) {
target = (*reinterpret_cast<wrapped_pointer_type*>(var.data()))->operator T*();
break;
} else {
target = static_cast<T*>(var.data());
break;
}
}
proto = proto.prototype();
}
}
} else if (value.isQObject()) {
QObject *qobj = value.toQObject();
QByteArray typeName = QMetaType::typeName(qMetaTypeId<T*>());
target = reinterpret_cast<T*>(qobj->qt_metacast(typeName.left(typeName.size()-1)));
} else {
target = 0;
}
}
uint flags() const
{ return m_flags; }
void setFlags(uint flags)
{ m_flags = flags; }
void unsetFlags(uint flags)
{ m_flags &= ~flags; }
protected:
Pointer(T* value, uint flags)
: m_flags(flags), m_value(value)
{}
private:
uint m_flags;
T* m_value;
};
template <typename T>
int registerPointerMetaType(
QScriptEngine *eng,
const QScriptValue &prototype = QScriptValue(),
T * /* dummy */ = 0
)
{
QScriptValue (*mf)(QScriptEngine *, T* const &) = Pointer<T>::toScriptValue;
void (*df)(const QScriptValue &, T* &) = Pointer<T>::fromScriptValue;
const int id = qMetaTypeId<T*>();
qScriptRegisterMetaType_helper(
eng, id, reinterpret_cast<QScriptEngine::MarshalFunction>(mf),
reinterpret_cast<QScriptEngine::DemarshalFunction>(df),
prototype);
eng->setDefaultPrototype(qMetaTypeId<typename Pointer<T>::wrapped_pointer_type>(), prototype);
return id;
}
inline void maybeReleaseOwnership(const QScriptValue &value)
{
if (value.isVariant()) {
QVariant var = value.toVariant();
QByteArray name = QMetaType::typeName(var.userType());
if (name.startsWith("QScript::Pointer<"))
(*reinterpret_cast<Pointer<void*>::wrapped_pointer_type *>(var.data()))->setFlags(UserOwnership);
}
}
inline void maybeTakeOwnership(const QScriptValue &value)
{
if (value.isVariant()) {
QVariant var = value.toVariant();
QByteArray name = QMetaType::typeName(var.userType());
if (name.startsWith("QScript::Pointer<"))
(*reinterpret_cast<Pointer<void*>::wrapped_pointer_type *>(var.data()))->unsetFlags(UserOwnership);
}
}
template <class T>
inline QScriptValue wrapPointer(QScriptEngine *eng, T *ptr, uint flags = 0)
{
return eng->newVariant(qVariantFromValue(Pointer<T>::create(ptr, flags)));
}
} // namespace QScript
#endif // QTSCRIPTEXTENSIONS_GLOBAL_H
......@@ -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;
}
}
......