File indexing completed on 2024-05-12 05:38:25
0001 /* 0002 SPDX-FileCopyrightText: 2009 Aaron Seigo <aseigo@kde.org> 0003 SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "scriptengine_v1.h" 0009 #include "debug.h" 0010 0011 #include <QDir> 0012 #include <QDirIterator> 0013 #include <QFile> 0014 #include <QFileInfo> 0015 #include <QFutureWatcher> 0016 #include <QGuiApplication> 0017 #include <QJSValueIterator> 0018 #include <QStandardPaths> 0019 0020 #include <KApplicationTrader> 0021 #include <QDebug> 0022 #include <klocalizedstring.h> 0023 #include <kshell.h> 0024 0025 // KIO 0026 // #include <kemailsettings.h> // no camelcase include 0027 0028 #include <KPackage/Package> 0029 #include <KPackage/PackageLoader> 0030 #include <Plasma/Applet> 0031 #include <Plasma/Containment> 0032 #include <Plasma/PluginLoader> 0033 #include <qstandardpaths.h> 0034 0035 #include <defaultservice.h> 0036 0037 #include "../screenpool.h" 0038 #include "appinterface.h" 0039 #include "configgroup.h" 0040 #include "containment.h" 0041 #include "panel.h" 0042 #include "widget.h" 0043 0044 using namespace Qt::StringLiterals; 0045 0046 namespace 0047 { 0048 template<typename T> 0049 inline void awaitFuture(const QFuture<T> &future) 0050 { 0051 while (!future.isFinished()) { 0052 QCoreApplication::processEvents(); 0053 } 0054 } 0055 0056 class ScriptArray_forEach_Helper 0057 { 0058 public: 0059 ScriptArray_forEach_Helper(const QJSValue &array) 0060 : array(array) 0061 { 0062 } 0063 0064 // operator + is commonly used for these things 0065 // to avoid having the lambda inside the parenthesis 0066 template<typename Function> 0067 void operator+(Function function) const 0068 { 0069 if (!array.isArray()) 0070 return; 0071 0072 int length = array.property("length").toInt(); 0073 for (int i = 0; i < length; ++i) { 0074 function(array.property(i)); 0075 } 0076 } 0077 0078 private: 0079 const QJSValue &array; 0080 }; 0081 0082 #define SCRIPT_ARRAY_FOREACH(Variable, Array) ScriptArray_forEach_Helper(Array) + [&](const QJSValue &Variable) 0083 0084 class ScriptObject_forEach_Helper 0085 { 0086 public: 0087 ScriptObject_forEach_Helper(const QJSValue &object) 0088 : object(object) 0089 { 0090 } 0091 0092 // operator + is commonly used for these things 0093 // to avoid having the lambda inside the parenthesis 0094 template<typename Function> 0095 void operator+(Function function) const 0096 { 0097 QJSValueIterator it(object); 0098 while (it.hasNext()) { 0099 it.next(); 0100 function(it.name(), it.value()); 0101 } 0102 } 0103 0104 private: 0105 const QJSValue &object; 0106 }; 0107 0108 #define SCRIPT_OBJECT_FOREACH(Key, Value, Array) ScriptObject_forEach_Helper(Array) + [&](const QString &Key, const QJSValue &Value) 0109 0110 // Case insensitive comparison of two strings 0111 template<typename StringType> 0112 inline bool matches(const QString &object, const StringType &string) 0113 { 0114 return object.compare(string, Qt::CaseInsensitive) == 0; 0115 } 0116 } 0117 0118 namespace WorkspaceScripting 0119 { 0120 ScriptEngine::V1::V1(ScriptEngine *parent) 0121 : QObject(parent) 0122 , m_engine(parent) 0123 { 0124 } 0125 0126 ScriptEngine::V1::~V1() 0127 { 0128 } 0129 0130 QJSValue ScriptEngine::V1::getApiVersion(const QJSValue ¶m) 0131 { 0132 if (param.toInt() != 1) { 0133 return m_engine->newError(i18n("maximum api version supported is 1")); 0134 } 0135 return m_engine->newQObject(this); 0136 } 0137 0138 int ScriptEngine::V1::gridUnit() const 0139 { 0140 int gridUnit = QFontMetrics(QGuiApplication::font()).boundingRect(QStringLiteral("M")).height(); 0141 if (gridUnit % 2 != 0) { 0142 gridUnit++; 0143 } 0144 0145 return gridUnit; 0146 } 0147 0148 QJSValue ScriptEngine::V1::desktopById(const QJSValue ¶m) const 0149 { 0150 // this needs to work also for string of numberls, like "20" 0151 if (param.isUndefined()) { 0152 return m_engine->newError(i18n("desktopById required an id")); 0153 } 0154 0155 const quint32 id = param.toInt(); 0156 0157 foreach (Plasma::Containment *c, m_engine->m_corona->containments()) { 0158 if (c->id() == id && !isPanel(c)) { 0159 return m_engine->wrap(c); 0160 } 0161 } 0162 0163 return QJSValue(); 0164 } 0165 0166 QJSValue ScriptEngine::V1::desktopsForActivity(const QJSValue &actId) const 0167 { 0168 if (!actId.isString()) { 0169 return m_engine->newError(i18n("desktopsForActivity requires an id")); 0170 } 0171 0172 QJSValue containments = m_engine->newArray(); 0173 int count = 0; 0174 0175 const QString id = actId.toString(); 0176 0177 const auto result = m_engine->desktopsForActivity(id); 0178 0179 for (Containment *c : result) { 0180 containments.setProperty(count, m_engine->newQObject(c)); 0181 ++count; 0182 } 0183 0184 containments.setProperty(QStringLiteral("length"), count); 0185 return containments; 0186 } 0187 0188 QJSValue ScriptEngine::V1::desktopForScreen(const QJSValue ¶m) const 0189 { 0190 // this needs to work also for string of numberls, like "20" 0191 if (param.isUndefined()) { 0192 return m_engine->newError(i18n("activityForScreen requires a screen id")); 0193 } 0194 0195 const uint screen = param.toInt(); 0196 // "null": don't create a containment if it doesn't exist, 0197 // return nullptr instead. 0198 const auto containment = m_engine->m_corona->containmentForScreen(screen, currentActivity(), QStringLiteral("null")); 0199 return m_engine->wrap(containment); 0200 } 0201 0202 // TODO Plasma6: remove this 0203 QJSValue ScriptEngine::V1::screenForConnector(const QJSValue ¶m) const 0204 { 0205 // this needs to work also for string of numerals, like "20" 0206 if (param.isUndefined()) { 0207 return m_engine->newError(i18n("screenForConnector requires a connector name")); 0208 } 0209 0210 const QString connector = param.toString(); 0211 ShellCorona *sc = qobject_cast<ShellCorona *>(m_engine->m_corona); 0212 if (sc) { 0213 return m_engine->toScriptValue<int>(sc->screenPool()->idForName(connector)); 0214 } 0215 return m_engine->toScriptValue<int>(-1); 0216 } 0217 0218 QJSValue ScriptEngine::V1::createActivity(const QJSValue &nameParam, const QString &pluginParam) 0219 { 0220 if (!nameParam.isString()) { 0221 return m_engine->newError(i18n("createActivity required the activity name")); 0222 } 0223 0224 QString plugin = pluginParam; 0225 const QString name = nameParam.toString(); 0226 0227 KActivities::Controller controller; 0228 0229 // This is not the nicest way to do this, but createActivity 0230 // is a synchronous API :/ 0231 QFuture<QString> futureId = controller.addActivity(name); 0232 awaitFuture(futureId); 0233 0234 QString id = futureId.result(); 0235 0236 qDebug() << "Setting default Containment plugin:" << plugin; 0237 0238 ShellCorona *sc = static_cast<ShellCorona *>(m_engine->m_corona); 0239 0240 if (plugin.isEmpty() || plugin == QLatin1String("undefined")) { 0241 plugin = sc->defaultContainmentPlugin(); 0242 } 0243 sc->insertActivity(id, plugin); 0244 0245 return m_engine->toScriptValue<QString>(id); 0246 } 0247 0248 QJSValue ScriptEngine::V1::setCurrentActivity(const QJSValue ¶m) 0249 { 0250 if (!param.isString()) { 0251 return m_engine->newError(i18n("setCurrentActivity required the activity id")); 0252 } 0253 0254 const QString id = param.toString(); 0255 0256 KActivities::Controller controller; 0257 0258 QFuture<bool> task = controller.setCurrentActivity(id); 0259 awaitFuture(task); 0260 0261 return task.result(); 0262 } 0263 0264 QJSValue ScriptEngine::V1::setActivityName(const QJSValue &idParam, const QJSValue &nameParam) 0265 { 0266 if (!idParam.isString() || !nameParam.isString()) { 0267 return m_engine->newError(i18n("setActivityName required the activity id and name")); 0268 } 0269 0270 const QString id = idParam.toString(); 0271 const QString name = nameParam.toString(); 0272 0273 KActivities::Controller controller; 0274 0275 QFuture<void> task = controller.setActivityName(id, name); 0276 awaitFuture(task); 0277 return QJSValue(); 0278 } 0279 0280 QJSValue ScriptEngine::V1::activityName(const QJSValue &idParam) const 0281 { 0282 if (!idParam.isString()) { 0283 return m_engine->newError(i18n("activityName required the activity id")); 0284 } 0285 0286 const QString id = idParam.toString(); 0287 0288 KActivities::Info info(id); 0289 0290 return QJSValue(info.name()); 0291 } 0292 0293 QString ScriptEngine::V1::currentActivity() const 0294 { 0295 KActivities::Consumer consumer; 0296 return consumer.currentActivity(); 0297 } 0298 0299 QJSValue ScriptEngine::V1::activities() const 0300 { 0301 QJSValue acts = m_engine->newArray(); 0302 int count = 0; 0303 0304 const auto result = m_engine->availableActivities(); 0305 0306 for (const auto &a : result) { 0307 acts.setProperty(count, a); 0308 ++count; 0309 } 0310 acts.setProperty(QStringLiteral("length"), count); 0311 0312 return acts; 0313 } 0314 0315 // Utility function to process configs and config groups 0316 template<typename Object> 0317 void loadSerializedConfigs(Object *object, const QJSValue &configs) 0318 { 0319 SCRIPT_OBJECT_FOREACH(escapedGroup, config, configs) 0320 { 0321 // If the config group is set, pass it on to the containment 0322 QStringList groups = escapedGroup.split('/', Qt::SkipEmptyParts); 0323 for (QString &group : groups) { 0324 group = QUrl::fromPercentEncoding(group.toUtf8()); 0325 } 0326 qDebug() << "Config group" << groups; 0327 object->setCurrentConfigGroup(groups); 0328 0329 // Read other properties and set the configuration 0330 SCRIPT_OBJECT_FOREACH(key, value, config) 0331 { 0332 object->writeConfig(key, value); 0333 }; 0334 }; 0335 } 0336 0337 QJSValue ScriptEngine::V1::loadSerializedLayout(const QJSValue &data) 0338 { 0339 if (!data.isObject()) { 0340 return m_engine->newError(i18n("loadSerializedLayout requires the JSON object to deserialize from")); 0341 } 0342 0343 if (data.property("serializationFormatVersion").toInt() != 1) { 0344 return m_engine->newError(i18n("loadSerializedLayout: invalid version of the serialized object")); 0345 } 0346 0347 const auto desktops = m_engine->desktopsForActivity(KActivities::Consumer().currentActivity()); 0348 Q_ASSERT_X(desktops.size() != 0, "V1::loadSerializedLayout", "We need desktops"); 0349 0350 // qDebug() << "DESKTOP DESERIALIZATION: Loading desktops..."; 0351 0352 int count = 0; 0353 SCRIPT_ARRAY_FOREACH(desktopData, data.property("desktops")) 0354 { 0355 // If the template has more desktops than we do, ignore them 0356 if (count >= desktops.size()) 0357 return; 0358 0359 auto desktop = desktops[count]; 0360 // qDebug() << "DESKTOP DESERIALIZATION: var cont = desktopsArray[...]; " << count << " -> " << desktop; 0361 0362 // Setting the wallpaper plugin because it is special 0363 desktop->setWallpaperPlugin(desktopData.property("wallpaperPlugin").toString()); 0364 // qDebug() << "DESKTOP DESERIALIZATION: cont->setWallpaperPlugin(...) " << desktop->wallpaperPlugin(); 0365 0366 // Now, lets go through the configs 0367 loadSerializedConfigs(desktop, desktopData.property("config")); 0368 0369 // After the config, we want to load the applets 0370 SCRIPT_ARRAY_FOREACH(appletData, desktopData.property("applets")) 0371 { 0372 // qDebug() << "DESKTOP DESERIALIZATION: Applet: " << appletData.toString(); 0373 0374 auto appletObject = desktop->addWidget(appletData.property("plugin"), 0375 appletData.property("geometry.x").toInt() * gridUnit(), 0376 appletData.property("geometry.y").toInt() * gridUnit(), 0377 appletData.property("geometry.width").toInt() * gridUnit(), 0378 appletData.property("geometry.height").toInt() * gridUnit()); 0379 0380 if (auto applet = qobject_cast<Widget *>(appletObject.toQObject())) { 0381 // Now, lets go through the configs for the applet 0382 loadSerializedConfigs(applet, appletData.property("config")); 0383 } 0384 }; 0385 0386 count++; 0387 }; 0388 0389 // qDebug() << "PANEL DESERIALIZATION: Loading panels..."; 0390 0391 SCRIPT_ARRAY_FOREACH(panelData, data.property("panels")) 0392 { 0393 const auto panel = qobject_cast<Panel *>(m_engine->createContainmentWrapper(QStringLiteral("Panel"), QStringLiteral("org.kde.panel"))); 0394 0395 Q_ASSERT(panel); 0396 0397 // Basic panel setup 0398 panel->setLocation(panelData.property("location").toString()); 0399 panel->setHeight(panelData.property("height").toNumber() * gridUnit()); 0400 panel->setLengthMode(panelData.property("lengthMode").toString()); 0401 panel->setMaximumLength(panelData.property("maximumLength").toNumber() * gridUnit()); 0402 panel->setMinimumLength(panelData.property("minimumLength").toNumber() * gridUnit()); 0403 panel->setOffset(panelData.property("offset").toNumber() * gridUnit()); 0404 panel->setAlignment(panelData.property("alignment").toString()); 0405 panel->setHiding(panelData.property("hiding").toString()); 0406 0407 // Loading the config for the panel 0408 loadSerializedConfigs(panel, panelData.property("config")); 0409 0410 // Now dealing with the applets 0411 SCRIPT_ARRAY_FOREACH(appletData, panelData.property("applets")) 0412 { 0413 // qDebug() << "PANEL DESERIALIZATION: Applet: " << appletData.toString(); 0414 0415 auto appletObject = panel->addWidget(appletData.property("plugin")); 0416 // qDebug() << "PANEL DESERIALIZATION: addWidget" 0417 // << appletData.property("plugin").toString() 0418 // ; 0419 0420 if (auto applet = qobject_cast<Widget *>(appletObject.toQObject())) { 0421 // Now, lets go through the configs for the applet 0422 loadSerializedConfigs(applet, appletData.property("config")); 0423 } 0424 }; 0425 }; 0426 0427 return QJSValue(); 0428 } 0429 0430 QJSValue ScriptEngine::V1::newPanel(const QString &plugin) 0431 { 0432 return createContainment(QStringLiteral("Panel"), QStringLiteral("org.kde.panel"), plugin); 0433 } 0434 0435 QJSValue ScriptEngine::V1::panelById(const QJSValue &idParam) const 0436 { 0437 // this needs to work also for string of numberls, like "20" 0438 if (idParam.isUndefined()) { 0439 return m_engine->newError(i18n("panelById requires an id")); 0440 } 0441 0442 const quint32 id = idParam.toInt(); 0443 0444 foreach (Plasma::Containment *c, m_engine->m_corona->containments()) { 0445 if (c->id() == id && isPanel(c)) { 0446 return m_engine->wrap(c); 0447 } 0448 } 0449 0450 return QJSValue(); 0451 } 0452 0453 QJSValue ScriptEngine::V1::desktops() const 0454 { 0455 QJSValue containments = m_engine->newArray(); 0456 int count = 0; 0457 0458 const auto result = m_engine->m_corona->containments(); 0459 0460 for (const auto c : result) { 0461 // make really sure we get actual desktops, so check for a non empty 0462 // activity id 0463 if (!isPanel(c) && !c->activity().isEmpty()) { 0464 containments.setProperty(count, m_engine->wrap(c)); 0465 ++count; 0466 } 0467 } 0468 0469 containments.setProperty(QStringLiteral("length"), count); 0470 return containments; 0471 } 0472 0473 QJSValue ScriptEngine::V1::panels() const 0474 { 0475 QJSValue panels = m_engine->newArray(); 0476 int count = 0; 0477 0478 const auto result = m_engine->m_corona->containments(); 0479 0480 for (const auto c : result) { 0481 if (isPanel(c)) { 0482 panels.setProperty(count, m_engine->wrap(c)); 0483 ++count; 0484 } 0485 } 0486 panels.setProperty(QStringLiteral("length"), count); 0487 0488 return panels; 0489 } 0490 0491 bool ScriptEngine::V1::fileExists(const QString &path) const 0492 { 0493 if (path.isEmpty()) { 0494 return false; 0495 } 0496 0497 QFile f(KShell::tildeExpand(path)); 0498 return f.exists(); 0499 } 0500 0501 bool ScriptEngine::V1::loadTemplate(const QString &layout) 0502 { 0503 if (layout.isEmpty() || layout.contains(QLatin1Char('\''))) { 0504 // qDebug() << "layout is empty"; 0505 return false; 0506 } 0507 0508 auto filter = [&layout](const KPluginMetaData &md) -> bool { 0509 return md.pluginId() == layout && md.value(QStringLiteral("X-Plasma-ContainmentCategories"), QStringList()).contains(QLatin1String("panel")); 0510 }; 0511 QList<KPluginMetaData> offers = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/LayoutTemplate"), QString(), filter); 0512 0513 if (offers.isEmpty()) { 0514 // qDebug() << "offers fail" << constraint; 0515 return false; 0516 } 0517 0518 KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LayoutTemplate")); 0519 KPluginMetaData pluginData(offers.first()); 0520 0521 QString path; 0522 { 0523 ShellCorona *sc = qobject_cast<ShellCorona *>(m_engine->m_corona); 0524 if (sc) { 0525 const QString overridePackagePath = sc->lookAndFeelPackage().path() + QLatin1String("contents/layouts/") + pluginData.pluginId(); 0526 0527 path = overridePackagePath + QStringLiteral("/metadata.json"); 0528 if (QFile::exists(path)) { 0529 package.setPath(overridePackagePath); 0530 } 0531 0532 path = overridePackagePath + QStringLiteral("/metadata.desktop"); 0533 if (QFile::exists(path)) { 0534 package.setPath(overridePackagePath); 0535 } 0536 } 0537 } 0538 0539 if (!package.isValid()) { 0540 path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, package.defaultPackageRoot() + pluginData.pluginId() + "/metadata.json"); 0541 if (path.isEmpty()) { 0542 path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, package.defaultPackageRoot() + pluginData.pluginId() + "/metadata.desktop"); 0543 } 0544 if (path.isEmpty()) { 0545 // qDebug() << "script path is empty"; 0546 return false; 0547 } 0548 0549 package.setPath(pluginData.pluginId()); 0550 } 0551 0552 const QString scriptFile = package.filePath("mainscript"); 0553 if (scriptFile.isEmpty()) { 0554 // qDebug() << "scriptfile is empty"; 0555 return false; 0556 } 0557 0558 QFile file(scriptFile); 0559 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { 0560 qCWarning(PLASMASHELL) << "Unable to load script file:" << path; 0561 return false; 0562 } 0563 0564 QString script = file.readAll(); 0565 if (script.isEmpty()) { 0566 // qDebug() << "script is empty"; 0567 return false; 0568 } 0569 0570 ScriptEngine *engine = new ScriptEngine(m_engine->corona(), this); 0571 engine->globalObject().setProperty(QStringLiteral("templateName"), pluginData.name()); 0572 engine->globalObject().setProperty(QStringLiteral("templateComment"), pluginData.description()); 0573 0574 engine->evaluateScript(script, path); 0575 0576 engine->deleteLater(); 0577 return true; 0578 } 0579 0580 bool ScriptEngine::V1::applicationExists(const QString &application) const 0581 { 0582 if (application.isEmpty()) { 0583 return false; 0584 } 0585 0586 // first, check for it in $PATH 0587 if (!QStandardPaths::findExecutable(application).isEmpty()) { 0588 return true; 0589 } 0590 0591 if (KService::serviceByStorageId(application)) { 0592 return true; 0593 } 0594 0595 if (application.contains(QLatin1Char('\''))) { 0596 // apostrophes just screw up the trader lookups below, so check for it 0597 return false; 0598 } 0599 0600 // next, consult ksycoca for an app by that name 0601 const auto servciesByName = KApplicationTrader::query([&application](const KService::Ptr &service) { 0602 return service->name().compare(application, Qt::CaseInsensitive) == 0 || service->genericName().compare(application, Qt::CaseInsensitive) == 0; 0603 }); 0604 return !servciesByName.isEmpty(); 0605 } 0606 0607 QJSValue ScriptEngine::V1::defaultApplication(const QString &application, bool storageId) const 0608 { 0609 if (application.isEmpty()) { 0610 return false; 0611 } 0612 0613 // FIXME: there are some pretty horrible hacks below, in the sense that they 0614 // assume a very 0615 // specific implementation system. there is much room for improvement here. 0616 // see 0617 // kdebase-runtime/kcontrol/componentchooser/ for all the gory details ;) 0618 if (matches(application, QLatin1String("mailer"))) { 0619 // KEMailSettings settings; 0620 0621 // in KToolInvocation, the default is kmail; but let's be friendlier :) 0622 // QString command = settings.getSetting(KEMailSettings::ClientProgram); 0623 QString command; 0624 if (command.isEmpty()) { 0625 if (KService::Ptr kontact = KService::serviceByStorageId(QStringLiteral("kontact"))) { 0626 return storageId ? kontact->storageId() : onlyExec(kontact->exec()); 0627 } else if (KService::Ptr kmail = KService::serviceByStorageId(QStringLiteral("kmail"))) { 0628 return storageId ? kmail->storageId() : onlyExec(kmail->exec()); 0629 } 0630 } 0631 0632 if (!command.isEmpty()) { 0633 if (false) { 0634 KConfigGroup confGroup(KSharedConfig::openConfig(), u"General"_s); 0635 const QString preferredTerminal = confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole")); 0636 command = preferredTerminal + QLatin1String(" -e ") + command; 0637 } 0638 0639 return command; 0640 } 0641 0642 } else if (matches(application, QLatin1String("browser"))) { 0643 auto service = DefaultService::browser(); 0644 if (service) { 0645 return onlyExec(storageId ? service->storageId() : service->exec()); 0646 } 0647 return onlyExec(DefaultService::legacyBrowserExec()); 0648 } else if (matches(application, QLatin1String("terminal"))) { 0649 KConfigGroup confGroup(KSharedConfig::openConfig(), u"General"_s); 0650 return onlyExec(confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole"))); 0651 0652 } else if (matches(application, QLatin1String("filemanager"))) { 0653 KService::Ptr service = KApplicationTrader::preferredService(QStringLiteral("inode/directory")); 0654 if (service) { 0655 return storageId ? service->storageId() : onlyExec(service->exec()); 0656 } 0657 0658 } else if (matches(application, QLatin1String("windowmanager"))) { 0659 KConfig cfg(QStringLiteral("ksmserverrc"), KConfig::NoGlobals); 0660 KConfigGroup confGroup(&cfg, u"General"_s); 0661 return onlyExec(confGroup.readEntry("windowManager", QStringLiteral("kwin"))); 0662 0663 } else if (KService::Ptr service = KApplicationTrader::preferredService(application)) { 0664 return storageId ? service->storageId() : onlyExec(service->exec()); 0665 } 0666 0667 return false; 0668 } 0669 0670 QJSValue ScriptEngine::V1::applicationPath(const QString &application) const 0671 { 0672 if (application.isEmpty()) { 0673 return false; 0674 } 0675 0676 // first, check for it in $PATH 0677 const QString path = QStandardPaths::findExecutable(application); 0678 if (!path.isEmpty()) { 0679 return path; 0680 } 0681 0682 if (KService::Ptr service = KService::serviceByStorageId(application)) { 0683 return QStandardPaths::locate(QStandardPaths::ApplicationsLocation, service->entryPath()); 0684 } 0685 0686 if (application.contains(QLatin1Char('\''))) { 0687 // apostrophes just screw up the trader lookups below, so check for it 0688 return QString(); 0689 } 0690 0691 // next, consult ksycoca for an app by that name 0692 const auto offers = KApplicationTrader::query([&application](const KService::Ptr &service) { 0693 return service->name().compare(application, Qt::CaseInsensitive) == 0 || service->genericName().compare(application, Qt::CaseInsensitive) == 0; 0694 }); 0695 0696 if (!offers.isEmpty()) { 0697 KService::Ptr offer = offers.first(); 0698 return QStandardPaths::locate(QStandardPaths::ApplicationsLocation, offer->entryPath()); 0699 } 0700 0701 return QString(); 0702 } 0703 0704 QJSValue ScriptEngine::V1::userDataPath(const QString &type, const QString &path) const 0705 { 0706 if (type.isEmpty()) { 0707 return QDir::homePath(); 0708 } 0709 0710 QStandardPaths::StandardLocation location = QStandardPaths::GenericDataLocation; 0711 if (matches(type, QLatin1String("desktop"))) { 0712 location = QStandardPaths::DesktopLocation; 0713 0714 } else if (matches(type, QLatin1String("documents"))) { 0715 location = QStandardPaths::DocumentsLocation; 0716 0717 } else if (matches(type, QLatin1String("music"))) { 0718 location = QStandardPaths::MusicLocation; 0719 0720 } else if (matches(type, QLatin1String("video"))) { 0721 location = QStandardPaths::MoviesLocation; 0722 0723 } else if (matches(type, QLatin1String("downloads"))) { 0724 location = QStandardPaths::DownloadLocation; 0725 0726 } else if (matches(type, QLatin1String("pictures"))) { 0727 location = QStandardPaths::PicturesLocation; 0728 0729 } else if (matches(type, QLatin1String("config"))) { 0730 location = QStandardPaths::GenericConfigLocation; 0731 } 0732 0733 if (!path.isEmpty()) { 0734 QString loc = QStandardPaths::writableLocation(location); 0735 loc.append(QDir::separator()); 0736 loc.append(path); 0737 return loc; 0738 } 0739 0740 const QStringList &locations = QStandardPaths::standardLocations(location); 0741 return locations.count() ? locations.first() : QString(); 0742 } 0743 0744 QJSValue ScriptEngine::V1::knownWallpaperPlugins(const QString &formFactor) const 0745 { 0746 QString constraint; 0747 if (!formFactor.isEmpty()) { 0748 constraint.append("[X-Plasma-FormFactors] ~~ '").append(formFactor).append("'"); 0749 } 0750 0751 const QList<KPluginMetaData> wallpapers = KPackage::PackageLoader::self()->listPackages(QStringLiteral("Plasma/Wallpaper"), QString()); 0752 QJSValue rv = m_engine->newArray(wallpapers.size()); 0753 for (auto wp : wallpapers) { 0754 rv.setProperty(wp.name(), m_engine->newArray(0)); 0755 } 0756 0757 return rv; 0758 } 0759 0760 QJSValue ScriptEngine::V1::configFile(const QJSValue &config, const QString &group) 0761 { 0762 ConfigGroup *file = nullptr; 0763 0764 if (!config.isUndefined()) { 0765 if (config.isString()) { 0766 file = new ConfigGroup; 0767 0768 const Plasma::Corona *corona = m_engine->corona(); 0769 const QString &fileName = config.toString(); 0770 0771 if (fileName == corona->config()->name()) { 0772 file->setConfig(corona->config()); 0773 } else { 0774 file->setFile(fileName); 0775 } 0776 0777 if (!group.isEmpty()) { 0778 file->setGroup(group); 0779 } 0780 0781 } else if (ConfigGroup *parent = qobject_cast<ConfigGroup *>(config.toQObject())) { 0782 file = new ConfigGroup(parent); 0783 0784 if (!group.isEmpty()) { 0785 file->setGroup(group); 0786 } 0787 } 0788 0789 } else { 0790 file = new ConfigGroup; 0791 } 0792 0793 QJSValue v = m_engine->newQObject(file); 0794 return v; 0795 } 0796 0797 void ScriptEngine::V1::setImmutability(const QString &immutability) 0798 { 0799 if (immutability.isEmpty()) { 0800 return; 0801 } 0802 0803 if (immutability == QLatin1String("systemImmutable")) { 0804 m_engine->corona()->setImmutability(Plasma::Types::SystemImmutable); 0805 } else if (immutability == QLatin1String("userImmutable")) { 0806 m_engine->corona()->setImmutability(Plasma::Types::UserImmutable); 0807 } else { 0808 m_engine->corona()->setImmutability(Plasma::Types::Mutable); 0809 } 0810 0811 return; 0812 } 0813 0814 QString ScriptEngine::V1::immutability() const 0815 { 0816 switch (m_engine->corona()->immutability()) { 0817 case Plasma::Types::SystemImmutable: 0818 return QLatin1String("systemImmutable"); 0819 case Plasma::Types::UserImmutable: 0820 return QLatin1String("userImmutable"); 0821 default: 0822 return QLatin1String("mutable"); 0823 } 0824 } 0825 0826 QJSValue ScriptEngine::V1::createContainment(const QString &type, const QString &defaultPlugin, const QString &plugin) 0827 { 0828 const QString actualPlugin = plugin.isEmpty() ? defaultPlugin : plugin; 0829 0830 auto result = m_engine->createContainmentWrapper(type, actualPlugin); 0831 0832 if (!result) { 0833 return m_engine->newError(i18n("Could not find a plugin for %1 named %2.", type, actualPlugin)); 0834 } 0835 0836 return m_engine->newQObject(result); 0837 } 0838 0839 } // namespace WorkspaceScripting