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,5 +21,8 @@
+
+
+
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/shellcorona.h b/shell/shellcorona.h
--- a/shell/shellcorona.h
+++ b/shell/shellcorona.h
@@ -143,6 +143,7 @@
void loadKWinScriptInInteractiveConsole(const QString &script);
void toggleActivityManager();
void evaluateScript(const QString &string);
+ QString dumpCurrentLayoutJS();
Plasma::Containment *addPanel(const QString &plugin);
@@ -198,6 +199,7 @@
void alternativesVisibilityChanged(bool visible);
void interactiveConsoleVisibilityChanged(bool visible);
void screenRemoved(QScreen* screen);
+ void updateLookAndFeelPackage(const QString &file);
private:
void updateStruts();
diff --git a/shell/shellcorona.cpp b/shell/shellcorona.cpp
--- a/shell/shellcorona.cpp
+++ b/shell/shellcorona.cpp
@@ -43,6 +43,7 @@
#include
#include
#include
+#include
#include
@@ -63,7 +64,7 @@
#include "waylanddialogfilter.h"
#include "plasmashelladaptor.h"
-
+#include "debug.h"
#include "futureutil.h"
#ifndef NDEBUG
@@ -100,6 +101,10 @@
m_lookAndFeelPackage.setPath(packageName);
}
+ KDirWatch::self()->addFile(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + QStringLiteral("kdeglobals"));
+ connect(KDirWatch::self(), &KDirWatch::dirty, this, &ShellCorona::updateLookAndFeelPackage);
+ connect(KDirWatch::self(), &KDirWatch::created, this, &ShellCorona::updateLookAndFeelPackage);
+
connect(this, &Plasma::Corona::containmentCreated, this, [this] (Plasma::Containment *c) {
executeSetupPlasmoidScript(c, c);
});
@@ -128,14 +133,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
@@ -311,6 +317,261 @@
}
}
+
+QString dumpconfigGroupJS(const KConfigGroup &rootGroup, const QString &prefix)
+{
+ QString script;
+ QStringList hierarchy;
+ QList groups;
+ groups << rootGroup;
+ QSet visitedNodes;
+
+ QSet forbiddenKeys;
+ 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 enoug
+ hierarchy.clear();
+ while (parentCg.isValid() && parentCg.name() != rootGroup.name()) {
+ hierarchy.prepend(parentCg.name());
+ parentCg = parentCg.parent();
+ }
+
+ visitedNodes.insert(hierarchy.join(QChar()));
+ groups.pop_back();
+
+ if (!cg.keyList().isEmpty()) {
+ script += "\n";
+ //TODO: this is conditional if applet or containment
+ if (hierarchy.length() > 0) {
+ script += QStringLiteral(" //all config values in the \"%1\" group\n").arg(hierarchy.last());
+ script += prefix + QStringLiteral(".currentConfigGroup = Array(\"") + hierarchy.join("\", \"") + "\");\n";
+ }
+
+ QMap::const_iterator i;
+ QMap map = cg.entryMap();
+ for (i = map.constBegin(); i != map.constEnd(); ++i) {
+ //some blacklisted keys we don't want to save
+ if (!forbiddenKeys.contains(i.key())) {
+ script += prefix + ".writeConfig(\"" + i.key() + "\", \"" + i.value() + "\");\n";
+ }
+ }
+ }
+
+ foreach (const QString &groupName, cg.groupList()) {
+ if (groupName == QStringLiteral("Applets") ||
+ visitedNodes.contains(hierarchy.join(QChar()) + groupName)) {
+ continue;
+ }
+ groups << KConfigGroup(&cg, groupName);
+ }
+ }
+
+ return script;
+}
+
+QString ShellCorona::dumpCurrentLayoutJS()
+{
+ QString script;
+
+ //same gridUnit calculation as ScriptEngine
+ int gridUnit = QFontMetrics(QGuiApplication::font()).boundingRect(QStringLiteral("M")).height();
+ if (gridUnit % 2 != 0) {
+ gridUnit++;
+ }
+
+ int i = 0;
+
+ //dump panels
+ script += "//////Panels\n";
+ foreach (Plasma::Containment *cont, containments()) {
+ if ((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")) {
+
+ const PanelView *view = m_panelViews.value(cont);
+
+ script += "{\n";
+ script += " var panel = new Panel;\n";
+
+ switch (cont->location()) {
+ case Plasma::Types::TopEdge:
+ script += " panel.location = \"top\";\n";
+ break;
+ case Plasma::Types::LeftEdge:
+ script += " panel.location = \"left\";\n";
+ break;
+ case Plasma::Types::RightEdge:
+ script += " panel.location = \"right\";\n";
+ break;
+ case Plasma::Types::BottomEdge:
+ default:
+ script += " panel.location = \"bottom\";\n";
+ break;
+ }
+
+ //for panels we don't have view for, fallback to 4 units
+ qreal units = 4;
+
+ //Repeated switch as one depends from view the other doesn't
+ if (view) {
+ switch (cont->location()) {
+ case Plasma::Types::TopEdge:
+ units = view->height() / gridUnit;
+ break;
+ case Plasma::Types::LeftEdge:
+ units = view->width() / gridUnit;
+ break;
+ case Plasma::Types::RightEdge:
+ units = view->width() / gridUnit;
+ break;
+ case Plasma::Types::BottomEdge:
+ default:
+ units = view->height() / gridUnit;
+ break;
+ }
+ }
+
+ script += " panel.height = gridUnit * " + QString::number(units) + ";\n";
+
+ //enumerate config keys for containment
+ KConfigGroup contConfig = cont->config();
+ script += " //Panel containment configuration\n";
+ script += dumpconfigGroupJS(contConfig, QStringLiteral(" panel"));
+ script += "\n\n";
+
+ //try to parse the encoded applets order
+ 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 &stringId, appletsOrderStrings) {
+ KConfigGroup appletConfig(&contConfig, QStringLiteral("Applets"));
+ appletConfig = KConfigGroup(&appletConfig, stringId);
+ const QString pluginName = appletConfig.readEntry(QStringLiteral("plugin"), QString());
+
+ if (pluginName.isEmpty()) {
+ continue;
+ }
+
+ script += " {\n";
+ script += " //Configuration of applet " + pluginName + "\n";
+ script += " var applet = panel.addWidget(\"" + pluginName + "\");\n";
+
+ script += dumpconfigGroupJS(appletConfig, QStringLiteral(" applet"));
+
+ script += " }\n";
+ }
+ //applets order not found, just use order returned by applets()
+ } else {
+ foreach (Plasma::Applet *applet, cont->applets()) {
+ script += " {\n";
+ script += " //Configuration of applet " + applet->pluginInfo().pluginName() + "\n";
+ script += " var applet = panel.addWidget(\"" + applet->pluginInfo().pluginName() + "\", " + QString::number(applet->id()) + ");\n";
+
+ KConfigGroup appletConfig = applet->config();
+ script += dumpconfigGroupJS(appletConfig, QStringLiteral(" applet"));
+
+ script += " }\n";
+ }
+ }
+
+ script += "}\n";
+ script += "\n\n";
+ }
+ }
+
+ //dump desktop containments
+ script += "//////Desktops\n";
+ script += "var desktopsArray = desktopsForActivity(currentActivity());\n";
+
+ //two separate loops to have desktops and panels separated in scripts
+ foreach (Plasma::Containment *cont, containments()) {
+ if (!cont->activity().isEmpty()) {
+ script += "if (desktopsArray.length > " + QString::number(i) + ") {\n";
+ script += " var cont = desktopsArray[" + QString::number(i) + "];\n\n";
+ script += " cont.wallpaperPlugin = '" + cont->wallpaper() + "';\n";
+
+ //enumerate config keys for containment
+ KConfigGroup contConfig = cont->config();
+ script += " //Containment configuration\n";
+ script += dumpconfigGroupJS(contConfig, QStringLiteral(" cont"));
+
+ //try to parse the items geometries
+ KConfigGroup genericConf(&contConfig, QStringLiteral("General"));
+ const QStringList appletsGeomStrings = genericConf.readEntry(QStringLiteral("ItemsGeometries"), QString()).split(QChar(';'));
+
+ QHash appletGeometries;
+ for (const QString encoded : appletsGeomStrings) {
+ const QStringList keyValue = encoded.split(QChar(':'));
+ if (keyValue.length() != 2) {
+ continue;
+ }
+ 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;
+ }
+
+ script += "\n\n";
+ foreach (Plasma::Applet *applet, cont->applets()) {
+ const QRect geometry = appletGeometries.value(QStringLiteral("Applet-") % QString::number(applet->id()));
+ script += " {\n";
+ script += " //Configuration of applet " + applet->title() + "\n";
+ script += " var applet = cont.addWidget(\"" + applet->pluginInfo().pluginName() + "\", gridUnit * " + QString::number(geometry.x()/gridUnit) +
+ ", gridUnit * " + QString::number(geometry.y()/gridUnit) +
+ ", gridUnit * " + QString::number(geometry.width()/gridUnit) +
+ ", gridUnit * " + QString::number(geometry.height()/gridUnit) + ");\n";
+
+ KConfigGroup appletConfig = applet->config();
+ script += dumpconfigGroupJS(appletConfig, QStringLiteral(" applet"));
+
+ script += " }\n";
+ }
+ ++i;
+ script += "}\n\n";
+ }
+ }
+
+
+
+ return script;
+}
+
+void ShellCorona::updateLookAndFeelPackage(const QString &file)
+{
+ //only care about kdeglobals
+ if (!file.endsWith(QStringLiteral("kdeglobals"))) {
+ return;
+ }
+
+ //TODO: put here anything that needs to update lnf-based
+ KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), "KDE");
+ const QString packageName = cg.readEntry("LookAndFeelPackage", QString());
+ if (packageName.isEmpty()) {
+ return;
+ }
+
+ if (packageName == m_lookAndFeelPackage.metadata().pluginId()) {
+ return;
+ }
+
+ m_lookAndFeelPackage.setPath(packageName);
+
+ //NOTE: updateng the plasma theme should *not* be necessary here as the kcm is already doing this
+ unload();
+ load();
+}
+
QString ShellCorona::shell() const
{
return m_shell;
@@ -325,7 +586,18 @@
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 + QChar('-') + m_lookAndFeelPackage.metadata().pluginId() + QStringLiteral("-appletsrc"));
+
+ //NOTE: this is just for the transition from Plasma 5.7
+ {
+ QString oldConfigFilePath(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/plasma-") + m_shell + QStringLiteral("-appletsrc"));
+ if (QFile::exists(oldConfigFilePath)) {
+ qCDebug(PLASMASHELL) << "Old config file older than Plasma 5.8 found: moving to new location";
+ QFile::rename(oldConfigFilePath, QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QChar('/') + configFileName);
+ }
+ }
+ loadLayout(configFileName);
checkActivities();
@@ -358,6 +630,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);
}
@@ -514,8 +789,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()
@@ -530,6 +816,13 @@
void ShellCorona::loadDefaultLayout()
{
+ //NOTE: Is important the containments already exist for each screen
+ // at the moment of the script execution,the same loop in :load()
+ // is executed too late
+ for (QScreen* screen : qGuiApp->screens()) {
+ addOutput(screen);
+ }
+
QString script = ShellManager::s_testModeLayout;
if (script.isEmpty()) {