File indexing completed on 2024-12-08 13:22:28
0001 /* 0002 * Copyright 2016 Smith AR <audoban@openmaibox.org> 0003 * Michail Vourlakos <mvourlakos@gmail.com> 0004 * 0005 * This file is part of Latte-Dock 0006 * 0007 * Latte-Dock is free software; you can redistribute it and/or 0008 * modify it under the terms of the GNU General Public License as 0009 * published by the Free Software Foundation; either version 2 of 0010 * the License, or (at your option) any later version. 0011 * 0012 * Latte-Dock is distributed in the hope that it will be useful, 0013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0015 * GNU General Public License for more details. 0016 * 0017 * You should have received a copy of the GNU General Public License 0018 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0019 */ 0020 0021 #include "lattecorona.h" 0022 0023 // local 0024 #include "alternativeshelper.h" 0025 #include "lattedockadaptor.h" 0026 #include "screenpool.h" 0027 #include "indicator/factory.h" 0028 #include "layout/centrallayout.h" 0029 #include "layout/genericlayout.h" 0030 #include "layout/sharedlayout.h" 0031 #include "layouts/importer.h" 0032 #include "layouts/manager.h" 0033 #include "layouts/synchronizer.h" 0034 #include "layouts/launcherssignals.h" 0035 #include "shortcuts/globalshortcuts.h" 0036 #include "package/lattepackage.h" 0037 #include "plasma/extended/screenpool.h" 0038 #include "plasma/extended/theme.h" 0039 #include "settings/universalsettings.h" 0040 #include "view/view.h" 0041 #include "view/windowstracker/windowstracker.h" 0042 #include "view/windowstracker/allscreenstracker.h" 0043 #include "view/windowstracker/currentscreentracker.h" 0044 #include "wm/abstractwindowinterface.h" 0045 #include "wm/schemecolors.h" 0046 #include "wm/waylandinterface.h" 0047 #include "wm/xwindowinterface.h" 0048 #include "wm/tracker/lastactivewindow.h" 0049 #include "wm/tracker/schemes.h" 0050 #include "wm/tracker/windowstracker.h" 0051 0052 // Qt 0053 #include <QAction> 0054 #include <QApplication> 0055 #include <QScreen> 0056 #include <QDBusConnection> 0057 #include <QDebug> 0058 #include <QDesktopWidget> 0059 #include <QFile> 0060 #include <QFontDatabase> 0061 #include <QQmlContext> 0062 0063 // Plasma 0064 #include <Plasma> 0065 #include <Plasma/Corona> 0066 #include <Plasma/Containment> 0067 #include <PlasmaQuick/ConfigView> 0068 0069 // KDE 0070 #include <KActionCollection> 0071 #include <KPluginMetaData> 0072 #include <KGlobalAccel> 0073 #include <KLocalizedString> 0074 #include <KPackage/Package> 0075 #include <KPackage/PackageLoader> 0076 #include <KAboutData> 0077 #include <KActivities/Consumer> 0078 #include <KDeclarative/QmlObjectSharedEngine> 0079 #include <KWindowSystem> 0080 #include <KWayland/Client/connection_thread.h> 0081 #include <KWayland/Client/registry.h> 0082 #include <KWayland/Client/plasmashell.h> 0083 #include <KWayland/Client/plasmawindowmanagement.h> 0084 0085 namespace Latte { 0086 0087 Corona::Corona(bool defaultLayoutOnStartup, QString layoutNameOnStartUp, int userSetMemoryUsage, QObject *parent) 0088 : Plasma::Corona(parent), 0089 m_defaultLayoutOnStartup(defaultLayoutOnStartup), 0090 m_userSetMemoryUsage(userSetMemoryUsage), 0091 m_layoutNameOnStartUp(layoutNameOnStartUp), 0092 m_activityConsumer(new KActivities::Consumer(this)), 0093 m_screenPool(new ScreenPool(KSharedConfig::openConfig(), this)), 0094 m_indicatorFactory(new Indicator::Factory(this)), 0095 m_universalSettings(new UniversalSettings(KSharedConfig::openConfig(), this)), 0096 m_globalShortcuts(new GlobalShortcuts(this)), 0097 m_plasmaScreenPool(new PlasmaExtended::ScreenPool(this)), 0098 m_themeExtended(new PlasmaExtended::Theme(KSharedConfig::openConfig(), this)), 0099 m_layoutsManager(new Layouts::Manager(this)), 0100 m_dialogShadows(new PanelShadows(this, QStringLiteral("dialogs/background"))) 0101 { 0102 //! create the window manager 0103 0104 if (KWindowSystem::isPlatformWayland()) { 0105 m_wm = new WindowSystem::WaylandInterface(this); 0106 } else { 0107 m_wm = new WindowSystem::XWindowInterface(this); 0108 } 0109 0110 setupWaylandIntegration(); 0111 0112 KPackage::Package package(new Latte::Package(this)); 0113 0114 m_screenPool->load(); 0115 0116 if (!package.isValid()) { 0117 qWarning() << staticMetaObject.className() 0118 << "the package" << package.metadata().rawData() << "is invalid!"; 0119 return; 0120 } else { 0121 qDebug() << staticMetaObject.className() 0122 << "the package" << package.metadata().rawData() << "is valid!"; 0123 } 0124 0125 setKPackage(package); 0126 //! universal settings / extendedtheme must be loaded after the package has been set 0127 m_universalSettings->load(); 0128 m_themeExtended->load(); 0129 0130 qmlRegisterTypes(); 0131 0132 if (m_activityConsumer && (m_activityConsumer->serviceStatus() == KActivities::Consumer::Running)) { 0133 load(); 0134 } 0135 0136 connect(m_activityConsumer, &KActivities::Consumer::serviceStatusChanged, this, &Corona::load); 0137 0138 m_viewsScreenSyncTimer.setSingleShot(true); 0139 m_viewsScreenSyncTimer.setInterval(m_universalSettings->screenTrackerInterval()); 0140 connect(&m_viewsScreenSyncTimer, &QTimer::timeout, this, &Corona::syncLatteViewsToScreens); 0141 connect(m_universalSettings, &UniversalSettings::screenTrackerIntervalChanged, this, [this]() { 0142 m_viewsScreenSyncTimer.setInterval(m_universalSettings->screenTrackerInterval()); 0143 }); 0144 0145 //! initialize the background tracer for broadcasted backgrounds 0146 m_backgroundTracer = new KDeclarative::QmlObjectSharedEngine(this); 0147 m_backgroundTracer->setInitializationDelayed(true); 0148 m_backgroundTracer->setSource(kPackage().filePath("backgroundTracer")); 0149 m_backgroundTracer->completeInitialization(); 0150 0151 //! Dbus adaptor initialization 0152 new LatteDockAdaptor(this); 0153 QDBusConnection dbus = QDBusConnection::sessionBus(); 0154 dbus.registerObject(QStringLiteral("/Latte"), this); 0155 } 0156 0157 Corona::~Corona() 0158 { 0159 //! BEGIN: Give the time to slide-out views when closing 0160 m_layoutsManager->synchronizer()->hideAllViews(); 0161 0162 //! Don't delay the destruction under wayland in any case 0163 //! because it creates a crash with kwin effects 0164 //! https://bugs.kde.org/show_bug.cgi?id=392890 0165 if (!KWindowSystem::isPlatformWayland()) { 0166 QTimer::singleShot(400, [this]() { 0167 m_quitTimedEnded = true; 0168 }); 0169 0170 while (!m_quitTimedEnded) { 0171 QGuiApplication::processEvents(QEventLoop::AllEvents, 50); 0172 } 0173 } 0174 0175 //! END: slide-out views when closing 0176 0177 m_viewsScreenSyncTimer.stop(); 0178 0179 if (m_layoutsManager->memoryUsage() == Types::SingleLayout) { 0180 cleanConfig(); 0181 } 0182 0183 qDebug() << "Latte Corona - unload: containments ..."; 0184 0185 m_layoutsManager->unload(); 0186 0187 m_wm->deleteLater(); 0188 m_dialogShadows->deleteLater(); 0189 m_globalShortcuts->deleteLater(); 0190 m_layoutsManager->deleteLater(); 0191 m_screenPool->deleteLater(); 0192 m_universalSettings->deleteLater(); 0193 m_plasmaScreenPool->deleteLater(); 0194 m_backgroundTracer->deleteLater(); 0195 m_themeExtended->deleteLater(); 0196 m_indicatorFactory->deleteLater(); 0197 0198 disconnect(m_activityConsumer, &KActivities::Consumer::serviceStatusChanged, this, &Corona::load); 0199 delete m_activityConsumer; 0200 0201 qDebug() << "Latte Corona - deleted..."; 0202 } 0203 0204 void Corona::load() 0205 { 0206 if (m_activityConsumer && (m_activityConsumer->serviceStatus() == KActivities::Consumer::Running) && m_activitiesStarting) { 0207 m_activitiesStarting = false; 0208 0209 disconnect(m_activityConsumer, &KActivities::Consumer::serviceStatusChanged, this, &Corona::load); 0210 0211 m_layoutsManager->load(); 0212 0213 connect(this, &Corona::availableScreenRectChangedFrom, this, &Plasma::Corona::availableScreenRectChanged); 0214 connect(this, &Corona::availableScreenRegionChangedFrom, this, &Plasma::Corona::availableScreenRegionChanged); 0215 0216 connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &Corona::primaryOutputChanged, Qt::UniqueConnection); 0217 0218 connect(m_screenPool, &ScreenPool::primaryPoolChanged, this, &Corona::screenCountChanged); 0219 0220 QString assignedLayout = m_layoutsManager->synchronizer()->shouldSwitchToLayout(m_activityConsumer->currentActivity()); 0221 0222 QString loadLayoutName = ""; 0223 0224 if (!m_defaultLayoutOnStartup && m_layoutNameOnStartUp.isEmpty()) { 0225 if (!assignedLayout.isEmpty() && assignedLayout != m_universalSettings->currentLayoutName()) { 0226 loadLayoutName = assignedLayout; 0227 } else { 0228 loadLayoutName = m_universalSettings->currentLayoutName(); 0229 } 0230 0231 if (!m_layoutsManager->synchronizer()->layoutExists(loadLayoutName)) { 0232 loadLayoutName = m_layoutsManager->defaultLayoutName(); 0233 m_layoutsManager->importDefaultLayout(false); 0234 } 0235 } else if (m_defaultLayoutOnStartup) { 0236 loadLayoutName = m_layoutsManager->importer()->uniqueLayoutName(m_layoutsManager->defaultLayoutName()); 0237 m_layoutsManager->importDefaultLayout(true); 0238 } else { 0239 loadLayoutName = m_layoutNameOnStartUp; 0240 } 0241 0242 if (m_userSetMemoryUsage != -1 && !KWindowSystem::isPlatformWayland()) { 0243 Types::LayoutsMemoryUsage usage = static_cast<Types::LayoutsMemoryUsage>(m_userSetMemoryUsage); 0244 0245 m_universalSettings->setLayoutsMemoryUsage(usage); 0246 } 0247 0248 if (KWindowSystem::isPlatformWayland()) { 0249 m_universalSettings->setLayoutsMemoryUsage(Types::SingleLayout); 0250 } 0251 0252 m_layoutsManager->loadLayoutOnStartup(loadLayoutName); 0253 0254 0255 //! load screens signals such screenGeometryChanged in order to support 0256 //! plasmoid.screenGeometry properly 0257 for (QScreen *screen : qGuiApp->screens()) { 0258 addOutput(screen); 0259 } 0260 0261 connect(qGuiApp, &QGuiApplication::screenAdded, this, &Corona::addOutput, Qt::UniqueConnection); 0262 connect(qGuiApp, &QGuiApplication::screenRemoved, this, &Corona::screenRemoved, Qt::UniqueConnection); 0263 } 0264 } 0265 0266 void Corona::unload() 0267 { 0268 qDebug() << "unload: removing containments..."; 0269 0270 while (!containments().isEmpty()) { 0271 //deleting a containment will remove it from the list due to QObject::destroyed connect in Corona 0272 //this form doesn't crash, while qDeleteAll(containments()) does 0273 delete containments().first(); 0274 } 0275 } 0276 0277 void Corona::setupWaylandIntegration() 0278 { 0279 if (!KWindowSystem::isPlatformWayland()) { 0280 return; 0281 } 0282 0283 using namespace KWayland::Client; 0284 0285 auto connection = ConnectionThread::fromApplication(this); 0286 0287 if (!connection) { 0288 return; 0289 } 0290 0291 Registry *registry{new Registry(this)}; 0292 registry->create(connection); 0293 0294 connect(registry, &Registry::plasmaShellAnnounced, this 0295 , [this, registry](quint32 name, quint32 version) { 0296 m_waylandCorona = registry->createPlasmaShell(name, version, this); 0297 }); 0298 0299 QObject::connect(registry, &KWayland::Client::Registry::plasmaWindowManagementAnnounced, 0300 [this, registry](quint32 name, quint32 version) { 0301 KWayland::Client::PlasmaWindowManagement *pwm = registry->createPlasmaWindowManagement(name, version, this); 0302 0303 WindowSystem::WaylandInterface *wI = qobject_cast<WindowSystem::WaylandInterface *>(m_wm); 0304 0305 if (wI) { 0306 wI->initWindowManagement(pwm); 0307 } 0308 }); 0309 0310 #if KF5_VERSION_MINOR >= 52 0311 QObject::connect(registry, &KWayland::Client::Registry::plasmaVirtualDesktopManagementAnnounced, 0312 [this, registry] (quint32 name, quint32 version) { 0313 KWayland::Client::PlasmaVirtualDesktopManagement *vdm = registry->createPlasmaVirtualDesktopManagement(name, version, this); 0314 0315 WindowSystem::WaylandInterface *wI = qobject_cast<WindowSystem::WaylandInterface *>(m_wm); 0316 0317 if (wI) { 0318 wI->initVirtualDesktopManagement(vdm); 0319 } 0320 }); 0321 #endif 0322 0323 registry->setup(); 0324 connection->roundtrip(); 0325 } 0326 0327 KActivities::Consumer *Corona::activityConsumer() const 0328 { 0329 return m_activityConsumer; 0330 } 0331 0332 KWayland::Client::PlasmaShell *Corona::waylandCoronaInterface() const 0333 { 0334 return m_waylandCorona; 0335 } 0336 0337 void Corona::cleanConfig() 0338 { 0339 auto containmentsEntries = config()->group("Containments"); 0340 bool changed = false; 0341 0342 for(const auto &cId : containmentsEntries.groupList()) { 0343 if (!containmentExists(cId.toUInt())) { 0344 //cleanup obsolete containments 0345 containmentsEntries.group(cId).deleteGroup(); 0346 changed = true; 0347 qDebug() << "obsolete containment configuration deleted:" << cId; 0348 } else { 0349 //cleanup obsolete applets of running containments 0350 auto appletsEntries = containmentsEntries.group(cId).group("Applets"); 0351 0352 for(const auto &appletId : appletsEntries.groupList()) { 0353 if (!appletExists(cId.toUInt(), appletId.toUInt())) { 0354 appletsEntries.group(appletId).deleteGroup(); 0355 changed = true; 0356 qDebug() << "obsolete applet configuration deleted:" << appletId; 0357 } 0358 } 0359 } 0360 } 0361 0362 if (changed) { 0363 config()->sync(); 0364 qDebug() << "configuration file cleaned..."; 0365 } 0366 } 0367 0368 bool Corona::containmentExists(uint id) const 0369 { 0370 for(const auto containment : containments()) { 0371 if (id == containment->id()) { 0372 return true; 0373 } 0374 } 0375 0376 return false; 0377 } 0378 0379 bool Corona::appletExists(uint containmentId, uint appletId) const 0380 { 0381 Plasma::Containment *containment = nullptr; 0382 0383 for(const auto cont : containments()) { 0384 if (containmentId == cont->id()) { 0385 containment = cont; 0386 break; 0387 } 0388 } 0389 0390 if (!containment) { 0391 return false; 0392 } 0393 0394 for(const auto applet : containment->applets()) { 0395 if (applet->id() == appletId) { 0396 return true; 0397 } 0398 } 0399 0400 return false; 0401 } 0402 0403 KActivities::Consumer *Corona::activitiesConsumer() const 0404 { 0405 return m_activityConsumer; 0406 } 0407 0408 PanelShadows *Corona::dialogShadows() const 0409 { 0410 return m_dialogShadows; 0411 } 0412 0413 GlobalShortcuts *Corona::globalShortcuts() const 0414 { 0415 return m_globalShortcuts; 0416 } 0417 0418 ScreenPool *Corona::screenPool() const 0419 { 0420 return m_screenPool; 0421 } 0422 0423 UniversalSettings *Corona::universalSettings() const 0424 { 0425 return m_universalSettings; 0426 } 0427 0428 WindowSystem::AbstractWindowInterface *Corona::wm() const 0429 { 0430 return m_wm; 0431 } 0432 0433 Indicator::Factory *Corona::indicatorFactory() const 0434 { 0435 return m_indicatorFactory; 0436 } 0437 0438 Layouts::Manager *Corona::layoutsManager() const 0439 { 0440 return m_layoutsManager; 0441 } 0442 0443 PlasmaExtended::ScreenPool *Corona::plasmaScreenPool() const 0444 { 0445 return m_plasmaScreenPool; 0446 } 0447 0448 PlasmaExtended::Theme *Corona::themeExtended() const 0449 { 0450 return m_themeExtended; 0451 } 0452 0453 int Corona::numScreens() const 0454 { 0455 return qGuiApp->screens().count(); 0456 } 0457 0458 QRect Corona::screenGeometry(int id) const 0459 { 0460 const auto screens = qGuiApp->screens(); 0461 const QScreen *screen{qGuiApp->primaryScreen()}; 0462 0463 QString screenName; 0464 0465 if (m_screenPool->hasId(id)) { 0466 screenName = m_screenPool->connector(id); 0467 } 0468 0469 for(const auto scr : screens) { 0470 if (scr->name() == screenName) { 0471 screen = scr; 0472 break; 0473 } 0474 } 0475 0476 return screen->geometry(); 0477 } 0478 0479 CentralLayout *Corona::centralLayout(QString name) const 0480 { 0481 CentralLayout *result{nullptr}; 0482 0483 if (name.isEmpty()) { 0484 result = m_layoutsManager->currentLayout(); 0485 } else { 0486 CentralLayout *tempCentral = m_layoutsManager->synchronizer()->centralLayout(name); 0487 0488 if (!tempCentral) { 0489 //! Identify best active layout to be used for metrics calculations. 0490 //! Active layouts are always take into account their shared layouts for their metrics 0491 SharedLayout *sharedLayout = m_layoutsManager->synchronizer()->sharedLayout(name); 0492 0493 if (sharedLayout) { 0494 tempCentral = sharedLayout->currentCentralLayout(); 0495 } 0496 } 0497 0498 if (tempCentral) { 0499 result = tempCentral; 0500 } 0501 } 0502 0503 return result; 0504 } 0505 0506 QRegion Corona::availableScreenRegion(int id) const 0507 { 0508 return availableScreenRegionWithCriteria(id); 0509 } 0510 0511 QRegion Corona::availableScreenRegionWithCriteria(int id, 0512 QString forLayout, 0513 QList<Types::Visibility> modes, 0514 QList<Plasma::Types::Location> edges, 0515 bool includeExternalPanels) const 0516 { 0517 const QScreen *screen = m_screenPool->screenForId(id); 0518 CentralLayout *layout = centralLayout(forLayout); 0519 0520 if (!screen) { 0521 return {}; 0522 } 0523 0524 QRegion available = includeExternalPanels ? screen->availableGeometry() : screen->geometry(); 0525 0526 if (!layout) { 0527 return available; 0528 } 0529 0530 bool allModes = modes.isEmpty(); 0531 bool allEdges = edges.isEmpty(); 0532 QList<Latte::View *> views = layout->latteViews(); 0533 0534 for (const auto *view : views) { 0535 if (view && view->containment() && view->screen() == screen 0536 && ((allEdges || edges.contains(view->location())) 0537 && (allModes || (view->visibility() && modes.contains(view->visibility()->mode()))))) { 0538 int realThickness = view->normalThickness(); 0539 0540 // Usually availableScreenRect is used by the desktop, 0541 // but Latte don't have desktop, then here just 0542 // need calculate available space for top and bottom location, 0543 // because the left and right are those who dodge others views 0544 switch (view->location()) { 0545 case Plasma::Types::TopEdge: 0546 if (view->behaveAsPlasmaPanel()) { 0547 available -= view->geometry(); 0548 } else { 0549 QRect realGeometry; 0550 int realWidth = view->maxLength() * view->width(); 0551 0552 switch (view->alignment()) { 0553 case Latte::Types::Left: 0554 realGeometry = QRect(view->x(), view->y(), 0555 realWidth, realThickness); 0556 break; 0557 0558 case Latte::Types::Center: 0559 case Latte::Types::Justify: 0560 realGeometry = QRect(qMax(view->geometry().x(), view->geometry().center().x() - realWidth / 2), view->y(), 0561 realWidth, realThickness); 0562 break; 0563 0564 case Latte::Types::Right: 0565 realGeometry = QRect(view->geometry().right() - realWidth + 1, view->y(), 0566 realWidth, realThickness); 0567 break; 0568 } 0569 0570 available -= realGeometry; 0571 } 0572 0573 break; 0574 0575 case Plasma::Types::BottomEdge: 0576 if (view->behaveAsPlasmaPanel()) { 0577 available -= view->geometry(); 0578 } else { 0579 QRect realGeometry; 0580 int realWidth = view->maxLength() * view->width(); 0581 int realY = view->geometry().bottom() - realThickness + 1; 0582 0583 switch (view->alignment()) { 0584 case Latte::Types::Left: 0585 realGeometry = QRect(view->x(), realY, 0586 realWidth, realThickness); 0587 break; 0588 0589 case Latte::Types::Center: 0590 case Latte::Types::Justify: 0591 realGeometry = QRect(qMax(view->geometry().x(), view->geometry().center().x() - realWidth / 2), 0592 realY, realWidth, realThickness); 0593 break; 0594 0595 case Latte::Types::Right: 0596 realGeometry = QRect(view->geometry().right() - realWidth + 1, realY, 0597 realWidth, realThickness); 0598 break; 0599 } 0600 0601 available -= realGeometry; 0602 } 0603 0604 break; 0605 0606 case Plasma::Types::LeftEdge: 0607 if (view->behaveAsPlasmaPanel()) { 0608 available -= view->geometry(); 0609 } else { 0610 QRect realGeometry; 0611 int realHeight = view->maxLength() * view->height(); 0612 0613 switch (view->alignment()) { 0614 case Latte::Types::Top: 0615 realGeometry = QRect(view->x(), view->y(), 0616 realThickness, realHeight); 0617 break; 0618 0619 case Latte::Types::Center: 0620 case Latte::Types::Justify: 0621 realGeometry = QRect(view->x(), qMax(view->geometry().y(), view->geometry().center().y() - realHeight / 2), 0622 realThickness, realHeight); 0623 break; 0624 0625 case Latte::Types::Bottom: 0626 realGeometry = QRect(view->x(), view->geometry().bottom() - realHeight + 1, 0627 realThickness, realHeight); 0628 break; 0629 } 0630 0631 available -= realGeometry; 0632 } 0633 0634 break; 0635 0636 case Plasma::Types::RightEdge: 0637 if (view->behaveAsPlasmaPanel()) { 0638 available -= view->geometry(); 0639 } else { 0640 QRect realGeometry; 0641 int realHeight = view->maxLength() * view->height(); 0642 int realX = view->geometry().right() - realThickness + 1; 0643 0644 switch (view->alignment()) { 0645 case Latte::Types::Top: 0646 realGeometry = QRect(realX, view->y(), 0647 realThickness, realHeight); 0648 break; 0649 0650 case Latte::Types::Center: 0651 case Latte::Types::Justify: 0652 realGeometry = QRect(realX, qMax(view->geometry().y(), view->geometry().center().y() - realHeight / 2), 0653 realThickness, realHeight); 0654 break; 0655 0656 case Latte::Types::Bottom: 0657 realGeometry = QRect(realX, view->geometry().bottom() - realHeight + 1, 0658 realThickness, realHeight); 0659 break; 0660 } 0661 0662 available -= realGeometry; 0663 } 0664 0665 break; 0666 0667 default: 0668 //! bypass clang warnings 0669 break; 0670 } 0671 } 0672 } 0673 0674 /*qDebug() << "::::: FREE AREAS :::::"; 0675 0676 for (int i = 0; i < available.rectCount(); ++i) { 0677 qDebug() << available.rects().at(i); 0678 } 0679 0680 qDebug() << "::::: END OF FREE AREAS :::::";*/ 0681 0682 return available; 0683 } 0684 0685 QRect Corona::availableScreenRect(int id) const 0686 { 0687 return availableScreenRectWithCriteria(id); 0688 } 0689 0690 QRect Corona::availableScreenRectWithCriteria(int id, 0691 QString forLayout, 0692 QList<Types::Visibility> modes, 0693 QList<Plasma::Types::Location> edges, 0694 bool includeExternalPanels) const 0695 { 0696 const QScreen *screen = m_screenPool->screenForId(id); 0697 CentralLayout *layout = centralLayout(forLayout); 0698 0699 if (!screen) { 0700 return {}; 0701 } 0702 0703 QRect available = includeExternalPanels ? screen->availableGeometry() : screen->geometry(); 0704 0705 if (!layout) { 0706 return available; 0707 } 0708 0709 bool allModes = modes.isEmpty(); 0710 bool allEdges = edges.isEmpty(); 0711 QList<Latte::View *> views = layout->latteViews(); 0712 0713 for (const auto *view : views) { 0714 if (view && view->containment() && view->screen() == screen 0715 && ((allEdges || edges.contains(view->location())) 0716 && (allModes || (view->visibility() && modes.contains(view->visibility()->mode()))))) { 0717 0718 // Usually availableScreenRect is used by the desktop, 0719 // but Latte don't have desktop, then here just 0720 // need calculate available space for top and bottom location, 0721 // because the left and right are those who dodge others docks 0722 switch (view->location()) { 0723 case Plasma::Types::TopEdge: 0724 available.setTop(view->y() + view->normalThickness()); 0725 break; 0726 0727 case Plasma::Types::BottomEdge: 0728 available.setBottom(view->y() + view->height() - view->normalThickness()); 0729 break; 0730 0731 case Plasma::Types::LeftEdge: 0732 available.setLeft(view->x() + view->normalThickness()); 0733 break; 0734 0735 case Plasma::Types::RightEdge: 0736 available.setRight(view->x() + view->width() - view->normalThickness()); 0737 break; 0738 0739 default: 0740 //! bypass clang warnings 0741 break; 0742 } 0743 } 0744 } 0745 0746 return available; 0747 } 0748 0749 void Corona::addOutput(QScreen *screen) 0750 { 0751 Q_ASSERT(screen); 0752 0753 int id = m_screenPool->id(screen->name()); 0754 0755 if (id == -1) { 0756 int newId = m_screenPool->firstAvailableId(); 0757 m_screenPool->insertScreenMapping(newId, screen->name()); 0758 } 0759 0760 connect(screen, &QScreen::geometryChanged, this, [ = ]() { 0761 const int id = m_screenPool->id(screen->name()); 0762 0763 if (id >= 0) { 0764 emit screenGeometryChanged(id); 0765 emit availableScreenRegionChanged(); 0766 emit availableScreenRectChanged(); 0767 } 0768 }); 0769 0770 emit availableScreenRectChanged(); 0771 emit screenAdded(m_screenPool->id(screen->name())); 0772 0773 screenCountChanged(); 0774 } 0775 0776 void Corona::primaryOutputChanged() 0777 { 0778 m_viewsScreenSyncTimer.start(); 0779 } 0780 0781 void Corona::screenRemoved(QScreen *screen) 0782 { 0783 screenCountChanged(); 0784 } 0785 0786 void Corona::screenCountChanged() 0787 { 0788 m_viewsScreenSyncTimer.start(); 0789 } 0790 0791 //! the central functions that updates loading/unloading latteviews 0792 //! concerning screen changed (for multi-screen setups mainly) 0793 void Corona::syncLatteViewsToScreens() 0794 { 0795 m_layoutsManager->synchronizer()->syncLatteViewsToScreens(); 0796 } 0797 0798 int Corona::primaryScreenId() const 0799 { 0800 return m_screenPool->id(qGuiApp->primaryScreen()->name()); 0801 } 0802 0803 void Corona::closeApplication() 0804 { 0805 //! this code must be called asynchronously because it is called 0806 //! also from qml (Settings window). 0807 QTimer::singleShot(300, [this]() { 0808 m_layoutsManager->hideLatteSettingsDialog(); 0809 m_layoutsManager->synchronizer()->hideAllViews(); 0810 }); 0811 0812 //! give the time for the views to hide themselves 0813 QTimer::singleShot(800, [this]() { 0814 qGuiApp->quit(); 0815 }); 0816 } 0817 0818 void Corona::aboutApplication() 0819 { 0820 if (aboutDialog) { 0821 aboutDialog->hide(); 0822 aboutDialog->deleteLater(); 0823 } 0824 0825 aboutDialog = new KAboutApplicationDialog(KAboutData::applicationData()); 0826 connect(aboutDialog.data(), &QDialog::finished, aboutDialog.data(), &QObject::deleteLater); 0827 m_wm->skipTaskBar(*aboutDialog); 0828 m_wm->setKeepAbove(*aboutDialog, true); 0829 0830 aboutDialog->show(); 0831 } 0832 0833 int Corona::screenForContainment(const Plasma::Containment *containment) const 0834 { 0835 //FIXME: indexOf is not a proper way to support multi-screen 0836 // as for environment to environment the indexes change 0837 // also there is the following issue triggered 0838 // from latteView adaptToScreen() 0839 // 0840 // in a multi-screen environment that 0841 // primary screen is not set to 0 it was 0842 // created an endless showing loop at 0843 // startup (catch-up race) between 0844 // screen:0 and primaryScreen 0845 0846 //case in which this containment is child of an applet, hello systray :) 0847 0848 if (Plasma::Applet *parentApplet = qobject_cast<Plasma::Applet *>(containment->parent())) { 0849 if (Plasma::Containment *cont = parentApplet->containment()) { 0850 return screenForContainment(cont); 0851 } else { 0852 return -1; 0853 } 0854 } 0855 0856 Plasma::Containment *c = const_cast<Plasma::Containment *>(containment); 0857 Latte::View *view = m_layoutsManager->synchronizer()->viewForContainment(c); 0858 0859 if (view && view->screen()) { 0860 return m_screenPool->id(view->screen()->name()); 0861 } 0862 0863 //Failed? fallback on lastScreen() 0864 //lastScreen() is the correct screen for panels 0865 //It is also correct for desktops *that have the correct activity()* 0866 //a containment with lastScreen() == 0 but another activity, 0867 //won't be associated to a screen 0868 // qDebug() << "ShellCorona screenForContainment: " << containment << " Last screen is " << containment->lastScreen(); 0869 0870 for (auto screen : qGuiApp->screens()) { 0871 // containment->lastScreen() == m_screenPool->id(screen->name()) to check if the lastScreen refers to a screen that exists/it's known 0872 if (containment->lastScreen() == m_screenPool->id(screen->name()) && 0873 (containment->activity() == m_activityConsumer->currentActivity() || 0874 containment->containmentType() == Plasma::Types::PanelContainment || containment->containmentType() == Plasma::Types::CustomPanelContainment)) { 0875 return containment->lastScreen(); 0876 } 0877 } 0878 0879 return -1; 0880 } 0881 0882 void Corona::showAlternativesForApplet(Plasma::Applet *applet) 0883 { 0884 const QString alternativesQML = kPackage().filePath("appletalternativesui"); 0885 0886 if (alternativesQML.isEmpty()) { 0887 return; 0888 } 0889 0890 Latte::View *latteView = m_layoutsManager->synchronizer()->viewForContainment(applet->containment()); 0891 0892 KDeclarative::QmlObjectSharedEngine *qmlObj{nullptr}; 0893 0894 if (latteView) { 0895 latteView->setAlternativesIsShown(true); 0896 qmlObj = new KDeclarative::QmlObjectSharedEngine(latteView); 0897 } else { 0898 qmlObj = new KDeclarative::QmlObjectSharedEngine(this); 0899 } 0900 0901 qmlObj->setInitializationDelayed(true); 0902 qmlObj->setSource(QUrl::fromLocalFile(alternativesQML)); 0903 0904 AlternativesHelper *helper = new AlternativesHelper(applet, qmlObj); 0905 qmlObj->rootContext()->setContextProperty(QStringLiteral("alternativesHelper"), helper); 0906 0907 m_alternativesObjects << qmlObj; 0908 qmlObj->completeInitialization(); 0909 0910 //! Alternative dialog signals 0911 connect(helper, &QObject::destroyed, this, [latteView]() { 0912 latteView->setAlternativesIsShown(false); 0913 }); 0914 0915 connect(qmlObj->rootObject(), SIGNAL(visibleChanged(bool)), 0916 this, SLOT(alternativesVisibilityChanged(bool))); 0917 0918 connect(applet, &Plasma::Applet::destroyedChanged, this, [this, qmlObj](bool destroyed) { 0919 if (!destroyed) { 0920 return; 0921 } 0922 0923 QMutableListIterator<KDeclarative::QmlObjectSharedEngine *> it(m_alternativesObjects); 0924 0925 while (it.hasNext()) { 0926 KDeclarative::QmlObjectSharedEngine *obj = it.next(); 0927 0928 if (obj == qmlObj) { 0929 it.remove(); 0930 obj->deleteLater(); 0931 } 0932 } 0933 }); 0934 } 0935 0936 void Corona::alternativesVisibilityChanged(bool visible) 0937 { 0938 if (visible) { 0939 return; 0940 } 0941 0942 QObject *root = sender(); 0943 0944 QMutableListIterator<KDeclarative::QmlObjectSharedEngine *> it(m_alternativesObjects); 0945 0946 while (it.hasNext()) { 0947 KDeclarative::QmlObjectSharedEngine *obj = it.next(); 0948 0949 if (obj->rootObject() == root) { 0950 it.remove(); 0951 obj->deleteLater(); 0952 } 0953 } 0954 } 0955 0956 void Corona::addViewForLayout(QString layoutName) 0957 { 0958 qDebug() << "loading default layout"; 0959 //! Setting mutable for create a containment 0960 setImmutability(Plasma::Types::Mutable); 0961 QVariantList args; 0962 auto defaultContainment = createContainmentDelayed("org.kde.latte.containment", args); 0963 defaultContainment->setContainmentType(Plasma::Types::PanelContainment); 0964 defaultContainment->init(); 0965 0966 if (!defaultContainment || !defaultContainment->kPackage().isValid()) { 0967 qWarning() << "the requested containment plugin can not be located or loaded"; 0968 return; 0969 } 0970 0971 auto config = defaultContainment->config(); 0972 defaultContainment->restore(config); 0973 0974 using Plasma::Types; 0975 QList<Types::Location> edges{Types::BottomEdge, Types::LeftEdge, 0976 Types::TopEdge, Types::RightEdge}; 0977 0978 Layout::GenericLayout *currentLayout = m_layoutsManager->synchronizer()->layout(layoutName); 0979 0980 if (currentLayout) { 0981 edges = currentLayout->freeEdges(defaultContainment->screen()); 0982 } 0983 0984 if ((edges.count() > 0)) { 0985 defaultContainment->setLocation(edges.at(0)); 0986 } else { 0987 defaultContainment->setLocation(Plasma::Types::BottomEdge); 0988 } 0989 0990 if (m_layoutsManager->memoryUsage() == Latte::Types::MultipleLayouts) { 0991 config.writeEntry("layoutId", layoutName); 0992 } 0993 0994 defaultContainment->updateConstraints(Plasma::Types::StartupCompletedConstraint); 0995 0996 defaultContainment->save(config); 0997 requestConfigSync(); 0998 0999 defaultContainment->flushPendingConstraintsEvents(); 1000 emit containmentAdded(defaultContainment); 1001 emit containmentCreated(defaultContainment); 1002 1003 defaultContainment->createApplet(QStringLiteral("org.kde.latte.plasmoid")); 1004 defaultContainment->createApplet(QStringLiteral("org.kde.plasma.analogclock")); 1005 } 1006 1007 void Corona::loadDefaultLayout() 1008 { 1009 addViewForLayout(m_layoutsManager->currentLayoutName()); 1010 } 1011 1012 QStringList Corona::containmentsIds() 1013 { 1014 QStringList ids; 1015 1016 for(const auto containment : containments()) { 1017 ids << QString::number(containment->id()); 1018 } 1019 1020 return ids; 1021 } 1022 1023 QStringList Corona::appletsIds() 1024 { 1025 QStringList ids; 1026 1027 for(const auto containment : containments()) { 1028 auto applets = containment->config().group("Applets"); 1029 ids << applets.groupList(); 1030 } 1031 1032 return ids; 1033 } 1034 1035 //! Activate launcher menu through dbus interface 1036 void Corona::activateLauncherMenu() 1037 { 1038 m_globalShortcuts->activateLauncherMenu(); 1039 } 1040 1041 void Corona::windowColorScheme(QString windowIdAndScheme) 1042 { 1043 int firstSlash = windowIdAndScheme.indexOf("-"); 1044 QString windowIdStr = windowIdAndScheme.mid(0, firstSlash); 1045 QString schemeStr = windowIdAndScheme.mid(firstSlash + 1); 1046 1047 if (KWindowSystem::isPlatformWayland()) { 1048 QTimer::singleShot(200, [this, schemeStr]() { 1049 //! [Wayland Case] - give the time to be informed correctly for the active window id 1050 //! otherwise the active window id may not be the same with the one trigerred 1051 //! the color scheme dbus signal 1052 QString windowIdStr = m_wm->activeWindow().toString(); 1053 m_wm->schemesTracker()->setColorSchemeForWindow(windowIdStr.toUInt(), schemeStr); 1054 }); 1055 } else { 1056 m_wm->schemesTracker()->setColorSchemeForWindow(windowIdStr.toUInt(), schemeStr); 1057 } 1058 } 1059 1060 //! update badge for specific view item 1061 void Corona::updateDockItemBadge(QString identifier, QString value) 1062 { 1063 m_globalShortcuts->updateViewItemBadge(identifier, value); 1064 } 1065 1066 1067 void Corona::switchToLayout(QString layout) 1068 { 1069 if ((layout.startsWith("file:/") || layout.startsWith("/")) && layout.endsWith(".layout.latte")) { 1070 //! Import and load runtime a layout through dbus interface 1071 //! It can be used from external programs that want to update runtime 1072 //! the Latte shown layout 1073 QString layoutPath = layout; 1074 1075 //! cleanup layout path 1076 if (layoutPath.startsWith("file:///")) { 1077 layoutPath = layout.remove("file://"); 1078 } else if (layoutPath.startsWith("file://")) { 1079 layoutPath = layout.remove("file:/"); 1080 } 1081 1082 //! check out layoutpath existence 1083 if (QFileInfo(layoutPath).exists()) { 1084 qDebug() << " Layout is going to be imported and loaded from file :: " << layoutPath; 1085 1086 QString importedLayout = m_layoutsManager->importer()->importLayoutHelper(layoutPath); 1087 1088 if (importedLayout.isEmpty()) { 1089 qDebug() << i18n("The layout cannot be imported from file :: ") << layoutPath; 1090 } else { 1091 m_layoutsManager->synchronizer()->loadLayouts(); 1092 m_layoutsManager->switchToLayout(importedLayout); 1093 } 1094 } else { 1095 qDebug() << " Layout from missing file can not be imported and loaded :: " << layoutPath; 1096 } 1097 } else { 1098 m_layoutsManager->switchToLayout(layout); 1099 } 1100 } 1101 1102 void Corona::showSettingsWindow(int page) 1103 { 1104 Types::LatteConfigPage p = Types::LayoutPage; 1105 1106 if (page >= Types::LayoutPage && page <= Types::PreferencesPage) { 1107 p = static_cast<Types::LatteConfigPage>(page); 1108 } 1109 1110 m_layoutsManager->showLatteSettingsDialog(p); 1111 } 1112 1113 void Corona::setContextMenuView(int id) 1114 { 1115 //! set context menu view id 1116 m_contextMenuViewId = id; 1117 } 1118 1119 QStringList Corona::contextMenuData() 1120 { 1121 QStringList data; 1122 Types::ViewType viewType{Types::DockView}; 1123 1124 Latte::CentralLayout *currentLayout = m_layoutsManager->currentLayout(); 1125 1126 if (currentLayout) { 1127 viewType = currentLayout->latteViewType(m_contextMenuViewId); 1128 } 1129 1130 data << QString::number((int)m_layoutsManager->memoryUsage()); 1131 data << m_layoutsManager->currentLayoutName(); 1132 data << QString::number((int)viewType); 1133 1134 for(const auto &layoutName : m_layoutsManager->menuLayouts()) { 1135 if (m_layoutsManager->synchronizer()->centralLayout(layoutName)) { 1136 data << QString("1," + layoutName); 1137 } else { 1138 data << QString("0," + layoutName); 1139 } 1140 } 1141 1142 //! reset context menu view id 1143 m_contextMenuViewId = -1; 1144 return data; 1145 } 1146 1147 void Corona::setBackgroundFromBroadcast(QString activity, QString screenName, QString filename) 1148 { 1149 if (filename.startsWith("file://")) { 1150 filename = filename.remove(0,7); 1151 } 1152 1153 QMetaObject::invokeMethod(m_backgroundTracer->rootObject(), 1154 "setBackgroundFromBroadcast", 1155 Q_ARG(QVariant, activity), 1156 Q_ARG(QVariant, screenName), 1157 Q_ARG(QVariant, filename)); 1158 } 1159 1160 void Corona::setBroadcastedBackgroundsEnabled(QString activity, QString screenName, bool enabled) 1161 { 1162 QMetaObject::invokeMethod(m_backgroundTracer->rootObject(), 1163 "setBroadcastedBackgroundsEnabled", 1164 Q_ARG(QVariant, activity), 1165 Q_ARG(QVariant, screenName), 1166 Q_ARG(QVariant, enabled)); 1167 } 1168 1169 inline void Corona::qmlRegisterTypes() const 1170 { 1171 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) 1172 qmlRegisterType<QScreen>(); 1173 qmlRegisterType<Latte::View>(); 1174 qmlRegisterType<Latte::ViewPart::WindowsTracker>(); 1175 qmlRegisterType<Latte::ViewPart::TrackerPart::CurrentScreenTracker>(); 1176 qmlRegisterType<Latte::ViewPart::TrackerPart::AllScreensTracker>(); 1177 qmlRegisterType<Latte::WindowSystem::SchemeColors>(); 1178 qmlRegisterType<Latte::WindowSystem::Tracker::LastActiveWindow>(); 1179 #else 1180 qmlRegisterAnonymousType<QScreen>("latte-dock", 1); 1181 qmlRegisterAnonymousType<Latte::View>("latte-dock", 1); 1182 qmlRegisterAnonymousType<Latte::ViewPart::WindowsTracker>("latte-dock", 1); 1183 qmlRegisterAnonymousType<Latte::ViewPart::TrackerPart::CurrentScreenTracker>("latte-dock", 1); 1184 qmlRegisterAnonymousType<Latte::ViewPart::TrackerPart::AllScreensTracker>("latte-dock", 1); 1185 qmlRegisterAnonymousType<Latte::WindowSystem::SchemeColors>("latte-dock", 1); 1186 qmlRegisterAnonymousType<Latte::WindowSystem::Tracker::LastActiveWindow>("latte-dock", 1); 1187 #endif 1188 } 1189 1190 }