diff --git a/libtaskmanager/virtualdesktopinfo.cpp b/libtaskmanager/virtualdesktopinfo.cpp deleted file mode 100644 --- a/libtaskmanager/virtualdesktopinfo.cpp +++ /dev/null @@ -1,470 +0,0 @@ -/******************************************************************** -Copyright 2016 Eike Hein - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) version 3, or any -later version accepted by the membership of KDE e.V. (or its -successor approved by the membership of KDE e.V.), which shall -act as a proxy defined in Section 6 of version 3 of the license. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library. If not, see . -*********************************************************************/ - -#include "virtualdesktopinfo.h" - -#include -#include -#include -#include -#include - -#include - -#include - -#if HAVE_X11 -#include -#include -#endif - -namespace TaskManager -{ -class Q_DECL_HIDDEN VirtualDesktopInfo::Private : public QObject -{ - Q_OBJECT - -public: - Private(VirtualDesktopInfo *q); - virtual ~Private() {} - - uint refCount = 1; - - virtual void init() = 0; - virtual QVariant currentDesktop() const = 0; - virtual int numberOfDesktops() const = 0; - virtual QVariantList desktopIds() const = 0; - virtual QStringList desktopNames() const = 0; - virtual quint32 position(const QVariant &desktop) const = 0; - virtual int desktopLayoutRows() const = 0; - virtual void requestActivate(const QVariant &desktop) = 0; - virtual void requestCreateDesktop(quint32 position) = 0; - virtual void requestRemoveDesktop(quint32 position) = 0; - -Q_SIGNALS: - void currentDesktopChanged() const; - void numberOfDesktopsChanged() const; - void desktopIdsChanged() const; - void desktopNamesChanged() const; - void desktopLayoutRowsChanged() const; - -protected: - VirtualDesktopInfo *q; -}; - -VirtualDesktopInfo::Private::Private(VirtualDesktopInfo *q) - : q(q) -{ -} - -#if HAVE_X11 -class Q_DECL_HIDDEN VirtualDesktopInfo::XWindowPrivate : public VirtualDesktopInfo::Private -{ -public: - XWindowPrivate(VirtualDesktopInfo *q); - - void init() override; - QVariant currentDesktop() const override; - int numberOfDesktops() const override; - QVariantList desktopIds() const override; - QStringList desktopNames() const override; - quint32 position(const QVariant &desktop) const override; - int desktopLayoutRows() const override; - void requestActivate(const QVariant &desktop) override; - void requestCreateDesktop(quint32 position) override; - void requestRemoveDesktop(quint32 position) override; -}; - -VirtualDesktopInfo::XWindowPrivate::XWindowPrivate(VirtualDesktopInfo *q) - : VirtualDesktopInfo::Private(q) -{ - init(); -} - -void VirtualDesktopInfo::XWindowPrivate::init() -{ - connect(KWindowSystem::self(), &KWindowSystem::currentDesktopChanged, - this, &VirtualDesktopInfo::XWindowPrivate::currentDesktopChanged); - - connect(KWindowSystem::self(), &KWindowSystem::numberOfDesktopsChanged, - this, &VirtualDesktopInfo::XWindowPrivate::numberOfDesktopsChanged); - - connect(KWindowSystem::self(), &KWindowSystem::desktopNamesChanged, - this, &VirtualDesktopInfo::XWindowPrivate::desktopNamesChanged); - - QDBusConnection dbus = QDBusConnection::sessionBus(); - dbus.connect(QString(), QStringLiteral("/KWin"), QStringLiteral("org.kde.KWin"), QStringLiteral("reloadConfig"), - this, SIGNAL(desktopLayoutRowsChanged())); -} - -QVariant VirtualDesktopInfo::XWindowPrivate::currentDesktop() const -{ - return KWindowSystem::currentDesktop(); -} - -int VirtualDesktopInfo::XWindowPrivate::numberOfDesktops() const -{ - return KWindowSystem::numberOfDesktops(); -} - -QVariantList VirtualDesktopInfo::XWindowPrivate::desktopIds() const -{ - QVariantList ids; - - for (int i = 1; i <= KWindowSystem::numberOfDesktops(); ++i) { - ids << i; - } - - return ids; -} - -QStringList VirtualDesktopInfo::XWindowPrivate::desktopNames() const -{ - QStringList names; - - // Virtual desktop numbers start at 1. - for (int i = 1; i <= KWindowSystem::numberOfDesktops(); ++i) { - names << KWindowSystem::desktopName(i); - } - - return names; -} - -quint32 VirtualDesktopInfo::XWindowPrivate::position(const QVariant &desktop) const -{ - bool ok = false; - - const quint32 desktopNumber = desktop.toUInt(&ok); - - if (!ok) { - return -1; - } - - return desktopNumber; -} - -int VirtualDesktopInfo::XWindowPrivate::desktopLayoutRows() const -{ - const NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops | NET::DesktopNames, NET::WM2DesktopLayout); - return info.desktopLayoutColumnsRows().height(); -} - -void VirtualDesktopInfo::XWindowPrivate::requestActivate(const QVariant &desktop) -{ - bool ok = false; - const int desktopNumber = desktop.toInt(&ok); - - // Virtual desktop numbers start at 1. - if (ok && desktopNumber > 0 && desktopNumber <= KWindowSystem::numberOfDesktops()) { - KWindowSystem::setCurrentDesktop(desktopNumber); - } -} - -void VirtualDesktopInfo::XWindowPrivate::requestCreateDesktop(quint32 position) -{ - Q_UNUSED(position) - - NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops); - info.setNumberOfDesktops(info.numberOfDesktops() + 1); -} - -void VirtualDesktopInfo::XWindowPrivate::requestRemoveDesktop(quint32 position) -{ - Q_UNUSED(position) - - NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops); - - if (info.numberOfDesktops() > 1) { - info.setNumberOfDesktops(info.numberOfDesktops() - 1); - } -} -#endif - -class Q_DECL_HIDDEN VirtualDesktopInfo::WaylandPrivate : public VirtualDesktopInfo::Private -{ -public: - WaylandPrivate(VirtualDesktopInfo *q); - - QVariant currentVirtualDesktop; - QStringList virtualDesktops; - KWayland::Client::PlasmaVirtualDesktopManagement *virtualDesktopManagement = nullptr; - - void init() override; - void addDesktop(const QString &id, quint32 position); - QVariant currentDesktop() const override; - int numberOfDesktops() const override; - QVariantList desktopIds() const override; - QStringList desktopNames() const override; - quint32 position(const QVariant &desktop) const override; - int desktopLayoutRows() const override; - void requestActivate(const QVariant &desktop) override; - void requestCreateDesktop(quint32 position) override; - void requestRemoveDesktop(quint32 position) override; -}; - -VirtualDesktopInfo::WaylandPrivate::WaylandPrivate(VirtualDesktopInfo *q) - : VirtualDesktopInfo::Private(q) -{ - init(); -} - -void VirtualDesktopInfo::WaylandPrivate::init() -{ - if (!KWindowSystem::isPlatformWayland()) { - return; - } - - KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(q); - - if (!connection) { - return; - } - - KWayland::Client::Registry *registry = new KWayland::Client::Registry(q); - registry->create(connection); - - QObject::connect(registry, &KWayland::Client::Registry::plasmaVirtualDesktopManagementAnnounced, - [this, registry] (quint32 name, quint32 version) { - virtualDesktopManagement = registry->createPlasmaVirtualDesktopManagement(name, version, q); - - const QList &desktops = virtualDesktopManagement->desktops(); - - QObject::connect(virtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::desktopCreated, q, - [this](const QString &id, quint32 position) { - addDesktop(id, position); - } - ); - - QObject::connect(virtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::desktopRemoved, q, - [this](const QString &id) { - virtualDesktops.removeOne(id); - - emit numberOfDesktopsChanged(); - emit desktopIdsChanged(); - emit desktopNamesChanged(); - - if (currentVirtualDesktop == id) { - currentVirtualDesktop.clear(); - emit currentDesktopChanged(); - } - } - ); - - QObject::connect(virtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::rowsChanged, - this, &VirtualDesktopInfo::WaylandPrivate::desktopLayoutRowsChanged); - } - ); - - registry->setup(); -} - -void VirtualDesktopInfo::WaylandPrivate::addDesktop(const QString &id, quint32 position) -{ - if (virtualDesktops.indexOf(id) != -1) { - return; - } - - virtualDesktops.insert(position, id); - - emit numberOfDesktopsChanged(); - emit desktopIdsChanged(); - emit desktopNamesChanged(); - - const KWayland::Client::PlasmaVirtualDesktop *desktop = virtualDesktopManagement->getVirtualDesktop(id); - - QObject::connect(desktop, &KWayland::Client::PlasmaVirtualDesktop::activated, q, - [desktop, this]() { - currentVirtualDesktop = desktop->id(); - emit currentDesktopChanged(); - } - ); - - QObject::connect(desktop, &KWayland::Client::PlasmaVirtualDesktop::done, q, - [this]() { - emit desktopNamesChanged(); - } - ); - - if (desktop->isActive()) { - currentVirtualDesktop = id; - emit currentDesktopChanged(); - } -} - -QVariant VirtualDesktopInfo::WaylandPrivate::currentDesktop() const -{ - return currentVirtualDesktop; -} - -int VirtualDesktopInfo::WaylandPrivate::numberOfDesktops() const -{ - return virtualDesktops.count(); -} - -quint32 VirtualDesktopInfo::WaylandPrivate::position(const QVariant &desktop) const -{ - return virtualDesktops.indexOf(desktop.toString()); -} - -QVariantList VirtualDesktopInfo::WaylandPrivate::desktopIds() const -{ - QVariantList ids; - - foreach (const QString &id, virtualDesktops) { - ids << id; - } - - return ids; -} - -QStringList VirtualDesktopInfo::WaylandPrivate::desktopNames() const -{ - QStringList names; - - foreach(const QString &id, virtualDesktops) { - const KWayland::Client::PlasmaVirtualDesktop *desktop = virtualDesktopManagement->getVirtualDesktop(id); - - if (desktop) { - names << desktop->name(); - } - } - - return names; -} - -int VirtualDesktopInfo::WaylandPrivate::desktopLayoutRows() const -{ - return virtualDesktopManagement->rows(); -} - -void VirtualDesktopInfo::WaylandPrivate::requestActivate(const QVariant &desktop) -{ - KWayland::Client::PlasmaVirtualDesktop *desktopObj = virtualDesktopManagement->getVirtualDesktop(desktop.toString()); - - if (desktopObj) { - desktopObj->requestActivate(); - } -} - -void VirtualDesktopInfo::WaylandPrivate::requestCreateDesktop(quint32 position) -{ - virtualDesktopManagement->requestCreateVirtualDesktop(i18n("New Desktop"), position); -} - -void VirtualDesktopInfo::WaylandPrivate::requestRemoveDesktop(quint32 position) -{ - if (virtualDesktops.count() == 1) { - return; - } - - if (position > ((quint32)virtualDesktops.count() - 1)) { - return; - } - - virtualDesktopManagement->requestRemoveVirtualDesktop(virtualDesktops.at(position)); -} - -VirtualDesktopInfo::Private* VirtualDesktopInfo::d = nullptr; - -VirtualDesktopInfo::VirtualDesktopInfo(QObject *parent) : QObject(parent) -{ - if (!d) { - #if HAVE_X11 - if (KWindowSystem::isPlatformX11()) { - d = new VirtualDesktopInfo::XWindowPrivate(this); - } else - #endif - { - d = new VirtualDesktopInfo::WaylandPrivate(this); - } - } else { - ++d->refCount; - } - - connect(d, &VirtualDesktopInfo::Private::currentDesktopChanged, - this, &VirtualDesktopInfo::currentDesktopChanged); - connect(d, &VirtualDesktopInfo::Private::numberOfDesktopsChanged, - this, &VirtualDesktopInfo::numberOfDesktopsChanged); - connect(d, &VirtualDesktopInfo::Private::desktopIdsChanged, - this, &VirtualDesktopInfo::desktopIdsChanged); - connect(d, &VirtualDesktopInfo::Private::desktopNamesChanged, - this, &VirtualDesktopInfo::desktopNamesChanged); - connect(d, &VirtualDesktopInfo::Private::desktopLayoutRowsChanged, - this, &VirtualDesktopInfo::desktopLayoutRowsChanged); -} - -VirtualDesktopInfo::~VirtualDesktopInfo() -{ - --d->refCount; - - if (!d->refCount) { - delete d; - d = nullptr; - } -} - -QVariant VirtualDesktopInfo::currentDesktop() const -{ - return d->currentDesktop(); -} - -int VirtualDesktopInfo::numberOfDesktops() const -{ - return d->numberOfDesktops(); -} - -QVariantList VirtualDesktopInfo::desktopIds() const -{ - return d->desktopIds(); -} - -QStringList VirtualDesktopInfo::desktopNames() const -{ - return d->desktopNames(); -} - -quint32 VirtualDesktopInfo::position(const QVariant &desktop) const -{ - return d->position(desktop); -} - -int VirtualDesktopInfo::desktopLayoutRows() const -{ - return d->desktopLayoutRows(); -} - -void VirtualDesktopInfo::requestActivate(const QVariant &desktop) -{ - d->requestActivate(desktop); -} - -void VirtualDesktopInfo::requestCreateDesktop(quint32 position) -{ - return d->requestCreateDesktop(position); -} - -void VirtualDesktopInfo::requestRemoveDesktop(quint32 position) -{ - return d->requestRemoveDesktop(position); -} - -} - -#include "virtualdesktopinfo.moc" diff --git a/lookandfeel/contents/components/ActionButton.qml b/lookandfeel/contents/components/ActionButton.qml --- a/lookandfeel/contents/components/ActionButton.qml +++ b/lookandfeel/contents/components/ActionButton.qml @@ -17,12 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import QtQuick 2.2 +import QtQuick 2.8 +import QtGraphicalEffects 1.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents Item { id: root + + // If we're using software rendering, draw outlines instead of shadows + // See https://bugs.kde.org/show_bug.cgi?id=398317 + readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software + property alias text: label.text property alias iconSource: icon.source property alias containsMouse: mouseArea.containsMouse @@ -48,6 +54,20 @@ colorGroup: PlasmaCore.ColorScope.colorGroup active: mouseArea.containsMouse || root.activeFocus } + + DropShadow { + id: labelShadow + visible: !softwareRendering + anchors.fill: label + source: label + horizontalOffset: 1 + verticalOffset: 1 + radius: 4 + samples: 9 + spread: 0.35 + color: "black" // the same settings as desktop containment + } // shadow must be rendered before Label, otherwise it ends up on top + PlasmaComponents.Label { id: label anchors { diff --git a/lookandfeel/contents/components/Clock.qml b/lookandfeel/contents/components/Clock.qml deleted file mode 100644 --- a/lookandfeel/contents/components/Clock.qml +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2016 David Edmundson - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -import QtQuick 2.8 -import QtQuick.Layouts 1.1 -import QtGraphicalEffects 1.0 - -import org.kde.plasma.core 2.0 -import org.kde.plasma.components 2.0 - -Item { - // If we're using software rendering, draw outlines instead of shadows - // See https://bugs.kde.org/show_bug.cgi?id=398317 - readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software - - width: clock.implicitWidth - height: clock.implicitHeight - - ColumnLayout { - id: clock - Label { - text: Qt.formatTime(timeSource.data["Local"]["DateTime"]) - style: softwareRendering ? Text.Outline : undefined - styleColor: softwareRendering ? ColorScope.backgroundColor : undefined - font.pointSize: 48 - Layout.alignment: Qt.AlignHCenter - } - Label { - text: Qt.formatDate(timeSource.data["Local"]["DateTime"], Qt.DefaultLocaleLongDate) - style: softwareRendering ? Text.Outline : undefined - styleColor: softwareRendering ? ColorScope.backgroundColor : undefined - font.pointSize: 24 - Layout.alignment: Qt.AlignHCenter - } - DataSource { - id: timeSource - engine: "time" - connectedSources: ["Local"] - interval: 1000 - } - } - - layer.enabled: !softwareRendering - layer.effect: DropShadow { - horizontalOffset: 0 - verticalOffset: 2 - radius: 14 - samples: 32 - spread: 0.3 - color: ColorScope.backgroundColor - } -} diff --git a/lookandfeel/contents/components/UserDelegate.qml b/lookandfeel/contents/components/UserDelegate.qml deleted file mode 100644 --- a/lookandfeel/contents/components/UserDelegate.qml +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2014 David Edmundson - * Copyright 2014 Aleix Pol Gonzalez - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -import QtQuick 2.8 -import QtGraphicalEffects 1.0 - -import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 2.0 as PlasmaComponents - -Item { - id: wrapper - - // If we're using software rendering, draw outlines instead of shadows - // See https://bugs.kde.org/show_bug.cgi?id=398317 - readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software - - property bool isCurrent: true - - readonly property var m: model - property string name - property string userName - property string avatarPath - property string iconSource - property bool constrainText: true - signal clicked() - - property real faceSize: Math.min(width, height - usernameDelegate.height - units.largeSpacing) - - opacity: isCurrent ? 1.0 : 0.5 - - Behavior on opacity { - OpacityAnimator { - duration: units.longDuration - } - } - - // Draw a translucent background circle under the user picture - Rectangle { - anchors.centerIn: imageSource - - width: imageSource.width + 2 - height: width - radius: width / 2 - - color: PlasmaCore.ColorScope.backgroundColor - opacity: 0.9 - } - - Item { - id: imageSource - anchors.horizontalCenter: parent.horizontalCenter - width: faceSize - height: faceSize - - //Image takes priority, taking a full path to a file, if that doesn't exist we show an icon - Image { - id: face - source: wrapper.avatarPath - sourceSize: Qt.size(faceSize, faceSize) - fillMode: Image.PreserveAspectCrop - anchors.fill: parent - } - - PlasmaCore.IconItem { - id: faceIcon - source: iconSource - visible: (face.status == Image.Error || face.status == Image.Null) - anchors.fill: parent - anchors.margins: units.gridUnit * 0.5 // because mockup says so... - colorGroup: PlasmaCore.ColorScope.colorGroup - } - } - - ShaderEffect { - anchors.top: parent.top - anchors.horizontalCenter: parent.horizontalCenter - - width: imageSource.width - height: imageSource.height - - supportsAtlasTextures: true - - property var source: ShaderEffectSource { - sourceItem: imageSource - // software rendering is just a fallback so we can accept not having a rounded avatar here - hideSource: wrapper.GraphicsInfo.api !== GraphicsInfo.Software - live: false - } - - property var colorBorder: PlasmaCore.ColorScope.textColor - - //draw a circle with an antialised border - //innerRadius = size of the inner circle with contents - //outerRadius = size of the border - //blend = area to blend between two colours - //all sizes are normalised so 0.5 == half the width of the texture - - //if copying into another project don't forget to connect themeChanged to update() - //but in SDDM that's a bit pointless - fragmentShader: " - varying highp vec2 qt_TexCoord0; - uniform highp float qt_Opacity; - uniform lowp sampler2D source; - - uniform lowp vec4 colorBorder; - highp float blend = 0.01; - highp float innerRadius = 0.47; - highp float outerRadius = 0.49; - lowp vec4 colorEmpty = vec4(0.0, 0.0, 0.0, 0.0); - - void main() { - lowp vec4 colorSource = texture2D(source, qt_TexCoord0.st); - - highp vec2 m = qt_TexCoord0 - vec2(0.5, 0.5); - highp float dist = sqrt(m.x * m.x + m.y * m.y); - - if (dist < innerRadius) - gl_FragColor = colorSource; - else if (dist < innerRadius + blend) - gl_FragColor = mix(colorSource, colorBorder, ((dist - innerRadius) / blend)); - else if (dist < outerRadius) - gl_FragColor = colorBorder; - else if (dist < outerRadius + blend) - gl_FragColor = mix(colorBorder, colorEmpty, ((dist - outerRadius) / blend)); - else - gl_FragColor = colorEmpty ; - - gl_FragColor = gl_FragColor * qt_Opacity; - } - " - } - - PlasmaComponents.Label { - id: usernameDelegate - anchors { - bottom: parent.bottom - horizontalCenter: parent.horizontalCenter - } - height: implicitHeight // work around stupid bug in Plasma Components that sets the height - width: constrainText ? parent.width : implicitWidth - text: wrapper.name - style: softwareRendering ? Text.Outline : undefined - styleColor: softwareRendering ? ColorScope.backgroundColor : undefined - elide: Text.ElideRight - horizontalAlignment: Text.AlignHCenter - //make an indication that this has active focus, this only happens when reached with keyboard navigation - font.underline: wrapper.activeFocus - - layer.enabled: !softwareRendering - layer.effect: DropShadow { - horizontalOffset: 0 - verticalOffset: 1 - radius: 12 - samples: 32 - spread: 0.35 - color: ColorScope.backgroundColor - } - } - - MouseArea { - anchors.fill: parent - hoverEnabled: true - - onClicked: wrapper.clicked(); - } - - Accessible.name: name - Accessible.role: Accessible.Button - function accessiblePressAction() { wrapper.clicked() } -} diff --git a/shell/shellcorona.cpp b/shell/shellcorona.cpp deleted file mode 100644 --- a/shell/shellcorona.cpp +++ /dev/null @@ -1,2119 +0,0 @@ -/* - * Copyright 2008 Aaron Seigo - * Copyright 2013 Sebastian Kügler - * Copyright 2013 Ivan Cukic - * Copyright 2013 Marco Martin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "shellcorona.h" - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include "config-ktexteditor.h" // HAVE_KTEXTEDITOR - -#include "alternativeshelper.h" -#include "desktopview.h" -#include "panelview.h" -#include "scripting/scriptengine.h" -#include "shellmanager.h" -#include "osd.h" -#include "screenpool.h" - -#include "plasmashelladaptor.h" -#include "debug.h" -#include "futureutil.h" - -#ifndef NDEBUG - #define CHECK_SCREEN_INVARIANTS screenInvariants(); -#else - #define CHECK_SCREEN_INVARIANTS -#endif - -#if HAVE_X11 -#include -#include -#include -#endif - - -static const int s_configSyncDelay = 10000; // 10 seconds - -ShellCorona::ShellCorona(QObject *parent) - : Plasma::Corona(parent), - m_config(KSharedConfig::openConfig(QStringLiteral("plasmarc"))), - m_screenPool(new ScreenPool(KSharedConfig::openConfig(), this)), - m_activityController(new KActivities::Controller(this)), - m_addPanelAction(nullptr), - m_addPanelsMenu(nullptr), - m_interactiveConsole(nullptr), - m_waylandPlasmaShell(nullptr), - m_closingDown(false) -{ - setupWaylandIntegration(); - qmlRegisterUncreatableType("org.kde.plasma.shell", 2, 0, "Desktop", QStringLiteral("It is not possible to create objects of type Desktop")); - qmlRegisterUncreatableType("org.kde.plasma.shell", 2, 0, "Panel", QStringLiteral("It is not possible to create objects of type Panel")); - - m_lookAndFeelPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); - KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), "KDE"); - const QString packageName = cg.readEntry("LookAndFeelPackage", QString()); - if (!packageName.isEmpty()) { - m_lookAndFeelPackage.setPath(packageName); - } - - connect(this, &Plasma::Corona::containmentCreated, this, [this] (Plasma::Containment *c) { - executeSetupPlasmoidScript(c, c); - }); - - connect(this, &Plasma::Corona::availableScreenRectChanged, this, &Plasma::Corona::availableScreenRegionChanged); - - m_appConfigSyncTimer.setSingleShot(true); - m_appConfigSyncTimer.setInterval(s_configSyncDelay); - connect(&m_appConfigSyncTimer, &QTimer::timeout, this, &ShellCorona::syncAppConfig); - //we want our application config with screen mapping to always be in sync with the applets one, so a crash at any time will still - //leave containments pointing to the correct screens - connect(this, &Corona::configSynced, this, &ShellCorona::syncAppConfig); - - m_waitingPanelsTimer.setSingleShot(true); - m_waitingPanelsTimer.setInterval(250); - connect(&m_waitingPanelsTimer, &QTimer::timeout, this, &ShellCorona::createWaitingPanels); - - m_reconsiderOutputsTimer.setSingleShot(true); - m_reconsiderOutputsTimer.setInterval(1000); - connect(&m_reconsiderOutputsTimer, &QTimer::timeout, this, &ShellCorona::reconsiderOutputs); - - m_desktopDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(package().filePath("defaults")), "Desktop"); - m_lnfDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(m_lookAndFeelPackage.filePath("defaults")), "Desktop"); - m_lnfDefaultsConfig = KConfigGroup(&m_lnfDefaultsConfig, QStringLiteral("org.kde.plasma.desktop")); - - new PlasmaShellAdaptor(this); - - QDBusConnection dbus = QDBusConnection::sessionBus(); - dbus.registerObject(QStringLiteral("/PlasmaShell"), this); - - connect(this, &Plasma::Corona::startupCompleted, 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 - // LookAndFeel package, if either is set, change the default theme - - connect(qApp, &QCoreApplication::aboutToQuit, this, [this]() { - //saveLayout is a slot but arguments not compatible - m_closingDown = true; - saveLayout(); - }); - - connect(this, &ShellCorona::containmentAdded, - this, &ShellCorona::handleContainmentAdded); - - QAction *dashboardAction = actions()->addAction(QStringLiteral("show dashboard")); - QObject::connect(dashboardAction, &QAction::triggered, - this, &ShellCorona::setDashboardShown); - dashboardAction->setText(i18n("Show Desktop")); - connect(KWindowSystem::self(), &KWindowSystem::showingDesktopChanged, [dashboardAction](bool showing) { - dashboardAction->setText(showing ? i18n("Hide Desktop") : i18n("Show Desktop")); - dashboardAction->setChecked(showing); - }); - - dashboardAction->setAutoRepeat(true); - dashboardAction->setCheckable(true); - dashboardAction->setIcon(QIcon::fromTheme(QStringLiteral("dashboard-show"))); - dashboardAction->setData(Plasma::Types::ControlAction); - KGlobalAccel::self()->setGlobalShortcut(dashboardAction, Qt::CTRL + Qt::Key_F12); - - checkAddPanelAction(); - connect(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), this, SLOT(checkAddPanelAction(QStringList))); - - - //Activity stuff - QAction *activityAction = actions()->addAction(QStringLiteral("manage activities")); - connect(activityAction, &QAction::triggered, - this, &ShellCorona::toggleActivityManager); - activityAction->setText(i18n("Activities...")); - activityAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-activities"))); - activityAction->setData(Plasma::Types::ConfigureAction); - activityAction->setShortcut(QKeySequence(QStringLiteral("alt+d, alt+a"))); - activityAction->setShortcutContext(Qt::ApplicationShortcut); - - KGlobalAccel::self()->setGlobalShortcut(activityAction, Qt::META + Qt::Key_Q); - - QAction *stopActivityAction = actions()->addAction(QStringLiteral("stop current activity")); - QObject::connect(stopActivityAction, &QAction::triggered, - this, &ShellCorona::stopCurrentActivity); - - stopActivityAction->setText(i18n("Stop Current Activity")); - stopActivityAction->setData(Plasma::Types::ControlAction); - stopActivityAction->setVisible(false); - - - KGlobalAccel::self()->setGlobalShortcut(stopActivityAction, Qt::META + Qt::Key_S); - - connect(m_activityController, &KActivities::Controller::currentActivityChanged, this, &ShellCorona::currentActivityChanged); - connect(m_activityController, &KActivities::Controller::activityAdded, this, &ShellCorona::activityAdded); - connect(m_activityController, &KActivities::Controller::activityRemoved, this, &ShellCorona::activityRemoved); - - KActionCollection *taskbarActions = new KActionCollection(this); - for (int i = 0; i < 10; ++i) { - const int entryNumber = i + 1; - const Qt::Key key = static_cast(Qt::Key_0 + (entryNumber % 10)); - - QAction *action = taskbarActions->addAction(QStringLiteral("activate task manager entry %1").arg(QString::number(entryNumber))); - action->setText(i18n("Activate Task Manager Entry %1", entryNumber)); - KGlobalAccel::setGlobalShortcut(action, QKeySequence(Qt::META + key)); - connect(action, &QAction::triggered, this, [this, i] { - activateTaskManagerEntry(i); - }); - } - - new Osd(m_config, this); - - // catch when plasmarc changes, so we e.g. enable/disable the OSd - m_configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + m_config->name(); - KDirWatch::self()->addFile(m_configPath); - connect(KDirWatch::self(), &KDirWatch::dirty, this, &ShellCorona::configurationChanged); - connect(KDirWatch::self(), &KDirWatch::created, this, &ShellCorona::configurationChanged); -} - -ShellCorona::~ShellCorona() -{ - while (!containments().isEmpty()) { - //deleting a containment will remove it from the list due to QObject::destroyed connect in Corona - delete containments().first(); - } - qDeleteAll(m_panelViews); - m_panelViews.clear(); -} - -KPackage::Package ShellCorona::lookAndFeelPackage() -{ - return m_lookAndFeelPackage; -} - -void ShellCorona::setShell(const QString &shell) -{ - if (m_shell == shell) { - return; - } - - m_shell = shell; - KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Shell")); - package.setPath(shell); - package.setAllowExternalPaths(true); - setKPackage(package); - m_desktopDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(package.filePath("defaults")), "Desktop"); - m_lnfDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(m_lookAndFeelPackage.filePath("defaults")), "Desktop"); - m_lnfDefaultsConfig = KConfigGroup(&m_lnfDefaultsConfig, shell); - - const QString themeGroupKey = QStringLiteral("Theme"); - const QString themeNameKey = QStringLiteral("name"); - - QString themeName; - - KConfigGroup plasmarc(m_config, themeGroupKey); - themeName = plasmarc.readEntry(themeNameKey, themeName); - - if (themeName.isEmpty()) { - KConfigGroup shellCfg = KConfigGroup(KSharedConfig::openConfig(package.filePath("defaults")), "Theme"); - - themeName = shellCfg.readEntry("name", "default"); - KConfigGroup lnfCfg = KConfigGroup(KSharedConfig::openConfig( - m_lookAndFeelPackage.filePath("defaults")), - "plasmarc" - ); - lnfCfg = KConfigGroup(&lnfCfg, themeGroupKey); - themeName = lnfCfg.readEntry(themeNameKey, themeName); - } - - if (!themeName.isEmpty()) { - Plasma::Theme *t = new Plasma::Theme(this); - t->setThemeName(themeName); - } - - //FIXME: this would change the runtime platform to a fixed one if available - // but a different way to load platform specific components is needed beforehand - // because if we import and use two different components plugin, the second time - // the import is called it will fail - /* KConfigGroup cg(KSharedConfig::openConfig(package.filePath("defaults")), "General"); - KDeclarative::KDeclarative::setRuntimePlatform(cg.readEntry("DefaultRuntimePlatform", QStringList()));*/ - - unload(); - - - /* - * we want to make an initial load once we have the initial screen config and we have loaded the activities _IF_ KAMD is running - * it is valid for KAMD to not be running. - * - * Potentially 2 async jobs - * - * here we connect for status changes from KAMD, and fetch the first config from kscreen. - * load() will check that we have a kscreen config, and m_activityController->serviceStatus() is not loading (i.e not unknown) - * - * It might seem that we only need this connection if the activityConsumer is currently in state Unknown, however - * there is an issue where m_activityController will start the kactivitymanagerd, as KAMD is starting the serviceStatus will be "not running" - * Whilst we are loading the kscreen config, the event loop runs and we might find KAMD has started. - * m_activityController will change from "not running" to unknown, and might still be unknown when the kscreen fetching is complete. - * - * if that happens we want to continue monitoring for state changes, and only finally load when it is up. - * - * See https://bugs.kde.org/show_bug.cgi?id=342431 be careful about changing - * - * The unique connection makes sure we don't reload plasma if KAMD ever crashes and reloads, the signal is disconnected in the body of load - */ - - connect(m_activityController, &KActivities::Controller::serviceStatusChanged, this, &ShellCorona::load, Qt::UniqueConnection); - - load(); -} - - -QJsonObject dumpconfigGroupJS(const KConfigGroup &rootGroup) -{ - QJsonObject result; - - QStringList hierarchy; - QStringList escapedHierarchy; - QList groups{rootGroup}; - QSet visitedNodes; - - const QSet forbiddenKeys { - QStringLiteral("activityId"), - QStringLiteral("ItemsGeometries"), - QStringLiteral("AppletOrder"), - QStringLiteral("SystrayContainmentId"), - QStringLiteral("location"), - QStringLiteral("plugin") - }; - - auto groupID = [&escapedHierarchy]() { - return '/' + escapedHierarchy.join('/'); - }; - - // 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(); - escapedHierarchy.clear(); - while (parentCg.isValid() && parentCg.name() != rootGroup.name()) { - const auto name = parentCg.name(); - hierarchy.prepend(name); - escapedHierarchy.prepend(QString::fromUtf8(QUrl::toPercentEncoding(name.toUtf8()))); - parentCg = parentCg.parent(); - } - - visitedNodes.insert(groupID()); - groups.pop_back(); - - QJsonObject configGroupJson; - - if (!cg.keyList().isEmpty()) { - //TODO: this is conditional if applet or containment - - 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(groupID() + '/' + groupName)) { - continue; - } - groups << KConfigGroup(&cg, groupName); - } - - if (!configGroupJson.isEmpty()) { - result.insert(groupID(), 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->pluginMetaData().pluginId() != 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 height = - // If we do not have a panel, fallback to 4 units - !view ? 4 - : (qreal)view->thickness() / gridUnit; - - panelJson.insert("height", height); - if (view) { - const auto alignment = view->alignment(); - panelJson.insert("maximumLength", (qreal)view->maximumLength() / gridUnit); - panelJson.insert("minimumLength", (qreal)view->minimumLength() / gridUnit); - panelJson.insert("offset", (qreal)view->offset() / gridUnit); - panelJson.insert("alignment", - alignment == Qt::AlignRight ? "right" - : alignment == Qt::AlignCenter ? "center" - : "left"); - switch (view->visibilityMode()) { - case PanelView::AutoHide: - panelJson.insert("hiding", "autohide"); - break; - case PanelView::LetWindowsCover: - panelJson.insert("hiding", "windowscover"); - break; - case PanelView::WindowsGoBelow: - panelJson.insert("hiding", "windowsbelow"); - break; - case PanelView::NormalPanel: - default: - panelJson.insert("hiding", "normal"); - break; - } - } - - // 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->pluginMetaData().pluginId()); - 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->pluginMetaData().pluginId()); - - 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; -} - -void ShellCorona::load() -{ - if (m_shell.isEmpty()) { - return; - } - - if (m_activityController->serviceStatus() != KActivities::Controller::Running && - !qApp->property("org.kde.KActivities.core.disableAutostart").toBool()) { - qWarning("Aborting shell load: The activity manager daemon (kactivitymanagerd) is not running."); - qWarning("If this Plasma has been installed into a custom prefix, verify that its D-Bus services dir is known to the system for the daemon to be activatable."); - return; - } - - disconnect(m_activityController, &KActivities::Controller::serviceStatusChanged, this, &ShellCorona::load); - - m_screenPool->load(); - - //TODO: a kconf_update script is needed - QString configFileName(QStringLiteral("plasma-") + m_shell + QStringLiteral("-appletsrc")); - - loadLayout(configFileName); - - checkActivities(); - - if (containments().isEmpty()) { - // Seems like we never really get to this point since loadLayout already - // (virtually) calls loadDefaultLayout if it does not load anything - // from the config file. Maybe if the config file is not empty, - // but still does not have any containments - loadDefaultLayout(); - processUpdateScripts(); - } else { - processUpdateScripts(); - foreach(Plasma::Containment *containment, containments()) { - if (containment->containmentType() == Plasma::Types::PanelContainment || containment->containmentType() == Plasma::Types::CustomPanelContainment) { - //Don't give a view to containments that don't want one (negative lastscreen) - //(this is pretty mucha special case for the systray) - //also, make sure we don't have a view already. - //this will be true for first startup as the view has already been created at the new Panel JS call - if (!m_waitingPanels.contains(containment) && containment->lastScreen() >= 0 && !m_panelViews.contains(containment)) { - m_waitingPanels << containment; - } - //historically CustomContainments are treated as desktops - } else if (containment->containmentType() == Plasma::Types::DesktopContainment || containment->containmentType() == Plasma::Types::CustomContainment) { - //FIXME ideally fix this, or at least document the crap out of it - int screen = containment->lastScreen(); - if (screen < 0) { - screen = 0; - qWarning() << "last screen is < 0 so putting containment on screen " << screen; - } - insertContainment(containment->activity(), screen, containment); - } - } - } - - //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()) { - //the containments may have been created already by the startup script - //check their existence in order to not have duplicated desktopviews - if (!m_desktopViewforId.contains(m_screenPool->id(screen->name()))) { - addOutput(screen); - } - } - connect(qGuiApp, &QGuiApplication::screenAdded, this, &ShellCorona::addOutput, Qt::UniqueConnection); - connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &ShellCorona::primaryOutputChanged, Qt::UniqueConnection); - connect(qGuiApp, &QGuiApplication::screenRemoved, this, &ShellCorona::handleScreenRemoved, Qt::UniqueConnection); - - if (!m_waitingPanels.isEmpty()) { - m_waitingPanelsTimer.start(); - } - - if (config()->isImmutable() || - !KAuthorized::authorize(QStringLiteral("plasma/plasmashell/unlockedDesktop"))) { - setImmutability(Plasma::Types::SystemImmutable); - } else { - KConfigGroup coronaConfig(config(), "General"); - setImmutability((Plasma::Types::ImmutabilityType)coronaConfig.readEntry("immutability", static_cast(Plasma::Types::Mutable))); - } -} - -void ShellCorona::primaryOutputChanged() -{ - if (!m_desktopViewforId.contains(0)) { - return; - } - - //when the appearance of a new primary screen *moves* - //the position of the now secondary, the two screens will appear overlapped for an instant, and a spurious output redundant would happen here if checked immediately - m_reconsiderOutputsTimer.start(); - - QScreen *oldPrimary = m_desktopViewforId.value(0)->screen(); - QScreen *newPrimary = qGuiApp->primaryScreen(); - if (!newPrimary || newPrimary == oldPrimary) { - return; - } - - qWarning()<<"Old primary output:"<id(newPrimary->name()); - m_screenPool->setPrimaryConnector(newPrimary->name()); - //swap order in m_desktopViewforId - if (m_desktopViewforId.contains(0) && m_desktopViewforId.contains(oldIdOfPrimary)) { - DesktopView *primaryDesktop = m_desktopViewforId.value(0); - DesktopView *oldDesktopOfPrimary = m_desktopViewforId.value(oldIdOfPrimary); - - primaryDesktop->setScreenToFollow(newPrimary); - oldDesktopOfPrimary->setScreenToFollow(oldPrimary); - primaryDesktop->show(); - oldDesktopOfPrimary->show(); - //corner case: the new primary screen was added into redundant outputs when appeared, *and* !m_desktopViewforId.contains(oldIdOfPrimary) - //meaning that we had only one screen, connected a new oone that - //a) is now primary and - //b) is at 0,0 position, moving the current screen out of the way - // and this will always happen in two events - } else if (m_desktopViewforId.contains(0) && m_redundantOutputs.contains(newPrimary)) { - m_desktopViewforId[0]->setScreenToFollow(newPrimary); - m_redundantOutputs.remove(newPrimary); - m_redundantOutputs.insert(oldPrimary); - } - - foreach (PanelView *panel, m_panelViews) { - if (panel->screen() == oldPrimary) { - panel->setScreenToFollow(newPrimary); - } else if (panel->screen() == newPrimary) { - panel->setScreenToFollow(oldPrimary); - } - } - - //can't do the screen invariant here as reconsideroutputs wasn't executed yet - //CHECK_SCREEN_INVARIANTS -} - -#ifndef NDEBUG -void ShellCorona::screenInvariants() const -{ - Q_ASSERT(m_desktopViewforId.keys().count() <= QGuiApplication::screens().count()); - - QSet screens; - foreach (const int id, m_desktopViewforId.keys()) { - const DesktopView *view = m_desktopViewforId.value(id); - QScreen *screen = view->screenToFollow(); - Q_ASSERT(!screens.contains(screen)); - Q_ASSERT(!m_redundantOutputs.contains(screen)); -// commented out because a different part of the code-base is responsible for this -// and sometimes is not yet called here. -// Q_ASSERT(!view->fillScreen() || view->geometry() == screen->geometry()); - Q_ASSERT(view->containment()); - - Q_ASSERT(view->containment()->screen() == id || view->containment()->screen() == -1); - Q_ASSERT(view->containment()->lastScreen() == id || view->containment()->lastScreen() == -1); - Q_ASSERT(view->isVisible()); - - foreach (const PanelView *panel, panelsForScreen(screen)) { - Q_ASSERT(panel->containment()); - Q_ASSERT(panel->containment()->screen() == id || panel->containment()->screen() == -1); - //If any kscreen related activities occurred - //during startup, the panel wouldn't be visible yet, and this would assert - if (panel->containment()->isUiReady()) { - Q_ASSERT(panel->isVisible()); - } - } - - screens.insert(screen); - } - - foreach (QScreen* out, m_redundantOutputs) { - Q_ASSERT(isOutputRedundant(out)); - } - - if (m_desktopViewforId.isEmpty()) { - qWarning() << "no screens!!"; - } -} -#endif - -void ShellCorona::showAlternativesForApplet(Plasma::Applet *applet) -{ - const QUrl alternativesQML = kPackage().fileUrl("appletalternativesui"); - if (alternativesQML.isEmpty()) { - return; - } - - auto *qmlObj = new KDeclarative::QmlObjectSharedEngine(this); - qmlObj->setInitializationDelayed(true); - qmlObj->setSource(alternativesQML); - - AlternativesHelper *helper = new AlternativesHelper(applet, qmlObj); - qmlObj->rootContext()->setContextProperty(QStringLiteral("alternativesHelper"), helper); - - qmlObj->completeInitialization(); - - auto dialog = qobject_cast(qmlObj->rootObject()); - if (!dialog) { - qWarning() << "Alternatives UI does not inherit from Dialog"; - delete qmlObj; - return; - } - connect(applet, &Plasma::Applet::destroyedChanged, qmlObj, [qmlObj] (bool destroyed) { - if (!destroyed) { - return; - } - qmlObj->deleteLater(); - }); - connect(dialog, &PlasmaQuick::Dialog::visibleChanged, qmlObj, [qmlObj](bool visible) { - if (visible) { - return; - } - qmlObj->deleteLater(); - }); -} - -void ShellCorona::unload() -{ - if (m_shell.isEmpty()) { - return; - } - qDeleteAll(m_desktopViewforId); - m_desktopViewforId.clear(); - qDeleteAll(m_panelViews); - m_panelViews.clear(); - m_waitingPanels.clear(); - m_activityContainmentPlugins.clear(); - - 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() -{ - return KSharedConfig::openConfig(); -} - -void ShellCorona::requestApplicationConfigSync() -{ - m_appConfigSyncTimer.start(); -} - -void ShellCorona::loadDefaultLayout() -{ - //pre-startup scripts - QString script = m_lookAndFeelPackage.filePath("layouts", QString(shell() + "-prelayout.js").toLatin1()); - if (!script.isEmpty()) { - QFile file(script); - if (file.open(QIODevice::ReadOnly | QIODevice::Text) ) { - QString code = file.readAll(); - qDebug() << "evaluating pre-startup script:" << script; - - WorkspaceScripting::ScriptEngine scriptEngine(this); - - connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, - [](const QString &msg) { - qWarning() << msg; - }); - connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, - [](const QString &msg) { - qDebug() << msg; - }); - if (!scriptEngine.evaluateScript(code, script)) { - qWarning() << "failed to initialize layout properly:" << script; - } - } - } - - //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); - } - - script = ShellManager::s_testModeLayout; - - if (script.isEmpty()) { - script = m_lookAndFeelPackage.filePath("layouts", QString(shell() + "-layout.js").toLatin1()); - } - if (script.isEmpty()) { - script = package().filePath("defaultlayout"); - } - - QFile file(script); - if (file.open(QIODevice::ReadOnly | QIODevice::Text) ) { - QString code = file.readAll(); - qDebug() << "evaluating startup script:" << script; - - // We need to know which activities are here in order for - // the scripting engine to work. activityAdded does not mind - // if we pass it the same activity multiple times - QStringList existingActivities = m_activityController->activities(); - foreach (const QString &id, existingActivities) { - activityAdded(id); - } - - WorkspaceScripting::ScriptEngine scriptEngine(this); - - connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, - [](const QString &msg) { - qWarning() << msg; - }); - connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, - [](const QString &msg) { - qDebug() << msg; - }); - if (!scriptEngine.evaluateScript(code, script)) { - qWarning() << "failed to initialize layout properly:" << script; - } - } - - Q_EMIT startupCompleted(); -} - -void ShellCorona::processUpdateScripts() -{ - const QStringList scripts = WorkspaceScripting::ScriptEngine::pendingUpdateScripts(this); - if (scripts.isEmpty()) { - return; - } - - WorkspaceScripting::ScriptEngine scriptEngine(this); - - connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, - [](const QString &msg) { - qWarning() << msg; - }); - connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, - [](const QString &msg) { - qDebug() << msg; - }); - - for (const QString &script : scripts) { - QFile file(script); - if (file.open(QIODevice::ReadOnly | QIODevice::Text) ) { - QString code = file.readAll(); - scriptEngine.evaluateScript(code); - } else { - qWarning() << "Unable to open the script file" << script << "for reading"; - } - } -} - -int ShellCorona::numScreens() const -{ - return qGuiApp->screens().count(); -} - -QRect ShellCorona::screenGeometry(int id) const -{ - if (!m_desktopViewforId.contains(id)) { - qWarning() << "requesting unexisting screen" << id; - QScreen *s = qGuiApp->primaryScreen(); - return s ? s->geometry() : QRect(); - } - return m_desktopViewforId.value(id)->geometry(); -} - -QRegion ShellCorona::availableScreenRegion(int id) const -{ - if (!m_desktopViewforId.contains(id)) { - //each screen should have a view - qWarning() << "requesting unexisting screen" << id; - QScreen *s = qGuiApp->primaryScreen(); - return s ? s->availableGeometry() : QRegion(); - } - DesktopView *view = m_desktopViewforId.value(id); - - QRegion r = view->geometry(); - foreach (const PanelView *v, m_panelViews) { - if (v->isVisible() && view->screen() == v->screen() && v->visibilityMode() != PanelView::AutoHide) { - //if the panel is being moved around, we still want to calculate it from the edge - r -= v->geometryByDistance(0); - } - } - return r; -} - -QRect ShellCorona::availableScreenRect(int id) const -{ - if (!m_desktopViewforId.contains(id)) { - //each screen should have a view - qWarning() << "requesting unexisting screen" << id; - QScreen *s = qGuiApp->primaryScreen(); - return s ? s->availableGeometry() : QRect(); - } - - DesktopView *view = m_desktopViewforId.value(id); - - QRect r = view->geometry(); - foreach (PanelView *v, m_panelViews) { - if (v->isVisible() && v->screen() == view->screen() && v->visibilityMode() != PanelView::AutoHide) { - switch (v->location()) { - case Plasma::Types::LeftEdge: - r.setLeft(r.left() + v->thickness()); - break; - case Plasma::Types::RightEdge: - r.setRight(r.right() - v->thickness()); - break; - case Plasma::Types::TopEdge: - r.setTop(r.top() + v->thickness()); - break; - case Plasma::Types::BottomEdge: - r.setBottom(r.bottom() - v->thickness()); - default: - break; - } - } - } - return r; -} - -QStringList ShellCorona::availableActivities() const -{ - return m_activityContainmentPlugins.keys(); -} - -void ShellCorona::removeDesktop(DesktopView *desktopView) -{ - const int idx = m_screenPool->id(desktopView->screenToFollow()->name()); - - if (!m_desktopViewforId.contains(idx)) { - return; - } - - QMutableHashIterator it(m_panelViews); - while (it.hasNext()) { - it.next(); - PanelView *panelView = it.value(); - - if (panelView->containment()->screen() == idx) { - m_waitingPanels << panelView->containment(); - it.remove(); - delete panelView; - } - } - - Q_ASSERT(m_desktopViewforId.value(idx) == desktopView); - m_desktopViewforId.remove(idx); - delete desktopView; - - emit screenRemoved(idx); -} - -PanelView *ShellCorona::panelView(Plasma::Containment *containment) const -{ - return m_panelViews.value(containment); -} - -///// SLOTS - -QList ShellCorona::panelsForScreen(QScreen *screen) const -{ - QList ret; - foreach (PanelView *v, m_panelViews) { - if (v->screenToFollow() == screen) { - ret += v; - } - } - return ret; -} - -DesktopView* ShellCorona::desktopForScreen(QScreen* screen) const -{ - return m_desktopViewforId.value(m_screenPool->id(screen->name())); -} - -void ShellCorona::handleScreenRemoved(QScreen* screen) -{ - if (DesktopView* v = desktopForScreen(screen)) { - removeDesktop(v); - } - - m_reconsiderOutputsTimer.start(); - m_redundantOutputs.remove(screen); -} - -bool ShellCorona::isOutputRedundant(QScreen* screen) const -{ - Q_ASSERT(screen); - const QRect thisGeometry = screen->geometry(); - - const int thisId = m_screenPool->id(screen->name()); - - //FIXME: QScreen doesn't have any idea of "this qscreen is clone of this other one - //so this ultra inefficient heuristic has to stay until we have a slightly better api - //logic is: - //a screen is redundant if: - //* its geometry is contained in another one - //* if their resolutions are different, the "biggest" one wins - //* if they have the same geometry, the one with the lowest id wins (arbitrary, but gives reproducible behavior and makes the primary screen win) - foreach (QScreen* s, qGuiApp->screens()) { - //don't compare with itself - if (screen == s) { - continue; - } - - const QRect otherGeometry = s->geometry(); - - const int otherId = m_screenPool->id(s->name()); - - if (otherGeometry.contains(thisGeometry, false) && - (//since at this point contains is true, if either - //measure of othergeometry is bigger, has a bigger area - otherGeometry.width() > thisGeometry.width() || - otherGeometry.height() > thisGeometry.height() || - //ids not -1 are considered in descending order of importance - //-1 means that is a screen not known yet, just arrived and - //not yet in screenpool: this happens for screens that - //are hotplugged and weren't known. it does NOT happen - //at first startup, as screenpool populates on load with all screens connected at the moment before the rest of the shell starts up - (thisId == -1 && otherId != -1) || - (thisId > otherId && otherId != -1))) { - return true; - } - } - - return false; -} - -void ShellCorona::reconsiderOutputs() -{ - foreach (QScreen* screen, qGuiApp->screens()) { - if (m_redundantOutputs.contains(screen)) { - if (!isOutputRedundant(screen)) { - //qDebug() << "not redundant anymore" << screen; - addOutput(screen); - } - } else if (isOutputRedundant(screen)) { - qDebug() << "new redundant screen" << screen << "with primary screen" << qGuiApp->primaryScreen(); - - if (DesktopView* v = desktopForScreen(screen)) - removeDesktop(v); - - m_redundantOutputs.insert(screen); - } -// else -// qDebug() << "fine screen" << out; - } - - updateStruts(); - - CHECK_SCREEN_INVARIANTS -} - -void ShellCorona::addOutput(QScreen* screen) -{ - Q_ASSERT(screen); - connect(screen, &QScreen::geometryChanged, - &m_reconsiderOutputsTimer, static_cast(&QTimer::start), - Qt::UniqueConnection); - - if (isOutputRedundant(screen)) { - m_redundantOutputs.insert(screen); - return; - } else { - m_redundantOutputs.remove(screen); - } - - int insertPosition = m_screenPool->id(screen->name()); - if (insertPosition < 0) { - insertPosition = m_screenPool->firstAvailableId(); - } - - DesktopView *view = new DesktopView(this, screen); - - if (view->rendererInterface()->graphicsApi() != QSGRendererInterface::Software) { - connect(view, &QQuickWindow::sceneGraphError, this, &ShellCorona::glInitializationFailed); - } - connect(screen, &QScreen::geometryChanged, this, [=]() { - const int id = m_screenPool->id(screen->name()); - if (id >= 0) { - emit screenGeometryChanged(id); - emit availableScreenRegionChanged(); - emit availableScreenRectChanged(); - } - }); - - Plasma::Containment *containment = createContainmentForActivity(m_activityController->currentActivity(), insertPosition); - Q_ASSERT(containment); - - QAction *removeAction = containment->actions()->action(QStringLiteral("remove")); - if (removeAction) { - removeAction->deleteLater(); - } - - m_screenPool->insertScreenMapping(insertPosition, screen->name()); - m_desktopViewforId[insertPosition] = view; - view->setContainment(containment); - view->show(); - Q_ASSERT(screen == view->screen()); - - //need to specifically call the reactToScreenChange, since when the screen is shown it's not yet - //in the list. We still don't want to have an invisible view added. - containment->reactToScreenChange(); - - //were there any panels for this screen before it popped up? - if (!m_waitingPanels.isEmpty()) { - m_waitingPanelsTimer.start(); - } - - emit availableScreenRectChanged(); - emit screenAdded(m_screenPool->id(screen->name())); - - CHECK_SCREEN_INVARIANTS -} - -Plasma::Containment *ShellCorona::createContainmentForActivity(const QString& activity, int screenNum) -{ - for (Plasma::Containment *cont : containmentsForActivity(activity)) { - //in the case of a corrupt config file - //with multiple containments with same lastScreen - //it can happen two insertContainment happen for - //the same screen, leading to the old containment - //to be destroyed - if (!cont->destroyed() && cont->screen() == screenNum) { - return cont; - } - } - - QString plugin = m_activityContainmentPlugins.value(activity); - - if (plugin.isEmpty()) { - plugin = defaultContainmentPlugin(); - } - - Plasma::Containment *containment = containmentForScreen(screenNum, activity, plugin, QVariantList()); - Q_ASSERT(containment); - - if (containment) { - insertContainment(activity, screenNum, containment); - } - - return containment; -} - -void ShellCorona::createWaitingPanels() -{ - QList stillWaitingPanels; - - foreach (Plasma::Containment *cont, m_waitingPanels) { - //ignore non existing (yet?) screens - int requestedScreen = cont->lastScreen(); - if (requestedScreen < 0) { - requestedScreen = 0; - } - - if (!m_desktopViewforId.contains(requestedScreen)) { - stillWaitingPanels << cont; - continue; - } - - //TODO: does a similar check make sense? - //Q_ASSERT(qBound(0, requestedScreen, m_screenPool->count() - 1) == requestedScreen); - QScreen *screen = m_desktopViewforId.value(requestedScreen)->screenToFollow(); - PanelView* panel = new PanelView(this, screen); - if (panel->rendererInterface()->graphicsApi() != QSGRendererInterface::Software) { - connect(panel, &QQuickWindow::sceneGraphError, this, &ShellCorona::glInitializationFailed); - } - connect(panel, &QWindow::visibleChanged, this, &Plasma::Corona::availableScreenRectChanged); - connect(panel, &PanelView::locationChanged, this, &Plasma::Corona::availableScreenRectChanged); - connect(panel, &PanelView::visibilityModeChanged, this, &Plasma::Corona::availableScreenRectChanged); - connect(panel, &PanelView::thicknessChanged, this, &Plasma::Corona::availableScreenRectChanged); - - m_panelViews[cont] = panel; - panel->setContainment(cont); - cont->reactToScreenChange(); - - connect(cont, &QObject::destroyed, this, &ShellCorona::panelContainmentDestroyed); - } - m_waitingPanels = stillWaitingPanels; - emit availableScreenRectChanged(); -} - -void ShellCorona::panelContainmentDestroyed(QObject *cont) -{ - auto view = m_panelViews.take(static_cast(cont)); - view->deleteLater(); - //don't make things relayout when the application is quitting - //NOTE: qApp->closingDown() is still false here - if (!m_closingDown) { - emit availableScreenRectChanged(); - } -} - -void ShellCorona::handleContainmentAdded(Plasma::Containment *c) -{ - connect(c, &Plasma::Containment::showAddWidgetsInterface, - this, &ShellCorona::toggleWidgetExplorer); - connect(c, &Plasma::Containment::appletAlternativesRequested, - this, &ShellCorona::showAlternativesForApplet); - connect(c, &Plasma::Containment::appletCreated, this, [this, c] (Plasma::Applet *applet) { - executeSetupPlasmoidScript(c, applet); - }); -} - -void ShellCorona::executeSetupPlasmoidScript(Plasma::Containment *containment, Plasma::Applet *applet) -{ - if (!applet->pluginMetaData().isValid() || !containment->pluginMetaData().isValid()) { - return; - } - - const QString scriptFile = m_lookAndFeelPackage.filePath("plasmoidsetupscripts", applet->pluginMetaData().pluginId() + ".js"); - - if (scriptFile.isEmpty()) { - return; - } - - WorkspaceScripting::ScriptEngine scriptEngine(this); - - connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, - [](const QString &msg) { - qWarning() << msg; - }); - connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, - [](const QString &msg) { - qDebug() << msg; - }); - - QFile file(scriptFile); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - qWarning() << i18n("Unable to load script file: %1", scriptFile); - return; - } - - QString script = file.readAll(); - if (script.isEmpty()) { - // qDebug() << "script is empty"; - return; - } - - scriptEngine.globalObject().setProperty(QStringLiteral("applet"), scriptEngine.wrap(applet)); - scriptEngine.globalObject().setProperty(QStringLiteral("containment"), scriptEngine.wrap(containment)); - scriptEngine.evaluateScript(script, scriptFile); -} - -void ShellCorona::toggleWidgetExplorer() -{ - const QPoint cursorPos = QCursor::pos(); - foreach (DesktopView *view, m_desktopViewforId) { - if (view->screen()->geometry().contains(cursorPos)) { - //The view QML has to provide something to display the widget explorer - view->rootObject()->metaObject()->invokeMethod(view->rootObject(), "toggleWidgetExplorer", Q_ARG(QVariant, QVariant::fromValue(sender()))); - return; - } - } -} - -void ShellCorona::toggleActivityManager() -{ - const QPoint cursorPos = QCursor::pos(); - foreach (DesktopView *view, m_desktopViewforId) { - if (view->screen()->geometry().contains(cursorPos)) { - //The view QML has to provide something to display the activity explorer - view->rootObject()->metaObject()->invokeMethod(view->rootObject(), "toggleActivityManager", Qt::QueuedConnection); - return; - } - } -} - -void ShellCorona::syncAppConfig() -{ - applicationConfig()->sync(); -} - -void ShellCorona::setDashboardShown(bool show) -{ - KWindowSystem::setShowingDesktop(show); -} - -void ShellCorona::toggleDashboard() -{ - setDashboardShown(!KWindowSystem::showingDesktop()); -} - -void ShellCorona::loadInteractiveConsole() -{ - if (KSharedConfig::openConfig()->isImmutable() || !KAuthorized::authorize(QStringLiteral("plasma-desktop/scripting_console"))) { - delete m_interactiveConsole; - m_interactiveConsole = nullptr; - return; - } - - if (!m_interactiveConsole) { - const QUrl consoleQML = kPackage().fileUrl("interactiveconsole"); - if (consoleQML.isEmpty()) { - return; - } - - m_interactiveConsole = new KDeclarative::QmlObjectSharedEngine(this); - m_interactiveConsole->setInitializationDelayed(true); - m_interactiveConsole->setSource(consoleQML); - - QObject *engine = new WorkspaceScripting::ScriptEngine(this, m_interactiveConsole); - m_interactiveConsole->rootContext()->setContextProperty(QStringLiteral("scriptEngine"), engine); - - m_interactiveConsole->completeInitialization(); - if (m_interactiveConsole->rootObject()) { - connect(m_interactiveConsole->rootObject(), SIGNAL(visibleChanged(bool)), - this, SLOT(interactiveConsoleVisibilityChanged(bool))); - } - } -} - -void ShellCorona::showInteractiveConsole() -{ - loadInteractiveConsole(); - if (m_interactiveConsole && m_interactiveConsole->rootObject()) { - m_interactiveConsole->rootObject()->setProperty("mode", "desktop"); - m_interactiveConsole->rootObject()->setProperty("visible", true); - } -} - -void ShellCorona::loadScriptInInteractiveConsole(const QString &script) -{ - showInteractiveConsole(); - if (m_interactiveConsole) { - m_interactiveConsole->rootObject()->setProperty("script", script); - } -} - -void ShellCorona::showInteractiveKWinConsole() -{ - loadInteractiveConsole(); - - if (m_interactiveConsole && m_interactiveConsole->rootObject()) { - m_interactiveConsole->rootObject()->setProperty("mode", "windowmanager"); - m_interactiveConsole->rootObject()->setProperty("visible", true); - } -} - -void ShellCorona::loadKWinScriptInInteractiveConsole(const QString &script) -{ - showInteractiveKWinConsole(); - if (m_interactiveConsole) { - m_interactiveConsole->rootObject()->setProperty("script", script); - } -} - -void ShellCorona::evaluateScript(const QString &script) { - if (calledFromDBus()) { - if (immutability() == Plasma::Types::SystemImmutable) { - sendErrorReply(QDBusError::Failed, QStringLiteral("Widgets are locked")); - return; - } else if (!KAuthorized::authorize(QStringLiteral("plasma-desktop/scripting_console"))) { - sendErrorReply(QDBusError::Failed, QStringLiteral("Administrative policies prevent script execution")); - return; - } - } - - WorkspaceScripting::ScriptEngine scriptEngine(this); - - connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, - [](const QString &msg) { - qWarning() << msg; - }); - connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, - [](const QString &msg) { - qDebug() << msg; - }); - - scriptEngine.evaluateScript(script); - if (!scriptEngine.errorString().isEmpty() && calledFromDBus()) { - sendErrorReply(QDBusError::Failed, scriptEngine.errorString()); - } -} - -void ShellCorona::interactiveConsoleVisibilityChanged(bool visible) -{ - if (!visible) { - m_interactiveConsole->deleteLater(); - m_interactiveConsole = nullptr; - } -} - -void ShellCorona::checkActivities() -{ - KActivities::Controller::ServiceStatus status = m_activityController->serviceStatus(); - //qDebug() << "$%$%$#%$%$%Status:" << status; - if (status != KActivities::Controller::Running) { - //panic and give up - better than causing a mess - qDebug() << "ShellCorona::checkActivities is called whilst activity daemon is still connecting"; - return; - } - - QStringList existingActivities = m_activityController->activities(); - foreach (const QString &id, existingActivities) { - activityAdded(id); - } - - // Checking whether the result we got is valid. Just in case. - Q_ASSERT_X(!existingActivities.isEmpty(), "isEmpty", "There are no activities, and the service is running"); - Q_ASSERT_X(existingActivities[0] != QStringLiteral("00000000-0000-0000-0000-000000000000"), - "null uuid", "There is a nulluuid activity present"); - - // Killing the unassigned containments - foreach (Plasma::Containment *cont, containments()) { - if ((cont->containmentType() == Plasma::Types::DesktopContainment || - cont->containmentType() == Plasma::Types::CustomContainment) && - !existingActivities.contains(cont->activity())) { - cont->destroy(); - } - } -} - -void ShellCorona::currentActivityChanged(const QString &newActivity) -{ -// qDebug() << "Activity changed:" << newActivity; - - foreach (int id, m_desktopViewforId.keys()) { - Plasma::Containment *c = createContainmentForActivity(newActivity, id); - - QAction *removeAction = c->actions()->action(QStringLiteral("remove")); - if (removeAction) { - removeAction->deleteLater(); - } - m_desktopViewforId.value(id)->setContainment(c); - } -} - -void ShellCorona::activityAdded(const QString &id) -{ - //TODO more sanity checks - if (m_activityContainmentPlugins.contains(id)) { - qWarning() << "Activity added twice" << id; - return; - } - - m_activityContainmentPlugins.insert(id, defaultContainmentPlugin()); -} - -void ShellCorona::activityRemoved(const QString &id) -{ - m_activityContainmentPlugins.remove(id); - for (auto cont : containmentsForActivity(id)) { - cont->destroy(); - } -} - -void ShellCorona::insertActivity(const QString &id, const QString &plugin) -{ - activityAdded(id); - - const QString currentActivityReally = m_activityController->currentActivity(); - - // TODO: This needs to go away! - // The containment creation API does not know when we have a - // new activity to create a containment for, we need to pretend - // that the current activity has been changed - QFuture currentActivity = m_activityController->setCurrentActivity(id); - awaitFuture(currentActivity); - - if (!currentActivity.result()) { - qDebug() << "Failed to create and switch to the activity"; - return; - } - - while (m_activityController->currentActivity() != id) { - QCoreApplication::processEvents(); - } - - m_activityContainmentPlugins.insert(id, plugin); - foreach (int screenId, m_desktopViewforId.keys()) { - Plasma::Containment *c = createContainmentForActivity(id, screenId); - if (c) { - c->config().writeEntry("lastScreen", screenId); - } - } -} - -Plasma::Containment *ShellCorona::setContainmentTypeForScreen(int screen, const QString &plugin) -{ - //search but not create - Plasma::Containment *oldContainment = containmentForScreen(screen, m_activityController->currentActivity(), QString()); - - //no valid containment in given screen, giving up - if (!oldContainment) { - return nullptr; - } - - if (plugin.isEmpty()) { - return oldContainment; - } - - DesktopView *view = nullptr; - foreach (DesktopView *v, m_desktopViewforId) { - if (v->containment() == oldContainment) { - view = v; - break; - } - } - - //no view? give up - if (!view) { - return oldContainment; - } - - //create a new containment - Plasma::Containment *newContainment = createContainmentDelayed(plugin); - - //if creation failed or invalid plugin, give up - if (!newContainment) { - return oldContainment; - } else if (!newContainment->pluginMetaData().isValid()) { - newContainment->deleteLater(); - return oldContainment; - } - - newContainment->setWallpaper(oldContainment->wallpaper()); - - //At this point we have a valid new containment from plugin and a view - //copy all configuration groups (excluded applets) - KConfigGroup oldCg = oldContainment->config(); - - //newCg *HAS* to be from a KSharedConfig, because some KConfigSkeleton will need to be synced - //this makes the configscheme work - KConfigGroup newCg(KSharedConfig::openConfig(oldCg.config()->name()), "Containments"); - newCg = KConfigGroup(&newCg, QString::number(newContainment->id())); - - //this makes containment->config() work, is a separate thing from its configscheme - KConfigGroup newCg2 = newContainment->config(); - - foreach (const QString &group, oldCg.groupList()) { - if (group != QLatin1String("Applets")) { - KConfigGroup subGroup(&oldCg, group); - KConfigGroup newSubGroup(&newCg, group); - subGroup.copyTo(&newSubGroup); - - KConfigGroup newSubGroup2(&newCg2, group); - subGroup.copyTo(&newSubGroup2); - } - } - - newContainment->init(); - newCg.writeEntry("activityId", oldContainment->activity()); - newContainment->restore(newCg); - newContainment->updateConstraints(Plasma::Types::StartupCompletedConstraint); - newContainment->flushPendingConstraintsEvents(); - emit containmentAdded(newContainment); - - //Move the applets - foreach (Plasma::Applet *applet, oldContainment->applets()) { - newContainment->addApplet(applet); - } - - //remove the "remove" action - QAction *removeAction = newContainment->actions()->action(QStringLiteral("remove")); - if (removeAction) { - removeAction->deleteLater(); - } - view->setContainment(newContainment); - newContainment->setActivity(oldContainment->activity()); - insertContainment(oldContainment->activity(), screen, newContainment); - - //removing the focus from the item that is going to be destroyed - //fixes a crash - //delayout the destruction of the old containment fixes another crash - view->rootObject()->setFocus(true, Qt::MouseFocusReason); - QTimer::singleShot(2500, oldContainment, &Plasma::Applet::destroy); - - //Save now as we now have a screen, so lastScreen will not be -1 - newContainment->save(newCg); - requestConfigSync(); - emit availableScreenRectChanged(); - - return newContainment; -} - -void ShellCorona::checkAddPanelAction(const QStringList &sycocaChanges) -{ - if (!sycocaChanges.isEmpty() && !sycocaChanges.contains(QStringLiteral("services"))) { - return; - } - - delete m_addPanelAction; - m_addPanelAction = nullptr; - - m_addPanelsMenu.reset(nullptr); - - KPluginInfo::List panelContainmentPlugins = Plasma::PluginLoader::listContainmentsOfType(QStringLiteral("Panel")); - - auto filter = [](const KPluginMetaData &md) -> bool - { - return md.value(QStringLiteral("NoDisplay")) != QStringLiteral("true") && KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Plasma-ContainmentCategories")).contains(QStringLiteral("panel")); - }; - QList templates = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/LayoutTemplate"), QString(), filter); - - if (panelContainmentPlugins.count() + templates.count() == 1) { - m_addPanelAction = new QAction(i18n("Add Panel"), this); - m_addPanelAction->setData(Plasma::Types::AddAction); - connect(m_addPanelAction, SIGNAL(triggered(bool)), this, SLOT(addPanel())); - } else if (!panelContainmentPlugins.isEmpty()) { - m_addPanelsMenu.reset(new QMenu); - m_addPanelAction = m_addPanelsMenu->menuAction(); - m_addPanelAction->setText(i18n("Add Panel")); - m_addPanelAction->setData(Plasma::Types::AddAction); - connect(m_addPanelsMenu.data(), &QMenu::aboutToShow, this, &ShellCorona::populateAddPanelsMenu); - connect(m_addPanelsMenu.data(), SIGNAL(triggered(QAction*)), this, SLOT(addPanel(QAction*))); - } - - if (m_addPanelAction) { - m_addPanelAction->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); - actions()->addAction(QStringLiteral("add panel"), m_addPanelAction); - } -} - -void ShellCorona::populateAddPanelsMenu() -{ - m_addPanelsMenu->clear(); - const KPluginInfo emptyInfo; - - KPluginInfo::List panelContainmentPlugins = Plasma::PluginLoader::listContainmentsOfType(QStringLiteral("Panel")); - QMap > sorted; - foreach (const KPluginInfo &plugin, panelContainmentPlugins) { - if (plugin.property(QStringLiteral("NoDisplay")).toString() == QStringLiteral("true")) { - continue; - } - sorted.insert(plugin.name(), qMakePair(plugin, KPluginMetaData())); - } - - auto filter = [](const KPluginMetaData &md) -> bool - { - return md.value(QStringLiteral("NoDisplay")) != QStringLiteral("true") && KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Plasma-ContainmentCategories")).contains(QStringLiteral("panel")); - }; - const QList templates = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/LayoutTemplate"), QString(), filter); - for (auto tpl : templates) { - sorted.insert(tpl.name(), qMakePair(emptyInfo, tpl)); - } - - QMapIterator > it(sorted); - KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LayoutTemplate")); - while (it.hasNext()) { - it.next(); - QPair pair = it.value(); - if (pair.first.isValid()) { - KPluginInfo plugin = pair.first; - QAction *action = m_addPanelsMenu->addAction(i18n("Empty %1", plugin.name())); - if (!plugin.icon().isEmpty()) { - action->setIcon(QIcon::fromTheme(plugin.icon())); - } - - action->setData(plugin.pluginName()); - } else { - KPluginInfo info(pair.second); - package.setPath(info.pluginName()); - const QString scriptFile = package.filePath("mainscript"); - if (!scriptFile.isEmpty()) { - QAction *action = m_addPanelsMenu->addAction(info.name()); - action->setData(QStringLiteral("plasma-desktop-template:%1").arg(info.pluginName())); - } - } - } -} - -void ShellCorona::addPanel() -{ - KPluginInfo::List panelPlugins = Plasma::PluginLoader::listContainmentsOfType(QStringLiteral("Panel")); - - if (!panelPlugins.isEmpty()) { - addPanel(panelPlugins.first().pluginName()); - } -} - -void ShellCorona::addPanel(QAction *action) -{ - const QString plugin = action->data().toString(); - if (plugin.startsWith(QLatin1String("plasma-desktop-template:"))) { - WorkspaceScripting::ScriptEngine scriptEngine(this); - - connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, - [](const QString &msg) { - qWarning() << msg; - }); - connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, - [](const QString &msg) { - qDebug() << msg; - }); - const QString templateName = plugin.right(plugin.length() - qstrlen("plasma-desktop-template:")); - - scriptEngine.evaluateScript(QStringLiteral("loadTemplate(\"%1\")").arg(templateName)); - } else if (!plugin.isEmpty()) { - addPanel(plugin); - } -} - -Plasma::Containment *ShellCorona::addPanel(const QString &plugin) -{ - Plasma::Containment *panel = createContainment(plugin); - if (!panel) { - return nullptr; - } - - QList availableLocations; - availableLocations << Plasma::Types::LeftEdge << Plasma::Types::TopEdge << Plasma::Types::RightEdge << Plasma::Types::BottomEdge; - - foreach (const Plasma::Containment *cont, m_panelViews.keys()) { - availableLocations.removeAll(cont->location()); - } - - Plasma::Types::Location loc; - if (availableLocations.isEmpty()) { - loc = Plasma::Types::TopEdge; - } else { - loc = availableLocations.first(); - } - - panel->setLocation(loc); - switch (loc) { - case Plasma::Types::LeftEdge: - case Plasma::Types::RightEdge: - panel->setFormFactor(Plasma::Types::Vertical); - break; - default: - panel->setFormFactor(Plasma::Types::Horizontal); - break; - } - - Q_ASSERT(panel); - m_waitingPanels << panel; - //not creating the panel view yet in order to have the same code path - //between the first and subsequent plasma starts. we want to have the panel appearing only when its layout is completed, to not have - //many visible relayouts. otherwise we had even panel resizes at startup that - //made al lthe full representations be loaded. - m_waitingPanelsTimer.start(); - - const QPoint cursorPos(QCursor::pos()); - foreach (QScreen *screen, QGuiApplication::screens()) { - //m_panelViews.contains(panel) == false iff addPanel is executed in a startup script - if (screen->geometry().contains(cursorPos) && m_panelViews.contains(panel)) { - m_panelViews[panel]->setScreenToFollow(screen); - break; - } - } - return panel; -} - -int ShellCorona::screenForContainment(const Plasma::Containment *containment) const -{ - //case in which this containment is child of an applet, hello systray :) - if (Plasma::Applet *parentApplet = qobject_cast(containment->parent())) { - if (Plasma::Containment* cont = parentApplet->containment()) { - return screenForContainment(cont); - } else { - return -1; - } - } - - //if the desktop views already exist, base the decision upon them - for (auto it = m_desktopViewforId.constBegin(), end = m_desktopViewforId.constEnd(); it != end; ++it) { - if (it.value()->containment() == containment && containment->activity() == m_activityController->currentActivity()) { - return it.key(); - } - } - - //if the panel views already exist, base upon them - PanelView *view = m_panelViews.value(containment); - if (view && view->screenToFollow()) { - return m_screenPool->id(view->screenToFollow()->name()); - } - - //Failed? fallback on lastScreen() - //lastScreen() is the correct screen for panels - //It is also correct for desktops *that have the correct activity()* - //a containment with lastScreen() == 0 but another activity, - //won't be associated to a screen -// qDebug() << "ShellCorona screenForContainment: " << containment << " Last screen is " << containment->lastScreen(); - - for (auto screen : qGuiApp->screens()) { - // containment->lastScreen() == m_screenPool->id(screen->name()) to check if the lastScreen refers to a screen that exists/it's known - if (containment->lastScreen() == m_screenPool->id(screen->name()) && - (containment->activity() == m_activityController->currentActivity() || - containment->containmentType() == Plasma::Types::PanelContainment || containment->containmentType() == Plasma::Types::CustomPanelContainment)) { - return containment->lastScreen(); - } - } - - return -1; -} - -void ShellCorona::nextActivity() -{ - const QStringList list = m_activityController->activities(KActivities::Info::Running); - if (list.isEmpty()) { - return; - } - - const int start = list.indexOf(m_activityController->currentActivity()); - const int i = (start + 1) % list.size(); - - m_activityController->setCurrentActivity(list.at(i)); -} - -void ShellCorona::previousActivity() -{ - const QStringList list = m_activityController->activities(KActivities::Info::Running); - if (list.isEmpty()) { - return; - } - - const int start = list.indexOf(m_activityController->currentActivity()); - int i = start - 1; - if(i < 0) { - i = list.size() - 1; - } - - m_activityController->setCurrentActivity(list.at(i)); -} - -void ShellCorona::stopCurrentActivity() -{ - const QStringList list = m_activityController->activities(KActivities::Info::Running); - if (list.isEmpty()) { - return; - } - - m_activityController->stopActivity(m_activityController->currentActivity()); -} - -void ShellCorona::insertContainment(const QString &activity, int screenNum, Plasma::Containment *containment) -{ - Plasma::Containment *cont = nullptr; - auto candidates = containmentsForActivity(activity); - for (Plasma::Containment *c : candidates) { - //using lastScreen() instead of screen() catches also containments of activities that aren't the current one, so not assigned to a screen right now - if (c->lastScreen() == screenNum) { - cont = c; - if (containment == cont) { - return; - } - break; - } - } - - Q_ASSERT(containment != cont); - - //if there was a duplicate containment destroy the old one - //the new one replaces it - //FIXME: this whole function is probably redundant now - if (cont) { - cont->destroy(); - } -} - -void ShellCorona::setupWaylandIntegration() -{ - if (!KWindowSystem::isPlatformWayland()) { - return; - } - using namespace KWayland::Client; - ConnectionThread *connection = ConnectionThread::fromApplication(this); - if (!connection) { - return; - } - Registry *registry = new Registry(this); - registry->create(connection); - connect(registry, &Registry::plasmaShellAnnounced, this, - [this, registry] (quint32 name, quint32 version) { - m_waylandPlasmaShell = registry->createPlasmaShell(name, version, this); - } - ); - registry->setup(); - connection->roundtrip(); -} - -KWayland::Client::PlasmaShell *ShellCorona::waylandPlasmaShellInterface() const -{ - return m_waylandPlasmaShell; -} - -ScreenPool *ShellCorona::screenPool() const -{ - return m_screenPool; -} - -QList ShellCorona::screenIds() const -{ - return m_desktopViewforId.keys(); -} - -QString ShellCorona::defaultContainmentPlugin() const -{ - QString plugin = m_lnfDefaultsConfig.readEntry("Containment", QString()); - if (plugin.isEmpty()) { - plugin = m_desktopDefaultsConfig.readEntry("Containment", "org.kde.desktopcontainment"); - } - return plugin; -} - -void ShellCorona::updateStruts() -{ - foreach(PanelView* view, m_panelViews) { - view->updateStruts(); - } -} - -void ShellCorona::configurationChanged(const QString &path) -{ - if (path == m_configPath) { - m_config->reparseConfiguration(); - } -} - -void ShellCorona::activateLauncherMenu() -{ - for (auto it = m_panelViews.constBegin(), end = m_panelViews.constEnd(); it != end; ++it) { - const auto applets = it.key()->applets(); - for (auto applet : applets) { - const auto provides = KPluginMetaData::readStringList(applet->pluginMetaData().rawData(), QStringLiteral("X-Plasma-Provides")); - if (provides.contains(QLatin1String("org.kde.plasma.launchermenu"))) { - if (!applet->globalShortcut().isEmpty()) { - emit applet->activated(); - return; - } - } - } - } -} - -void ShellCorona::activateTaskManagerEntry(int index) -{ - auto activateTaskManagerEntryOnContainment = [](const Plasma::Containment *c, int index) { - const auto &applets = c->applets(); - for (auto *applet : applets) { - const auto &provides = KPluginMetaData::readStringList(applet->pluginMetaData().rawData(), QStringLiteral("X-Plasma-Provides")); - if (provides.contains(QLatin1String("org.kde.plasma.multitasking"))) { - if (QQuickItem *appletInterface = applet->property("_plasma_graphicObject").value()) { - const auto &childItems = appletInterface->childItems(); - if (childItems.isEmpty()) { - continue; - } - - for (QQuickItem *item : childItems) { - if (auto *metaObject = item->metaObject()) { - // not using QMetaObject::invokeMethod to avoid warnings when calling - // this on applets that don't have it or other child items since this - // is pretty much trial and error. - - // Also, "var" arguments are treated as QVariant in QMetaObject - int methodIndex = metaObject->indexOfMethod("activateTaskAtIndex(QVariant)"); - if (methodIndex == -1) { - continue; - } - - QMetaMethod method = metaObject->method(methodIndex); - if (method.invoke(item, Q_ARG(QVariant, index))) { - return true; - } - } - } - } - } - } - return false; - }; - - // To avoid overly complex configuration, we'll try to get the 90% usecase to work - // which is activating a task on the task manager on a panel on the primary screen. - - for (auto it = m_panelViews.constBegin(), end = m_panelViews.constEnd(); it != end; ++it) { - if (it.value()->screen() != qGuiApp->primaryScreen()) { - continue; - } - if (activateTaskManagerEntryOnContainment(it.key(), index)) { - return; - } - } - - // we didn't find anything on primary, try all the panels - for (auto it = m_panelViews.constBegin(), end = m_panelViews.constEnd(); it != end; ++it) { - if (activateTaskManagerEntryOnContainment(it.key(), index)) { - return; - } - } -} - -// Desktop corona handler - - -#include "moc_shellcorona.cpp" -