You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1118 lines
36 KiB
1118 lines
36 KiB
/* |
|
* Copyright 2008 Aaron Seigo <aseigo@kde.org> |
|
* Copyright 2013 Sebastian Kügler <sebas@kde.org> |
|
* Copyright 2013 Ivan Cukic <ivan.cukic@kde.org> |
|
* Copyright 2013 Marco Martin <mart@kde.org> |
|
* |
|
* 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 <QApplication> |
|
#include <QDebug> |
|
#include <QMenu> |
|
#include <QDesktopWidget> |
|
#include <QQmlContext> |
|
#include <QTimer> |
|
#include <QDBusConnection> |
|
|
|
#include <kactioncollection.h> |
|
#include <klocalizedstring.h> |
|
#include <Plasma/Package> |
|
#include <Plasma/PluginLoader> |
|
#include <kactivities/controller.h> |
|
#include <kactivities/consumer.h> |
|
#include <ksycoca.h> |
|
#include <kservicetypetrader.h> |
|
#include <KGlobalAccel> |
|
#include <KAuthorized> |
|
#include <KWindowSystem> |
|
|
|
#include <KScreen/Config> |
|
#include <kscreen/configmonitor.h> |
|
|
|
#include "config-ktexteditor.h" // HAVE_KTEXTEDITOR |
|
|
|
|
|
#include "activity.h" |
|
#include "desktopview.h" |
|
#include "panelview.h" |
|
#include "scripting/desktopscriptengine.h" |
|
#include "widgetexplorer/widgetexplorer.h" |
|
#include "configview.h" |
|
#include "shellpluginloader.h" |
|
#include "osd.h" |
|
#if HAVE_KTEXTEDITOR |
|
#include "interactiveconsole.h" |
|
#endif |
|
|
|
#include "plasmashelladaptor.h" |
|
|
|
static const int s_configSyncDelay = 10000; // 10 seconds |
|
|
|
class ShellCorona::Private { |
|
public: |
|
Private(ShellCorona *corona) |
|
: q(corona), |
|
activityController(new KActivities::Controller(q)), |
|
activityConsumer(new KActivities::Consumer(q)), |
|
addPanelAction(nullptr), |
|
addPanelsMenu(nullptr), |
|
screenConfiguration(nullptr) |
|
{ |
|
appConfigSyncTimer.setSingleShot(true); |
|
appConfigSyncTimer.setInterval(s_configSyncDelay); |
|
connect(&appConfigSyncTimer, &QTimer::timeout, q, &ShellCorona::syncAppConfig); |
|
|
|
waitingPanelsTimer.setSingleShot(true); |
|
waitingPanelsTimer.setInterval(250); |
|
connect(&waitingPanelsTimer, &QTimer::timeout, q, &ShellCorona::createWaitingPanels); |
|
} |
|
|
|
ShellCorona *q; |
|
QString shell; |
|
QList<DesktopView *> views; |
|
KActivities::Controller *activityController; |
|
KActivities::Consumer *activityConsumer; |
|
QHash<const Plasma::Containment *, PanelView *> panelViews; |
|
KConfigGroup desktopDefaultsConfig; |
|
WorkspaceScripting::DesktopScriptEngine *scriptEngine; |
|
QList<Plasma::Containment *> waitingPanels; |
|
QHash<QString, Activity *> activities; |
|
QHash<QString, QHash<int, Plasma::Containment *> > desktopContainments; |
|
QAction *addPanelAction; |
|
QMenu *addPanelsMenu; |
|
Plasma::Package lookNFeelPackage; |
|
#if HAVE_KTEXTEDITOR |
|
QWeakPointer<InteractiveConsole> console; |
|
#endif |
|
|
|
KScreen::Config *screenConfiguration; |
|
QTimer waitingPanelsTimer; |
|
QTimer appConfigSyncTimer; |
|
}; |
|
|
|
static QScreen *outputToScreen(KScreen::Output *output) |
|
{ |
|
foreach(QScreen *screen, QGuiApplication::screens()) { |
|
if (screen->name() == output->name()) { |
|
return screen; |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
WorkspaceScripting::DesktopScriptEngine* ShellCorona::scriptEngine() const |
|
{ |
|
return d->scriptEngine; |
|
} |
|
|
|
|
|
ShellCorona::ShellCorona(QObject *parent) |
|
: Plasma::Corona(parent), |
|
d(new Private(this)) |
|
{ |
|
d->desktopDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(package().filePath("defaults")), "Desktop"); |
|
|
|
new PlasmaShellAdaptor(this); |
|
|
|
QDBusConnection dbus = QDBusConnection::sessionBus(); |
|
dbus.registerObject(QStringLiteral("/PlasmaShell"), this); |
|
|
|
connect(this, &Plasma::Corona::startupCompleted, this, |
|
[]() { |
|
QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), |
|
QStringLiteral("/KSplash"), |
|
QStringLiteral("org.kde.KSplash"), |
|
QStringLiteral("setStage")); |
|
ksplashProgressMessage.setArguments(QList<QVariant>() << QStringLiteral("desktop")); |
|
QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); |
|
}); |
|
|
|
// 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 |
|
|
|
const QString themeGroupKey = QStringLiteral("Theme"); |
|
const QString themeNameKey = QStringLiteral("name"); |
|
|
|
QString themeName; |
|
|
|
KConfigGroup plasmarc(KSharedConfig::openConfig("plasmarc"), themeGroupKey); |
|
themeName = plasmarc.readEntry(themeNameKey, themeName); |
|
|
|
if (themeName.isEmpty()) { |
|
const KConfigGroup lnfCfg = KConfigGroup(KSharedConfig::openConfig( |
|
lookAndFeelPackage().filePath("defaults")), |
|
themeGroupKey |
|
); |
|
themeName = lnfCfg.readEntry(themeNameKey, themeName); |
|
} |
|
|
|
if (!themeName.isEmpty()) { |
|
Plasma::Theme *t = new Plasma::Theme(this); |
|
t->setThemeName(themeName); |
|
} |
|
|
|
connect(this, &ShellCorona::containmentAdded, |
|
this, &ShellCorona::handleContainmentAdded); |
|
|
|
d->scriptEngine = new WorkspaceScripting::DesktopScriptEngine(this, true); |
|
|
|
connect(d->scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, |
|
[](const QString &msg) { |
|
qWarning() << msg; |
|
}); |
|
connect(d->scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, |
|
[](const QString &msg) { |
|
qDebug() << msg; |
|
}); |
|
|
|
QAction *dashboardAction = actions()->add<QAction>("show dashboard"); |
|
QObject::connect(dashboardAction, &QAction::triggered, |
|
this, &ShellCorona::setDashboardShown); |
|
dashboardAction->setText(i18n("Show Dashboard")); |
|
|
|
dashboardAction->setAutoRepeat(true); |
|
dashboardAction->setCheckable(true); |
|
dashboardAction->setIcon(QIcon::fromTheme("dashboard-show")); |
|
dashboardAction->setData(Plasma::Types::ControlAction); |
|
KGlobalAccel::self()->setDefaultShortcut(dashboardAction, QList<QKeySequence>() << QKeySequence(Qt::CTRL + Qt::Key_F12)); |
|
KGlobalAccel::self()->setShortcut(dashboardAction, QList<QKeySequence>() << QKeySequence(Qt::CTRL + Qt::Key_F12)); |
|
|
|
|
|
checkAddPanelAction(); |
|
connect(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), this, SLOT(checkAddPanelAction(QStringList))); |
|
|
|
|
|
//Activity stuff |
|
QAction *activityAction = actions()->addAction("manage activities"); |
|
connect(activityAction, &QAction::triggered, |
|
this, &ShellCorona::toggleActivityManager); |
|
activityAction->setText(i18n("Activities...")); |
|
activityAction->setIcon(QIcon::fromTheme("preferences-activities")); |
|
activityAction->setData(Plasma::Types::ConfigureAction); |
|
activityAction->setShortcut(QKeySequence("alt+d, alt+a")); |
|
activityAction->setShortcutContext(Qt::ApplicationShortcut); |
|
|
|
connect(d->activityConsumer, SIGNAL(currentActivityChanged(QString)), this, SLOT(currentActivityChanged(QString))); |
|
connect(d->activityConsumer, SIGNAL(activityAdded(QString)), this, SLOT(activityAdded(QString))); |
|
connect(d->activityConsumer, SIGNAL(activityRemoved(QString)), this, SLOT(activityRemoved(QString))); |
|
|
|
new Osd(this); |
|
} |
|
|
|
ShellCorona::~ShellCorona() |
|
{ |
|
qDeleteAll(d->views); |
|
qDeleteAll(d->panelViews); |
|
delete d; |
|
} |
|
|
|
void ShellCorona::setShell(const QString &shell) |
|
{ |
|
if (d->shell == shell) { |
|
return; |
|
} |
|
|
|
unload(); |
|
|
|
d->shell = shell; |
|
Plasma::Package package = Plasma::PluginLoader::self()->loadPackage("Plasma/Shell"); |
|
package.setPath(shell); |
|
setPackage(package); |
|
|
|
if (d->activityConsumer->serviceStatus() == KActivities::Consumer::Unknown) { |
|
connect(d->activityConsumer, SIGNAL(serviceStatusChanged(Consumer::ServiceStatus)), SLOT(load()), Qt::UniqueConnection); |
|
} else { |
|
load(); |
|
} |
|
} |
|
|
|
QString ShellCorona::shell() const |
|
{ |
|
return d->shell; |
|
} |
|
|
|
static QList<KScreen::Output*> sortOutputs(const QHash<int, KScreen::Output*> &outputs) |
|
{ |
|
QList<KScreen::Output*> ret; |
|
foreach(KScreen::Output *output, outputs) { |
|
if (output->isPrimary()) |
|
ret.prepend(output); |
|
else |
|
ret.append(output); |
|
} |
|
return ret; |
|
} |
|
|
|
void ShellCorona::load() |
|
{ |
|
if (d->shell.isEmpty() || |
|
d->activityConsumer->serviceStatus() == KActivities::Consumer::Unknown) { |
|
return; |
|
} |
|
|
|
disconnect(d->activityConsumer, SIGNAL(serviceStatusChanged(Consumer::ServiceStatus)), this, SLOT(load())); |
|
|
|
loadLayout("plasma-" + d->shell + "-appletsrc"); |
|
|
|
checkActivities(); |
|
if (containments().isEmpty()) { |
|
loadDefaultLayout(); |
|
foreach(Plasma::Containment *containment, containments()) { |
|
containment->setActivity(d->activityConsumer->currentActivity()); |
|
} |
|
} |
|
|
|
processUpdateScripts(); |
|
foreach(Plasma::Containment *containment, containments()) { |
|
if (containment->formFactor() == Plasma::Types::Horizontal || |
|
containment->formFactor() == Plasma::Types::Vertical) { |
|
if (!d->waitingPanels.contains(containment)) { |
|
d->waitingPanels << containment; |
|
} |
|
} else { |
|
//FIXME ideally fix this, or at least document the crap out of it |
|
int screen = containment->lastScreen(); |
|
if (screen < 0) { |
|
screen = d->desktopContainments[containment->activity()].count(); |
|
} |
|
insertContainment(containment->activity(), screen, containment); |
|
} |
|
} |
|
|
|
d->screenConfiguration = KScreen::Config::current(); |
|
KScreen::ConfigMonitor::instance()->addConfig(d->screenConfiguration); |
|
for (KScreen::Output *output : sortOutputs(d->screenConfiguration->connectedOutputs())) { |
|
addOutput(output); |
|
} |
|
connect(d->screenConfiguration, &KScreen::Config::outputAdded, this, &ShellCorona::addOutput); |
|
connect(d->screenConfiguration, &KScreen::Config::primaryOutputChanged, |
|
this, &ShellCorona::primaryOutputChanged); |
|
|
|
if (!d->waitingPanels.isEmpty()) { |
|
d->waitingPanelsTimer.start(); |
|
} |
|
} |
|
|
|
void ShellCorona::primaryOutputChanged() |
|
{ |
|
KScreen::Config *current = d->screenConfiguration; |
|
if (!current->primaryOutput()) |
|
return; |
|
QScreen *newPrimary = outputToScreen(current->primaryOutput()); |
|
int i=0; |
|
foreach(DesktopView *view, d->views) { |
|
if (view->screen() == newPrimary) |
|
break; |
|
i++; |
|
} |
|
QScreen *oldPrimary = d->views.first()->screen(); |
|
qDebug() << "primary changed!" << oldPrimary->name() << newPrimary->name() << i; |
|
// |
|
// //if it was not found, it means that addOutput hasn't been called yet |
|
if (i>=d->views.count() || i==0) |
|
return; |
|
// |
|
Q_ASSERT(oldPrimary != newPrimary); |
|
Q_ASSERT(d->views[0]->screen() != d->views[i]->screen()); |
|
Q_ASSERT(d->views[0]->screen() == oldPrimary); |
|
Q_ASSERT(d->views[0]->screen() != newPrimary); |
|
Q_ASSERT(d->views[0]->geometry() == oldPrimary->geometry()); |
|
qDebug() << "adapting" << newPrimary->geometry() << d->views[0]->containment()->wallpaper() |
|
<< oldPrimary->geometry() << d->views[i]->containment()->wallpaper() << i; |
|
|
|
d->views[0]->setScreen(newPrimary); |
|
d->views[i]->setScreen(oldPrimary); |
|
screenInvariants(); |
|
|
|
QList<Plasma::Containment*> panels; |
|
foreach(PanelView *panel, d->panelViews) { |
|
if (panel->screen() == oldPrimary) |
|
panel->setScreen(newPrimary); |
|
else if (panel->screen() == newPrimary) |
|
panel->setScreen(oldPrimary); |
|
} |
|
} |
|
|
|
void ShellCorona::screenInvariants() |
|
{ |
|
Q_ASSERT(d->views.count() <= QGuiApplication::screens().count()); |
|
QScreen *s = d->screenConfiguration->primaryOutput() ? outputToScreen(d->screenConfiguration->primaryOutput()) : d->views.isEmpty() ? nullptr : d->views[0]->screen(); |
|
if (!s) { |
|
qWarning() << "error: couldn't find primary output" << d->screenConfiguration->primaryOutput(); |
|
return; |
|
} |
|
|
|
Q_ASSERT(d->views[0]->screen()->name() == s->name()); |
|
Q_ASSERT(d->views[0]->geometry() == s->geometry()); |
|
|
|
QSet<QScreen*> screens; |
|
foreach(DesktopView *view, d->views) { |
|
Q_ASSERT(!screens.contains(view->screen())); |
|
screens.insert(view->screen()); |
|
} |
|
} |
|
|
|
|
|
void ShellCorona::unload() |
|
{ |
|
if (d->shell.isEmpty()) { |
|
return; |
|
} |
|
|
|
qDeleteAll(containments()); |
|
} |
|
|
|
KSharedConfig::Ptr ShellCorona::applicationConfig() |
|
{ |
|
return KSharedConfig::openConfig(); |
|
} |
|
|
|
void ShellCorona::requestApplicationConfigSync() |
|
{ |
|
d->appConfigSyncTimer.start(); |
|
} |
|
|
|
void ShellCorona::loadDefaultLayout() |
|
{ |
|
const QString script = package().filePath("defaultlayout"); |
|
QFile file(script); |
|
if (file.open(QIODevice::ReadOnly | QIODevice::Text) ) { |
|
QString code = file.readAll(); |
|
qDebug() << "evaluating startup script:" << script; |
|
d->scriptEngine->evaluateScript(code); |
|
} |
|
} |
|
|
|
void ShellCorona::processUpdateScripts() |
|
{ |
|
foreach (const QString &script, WorkspaceScripting::ScriptEngine::pendingUpdateScripts(this)) { |
|
d->scriptEngine->evaluateScript(script); |
|
} |
|
} |
|
|
|
KActivities::Controller *ShellCorona::activityController() |
|
{ |
|
return d->activityController; |
|
} |
|
|
|
int ShellCorona::numScreens() const |
|
{ |
|
return QApplication::desktop()->screenCount(); |
|
} |
|
|
|
QRect ShellCorona::screenGeometry(int id) const |
|
{ |
|
DesktopView *view = 0; |
|
foreach (DesktopView *v, d->views) { |
|
if (v->containment() && v->containment()->screen() == id) { |
|
view = v; |
|
break; |
|
} |
|
} |
|
|
|
if (view) { |
|
return view->geometry(); |
|
} else { |
|
return QApplication::desktop()->screenGeometry(id); |
|
} |
|
} |
|
|
|
QRegion ShellCorona::availableScreenRegion(int id) const |
|
{ |
|
DesktopView *view = 0; |
|
if (id >= 0 && id < d->views.count()) |
|
view = d->views[id]; |
|
|
|
if (view) { |
|
QRegion r = view->geometry(); |
|
foreach (PanelView *v, d->panelViews) { |
|
if (v->containment()->screen() == id && v->visibilityMode() != PanelView::AutoHide) { |
|
r -= v->geometry(); |
|
} |
|
} |
|
return r; |
|
} else { |
|
//each screen should have a view |
|
qWarning() << "requesting unexisting screen" << id; |
|
QScreen *s = outputToScreen(d->screenConfiguration->primaryOutput()); |
|
return s ? s->availableGeometry() : QRegion(); |
|
} |
|
} |
|
|
|
QRect ShellCorona::availableScreenRect(int id) const |
|
{ |
|
if (id < 0) { |
|
id = 0; |
|
} |
|
|
|
QRect r(screenGeometry(id)); |
|
|
|
foreach (PanelView *view, d->panelViews) { |
|
if (view->containment()->screen() == id && view->visibilityMode() != PanelView::AutoHide) { |
|
QRect v = view->geometry(); |
|
switch (view->location()) { |
|
case Plasma::Types::TopEdge: |
|
if (v.bottom() > r.top()) { |
|
r.setTop(v.bottom()); |
|
} |
|
break; |
|
|
|
case Plasma::Types::BottomEdge: |
|
if (v.top() < r.bottom()) { |
|
r.setBottom(v.top()); |
|
} |
|
break; |
|
|
|
case Plasma::Types::LeftEdge: |
|
if (v.right() > r.left()) { |
|
r.setLeft(v.right()); |
|
} |
|
break; |
|
|
|
case Plasma::Types::RightEdge: |
|
if (v.left() < r.right()) { |
|
r.setRight(v.left()); |
|
} |
|
break; |
|
|
|
default: |
|
break; |
|
} |
|
} |
|
} |
|
|
|
return r; |
|
} |
|
|
|
PanelView *ShellCorona::panelView(Plasma::Containment *containment) const |
|
{ |
|
return d->panelViews.value(containment); |
|
} |
|
|
|
///// SLOTS |
|
|
|
void ShellCorona::addOutput(KScreen::Output *output) |
|
{ |
|
connect(output, &KScreen::Output::isEnabledChanged, this, [this, output]() { this->addOutput(output); }, Qt::UniqueConnection); |
|
if (!output->isEnabled()) |
|
return; |
|
QScreen *screen = outputToScreen(output); |
|
Q_ASSERT(screen); |
|
|
|
//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 |
|
foreach (QScreen *s, QGuiApplication::screens()) { |
|
if (s->geometry().contains(screen->geometry(), false) && |
|
s->geometry().width() > screen->geometry().width() && |
|
s->geometry().height() > screen->geometry().height()) { |
|
return; |
|
} |
|
} |
|
|
|
//let's make sure the output hasn't been added already, I think libkscreen is emitting some |
|
//signals twice |
|
foreach (DesktopView *view, d->views) { |
|
if (screen == view->screen()) { |
|
qWarning() << "trying to add output twice" << output; |
|
return; |
|
} |
|
} |
|
|
|
DesktopView *view = new DesktopView(this, screen); |
|
|
|
//We have to do it in a lambda, |
|
connect(screen, &QObject::destroyed, this, [=]() { removeDesktop(view); }); |
|
|
|
const QString currentActivity = d->activityController->currentActivity(); |
|
|
|
qDebug() << "added screen" << output; |
|
if (!d->views.isEmpty() && output->isPrimary()) { |
|
DesktopView *oldPrimaryView = d->views.first(); |
|
QScreen *oldPrimaryScreen = oldPrimaryView->screen(); |
|
|
|
//move any panels that were preivously on the old primary screen to the new primary screen |
|
foreach (PanelView *panelView, d->panelViews) { |
|
if (oldPrimaryScreen==panelView->screen()) |
|
panelView->setScreen(screen); |
|
if (view->containment()) { |
|
view->containment()->reactToScreenChange(); |
|
} |
|
} |
|
|
|
Plasma::Containment *primaryContainment = oldPrimaryView->containment(); |
|
oldPrimaryView->setContainment(0); |
|
view->setContainment(primaryContainment); |
|
|
|
d->views.prepend(view); |
|
view = oldPrimaryView; |
|
} else { |
|
Q_ASSERT(d->views.isEmpty() || !output->isPrimary()); |
|
d->views.append(view); |
|
} |
|
|
|
int screenNum = d->views.count()-1; |
|
Plasma::Containment *containment = d->desktopContainments[currentActivity][screenNum]; |
|
if (!containment) { |
|
containment = createContainmentForActivity(currentActivity, screenNum); |
|
} |
|
|
|
QAction *removeAction = containment->actions()->action("remove"); |
|
if (removeAction) { |
|
removeAction->deleteLater(); |
|
} |
|
|
|
view->setContainment(containment); |
|
|
|
// connect(screen, SIGNAL(destroyed(QObject*)), SLOT(screenRemoved(QObject*))); |
|
view->show(); |
|
|
|
//were there any panels for this screen before it popped up? |
|
if (!d->waitingPanels.isEmpty()) { |
|
d->waitingPanelsTimer.start(); |
|
} |
|
|
|
emit availableScreenRectChanged(); |
|
emit availableScreenRegionChanged(); |
|
|
|
screenInvariants(); |
|
} |
|
|
|
void ShellCorona::removeDesktop(DesktopView *view) |
|
{ |
|
int idx = d->views.indexOf(view); |
|
DesktopView *lastView = d->views.takeAt(d->views.count()-1); |
|
lastView->containment()->reactToScreenChange(); |
|
lastView->deleteLater(); |
|
|
|
for (int i = idx; i<d->views.count()-1; ++i) { |
|
d->views[i]->setScreen(d->views[i+1]->screen()); |
|
d->views[i]->adaptToScreen(); |
|
} |
|
|
|
screenInvariants(); |
|
} |
|
|
|
void ShellCorona::removePanel(QObject *c) |
|
{ |
|
Plasma::Containment *cont = qobject_cast<Plasma::Containment*>(c); |
|
|
|
d->panelViews[cont]->deleteLater(); |
|
d->waitingPanels << cont; |
|
d->panelViews.remove(cont); |
|
} |
|
|
|
Plasma::Containment *ShellCorona::createContainmentForActivity(const QString& activity, int screenNum) |
|
{ |
|
if (d->desktopContainments.contains(activity) && |
|
d->desktopContainments[activity].contains(screenNum) && |
|
d->desktopContainments.value(activity).value(screenNum)) { |
|
return d->desktopContainments[activity][screenNum]; |
|
} |
|
|
|
QString plugin = "org.kde.desktopcontainment"; |
|
if (d->activities.contains(activity)) { |
|
// plugin = d->activities.value(activity)->defaultPlugin(); |
|
} |
|
|
|
Plasma::Containment *containment = createContainment(d->desktopDefaultsConfig.readEntry("Containment", plugin)); |
|
containment->setActivity(activity); |
|
insertContainment(activity, screenNum, containment); |
|
|
|
return containment; |
|
} |
|
|
|
void ShellCorona::createWaitingPanels() |
|
{ |
|
QList<Plasma::Containment *> stillWaitingPanels; |
|
|
|
foreach (Plasma::Containment *cont, d->waitingPanels) { |
|
//ignore non existing (yet?) screens |
|
int requestedScreen = cont->lastScreen(); |
|
if (requestedScreen < 0) |
|
++requestedScreen; |
|
|
|
if (requestedScreen > (d->views.size() - 1)) { |
|
stillWaitingPanels << cont; |
|
continue; |
|
} |
|
|
|
d->panelViews[cont] = new PanelView(this); |
|
|
|
Q_ASSERT(qBound(0, requestedScreen, d->views.size() -1) == requestedScreen); |
|
QScreen *screen = d->views[requestedScreen]->screen(); |
|
|
|
d->panelViews[cont]->setContainment(cont); |
|
d->panelViews[cont]->setScreen(screen); |
|
|
|
connect(cont, SIGNAL(destroyed(QObject*)), this, SLOT(containmentDeleted(QObject*))); |
|
connect(screen, SIGNAL(destroyed(QObject*)), this, SLOT(removePanel(QObject*))); |
|
} |
|
d->waitingPanels.clear(); |
|
d->waitingPanels << stillWaitingPanels; |
|
emit availableScreenRectChanged(); |
|
emit availableScreenRegionChanged(); |
|
} |
|
|
|
void ShellCorona::containmentDeleted(QObject *cont) |
|
{ |
|
d->panelViews.remove(static_cast<Plasma::Containment*>(cont)); |
|
emit availableScreenRectChanged(); |
|
emit availableScreenRegionChanged(); |
|
} |
|
|
|
void ShellCorona::handleContainmentAdded(Plasma::Containment *c) |
|
{ |
|
connect(c, &Plasma::Containment::showAddWidgetsInterface, |
|
this, &ShellCorona::toggleWidgetExplorer); |
|
} |
|
|
|
void ShellCorona::toggleWidgetExplorer() |
|
{ |
|
const QPoint cursorPos = QCursor::pos(); |
|
foreach (DesktopView *view, d->views) { |
|
if (view->screen()->geometry().contains(cursorPos)) { |
|
//The view QML has to provide something to display the activity 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, d->views) { |
|
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"); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
void ShellCorona::syncAppConfig() |
|
{ |
|
qDebug() << "Syncing plasma-shellrc config"; |
|
applicationConfig()->sync(); |
|
} |
|
|
|
void ShellCorona::setDashboardShown(bool show) |
|
{ |
|
qDebug() << "TODO: Toggling dashboard view"; |
|
|
|
QAction *dashboardAction = actions()->action("show dashboard"); |
|
|
|
if (dashboardAction) { |
|
dashboardAction->setText(show ? i18n("Hide Dashboard") : i18n("Show Dashboard")); |
|
} |
|
|
|
foreach (DesktopView *view, d->views) { |
|
view->setDashboardShown(show); |
|
} |
|
} |
|
|
|
void ShellCorona::toggleDashboard() |
|
{ |
|
foreach (DesktopView *view, d->views) { |
|
view->setDashboardShown(!view->isDashboardShown()); |
|
} |
|
} |
|
|
|
void ShellCorona::showInteractiveConsole() |
|
{ |
|
if (KSharedConfig::openConfig()->isImmutable() || !KAuthorized::authorize("plasma-desktop/scripting_console")) { |
|
return; |
|
} |
|
|
|
#if HAVE_KTEXTEDITOR |
|
InteractiveConsole *console = d->console.data(); |
|
if (!console) { |
|
d->console = console = new InteractiveConsole(this); |
|
} |
|
d->console.data()->setMode(InteractiveConsole::PlasmaConsole); |
|
|
|
KWindowSystem::setOnDesktop(console->winId(), KWindowSystem::currentDesktop()); |
|
console->show(); |
|
console->raise(); |
|
KWindowSystem::forceActiveWindow(console->winId()); |
|
#endif |
|
} |
|
|
|
void ShellCorona::loadScriptInInteractiveConsole(const QString &script) |
|
{ |
|
#if HAVE_KTEXTEDITOR |
|
showInteractiveConsole(); |
|
if (d->console) { |
|
d->console.data()->loadScript(script); |
|
} |
|
#endif |
|
} |
|
|
|
void ShellCorona::checkActivities() |
|
{ |
|
KActivities::Consumer::ServiceStatus status = d->activityController->serviceStatus(); |
|
//qDebug() << "$%$%$#%$%$%Status:" << status; |
|
if (status != KActivities::Consumer::Running) { |
|
//panic and give up - better than causing a mess |
|
qDebug() << "ShellCorona::checkActivities is called whilst activity daemon is still connecting"; |
|
return; |
|
} |
|
|
|
QStringList existingActivities = d->activityConsumer->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; |
|
|
|
for (int i = 0; i < d->views.count(); ++i) { |
|
Plasma::Containment *c = d->desktopContainments[newActivity][i]; |
|
if (!c) { |
|
c = createContainmentForActivity(newActivity, i); |
|
} |
|
QAction *removeAction = c->actions()->action("remove"); |
|
if (removeAction) { |
|
removeAction->deleteLater(); |
|
} |
|
d->views[i]->setContainment(c); |
|
} |
|
} |
|
|
|
void ShellCorona::activityAdded(const QString &id) |
|
{ |
|
//TODO more sanity checks |
|
if (d->activities.contains(id)) { |
|
qDebug() << "you're late." << id; |
|
return; |
|
} |
|
|
|
Activity *a = new Activity(id, this); |
|
d->activities.insert(id, a); |
|
} |
|
|
|
void ShellCorona::activityRemoved(const QString &id) |
|
{ |
|
Activity *a = d->activities.take(id); |
|
a->deleteLater(); |
|
} |
|
|
|
Activity *ShellCorona::activity(const QString &id) |
|
{ |
|
return d->activities.value(id); |
|
} |
|
|
|
void ShellCorona::insertActivity(const QString &id, Activity *activity) |
|
{ |
|
d->activities.insert(id, activity); |
|
Plasma::Containment *c = createContainmentForActivity(id, -1); |
|
c->config().writeEntry("lastScreen", 0); |
|
} |
|
|
|
Plasma::Containment *ShellCorona::setContainmentTypeForScreen(int screen, const QString &plugin) |
|
{ |
|
Plasma::Containment *oldContainment = containmentForScreen(screen); |
|
|
|
//no valid containment in given screen, giving up |
|
if (!oldContainment) { |
|
return 0; |
|
} |
|
if (plugin.isEmpty()) { |
|
return oldContainment; |
|
} |
|
|
|
DesktopView *view = 0; |
|
foreach (DesktopView *v, d->views) { |
|
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->pluginInfo().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 |
|
KConfigGroup newCg(KSharedConfig::openConfig(oldCg.config()->name()), "Containments"); |
|
newCg = KConfigGroup(&newCg, QString::number(newContainment->id())); |
|
|
|
foreach (const QString &group, oldCg.groupList()) { |
|
if (group != "Applets") { |
|
KConfigGroup subGroup(&oldCg, group); |
|
KConfigGroup newSubGroup(&newCg, group); |
|
subGroup.copyTo(&newSubGroup); |
|
} |
|
} |
|
|
|
newContainment->init(); |
|
newContainment->restore(newCg); |
|
newContainment->updateConstraints(Plasma::Types::StartupCompletedConstraint); |
|
newContainment->save(newCg); |
|
requestConfigSync(); |
|
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("remove"); |
|
if (removeAction) { |
|
removeAction->deleteLater(); |
|
} |
|
view->setContainment(newContainment); |
|
newContainment->setActivity(oldContainment->activity()); |
|
insertContainment(oldContainment->activity(), screen, newContainment); |
|
oldContainment->destroy(); |
|
|
|
return newContainment; |
|
} |
|
|
|
void ShellCorona::checkAddPanelAction(const QStringList &sycocaChanges) |
|
{ |
|
if (!sycocaChanges.isEmpty() && !sycocaChanges.contains("services")) { |
|
return; |
|
} |
|
|
|
delete d->addPanelAction; |
|
d->addPanelAction = 0; |
|
|
|
delete d->addPanelsMenu; |
|
d->addPanelsMenu = 0; |
|
|
|
KPluginInfo::List panelContainmentPlugins = Plasma::PluginLoader::listContainmentsOfType("Panel"); |
|
const QString constraint = QString("[X-Plasma-Shell] == '%1' and 'panel' ~in [X-Plasma-ContainmentCategories]") |
|
.arg(qApp->applicationName()); |
|
KService::List templates = KServiceTypeTrader::self()->query("Plasma/LayoutTemplate", constraint); |
|
|
|
if (panelContainmentPlugins.count() + templates.count() == 1) { |
|
d->addPanelAction = new QAction(i18n("Add Panel"), this); |
|
d->addPanelAction->setData(Plasma::Types::AddAction); |
|
connect(d->addPanelAction, SIGNAL(triggered(bool)), this, SLOT(addPanel())); |
|
} else if (!panelContainmentPlugins.isEmpty()) { |
|
d->addPanelsMenu = new QMenu; |
|
d->addPanelAction = d->addPanelsMenu->menuAction(); |
|
d->addPanelAction->setText(i18n("Add Panel")); |
|
d->addPanelAction->setData(Plasma::Types::AddAction); |
|
qDebug() << "populateAddPanelsMenu" << panelContainmentPlugins.count(); |
|
connect(d->addPanelsMenu, SIGNAL(aboutToShow()), this, SLOT(populateAddPanelsMenu())); |
|
connect(d->addPanelsMenu, SIGNAL(triggered(QAction*)), this, SLOT(addPanel(QAction*))); |
|
} |
|
|
|
if (d->addPanelAction) { |
|
d->addPanelAction->setIcon(QIcon::fromTheme("list-add")); |
|
actions()->addAction("add panel", d->addPanelAction); |
|
} |
|
} |
|
|
|
void ShellCorona::addPanel() |
|
{ |
|
KPluginInfo::List panelPlugins = Plasma::PluginLoader::listContainmentsOfType("Panel"); |
|
|
|
if (!panelPlugins.isEmpty()) { |
|
addPanel(panelPlugins.first().pluginName()); |
|
} |
|
} |
|
|
|
void ShellCorona::addPanel(QAction *action) |
|
{ |
|
const QString plugin = action->data().toString(); |
|
if (plugin.startsWith("plasma-desktop-template:")) { |
|
d->scriptEngine->evaluateScript(plugin.right(plugin.length() - qstrlen("plasma-desktop-template:"))); |
|
} else if (!plugin.isEmpty()) { |
|
addPanel(plugin); |
|
} |
|
} |
|
|
|
void ShellCorona::addPanel(const QString &plugin) |
|
{ |
|
Plasma::Containment *panel = createContainment(plugin); |
|
if (!panel) { |
|
return; |
|
} |
|
|
|
QList<Plasma::Types::Location> availableLocations; |
|
availableLocations << Plasma::Types::LeftEdge << Plasma::Types::TopEdge << Plasma::Types::RightEdge << Plasma::Types::BottomEdge; |
|
|
|
foreach (const Plasma::Containment *cont, d->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; |
|
} |
|
|
|
d->waitingPanels << panel; |
|
createWaitingPanels(); |
|
Q_ASSERT(d->panelViews.contains(panel)); |
|
const QPoint cursorPos(QCursor::pos()); |
|
foreach (QScreen *screen, QGuiApplication::screens()) { |
|
if (screen->geometry().contains(cursorPos)) { |
|
d->panelViews[panel]->setScreen(screen); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
int ShellCorona::screenForContainment(const Plasma::Containment *containment) const |
|
{ |
|
for (int i = 0; i < d->views.size(); i++) { |
|
if (d->views[i]->containment() == containment) { |
|
return i; |
|
} |
|
} |
|
|
|
PanelView *view = d->panelViews.value(containment); |
|
if (view) { |
|
QScreen *screen = view->screen(); |
|
for (int i = 0; i < d->views.size(); i++) { |
|
if (d->views[i]->screen() == screen) { |
|
return i; |
|
} |
|
} |
|
} |
|
Q_ASSERT(false); |
|
return -1; |
|
} |
|
|
|
void ShellCorona::activityOpened() |
|
{ |
|
Activity *activity = qobject_cast<Activity *>(sender()); |
|
if (activity) { |
|
QList<Plasma::Containment*> cs = importLayout(activity->config()); |
|
for (Plasma::Containment *containment : cs) { |
|
insertContainment(activity->name(), containment->lastScreen(), containment); |
|
} |
|
} |
|
} |
|
|
|
void ShellCorona::activityClosed() |
|
{ |
|
Activity *activity = qobject_cast<Activity *>(sender()); |
|
if (activity) { |
|
KConfigGroup cg = activity->config(); |
|
exportLayout(cg, d->desktopContainments.value(activity->name()).values()); |
|
} |
|
} |
|
|
|
void ShellCorona::activityRemoved() |
|
{ |
|
//when an activity is removed delete all associated desktop containments |
|
Activity *activity = qobject_cast<Activity *>(sender()); |
|
if (activity) { |
|
QHash< int, Plasma::Containment* > containmentHash = d->desktopContainments.take(activity->name()); |
|
for (auto a : containmentHash) { |
|
a->destroy(); |
|
} |
|
} |
|
} |
|
|
|
void ShellCorona::insertContainment(const QString &activity, int screenNum, Plasma::Containment *containment) |
|
{ |
|
d->desktopContainments[activity][screenNum] = containment; |
|
|
|
//when a containment gets deleted update our map of containments |
|
connect(containment, SIGNAL(destroyed(QObject*)), this, SLOT(desktopContainmentDestroyed(QObject*))); |
|
} |
|
|
|
void ShellCorona::desktopContainmentDestroyed(QObject *obj) |
|
{ |
|
// when QObject::destroyed arrives, ~Plasma::Containment has run, |
|
// members of Containment are not accessible anymore, |
|
// so keep ugly bookeeping by hand |
|
auto containment = static_cast<Plasma::Containment*>(obj); |
|
for (auto a : d->desktopContainments) { |
|
QMutableHashIterator<int, Plasma::Containment *> it(a); |
|
while (it.hasNext()) { |
|
it.next(); |
|
if (it.value() == containment) { |
|
it.remove(); |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
|
|
Plasma::Package ShellCorona::lookAndFeelPackage() const |
|
{ |
|
if (!d->lookNFeelPackage.isValid()) { |
|
d->lookNFeelPackage = ShellPluginLoader::self()->loadPackage("Plasma/LookAndFeel"); |
|
//TODO: make loading from config once we have some UI for setting the package |
|
d->lookNFeelPackage.setPath("org.kde.lookandfeel"); |
|
} |
|
|
|
return d->lookNFeelPackage; |
|
} |
|
|
|
|
|
// Desktop corona handler |
|
|
|
|
|
#include "moc_shellcorona.cpp" |
|
|
|
|