File indexing completed on 2024-05-12 17:10:23
0001 /* 0002 SPDX-FileCopyrightText: 2009 Aaron Seigo <aseigo@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "scriptengine.h" 0008 #include "debug.h" 0009 #include "scriptengine_v1.h" 0010 0011 #include <QDir> 0012 #include <QDirIterator> 0013 #include <QFile> 0014 #include <QFileInfo> 0015 #include <QFutureWatcher> 0016 #include <QJSValueIterator> 0017 #include <QStandardPaths> 0018 0019 #include <KLocalizedContext> 0020 #include <QDebug> 0021 #include <klocalizedstring.h> 0022 #include <kmimetypetrader.h> 0023 #include <kshell.h> 0024 0025 #include <KPackage/Package> 0026 #include <KPackage/PackageLoader> 0027 #include <Plasma/Applet> 0028 #include <Plasma/Containment> 0029 #include <Plasma/PluginLoader> 0030 #include <qstandardpaths.h> 0031 0032 #include "../screenpool.h" 0033 #include "../standaloneappcorona.h" 0034 #include "appinterface.h" 0035 #include "configgroup.h" 0036 #include "containment.h" 0037 #include "panel.h" 0038 #include "widget.h" 0039 0040 namespace WorkspaceScripting 0041 { 0042 ScriptEngine::ScriptEngine(Plasma::Corona *corona, QObject *parent) 0043 : QJSEngine(parent) 0044 , m_corona(corona) 0045 { 0046 Q_ASSERT(m_corona); 0047 m_appInterface = new AppInterface(this); 0048 connect(m_appInterface, &AppInterface::print, this, &ScriptEngine::print); 0049 m_scriptSelf = globalObject(); 0050 m_globalScriptEngineObject = new ScriptEngine::V1(this); 0051 m_localizedContext = new KLocalizedContext(this); 0052 setupEngine(); 0053 } 0054 0055 ScriptEngine::~ScriptEngine() 0056 { 0057 } 0058 0059 QString ScriptEngine::errorString() const 0060 { 0061 return m_errorString; 0062 } 0063 0064 QJSValue ScriptEngine::wrap(Plasma::Applet *w) 0065 { 0066 Widget *wrapper = new Widget(w, this); 0067 return newQObject(wrapper); 0068 } 0069 0070 QJSValue ScriptEngine::wrap(Plasma::Containment *c) 0071 { 0072 Containment *wrapper = isPanel(c) ? new Panel(c, this) : new Containment(c, this); 0073 return newQObject(wrapper); 0074 } 0075 0076 int ScriptEngine::defaultPanelScreen() const 0077 { 0078 return 1; 0079 } 0080 0081 QJSValue ScriptEngine::newError(const QString &message) 0082 { 0083 return evaluate(QStringLiteral("new Error('%1');").arg(message)); 0084 } 0085 0086 QString ScriptEngine::onlyExec(const QString &commandLine) 0087 { 0088 if (commandLine.isEmpty()) { 0089 return commandLine; 0090 } 0091 0092 return KShell::splitArgs(commandLine, KShell::TildeExpand).first(); 0093 } 0094 0095 void ScriptEngine::setupEngine() 0096 { 0097 QJSValue globalScriptEngineObject = newQObject(m_globalScriptEngineObject); 0098 QJSValue localizedContext = newQObject(m_localizedContext); 0099 QJSValue appInterface = newQObject(m_appInterface); 0100 0101 // AppInterface stuff 0102 // FIXME: this line doesn't have much effect for now, if QTBUG-68397 gets fixed, 0103 // all the connects to rewrite the properties won't be necessary anymore 0104 // globalObject().setPrototype(appInterface); 0105 // FIXME: remove __AppInterface if QTBUG-68397 gets solved 0106 // as workaround we build manually a js object with getters and setters 0107 m_scriptSelf.setProperty(QStringLiteral("__AppInterface"), appInterface); 0108 QJSValue res = evaluate( 0109 "__proto__ = {\ 0110 get locked() {return __AppInterface.locked;},\ 0111 get hasBattery() {return __AppInterface.hasBattery;},\ 0112 get screenCount() {return __AppInterface.screenCount;},\ 0113 get activityIds() {return __AppInterface.activityIds;},\ 0114 get panelIds() {return __AppInterface.panelIds;},\ 0115 get knownPanelTypes() {return __AppInterface.knownPanelTypes;},\ 0116 get knownActivityTypes() {return __AppInterface.knownActivityTypes;},\ 0117 get knownWidgetTypes() {return __AppInterface.knownWidgetTypes;},\ 0118 get theme() {return __AppInterface.theme;},\ 0119 set theme(name) {__AppInterface.theme = name;},\ 0120 get applicationVersion() {return __AppInterface.applicationVersion;},\ 0121 get platformVersion() {return __AppInterface.platformVersion;},\ 0122 get scriptingVersion() {return __AppInterface.scriptingVersion;},\ 0123 get multihead() {return __AppInterface.multihead;},\ 0124 get multiheadScreen() {return __AppInterface.multihead;},\ 0125 get locale() {return __AppInterface.locale;},\ 0126 get language() {return __AppInterface.language;},\ 0127 get languageId() {return __AppInterface.languageId;},\ 0128 }"); 0129 Q_ASSERT(!res.isError()); 0130 // methods from AppInterface 0131 m_scriptSelf.setProperty(QStringLiteral("screenGeometry"), appInterface.property("screenGeometry")); 0132 m_scriptSelf.setProperty(QStringLiteral("lockCorona"), appInterface.property("lockCorona")); 0133 m_scriptSelf.setProperty(QStringLiteral("sleep"), appInterface.property("sleep")); 0134 m_scriptSelf.setProperty(QStringLiteral("print"), appInterface.property("print")); 0135 0136 m_scriptSelf.setProperty(QStringLiteral("getApiVersion"), globalScriptEngineObject.property("getApiVersion")); 0137 0138 // Constructors: prefer them js based as they make the c++ code of panel et al way simpler without hacks to get the engine 0139 m_scriptSelf.setProperty(QStringLiteral("__newPanel"), globalScriptEngineObject.property("newPanel")); 0140 m_scriptSelf.setProperty(QStringLiteral("__newConfigFile"), globalScriptEngineObject.property("configFile")); 0141 // definitions of qrectf properties from documentation 0142 // only properties/functions which were already binded are. 0143 // TODO KF6: just a plain QRectF binding 0144 res = evaluate( 0145 "function QRectF(x,y,w,h) {\ 0146 return {x: x, y: y, width: w, height: h,\ 0147 get left() {return this.x},\ 0148 get top() {return this.y},\ 0149 get right() {return this.x + this.width},\ 0150 get bottom() {return this.y + this.height},\ 0151 get empty() {return this.width <= 0 || this.height <= 0},\ 0152 get null() {return this.width == 0 || this.height == 0},\ 0153 get valid() {return !this.empty},\ 0154 adjust: function(dx1, dy1, dx2, dy2) {\ 0155 this.x += dx1; this.y += dy1;\ 0156 this.width = this.width - dx1 + dx2;\ 0157 this.height = this.height - dy1 + dy2;},\ 0158 adjusted: function(dx1, dy1, dx2, dy2) {\ 0159 return new QRectF(this.x + dx1, this.y + dy1,\ 0160 this.width - dx1 + dx2,\ 0161 this.height - dy1 + dy2)},\ 0162 translate: function(dx, dy) {this.x += dx; this.y += dy;},\ 0163 setCoords: function(x1, y1, x2, y2) {\ 0164 this.x = x1; this.y = y1;\ 0165 this.width = x2 - x1;\ 0166 this.height = y2 - y1;},\ 0167 setRect: function(x1, y1, w1, h1) {\ 0168 this.x = x1; this.y = y1;\ 0169 this.width = w1; this.height = h1;},\ 0170 contains: function(x1, y1) { return x1 >= this.x && x1 <= this.x + this.width && y1 >= this.y && y1 <= this.y + this.height},\ 0171 moveBottom: function(bottom1) {this.y = bottom1 - this.height;},\ 0172 moveLeft: function(left1) {this.x = left1;},\ 0173 moveRight: function(right1) {this.x = right1 - this.width;},\ 0174 moveTop: function(top1) {this.y = top1;},\ 0175 moveTo: function(x1, y1) {this.x = x1; this.y = y1;}\ 0176 }};\ 0177 function ConfigFile(config, group){return __newConfigFile(config, group)};\ 0178 function Panel(plugin){return __newPanel(plugin)};"); 0179 Q_ASSERT(!res.isError()); 0180 0181 m_scriptSelf.setProperty(QStringLiteral("createActivity"), globalScriptEngineObject.property("createActivity")); 0182 m_scriptSelf.setProperty(QStringLiteral("setCurrentActivity"), globalScriptEngineObject.property("setCurrentActivity")); 0183 m_scriptSelf.setProperty(QStringLiteral("currentActivity"), globalScriptEngineObject.property("currentActivity")); 0184 m_scriptSelf.setProperty(QStringLiteral("activities"), globalScriptEngineObject.property("activities")); 0185 m_scriptSelf.setProperty(QStringLiteral("activityName"), globalScriptEngineObject.property("activityName")); 0186 m_scriptSelf.setProperty(QStringLiteral("setActivityName"), globalScriptEngineObject.property("setActivityName")); 0187 m_scriptSelf.setProperty(QStringLiteral("loadSerializedLayout"), globalScriptEngineObject.property("loadSerializedLayout")); 0188 m_scriptSelf.setProperty(QStringLiteral("desktopsForActivity"), globalScriptEngineObject.property("desktopsForActivity")); 0189 m_scriptSelf.setProperty(QStringLiteral("desktops"), globalScriptEngineObject.property("desktops")); 0190 m_scriptSelf.setProperty(QStringLiteral("desktopById"), globalScriptEngineObject.property("desktopById")); 0191 m_scriptSelf.setProperty(QStringLiteral("desktopForScreen"), globalScriptEngineObject.property("desktopForScreen")); 0192 m_scriptSelf.setProperty(QStringLiteral("screenForConnector"), globalScriptEngineObject.property("screenForConnector")); 0193 m_scriptSelf.setProperty(QStringLiteral("panelById"), globalScriptEngineObject.property("panelById")); 0194 m_scriptSelf.setProperty(QStringLiteral("panels"), globalScriptEngineObject.property("panels")); 0195 m_scriptSelf.setProperty(QStringLiteral("fileExists"), globalScriptEngineObject.property("fileExists")); 0196 m_scriptSelf.setProperty(QStringLiteral("loadTemplate"), globalScriptEngineObject.property("loadTemplate")); 0197 m_scriptSelf.setProperty(QStringLiteral("applicationExists"), globalScriptEngineObject.property("applicationExists")); 0198 m_scriptSelf.setProperty(QStringLiteral("defaultApplication"), globalScriptEngineObject.property("defaultApplication")); 0199 m_scriptSelf.setProperty(QStringLiteral("userDataPath"), globalScriptEngineObject.property("userDataPath")); 0200 m_scriptSelf.setProperty(QStringLiteral("applicationPath"), globalScriptEngineObject.property("applicationPath")); 0201 m_scriptSelf.setProperty(QStringLiteral("knownWallpaperPlugins"), globalScriptEngineObject.property("knownWallpaperPlugins")); 0202 m_scriptSelf.setProperty(QStringLiteral("gridUnit"), globalScriptEngineObject.property("gridUnit")); 0203 m_scriptSelf.setProperty(QStringLiteral("setImmutability"), globalScriptEngineObject.property("setImmutability")); 0204 m_scriptSelf.setProperty(QStringLiteral("immutability"), globalScriptEngineObject.property("immutability")); 0205 0206 // i18n 0207 m_scriptSelf.setProperty(QStringLiteral("i18n"), localizedContext.property("i18n")); 0208 m_scriptSelf.setProperty(QStringLiteral("i18nc"), localizedContext.property("i18nc")); 0209 m_scriptSelf.setProperty(QStringLiteral("i18np"), localizedContext.property("i18np")); 0210 m_scriptSelf.setProperty(QStringLiteral("i18ncp"), localizedContext.property("i18ncp")); 0211 m_scriptSelf.setProperty(QStringLiteral("i18nd"), localizedContext.property("i18nd")); 0212 m_scriptSelf.setProperty(QStringLiteral("i18ndc"), localizedContext.property("i18ndc")); 0213 m_scriptSelf.setProperty(QStringLiteral("i18ndp"), localizedContext.property("i18ndp")); 0214 m_scriptSelf.setProperty(QStringLiteral("i18ndcp"), localizedContext.property("i18ndcp")); 0215 0216 m_scriptSelf.setProperty(QStringLiteral("xi18n"), localizedContext.property("xi18n")); 0217 m_scriptSelf.setProperty(QStringLiteral("xi18nc"), localizedContext.property("xi18nc")); 0218 m_scriptSelf.setProperty(QStringLiteral("xi18np"), localizedContext.property("xi18np")); 0219 m_scriptSelf.setProperty(QStringLiteral("xi18ncp"), localizedContext.property("xi18ncp")); 0220 m_scriptSelf.setProperty(QStringLiteral("xi18nd"), localizedContext.property("xi18nd")); 0221 m_scriptSelf.setProperty(QStringLiteral("xi18ndc"), localizedContext.property("xi18ndc")); 0222 m_scriptSelf.setProperty(QStringLiteral("xi18ndp"), localizedContext.property("xi18ndp")); 0223 m_scriptSelf.setProperty(QStringLiteral("xi18ndcp"), localizedContext.property("xi18ndcp")); 0224 } 0225 0226 bool ScriptEngine::isPanel(const Plasma::Containment *c) 0227 { 0228 if (!c) { 0229 return false; 0230 } 0231 0232 return c->containmentType() == Plasma::Types::PanelContainment || c->containmentType() == Plasma::Types::CustomPanelContainment; 0233 } 0234 0235 Plasma::Corona *ScriptEngine::corona() const 0236 { 0237 return m_corona; 0238 } 0239 0240 bool ScriptEngine::evaluateScript(const QString &script, const QString &path) 0241 { 0242 m_errorString = QString(); 0243 0244 QJSValue result = evaluate(script, path); 0245 if (result.isError()) { 0246 QString error = i18n("Error: %1 at line %2\n\nBacktrace:\n%3", 0247 result.toString(), 0248 result.property("lineNumber").toInt(), 0249 result.property("stack").toVariant().value<QStringList>().join(QLatin1String("\n "))); 0250 Q_EMIT printError(error); 0251 Q_EMIT exception(result); 0252 m_errorString = error; 0253 return false; 0254 } 0255 0256 return true; 0257 } 0258 0259 void ScriptEngine::exception(const QJSValue &value) 0260 { 0261 Q_EMIT printError(value.toVariant().toString()); 0262 } 0263 0264 QStringList ScriptEngine::pendingUpdateScripts(Plasma::Corona *corona) 0265 { 0266 if (!corona->kPackage().isValid()) { 0267 qCWarning(PLASMASHELL) << "Warning: corona package invalid"; 0268 return QStringList(); 0269 } 0270 0271 const QString appName = corona->kPackage().metadata().pluginId(); 0272 QStringList scripts; 0273 0274 const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, 0275 "plasma/shells/" + appName + QStringLiteral("/contents/updates"), 0276 QStandardPaths::LocateDirectory); 0277 for (const QString &dir : dirs) { 0278 QDirIterator it(dir, QStringList() << QStringLiteral("*.js")); 0279 while (it.hasNext()) { 0280 scripts.append(it.next()); 0281 } 0282 } 0283 QStringList scriptPaths; 0284 0285 if (scripts.isEmpty()) { 0286 return scriptPaths; 0287 } 0288 0289 KConfigGroup cg(KSharedConfig::openConfig(), "Updates"); 0290 QStringList performed = cg.readEntry("performed", QStringList()); 0291 const QString localXdgDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); 0292 0293 foreach (const QString &script, scripts) { 0294 if (performed.contains(script)) { 0295 continue; 0296 } 0297 0298 if (script.startsWith(localXdgDir)) { 0299 continue; 0300 } 0301 0302 scriptPaths.append(script); 0303 performed.append(script); 0304 } 0305 0306 cg.writeEntry("performed", performed); 0307 KSharedConfig::openConfig()->sync(); 0308 return scriptPaths; 0309 } 0310 0311 QStringList ScriptEngine::availableActivities() const 0312 { 0313 ShellCorona *sc = qobject_cast<ShellCorona *>(m_corona); 0314 StandaloneAppCorona *ac = qobject_cast<StandaloneAppCorona *>(m_corona); 0315 if (sc) { 0316 return sc->availableActivities(); 0317 } else if (ac) { 0318 return ac->availableActivities(); 0319 } 0320 0321 return QStringList(); 0322 } 0323 0324 QList<Containment *> ScriptEngine::desktopsForActivity(const QString &id) 0325 { 0326 QList<Containment *> result; 0327 0328 // confirm this activity actually exists 0329 bool found = false; 0330 for (const QString &act : availableActivities()) { 0331 if (act == id) { 0332 found = true; 0333 break; 0334 } 0335 } 0336 0337 if (!found) { 0338 return result; 0339 } 0340 0341 foreach (Plasma::Containment *c, m_corona->containments()) { 0342 if (c->activity() == id && !isPanel(c)) { 0343 result << new Containment(c, this); 0344 } 0345 } 0346 0347 if (result.count() == 0) { 0348 // we have no desktops for this activity, so lets make them now 0349 // this can happen when the activity already exists but has never been activated 0350 // with the current shell package and layout.js is run to set up the shell for the 0351 // first time 0352 ShellCorona *sc = qobject_cast<ShellCorona *>(m_corona); 0353 StandaloneAppCorona *ac = qobject_cast<StandaloneAppCorona *>(m_corona); 0354 if (sc) { 0355 foreach (int i, sc->screenIds()) { 0356 result << new Containment(sc->createContainmentForActivity(id, i), this); 0357 } 0358 } else if (ac) { 0359 const int numScreens = m_corona->numScreens(); 0360 for (int i = 0; i < numScreens; ++i) { 0361 result << new Containment(ac->createContainmentForActivity(id, i), this); 0362 } 0363 } 0364 } 0365 0366 return result; 0367 } 0368 0369 Plasma::Containment *ScriptEngine::createContainment(const QString &type, const QString &plugin) 0370 { 0371 bool exists = false; 0372 const QList<KPluginMetaData> list = Plasma::PluginLoader::listContainmentsMetaDataOfType(type); 0373 foreach (const KPluginMetaData &pluginInfo, list) { 0374 if (pluginInfo.pluginId() == plugin) { 0375 exists = true; 0376 break; 0377 } 0378 } 0379 0380 if (!exists) { 0381 return nullptr; 0382 } 0383 0384 Plasma::Containment *c = nullptr; 0385 if (type == QLatin1String("Panel")) { 0386 ShellCorona *sc = qobject_cast<ShellCorona *>(m_corona); 0387 StandaloneAppCorona *ac = qobject_cast<StandaloneAppCorona *>(m_corona); 0388 if (sc) { 0389 c = sc->addPanel(plugin); 0390 } else if (ac) { 0391 c = ac->addPanel(plugin); 0392 } 0393 } else { 0394 c = m_corona->createContainment(plugin); 0395 } 0396 0397 if (c) { 0398 if (type == QLatin1String("Panel")) { 0399 // we have to force lastScreen of the newly created containment, 0400 // or it won't have a screen yet at that point, breaking JS code 0401 // that relies on it 0402 // NOTE: if we'll allow setting a panel screen from JS, it will have to use the following lines as well 0403 KConfigGroup cg = c->config(); 0404 cg.writeEntry(QStringLiteral("lastScreen"), 0); 0405 c->restore(cg); 0406 } 0407 c->updateConstraints(Plasma::Types::AllConstraints | Plasma::Types::StartupCompletedConstraint); 0408 c->flushPendingConstraintsEvents(); 0409 } 0410 0411 return c; 0412 } 0413 0414 Containment *ScriptEngine::createContainmentWrapper(const QString &type, const QString &plugin) 0415 { 0416 Plasma::Containment *c = createContainment(type, plugin); 0417 return isPanel(c) ? new Panel(c, this) : new Containment(c, this); 0418 } 0419 0420 } // namespace WorkspaceScripting