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 &param)
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 &param) 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 &param) 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 &param) 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 &param)
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