diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt --- a/shell/CMakeLists.txt +++ b/shell/CMakeLists.txt @@ -33,6 +33,7 @@ scripting/panel.cpp scripting/rect.cpp scripting/scriptengine.cpp + scripting/scriptengine_v1.cpp scripting/widget.cpp ) diff --git a/shell/dbus/org.kde.PlasmaShell.xml b/shell/dbus/org.kde.PlasmaShell.xml --- a/shell/dbus/org.kde.PlasmaShell.xml +++ b/shell/dbus/org.kde.PlasmaShell.xml @@ -21,6 +21,12 @@ + + + + + + diff --git a/shell/scripting/containment.cpp b/shell/scripting/containment.cpp --- a/shell/scripting/containment.cpp +++ b/shell/scripting/containment.cpp @@ -20,13 +20,15 @@ #include "containment.h" #include +#include #include #include #include #include #include +#include #include "scriptengine.h" #include "widget.h" @@ -171,7 +173,7 @@ QScriptValue Containment::addWidget(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() == 0) { - return context->throwError(i18n("widgetById requires a name of a widget or a widget object")); + return context->throwError(i18n("addWidget requires a name of a widget or a widget object")); } Containment *c = qobject_cast(context->thisObject().toQObject()); @@ -182,8 +184,44 @@ QScriptValue v = context->argument(0); Plasma::Applet *applet = 0; + QRectF geometry(-1, -1, -1, -1); + if (context->argumentCount() > 4) { + //The user provided a geometry as parameter + if (context->argument(1).isNumber() && + context->argument(2).isNumber() && + context->argument(3).isNumber() && + context->argument(4).isNumber()) { + //Try to reconstruct a rectangle from the object hat has been passed + //It's expected a js object such as + //addWidget("org.kde.plasma.analogclock", {"x": 0, "y": 100, "width": 300, "height": 400}); + const QVariantMap geom = context->argument(1).toVariant().value(); + geometry = QRectF(context->argument(1).toNumber(), + context->argument(2).toNumber(), + context->argument(3).toNumber(), + context->argument(4).toNumber()); + } + } if (v.isString()) { + //A position has been supplied: search for the containment's graphics object + QQuickItem *containmentItem = nullptr; + + if (geometry.x() >= 0 && geometry.y() >= 0) { + containmentItem = c->d->containment.data()->property("_plasma_graphicObject").value(); + Plasma::Applet *applet = nullptr; + if (containmentItem) { + QMetaObject::invokeMethod(containmentItem , "createApplet", Qt::DirectConnection, Q_RETURN_ARG(Plasma::Applet *, applet), Q_ARG(QString, v.toString()), Q_ARG(QVariantList, QVariantList()), Q_ARG(QRectF, geometry)); + } + if (applet) { + ScriptEngine *env = ScriptEngine::envFor(engine); + return env->wrap(applet); + } + } + + //Case in which either: + // * a geometry wasn't provided + // * containmentItem wasn't found applet = c->d->containment.data()->createApplet(v.toString()); + if (applet) { ScriptEngine *env = ScriptEngine::envFor(engine); return env->wrap(applet); diff --git a/shell/scripting/scriptengine.h b/shell/scripting/scriptengine.h --- a/shell/scripting/scriptengine.h +++ b/shell/scripting/scriptengine.h @@ -71,34 +71,17 @@ void setupEngine(); static QString onlyExec(const QString &commandLine); - static QStringList availableActivities(QScriptContext *context, QScriptEngine *engine); - - static QScriptValue createActivity(QScriptContext *context, QScriptEngine *engine); - static QScriptValue setCurrentActivity(QScriptContext *context, QScriptEngine *engine); - static QScriptValue currentActivity(QScriptContext *controller, QScriptEngine *engine); - static QScriptValue activities(QScriptContext *context, QScriptEngine *engine); - static QScriptValue setActivityName(QScriptContext *context, QScriptEngine *engine); - static QScriptValue activityName(QScriptContext *context, QScriptEngine *engine); - static QScriptValue newPanel(QScriptContext *context, QScriptEngine *engine); - static QScriptValue desktopsForActivity(QScriptContext *context, QScriptEngine *engine); - static QScriptValue desktops(QScriptContext *context, QScriptEngine *engine); - static QScriptValue desktopById(QScriptContext *context, QScriptEngine *engine); - static QScriptValue desktopForScreen(QScriptContext *context, QScriptEngine *engine); - static QScriptValue panelById(QScriptContext *context, QScriptEngine *engine); - static QScriptValue panels(QScriptContext *context, QScriptEngine *engine); - static QScriptValue fileExists(QScriptContext *context, QScriptEngine *engine); - static QScriptValue loadTemplate(QScriptContext *context, QScriptEngine *engine); - static QScriptValue applicationExists(QScriptContext *context, QScriptEngine *engine); - static QScriptValue defaultApplication(QScriptContext *context, QScriptEngine *engine); - static QScriptValue applicationPath(QScriptContext *context, QScriptEngine *engine); - static QScriptValue userDataPath(QScriptContext *context, QScriptEngine *engine); - static QScriptValue knownWallpaperPlugins(QScriptContext *context, QScriptEngine *engine); - static QScriptValue configFile(QScriptContext *context, QScriptEngine *engine); - static QScriptValue gridUnit(); + static QScriptValue createAPIForVersion(QScriptContext *context, QScriptEngine *engine); + + // Script API versions + class V1; // helpers - static QScriptValue createContainment(const QString &type, const QString &defautPlugin, - QScriptContext *context, QScriptEngine *engine); + QStringList availableActivities() const; + QList desktopsForActivity(const QString &id); + Containment *createContainment(const QString &type, const QString &plugin); + + static int gridUnit(); private Q_SLOTS: void exception(const QScriptValue &value); diff --git a/shell/scripting/scriptengine.cpp b/shell/scripting/scriptengine.cpp --- a/shell/scripting/scriptengine.cpp +++ b/shell/scripting/scriptengine.cpp @@ -18,6 +18,7 @@ */ #include "scriptengine.h" +#include "scriptengine_v1.h" #include #include @@ -55,16 +56,6 @@ QScriptValue constructQRectFClass(QScriptEngine *engine); -namespace { - template - inline void awaitFuture(const QFuture &future) - { - while (!future.isFinished()) { - QCoreApplication::processEvents(); - } - } -} - namespace WorkspaceScripting { @@ -87,277 +78,6 @@ { } -QScriptValue ScriptEngine::desktopById(QScriptContext *context, QScriptEngine *engine) -{ - if (context->argumentCount() == 0) { - return context->throwError(i18n("activityById requires an id")); - } - - const uint id = context->argument(0).toInt32(); - ScriptEngine *env = envFor(engine); - foreach (Plasma::Containment *c, env->m_corona->containments()) { - if (c->id() == id && !isPanel(c)) { - return env->wrap(c); - } - } - - return engine->undefinedValue(); -} - -QStringList ScriptEngine::availableActivities(QScriptContext *context, QScriptEngine *engine) -{ - Q_UNUSED(engine) - - ScriptEngine *env = envFor(engine); - - ShellCorona *sc = qobject_cast(env->m_corona); - StandaloneAppCorona *ac = qobject_cast(env->m_corona); - if (sc) { - return sc->availableActivities(); - } else if (ac) { - return ac->availableActivities(); - } - - return QStringList(); -} - -QScriptValue ScriptEngine::desktopsForActivity(QScriptContext *context, QScriptEngine *engine) -{ - if (context->argumentCount() == 0) { - return context->throwError(i18n("desktopsForActivity requires an id")); - } - - QScriptValue containments = engine->newArray(); - int count = 0; - - const QString id = context->argument(0).toString(); - - // confirm this activity actually exists - bool found = false; - for (const QString &act: availableActivities(context, engine)) { - if (act == id) { - found = true; - break; - } - } - - if (!found) { - containments.setProperty(QStringLiteral("length"), 0); - return containments; - } - - ScriptEngine *env = envFor(engine); - foreach (Plasma::Containment *c, env->m_corona->containments()) { - if (c->activity() == id && !isPanel(c)) { - containments.setProperty(count, env->wrap(c)); - ++count; - } - } - - if (count == 0) { - // we have no desktops for this activity, so lets make them now - // this can happen when the activity already exists but has never been activated - // with the current shell package and layout.js is run to set up the shell for the - // first time - ShellCorona *sc = qobject_cast(env->m_corona); - StandaloneAppCorona *ac = qobject_cast(env->m_corona); - if (sc) { - foreach (int i, sc->screenIds()) { - Plasma::Containment *c = sc->createContainmentForActivity(id, i); - containments.setProperty(count, env->wrap(c)); - ++count; - } - } else if (ac) { - const int numScreens = env->m_corona->numScreens(); - for (int i = 0; i < numScreens; ++i) { - Plasma::Containment *c = ac->createContainmentForActivity(id, i); - containments.setProperty(count, env->wrap(c)); - ++count; - } - } - } - - containments.setProperty(QStringLiteral("length"), count); - return containments; -} - -QScriptValue ScriptEngine::desktopForScreen(QScriptContext *context, QScriptEngine *engine) -{ - if (context->argumentCount() == 0) { - return context->throwError(i18n("activityForScreen requires a screen id")); - } - - const uint screen = context->argument(0).toInt32(); - ScriptEngine *env = envFor(engine); - return env->wrap(env->m_corona->containmentForScreen(screen)); -} - -QScriptValue ScriptEngine::createActivity(QScriptContext *context, QScriptEngine *engine) -{ - if (context->argumentCount() < 0) { - return context->throwError(i18n("createActivity required the activity name")); - } - - const QString name = context->argument(0).toString(); - QString plugin = context->argument(1).toString(); - - ScriptEngine *env = envFor(engine); - - KActivities::Controller controller; - - // This is not the nicest way to do this, but createActivity - // is a synchronous API :/ - QFuture futureId = controller.addActivity(name); - awaitFuture(futureId); - - QString id = futureId.result(); - - qDebug() << "Setting default Containment plugin:" << plugin; - - ShellCorona *sc = qobject_cast(env->m_corona); - StandaloneAppCorona *ac = qobject_cast(env->m_corona); - if (sc) { - if (plugin.isEmpty() || plugin == QLatin1String("undefined")) { - plugin = sc->defaultContainmentPlugin(); - } - sc->insertActivity(id, plugin); - } else if (ac) { - if (plugin.isEmpty() || plugin == QLatin1String("undefined")) { - KConfigGroup shellCfg = KConfigGroup(KSharedConfig::openConfig(env->m_corona->package().filePath("defaults")), "Desktop"); - plugin = shellCfg.readEntry("Containment", "org.kde.desktopcontainment"); - } - ac->insertActivity(id, plugin); - } - - return QScriptValue(id); -} - -QScriptValue ScriptEngine::setCurrentActivity(QScriptContext *context, QScriptEngine *engine) -{ - Q_UNUSED(engine) - - if (context->argumentCount() < 0) { - return context->throwError(i18n("setCurrentActivity required the activity id")); - } - - const QString id = context->argument(0).toString(); - - KActivities::Controller controller; - - QFuture task = controller.setCurrentActivity(id); - awaitFuture(task); - - return QScriptValue(task.result()); -} - -QScriptValue ScriptEngine::setActivityName(QScriptContext *context, QScriptEngine *engine) -{ - Q_UNUSED(engine) - - if (context->argumentCount() < 2) { - return context->throwError(i18n("setActivityName required the activity id and name")); - } - - const QString id = context->argument(0).toString(); - const QString name = context->argument(1).toString(); - - KActivities::Controller controller; - - QFuture task = controller.setActivityName(id, name); - awaitFuture(task); - - return QScriptValue(); -} - -QScriptValue ScriptEngine::activityName(QScriptContext *context, QScriptEngine *engine) -{ - Q_UNUSED(engine) - - if (context->argumentCount() < 1) { - return context->throwError(i18n("setActivityName required the activity id and name")); - } - - const QString id = context->argument(0).toString(); - - KActivities::Info info(id); - - return QScriptValue(info.name()); -} - -QScriptValue ScriptEngine::currentActivity(QScriptContext *context, QScriptEngine *engine) -{ - Q_UNUSED(engine) - Q_UNUSED(context) - - KActivities::Consumer consumer; - return consumer.currentActivity(); -} - -QScriptValue ScriptEngine::activities(QScriptContext *context, QScriptEngine *engine) -{ - Q_UNUSED(context) - - return qScriptValueFromSequence(engine, availableActivities(context, engine)); -} - -QScriptValue ScriptEngine::newPanel(QScriptContext *context, QScriptEngine *engine) -{ - QString plugin(QStringLiteral("org.kde.panel")); - - if (context->argumentCount() > 0) { - plugin = context->argument(0).toString(); - } - - return createContainment(QStringLiteral("Panel"), plugin, context, engine); -} - -QScriptValue ScriptEngine::createContainment(const QString &type, const QString &defaultPlugin, - QScriptContext *context, QScriptEngine *engine) -{ - QString plugin = context->argumentCount() > 0 ? context->argument(0).toString() : - defaultPlugin; - - bool exists = false; - const KPluginInfo::List list = Plasma::PluginLoader::listContainmentsOfType(type); - foreach (const KPluginInfo &info, list) { - if (info.pluginName() == plugin) { - exists = true; - break; - } - } - - if (!exists) { - return context->throwError(i18n("Could not find a plugin for %1 named %2.", type, plugin)); - } - - - ScriptEngine *env = envFor(engine); - Plasma::Containment *c = 0; - if (type == QLatin1String("Panel")) { - ShellCorona *sc = qobject_cast(env->m_corona); - StandaloneAppCorona *ac = qobject_cast(env->m_corona); - if (sc) { - c = sc->addPanel(plugin); - } else if (ac) { - c = ac->addPanel(plugin); - } - } else { - c = env->m_corona->createContainment(plugin); - } - - if (c) { - if (type == QLatin1String("Panel")) { - // some defaults - c->setFormFactor(Plasma::Types::Horizontal); - c->setLocation(Plasma::Types::TopEdge); - } - c->updateConstraints(Plasma::Types::AllConstraints | Plasma::Types::StartupCompletedConstraint); - c->flushPendingConstraintsEvents(); - } - - return env->wrap(c); -} - QScriptValue ScriptEngine::wrap(Plasma::Applet *w) { Widget *wrapper = new Widget(w); @@ -404,178 +124,6 @@ return env; } -QScriptValue ScriptEngine::panelById(QScriptContext *context, QScriptEngine *engine) -{ - if (context->argumentCount() == 0) { - return context->throwError(i18n("panelById requires an id")); - } - - const uint id = context->argument(0).toInt32(); - ScriptEngine *env = envFor(engine); - foreach (Plasma::Containment *c, env->m_corona->containments()) { - if (c->id() == id && isPanel(c)) { - return env->wrap(c); - } - } - - return engine->undefinedValue(); -} - -QScriptValue ScriptEngine::panels(QScriptContext *context, QScriptEngine *engine) -{ - Q_UNUSED(context) - - QScriptValue panels = engine->newArray(); - ScriptEngine *env = envFor(engine); - int count = 0; - - foreach (Plasma::Containment *c, env->m_corona->containments()) { - if (isPanel(c)) { - panels.setProperty(count, env->wrap(c)); - ++count; - } - } - - panels.setProperty(QStringLiteral("length"), count); - return panels; -} - -QScriptValue ScriptEngine::fileExists(QScriptContext *context, QScriptEngine *engine) -{ - Q_UNUSED(engine) - if (context->argumentCount() == 0) { - return false; - } - - const QString path = context->argument(0).toString(); - if (path.isEmpty()) { - return false; - } - - QFile f(KShell::tildeExpand(path)); - return f.exists(); -} - -QScriptValue ScriptEngine::loadTemplate(QScriptContext *context, QScriptEngine *engine) -{ - Q_UNUSED(engine) - if (context->argumentCount() == 0) { - // qDebug() << "no arguments"; - return false; - } - - const QString layout = context->argument(0).toString(); - if (layout.isEmpty() || layout.contains(QStringLiteral("'"))) { - // qDebug() << "layout is empty"; - return false; - } - - const QString constraint = QStringLiteral("[X-KDE-PluginInfo-Name] == '%2'").arg(layout); - KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("Plasma/LayoutTemplate"), constraint); - - if (offers.isEmpty()) { - // qDebug() << "offers fail" << constraint; - return false; - } - - KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LayoutTemplate")); - KPluginInfo info(offers.first()); - - QString path; - { - ScriptEngine *env = envFor(engine); - ShellCorona *sc = qobject_cast(env->m_corona); - if (sc) { - const QString overridePackagePath = sc->lookAndFeelPackage().path() + QStringLiteral("contents/layouts/") + info.pluginName(); - - path = overridePackagePath + QStringLiteral("/metadata.desktop"); - if (QFile::exists(path)) { - package.setPath(overridePackagePath); - } - } - } - - if (!package.isValid()) { - path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, package.defaultPackageRoot() + info.pluginName() + "/metadata.desktop"); - if (path.isEmpty()) { - // qDebug() << "script path is empty"; - return false; - } - - package.setPath(info.pluginName()); - } - - const QString scriptFile = package.filePath("mainscript"); - if (scriptFile.isEmpty()) { - // qDebug() << "scriptfile is empty"; - return false; - } - - QFile file(scriptFile); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - qWarning() << i18n("Unable to load script file: %1", path); - return false; - } - - QString script = file.readAll(); - if (script.isEmpty()) { - // qDebug() << "script is empty"; - return false; - } - - ScriptEngine *env = envFor(engine); - env->globalObject().setProperty(QStringLiteral("templateName"), env->newVariant(info.name()), QScriptValue::ReadOnly | QScriptValue::Undeletable); - env->globalObject().setProperty(QStringLiteral("templateComment"), env->newVariant(info.comment()), QScriptValue::ReadOnly | QScriptValue::Undeletable); - - QScriptValue rv = env->newObject(); - QScriptContext *ctx = env->pushContext(); - ctx->setThisObject(rv); - - env->evaluateScript(script, path); - - env->popContext(); - return rv; -} - -QScriptValue ScriptEngine::applicationExists(QScriptContext *context, QScriptEngine *engine) -{ - Q_UNUSED(engine) - if (context->argumentCount() == 0) { - return false; - } - - const QString application = context->argument(0).toString(); - if (application.isEmpty()) { - return false; - } - - // first, check for it in $PATH - if (!QStandardPaths::findExecutable(application).isEmpty()) { - return true; - } - - if (KService::serviceByStorageId(application)) { - return true; - } - - if (application.contains(QStringLiteral("'"))) { - // apostrophes just screw up the trader lookups below, so check for it - return false; - } - - // next, consult ksycoca for an app by that name - if (!KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("Name =~ '%1'").arg(application)).isEmpty()) { - return true; - } - - // next, consult ksycoca for an app by that generic name - if (!KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("GenericName =~ '%1'").arg(application)).isEmpty()) { - return true; - } - - return false; -} - QString ScriptEngine::onlyExec(const QString &commandLine) { if (commandLine.isEmpty()) { @@ -585,246 +133,6 @@ return KShell::splitArgs(commandLine, KShell::TildeExpand).first(); } -QScriptValue ScriptEngine::defaultApplication(QScriptContext *context, QScriptEngine *engine) -{ - Q_UNUSED(engine) - if (context->argumentCount() == 0) { - return false; - } - - const QString application = context->argument(0).toString(); - if (application.isEmpty()) { - return false; - } - - const bool storageId = context->argumentCount() < 2 ? false : context->argument(1).toBool(); - - // FIXME: there are some pretty horrible hacks below, in the sense that they assume a very - // specific implementation system. there is much room for improvement here. see - // kdebase-runtime/kcontrol/componentchooser/ for all the gory details ;) - if (application.compare(QLatin1String("mailer"), Qt::CaseInsensitive) == 0) { - // KEMailSettings settings; - - // in KToolInvocation, the default is kmail; but let's be friendlier :) -// QString command = settings.getSetting(KEMailSettings::ClientProgram); - QString command; - if (command.isEmpty()) { - if (KService::Ptr kontact = KService::serviceByStorageId(QStringLiteral("kontact"))) { - return storageId ? kontact->storageId() : onlyExec(kontact->exec()); - } else if (KService::Ptr kmail = KService::serviceByStorageId(QStringLiteral("kmail"))) { - return storageId ? kmail->storageId() : onlyExec(kmail->exec()); - } - } - - if (!command.isEmpty()) { - //if (settings.getSetting(KEMailSettings::ClientTerminal) == "true") { - if (false) { - KConfigGroup confGroup(KSharedConfig::openConfig(), "General"); - const QString preferredTerminal = confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole")); - command = preferredTerminal + QLatin1String(" -e ") + command; - } - - return command; - } - } else if (application.compare(QLatin1String("browser"), Qt::CaseInsensitive) == 0) { - KConfigGroup config(KSharedConfig::openConfig(), "General"); - QString browserApp = config.readPathEntry("BrowserApplication", QString()); - if (browserApp.isEmpty()) { - const KService::Ptr htmlApp = KMimeTypeTrader::self()->preferredService(QStringLiteral("text/html")); - if (htmlApp) { - browserApp = storageId ? htmlApp->storageId() : htmlApp->exec(); - } - } else if (browserApp.startsWith('!')) { - browserApp = browserApp.mid(1); - } - - return onlyExec(browserApp); - } else if (application.compare(QLatin1String("terminal"), Qt::CaseInsensitive) == 0) { - KConfigGroup confGroup(KSharedConfig::openConfig(), "General"); - return onlyExec(confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole"))); - } else if (application.compare(QLatin1String("filemanager"), Qt::CaseInsensitive) == 0) { - KService::Ptr service = KMimeTypeTrader::self()->preferredService(QStringLiteral("inode/directory")); - if (service) { - return storageId ? service->storageId() : onlyExec(service->exec()); - } - } else if (application.compare(QLatin1String("windowmanager"), Qt::CaseInsensitive) == 0) { - KConfig cfg(QStringLiteral("ksmserverrc"), KConfig::NoGlobals); - KConfigGroup confGroup(&cfg, "General"); - return onlyExec(confGroup.readEntry("windowManager", QStringLiteral("kwin"))); - } else if (KService::Ptr service = KMimeTypeTrader::self()->preferredService(application)) { - return storageId ? service->storageId() : onlyExec(service->exec()); - } else { - // try the files in share/apps/kcm_componentchooser/ - const QStringList services = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kcm_componentchooser/")); - qDebug() << "ok, trying in" << services; - foreach (const QString &service, services) { - if (!service.endsWith(QLatin1String(".desktop"))) { - continue; - } - KConfig config(service, KConfig::SimpleConfig); - KConfigGroup cg = config.group(QByteArray()); - const QString type = cg.readEntry("valueName", QString()); - //qDebug() << " checking" << service << type << application; - if (type.compare(application, Qt::CaseInsensitive) == 0) { - KConfig store(cg.readPathEntry("storeInFile", QStringLiteral("null"))); - KConfigGroup storeCg(&store, cg.readEntry("valueSection", QString())); - const QString exec = storeCg.readPathEntry(cg.readEntry("valueName", "kcm_componenchooser_null"), - cg.readEntry("defaultImplementation", QString())); - if (!exec.isEmpty()) { - return exec; - } - - break; - } - } - } - - return false; -} - -QScriptValue ScriptEngine::applicationPath(QScriptContext *context, QScriptEngine *engine) -{ - Q_UNUSED(engine) - if (context->argumentCount() == 0) { - return false; - } - - const QString application = context->argument(0).toString(); - if (application.isEmpty()) { - return false; - } - - // first, check for it in $PATH - const QString path = QStandardPaths::findExecutable(application); - if (!path.isEmpty()) { - return path; - } - - if (KService::Ptr service = KService::serviceByStorageId(application)) { - return QStandardPaths::locate(QStandardPaths::ApplicationsLocation, service->entryPath()); - } - - if (application.contains(QStringLiteral("'"))) { - // apostrophes just screw up the trader lookups below, so check for it - return QString(); - } - - // next, consult ksycoca for an app by that name - KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("Name =~ '%1'").arg(application)); - if (offers.isEmpty()) { - // next, consult ksycoca for an app by that generic name - offers = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("GenericName =~ '%1'").arg(application)); - } - - if (!offers.isEmpty()) { - KService::Ptr offer = offers.first(); - return QStandardPaths::locate(QStandardPaths::ApplicationsLocation, offer->entryPath()); - } - - return QString(); -} - -QScriptValue ScriptEngine::userDataPath(QScriptContext *context, QScriptEngine *engine) -{ - Q_UNUSED(engine) - if (context->argumentCount() == 0) { - return QDir::homePath(); - } - - const QString type = context->argument(0).toString(); - if (type.isEmpty()) { - return QDir::homePath(); - } - - QStandardPaths::StandardLocation location = QStandardPaths::GenericDataLocation; - if (type.compare(QLatin1String("desktop"), Qt::CaseInsensitive) == 0) { - location = QStandardPaths::DesktopLocation; - } else if (type.compare(QLatin1String("documents"), Qt::CaseInsensitive) == 0) { - location = QStandardPaths::DocumentsLocation; - } else if (type.compare(QLatin1String("music"), Qt::CaseInsensitive) == 0) { - location = QStandardPaths::MusicLocation; - } else if (type.compare(QLatin1String("video"), Qt::CaseInsensitive) == 0) { - location = QStandardPaths::MoviesLocation; - } else if (type.compare(QLatin1String("downloads"), Qt::CaseInsensitive) == 0) { - location = QStandardPaths::DownloadLocation; - } else if (type.compare(QLatin1String("pictures"), Qt::CaseInsensitive) == 0) { - location = QStandardPaths::PicturesLocation; - } else if (type.compare(QLatin1String("config"), Qt::CaseInsensitive) == 0) { - location = QStandardPaths::GenericConfigLocation; - } - if (context->argumentCount() > 1) { - QString loc = QStandardPaths::writableLocation(location); - loc.append(QDir::separator()); - loc.append(context->argument(1).toString()); - return loc; - } - const QStringList &locations = QStandardPaths::standardLocations(location); - return locations.count() ? locations.first() : QString(); -} - -QScriptValue ScriptEngine::knownWallpaperPlugins(QScriptContext *context, QScriptEngine *engine) -{ - Q_UNUSED(engine) - - QString formFactor; - if (context->argumentCount() > 0) { - formFactor = context->argument(0).toString(); - } - - QString constraint; - if (!formFactor.isEmpty()) { - constraint.append("[X-Plasma-FormFactors] ~~ '").append(formFactor).append("'"); - } - - QList wallpapers = KPackage::PackageLoader::self()->listPackages(QStringLiteral("Plasma/Wallpaper"), QString()); - QScriptValue rv = engine->newArray(wallpapers.size()); - for (auto wp : wallpapers) { - rv.setProperty(wp.name(), engine->newArray(0)); - } - - return rv; -} - -QScriptValue ScriptEngine::configFile(QScriptContext *context, QScriptEngine *engine) -{ - ConfigGroup *file = 0; - - if (context->argumentCount() > 0) { - if (context->argument(0).isString()) { - file = new ConfigGroup; - - const QString &fileName = context->argument(0).toString(); - const ScriptEngine *env = envFor(engine); - const Plasma::Corona* corona = env->corona(); - - if (fileName == corona->config()->name()) { - file->setConfig(corona->config()); - } else { - file->setFile(fileName); - } - - if (context->argumentCount() > 1) { - file->setGroup(context->argument(1).toString()); - } - } else if (ConfigGroup *parent= qobject_cast(context->argument(0).toQObject())) { - file = new ConfigGroup(parent); - - if (context->argumentCount() > 1) { - file->setGroup(context->argument(1).toString()); - } - } - } else { - file = new ConfigGroup; - } - - QScriptValue v = engine->newQObject(file, - QScriptEngine::ScriptOwnership, - QScriptEngine::ExcludeSuperClassProperties | - QScriptEngine::ExcludeSuperClassMethods); - return v; - -} - void ScriptEngine::setupEngine() { QScriptValue v = globalObject(); @@ -837,72 +145,59 @@ } } + m_scriptSelf.setProperty(QStringLiteral("getApiVersion"), newFunction(ScriptEngine::createAPIForVersion)); + m_scriptSelf.setProperty(QStringLiteral("QRectF"), constructQRectFClass(this)); - m_scriptSelf.setProperty(QStringLiteral("createActivity"), newFunction(ScriptEngine::createActivity)); - m_scriptSelf.setProperty(QStringLiteral("setCurrentActivity"), newFunction(ScriptEngine::setCurrentActivity)); - m_scriptSelf.setProperty(QStringLiteral("currentActivity"), newFunction(ScriptEngine::currentActivity)); - m_scriptSelf.setProperty(QStringLiteral("activities"), newFunction(ScriptEngine::activities)); - m_scriptSelf.setProperty(QStringLiteral("setActivityName"), newFunction(ScriptEngine::setActivityName)); - m_scriptSelf.setProperty(QStringLiteral("activityName"), newFunction(ScriptEngine::activityName)); - m_scriptSelf.setProperty(QStringLiteral("setActivityName"), newFunction(ScriptEngine::setActivityName)); - m_scriptSelf.setProperty(QStringLiteral("Panel"), newFunction(ScriptEngine::newPanel, newObject())); - m_scriptSelf.setProperty(QStringLiteral("desktopsForActivity"), newFunction(ScriptEngine::desktopsForActivity)); - m_scriptSelf.setProperty(QStringLiteral("desktops"), newFunction(ScriptEngine::desktops)); - m_scriptSelf.setProperty(QStringLiteral("desktopById"), newFunction(ScriptEngine::desktopById)); - m_scriptSelf.setProperty(QStringLiteral("desktopForScreen"), newFunction(ScriptEngine::desktopForScreen)); - m_scriptSelf.setProperty(QStringLiteral("panelById"), newFunction(ScriptEngine::panelById)); - m_scriptSelf.setProperty(QStringLiteral("panels"), newFunction(ScriptEngine::panels)); - m_scriptSelf.setProperty(QStringLiteral("fileExists"), newFunction(ScriptEngine::fileExists)); - m_scriptSelf.setProperty(QStringLiteral("loadTemplate"), newFunction(ScriptEngine::loadTemplate)); - m_scriptSelf.setProperty(QStringLiteral("applicationExists"), newFunction(ScriptEngine::applicationExists)); - m_scriptSelf.setProperty(QStringLiteral("defaultApplication"), newFunction(ScriptEngine::defaultApplication)); - m_scriptSelf.setProperty(QStringLiteral("userDataPath"), newFunction(ScriptEngine::userDataPath)); - m_scriptSelf.setProperty(QStringLiteral("applicationPath"), newFunction(ScriptEngine::applicationPath)); - m_scriptSelf.setProperty(QStringLiteral("knownWallpaperPlugins"), newFunction(ScriptEngine::knownWallpaperPlugins)); - m_scriptSelf.setProperty(QStringLiteral("ConfigFile"), newFunction(ScriptEngine::configFile)); - m_scriptSelf.setProperty(QStringLiteral("gridUnit"), ScriptEngine::gridUnit()); + m_scriptSelf.setProperty(QStringLiteral("createActivity"), newFunction(ScriptEngine::V1::createActivity)); + m_scriptSelf.setProperty(QStringLiteral("setCurrentActivity"), newFunction(ScriptEngine::V1::setCurrentActivity)); + m_scriptSelf.setProperty(QStringLiteral("currentActivity"), newFunction(ScriptEngine::V1::currentActivity)); + m_scriptSelf.setProperty(QStringLiteral("activities"), newFunction(ScriptEngine::V1::activities)); + m_scriptSelf.setProperty(QStringLiteral("activityName"), newFunction(ScriptEngine::V1::activityName)); + m_scriptSelf.setProperty(QStringLiteral("setActivityName"), newFunction(ScriptEngine::V1::setActivityName)); + m_scriptSelf.setProperty(QStringLiteral("loadSerializedLayout"), newFunction(ScriptEngine::V1::loadSerializedLayout)); + m_scriptSelf.setProperty(QStringLiteral("Panel"), newFunction(ScriptEngine::V1::newPanel, newObject())); + m_scriptSelf.setProperty(QStringLiteral("desktopsForActivity"), newFunction(ScriptEngine::V1::desktopsForActivity)); + m_scriptSelf.setProperty(QStringLiteral("desktops"), newFunction(ScriptEngine::V1::desktops)); + m_scriptSelf.setProperty(QStringLiteral("desktopById"), newFunction(ScriptEngine::V1::desktopById)); + m_scriptSelf.setProperty(QStringLiteral("desktopForScreen"), newFunction(ScriptEngine::V1::desktopForScreen)); + m_scriptSelf.setProperty(QStringLiteral("panelById"), newFunction(ScriptEngine::V1::panelById)); + m_scriptSelf.setProperty(QStringLiteral("panels"), newFunction(ScriptEngine::V1::panels)); + m_scriptSelf.setProperty(QStringLiteral("fileExists"), newFunction(ScriptEngine::V1::fileExists)); + m_scriptSelf.setProperty(QStringLiteral("loadTemplate"), newFunction(ScriptEngine::V1::loadTemplate)); + m_scriptSelf.setProperty(QStringLiteral("applicationExists"), newFunction(ScriptEngine::V1::applicationExists)); + m_scriptSelf.setProperty(QStringLiteral("defaultApplication"), newFunction(ScriptEngine::V1::defaultApplication)); + m_scriptSelf.setProperty(QStringLiteral("userDataPath"), newFunction(ScriptEngine::V1::userDataPath)); + m_scriptSelf.setProperty(QStringLiteral("applicationPath"), newFunction(ScriptEngine::V1::applicationPath)); + m_scriptSelf.setProperty(QStringLiteral("knownWallpaperPlugins"), newFunction(ScriptEngine::V1::knownWallpaperPlugins)); + m_scriptSelf.setProperty(QStringLiteral("ConfigFile"), newFunction(ScriptEngine::V1::configFile)); + m_scriptSelf.setProperty(QStringLiteral("gridUnit"), ScriptEngine::V1::gridUnit()); setGlobalObject(m_scriptSelf); } -bool ScriptEngine::isPanel(const Plasma::Containment *c) +QScriptValue ScriptEngine::createAPIForVersion(QScriptContext *context, QScriptEngine *engine) { - if (!c) { - return false; + if (context->argumentCount() < 1) { + return context->throwError(i18n("getApiVersion() needs you to specify the version")); } - return c->containmentType() == Plasma::Types::PanelContainment || - c->containmentType() == Plasma::Types::CustomPanelContainment; -} - -QScriptValue ScriptEngine::desktops(QScriptContext *context, QScriptEngine *engine) -{ - Q_UNUSED(context) + const uint version = context->argument(0).toInt32(); - QScriptValue containments = engine->newArray(); - ScriptEngine *env = envFor(engine); - int count = 0; - - foreach (Plasma::Containment *c, env->corona()->containments()) { - //make really sure we get actual desktops, so check for a non empty activty id - if (!isPanel(c) && !c->activity().isEmpty()) { - containments.setProperty(count, env->wrap(c)); - ++count; - } + if (version != 1) { + return context->throwError(i18n("getApiVersion() invalid version number")); } - containments.setProperty(QStringLiteral("length"), count); - return containments; + return envFor(engine)->m_scriptSelf; } -QScriptValue ScriptEngine::gridUnit() +bool ScriptEngine::isPanel(const Plasma::Containment *c) { - int gridUnit = QFontMetrics(QGuiApplication::font()).boundingRect(QStringLiteral("M")).height(); - if (gridUnit % 2 != 0) { - gridUnit++; + if (!c) { + return false; } - return gridUnit; + return c->containmentType() == Plasma::Types::PanelContainment || + c->containmentType() == Plasma::Types::CustomPanelContainment; } Plasma::Corona *ScriptEngine::corona() const @@ -980,7 +275,115 @@ return scriptPaths; } +QStringList ScriptEngine::availableActivities() const +{ + ShellCorona *sc = qobject_cast(m_corona); + StandaloneAppCorona *ac = qobject_cast(m_corona); + if (sc) { + return sc->availableActivities(); + } else if (ac) { + return ac->availableActivities(); + } + + return QStringList(); +} + +QList ScriptEngine::desktopsForActivity(const QString &id) +{ + QList result; + + // confirm this activity actually exists + bool found = false; + for (const QString &act: availableActivities()) { + if (act == id) { + found = true; + break; + } + } + + if (!found) { + return result; + } + + foreach (Plasma::Containment *c, m_corona->containments()) { + if (c->activity() == id && !isPanel(c)) { + result << new Containment(c); + } + } + + if (result.count() == 0) { + // we have no desktops for this activity, so lets make them now + // this can happen when the activity already exists but has never been activated + // with the current shell package and layout.js is run to set up the shell for the + // first time + ShellCorona *sc = qobject_cast(m_corona); + StandaloneAppCorona *ac = qobject_cast(m_corona); + if (sc) { + foreach (int i, sc->screenIds()) { + result << new Containment(sc->createContainmentForActivity(id, i)); + } + } else if (ac) { + const int numScreens = m_corona->numScreens(); + for (int i = 0; i < numScreens; ++i) { + result << new Containment(ac->createContainmentForActivity(id, i)); + } + } + } + + return result; +} + +int ScriptEngine::gridUnit() +{ + int gridUnit = QFontMetrics(QGuiApplication::font()).boundingRect(QStringLiteral("M")).height(); + if (gridUnit % 2 != 0) { + gridUnit++; + } + + return gridUnit; +} + +Containment *ScriptEngine::createContainment(const QString &type, const QString &plugin) +{ + bool exists = false; + const KPluginInfo::List list = Plasma::PluginLoader::listContainmentsOfType(type); + foreach (const KPluginInfo &info, list) { + if (info.pluginName() == plugin) { + exists = true; + break; + } + } + + if (!exists) { + return nullptr; + } + + Plasma::Containment *c = 0; + if (type == QLatin1String("Panel")) { + ShellCorona *sc = qobject_cast(m_corona); + StandaloneAppCorona *ac = qobject_cast(m_corona); + if (sc) { + c = sc->addPanel(plugin); + } else if (ac) { + c = ac->addPanel(plugin); + } + } else { + c = m_corona->createContainment(plugin); + } + + if (c) { + if (type == QLatin1String("Panel")) { + // some defaults + c->setFormFactor(Plasma::Types::Horizontal); + c->setLocation(Plasma::Types::TopEdge); + } + c->updateConstraints(Plasma::Types::AllConstraints | Plasma::Types::StartupCompletedConstraint); + c->flushPendingConstraintsEvents(); + } + + return isPanel(c) ? new Panel(c) : new Containment(c); } +} // namespace WorkspaceScripting diff --git a/shell/scripting/scriptengine.h b/shell/scripting/scriptengine_v1.h copy from shell/scripting/scriptengine.h copy to shell/scripting/scriptengine_v1.h --- a/shell/scripting/scriptengine.h +++ b/shell/scripting/scriptengine_v1.h @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef SCRIPTENGINE -#define SCRIPTENGINE +#ifndef SCRIPTENGINE_V1 +#define SCRIPTENGINE_V1 #include #include @@ -28,57 +28,20 @@ #include #include "../shellcorona.h" - -namespace Plasma -{ - class Applet; - class Containment; -} // namespace Plasma - +#include "scriptengine.h" namespace WorkspaceScripting { -class Containment; - -class ScriptEngine : public QScriptEngine -{ - Q_OBJECT - +class ScriptEngine::V1 { public: - ScriptEngine(Plasma::Corona *corona, QObject *parent = 0); - ~ScriptEngine() override; - - static QStringList pendingUpdateScripts(Plasma::Corona *corona); - - Plasma::Corona *corona() const; - QScriptValue wrap(Plasma::Applet *w); - virtual QScriptValue wrap(Plasma::Containment *c); - QScriptValue wrap(Containment *c); - virtual int defaultPanelScreen() const; - - static bool isPanel(const Plasma::Containment *c); - static ScriptEngine *envFor(QScriptEngine *engine); - -public Q_SLOTS: - bool evaluateScript(const QString &script, const QString &path = QString()); - -Q_SIGNALS: - void print(const QString &string); - void printError(const QString &string); - -private: - void setupEngine(); - static QString onlyExec(const QString &commandLine); - - static QStringList availableActivities(QScriptContext *context, QScriptEngine *engine); - static QScriptValue createActivity(QScriptContext *context, QScriptEngine *engine); static QScriptValue setCurrentActivity(QScriptContext *context, QScriptEngine *engine); static QScriptValue currentActivity(QScriptContext *controller, QScriptEngine *engine); static QScriptValue activities(QScriptContext *context, QScriptEngine *engine); static QScriptValue setActivityName(QScriptContext *context, QScriptEngine *engine); static QScriptValue activityName(QScriptContext *context, QScriptEngine *engine); + static QScriptValue loadSerializedLayout(QScriptContext *context, QScriptEngine *engine); static QScriptValue newPanel(QScriptContext *context, QScriptEngine *engine); static QScriptValue desktopsForActivity(QScriptContext *context, QScriptEngine *engine); static QScriptValue desktops(QScriptContext *context, QScriptEngine *engine); @@ -95,20 +58,10 @@ static QScriptValue knownWallpaperPlugins(QScriptContext *context, QScriptEngine *engine); static QScriptValue configFile(QScriptContext *context, QScriptEngine *engine); static QScriptValue gridUnit(); - - // helpers static QScriptValue createContainment(const QString &type, const QString &defautPlugin, QScriptContext *context, QScriptEngine *engine); - -private Q_SLOTS: - void exception(const QScriptValue &value); - -private: - Plasma::Corona *m_corona; - QScriptValue m_scriptSelf; }; -static const int PLASMA_DESKTOP_SCRIPTING_VERSION = 20; } #endif diff --git a/shell/scripting/scriptengine.cpp b/shell/scripting/scriptengine_v1.cpp copy from shell/scripting/scriptengine.cpp copy to shell/scripting/scriptengine_v1.cpp --- a/shell/scripting/scriptengine.cpp +++ b/shell/scripting/scriptengine_v1.cpp @@ -17,7 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "scriptengine.h" +#include "scriptengine_v1.h" #include #include @@ -53,41 +53,54 @@ #include "../standaloneappcorona.h" #include "../screenpool.h" -QScriptValue constructQRectFClass(QScriptEngine *engine); - namespace { template inline void awaitFuture(const QFuture &future) { while (!future.isFinished()) { QCoreApplication::processEvents(); } } -} -namespace WorkspaceScripting -{ + class ScriptArray_forEach_Helper { + public: + ScriptArray_forEach_Helper(const QScriptValue &array) + : array(array) + { + } -ScriptEngine::ScriptEngine(Plasma::Corona *corona, QObject *parent) - : QScriptEngine(parent), - m_corona(corona) -{ - Q_ASSERT(m_corona); - AppInterface *interface = new AppInterface(this); - connect(interface, &AppInterface::print, this, &ScriptEngine::print); - m_scriptSelf = newQObject(interface, QScriptEngine::QtOwnership, - QScriptEngine::ExcludeSuperClassProperties | - QScriptEngine::ExcludeSuperClassMethods); - setupEngine(); - connect(this, &ScriptEngine::signalHandlerException, this, &ScriptEngine::exception); - bindI18N(this); + // operator + is commonly used for these things + // to avoid having the lambda inside the parenthesis + template + void operator+ (Function function) const + { + if (!array.isArray()) return; + + int length = array.property("length").toInteger(); + for (int i = 0; i < length; ++i) { + function(array.property(i)); + } + } + + private: + const QScriptValue &array; + }; + + #define SCRIPT_FOREACH(Variable, Array) \ + ScriptArray_forEach_Helper(Array) + [&] (const QScriptValue &Variable) + + // Case insensitive comparison of two strings + template + inline bool matches(const QString &object, const StringType &string) + { + return object.compare(string, Qt::CaseInsensitive) == 0; + } } -ScriptEngine::~ScriptEngine() +namespace WorkspaceScripting { -} -QScriptValue ScriptEngine::desktopById(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::desktopById(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() == 0) { return context->throwError(i18n("activityById requires an id")); @@ -104,24 +117,7 @@ return engine->undefinedValue(); } -QStringList ScriptEngine::availableActivities(QScriptContext *context, QScriptEngine *engine) -{ - Q_UNUSED(engine) - - ScriptEngine *env = envFor(engine); - - ShellCorona *sc = qobject_cast(env->m_corona); - StandaloneAppCorona *ac = qobject_cast(env->m_corona); - if (sc) { - return sc->availableActivities(); - } else if (ac) { - return ac->availableActivities(); - } - - return QStringList(); -} - -QScriptValue ScriptEngine::desktopsForActivity(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::desktopsForActivity(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() == 0) { return context->throwError(i18n("desktopsForActivity requires an id")); @@ -132,56 +128,20 @@ const QString id = context->argument(0).toString(); - // confirm this activity actually exists - bool found = false; - for (const QString &act: availableActivities(context, engine)) { - if (act == id) { - found = true; - break; - } - } - - if (!found) { - containments.setProperty(QStringLiteral("length"), 0); - return containments; - } - ScriptEngine *env = envFor(engine); - foreach (Plasma::Containment *c, env->m_corona->containments()) { - if (c->activity() == id && !isPanel(c)) { - containments.setProperty(count, env->wrap(c)); - ++count; - } - } - if (count == 0) { - // we have no desktops for this activity, so lets make them now - // this can happen when the activity already exists but has never been activated - // with the current shell package and layout.js is run to set up the shell for the - // first time - ShellCorona *sc = qobject_cast(env->m_corona); - StandaloneAppCorona *ac = qobject_cast(env->m_corona); - if (sc) { - foreach (int i, sc->screenIds()) { - Plasma::Containment *c = sc->createContainmentForActivity(id, i); - containments.setProperty(count, env->wrap(c)); - ++count; - } - } else if (ac) { - const int numScreens = env->m_corona->numScreens(); - for (int i = 0; i < numScreens; ++i) { - Plasma::Containment *c = ac->createContainmentForActivity(id, i); - containments.setProperty(count, env->wrap(c)); - ++count; - } - } + const auto result = env->desktopsForActivity(id); + + for (Containment* c: result) { + containments.setProperty(count, env->wrap(c)); + ++count; } containments.setProperty(QStringLiteral("length"), count); return containments; } -QScriptValue ScriptEngine::desktopForScreen(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::desktopForScreen(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() == 0) { return context->throwError(i18n("activityForScreen requires a screen id")); @@ -192,7 +152,7 @@ return env->wrap(env->m_corona->containmentForScreen(screen)); } -QScriptValue ScriptEngine::createActivity(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::createActivity(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() < 0) { return context->throwError(i18n("createActivity required the activity name")); @@ -232,7 +192,7 @@ return QScriptValue(id); } -QScriptValue ScriptEngine::setCurrentActivity(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::setCurrentActivity(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) @@ -250,7 +210,7 @@ return QScriptValue(task.result()); } -QScriptValue ScriptEngine::setActivityName(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::setActivityName(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) @@ -269,7 +229,7 @@ return QScriptValue(); } -QScriptValue ScriptEngine::activityName(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::activityName(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) @@ -284,127 +244,163 @@ return QScriptValue(info.name()); } -QScriptValue ScriptEngine::currentActivity(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::currentActivity(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) Q_UNUSED(context) KActivities::Consumer consumer; return consumer.currentActivity(); } -QScriptValue ScriptEngine::activities(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::activities(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(context) - return qScriptValueFromSequence(engine, availableActivities(context, engine)); + return qScriptValueFromSequence(engine, envFor(engine)->availableActivities()); } -QScriptValue ScriptEngine::newPanel(QScriptContext *context, QScriptEngine *engine) +// Utility function to process configs and config groups +template +void loadSerializedConfigs(Object *object, const QScriptValue &configs) { - QString plugin(QStringLiteral("org.kde.panel")); + SCRIPT_FOREACH(config, configs) { + // If the config group is set, pass it on to the containment + auto currentConfigGroup = config.property("currentConfigGroup"); + if (currentConfigGroup.isArray()) { + QStringList groups; + SCRIPT_FOREACH(group, currentConfigGroup) { + groups << group.toString(); + }; + object->setCurrentConfigGroup(groups); + // qDebug() << "DESERIALIZATION: currentConfigGroup = " << groups; + } - if (context->argumentCount() > 0) { - plugin = context->argument(0).toString(); - } + // Read other properties and set the configuration + QScriptValueIterator it(config); + while (it.hasNext()) { + it.next(); + if (it.name() == "currentConfigGroup") continue; - return createContainment(QStringLiteral("Panel"), plugin, context, engine); + object->writeConfig(it.name(), it.value().toVariant()); + // qDebug() << "DESERIALIZATION: writeConfig(...) " << it.name() << it.value().toVariant(); + } + }; } -QScriptValue ScriptEngine::createContainment(const QString &type, const QString &defaultPlugin, - QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::loadSerializedLayout(QScriptContext *context, QScriptEngine *engine) { - QString plugin = context->argumentCount() > 0 ? context->argument(0).toString() : - defaultPlugin; - - bool exists = false; - const KPluginInfo::List list = Plasma::PluginLoader::listContainmentsOfType(type); - foreach (const KPluginInfo &info, list) { - if (info.pluginName() == plugin) { - exists = true; - break; - } - } + Q_UNUSED(engine) - if (!exists) { - return context->throwError(i18n("Could not find a plugin for %1 named %2.", type, plugin)); + if (context->argumentCount() < 1) { + return context->throwError(i18n("loadSerializedLayout requires the JSON object to deserialize from")); } - ScriptEngine *env = envFor(engine); - Plasma::Containment *c = 0; - if (type == QLatin1String("Panel")) { - ShellCorona *sc = qobject_cast(env->m_corona); - StandaloneAppCorona *ac = qobject_cast(env->m_corona); - if (sc) { - c = sc->addPanel(plugin); - } else if (ac) { - c = ac->addPanel(plugin); - } - } else { - c = env->m_corona->createContainment(plugin); - } - if (c) { - if (type == QLatin1String("Panel")) { - // some defaults - c->setFormFactor(Plasma::Types::Horizontal); - c->setLocation(Plasma::Types::TopEdge); - } - c->updateConstraints(Plasma::Types::AllConstraints | Plasma::Types::StartupCompletedConstraint); - c->flushPendingConstraintsEvents(); + const auto data = context->argument(0); + + if (data.property("serializationFormatVersion").toInteger() != 1) { + return context->throwError(i18n("loadSerializedLayout: invalid version of the serialized object")); } - return env->wrap(c); -} + const auto desktops = env->desktopsForActivity(KActivities::Consumer().currentActivity()); + Q_ASSERT_X(desktops.size() != 0, "V1::loadSerializedLayout", "We need desktops"); -QScriptValue ScriptEngine::wrap(Plasma::Applet *w) -{ - Widget *wrapper = new Widget(w); - QScriptValue v = newQObject(wrapper, QScriptEngine::ScriptOwnership, - QScriptEngine::ExcludeSuperClassProperties | - QScriptEngine::ExcludeSuperClassMethods); - return v; -} + // qDebug() << "DESKTOP DESERIALIZATION: Loading desktops..."; -QScriptValue ScriptEngine::wrap(Plasma::Containment *c) -{ - Containment *wrapper = isPanel(c) ? new Panel(c) : new Containment(c); - return wrap(wrapper); -} + int count = 0; + SCRIPT_FOREACH(desktopData, data.property("desktops")) { + // If the template has more desktops than we do, ignore them + if (count >= desktops.size()) return; + + auto desktop = desktops[count]; + // qDebug() << "DESKTOP DESERIALIZATION: var cont = desktopsArray[...]; " << count << " -> " << desktop; + + // Setting the wallpaper plugin because it is special + desktop->setWallpaperPlugin(desktopData.property("wallpaperPlugin").toString()); + // qDebug() << "DESKTOP DESERIALIZATION: cont->setWallpaperPlugin(...) " << desktop->wallpaperPlugin(); + + // Now, lets go through the configs + loadSerializedConfigs(desktop, desktopData.property("config")); + + // After the config, we want to load the applets + SCRIPT_FOREACH(appletData, desktopData.property("applets")) { + // qDebug() << "DESKTOP DESERIALIZATION: Applet: " << appletData.toString(); + + // TODO: It would be nicer to be able to call addWidget directly + auto desktopObject = env->wrap(desktop); + auto addAppletFunction = desktopObject.property("addWidget"); + QScriptValueList args { + appletData.property("plugin"), + appletData.property("geometry.x").toInteger() * ScriptEngine::gridUnit(), + appletData.property("geometry.y").toInteger() * ScriptEngine::gridUnit(), + appletData.property("geometry.width").toInteger() * ScriptEngine::gridUnit(), + appletData.property("geometry.height").toInteger() * ScriptEngine::gridUnit() + }; + + auto appletObject = addAppletFunction.call(desktopObject, args); + + if (auto applet = qobject_cast(appletObject.toQObject())) { + // Now, lets go through the configs for the applet + loadSerializedConfigs(applet, appletData.property("config")); + } + }; -QScriptValue ScriptEngine::wrap(Containment *c) -{ - QScriptValue v = newQObject(c, QScriptEngine::ScriptOwnership, - QScriptEngine::ExcludeSuperClassProperties | - QScriptEngine::ExcludeSuperClassMethods); - v.setProperty(QStringLiteral("widgetById"), newFunction(Containment::widgetById)); - v.setProperty(QStringLiteral("addWidget"), newFunction(Containment::addWidget)); - v.setProperty(QStringLiteral("widgets"), newFunction(Containment::widgets)); + count++; + }; - return v; -} + // qDebug() << "PANEL DESERIALIZATION: Loading panels..."; -int ScriptEngine::defaultPanelScreen() const -{ - return 0; + SCRIPT_FOREACH(panelData, data.property("panels")) { + const auto panel = qobject_cast(env->createContainment( + QStringLiteral("Panel"), QStringLiteral("org.kde.panel"))); + + Q_ASSERT(panel); + + // Basic panel setup + panel->setLocation(panelData.property("location").toString()); + panel->setHeight(panelData.property("height").toInteger() * ScriptEngine::gridUnit()); + + // Loading the config for the panel + loadSerializedConfigs(panel, panelData.property("config")); + + // Now dealing with the applets + SCRIPT_FOREACH(appletData, panelData.property("applets")) { + // qDebug() << "PANEL DESERIALIZATION: Applet: " << appletData.toString(); + + // TODO: It would be nicer to be able to call addWidget directly + auto panelObject = env->wrap(panel); + auto addAppletFunction = panelObject.property("addWidget"); + QScriptValueList args { appletData.property("plugin") }; + + auto appletObject = addAppletFunction.call(panelObject, args); + // qDebug() << "PANEL DESERIALIZATION: addWidget" + // << appletData.property("plugin").toString() + // ; + + if (auto applet = qobject_cast(appletObject.toQObject())) { + // Now, lets go through the configs for the applet + loadSerializedConfigs(applet, appletData.property("config")); + } + }; + }; + + return QScriptValue(); } -ScriptEngine *ScriptEngine::envFor(QScriptEngine *engine) +QScriptValue ScriptEngine::V1::newPanel(QScriptContext *context, QScriptEngine *engine) { - QObject *object = engine->globalObject().toQObject(); - Q_ASSERT(object); - - AppInterface *interface = qobject_cast(object); - Q_ASSERT(interface); + QString plugin(QStringLiteral("org.kde.panel")); - ScriptEngine *env = qobject_cast(interface->parent()); - Q_ASSERT(env); + if (context->argumentCount() > 0) { + plugin = context->argument(0).toString(); + } - return env; + return createContainment(QStringLiteral("Panel"), plugin, context, engine); } -QScriptValue ScriptEngine::panelById(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::panelById(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() == 0) { return context->throwError(i18n("panelById requires an id")); @@ -421,7 +417,7 @@ return engine->undefinedValue(); } -QScriptValue ScriptEngine::panels(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::panels(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(context) @@ -440,7 +436,7 @@ return panels; } -QScriptValue ScriptEngine::fileExists(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::fileExists(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { @@ -456,9 +452,10 @@ return f.exists(); } -QScriptValue ScriptEngine::loadTemplate(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::loadTemplate(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) + if (context->argumentCount() == 0) { // qDebug() << "no arguments"; return false; @@ -537,7 +534,7 @@ return rv; } -QScriptValue ScriptEngine::applicationExists(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::applicationExists(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { @@ -576,18 +573,10 @@ return false; } -QString ScriptEngine::onlyExec(const QString &commandLine) -{ - if (commandLine.isEmpty()) { - return commandLine; - } - - return KShell::splitArgs(commandLine, KShell::TildeExpand).first(); -} - -QScriptValue ScriptEngine::defaultApplication(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::defaultApplication(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) + if (context->argumentCount() == 0) { return false; } @@ -597,79 +586,101 @@ return false; } - const bool storageId = context->argumentCount() < 2 ? false : context->argument(1).toBool(); + const bool storageId + = context->argumentCount() < 2 ? false : context->argument(1).toBool(); - // FIXME: there are some pretty horrible hacks below, in the sense that they assume a very - // specific implementation system. there is much room for improvement here. see + // FIXME: there are some pretty horrible hacks below, in the sense that they + // assume a very + // specific implementation system. there is much room for improvement here. + // see // kdebase-runtime/kcontrol/componentchooser/ for all the gory details ;) - if (application.compare(QLatin1String("mailer"), Qt::CaseInsensitive) == 0) { - // KEMailSettings settings; + if (matches(application, QLatin1String("mailer"))) { + // KEMailSettings settings; // in KToolInvocation, the default is kmail; but let's be friendlier :) -// QString command = settings.getSetting(KEMailSettings::ClientProgram); + // QString command = settings.getSetting(KEMailSettings::ClientProgram); QString command; if (command.isEmpty()) { - if (KService::Ptr kontact = KService::serviceByStorageId(QStringLiteral("kontact"))) { - return storageId ? kontact->storageId() : onlyExec(kontact->exec()); - } else if (KService::Ptr kmail = KService::serviceByStorageId(QStringLiteral("kmail"))) { + if (KService::Ptr kontact + = KService::serviceByStorageId(QStringLiteral("kontact"))) { + return storageId ? kontact->storageId() + : onlyExec(kontact->exec()); + } else if (KService::Ptr kmail + = KService::serviceByStorageId(QStringLiteral("kmail"))) { return storageId ? kmail->storageId() : onlyExec(kmail->exec()); } } if (!command.isEmpty()) { - //if (settings.getSetting(KEMailSettings::ClientTerminal) == "true") { - if (false) { + if (false) { KConfigGroup confGroup(KSharedConfig::openConfig(), "General"); - const QString preferredTerminal = confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole")); + const QString preferredTerminal = confGroup.readPathEntry( + "TerminalApplication", QStringLiteral("konsole")); command = preferredTerminal + QLatin1String(" -e ") + command; } return command; } - } else if (application.compare(QLatin1String("browser"), Qt::CaseInsensitive) == 0) { + + } else if (matches(application, QLatin1String("browser"))) { KConfigGroup config(KSharedConfig::openConfig(), "General"); - QString browserApp = config.readPathEntry("BrowserApplication", QString()); + QString browserApp + = config.readPathEntry("BrowserApplication", QString()); if (browserApp.isEmpty()) { - const KService::Ptr htmlApp = KMimeTypeTrader::self()->preferredService(QStringLiteral("text/html")); + const KService::Ptr htmlApp + = KMimeTypeTrader::self()->preferredService(QStringLiteral("text/html")); if (htmlApp) { browserApp = storageId ? htmlApp->storageId() : htmlApp->exec(); } } else if (browserApp.startsWith('!')) { browserApp = browserApp.mid(1); } return onlyExec(browserApp); - } else if (application.compare(QLatin1String("terminal"), Qt::CaseInsensitive) == 0) { + + } else if (matches(application, QLatin1String("terminal"))) { KConfigGroup confGroup(KSharedConfig::openConfig(), "General"); - return onlyExec(confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole"))); - } else if (application.compare(QLatin1String("filemanager"), Qt::CaseInsensitive) == 0) { - KService::Ptr service = KMimeTypeTrader::self()->preferredService(QStringLiteral("inode/directory")); + return onlyExec(confGroup.readPathEntry("TerminalApplication", + QStringLiteral("konsole"))); + + } else if (matches(application, QLatin1String("filemanager"))) { + KService::Ptr service = KMimeTypeTrader::self()->preferredService( + QStringLiteral("inode/directory")); if (service) { return storageId ? service->storageId() : onlyExec(service->exec()); } - } else if (application.compare(QLatin1String("windowmanager"), Qt::CaseInsensitive) == 0) { + + } else if (matches(application, QLatin1String("windowmanager"))) { KConfig cfg(QStringLiteral("ksmserverrc"), KConfig::NoGlobals); KConfigGroup confGroup(&cfg, "General"); - return onlyExec(confGroup.readEntry("windowManager", QStringLiteral("kwin"))); + return onlyExec( + confGroup.readEntry("windowManager", QStringLiteral("kwin"))); + } else if (KService::Ptr service = KMimeTypeTrader::self()->preferredService(application)) { return storageId ? service->storageId() : onlyExec(service->exec()); + } else { // try the files in share/apps/kcm_componentchooser/ - const QStringList services = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kcm_componentchooser/")); + const QStringList services = QStandardPaths::locateAll( + QStandardPaths::GenericDataLocation, + QStringLiteral("kcm_componentchooser/")); qDebug() << "ok, trying in" << services; foreach (const QString &service, services) { if (!service.endsWith(QLatin1String(".desktop"))) { continue; } KConfig config(service, KConfig::SimpleConfig); KConfigGroup cg = config.group(QByteArray()); const QString type = cg.readEntry("valueName", QString()); - //qDebug() << " checking" << service << type << application; - if (type.compare(application, Qt::CaseInsensitive) == 0) { - KConfig store(cg.readPathEntry("storeInFile", QStringLiteral("null"))); - KConfigGroup storeCg(&store, cg.readEntry("valueSection", QString())); - const QString exec = storeCg.readPathEntry(cg.readEntry("valueName", "kcm_componenchooser_null"), - cg.readEntry("defaultImplementation", QString())); + // qDebug() << " checking" << service << type << application; + if (matches(type, application)) { + KConfig store( + cg.readPathEntry("storeInFile", QStringLiteral("null"))); + KConfigGroup storeCg(&store, + cg.readEntry("valueSection", QString())); + const QString exec = storeCg.readPathEntry( + cg.readEntry("valueName", "kcm_componenchooser_null"), + cg.readEntry("defaultImplementation", QString())); if (!exec.isEmpty()) { return exec; } @@ -682,7 +693,8 @@ return false; } -QScriptValue ScriptEngine::applicationPath(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::applicationPath(QScriptContext *context, + QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { @@ -701,30 +713,37 @@ } if (KService::Ptr service = KService::serviceByStorageId(application)) { - return QStandardPaths::locate(QStandardPaths::ApplicationsLocation, service->entryPath()); + return QStandardPaths::locate(QStandardPaths::ApplicationsLocation, + service->entryPath()); } if (application.contains(QStringLiteral("'"))) { // apostrophes just screw up the trader lookups below, so check for it return QString(); } // next, consult ksycoca for an app by that name - KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("Name =~ '%1'").arg(application)); + KService::List offers = KServiceTypeTrader::self()->query( + QStringLiteral("Application"), + QStringLiteral("Name =~ '%1'").arg(application)); if (offers.isEmpty()) { // next, consult ksycoca for an app by that generic name - offers = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("GenericName =~ '%1'").arg(application)); + offers = KServiceTypeTrader::self()->query( + QStringLiteral("Application"), + QStringLiteral("GenericName =~ '%1'").arg(application)); } if (!offers.isEmpty()) { KService::Ptr offer = offers.first(); - return QStandardPaths::locate(QStandardPaths::ApplicationsLocation, offer->entryPath()); + return QStandardPaths::locate(QStandardPaths::ApplicationsLocation, + offer->entryPath()); } return QString(); } -QScriptValue ScriptEngine::userDataPath(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::userDataPath(QScriptContext *context, + QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { @@ -736,33 +755,43 @@ return QDir::homePath(); } - QStandardPaths::StandardLocation location = QStandardPaths::GenericDataLocation; - if (type.compare(QLatin1String("desktop"), Qt::CaseInsensitive) == 0) { + QStandardPaths::StandardLocation location + = QStandardPaths::GenericDataLocation; + if (matches(type, QLatin1String("desktop"))) { location = QStandardPaths::DesktopLocation; - } else if (type.compare(QLatin1String("documents"), Qt::CaseInsensitive) == 0) { + + } else if (matches(type, QLatin1String("documents"))) { location = QStandardPaths::DocumentsLocation; - } else if (type.compare(QLatin1String("music"), Qt::CaseInsensitive) == 0) { + + } else if (matches(type, QLatin1String("music"))) { location = QStandardPaths::MusicLocation; - } else if (type.compare(QLatin1String("video"), Qt::CaseInsensitive) == 0) { + + } else if (matches(type, QLatin1String("video"))) { location = QStandardPaths::MoviesLocation; - } else if (type.compare(QLatin1String("downloads"), Qt::CaseInsensitive) == 0) { + + } else if (matches(type, QLatin1String("downloads"))) { location = QStandardPaths::DownloadLocation; - } else if (type.compare(QLatin1String("pictures"), Qt::CaseInsensitive) == 0) { + + } else if (matches(type, QLatin1String("pictures"))) { location = QStandardPaths::PicturesLocation; - } else if (type.compare(QLatin1String("config"), Qt::CaseInsensitive) == 0) { + + } else if (matches(type, QLatin1String("config"))) { location = QStandardPaths::GenericConfigLocation; } + if (context->argumentCount() > 1) { QString loc = QStandardPaths::writableLocation(location); loc.append(QDir::separator()); loc.append(context->argument(1).toString()); return loc; } + const QStringList &locations = QStandardPaths::standardLocations(location); return locations.count() ? locations.first() : QString(); } -QScriptValue ScriptEngine::knownWallpaperPlugins(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::knownWallpaperPlugins(QScriptContext *context, + QScriptEngine *engine) { Q_UNUSED(engine) @@ -773,19 +802,24 @@ QString constraint; if (!formFactor.isEmpty()) { - constraint.append("[X-Plasma-FormFactors] ~~ '").append(formFactor).append("'"); + constraint.append("[X-Plasma-FormFactors] ~~ '") + .append(formFactor) + .append("'"); } - QList wallpapers = KPackage::PackageLoader::self()->listPackages(QStringLiteral("Plasma/Wallpaper"), QString()); + QList wallpapers + = KPackage::PackageLoader::self()->listPackages( + QStringLiteral("Plasma/Wallpaper"), QString()); QScriptValue rv = engine->newArray(wallpapers.size()); for (auto wp : wallpapers) { rv.setProperty(wp.name(), engine->newArray(0)); } return rv; } -QScriptValue ScriptEngine::configFile(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::configFile(QScriptContext *context, + QScriptEngine *engine) { ConfigGroup *file = 0; @@ -795,7 +829,7 @@ const QString &fileName = context->argument(0).toString(); const ScriptEngine *env = envFor(engine); - const Plasma::Corona* corona = env->corona(); + const Plasma::Corona *corona = env->corona(); if (fileName == corona->config()->name()) { file->setConfig(corona->config()); @@ -806,85 +840,39 @@ if (context->argumentCount() > 1) { file->setGroup(context->argument(1).toString()); } - } else if (ConfigGroup *parent= qobject_cast(context->argument(0).toQObject())) { + + } else if (ConfigGroup *parent = qobject_cast( + context->argument(0).toQObject())) { file = new ConfigGroup(parent); if (context->argumentCount() > 1) { file->setGroup(context->argument(1).toString()); } } + } else { file = new ConfigGroup; } - QScriptValue v = engine->newQObject(file, - QScriptEngine::ScriptOwnership, - QScriptEngine::ExcludeSuperClassProperties | - QScriptEngine::ExcludeSuperClassMethods); + QScriptValue v + = engine->newQObject(file, QScriptEngine::ScriptOwnership, + QScriptEngine::ExcludeSuperClassProperties + | QScriptEngine::ExcludeSuperClassMethods); return v; - -} - -void ScriptEngine::setupEngine() -{ - QScriptValue v = globalObject(); - QScriptValueIterator it(v); - while (it.hasNext()) { - it.next(); - // we provide our own print implementation, but we want the rest - if (it.name() != QLatin1String("print")) { - m_scriptSelf.setProperty(it.name(), it.value()); - } - } - - m_scriptSelf.setProperty(QStringLiteral("QRectF"), constructQRectFClass(this)); - m_scriptSelf.setProperty(QStringLiteral("createActivity"), newFunction(ScriptEngine::createActivity)); - m_scriptSelf.setProperty(QStringLiteral("setCurrentActivity"), newFunction(ScriptEngine::setCurrentActivity)); - m_scriptSelf.setProperty(QStringLiteral("currentActivity"), newFunction(ScriptEngine::currentActivity)); - m_scriptSelf.setProperty(QStringLiteral("activities"), newFunction(ScriptEngine::activities)); - m_scriptSelf.setProperty(QStringLiteral("setActivityName"), newFunction(ScriptEngine::setActivityName)); - m_scriptSelf.setProperty(QStringLiteral("activityName"), newFunction(ScriptEngine::activityName)); - m_scriptSelf.setProperty(QStringLiteral("setActivityName"), newFunction(ScriptEngine::setActivityName)); - m_scriptSelf.setProperty(QStringLiteral("Panel"), newFunction(ScriptEngine::newPanel, newObject())); - m_scriptSelf.setProperty(QStringLiteral("desktopsForActivity"), newFunction(ScriptEngine::desktopsForActivity)); - m_scriptSelf.setProperty(QStringLiteral("desktops"), newFunction(ScriptEngine::desktops)); - m_scriptSelf.setProperty(QStringLiteral("desktopById"), newFunction(ScriptEngine::desktopById)); - m_scriptSelf.setProperty(QStringLiteral("desktopForScreen"), newFunction(ScriptEngine::desktopForScreen)); - m_scriptSelf.setProperty(QStringLiteral("panelById"), newFunction(ScriptEngine::panelById)); - m_scriptSelf.setProperty(QStringLiteral("panels"), newFunction(ScriptEngine::panels)); - m_scriptSelf.setProperty(QStringLiteral("fileExists"), newFunction(ScriptEngine::fileExists)); - m_scriptSelf.setProperty(QStringLiteral("loadTemplate"), newFunction(ScriptEngine::loadTemplate)); - m_scriptSelf.setProperty(QStringLiteral("applicationExists"), newFunction(ScriptEngine::applicationExists)); - m_scriptSelf.setProperty(QStringLiteral("defaultApplication"), newFunction(ScriptEngine::defaultApplication)); - m_scriptSelf.setProperty(QStringLiteral("userDataPath"), newFunction(ScriptEngine::userDataPath)); - m_scriptSelf.setProperty(QStringLiteral("applicationPath"), newFunction(ScriptEngine::applicationPath)); - m_scriptSelf.setProperty(QStringLiteral("knownWallpaperPlugins"), newFunction(ScriptEngine::knownWallpaperPlugins)); - m_scriptSelf.setProperty(QStringLiteral("ConfigFile"), newFunction(ScriptEngine::configFile)); - m_scriptSelf.setProperty(QStringLiteral("gridUnit"), ScriptEngine::gridUnit()); - - setGlobalObject(m_scriptSelf); -} - -bool ScriptEngine::isPanel(const Plasma::Containment *c) -{ - if (!c) { - return false; - } - - return c->containmentType() == Plasma::Types::PanelContainment || - c->containmentType() == Plasma::Types::CustomPanelContainment; } -QScriptValue ScriptEngine::desktops(QScriptContext *context, QScriptEngine *engine) +QScriptValue ScriptEngine::V1::desktops(QScriptContext *context, + QScriptEngine *engine) { Q_UNUSED(context) QScriptValue containments = engine->newArray(); ScriptEngine *env = envFor(engine); int count = 0; foreach (Plasma::Containment *c, env->corona()->containments()) { - //make really sure we get actual desktops, so check for a non empty activty id + // make really sure we get actual desktops, so check for a non empty + // activty id if (!isPanel(c) && !c->activity().isEmpty()) { containments.setProperty(count, env->wrap(c)); ++count; @@ -895,92 +883,28 @@ return containments; } -QScriptValue ScriptEngine::gridUnit() -{ - int gridUnit = QFontMetrics(QGuiApplication::font()).boundingRect(QStringLiteral("M")).height(); - if (gridUnit % 2 != 0) { - gridUnit++; - } - - return gridUnit; -} - -Plasma::Corona *ScriptEngine::corona() const -{ - return m_corona; -} - -bool ScriptEngine::evaluateScript(const QString &script, const QString &path) -{ - //qDebug() << "evaluating" << m_editor->toPlainText(); - evaluate(script, path); - if (hasUncaughtException()) { - //qDebug() << "catch the exception!"; - QString error = i18n("Error: %1 at line %2\n\nBacktrace:\n%3", - uncaughtException().toString(), - QString::number(uncaughtExceptionLineNumber()), - uncaughtExceptionBacktrace().join(QStringLiteral("\n "))); - emit printError(error); - return false; - } - - return true; -} - -void ScriptEngine::exception(const QScriptValue &value) +QScriptValue ScriptEngine::V1::gridUnit() { - //qDebug() << "exception caught!" << value.toVariant(); - emit printError(value.toVariant().toString()); + return ScriptEngine::gridUnit(); } -QStringList ScriptEngine::pendingUpdateScripts(Plasma::Corona *corona) +QScriptValue ScriptEngine::V1::createContainment(const QString &type, const QString &defaultPlugin, + QScriptContext *context, QScriptEngine *engine) { - if (!corona->package().metadata().isValid()) { - qWarning() << "Warning: corona package invalid"; - return QStringList(); - } - - const QString appName = corona->package().metadata().pluginName(); - QStringList scripts; - - const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "plasma/shells/" + appName + QStringLiteral("/contents/updates"), QStandardPaths::LocateDirectory); - Q_FOREACH(const QString& dir, dirs) { - QDirIterator it(dir, QStringList() << QStringLiteral("*.js")); - while (it.hasNext()) { - scripts.append(it.next()); - } - } - QStringList scriptPaths; + const QString plugin = context->argumentCount() > 0 + ? context->argument(0).toString() + : defaultPlugin; - if (scripts.isEmpty()) { - //qDebug() << "no update scripts"; - return scriptPaths; - } - - KConfigGroup cg(KSharedConfig::openConfig(), "Updates"); - QStringList performed = cg.readEntry("performed", QStringList()); - const QString localXdgDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); - - foreach (const QString &script, scripts) { - if (performed.contains(script)) { - continue; - } - - if (script.startsWith(localXdgDir)) { - // qDebug() << "skipping user local script: " << script; - continue; - } + ScriptEngine *env = envFor(engine); + auto result = env->createContainment(type, plugin); - scriptPaths.append(script); - performed.append(script); + if (!result) { + return context->throwError(i18n("Could not find a plugin for %1 named %2.", type, plugin)); } - cg.writeEntry("performed", performed); - KSharedConfig::openConfig()->sync(); - return scriptPaths; -} - + return env->wrap(result); } +} // namespace WorkspaceScripting diff --git a/shell/shellcorona.h b/shell/shellcorona.h --- a/shell/shellcorona.h +++ b/shell/shellcorona.h @@ -145,6 +145,16 @@ void evaluateScript(const QString &string); void activateLauncherMenu(); + QByteArray dumpCurrentLayoutJS() const; + + /** + * loads the shell layout from a look and feel package, + * resetting it to the default layout exported in the + * look and feel package + */ + void loadLookAndFeelDefaultLayout(const QString &layout); + + Plasma::Containment *addPanel(const QString &plugin); void nextActivity(); diff --git a/shell/shellcorona.cpp b/shell/shellcorona.cpp --- a/shell/shellcorona.cpp +++ b/shell/shellcorona.cpp @@ -30,6 +30,9 @@ #include #include +#include +#include + #include #include #include @@ -43,6 +46,7 @@ #include #include #include +#include #include @@ -63,7 +67,7 @@ #include "waylanddialogfilter.h" #include "plasmashelladaptor.h" - +#include "debug.h" #include "futureutil.h" #ifndef NDEBUG @@ -128,14 +132,15 @@ dbus.registerObject(QStringLiteral("/PlasmaShell"), this); connect(this, &Plasma::Corona::startupCompleted, this, - []() { + [this]() { qDebug() << "Plasma Shell startup completed"; QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), QStringLiteral("/KSplash"), QStringLiteral("org.kde.KSplash"), QStringLiteral("setStage")); ksplashProgressMessage.setArguments(QList() << QStringLiteral("desktop")); QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); + //TODO: remove }); // Look for theme config in plasmarc, if it isn't configured, take the theme from the @@ -310,6 +315,296 @@ } } + +QJsonArray dumpconfigGroupJS(const KConfigGroup &rootGroup) +{ + QJsonArray result; + + QStringList hierarchy; + QList groups{rootGroup}; + QSet visitedNodes; + + const QSet forbiddenKeys { + QStringLiteral("activityId"), + QStringLiteral("ItemsGeometries"), + QStringLiteral("AppletOrder"), + QStringLiteral("SystrayContainmentId"), + QStringLiteral("location"), + QStringLiteral("plugin") + }; + + // Perform a depth-first tree traversal for config groups + while (!groups.isEmpty()) { + KConfigGroup cg = groups.last(); + + KConfigGroup parentCg = cg; + //FIXME: name is not enough + + hierarchy.clear(); + while (parentCg.isValid() && parentCg.name() != rootGroup.name()) { + hierarchy.prepend(parentCg.name()); + parentCg = parentCg.parent(); + } + + visitedNodes.insert(hierarchy.join(QChar())); + groups.pop_back(); + + QJsonObject configGroupJson; + + if (!cg.keyList().isEmpty()) { + //TODO: this is conditional if applet or containment + if (hierarchy.length() > 0) { + QJsonArray currentConfigGroup; + + foreach (const QString &item, hierarchy) { + currentConfigGroup << item; + } + + configGroupJson.insert("currentConfigGroup", currentConfigGroup); + } + + const auto map = cg.entryMap(); + auto i = map.cbegin(); + for (; i != map.cend(); ++i) { + //some blacklisted keys we don't want to save + if (!forbiddenKeys.contains(i.key())) { + configGroupJson.insert(i.key(), i.value()); + } + } + } + + foreach (const QString &groupName, cg.groupList()) { + if (groupName == QStringLiteral("Applets") || + visitedNodes.contains(hierarchy.join(QChar()) + groupName)) { + continue; + } + groups << KConfigGroup(&cg, groupName); + } + + if (!configGroupJson.isEmpty()) { + result << configGroupJson; + } + } + + return result; +} + +QByteArray ShellCorona::dumpCurrentLayoutJS() const +{ + QJsonObject root; + root.insert("serializationFormatVersion", "1"); + + //same gridUnit calculation as ScriptEngine + int gridUnit = QFontMetrics(QGuiApplication::font()).boundingRect(QStringLiteral("M")).height(); + if (gridUnit % 2 != 0) { + gridUnit++; + } + + auto isPanel = [] (Plasma::Containment *cont) { + return + (cont->formFactor() == Plasma::Types::Horizontal + || cont->formFactor() == Plasma::Types::Vertical) && + (cont->location() == Plasma::Types::TopEdge + || cont->location() == Plasma::Types::BottomEdge + || cont->location() == Plasma::Types::LeftEdge + || cont->location() == Plasma::Types::RightEdge) && + cont->pluginInfo().pluginName() != QStringLiteral("org.kde.plasma.private.systemtray"); + }; + + auto isDesktop = [] (Plasma::Containment *cont) { + return !cont->activity().isEmpty(); + }; + + const auto containments = ShellCorona::containments(); + + // Collecting panels + + QJsonArray panelsJsonArray; + + foreach (Plasma::Containment *cont, containments) { + if (!isPanel(cont)) { + continue; + } + + QJsonObject panelJson; + + const PanelView *view = m_panelViews.value(cont); + const auto location = cont->location(); + + panelJson.insert("location", + location == Plasma::Types::TopEdge ? "top" + : location == Plasma::Types::LeftEdge ? "left" + : location == Plasma::Types::RightEdge ? "right" + : /* Plasma::Types::BottomEdge */ "bottom"); + + const qreal units = + // If we do not have a panel, fallback to 4 units + !view ? 4 + : location == Plasma::Types::TopEdge ? view->height() / gridUnit + : location == Plasma::Types::LeftEdge ? view->width() / gridUnit + : location == Plasma::Types::RightEdge ? view->width() / gridUnit + : /* Plasma::Types::BottomEdge */ view->height() / gridUnit; + + panelJson.insert("height", units); + + // Saving the config keys + const KConfigGroup contConfig = cont->config(); + + panelJson.insert("config", dumpconfigGroupJS(contConfig)); + + // Generate the applets array + QJsonArray appletsJsonArray; + + // Try to parse the encoded applets order + const KConfigGroup genericConf(&contConfig, QStringLiteral("General")); + const QStringList appletsOrderStrings = + genericConf.readEntry(QStringLiteral("AppletOrder"), QString()) + .split(QChar(';')); + + // Consider the applet order to be valid only if there are as many entries as applets() + if (appletsOrderStrings.length() == cont->applets().length()) { + foreach (const QString &appletId, appletsOrderStrings) { + KConfigGroup appletConfig(&contConfig, QStringLiteral("Applets")); + appletConfig = KConfigGroup(&appletConfig, appletId); + + const QString pluginName = + appletConfig.readEntry(QStringLiteral("plugin"), QString()); + + if (pluginName.isEmpty()) { + continue; + } + + QJsonObject appletJson; + + appletJson.insert("plugin", pluginName); + appletJson.insert("config", dumpconfigGroupJS(appletConfig)); + + appletsJsonArray << appletJson; + } + + } else { + foreach (Plasma::Applet *applet, cont->applets()) { + QJsonObject appletJson; + + KConfigGroup appletConfig = applet->config(); + + appletJson.insert("plugin", applet->pluginInfo().pluginName()); + appletJson.insert("config", dumpconfigGroupJS(appletConfig)); + + appletsJsonArray << appletJson; + } + } + + panelJson.insert("applets", appletsJsonArray); + + panelsJsonArray << panelJson; + } + + root.insert("panels", panelsJsonArray); + + // Now we are collecting desktops + + QJsonArray desktopsJson; + + const auto currentActivity = m_activityController->currentActivity(); + + foreach (Plasma::Containment *cont, containments) { + if (!isDesktop(cont) || cont->activity() != currentActivity) { + continue; + } + + QJsonObject desktopJson; + + desktopJson.insert("wallpaperPlugin", cont->wallpaper()); + + // Get the config for the containment + KConfigGroup contConfig = cont->config(); + desktopJson.insert("config", dumpconfigGroupJS(contConfig)); + + // Try to parse the item geometries + const KConfigGroup genericConf(&contConfig, QStringLiteral("General")); + const QStringList appletsGeomStrings = + genericConf.readEntry(QStringLiteral("ItemsGeometries"), QString()) + .split(QChar(';')); + + + QHash appletGeometries; + foreach (const QString &encoded, appletsGeomStrings) { + const QStringList keyValue = encoded.split(QChar(':')); + if (keyValue.length() != 2) { + continue; + } + + const QStringList rectPieces = keyValue.last().split(QChar(',')); + if (rectPieces.length() != 5) { + continue; + } + + QRect rect(rectPieces[0].toInt(), rectPieces[1].toInt(), + rectPieces[2].toInt(), rectPieces[3].toInt()); + + appletGeometries[keyValue.first()] = rect; + } + + QJsonArray appletsJsonArray; + + foreach (Plasma::Applet *applet, cont->applets()) { + const QRect geometry = appletGeometries.value( + QStringLiteral("Applet-") % QString::number(applet->id())); + + QJsonObject appletJson; + + appletJson.insert("title", applet->title()); + appletJson.insert("plugin", applet->pluginInfo().pluginName()); + + appletJson.insert("geometry.x", geometry.x() / gridUnit); + appletJson.insert("geometry.y", geometry.y() / gridUnit); + appletJson.insert("geometry.width", geometry.width() / gridUnit); + appletJson.insert("geometry.height", geometry.height() / gridUnit); + + KConfigGroup appletConfig = applet->config(); + appletJson.insert("config", dumpconfigGroupJS(appletConfig)); + + appletsJsonArray << appletJson; + } + + desktopJson.insert("applets", appletsJsonArray); + desktopsJson << desktopJson; + } + + root.insert("desktops", desktopsJson); + + QJsonDocument json; + json.setObject(root); + + return + "var plasma = getApiVersion(1);\n\n" + "var layout = " + json.toJson() + ";\n\n" + "plasma.loadSerializedLayout(layout);\n"; +} + +void ShellCorona::loadLookAndFeelDefaultLayout(const QString &packageName) +{ + KPackage::Package newPack = m_lookAndFeelPackage; + newPack.setPath(packageName); + + if (!newPack.isValid()) { + return; + } + + KSharedConfig::Ptr conf = KSharedConfig::openConfig(QStringLiteral("plasma-") + m_shell + QStringLiteral("-appletsrc"), KConfig::SimpleConfig); + + m_lookAndFeelPackage.setPath(packageName); + + //get rid of old config + for (const QString &group : conf->groupList()) { + conf->deleteGroup(group); + } + conf->sync(); + unload(); + load(); +} + QString ShellCorona::shell() const { return m_shell; @@ -324,7 +619,10 @@ disconnect(m_activityController, &KActivities::Controller::serviceStatusChanged, this, &ShellCorona::load); - loadLayout("plasma-" + m_shell + "-appletsrc"); + //TODO: a kconf_update script is needed + QString configFileName(QStringLiteral("plasma-") + m_shell + QStringLiteral("-appletsrc")); + + loadLayout(configFileName); checkActivities(); @@ -357,6 +655,9 @@ } } + //NOTE: this is needed in case loadLayout() did *not* call loadDefaultLayout() + //it needs to be after of loadLayout() as it would always create new + //containments on each startup otherwise for (QScreen* screen : qGuiApp->screens()) { addOutput(screen); } @@ -513,8 +814,19 @@ if (m_shell.isEmpty()) { return; } + qDeleteAll(m_desktopViewforId.values()); + m_desktopViewforId.clear(); + qDeleteAll(m_panelViews.values()); + m_panelViews.clear(); + m_desktopContainments.clear(); + m_waitingPanels.clear(); + m_activityContainmentPlugins.clear(); - qDeleteAll(containments()); + while (!containments().isEmpty()) { + //deleting a containment will remove it from the list due to QObject::destroyed connect in Corona + //this form doesn't crash, while qDeleteAll(containments()) does + delete containments().first(); + } } KSharedConfig::Ptr ShellCorona::applicationConfig()