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