3
0
mirror of https://github.com/Qortal/Brooklyn.git synced 2025-02-19 13:45:54 +00:00
2022-03-05 22:41:29 +05:00

425 lines
18 KiB
C++

/*
SPDX-FileCopyrightText: 2009 Aaron Seigo <aseigo@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "scriptengine.h"
#include "debug.h"
#include "scriptengine_v1.h"
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QFutureWatcher>
#include <QJSValueIterator>
#include <QStandardPaths>
#include <KLocalizedContext>
#include <QDebug>
#include <klocalizedstring.h>
#include <kmimetypetrader.h>
#include <kservicetypetrader.h>
#include <kshell.h>
#include <KPackage/Package>
#include <KPackage/PackageLoader>
#include <Plasma/Applet>
#include <Plasma/Containment>
#include <Plasma/PluginLoader>
#include <qstandardpaths.h>
#include "../screenpool.h"
#include "../standaloneappcorona.h"
#include "appinterface.h"
#include "configgroup.h"
#include "containment.h"
#include "panel.h"
#include "widget.h"
namespace WorkspaceScripting
{
ScriptEngine::ScriptEngine(Plasma::Corona *corona, QObject *parent)
: QJSEngine(parent)
, m_corona(corona)
{
Q_ASSERT(m_corona);
m_appInterface = new AppInterface(this);
connect(m_appInterface, &AppInterface::print, this, &ScriptEngine::print);
m_scriptSelf = globalObject();
m_globalScriptEngineObject = new ScriptEngine::V1(this);
m_localizedContext = new KLocalizedContext(this);
setupEngine();
}
ScriptEngine::~ScriptEngine()
{
}
QString ScriptEngine::errorString() const
{
return m_errorString;
}
QJSValue ScriptEngine::wrap(Plasma::Applet *w)
{
Widget *wrapper = new Widget(w, this);
return newQObject(wrapper);
}
QJSValue ScriptEngine::wrap(Plasma::Containment *c)
{
Containment *wrapper = isPanel(c) ? new Panel(c, this) : new Containment(c, this);
return newQObject(wrapper);
}
int ScriptEngine::defaultPanelScreen() const
{
return 1;
}
QJSValue ScriptEngine::newError(const QString &message)
{
return evaluate(QStringLiteral("new Error('%1');").arg(message));
}
QString ScriptEngine::onlyExec(const QString &commandLine)
{
if (commandLine.isEmpty()) {
return commandLine;
}
return KShell::splitArgs(commandLine, KShell::TildeExpand).first();
}
void ScriptEngine::setupEngine()
{
QJSValue globalScriptEngineObject = newQObject(m_globalScriptEngineObject);
QJSValue localizedContext = newQObject(m_localizedContext);
QJSValue appInterface = newQObject(m_appInterface);
// AppInterface stuff
// FIXME: this line doesn't have much effect for now, if QTBUG-68397 gets fixed,
// all the connects to rewrite the properties won't be necessary anymore
// globalObject().setPrototype(appInterface);
// FIXME: remove __AppInterface if QTBUG-68397 gets solved
// as workaround we build manually a js object with getters and setters
m_scriptSelf.setProperty(QStringLiteral("__AppInterface"), appInterface);
QJSValue res = evaluate(
"__proto__ = {\
get locked() {return __AppInterface.locked;},\
get hasBattery() {return __AppInterface.hasBattery;},\
get screenCount() {return __AppInterface.screenCount;},\
get activityIds() {return __AppInterface.activityIds;},\
get panelIds() {return __AppInterface.panelIds;},\
get knownPanelTypes() {return __AppInterface.knownPanelTypes;},\
get knownActivityTypes() {return __AppInterface.knownActivityTypes;},\
get knownWidgetTypes() {return __AppInterface.knownWidgetTypes;},\
get theme() {return __AppInterface.theme;},\
set theme(name) {__AppInterface.theme = name;},\
get applicationVersion() {return __AppInterface.applicationVersion;},\
get platformVersion() {return __AppInterface.platformVersion;},\
get scriptingVersion() {return __AppInterface.scriptingVersion;},\
get multihead() {return __AppInterface.multihead;},\
get multiheadScreen() {return __AppInterface.multihead;},\
get locale() {return __AppInterface.locale;},\
get language() {return __AppInterface.language;},\
get languageId() {return __AppInterface.languageId;},\
}");
Q_ASSERT(!res.isError());
// methods from AppInterface
m_scriptSelf.setProperty(QStringLiteral("screenGeometry"), appInterface.property("screenGeometry"));
m_scriptSelf.setProperty(QStringLiteral("lockCorona"), appInterface.property("lockCorona"));
m_scriptSelf.setProperty(QStringLiteral("sleep"), appInterface.property("sleep"));
m_scriptSelf.setProperty(QStringLiteral("print"), appInterface.property("print"));
m_scriptSelf.setProperty(QStringLiteral("getApiVersion"), globalScriptEngineObject.property("getApiVersion"));
// Constructors: prefer them js based as they make the c++ code of panel et al way simpler without hacks to get the engine
m_scriptSelf.setProperty(QStringLiteral("__newPanel"), globalScriptEngineObject.property("newPanel"));
m_scriptSelf.setProperty(QStringLiteral("__newConfigFile"), globalScriptEngineObject.property("configFile"));
// definitions of qrectf properties from documentation
// only properties/functions which were already binded are.
// TODO KF6: just a plain QRectF binding
res = evaluate(
"function QRectF(x,y,w,h) {\
return {x: x, y: y, width: w, height: h,\
get left() {return this.x},\
get top() {return this.y},\
get right() {return this.x + this.width},\
get bottom() {return this.y + this.height},\
get empty() {return this.width <= 0 || this.height <= 0},\
get null() {return this.width == 0 || this.height == 0},\
get valid() {return !this.empty},\
adjust: function(dx1, dy1, dx2, dy2) {\
this.x += dx1; this.y += dy1;\
this.width = this.width - dx1 + dx2;\
this.height = this.height - dy1 + dy2;},\
adjusted: function(dx1, dy1, dx2, dy2) {\
return new QRectF(this.x + dx1, this.y + dy1,\
this.width - dx1 + dx2,\
this.height - dy1 + dy2)},\
translate: function(dx, dy) {this.x += dx; this.y += dy;},\
setCoords: function(x1, y1, x2, y2) {\
this.x = x1; this.y = y1;\
this.width = x2 - x1;\
this.height = y2 - y1;},\
setRect: function(x1, y1, w1, h1) {\
this.x = x1; this.y = y1;\
this.width = w1; this.height = h1;},\
contains: function(x1, y1) { return x1 >= this.x && x1 <= this.x + this.width && y1 >= this.y && y1 <= this.y + this.height},\
moveBottom: function(bottom1) {this.y = bottom1 - this.height;},\
moveLeft: function(left1) {this.x = left1;},\
moveRight: function(right1) {this.x = right1 - this.width;},\
moveTop: function(top1) {this.y = top1;},\
moveTo: function(x1, y1) {this.x = x1; this.y = y1;}\
}};\
function ConfigFile(config, group){return __newConfigFile(config, group)};\
function Panel(plugin){return __newPanel(plugin)};");
Q_ASSERT(!res.isError());
m_scriptSelf.setProperty(QStringLiteral("createActivity"), globalScriptEngineObject.property("createActivity"));
m_scriptSelf.setProperty(QStringLiteral("setCurrentActivity"), globalScriptEngineObject.property("setCurrentActivity"));
m_scriptSelf.setProperty(QStringLiteral("currentActivity"), globalScriptEngineObject.property("currentActivity"));
m_scriptSelf.setProperty(QStringLiteral("activities"), globalScriptEngineObject.property("activities"));
m_scriptSelf.setProperty(QStringLiteral("activityName"), globalScriptEngineObject.property("activityName"));
m_scriptSelf.setProperty(QStringLiteral("setActivityName"), globalScriptEngineObject.property("setActivityName"));
m_scriptSelf.setProperty(QStringLiteral("loadSerializedLayout"), globalScriptEngineObject.property("loadSerializedLayout"));
m_scriptSelf.setProperty(QStringLiteral("desktopsForActivity"), globalScriptEngineObject.property("desktopsForActivity"));
m_scriptSelf.setProperty(QStringLiteral("desktops"), globalScriptEngineObject.property("desktops"));
m_scriptSelf.setProperty(QStringLiteral("desktopById"), globalScriptEngineObject.property("desktopById"));
m_scriptSelf.setProperty(QStringLiteral("desktopForScreen"), globalScriptEngineObject.property("desktopForScreen"));
m_scriptSelf.setProperty(QStringLiteral("screenForConnector"), globalScriptEngineObject.property("screenForConnector"));
m_scriptSelf.setProperty(QStringLiteral("panelById"), globalScriptEngineObject.property("panelById"));
m_scriptSelf.setProperty(QStringLiteral("panels"), globalScriptEngineObject.property("panels"));
m_scriptSelf.setProperty(QStringLiteral("fileExists"), globalScriptEngineObject.property("fileExists"));
m_scriptSelf.setProperty(QStringLiteral("loadTemplate"), globalScriptEngineObject.property("loadTemplate"));
m_scriptSelf.setProperty(QStringLiteral("applicationExists"), globalScriptEngineObject.property("applicationExists"));
m_scriptSelf.setProperty(QStringLiteral("defaultApplication"), globalScriptEngineObject.property("defaultApplication"));
m_scriptSelf.setProperty(QStringLiteral("userDataPath"), globalScriptEngineObject.property("userDataPath"));
m_scriptSelf.setProperty(QStringLiteral("applicationPath"), globalScriptEngineObject.property("applicationPath"));
m_scriptSelf.setProperty(QStringLiteral("knownWallpaperPlugins"), globalScriptEngineObject.property("knownWallpaperPlugins"));
m_scriptSelf.setProperty(QStringLiteral("gridUnit"), globalScriptEngineObject.property("gridUnit"));
m_scriptSelf.setProperty(QStringLiteral("setImmutability"), globalScriptEngineObject.property("setImmutability"));
m_scriptSelf.setProperty(QStringLiteral("immutability"), globalScriptEngineObject.property("immutability"));
// i18n
m_scriptSelf.setProperty(QStringLiteral("i18n"), localizedContext.property("i18n"));
m_scriptSelf.setProperty(QStringLiteral("i18nc"), localizedContext.property("i18nc"));
m_scriptSelf.setProperty(QStringLiteral("i18np"), localizedContext.property("i18np"));
m_scriptSelf.setProperty(QStringLiteral("i18ncp"), localizedContext.property("i18ncp"));
m_scriptSelf.setProperty(QStringLiteral("i18nd"), localizedContext.property("i18nd"));
m_scriptSelf.setProperty(QStringLiteral("i18ndc"), localizedContext.property("i18ndc"));
m_scriptSelf.setProperty(QStringLiteral("i18ndp"), localizedContext.property("i18ndp"));
m_scriptSelf.setProperty(QStringLiteral("i18ndcp"), localizedContext.property("i18ndcp"));
m_scriptSelf.setProperty(QStringLiteral("xi18n"), localizedContext.property("xi18n"));
m_scriptSelf.setProperty(QStringLiteral("xi18nc"), localizedContext.property("xi18nc"));
m_scriptSelf.setProperty(QStringLiteral("xi18np"), localizedContext.property("xi18np"));
m_scriptSelf.setProperty(QStringLiteral("xi18ncp"), localizedContext.property("xi18ncp"));
m_scriptSelf.setProperty(QStringLiteral("xi18nd"), localizedContext.property("xi18nd"));
m_scriptSelf.setProperty(QStringLiteral("xi18ndc"), localizedContext.property("xi18ndc"));
m_scriptSelf.setProperty(QStringLiteral("xi18ndp"), localizedContext.property("xi18ndp"));
m_scriptSelf.setProperty(QStringLiteral("xi18ndcp"), localizedContext.property("xi18ndcp"));
}
bool ScriptEngine::isPanel(const Plasma::Containment *c)
{
if (!c) {
return false;
}
return c->containmentType() == Plasma::Types::PanelContainment || c->containmentType() == Plasma::Types::CustomPanelContainment;
}
Plasma::Corona *ScriptEngine::corona() const
{
return m_corona;
}
bool ScriptEngine::evaluateScript(const QString &script, const QString &path)
{
m_errorString = QString();
QJSValue result = evaluate(script, path);
if (result.isError()) {
QString error = i18n("Error: %1 at line %2\n\nBacktrace:\n%3",
result.toString(),
result.property("lineNumber").toInt(),
result.property("stack").toVariant().value<QStringList>().join(QLatin1String("\n ")));
Q_EMIT printError(error);
Q_EMIT exception(result);
m_errorString = error;
return false;
}
return true;
}
void ScriptEngine::exception(const QJSValue &value)
{
Q_EMIT printError(value.toVariant().toString());
}
QStringList ScriptEngine::pendingUpdateScripts(Plasma::Corona *corona)
{
if (!corona->kPackage().isValid()) {
qCWarning(PLASMASHELL) << "Warning: corona package invalid";
return QStringList();
}
const QString appName = corona->kPackage().metadata().pluginId();
QStringList scripts;
const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
"plasma/shells/" + appName + QStringLiteral("/contents/updates"),
QStandardPaths::LocateDirectory);
for (const QString &dir : dirs) {
QDirIterator it(dir, QStringList() << QStringLiteral("*.js"));
while (it.hasNext()) {
scripts.append(it.next());
}
}
QStringList scriptPaths;
if (scripts.isEmpty()) {
return scriptPaths;
}
KConfigGroup cg(KSharedConfig::openConfig(), "Updates");
QStringList performed = cg.readEntry("performed", QStringList());
const QString localXdgDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
foreach (const QString &script, scripts) {
if (performed.contains(script)) {
continue;
}
if (script.startsWith(localXdgDir)) {
continue;
}
scriptPaths.append(script);
performed.append(script);
}
cg.writeEntry("performed", performed);
KSharedConfig::openConfig()->sync();
return scriptPaths;
}
QStringList ScriptEngine::availableActivities() const
{
ShellCorona *sc = qobject_cast<ShellCorona *>(m_corona);
StandaloneAppCorona *ac = qobject_cast<StandaloneAppCorona *>(m_corona);
if (sc) {
return sc->availableActivities();
} else if (ac) {
return ac->availableActivities();
}
return QStringList();
}
QList<Containment *> ScriptEngine::desktopsForActivity(const QString &id)
{
QList<Containment *> result;
// confirm this activity actually exists
bool found = false;
for (const QString &act : availableActivities()) {
if (act == id) {
found = true;
break;
}
}
if (!found) {
return result;
}
foreach (Plasma::Containment *c, m_corona->containments()) {
if (c->activity() == id && !isPanel(c)) {
result << new Containment(c, this);
}
}
if (result.count() == 0) {
// we have no desktops for this activity, so lets make them now
// this can happen when the activity already exists but has never been activated
// with the current shell package and layout.js is run to set up the shell for the
// first time
ShellCorona *sc = qobject_cast<ShellCorona *>(m_corona);
StandaloneAppCorona *ac = qobject_cast<StandaloneAppCorona *>(m_corona);
if (sc) {
foreach (int i, sc->screenIds()) {
result << new Containment(sc->createContainmentForActivity(id, i), this);
}
} else if (ac) {
const int numScreens = m_corona->numScreens();
for (int i = 0; i < numScreens; ++i) {
result << new Containment(ac->createContainmentForActivity(id, i), this);
}
}
}
return result;
}
Plasma::Containment *ScriptEngine::createContainment(const QString &type, const QString &plugin)
{
bool exists = false;
const QList<KPluginMetaData> list = Plasma::PluginLoader::listContainmentsMetaDataOfType(type);
foreach (const KPluginMetaData &pluginInfo, list) {
if (pluginInfo.pluginId() == plugin) {
exists = true;
break;
}
}
if (!exists) {
return nullptr;
}
Plasma::Containment *c = nullptr;
if (type == QLatin1String("Panel")) {
ShellCorona *sc = qobject_cast<ShellCorona *>(m_corona);
StandaloneAppCorona *ac = qobject_cast<StandaloneAppCorona *>(m_corona);
if (sc) {
c = sc->addPanel(plugin);
} else if (ac) {
c = ac->addPanel(plugin);
}
} else {
c = m_corona->createContainment(plugin);
}
if (c) {
if (type == QLatin1String("Panel")) {
// some defaults
c->setFormFactor(Plasma::Types::Horizontal);
c->setLocation(Plasma::Types::TopEdge);
// we have to force lastScreen of the newly created containment,
// or it won't have a screen yet at that point, breaking JS code
// that relies on it
// NOTE: if we'll allow setting a panel screen from JS, it will have to use the following lines as well
KConfigGroup cg = c->config();
cg.writeEntry(QStringLiteral("lastScreen"), 0);
c->restore(cg);
}
c->updateConstraints(Plasma::Types::AllConstraints | Plasma::Types::StartupCompletedConstraint);
c->flushPendingConstraintsEvents();
}
return c;
}
Containment *ScriptEngine::createContainmentWrapper(const QString &type, const QString &plugin)
{
Plasma::Containment *c = createContainment(type, plugin);
return isPanel(c) ? new Panel(c, this) : new Containment(c, this);
}
} // namespace WorkspaceScripting