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 }