File indexing completed on 2024-04-28 16:55:02
0001 /* 0002 SPDX-FileCopyrightText: 2008 Aaron Seigo <aseigo@kde.org> 0003 SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org> 0004 SPDX-FileCopyrightText: 2013 Ivan Cukic <ivan.cukic@kde.org> 0005 SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "shellcorona.h" 0011 #include "debug.h" 0012 #include "strutmanager.h" 0013 0014 #include <config-plasma.h> 0015 0016 #include <QApplication> 0017 #include <QDBusConnection> 0018 #include <QDebug> 0019 #include <QMenu> 0020 #include <QQmlContext> 0021 #include <QQuickItemGrabResult> 0022 #include <QScreen> 0023 #include <QUrl> 0024 0025 #include <QJsonDocument> 0026 #include <QJsonObject> 0027 0028 #include <Plasma/Package> 0029 #include <Plasma/PluginLoader> 0030 #include <PlasmaQuick/Dialog> 0031 #include <kactioncollection.h> 0032 #include <klocalizedstring.h> 0033 0034 #include <KAuthorized> 0035 #include <KGlobalAccel> 0036 #include <KMessageBox> 0037 #include <KWindowSystem> 0038 #include <KX11Extras> 0039 #include <kactivities/consumer.h> 0040 #include <kactivities/controller.h> 0041 #include <kdeclarative/kdeclarative.h> 0042 #include <kdeclarative/qmlobjectsharedengine.h> 0043 #include <kdirwatch.h> 0044 #include <ksycoca.h> 0045 0046 #include <KPackage/PackageLoader> 0047 0048 #include <KWayland/Client/connection_thread.h> 0049 #include <KWayland/Client/plasmashell.h> 0050 #include <KWayland/Client/plasmawindowmanagement.h> 0051 #include <KWayland/Client/registry.h> 0052 #include <plasma/plasma.h> 0053 0054 #include "config-ktexteditor.h" // HAVE_KTEXTEDITOR 0055 0056 #include "alternativeshelper.h" 0057 #include "desktopview.h" 0058 #include "osd.h" 0059 #include "panelview.h" 0060 #include "screenpool.h" 0061 #if USE_SCRIPTING 0062 #include "scripting/scriptengine.h" 0063 #endif 0064 #include "shellcontainmentconfig.h" 0065 0066 #include "debug.h" 0067 #include "futureutil.h" 0068 #include "plasmashelladaptor.h" 0069 0070 #ifndef NDEBUG 0071 #define CHECK_SCREEN_INVARIANTS screenInvariants(); 0072 #else 0073 #define CHECK_SCREEN_INVARIANTS 0074 #endif 0075 0076 #if HAVE_X11 0077 #include <NETWM> 0078 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0079 #include <private/qtx11extras_p.h> 0080 #else 0081 #include <QX11Info> 0082 #endif 0083 #include <xcb/xcb.h> 0084 #endif 0085 #include <chrono> 0086 0087 using namespace std::chrono_literals; 0088 static const int s_configSyncDelay = 10000; // 10 seconds 0089 0090 ShellCorona::ShellCorona(QObject *parent) 0091 : Plasma::Corona(parent) 0092 , m_config(KSharedConfig::openConfig(QStringLiteral("plasmarc"))) 0093 , m_screenPool(new ScreenPool(this)) 0094 , m_activityController(new KActivities::Controller(this)) 0095 , m_addPanelAction(nullptr) 0096 , m_addPanelsMenu(nullptr) 0097 , m_waylandPlasmaShell(nullptr) 0098 , m_closingDown(false) 0099 , m_strutManager(new StrutManager(this)) 0100 , m_shellContainmentConfig(nullptr) 0101 { 0102 setupWaylandIntegration(); 0103 qmlRegisterUncreatableType<DesktopView>("org.kde.plasma.shell", 2, 0, "Desktop", QStringLiteral("It is not possible to create objects of type Desktop")); 0104 qmlRegisterUncreatableType<PanelView>("org.kde.plasma.shell", 2, 0, "Panel", QStringLiteral("It is not possible to create objects of type Panel")); 0105 0106 KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), "KDE"); 0107 const QString packageName = cg.readEntry("LookAndFeelPackage", QString()); 0108 m_lookAndFeelPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel"), packageName); 0109 0110 // Accent color setting 0111 KSharedConfigPtr globalConfig = KSharedConfig::openConfig(); 0112 KConfigGroup accentColorConfigGroup(globalConfig, "General"); 0113 m_accentColorFromWallpaperEnabled = accentColorConfigGroup.readEntry("accentColorFromWallpaper", false); 0114 0115 m_accentColorConfigWatcher = KConfigWatcher::create(globalConfig); 0116 connect(m_accentColorConfigWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) { 0117 if (names.contains(QByteArrayLiteral("accentColorFromWallpaper"))) { 0118 const bool result = group.readEntry("accentColorFromWallpaper", false); 0119 if (m_accentColorFromWallpaperEnabled != result) { 0120 m_accentColorFromWallpaperEnabled = result; 0121 Q_EMIT accentColorFromWallpaperEnabledChanged(); 0122 } 0123 } 0124 }); 0125 } 0126 0127 void ShellCorona::init() 0128 { 0129 #if USE_SCRIPTING 0130 connect(this, &Plasma::Corona::containmentCreated, this, [this](Plasma::Containment *c) { 0131 executeSetupPlasmoidScript(c, c); 0132 }); 0133 #endif 0134 0135 connect(this, &Plasma::Corona::availableScreenRectChanged, this, &Plasma::Corona::availableScreenRegionChanged); 0136 0137 m_appConfigSyncTimer.setSingleShot(true); 0138 m_appConfigSyncTimer.setInterval(s_configSyncDelay); 0139 connect(&m_appConfigSyncTimer, &QTimer::timeout, this, &ShellCorona::syncAppConfig); 0140 // 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 0141 // leave containments pointing to the correct screens 0142 connect(this, &Corona::configSynced, this, &ShellCorona::syncAppConfig); 0143 0144 m_waitingPanelsTimer.setSingleShot(true); 0145 m_waitingPanelsTimer.setInterval(250ms); 0146 connect(&m_waitingPanelsTimer, &QTimer::timeout, this, &ShellCorona::createWaitingPanels); 0147 0148 #ifndef NDEBUG 0149 m_invariantsTimer.setSingleShot(true); 0150 m_invariantsTimer.setInterval(250ms); 0151 connect(&m_invariantsTimer, &QTimer::timeout, this, &ShellCorona::screenInvariants); 0152 #endif 0153 0154 m_desktopDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(kPackage().filePath("defaults")), "Desktop"); 0155 m_lnfDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(m_lookAndFeelPackage.filePath("defaults")), "Desktop"); 0156 m_lnfDefaultsConfig = KConfigGroup(&m_lnfDefaultsConfig, QStringLiteral("org.kde.plasma.desktop")); 0157 0158 new PlasmaShellAdaptor(this); 0159 0160 QDBusConnection dbus = QDBusConnection::sessionBus(); 0161 dbus.registerObject(QStringLiteral("/PlasmaShell"), this); 0162 0163 // Look for theme config in plasmarc, if it isn't configured, take the theme from the 0164 // LookAndFeel package, if either is set, change the default theme 0165 0166 connect(qApp, &QCoreApplication::aboutToQuit, this, [this]() { 0167 // saveLayout is a slot but arguments not compatible 0168 m_closingDown = true; 0169 saveLayout(); 0170 }); 0171 0172 connect(this, &ShellCorona::containmentAdded, this, &ShellCorona::handleContainmentAdded); 0173 0174 QAction *dashboardAction = actions()->addAction(QStringLiteral("show dashboard")); 0175 QObject::connect(dashboardAction, &QAction::triggered, this, &ShellCorona::setDashboardShown); 0176 dashboardAction->setText(i18n("Show Desktop")); 0177 connect(KWindowSystem::self(), &KWindowSystem::showingDesktopChanged, dashboardAction, [dashboardAction](bool showing) { 0178 dashboardAction->setText(showing ? i18n("Hide Desktop") : i18n("Show Desktop")); 0179 dashboardAction->setChecked(showing); 0180 }); 0181 0182 dashboardAction->setAutoRepeat(true); 0183 dashboardAction->setCheckable(true); 0184 dashboardAction->setIcon(QIcon::fromTheme(QStringLiteral("dashboard-show"))); 0185 KGlobalAccel::self()->setGlobalShortcut(dashboardAction, Qt::CTRL | Qt::Key_F12); 0186 0187 checkAddPanelAction(); 0188 connect(KSycoca::self(), &KSycoca::databaseChanged, this, &ShellCorona::checkAddPanelAction); 0189 0190 // Activity stuff 0191 QAction *activityAction = actions()->addAction(QStringLiteral("manage activities")); 0192 connect(activityAction, &QAction::triggered, this, &ShellCorona::toggleActivityManager); 0193 activityAction->setText(i18n("Show Activity Switcher")); 0194 activityAction->setIcon(QIcon::fromTheme(QStringLiteral("activities"))); 0195 activityAction->setShortcut(QKeySequence(QStringLiteral("alt+d, alt+a"))); 0196 activityAction->setShortcutContext(Qt::ApplicationShortcut); 0197 0198 KGlobalAccel::self()->setGlobalShortcut(activityAction, Qt::META | Qt::Key_Q); 0199 0200 QAction *stopActivityAction = actions()->addAction(QStringLiteral("stop current activity")); 0201 QObject::connect(stopActivityAction, &QAction::triggered, this, &ShellCorona::stopCurrentActivity); 0202 0203 stopActivityAction->setText(i18n("Stop Current Activity")); 0204 stopActivityAction->setVisible(false); 0205 0206 KGlobalAccel::self()->setGlobalShortcut(stopActivityAction, Qt::META | Qt::Key_S); 0207 0208 QAction *previousActivityAction = actions()->addAction(QStringLiteral("switch to previous activity")); 0209 connect(previousActivityAction, &QAction::triggered, this, &ShellCorona::previousActivity); 0210 previousActivityAction->setText(i18n("Switch to Previous Activity")); 0211 previousActivityAction->setShortcutContext(Qt::ApplicationShortcut); 0212 0213 KGlobalAccel::self()->setGlobalShortcut(previousActivityAction, QKeySequence()); 0214 0215 QAction *nextActivityAction = actions()->addAction(QStringLiteral("switch to next activity")); 0216 connect(nextActivityAction, &QAction::triggered, this, &ShellCorona::nextActivity); 0217 nextActivityAction->setText(i18n("Switch to Next Activity")); 0218 nextActivityAction->setShortcutContext(Qt::ApplicationShortcut); 0219 0220 KGlobalAccel::self()->setGlobalShortcut(nextActivityAction, QKeySequence()); 0221 0222 connect(m_activityController, &KActivities::Controller::currentActivityChanged, this, &ShellCorona::currentActivityChanged); 0223 connect(m_activityController, &KActivities::Controller::activityAdded, this, &ShellCorona::activityAdded); 0224 connect(m_activityController, &KActivities::Controller::activityRemoved, this, &ShellCorona::activityRemoved); 0225 0226 KActionCollection *taskbarActions = new KActionCollection(this); 0227 for (int i = 0; i < 10; ++i) { 0228 const int entryNumber = i + 1; 0229 const Qt::Key key = static_cast<Qt::Key>(Qt::Key_0 + (entryNumber % 10)); 0230 0231 QAction *action = taskbarActions->addAction(QStringLiteral("activate task manager entry %1").arg(QString::number(entryNumber))); 0232 action->setText(i18n("Activate Task Manager Entry %1", entryNumber)); 0233 KGlobalAccel::setGlobalShortcut(action, QKeySequence(Qt::META + key)); 0234 connect(action, &QAction::triggered, this, [this, i] { 0235 activateTaskManagerEntry(i); 0236 }); 0237 } 0238 0239 new Osd(m_config, this); 0240 0241 // catch when plasmarc changes, so we e.g. enable/disable the OSd 0242 m_configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + m_config->name(); 0243 KDirWatch::self()->addFile(m_configPath); 0244 connect(KDirWatch::self(), &KDirWatch::dirty, this, &ShellCorona::configurationChanged); 0245 connect(KDirWatch::self(), &KDirWatch::created, this, &ShellCorona::configurationChanged); 0246 0247 connect(qApp, &QGuiApplication::focusWindowChanged, this, [this](QWindow *focusWindow) { 0248 if (!focusWindow) { 0249 setEditMode(false); 0250 } 0251 }); 0252 connect(this, &ShellCorona::editModeChanged, this, [this](bool edit) { 0253 setDashboardShown(edit); 0254 }); 0255 0256 QAction *manageContainmentsAction = actions()->addAction(QStringLiteral("manage-containments")); 0257 manageContainmentsAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-windows-effect-fadedesktop"))); 0258 manageContainmentsAction->setText(i18n("Manage Desktops And Panels...")); 0259 connect(manageContainmentsAction, &QAction::triggered, this, [this]() { 0260 if (m_shellContainmentConfig == nullptr) { 0261 m_shellContainmentConfig = new ShellContainmentConfig(this); 0262 m_shellContainmentConfig->init(); 0263 } 0264 // Swapping desktop views around causes problems with the show desktop effect 0265 setEditMode(false); 0266 }); 0267 auto updateManageContainmentsVisiblility = [this, manageContainmentsAction]() { 0268 QSet<int> allScreenIds; 0269 for (auto *cont : containments()) { 0270 allScreenIds.insert(cont->lastScreen()); 0271 } 0272 manageContainmentsAction->setVisible(allScreenIds.count() > 1); 0273 }; 0274 connect(this, &ShellCorona::containmentAdded, this, updateManageContainmentsVisiblility); 0275 connect(this, &ShellCorona::screenRemoved, this, updateManageContainmentsVisiblility); 0276 updateManageContainmentsVisiblility(); 0277 0278 QAction *cyclePanelFocusAction = actions()->addAction(QStringLiteral("cycle-panels")); 0279 cyclePanelFocusAction->setText(i18n("Move keyboard focus between panels")); 0280 KGlobalAccel::self()->setGlobalShortcut(cyclePanelFocusAction, Qt::META | Qt::ALT | Qt::Key_P); 0281 connect(cyclePanelFocusAction, &QAction::triggered, this, &ShellCorona::slotCyclePanelFocus); 0282 0283 unload(); 0284 /* 0285 * we want to make an initial load once we have loaded the activities _IF_ KAMD is running 0286 * it is valid for KAMD to not be running. 0287 * 0288 * Potentially 2 async jobs 0289 * 0290 * It might seem that we only need this connection if the activityConsumer is currently in state Unknown, however 0291 * there is an issue where m_activityController will start the kactivitymanagerd, as KAMD is starting the serviceStatus will be "not running" 0292 * Whilst we are loading the kscreen config, the event loop runs and we might find KAMD has started. 0293 * m_activityController will change from "not running" to unknown, and might still be unknown when the kscreen fetching is complete. 0294 * 0295 * if that happens we want to continue monitoring for state changes, and only finally load when it is up. 0296 * 0297 * See https://bugs.kde.org/show_bug.cgi?id=342431 be careful about changing 0298 * 0299 * 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 0300 */ 0301 connect(m_activityController, &KActivities::Controller::serviceStatusChanged, this, &ShellCorona::load, Qt::UniqueConnection); 0302 load(); 0303 } 0304 0305 ShellCorona::~ShellCorona() 0306 { 0307 while (!containments().isEmpty()) { 0308 // Deleting a containment will remove it from the list due to QObject::destroyed connect in Corona 0309 // Deleting a containment in turn also kills any panel views 0310 delete containments().constFirst(); 0311 } 0312 } 0313 0314 KPackage::Package ShellCorona::lookAndFeelPackage() 0315 { 0316 return m_lookAndFeelPackage; 0317 } 0318 0319 void ShellCorona::setShell(const QString &shell) 0320 { 0321 if (m_shell == shell) { 0322 return; 0323 } 0324 0325 m_shell = shell; 0326 KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Shell")); 0327 package.setPath(shell); 0328 package.setAllowExternalPaths(true); 0329 setKPackage(package); 0330 m_desktopDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(package.filePath("defaults")), "Desktop"); 0331 m_lnfDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(m_lookAndFeelPackage.filePath("defaults")), "Desktop"); 0332 m_lnfDefaultsConfig = KConfigGroup(&m_lnfDefaultsConfig, shell); 0333 0334 const QString themeGroupKey = QStringLiteral("Theme"); 0335 const QString themeNameKey = QStringLiteral("name"); 0336 0337 QString themeName; 0338 0339 KConfigGroup plasmarc(m_config, themeGroupKey); 0340 themeName = plasmarc.readEntry(themeNameKey, themeName); 0341 0342 if (themeName.isEmpty()) { 0343 KConfigGroup shellCfg = KConfigGroup(KSharedConfig::openConfig(package.filePath("defaults")), "Theme"); 0344 themeName = shellCfg.readEntry(themeNameKey, "default"); 0345 } 0346 0347 if (!themeName.isEmpty()) { 0348 Plasma::Theme *t = new Plasma::Theme(this); 0349 t->setThemeName(themeName); 0350 } 0351 } 0352 0353 QJsonObject dumpconfigGroupJS(const KConfigGroup &rootGroup) 0354 { 0355 QJsonObject result; 0356 0357 QStringList hierarchy; 0358 QStringList escapedHierarchy; 0359 QList<KConfigGroup> groups{rootGroup}; 0360 QSet<QString> visitedNodes; 0361 0362 const QSet<QString> forbiddenKeys{QStringLiteral("activityId"), 0363 QStringLiteral("ItemsGeometries"), 0364 QStringLiteral("AppletOrder"), 0365 QStringLiteral("SystrayContainmentId"), 0366 QStringLiteral("location"), 0367 QStringLiteral("plugin")}; 0368 0369 auto groupID = [&escapedHierarchy]() { 0370 return '/' + escapedHierarchy.join('/'); 0371 }; 0372 0373 // Perform a depth-first tree traversal for config groups 0374 while (!groups.isEmpty()) { 0375 KConfigGroup cg = groups.last(); 0376 0377 KConfigGroup parentCg = cg; 0378 // FIXME: name is not enough 0379 0380 hierarchy.clear(); 0381 escapedHierarchy.clear(); 0382 while (parentCg.isValid() && parentCg.name() != rootGroup.name()) { 0383 const auto name = parentCg.name(); 0384 hierarchy.prepend(name); 0385 escapedHierarchy.prepend(QString::fromUtf8(QUrl::toPercentEncoding(name.toUtf8()))); 0386 parentCg = parentCg.parent(); 0387 } 0388 0389 visitedNodes.insert(groupID()); 0390 groups.pop_back(); 0391 0392 QJsonObject configGroupJson; 0393 0394 if (!cg.keyList().isEmpty()) { 0395 // TODO: this is conditional if applet or containment 0396 0397 const auto map = cg.entryMap(); 0398 auto i = map.cbegin(); 0399 for (; i != map.cend(); ++i) { 0400 // some blacklisted keys we don't want to save 0401 if (!forbiddenKeys.contains(i.key())) { 0402 configGroupJson.insert(i.key(), i.value()); 0403 } 0404 } 0405 } 0406 0407 const auto groupList = cg.groupList(); 0408 for (const QString &groupName : groupList) { 0409 if (groupName == QLatin1String("Applets") || visitedNodes.contains(groupID() + '/' + groupName)) { 0410 continue; 0411 } 0412 groups << KConfigGroup(&cg, groupName); 0413 } 0414 0415 if (!configGroupJson.isEmpty()) { 0416 result.insert(groupID(), configGroupJson); 0417 } 0418 } 0419 0420 return result; 0421 } 0422 0423 QByteArray ShellCorona::dumpCurrentLayoutJS() const 0424 { 0425 QJsonObject root; 0426 root.insert("serializationFormatVersion", "1"); 0427 0428 // same gridUnit calculation as ScriptEngine 0429 int gridUnit = QFontMetrics(QGuiApplication::font()).boundingRect(QStringLiteral("M")).height(); 0430 if (gridUnit % 2 != 0) { 0431 gridUnit++; 0432 } 0433 0434 auto isPanel = [](Plasma::Containment *cont) { 0435 return (cont->formFactor() == Plasma::Types::Horizontal || cont->formFactor() == Plasma::Types::Vertical) 0436 && (cont->location() == Plasma::Types::TopEdge || cont->location() == Plasma::Types::BottomEdge || cont->location() == Plasma::Types::LeftEdge 0437 || cont->location() == Plasma::Types::RightEdge) 0438 && cont->pluginMetaData().pluginId() != QLatin1String("org.kde.plasma.private.systemtray"); 0439 }; 0440 0441 auto isDesktop = [](Plasma::Containment *cont) { 0442 return !cont->activity().isEmpty(); 0443 }; 0444 0445 const auto containments = ShellCorona::containments(); 0446 0447 // Collecting panels 0448 0449 QJsonArray panelsJsonArray; 0450 0451 for (Plasma::Containment *cont : containments) { 0452 if (!isPanel(cont)) { 0453 continue; 0454 } 0455 0456 QJsonObject panelJson; 0457 0458 const PanelView *view = m_panelViews.value(cont); 0459 const auto location = cont->location(); 0460 0461 panelJson.insert("location", 0462 location == Plasma::Types::TopEdge ? "top" 0463 : location == Plasma::Types::LeftEdge ? "left" 0464 : location == Plasma::Types::RightEdge ? "right" 0465 : /* Plasma::Types::BottomEdge */ "bottom"); 0466 0467 const qreal height = 0468 // If we do not have a panel, fallback to 4 units 0469 !view ? 4 : (qreal)view->totalThickness() / gridUnit; 0470 0471 panelJson.insert("height", height); 0472 if (view) { 0473 const auto alignment = view->alignment(); 0474 panelJson.insert("maximumLength", (qreal)view->maximumLength() / gridUnit); 0475 panelJson.insert("minimumLength", (qreal)view->minimumLength() / gridUnit); 0476 panelJson.insert("offset", (qreal)view->offset() / gridUnit); 0477 panelJson.insert("alignment", alignment == Qt::AlignRight ? "right" : alignment == Qt::AlignCenter ? "center" : "left"); 0478 switch (view->visibilityMode()) { 0479 case PanelView::AutoHide: 0480 panelJson.insert("hiding", "autohide"); 0481 break; 0482 case PanelView::LetWindowsCover: 0483 panelJson.insert("hiding", "windowscover"); 0484 break; 0485 case PanelView::WindowsGoBelow: 0486 panelJson.insert("hiding", "windowsbelow"); 0487 break; 0488 case PanelView::NormalPanel: 0489 default: 0490 panelJson.insert("hiding", "normal"); 0491 break; 0492 } 0493 } 0494 0495 // Saving the config keys 0496 const KConfigGroup contConfig = cont->config(); 0497 0498 panelJson.insert("config", dumpconfigGroupJS(contConfig)); 0499 0500 // Generate the applets array 0501 QJsonArray appletsJsonArray; 0502 0503 // Try to parse the encoded applets order 0504 const KConfigGroup genericConf(&contConfig, QStringLiteral("General")); 0505 const QStringList appletsOrderStrings = genericConf.readEntry(QStringLiteral("AppletOrder"), QString()).split(QChar(';')); 0506 0507 // Consider the applet order to be valid only if there are as many entries as applets() 0508 if (appletsOrderStrings.length() == cont->applets().length()) { 0509 for (const QString &appletId : appletsOrderStrings) { 0510 KConfigGroup appletConfig(&contConfig, QStringLiteral("Applets")); 0511 appletConfig = KConfigGroup(&appletConfig, appletId); 0512 0513 const QString pluginName = appletConfig.readEntry(QStringLiteral("plugin"), QString()); 0514 0515 if (pluginName.isEmpty()) { 0516 continue; 0517 } 0518 0519 QJsonObject appletJson; 0520 0521 appletConfig = KConfigGroup(&appletConfig, QStringLiteral("Configuration")); 0522 0523 appletJson.insert("plugin", pluginName); 0524 appletJson.insert("config", dumpconfigGroupJS(appletConfig)); 0525 0526 appletsJsonArray << appletJson; 0527 } 0528 0529 } else { 0530 const auto applets = cont->applets(); 0531 for (Plasma::Applet *applet : applets) { 0532 QJsonObject appletJson; 0533 0534 KConfigGroup appletConfig = applet->config(); 0535 0536 appletJson.insert("plugin", applet->pluginMetaData().pluginId()); 0537 appletJson.insert("config", dumpconfigGroupJS(appletConfig)); 0538 0539 appletsJsonArray << appletJson; 0540 } 0541 } 0542 0543 panelJson.insert("applets", appletsJsonArray); 0544 0545 panelsJsonArray << panelJson; 0546 } 0547 0548 root.insert("panels", panelsJsonArray); 0549 0550 // Now we are collecting desktops 0551 0552 QJsonArray desktopsJson; 0553 0554 const auto currentActivity = m_activityController->currentActivity(); 0555 0556 for (Plasma::Containment *cont : containments) { 0557 if (!isDesktop(cont) || cont->activity() != currentActivity) { 0558 continue; 0559 } 0560 0561 QJsonObject desktopJson; 0562 0563 desktopJson.insert("wallpaperPlugin", cont->wallpaper()); 0564 0565 // Get the config for the containment 0566 KConfigGroup contConfig = cont->config(); 0567 desktopJson.insert("config", dumpconfigGroupJS(contConfig)); 0568 0569 // Try to parse the item geometries 0570 const KConfigGroup genericConf(&contConfig, QStringLiteral("General")); 0571 const QStringList appletsGeomStrings = genericConf.readEntry(QStringLiteral("ItemsGeometries"), QString()).split(QChar(';')); 0572 0573 QHash<QString, QRect> appletGeometries; 0574 for (const QString &encoded : appletsGeomStrings) { 0575 const QStringList keyValue = encoded.split(QChar(':')); 0576 if (keyValue.length() != 2) { 0577 continue; 0578 } 0579 0580 const QStringList rectPieces = keyValue.last().split(QChar(',')); 0581 if (rectPieces.length() != 5) { 0582 continue; 0583 } 0584 0585 QRect rect(rectPieces[0].toInt(), rectPieces[1].toInt(), rectPieces[2].toInt(), rectPieces[3].toInt()); 0586 0587 appletGeometries[keyValue.first()] = rect; 0588 } 0589 0590 QJsonArray appletsJsonArray; 0591 0592 const auto applets = cont->applets(); 0593 for (Plasma::Applet *applet : applets) { 0594 const QRect geometry = appletGeometries.value(QStringLiteral("Applet-") % QString::number(applet->id())); 0595 0596 QJsonObject appletJson; 0597 0598 appletJson.insert("title", applet->title()); 0599 appletJson.insert("plugin", applet->pluginMetaData().pluginId()); 0600 0601 appletJson.insert("geometry.x", geometry.x() / gridUnit); 0602 appletJson.insert("geometry.y", geometry.y() / gridUnit); 0603 appletJson.insert("geometry.width", geometry.width() / gridUnit); 0604 appletJson.insert("geometry.height", geometry.height() / gridUnit); 0605 0606 KConfigGroup appletConfig = applet->config(); 0607 appletJson.insert("config", dumpconfigGroupJS(appletConfig)); 0608 0609 appletsJsonArray << appletJson; 0610 } 0611 0612 desktopJson.insert("applets", appletsJsonArray); 0613 desktopsJson << desktopJson; 0614 } 0615 0616 root.insert("desktops", desktopsJson); 0617 0618 QJsonDocument json; 0619 json.setObject(root); 0620 0621 return 0622 "var plasma = getApiVersion(1);\n\n" 0623 "var layout = " + json.toJson() + ";\n\n" 0624 "plasma.loadSerializedLayout(layout);\n"; 0625 } 0626 0627 void ShellCorona::loadLookAndFeelDefaultLayout(const QString &packageName) 0628 { 0629 KPackage::Package newPack = m_lookAndFeelPackage; 0630 newPack.setPath(packageName); 0631 0632 if (!newPack.isValid()) { 0633 return; 0634 } 0635 0636 KSharedConfig::Ptr conf = KSharedConfig::openConfig(QLatin1String("plasma-") + m_shell + QLatin1String("-appletsrc"), KConfig::SimpleConfig); 0637 0638 m_lookAndFeelPackage.setPath(packageName); 0639 0640 // get rid of old config 0641 const QStringList groupList = conf->groupList(); 0642 for (const QString &group : groupList) { 0643 conf->deleteGroup(group); 0644 } 0645 conf->sync(); 0646 unload(); 0647 // Put load in queue of the event loop to wait for the whole set of containments to have been deleteLater(), as some like FolderView operate on singletons 0648 // which can cause inconsistent states 0649 QTimer::singleShot(0, this, &ShellCorona::load); 0650 } 0651 0652 QString ShellCorona::shell() const 0653 { 0654 return m_shell; 0655 } 0656 0657 void ShellCorona::sanitizeScreenLayout(const QString &configFileName) 0658 { 0659 KConfigGroup cg(KSharedConfig::openConfig(configFileName), QStringLiteral("Containments")); 0660 0661 // The containment-> screen mappings we found in the config file 0662 QHash<QString, QMap<int, QString>> savedContainmentScreens; 0663 0664 // Desktop containments with screen = -1 or duplicated wanting to go on the same screen as somebody else 0665 QStringList orphanContainments; 0666 // Panel containments we found we may want to remap the screen 0667 QStringList panelContainments; 0668 QSet<Plasma::Types::Location> panelLocations({Plasma::Types::TopEdge, Plasma::Types::BottomEdge, Plasma::Types::LeftEdge, Plasma::Types::RightEdge}); 0669 0670 for (const QString &idStr : cg.groupList()) { 0671 if (idStr.toInt() <= 0) { 0672 continue; 0673 } 0674 0675 KConfigGroup contCg(&cg, idStr); 0676 int lastScreen = contCg.readEntry(QStringLiteral("lastScreen"), -1); 0677 0678 if (panelLocations.contains(Plasma::Types::Location(contCg.readEntry(QStringLiteral("location"), 0)))) { 0679 if (lastScreen >= 0) { 0680 panelContainments.append(idStr); 0681 } 0682 continue; 0683 } 0684 0685 const QString &activity = contCg.readEntry(QStringLiteral("activityId"), QString()); 0686 0687 if (lastScreen >= 0 && !savedContainmentScreens[activity].contains(lastScreen)) { 0688 savedContainmentScreens[activity][lastScreen] = idStr; 0689 } else { 0690 orphanContainments.append(idStr); 0691 } 0692 } 0693 0694 QHash<int, int> screenMapping; 0695 0696 // Ensure desktops screens are progressive 0697 for (auto activityIt = savedContainmentScreens.begin(); activityIt != savedContainmentScreens.end(); activityIt++) { 0698 const QString &activity = activityIt.key(); 0699 int progressiveScreen = 0; 0700 for (auto originalScreenIt = activityIt.value().begin(); originalScreenIt != activityIt.value().end(); originalScreenIt++) { 0701 KConfigGroup contCg(&cg, originalScreenIt.value()); 0702 screenMapping[originalScreenIt.key()] = progressiveScreen; 0703 contCg.writeEntry(QStringLiteral("lastScreen"), progressiveScreen++); 0704 } 0705 0706 for (auto orphanContainmentsIt = orphanContainments.constBegin(); orphanContainmentsIt != orphanContainments.constEnd(); orphanContainmentsIt++) { 0707 KConfigGroup contCg(&cg, (*orphanContainmentsIt)); 0708 const QString &orphanActivity = contCg.readEntry(QStringLiteral("activityId"), QString()); 0709 if (orphanActivity == activity) { 0710 contCg.writeEntry(QStringLiteral("lastScreen"), progressiveScreen++); 0711 } 0712 } 0713 } 0714 0715 // Remap panels to the screen changes we did for desktops 0716 for (auto panelsIt = panelContainments.begin(); panelsIt != panelContainments.end(); panelsIt++) { 0717 KConfigGroup contCg(&cg, (*panelsIt)); 0718 int lastScreen = contCg.readEntry(QStringLiteral("lastScreen"), -1); 0719 0720 // If we don't know where to put the panel, put it on the first screen 0721 contCg.writeEntry(QStringLiteral("lastScreen"), screenMapping.value(lastScreen, 0)); 0722 } 0723 } 0724 0725 void ShellCorona::load() 0726 { 0727 if (m_shell.isEmpty()) { 0728 return; 0729 } 0730 0731 auto activityStatus = m_activityController->serviceStatus(); 0732 if (activityStatus != KActivities::Controller::Running && !qApp->property("org.kde.KActivities.core.disableAutostart").toBool()) { 0733 if (activityStatus == KActivities::Controller::NotRunning) { 0734 qWarning("Aborting shell load: The activity manager daemon (kactivitymanagerd) is not running."); 0735 qWarning( 0736 "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 " 0737 "activatable."); 0738 } 0739 return; 0740 } 0741 0742 disconnect(m_activityController, &KActivities::Controller::serviceStatusChanged, this, &ShellCorona::load); 0743 0744 // TODO: a kconf_update script is needed 0745 QString configFileName(QStringLiteral("plasma-") + m_shell + QStringLiteral("-appletsrc")); 0746 0747 // Make sure all containments have screen numbers starting from 0 and are sequential 0748 sanitizeScreenLayout(configFileName); 0749 0750 loadLayout(configFileName); 0751 0752 checkActivities(); 0753 0754 if (containments().isEmpty()) { 0755 // Seems like we never really get to this point since loadLayout already 0756 // (virtually) calls loadDefaultLayout if it does not load anything 0757 // from the config file. Maybe if the config file is not empty, 0758 // but still does not have any containments 0759 loadDefaultLayout(); 0760 processUpdateScripts(); 0761 } else { 0762 processUpdateScripts(); 0763 const auto containments = this->containments(); 0764 0765 // Don't give a view to containments that don't want one (negative lastscreen) 0766 // (this is pretty mucha special case for the systray) 0767 // also, make sure we don't have a view already. 0768 // this will be true for first startup as the view has already been created at the new Panel JS call 0769 std::copy_if(containments.constBegin(), containments.constEnd(), std::back_inserter(m_waitingPanels), [this](Plasma::Containment *containment) { 0770 return ( 0771 (containment->containmentType() == Plasma::Types::PanelContainment || containment->containmentType() == Plasma::Types::CustomPanelContainment) 0772 && !m_waitingPanels.contains(containment) && containment->lastScreen() >= 0 && !m_panelViews.contains(containment)); 0773 }); 0774 } 0775 0776 // NOTE: this is needed in case loadLayout() did *not* call loadDefaultLayout() 0777 // it needs to be after of loadLayout() as it would always create new 0778 // containments on each startup otherwise 0779 const auto screens = m_screenPool->screenOrder(); 0780 for (QScreen *screen : screens) { 0781 QSet<QScreen *> managedScreens; 0782 for (auto *desk : m_desktopViewForScreen) { 0783 managedScreens.insert(desk->screenToFollow()); 0784 } 0785 0786 // the containments may have been created already by the startup script 0787 // check their existence in order to not have duplicated desktopviews 0788 if (!managedScreens.contains(screen)) { 0789 addOutput(screen); 0790 } 0791 } 0792 connect(m_screenPool, &ScreenPool::screenOrderChanged, this, &ShellCorona::handleScreenOrderChanged, Qt::UniqueConnection); 0793 connect(m_screenPool, &ScreenPool::screenRemoved, this, &ShellCorona::handleScreenRemoved, Qt::UniqueConnection); 0794 0795 if (!m_waitingPanels.isEmpty()) { 0796 m_waitingPanelsTimer.start(); 0797 } 0798 0799 if (config()->isImmutable() || !KAuthorized::authorize(QStringLiteral("plasma/plasmashell/unlockedDesktop"))) { 0800 setImmutability(Plasma::Types::SystemImmutable); 0801 } else { 0802 KConfigGroup coronaConfig(config(), "General"); 0803 setImmutability((Plasma::Types::ImmutabilityType)coronaConfig.readEntry("immutability", static_cast<int>(Plasma::Types::Mutable))); 0804 } 0805 } 0806 0807 #ifndef NDEBUG 0808 void ShellCorona::screenInvariants() const 0809 { 0810 if (m_screenPool->noRealOutputsConnected()) { 0811 Q_ASSERT(m_desktopViewForScreen.isEmpty()); 0812 Q_ASSERT(m_panelViews.isEmpty()); 0813 return; 0814 } 0815 0816 QSet<QScreen *> managedScreens; 0817 for (auto *desk : m_desktopViewForScreen) { 0818 managedScreens.insert(desk->screenToFollow()); 0819 } 0820 0821 Q_ASSERT(managedScreens.count() <= m_screenPool->screenOrder().count()); 0822 0823 QSet<QScreen *> screens; 0824 for (QScreen *knownScreen : managedScreens) { 0825 const int id = m_screenPool->idForScreen(knownScreen); 0826 const DesktopView *view = desktopForScreen(knownScreen); 0827 Q_ASSERT(view->isVisible()); 0828 QScreen *screen = view->screenToFollow(); 0829 Q_ASSERT(knownScreen == screen); 0830 Q_ASSERT(!screens.contains(screen)); 0831 // commented out because a different part of the code-base is responsible for this 0832 // and sometimes is not yet called here. 0833 // Q_ASSERT(!view->fillScreen() || view->geometry() == screen->geometry()); 0834 0835 Q_ASSERT(view->containment()->screen() == id || view->containment()->screen() == -1); 0836 Q_ASSERT(view->containment()->lastScreen() == id || view->containment()->lastScreen() == -1); 0837 Q_ASSERT(view->isVisible()); 0838 0839 for (const PanelView *panel : m_panelViews) { 0840 if (panel->screenToFollow() == screen) { 0841 Q_ASSERT(panel->containment()); 0842 Q_ASSERT(panel->containment()->screen() == id || panel->containment()->screen() == -1); 0843 // If any kscreen related activities occurred 0844 // during startup, the panel wouldn't be visible yet, and this would assert 0845 if (panel->containment()->isUiReady()) { 0846 Q_ASSERT(panel->isVisible()); 0847 } 0848 } 0849 } 0850 0851 screens.insert(screen); 0852 } 0853 0854 if (m_desktopViewForScreen.isEmpty()) { 0855 qWarning() << "no screens!!"; 0856 } 0857 } 0858 #endif 0859 0860 void ShellCorona::showAlternativesForApplet(Plasma::Applet *applet) 0861 { 0862 const QUrl alternativesQML = kPackage().fileUrl("appletalternativesui"); 0863 if (alternativesQML.isEmpty()) { 0864 return; 0865 } 0866 0867 auto *qmlObj = new KDeclarative::QmlObjectSharedEngine(this); 0868 qmlObj->setInitializationDelayed(true); 0869 qmlObj->setSource(alternativesQML); 0870 0871 AlternativesHelper *helper = new AlternativesHelper(applet, qmlObj); 0872 qmlObj->rootContext()->setContextProperty(QStringLiteral("alternativesHelper"), helper); 0873 0874 qmlObj->completeInitialization(); 0875 0876 auto dialog = qobject_cast<PlasmaQuick::Dialog *>(qmlObj->rootObject()); 0877 if (!dialog) { 0878 qCWarning(PLASMASHELL) << "Alternatives UI does not inherit from Dialog"; 0879 delete qmlObj; 0880 return; 0881 } 0882 connect(applet, &Plasma::Applet::destroyedChanged, qmlObj, [qmlObj](bool destroyed) { 0883 if (!destroyed) { 0884 return; 0885 } 0886 qmlObj->deleteLater(); 0887 }); 0888 connect(dialog, &PlasmaQuick::Dialog::visibleChanged, qmlObj, [qmlObj](bool visible) { 0889 if (visible) { 0890 return; 0891 } 0892 qmlObj->deleteLater(); 0893 }); 0894 } 0895 0896 void ShellCorona::unload() 0897 { 0898 if (m_shell.isEmpty()) { 0899 return; 0900 } 0901 0902 // Make double sure that we do not access the members while we are in the process of deleting them. 0903 // Most notably destroying PanelViews may issue signals that in turn cause iteration upon the m_panelViews. 0904 // First clear the members, then delete them. 0905 auto desktopViewForScreen = m_desktopViewForScreen; 0906 m_desktopViewForScreen.clear(); 0907 qDeleteAll(desktopViewForScreen); 0908 auto panelViews = m_panelViews; 0909 m_panelViews.clear(); 0910 qDeleteAll(panelViews); 0911 0912 m_waitingPanels.clear(); 0913 m_activityContainmentPlugins.clear(); 0914 0915 while (!containments().isEmpty()) { 0916 // Some applets react to destroyedChanged rather just destroyed, 0917 // give them the possibility to react 0918 // deleting a containment will remove it from the list due to QObject::destroyed connect in Corona 0919 // this form doesn't crash, while qDeleteAll(containments()) does 0920 // And is more correct anyways to use destroy() 0921 containments().constFirst()->destroy(); 0922 } 0923 } 0924 0925 KSharedConfig::Ptr ShellCorona::applicationConfig() 0926 { 0927 return KSharedConfig::openConfig(); 0928 } 0929 0930 void ShellCorona::requestApplicationConfigSync() 0931 { 0932 m_appConfigSyncTimer.start(); 0933 } 0934 0935 void ShellCorona::slotCyclePanelFocus() 0936 { 0937 if (m_panelViews.empty()) { 0938 return; 0939 } 0940 0941 PanelView *activePanel = qobject_cast<PanelView *>(qGuiApp->focusWindow()); 0942 if (!activePanel) { 0943 // Activate the first panel and save the previous window 0944 activePanel = m_panelViews.begin().value(); 0945 } 0946 0947 if (activePanel->containment()->status() != Plasma::Types::AcceptingInputStatus) { 0948 activePanel->containment()->setStatus(Plasma::Types::AcceptingInputStatus); 0949 } else { 0950 // Cancel focus on the current panel 0951 // Block focus on the panel if it's not the last panel 0952 if (activePanel != m_panelViews.last()) { 0953 m_blockRestorePreviousWindow = true; 0954 } 0955 activePanel->containment()->setStatus(Plasma::Types::PassiveStatus); 0956 m_blockRestorePreviousWindow = false; 0957 0958 // More than one panel and the current panel is not the last panel, 0959 // move focus to next panel. 0960 if (activePanel != m_panelViews.last()) { 0961 auto viewIt = std::find_if(m_panelViews.cbegin(), m_panelViews.cend(), [activePanel](const PanelView *panel) { 0962 return activePanel == panel; 0963 }); 0964 0965 if (viewIt == m_panelViews.cend()) { 0966 return; 0967 } 0968 0969 // Skip destroyed panels 0970 viewIt = std::next(viewIt); 0971 while (viewIt != m_panelViews.cend()) { 0972 if (!viewIt.value()->containment()->destroyed()) { 0973 break; 0974 } 0975 0976 viewIt = std::next(viewIt); 0977 } 0978 0979 if (viewIt != m_panelViews.cend()) { 0980 viewIt.value()->containment()->setStatus(Plasma::Types::AcceptingInputStatus); 0981 } else { 0982 restorePreviousWindow(); 0983 } 0984 } 0985 } 0986 } 0987 0988 void ShellCorona::loadDefaultLayout() 0989 { 0990 #if USE_SCRIPTING 0991 // pre-startup scripts 0992 QString script = m_lookAndFeelPackage.filePath("layouts", shell() + "-prelayout.js"); 0993 if (!script.isEmpty()) { 0994 QFile file(script); 0995 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { 0996 QString code = file.readAll(); 0997 qCDebug(PLASMASHELL) << "evaluating pre-startup script:" << script; 0998 0999 WorkspaceScripting::ScriptEngine scriptEngine(this); 1000 1001 connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, [](const QString &msg) { 1002 qCWarning(PLASMASHELL) << msg; 1003 }); 1004 connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, [](const QString &msg) { 1005 qCDebug(PLASMASHELL) << msg; 1006 }); 1007 if (!scriptEngine.evaluateScript(code, script)) { 1008 qCWarning(PLASMASHELL) << "failed to initialize layout properly:" << script; 1009 } 1010 } 1011 } 1012 1013 // NOTE: Is important the containments already exist for each screen 1014 // at the moment of the script execution,the same loop in :load() 1015 // is executed too late 1016 const auto screens = m_screenPool->screenOrder(); 1017 for (QScreen *screen : screens) { 1018 addOutput(screen); 1019 } 1020 1021 script = m_testModeLayout; 1022 1023 if (script.isEmpty()) { 1024 script = m_lookAndFeelPackage.filePath("layouts", shell() + "-layout.js"); 1025 } 1026 if (script.isEmpty()) { 1027 script = kPackage().filePath("defaultlayout"); 1028 } 1029 1030 QFile file(script); 1031 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { 1032 QString code = file.readAll(); 1033 qCDebug(PLASMASHELL) << "evaluating startup script:" << script; 1034 1035 // We need to know which activities are here in order for 1036 // the scripting engine to work. activityAdded does not mind 1037 // if we pass it the same activity multiple times 1038 const QStringList existingActivities = m_activityController->activities(); 1039 for (const QString &id : existingActivities) { 1040 activityAdded(id); 1041 } 1042 1043 WorkspaceScripting::ScriptEngine scriptEngine(this); 1044 1045 connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, [](const QString &msg) { 1046 qCWarning(PLASMASHELL) << msg; 1047 }); 1048 connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, [](const QString &msg) { 1049 qCDebug(PLASMASHELL) << msg; 1050 }); 1051 if (!scriptEngine.evaluateScript(code, script)) { 1052 qCWarning(PLASMASHELL) << "failed to initialize layout properly:" << script; 1053 } 1054 } 1055 #endif 1056 Q_EMIT startupCompleted(); 1057 } 1058 1059 void ShellCorona::processUpdateScripts() 1060 { 1061 #if USE_SCRIPTING 1062 const QStringList scripts = WorkspaceScripting::ScriptEngine::pendingUpdateScripts(this); 1063 if (scripts.isEmpty()) { 1064 return; 1065 } 1066 1067 WorkspaceScripting::ScriptEngine scriptEngine(this); 1068 1069 connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, [](const QString &msg) { 1070 qCWarning(PLASMASHELL) << msg; 1071 }); 1072 connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, [](const QString &msg) { 1073 qCDebug(PLASMASHELL) << msg; 1074 }); 1075 1076 for (const QString &script : scripts) { 1077 QFile file(script); 1078 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { 1079 QString code = file.readAll(); 1080 scriptEngine.evaluateScript(code); 1081 } else { 1082 qCWarning(PLASMASHELL) << "Unable to open the script file" << script << "for reading"; 1083 } 1084 } 1085 #endif 1086 } 1087 1088 int ShellCorona::numScreens() const 1089 { 1090 return m_screenPool->screenOrder().count(); 1091 } 1092 1093 QRect ShellCorona::screenGeometry(int id) const 1094 { 1095 QScreen *screen = m_screenPool->screenForId(id); 1096 if (!screen) { 1097 qWarning() << "requesting unexisting screen geometry" << id; 1098 screen = m_screenPool->primaryScreen(); 1099 return screen ? screen->geometry() : QRect(); 1100 } 1101 return screen->geometry(); 1102 } 1103 1104 QRegion ShellCorona::availableScreenRegion(int id) const 1105 { 1106 return m_strutManager->availableScreenRegion(id); 1107 } 1108 1109 QRegion ShellCorona::_availableScreenRegion(int id) const 1110 { 1111 QScreen *screen = m_screenPool->screenForId(id); 1112 if (!screen) { 1113 // each screen should have a view 1114 qWarning() << "requesting unexisting screen region" << id; 1115 screen = m_screenPool->primaryScreen(); 1116 return screen ? screen->availableGeometry() : QRegion(); 1117 } 1118 1119 return std::accumulate(m_panelViews.cbegin(), m_panelViews.cend(), QRegion(screen->geometry()), [screen](const QRegion &a, const PanelView *v) { 1120 if (v->isVisible() && screen == v->screen() && v->visibilityMode() != PanelView::AutoHide) { 1121 // if the panel is being moved around, we still want to calculate it from the edge 1122 return a - v->geometryByDistance(0); 1123 } 1124 return a; 1125 }); 1126 } 1127 1128 QRect ShellCorona::availableScreenRect(int id) const 1129 { 1130 return m_strutManager->availableScreenRect(id); 1131 } 1132 1133 QRect ShellCorona::_availableScreenRect(int id) const 1134 { 1135 QScreen *screen = m_screenPool->screenForId(id); 1136 if (!screen) { 1137 // each screen should have a view 1138 qWarning() << "requesting unexisting screen available rect" << id; 1139 screen = m_screenPool->primaryScreen(); 1140 return screen ? screen->availableGeometry() : QRect(); 1141 } 1142 1143 QRect r = screen->geometry(); 1144 int topThickness, leftThickness, rightThickness, bottomThickness; 1145 topThickness = leftThickness = rightThickness = bottomThickness = 0; 1146 for (PanelView *v : m_panelViews) { 1147 if (v->isVisible() && v->screen() == screen && v->visibilityMode() != PanelView::AutoHide) { 1148 switch (v->location()) { 1149 case Plasma::Types::LeftEdge: 1150 leftThickness = qMax(leftThickness, v->totalThickness()); 1151 break; 1152 case Plasma::Types::RightEdge: 1153 rightThickness = qMax(rightThickness, v->totalThickness()); 1154 break; 1155 case Plasma::Types::TopEdge: 1156 topThickness = qMax(topThickness, v->totalThickness()); 1157 break; 1158 case Plasma::Types::BottomEdge: 1159 bottomThickness = qMax(bottomThickness, v->totalThickness()); 1160 default: 1161 break; 1162 } 1163 } 1164 } 1165 r.setLeft(r.left() + leftThickness); 1166 r.setRight(r.right() - rightThickness); 1167 r.setTop(r.top() + topThickness); 1168 r.setBottom(r.bottom() - bottomThickness); 1169 return r; 1170 } 1171 1172 QStringList ShellCorona::availableActivities() const 1173 { 1174 return m_activityContainmentPlugins.keys(); 1175 } 1176 1177 void ShellCorona::removeDesktop(DesktopView *desktopView) 1178 { 1179 const int screenId = desktopView->containment()->lastScreen(); 1180 1181 auto result = std::find_if(m_desktopViewForScreen.begin(), m_desktopViewForScreen.end(), [desktopView](DesktopView *v) { 1182 return v == desktopView; 1183 }); 1184 1185 if (result != m_desktopViewForScreen.end()) { 1186 m_desktopViewForScreen.erase(result); 1187 } 1188 1189 QMutableMapIterator<const Plasma::Containment *, PanelView *> it(m_panelViews); 1190 1191 while (it.hasNext()) { 1192 it.next(); 1193 PanelView *panelView = it.value(); 1194 1195 if (panelView->containment()->lastScreen() == screenId) { 1196 m_waitingPanels << panelView->containment(); 1197 it.remove(); 1198 panelView->destroy(); 1199 panelView->containment()->reactToScreenChange(); 1200 } 1201 } 1202 1203 desktopView->destroy(); 1204 desktopView->containment()->reactToScreenChange(); 1205 } 1206 1207 PanelView *ShellCorona::panelView(Plasma::Containment *containment) const 1208 { 1209 return m_panelViews.value(containment); 1210 } 1211 1212 void ShellCorona::savePreviousWindow() 1213 { 1214 #if HAVE_X11 1215 if (KWindowSystem::isPlatformX11() && m_previousWId == 0) { 1216 m_previousWId = KX11Extras::activeWindow(); 1217 } 1218 #endif 1219 if (m_waylandWindowManagement && !m_previousPlasmaWindow) { 1220 m_previousPlasmaWindow = m_waylandWindowManagement->activeWindow(); 1221 } 1222 } 1223 1224 void ShellCorona::restorePreviousWindow() 1225 { 1226 if (m_blockRestorePreviousWindow) { 1227 return; 1228 } 1229 1230 #if HAVE_X11 1231 if (KWindowSystem::isPlatformX11() && m_previousWId) { 1232 KX11Extras::forceActiveWindow(m_previousWId); 1233 } 1234 #endif 1235 if (m_previousPlasmaWindow) { 1236 m_previousPlasmaWindow->requestActivate(); 1237 } 1238 1239 clearPreviousWindow(); 1240 } 1241 1242 void ShellCorona::clearPreviousWindow() 1243 { 1244 m_previousWId = 0; 1245 m_previousPlasmaWindow = nullptr; 1246 } 1247 1248 ///// SLOTS 1249 1250 DesktopView *ShellCorona::desktopForScreen(QScreen *screen) const 1251 { 1252 if (!screen) { 1253 return nullptr; 1254 } 1255 if (auto *v = m_desktopViewForScreen.value(m_screenPool->idForScreen(screen))) { 1256 return v; 1257 } 1258 1259 return nullptr; 1260 } 1261 1262 void ShellCorona::handleScreenRemoved(QScreen *screen) 1263 { 1264 if (DesktopView *v = desktopForScreen(screen)) { 1265 // Checking with m_screenPool->screenOrder().count() - 1 because when ScreenPool emits screenRemoved, the screen has not yet been removed from 1266 // ScreenPool::screenORder() 1267 if (v->containment()->lastScreen() < 0 || v->containment()->lastScreen() >= m_screenPool->screenOrder().count() - 1) { 1268 removeDesktop(v); 1269 } 1270 // Else do nothing, the view will be recycled 1271 } 1272 1273 // The real screen index that has been removed is *always* the highest one, because we enforce order. 1274 // There can't be a containment that has for instance screen 0 and another 2 but nothing on 1 1275 // It's size() - 1 because at this point screenpool didn't remove it from screenOrder() yet 1276 Q_EMIT screenRemoved(m_screenPool->screenOrder().size() - 1); 1277 #ifndef NDEBUG 1278 m_invariantsTimer.start(); 1279 #endif 1280 } 1281 1282 void ShellCorona::handleScreenOrderChanged(QList<QScreen *> screens) 1283 { 1284 m_screenReorderInProgress = true; 1285 // First: reassign existing views if applicable, otherwise remove them 1286 auto allDesktops = m_desktopViewForScreen.values(); 1287 m_desktopViewForScreen.clear(); 1288 for (auto *v : allDesktops) { 1289 const int screenNumber = v->containment()->lastScreen(); 1290 if (screenNumber >= 0 && screenNumber < screens.count()) { 1291 v->setScreenToFollow(screens[screenNumber]); 1292 v->setVisible(true); 1293 m_desktopViewForScreen[screenNumber] = v; 1294 } else { 1295 removeDesktop(v); 1296 } 1297 } 1298 1299 // Doing it here as m_panelViews might already have been modified by the step before 1300 auto allPanels = m_panelViews.values(); 1301 m_panelViews.clear(); 1302 for (auto *v : allPanels) { 1303 const int screenNumber = v->containment()->lastScreen(); 1304 if (screenNumber >= 0 && screenNumber < screens.count()) { 1305 v->setScreenToFollow(screens[screenNumber]); 1306 v->setVisible(true); 1307 m_panelViews[v->containment()] = v; 1308 } else { 1309 v->destroy(); 1310 v->containment()->reactToScreenChange(); 1311 } 1312 } 1313 1314 // Second: add everything that wasn't there yet 1315 for (auto *s : screens) { 1316 if (!desktopForScreen(s)) { 1317 addOutput(s); 1318 } 1319 } 1320 1321 m_screenReorderInProgress = false; 1322 Q_EMIT screenOrderChanged(screens); 1323 1324 Q_ASSERT(m_desktopViewForScreen.count() == screens.count()); 1325 for (int i = 0; i < screens.count(); ++i) { 1326 Q_EMIT screenGeometryChanged(i); 1327 } 1328 Q_EMIT availableScreenRectChanged(); 1329 1330 CHECK_SCREEN_INVARIANTS 1331 } 1332 1333 void ShellCorona::addOutput(QScreen *screen) 1334 { 1335 Q_ASSERT(screen); 1336 1337 if (desktopForScreen(screen)) { 1338 Q_EMIT screenAdded(m_screenPool->idForScreen(screen)); 1339 return; 1340 } 1341 Q_ASSERT(!screen->geometry().isNull()); 1342 #ifndef NDEBUG 1343 connect(screen, &QScreen::geometryChanged, &m_invariantsTimer, static_cast<void (QTimer::*)()>(&QTimer::start), Qt::UniqueConnection); 1344 #endif 1345 int insertPosition = m_screenPool->idForScreen(screen); 1346 Q_ASSERT(insertPosition >= 0); 1347 1348 DesktopView *view = new DesktopView(this, screen); 1349 1350 if (view->rendererInterface()->graphicsApi() != QSGRendererInterface::Software) { 1351 connect(view, &QQuickWindow::sceneGraphError, this, &ShellCorona::glInitializationFailed); 1352 } 1353 connect(view, &DesktopView::geometryChanged, this, [=]() { 1354 const int id = m_screenPool->idForScreen(view->screen()); 1355 if (id >= 0 && !m_screenReorderInProgress) { 1356 Q_EMIT screenGeometryChanged(id); 1357 Q_EMIT availableScreenRectChanged(); 1358 } 1359 }); 1360 1361 Plasma::Containment *containment = createContainmentForActivity(m_activityController->currentActivity(), insertPosition); 1362 Q_ASSERT(containment); 1363 1364 QAction *removeAction = containment->actions()->action(QStringLiteral("remove")); 1365 if (removeAction) { 1366 removeAction->deleteLater(); 1367 } 1368 1369 connect(containment, &Plasma::Containment::uiReadyChanged, this, &ShellCorona::checkAllDesktopsUiReady); 1370 1371 m_desktopViewForScreen[insertPosition] = view; 1372 view->setContainment(containment); 1373 view->show(); 1374 1375 Q_ASSERT(screen == view->screen()); 1376 1377 // need to specifically call the reactToScreenChange, since when the screen is shown it's not yet 1378 // in the list. We still don't want to have an invisible view added. 1379 containment->reactToScreenChange(); 1380 1381 // were there any panels for this screen before it popped up? 1382 if (!m_waitingPanels.isEmpty()) { 1383 m_waitingPanelsTimer.start(); 1384 } 1385 1386 if (!m_screenReorderInProgress) { 1387 Q_EMIT availableScreenRectChanged(); 1388 } 1389 Q_EMIT screenAdded(m_screenPool->idForScreen(screen)); 1390 #ifndef NDEBUG 1391 m_invariantsTimer.start(); 1392 #endif 1393 } 1394 1395 void ShellCorona::checkAllDesktopsUiReady(bool ready) 1396 { 1397 if (!ready) 1398 return; 1399 for (auto v : qAsConst(m_desktopViewForScreen)) { 1400 if (!v->containment()->isUiReady()) 1401 return; 1402 1403 qCDebug(PLASMASHELL) << "Plasma Shell startup completed"; 1404 QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), 1405 QStringLiteral("/KSplash"), 1406 QStringLiteral("org.kde.KSplash"), 1407 QStringLiteral("setStage")); 1408 ksplashProgressMessage.setArguments(QList<QVariant>() << QStringLiteral("desktop")); 1409 QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); 1410 } 1411 } 1412 1413 Plasma::Containment *ShellCorona::createContainmentForActivity(const QString &activity, int screenNum) 1414 { 1415 Plasma::Containment *lastScreenCont = nullptr; 1416 Plasma::Containment *orphanCont = nullptr; 1417 const auto containments = containmentsForActivity(activity); 1418 for (Plasma::Containment *cont : containments) { 1419 // in the case of a corrupt config file 1420 // with multiple containments with same lastScreen 1421 // Always prefer a containment that already has a screen, if any 1422 // This piece of code always fails at startup, used only later in plasma runtime 1423 if (cont->destroyed()) { 1424 continue; 1425 } 1426 if (cont->screen() == screenNum) { 1427 // Always prefer a containment that already has a view 1428 return cont; 1429 } else if (cont->lastScreen() == screenNum) { 1430 // Otherwise base off lastScreen 1431 lastScreenCont = cont; 1432 } else if (cont->lastScreen() < 0) { 1433 // Last resort, if we found a desktop for the activity that for whatever reason had screen -1 (very unlikely) recycle it 1434 orphanCont = cont; 1435 } 1436 } 1437 if (lastScreenCont) { 1438 return lastScreenCont; 1439 } else if (orphanCont) { 1440 return orphanCont; 1441 } 1442 1443 QString plugin = m_activityContainmentPlugins.value(activity); 1444 1445 if (plugin.isEmpty()) { 1446 plugin = defaultContainmentPlugin(); 1447 } 1448 1449 Plasma::Containment *containment = containmentForScreen(screenNum, activity, plugin, QVariantList()); 1450 Q_ASSERT(containment); 1451 1452 return containment; 1453 } 1454 1455 void ShellCorona::createWaitingPanels() 1456 { 1457 QList<Plasma::Containment *> stillWaitingPanels; 1458 1459 for (Plasma::Containment *cont : qAsConst(m_waitingPanels)) { 1460 // ignore non existing (yet?) screens 1461 int requestedScreen = cont->lastScreen(); 1462 if (requestedScreen < 0) { 1463 requestedScreen = 0; 1464 } 1465 1466 QScreen *screen = m_screenPool->screenForId(requestedScreen); 1467 DesktopView *desktopView = desktopForScreen(screen); 1468 if (!screen || !desktopView) { 1469 stillWaitingPanels << cont; 1470 continue; 1471 } 1472 1473 // TODO: does a similar check make sense? 1474 // Q_ASSERT(qBound(0, requestedScreen, m_screenPool->count() - 1) == requestedScreen); 1475 PanelView *panel = new PanelView(this, screen); 1476 if (panel->rendererInterface()->graphicsApi() != QSGRendererInterface::Software) { 1477 connect(panel, &QQuickWindow::sceneGraphError, this, &ShellCorona::glInitializationFailed); 1478 } 1479 auto rectNotify = [this]() { 1480 if (!m_screenReorderInProgress) { 1481 Q_EMIT availableScreenRectChanged(); 1482 } 1483 }; 1484 1485 connect(panel, &QWindow::visibleChanged, this, rectNotify); 1486 connect(panel, &QWindow::screenChanged, this, rectNotify); 1487 connect(panel, &PanelView::locationChanged, this, rectNotify); 1488 connect(panel, &PanelView::visibilityModeChanged, this, rectNotify); 1489 connect(panel, &PanelView::thicknessChanged, this, rectNotify); 1490 1491 m_panelViews[cont] = panel; 1492 panel->setContainment(cont); 1493 cont->reactToScreenChange(); 1494 1495 connect(cont, &QObject::destroyed, this, &ShellCorona::panelContainmentDestroyed); 1496 } 1497 m_waitingPanels = stillWaitingPanels; 1498 1499 if (!m_screenReorderInProgress) { 1500 Q_EMIT availableScreenRectChanged(); 1501 } 1502 } 1503 1504 void ShellCorona::panelContainmentDestroyed(QObject *cont) 1505 { 1506 auto view = m_panelViews.take(static_cast<Plasma::Containment *>(cont)); 1507 delete view; 1508 // don't make things relayout when the application is quitting 1509 // NOTE: qApp->closingDown() is still false here 1510 if (!m_closingDown && !m_screenReorderInProgress) { 1511 Q_EMIT availableScreenRectChanged(); 1512 } 1513 } 1514 1515 void ShellCorona::handleContainmentAdded(Plasma::Containment *c) 1516 { 1517 connect(c, &Plasma::Containment::showAddWidgetsInterface, this, &ShellCorona::toggleWidgetExplorer); 1518 connect(c, &Plasma::Containment::appletAlternativesRequested, this, &ShellCorona::showAlternativesForApplet); 1519 connect(c, &Plasma::Containment::appletCreated, this, [this, c](Plasma::Applet *applet) { 1520 executeSetupPlasmoidScript(c, applet); 1521 }); 1522 1523 // When a containment is removed, remove the thumbnail as well 1524 connect(c, &Plasma::Containment::destroyedChanged, this, [this, c](bool destroyed) { 1525 if (!destroyed) { 1526 return; 1527 } 1528 const QString snapshotPath = containmentPreviewPath(c); 1529 if (!snapshotPath.isEmpty()) { 1530 QFile f(snapshotPath); 1531 f.remove(); 1532 } 1533 }); 1534 } 1535 1536 void ShellCorona::executeSetupPlasmoidScript(Plasma::Containment *containment, Plasma::Applet *applet) 1537 { 1538 #if USE_SCRIPTING 1539 if (!applet->pluginMetaData().isValid() || !containment->pluginMetaData().isValid()) { 1540 return; 1541 } 1542 1543 const QString scriptFile = m_lookAndFeelPackage.filePath("plasmoidsetupscripts", applet->pluginMetaData().pluginId() + ".js"); 1544 1545 if (scriptFile.isEmpty()) { 1546 return; 1547 } 1548 1549 WorkspaceScripting::ScriptEngine scriptEngine(this); 1550 1551 connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, [](const QString &msg) { 1552 qCWarning(PLASMASHELL) << msg; 1553 }); 1554 connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, [](const QString &msg) { 1555 qCDebug(PLASMASHELL) << msg; 1556 }); 1557 1558 QFile file(scriptFile); 1559 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { 1560 qCWarning(PLASMASHELL) << "Unable to load script file:" << scriptFile; 1561 return; 1562 } 1563 1564 QString script = file.readAll(); 1565 if (script.isEmpty()) { 1566 // qCDebug(PLASMASHELL) << "script is empty"; 1567 return; 1568 } 1569 1570 scriptEngine.globalObject().setProperty(QStringLiteral("applet"), scriptEngine.wrap(applet)); 1571 scriptEngine.globalObject().setProperty(QStringLiteral("containment"), scriptEngine.wrap(containment)); 1572 scriptEngine.evaluateScript(script, scriptFile); 1573 #endif 1574 } 1575 1576 void ShellCorona::toggleWidgetExplorer() 1577 { 1578 // FIXME: This does not work on wayland 1579 const QPoint cursorPos = QCursor::pos(); 1580 for (DesktopView *view : qAsConst(m_desktopViewForScreen)) { 1581 if (view->screen()->geometry().contains(cursorPos)) { 1582 // The view QML has to provide something to display the widget explorer 1583 view->rootObject()->metaObject()->invokeMethod(view->rootObject(), "toggleWidgetExplorer", Q_ARG(QVariant, QVariant::fromValue(sender()))); 1584 return; 1585 } 1586 } 1587 } 1588 1589 void ShellCorona::toggleActivityManager() 1590 { 1591 const QPoint cursorPos = QCursor::pos(); 1592 for (DesktopView *view : qAsConst(m_desktopViewForScreen)) { 1593 if (view->screen()->geometry().contains(cursorPos)) { 1594 // The view QML has to provide something to display the activity explorer 1595 view->rootObject()->metaObject()->invokeMethod(view->rootObject(), "toggleActivityManager", Qt::QueuedConnection); 1596 return; 1597 } 1598 } 1599 } 1600 1601 void ShellCorona::syncAppConfig() 1602 { 1603 applicationConfig()->sync(); 1604 } 1605 1606 void ShellCorona::setDashboardShown(bool show) 1607 { 1608 KWindowSystem::setShowingDesktop(show); 1609 } 1610 1611 void ShellCorona::toggleDashboard() 1612 { 1613 setDashboardShown(!KWindowSystem::showingDesktop()); 1614 } 1615 1616 void ShellCorona::handleColorRequestedFromDBus(const QDBusMessage &msg) 1617 { 1618 Q_ASSERT(!m_accentColorFromWallpaperEnabled); 1619 Q_ASSERT(!m_fakeColorRequestConn); 1620 msg.setDelayedReply(true); 1621 1622 m_fakeColorRequestConn = connect(this, &ShellCorona::colorChanged, this, [this, msg] { 1623 disconnect(m_fakeColorRequestConn); 1624 const QRgb color = desktopForScreen(m_screenPool->primaryScreen())->accentColor().rgba(); 1625 1626 m_accentColorFromWallpaperEnabled = false; 1627 Q_EMIT accentColorFromWallpaperEnabledChanged(); 1628 1629 const QDBusMessage reply = msg.createReply(color); 1630 QDBusConnection::sessionBus().send(reply); 1631 }); 1632 1633 m_accentColorFromWallpaperEnabled = true; 1634 Q_EMIT accentColorFromWallpaperEnabledChanged(); 1635 } 1636 1637 QRgb ShellCorona::color() const 1638 { 1639 // Colors from wallpaper are not generated when they are turned off in the settings. 1640 // To return a color we need to fake that the setting is on, and then take the color, 1641 // turn off the setting again(or the color engine will keep runnig) and return the color. 1642 1643 // Note that whenever a color is generated, it is also set as accent color. When we fake the 1644 // setting, we should not apply the generated color. The color applying kded module take care 1645 // of that by checking for the original state of the setting, so it is important that the check 1646 // may not be removed accidentally. 1647 1648 static QRgb defaultColor = QColor(Qt::transparent).rgba(); 1649 auto const primaryDesktopViewExists = desktopForScreen(m_screenPool->primaryScreen()); 1650 if (!primaryDesktopViewExists) { 1651 return defaultColor; 1652 } 1653 1654 if (m_accentColorFromWallpaperEnabled) { 1655 return desktopForScreen(m_screenPool->primaryScreen())->accentColor().rgba(); 1656 } else if (calledFromDBus()) { 1657 if (m_fakeColorRequestConn) { 1658 disconnect(m_fakeColorRequestConn); 1659 } 1660 const_cast<ShellCorona *>(this)->handleColorRequestedFromDBus(message()); 1661 } 1662 1663 return defaultColor; 1664 } 1665 1666 QString ShellCorona::evaluateScript(const QString &script) 1667 { 1668 #if USE_SCRIPTING 1669 if (calledFromDBus()) { 1670 if (immutability() == Plasma::Types::SystemImmutable) { 1671 sendErrorReply(QDBusError::Failed, QStringLiteral("Widgets are locked")); 1672 return QString(); 1673 } else if (!KAuthorized::authorize(QStringLiteral("plasma-desktop/scripting_console"))) { 1674 sendErrorReply(QDBusError::Failed, QStringLiteral("Administrative policies prevent script execution")); 1675 return QString(); 1676 } 1677 } 1678 1679 WorkspaceScripting::ScriptEngine scriptEngine(this); 1680 QString buffer; 1681 QTextStream bufferStream(&buffer, QIODevice::WriteOnly | QIODevice::Text); 1682 1683 connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, [&bufferStream](const QString &msg) { 1684 qCWarning(PLASMASHELL) << msg; 1685 bufferStream << msg; 1686 }); 1687 connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, [&bufferStream](const QString &msg) { 1688 qCDebug(PLASMASHELL) << msg; 1689 bufferStream << msg; 1690 }); 1691 1692 scriptEngine.evaluateScript(script); 1693 1694 bufferStream.flush(); 1695 1696 if (calledFromDBus() && !scriptEngine.errorString().isEmpty()) { 1697 sendErrorReply(QDBusError::Failed, scriptEngine.errorString()); 1698 return QString(); 1699 } 1700 1701 return buffer; 1702 #else 1703 return QString(); 1704 #endif 1705 } 1706 1707 void ShellCorona::checkActivities() 1708 { 1709 KActivities::Controller::ServiceStatus status = m_activityController->serviceStatus(); 1710 // qCDebug(PLASMASHELL) << "$%$%$#%$%$%Status:" << status; 1711 if (status != KActivities::Controller::Running) { 1712 // panic and give up - better than causing a mess 1713 qCDebug(PLASMASHELL) << "ShellCorona::checkActivities is called whilst activity daemon is still connecting"; 1714 return; 1715 } 1716 1717 const QStringList existingActivities = m_activityController->activities(); 1718 for (const QString &id : existingActivities) { 1719 activityAdded(id); 1720 } 1721 1722 // Checking whether the result we got is valid. Just in case. 1723 Q_ASSERT_X(!existingActivities.isEmpty(), "isEmpty", "There are no activities, and the service is running"); 1724 Q_ASSERT_X(existingActivities[0] != QLatin1String("00000000-0000-0000-0000-000000000000"), "null uuid", "There is a nulluuid activity present"); 1725 1726 // Killing the unassigned containments 1727 const auto conts = containments(); 1728 for (Plasma::Containment *cont : conts) { 1729 if ((cont->containmentType() == Plasma::Types::DesktopContainment || cont->containmentType() == Plasma::Types::CustomContainment) 1730 && !existingActivities.contains(cont->activity()) && m_activityController->currentActivity() != cont->activity()) { 1731 cont->destroy(); 1732 } 1733 } 1734 } 1735 1736 void ShellCorona::currentActivityChanged(const QString &newActivity) 1737 { 1738 // qCDebug(PLASMASHELL) << "Activity changed:" << newActivity; 1739 1740 for (auto it = m_desktopViewForScreen.constBegin(); it != m_desktopViewForScreen.constEnd(); ++it) { 1741 Plasma::Containment *c = createContainmentForActivity(newActivity, it.key()); 1742 1743 QAction *removeAction = c->actions()->action(QStringLiteral("remove")); 1744 if (removeAction) { 1745 removeAction->deleteLater(); 1746 } 1747 (*it)->setContainment(c); 1748 } 1749 } 1750 1751 void ShellCorona::activityAdded(const QString &id) 1752 { 1753 // TODO more sanity checks 1754 if (m_activityContainmentPlugins.contains(id)) { 1755 qCWarning(PLASMASHELL) << "Activity added twice" << id; 1756 return; 1757 } 1758 1759 m_activityContainmentPlugins.insert(id, defaultContainmentPlugin()); 1760 } 1761 1762 void ShellCorona::activityRemoved(const QString &id) 1763 { 1764 m_activityContainmentPlugins.remove(id); 1765 const QList<Plasma::Containment *> containments = containmentsForActivity(id); 1766 for (auto cont : containments) { 1767 cont->destroy(); 1768 } 1769 } 1770 1771 void ShellCorona::insertActivity(const QString &id, const QString &plugin) 1772 { 1773 activityAdded(id); 1774 1775 // TODO: This needs to go away! 1776 // The containment creation API does not know when we have a 1777 // new activity to create a containment for, we need to pretend 1778 // that the current activity has been changed 1779 QFuture<bool> currentActivity = m_activityController->setCurrentActivity(id); 1780 awaitFuture(currentActivity); 1781 1782 if (!currentActivity.result()) { 1783 qCDebug(PLASMASHELL) << "Failed to create and switch to the activity"; 1784 return; 1785 } 1786 1787 while (m_activityController->currentActivity() != id) { 1788 QCoreApplication::processEvents(); 1789 } 1790 1791 m_activityContainmentPlugins.insert(id, plugin); 1792 for (auto it = m_desktopViewForScreen.constBegin(); it != m_desktopViewForScreen.constEnd(); ++it) { 1793 Plasma::Containment *c = createContainmentForActivity(id, (*it)->containment()->screen()); 1794 if (c) { 1795 c->config().writeEntry("lastScreen", (*it)->containment()->screen()); 1796 } 1797 } 1798 } 1799 1800 Plasma::Containment *ShellCorona::setContainmentTypeForScreen(int screen, const QString &plugin) 1801 { 1802 // search but not create 1803 Plasma::Containment *oldContainment = containmentForScreen(screen, m_activityController->currentActivity(), QString()); 1804 1805 // no valid containment in given screen, giving up 1806 if (!oldContainment) { 1807 return nullptr; 1808 } 1809 1810 if (plugin.isEmpty()) { 1811 return oldContainment; 1812 } 1813 1814 auto viewIt = std::find_if(m_desktopViewForScreen.cbegin(), m_desktopViewForScreen.cend(), [oldContainment](const DesktopView *v) { 1815 return v->containment() == oldContainment; 1816 }); 1817 1818 // no view? give up 1819 if (viewIt == m_desktopViewForScreen.cend()) { 1820 return oldContainment; 1821 } 1822 1823 // create a new containment 1824 Plasma::Containment *newContainment = createContainmentDelayed(plugin); 1825 1826 // if creation failed or invalid plugin, give up 1827 if (!newContainment) { 1828 return oldContainment; 1829 } else if (!newContainment->pluginMetaData().isValid()) { 1830 newContainment->deleteLater(); 1831 return oldContainment; 1832 } 1833 1834 // At this point we have a valid new containment from plugin and a view 1835 // copy all configuration groups (excluded applets) 1836 KConfigGroup oldCg = oldContainment->config(); 1837 1838 // newCg *HAS* to be from a KSharedConfig, because some KConfigSkeleton will need to be synced 1839 // this makes the configscheme work 1840 KConfigGroup newCg(KSharedConfig::openConfig(oldCg.config()->name()), "Containments"); 1841 newCg = KConfigGroup(&newCg, QString::number(newContainment->id())); 1842 1843 // this makes containment->config() work, is a separate thing from its configscheme 1844 KConfigGroup newCg2 = newContainment->config(); 1845 1846 const auto groups = oldCg.groupList(); 1847 for (const QString &group : groups) { 1848 if (group != QLatin1String("Applets")) { 1849 KConfigGroup subGroup(&oldCg, group); 1850 KConfigGroup newSubGroup(&newCg, group); 1851 subGroup.copyTo(&newSubGroup); 1852 1853 KConfigGroup newSubGroup2(&newCg2, group); 1854 subGroup.copyTo(&newSubGroup2); 1855 } 1856 } 1857 1858 newContainment->init(); 1859 newCg.writeEntry("activityId", oldContainment->activity()); 1860 newCg.writeEntry("wallpaperplugin", oldContainment->wallpaper()); 1861 newContainment->restore(newCg); 1862 newContainment->updateConstraints(Plasma::Types::StartupCompletedConstraint); 1863 newContainment->flushPendingConstraintsEvents(); 1864 Q_EMIT containmentAdded(newContainment); 1865 1866 // Move the applets 1867 const auto applets = oldContainment->applets(); 1868 for (Plasma::Applet *applet : applets) { 1869 newContainment->addApplet(applet); 1870 } 1871 1872 // remove the "remove" action 1873 QAction *removeAction = newContainment->actions()->action(QStringLiteral("remove")); 1874 if (removeAction) { 1875 removeAction->deleteLater(); 1876 } 1877 (*viewIt)->setContainment(newContainment); 1878 newContainment->setActivity(oldContainment->activity()); 1879 1880 oldContainment->destroy(); 1881 1882 // removing the focus from the item that is going to be destroyed 1883 // fixes a crash 1884 // delayout the destruction of the old containment fixes another crash 1885 (*viewIt)->rootObject()->setFocus(true, Qt::MouseFocusReason); 1886 QTimer::singleShot(2500, oldContainment, &Plasma::Applet::destroy); 1887 1888 // Save now as we now have a screen, so lastScreen will not be -1 1889 newContainment->save(newCg); 1890 requestConfigSync(); 1891 Q_EMIT availableScreenRectChanged(); 1892 1893 return newContainment; 1894 } 1895 1896 void ShellCorona::checkAddPanelAction() 1897 { 1898 delete m_addPanelAction; 1899 m_addPanelAction = nullptr; 1900 1901 m_addPanelsMenu.reset(nullptr); 1902 1903 const QList<KPluginMetaData> panelContainmentPlugins = Plasma::PluginLoader::listContainmentsMetaDataOfType(QStringLiteral("Panel")); 1904 1905 auto filter = [](const KPluginMetaData &md) -> bool { 1906 return !md.rawData().value(QStringLiteral("NoDisplay")).toBool() 1907 && md.value(QStringLiteral("X-Plasma-ContainmentCategories"), QStringList()).contains(QLatin1String("panel")); 1908 }; 1909 QList<KPluginMetaData> templates = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/LayoutTemplate"), QString(), filter); 1910 1911 if (panelContainmentPlugins.count() + templates.count() == 1) { 1912 m_addPanelAction = new QAction(this); 1913 connect(m_addPanelAction, &QAction::triggered, this, qOverload<>(&ShellCorona::addPanel)); 1914 } else if (!panelContainmentPlugins.isEmpty()) { 1915 m_addPanelAction = new QAction(this); 1916 m_addPanelsMenu.reset(new QMenu); 1917 m_addPanelAction->setMenu(m_addPanelsMenu.get()); 1918 connect(m_addPanelsMenu.get(), &QMenu::aboutToShow, this, &ShellCorona::populateAddPanelsMenu); 1919 connect(m_addPanelsMenu.get(), &QMenu::triggered, this, qOverload<QAction *>(&ShellCorona::addPanel)); 1920 } 1921 1922 if (m_addPanelAction) { 1923 m_addPanelAction->setText(i18n("Add Panel")); 1924 m_addPanelAction->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); 1925 actions()->addAction(QStringLiteral("add panel"), m_addPanelAction); 1926 } 1927 } 1928 1929 void ShellCorona::populateAddPanelsMenu() 1930 { 1931 m_addPanelsMenu->clear(); 1932 const KPluginMetaData emptyInfo; 1933 1934 const QList<KPluginMetaData> panelContainmentPlugins = Plasma::PluginLoader::listContainmentsMetaDataOfType(QStringLiteral("Panel")); 1935 QMap<QString, QPair<KPluginMetaData, KPluginMetaData>> sorted; 1936 for (const KPluginMetaData &plugin : panelContainmentPlugins) { 1937 if (plugin.rawData().value(QStringLiteral("NoDisplay")).toBool()) { 1938 continue; 1939 } 1940 sorted.insert(plugin.name(), qMakePair(plugin, KPluginMetaData())); 1941 } 1942 1943 auto filter = [](const KPluginMetaData &md) -> bool { 1944 return !md.rawData().value(QStringLiteral("NoDisplay")).toBool() 1945 && md.value(QStringLiteral("X-Plasma-ContainmentCategories"), QStringList()).contains(QLatin1String("panel")); 1946 }; 1947 const QList<KPluginMetaData> templates = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/LayoutTemplate"), QString(), filter); 1948 for (const auto &tpl : templates) { 1949 sorted.insert(tpl.name(), qMakePair(emptyInfo, tpl)); 1950 } 1951 1952 QMapIterator<QString, QPair<KPluginMetaData, KPluginMetaData>> it(sorted); 1953 KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LayoutTemplate")); 1954 while (it.hasNext()) { 1955 it.next(); 1956 QPair<KPluginMetaData, KPluginMetaData> pair = it.value(); 1957 if (pair.first.isValid()) { 1958 KPluginMetaData plugin = pair.first; 1959 QAction *action = m_addPanelsMenu->addAction(i18nc("Creates an empty containment (%1 is the containment name)", "Empty %1", plugin.name())); 1960 if (!plugin.iconName().isEmpty()) { 1961 action->setIcon(QIcon::fromTheme(plugin.iconName())); 1962 } 1963 1964 action->setData(plugin.pluginId()); 1965 } else { 1966 KPluginMetaData plugin(pair.second); 1967 package.setPath(plugin.pluginId()); 1968 const QString scriptFile = package.filePath("mainscript"); 1969 if (!scriptFile.isEmpty()) { 1970 QAction *action = m_addPanelsMenu->addAction(plugin.name()); 1971 action->setData(QStringLiteral("plasma-desktop-template:%1").arg(plugin.pluginId())); 1972 } 1973 } 1974 } 1975 } 1976 1977 void ShellCorona::addPanel() 1978 { 1979 const QList<KPluginMetaData> panelPlugins = Plasma::PluginLoader::listContainmentsMetaDataOfType(QStringLiteral("Panel")); 1980 1981 if (!panelPlugins.isEmpty()) { 1982 addPanel(panelPlugins.first().pluginId()); 1983 } 1984 } 1985 1986 void ShellCorona::addPanel(QAction *action) 1987 { 1988 const QString plugin = action->data().toString(); 1989 #if USE_SCRIPTING 1990 if (plugin.startsWith(QLatin1String("plasma-desktop-template:"))) { 1991 WorkspaceScripting::ScriptEngine scriptEngine(this); 1992 1993 connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, [](const QString &msg) { 1994 qCWarning(PLASMASHELL) << msg; 1995 }); 1996 connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, [](const QString &msg) { 1997 qCDebug(PLASMASHELL) << msg; 1998 }); 1999 const QString templateName = plugin.right(plugin.length() - qstrlen("plasma-desktop-template:")); 2000 2001 scriptEngine.evaluateScript(QStringLiteral("loadTemplate(\"%1\")").arg(templateName)); 2002 } else 2003 #endif 2004 if (!plugin.isEmpty()) { 2005 addPanel(plugin); 2006 } 2007 } 2008 2009 Plasma::Containment *ShellCorona::addPanel(const QString &plugin) 2010 { 2011 Plasma::Containment *panel = createContainment(plugin); 2012 if (!panel) { 2013 return nullptr; 2014 } 2015 2016 // find out what screen this panel should go on 2017 QScreen *wantedScreen = qGuiApp->focusWindow() ? qGuiApp->focusWindow()->screen() : m_screenPool->primaryScreen(); 2018 2019 QList<Plasma::Types::Location> availableLocations; 2020 availableLocations << Plasma::Types::BottomEdge << Plasma::Types::TopEdge << Plasma::Types::LeftEdge << Plasma::Types::RightEdge; 2021 2022 for (auto it = m_panelViews.constBegin(); it != m_panelViews.constEnd(); ++it) { 2023 if ((*it)->screenToFollow() == wantedScreen) { 2024 availableLocations.removeAll((*it)->location()); 2025 } 2026 } 2027 2028 Plasma::Types::Location loc; 2029 if (availableLocations.isEmpty()) { 2030 loc = Plasma::Types::TopEdge; 2031 } else { 2032 loc = availableLocations.first(); 2033 } 2034 2035 panel->setLocation(loc); 2036 switch (loc) { 2037 case Plasma::Types::LeftEdge: 2038 case Plasma::Types::RightEdge: 2039 panel->setFormFactor(Plasma::Types::Vertical); 2040 break; 2041 default: 2042 panel->setFormFactor(Plasma::Types::Horizontal); 2043 break; 2044 } 2045 2046 Q_ASSERT(panel); 2047 m_waitingPanels << panel; 2048 // immediately create the panel here so that we have access to the panel view 2049 createWaitingPanels(); 2050 2051 if (m_panelViews.contains(panel)) { 2052 m_panelViews.value(panel)->setScreenToFollow(wantedScreen); 2053 } 2054 2055 return panel; 2056 } 2057 2058 void ShellCorona::swapDesktopScreens(int oldScreen, int newScreen) 2059 { 2060 for (auto *containment : containmentsForScreen(oldScreen)) { 2061 if (containment->containmentType() != Plasma::Types::PanelContainment && containment->containmentType() != Plasma::Types::CustomPanelContainment) { 2062 setScreenForContainment(containment, newScreen); 2063 } 2064 } 2065 } 2066 2067 void ShellCorona::setScreenForContainment(Plasma::Containment *containment, int newScreenId) 2068 { 2069 const int oldScreenId = containment->screen() >= 0 ? containment->screen() : containment->lastScreen(); 2070 2071 if (oldScreenId == newScreenId) { 2072 return; 2073 } 2074 2075 m_pendingScreenChanges[containment] = newScreenId; 2076 2077 if (containment->containmentType() == Plasma::Types::PanelContainment || containment->containmentType() == Plasma::Types::CustomPanelContainment) { 2078 // Panel Case 2079 containment->reactToScreenChange(); 2080 auto *panelView = m_panelViews.value(containment); 2081 // If newScreen != nullptr we are also assured it won't be redundant 2082 QScreen *newScreen = m_screenPool->screenForId(newScreenId); 2083 2084 if (panelView) { 2085 // There was an existing panel view 2086 if (newScreen) { 2087 panelView->setScreenToFollow(newScreen); 2088 } else { 2089 // Not on a connected screen: destroy the panel 2090 if (!m_waitingPanels.contains(containment)) { 2091 m_waitingPanels << containment; 2092 } 2093 m_panelViews.remove(containment); 2094 panelView->destroy(); 2095 } 2096 } else { 2097 // Didn't have a view, createWaitingPanels() will create it if needed 2098 createWaitingPanels(); 2099 } 2100 2101 } else { 2102 // Desktop case: a bit more complicate because we may have to swap 2103 Plasma::Containment *contSwap = containmentForScreen(newScreenId, containment->activity(), "org.kde.plasma.folder"); 2104 Q_ASSERT(contSwap); 2105 2106 // Perform the lastScreen changes 2107 containment->reactToScreenChange(); 2108 if (contSwap) { 2109 m_pendingScreenChanges[contSwap] = oldScreenId; 2110 contSwap->reactToScreenChange(); 2111 } 2112 2113 // If those will be != nullptr we are also assured they won't be redundant 2114 QScreen *oldScreen = m_screenPool->screenForId(oldScreenId); 2115 QScreen *newScreen = m_screenPool->screenForId(newScreenId); 2116 2117 // Actually perform a view swap when we are dealing with containments of current activity 2118 if (contSwap && containment->activity() == m_activityController->currentActivity()) { 2119 auto *containmentView = m_desktopViewForScreen.value(oldScreenId); 2120 auto *contSwapView = m_desktopViewForScreen.value(newScreenId); 2121 2122 if (containmentView) { 2123 Q_ASSERT(containment == containmentView->containment()); 2124 m_desktopViewForScreen.remove(oldScreenId); 2125 } 2126 if (contSwapView) { 2127 Q_ASSERT(contSwap == contSwapView->containment()); 2128 m_desktopViewForScreen.remove(newScreenId); 2129 } 2130 2131 if (containmentView) { 2132 if (newScreen) { 2133 containmentView->setScreenToFollow(newScreen); 2134 m_desktopViewForScreen[newScreenId] = containmentView; 2135 } else { 2136 containmentView->destroy(); 2137 } 2138 } else if (newScreen) { 2139 addOutput(newScreen); 2140 } 2141 2142 if (contSwapView) { 2143 if (oldScreen) { 2144 contSwapView->setScreenToFollow(oldScreen); 2145 m_desktopViewForScreen[oldScreenId] = contSwapView; 2146 } else { 2147 contSwapView->destroy(); 2148 } 2149 } else if (oldScreen) { 2150 addOutput(oldScreen); 2151 } 2152 } 2153 } 2154 2155 m_pendingScreenChanges.clear(); 2156 } 2157 2158 int ShellCorona::screenForContainment(const Plasma::Containment *containment) const 2159 { 2160 // TODO: when we can depend on a new framework, use a p-f method to actuall set lastScreen instead of this? 2161 // m_pendingScreenChanges controls an explicit user-determined screen change 2162 if (!m_pendingScreenChanges.isEmpty() && m_pendingScreenChanges.contains(containment)) { 2163 return m_pendingScreenChanges.value(containment); 2164 } 2165 2166 // case in which this containment is child of an applet, hello systray :) 2167 if (Plasma::Applet *parentApplet = qobject_cast<Plasma::Applet *>(containment->parent())) { 2168 if (Plasma::Containment *cont = parentApplet->containment()) { 2169 return screenForContainment(cont); 2170 } else { 2171 return -1; 2172 } 2173 } 2174 2175 // This part is necessary as is the thing which promotes a containment from screen -1 to 2176 // its final screen which will be saved as lastScreen) 2177 // if the desktop views already exist, base the decision upon them 2178 for (auto it = m_desktopViewForScreen.constBegin(), end = m_desktopViewForScreen.constEnd(); it != end; ++it) { 2179 if (it.value()->containment() == containment && containment->activity() == m_activityController->currentActivity()) { 2180 return m_screenPool->idForScreen(it.value()->screenToFollow()); 2181 } 2182 } 2183 2184 // if the panel views already exist, base upon them 2185 PanelView *view = m_panelViews.value(containment); 2186 if (view && view->screenToFollow()) { 2187 return m_screenPool->idForScreen(view->screenToFollow()); 2188 } 2189 2190 return -1; 2191 } 2192 2193 void ShellCorona::grabContainmentPreview(Plasma::Containment *containment) 2194 { 2195 QQuickWindow *viewToGrab = nullptr; 2196 2197 if (containment->containmentType() == Plasma::Types::PanelContainment || containment->containmentType() == Plasma::Types::CustomPanelContainment) { 2198 // Panel Containment 2199 auto it = m_panelViews.constBegin(); 2200 while (it != m_panelViews.constEnd()) { 2201 if (it.key() == containment) { 2202 viewToGrab = it.value(); 2203 break; 2204 } 2205 it++; 2206 } 2207 } else if (m_desktopViewForScreen.contains(containment->screen())) { 2208 // Desktop Containment 2209 viewToGrab = m_desktopViewForScreen[containment->screen()]; 2210 } 2211 2212 if (viewToGrab) { 2213 QSize size(512, 512); 2214 size = viewToGrab->size().scaled(size, Qt::KeepAspectRatio); 2215 auto result = viewToGrab->contentItem()->grabToImage(size); 2216 2217 if (result) { 2218 connect(result.data(), &QQuickItemGrabResult::ready, this, [this, result, containment]() { 2219 // DataLocation is plasmashell, we need just "plasma" 2220 QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); 2221 QDir dir(path); 2222 path += QStringLiteral("/plasma/containmentpreviews/"); 2223 dir.mkpath(path); 2224 path += QString::number(containment->id()) + QChar('-') + containment->activity() + QStringLiteral(".png"); 2225 result->saveToFile(path); 2226 emit containmentPreviewReady(containment, path); 2227 }); 2228 } 2229 } 2230 } 2231 2232 QString ShellCorona::containmentPreviewPath(Plasma::Containment *containment) const 2233 { 2234 const QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/plasma/containmentpreviews/") 2235 + QString::number(containment->id()) + QChar('-') + containment->activity() + QStringLiteral(".png"); 2236 if (QFile::exists(path)) { 2237 return path; 2238 } else { 2239 return QString(); 2240 } 2241 } 2242 2243 bool ShellCorona::accentColorFromWallpaperEnabled() const 2244 { 2245 return m_accentColorFromWallpaperEnabled; 2246 } 2247 2248 void ShellCorona::nextActivity() 2249 { 2250 m_activityController->nextActivity(); 2251 } 2252 2253 void ShellCorona::previousActivity() 2254 { 2255 m_activityController->previousActivity(); 2256 } 2257 2258 void ShellCorona::stopCurrentActivity() 2259 { 2260 const QStringList list = m_activityController->activities(KActivities::Info::Running); 2261 if (list.isEmpty()) { 2262 return; 2263 } 2264 2265 m_activityController->stopActivity(m_activityController->currentActivity()); 2266 } 2267 2268 /** 2269 * @internal 2270 * 2271 * The DismissPopupEventFilter class monitors mouse button press events and 2272 * when needed dismisses the active popup widget. 2273 * 2274 * plasmashell uses both QtQuick and QtWidgets under a single roof, QtQuick is 2275 * used for most of things, while QtWidgets is used for things such as context 2276 * menus, etc. 2277 * 2278 * If user clicks outside a popup window, it's expected that the popup window 2279 * will be closed. On X11, it's achieved by establishing both a keyboard grab 2280 * and a pointer grab. But on Wayland, you can't grab keyboard or pointer. If 2281 * user clicks a surface of another app, the compositor will dismiss the popup 2282 * surface. However, if user clicks some surface of the same application, the 2283 * popup surface won't be dismissed, it's up to the application to decide 2284 * whether the popup must be closed. In 99% cases, it must. 2285 * 2286 * Qt has some code that dismisses the active popup widget if another window 2287 * of the same app has been clicked. But, that code works only if the 2288 * application uses solely Qt widgets. See QTBUG-83972. For plasma it doesn't 2289 * work, because as we said previously, it uses both Qt Quick and Qt Widgets. 2290 * 2291 * Ideally, this bug needs to be fixed upstream, but given that it'll involve 2292 * major changes in Qt, the chances of it being fixed any time soon are slim. 2293 * 2294 * In order to work around the popup dismissal bug, we install an event filter 2295 * that monitors Qt::MouseButtonPress events. If it happens that user has 2296 * clicked outside an active popup widget, that popup will be closed. This 2297 * event filter is not needed on X11! 2298 */ 2299 class DismissPopupEventFilter : public QObject 2300 { 2301 Q_OBJECT 2302 2303 public: 2304 explicit DismissPopupEventFilter(QObject *parent = nullptr); 2305 2306 protected: 2307 bool eventFilter(QObject *watched, QEvent *event) override; 2308 2309 private: 2310 bool m_filterMouseEvents = false; 2311 }; 2312 2313 DismissPopupEventFilter::DismissPopupEventFilter(QObject *parent) 2314 : QObject(parent) 2315 { 2316 } 2317 2318 bool DismissPopupEventFilter::eventFilter(QObject *watched, QEvent *event) 2319 { 2320 if (event->type() == QEvent::MouseButtonPress) { 2321 if (m_filterMouseEvents) { 2322 // Eat events until all mouse buttons are released. 2323 return true; 2324 } 2325 2326 QWidget *popup = QApplication::activePopupWidget(); 2327 if (!popup) { 2328 return false; 2329 } 2330 2331 QWindow *window = qobject_cast<QWindow *>(watched); 2332 if (popup->windowHandle() == window) { 2333 // The popup window handles mouse events before the widget. 2334 return false; 2335 } 2336 2337 QWidget *widget = qobject_cast<QWidget *>(watched); 2338 if (widget) { 2339 // Let the popup widget handle the mouse press event. 2340 return false; 2341 } 2342 2343 popup->close(); 2344 m_filterMouseEvents = true; 2345 return true; 2346 2347 } else if (event->type() == QEvent::MouseButtonRelease) { 2348 if (m_filterMouseEvents) { 2349 // Eat events until all mouse buttons are released. 2350 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event); 2351 if (mouseEvent->buttons() == Qt::NoButton) { 2352 m_filterMouseEvents = false; 2353 } 2354 return true; 2355 } 2356 } 2357 2358 return false; 2359 } 2360 2361 void ShellCorona::setupWaylandIntegration() 2362 { 2363 if (!KWindowSystem::isPlatformWayland()) { 2364 return; 2365 } 2366 using namespace KWayland::Client; 2367 ConnectionThread *connection = ConnectionThread::fromApplication(this); 2368 if (!connection) { 2369 return; 2370 } 2371 Registry *registry = new Registry(this); 2372 registry->create(connection); 2373 connect(registry, &Registry::plasmaShellAnnounced, this, [this, registry](quint32 name, quint32 version) { 2374 m_waylandPlasmaShell = registry->createPlasmaShell(name, version, this); 2375 }); 2376 connect(registry, &KWayland::Client::Registry::plasmaWindowManagementAnnounced, this, [this, registry](quint32 name, quint32 version) { 2377 m_waylandWindowManagement = registry->createPlasmaWindowManagement(name, version, this); 2378 }); 2379 registry->setup(); 2380 connection->roundtrip(); 2381 qApp->installEventFilter(new DismissPopupEventFilter(this)); 2382 } 2383 2384 KWayland::Client::PlasmaShell *ShellCorona::waylandPlasmaShellInterface() const 2385 { 2386 return m_waylandPlasmaShell; 2387 } 2388 2389 ScreenPool *ShellCorona::screenPool() const 2390 { 2391 return m_screenPool; 2392 } 2393 2394 QList<int> ShellCorona::screenIds() const 2395 { 2396 QList<int> ids; 2397 for (int i = 0; i < m_screenPool->screenOrder().size(); ++i) { 2398 ids.append(i); 2399 } 2400 return ids; 2401 } 2402 2403 QString ShellCorona::defaultContainmentPlugin() const 2404 { 2405 QString plugin = m_lnfDefaultsConfig.readEntry("Containment", QString()); 2406 if (plugin.isEmpty()) { 2407 plugin = m_desktopDefaultsConfig.readEntry("Containment", "org.kde.desktopcontainment"); 2408 } 2409 return plugin; 2410 } 2411 2412 void ShellCorona::updateStruts() 2413 { 2414 for (PanelView *view : qAsConst(m_panelViews)) { 2415 view->updateStruts(); 2416 } 2417 } 2418 2419 void ShellCorona::configurationChanged(const QString &path) 2420 { 2421 if (path == m_configPath) { 2422 m_config->reparseConfiguration(); 2423 } 2424 } 2425 2426 void ShellCorona::activateLauncherMenu() 2427 { 2428 auto message = QDBusMessage::createMethodCall("org.kde.KWin", "/KWin", "org.kde.KWin", "activeOutputName"); 2429 auto watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(message)); 2430 connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher] { 2431 watcher->deleteLater(); 2432 QDBusReply<QString> reply = *watcher; 2433 if (reply.isValid()) { 2434 activateLauncherMenu(reply.value()); 2435 } 2436 }); 2437 } 2438 2439 void ShellCorona::activateLauncherMenu(const QString &screenName) 2440 { 2441 auto activateLauncher = [](Plasma::Applet *applet) -> bool { 2442 const auto provides = applet->pluginMetaData().value(QStringLiteral("X-Plasma-Provides"), QStringList()); 2443 if (provides.contains(QLatin1String("org.kde.plasma.launchermenu"))) { 2444 Q_EMIT applet->activated(); 2445 return true; 2446 } 2447 return false; 2448 }; 2449 2450 int screenId = m_screenPool->idForName(screenName); 2451 2452 for (auto *cont : containments()) { 2453 if ((screenId < 0 || cont->screen() == screenId) 2454 && (cont->containmentType() == Plasma::Types::ContainmentType::PanelContainment 2455 || cont->containmentType() == Plasma::Types::ContainmentType::CustomPanelContainment)) { 2456 const auto applets = cont->applets(); 2457 for (auto applet : applets) { 2458 if (activateLauncher(applet)) { 2459 return; 2460 } 2461 } 2462 if (activateLauncher(cont)) { 2463 return; 2464 } 2465 } 2466 } 2467 2468 for (auto *cont : containments()) { 2469 if ((screenId < 0 || cont->screen() == screenId) && cont->containmentType() == Plasma::Types::ContainmentType::DesktopContainment) { 2470 const auto applets = cont->applets(); 2471 for (auto applet : applets) { 2472 if (activateLauncher(applet)) { 2473 return; 2474 } 2475 } 2476 if (activateLauncher(cont)) { 2477 return; 2478 } 2479 } 2480 } 2481 2482 if (screenId >= 0) { 2483 activateLauncherMenu(QString()); 2484 } 2485 } 2486 2487 void ShellCorona::activateTaskManagerEntry(int index) 2488 { 2489 auto activateTaskManagerEntryOnContainment = [](const Plasma::Containment *c, int index) { 2490 const auto &applets = c->applets(); 2491 for (auto *applet : applets) { 2492 const auto &provides = applet->pluginMetaData().value(QStringLiteral("X-Plasma-Provides"), QStringList()); 2493 if (provides.contains(QLatin1String("org.kde.plasma.multitasking"))) { 2494 if (QQuickItem *appletInterface = applet->property("_plasma_graphicObject").value<QQuickItem *>()) { 2495 const auto &childItems = appletInterface->childItems(); 2496 if (childItems.isEmpty()) { 2497 continue; 2498 } 2499 2500 for (QQuickItem *item : childItems) { 2501 if (auto *metaObject = item->metaObject()) { 2502 // not using QMetaObject::invokeMethod to avoid warnings when calling 2503 // this on applets that don't have it or other child items since this 2504 // is pretty much trial and error. 2505 2506 // Also, "var" arguments are treated as QVariant in QMetaObject 2507 int methodIndex = metaObject->indexOfMethod("activateTaskAtIndex(QVariant)"); 2508 if (methodIndex == -1) { 2509 continue; 2510 } 2511 2512 QMetaMethod method = metaObject->method(methodIndex); 2513 if (method.invoke(item, Q_ARG(QVariant, index))) { 2514 return true; 2515 } 2516 } 2517 } 2518 } 2519 } 2520 } 2521 return false; 2522 }; 2523 2524 // To avoid overly complex configuration, we'll try to get the 90% usecase to work 2525 // which is activating a task on the task manager on a panel on the primary screen. 2526 2527 for (auto it = m_panelViews.constBegin(), end = m_panelViews.constEnd(); it != end; ++it) { 2528 if (it.value()->screen() != m_screenPool->primaryScreen()) { 2529 continue; 2530 } 2531 if (activateTaskManagerEntryOnContainment(it.key(), index)) { 2532 return; 2533 } 2534 } 2535 2536 // we didn't find anything on primary, try all the panels 2537 for (auto it = m_panelViews.constBegin(), end = m_panelViews.constEnd(); it != end; ++it) { 2538 if (activateTaskManagerEntryOnContainment(it.key(), index)) { 2539 return; 2540 } 2541 } 2542 } 2543 2544 QString ShellCorona::defaultShell() 2545 { 2546 KSharedConfig::Ptr startupConf = KSharedConfig::openConfig(QStringLiteral("plasmashellrc")); 2547 KConfigGroup startupConfGroup(startupConf, "Shell"); 2548 const QString defaultValue = qEnvironmentVariable("PLASMA_DEFAULT_SHELL", "org.kde.plasma.desktop"); 2549 QString value = startupConfGroup.readEntry("ShellPackage", defaultValue); 2550 2551 // In the global theme an empty value was written, make sure we still return a shell package 2552 return value.isEmpty() ? defaultValue : value; 2553 } 2554 2555 void ShellCorona::refreshCurrentShell() 2556 { 2557 KSharedConfig::openConfig(QStringLiteral("plasmashellrc"))->reparseConfiguration(); 2558 // FIXME: setShell(defaultShell()); 2559 QProcess::startDetached("plasmashell", {"--replace"}); 2560 } 2561 2562 // Desktop corona handler 2563 2564 #include "moc_shellcorona.cpp" 2565 #include "shellcorona.moc"