File indexing completed on 2024-05-12 17:10:24

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