File indexing completed on 2024-12-08 10:56:40

0001 /*
0002     SPDX-FileCopyrightText: 2019 Michail Vourlakos <mvourlakos@gmail.com>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "synchronizer.h"
0007 
0008 //! local
0009 #include <config-latte.h>
0010 #include "importer.h"
0011 #include "manager.h"
0012 #include "../apptypes.h"
0013 #include "../screenpool.h"
0014 #include "../data/layoutdata.h"
0015 #include "../lattecorona.h"
0016 #include "../layout/centrallayout.h"
0017 #include "../layout/genericlayout.h"
0018 #include "../settings/universalsettings.h"
0019 #include "../templates/templatesmanager.h"
0020 #include "../view/view.h"
0021 
0022 // Qt
0023 #include <QDir>
0024 #include <QFile>
0025 #include <QStringList>
0026 
0027 // Plasma
0028 #include <Plasma/Containment>
0029 
0030 // KDE
0031 #include <KActivities/Consumer>
0032 #include <KActivities/Controller>
0033 #include <KWindowSystem>
0034 
0035 #define LAYOUTSINITINTERVAL 350
0036 
0037 namespace Latte {
0038 namespace Layouts {
0039 
0040 Synchronizer::Synchronizer(QObject *parent)
0041     : QObject(parent),
0042       m_activitiesController(new KActivities::Controller)
0043 {
0044     m_manager = qobject_cast<Manager *>(parent);
0045 
0046     connect(this, &Synchronizer::layoutsChanged, this, &Synchronizer::reloadAssignedLayouts);
0047 
0048     //! KWin update Disabled Borders
0049     connect(this, &Synchronizer::centralLayoutsChanged, this, &Synchronizer::updateBorderlessMaximizedAfterTimer);
0050     connect(m_manager->corona()->universalSettings(), &UniversalSettings::canDisableBordersChanged, this, &Synchronizer::updateKWinDisabledBorders);
0051 
0052     m_updateBorderlessMaximized.setInterval(500);
0053     m_updateBorderlessMaximized.setSingleShot(true);
0054     connect(&m_updateBorderlessMaximized, &QTimer::timeout, this, &Synchronizer::updateKWinDisabledBorders);
0055 
0056     //! KActivities tracking
0057     connect(m_manager->corona()->activitiesConsumer(), &KActivities::Consumer::activityRemoved,
0058             this, &Synchronizer::onActivityRemoved);
0059 
0060     connect(m_manager->corona()->activitiesConsumer(), &KActivities::Consumer::currentActivityChanged,
0061             this, [&]() {
0062         if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
0063             //! this signal is also triggered when runningactivities are changed and actually is received first
0064             //! this is why we need a timer here in order to delay that execution and not activate/deactivate
0065             //! maximizedborders faulty because syncMultipleLayoutsToActivities(); has not been executed yet
0066             updateBorderlessMaximizedAfterTimer();
0067         }
0068     });
0069 
0070     connect(m_manager->corona()->activitiesConsumer(), &KActivities::Consumer::runningActivitiesChanged,
0071             this, [&]() {
0072         if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
0073             syncMultipleLayoutsToActivities();
0074         }
0075     });
0076 }
0077 
0078 Synchronizer::~Synchronizer()
0079 {
0080     m_activitiesController->deleteLater();
0081 }
0082 
0083 KActivities::Controller *Synchronizer::activitiesController() const
0084 {
0085     return m_activitiesController;
0086 }
0087 
0088 bool Synchronizer::latteViewExists(Latte::View *view) const
0089 {
0090     for (const auto layout : m_centralLayouts) {
0091         for (const auto &v : layout->latteViews()) {
0092             if (v == view) {
0093                 return true;
0094             }
0095         }
0096     }
0097 
0098     return false;
0099 }
0100 
0101 bool Synchronizer::layoutExists(QString layoutName) const
0102 {
0103     return m_layouts.containsName(layoutName);
0104 }
0105 
0106 
0107 bool Synchronizer::isAssigned(QString layoutName) const
0108 {
0109     for(auto activityid : m_assignedLayouts.keys()) {
0110         if (m_assignedLayouts[activityid].contains(layoutName)) {
0111             return true;
0112         }
0113     }
0114 
0115     return false;
0116 }
0117 
0118 int Synchronizer::centralLayoutPos(QString id) const
0119 {
0120     for (int i = 0; i < m_centralLayouts.size(); ++i) {
0121         CentralLayout *layout = m_centralLayouts.at(i);
0122 
0123         if (layout->name() == id) {
0124             return i;
0125         }
0126     }
0127 
0128     return -1;
0129 }
0130 
0131 QString Synchronizer::layoutPath(QString layoutName)
0132 {
0133     QString path = Layouts::Importer::layoutUserFilePath(layoutName);
0134 
0135     if (!QFile(path).exists()) {
0136         path = "";
0137     }
0138 
0139     return path;
0140 }
0141 
0142 QStringList Synchronizer::activities()
0143 {
0144     return m_manager->corona()->activitiesConsumer()->activities();
0145 }
0146 
0147 QStringList Synchronizer::freeActivities()
0148 {
0149     QStringList frees = activities();
0150 
0151     for(auto assigned : m_assignedLayouts.keys()) {
0152         frees.removeAll(assigned);
0153     }
0154 
0155     return frees;
0156 }
0157 
0158 QStringList Synchronizer::runningActivities()
0159 {   
0160     return m_manager->corona()->activitiesConsumer()->runningActivities();
0161 }
0162 
0163 QStringList Synchronizer::freeRunningActivities()
0164 {
0165     QStringList fActivities;
0166 
0167     for (const auto &activity : runningActivities()) {
0168         if (!m_assignedLayouts.contains(activity)) {
0169             fActivities.append(activity);
0170         }
0171     }
0172 
0173     return fActivities;
0174 }
0175 
0176 QStringList Synchronizer::validActivities(const QStringList &layoutActivities)
0177 {
0178     QStringList valids;
0179     QStringList allactivities = activities();
0180 
0181     for(auto activity : layoutActivities) {
0182         if (allactivities.contains(activity)) {
0183             valids << activity;
0184         }
0185     }
0186 
0187     return valids;
0188 }
0189 
0190 QStringList Synchronizer::centralLayoutsNames()
0191 {
0192     QStringList names;
0193 
0194     if (m_manager->memoryUsage() == MemoryUsage::SingleLayout) {
0195         if (m_centralLayouts.count() > 0) {
0196             names << m_centralLayouts.at(0)->name();
0197         }
0198     } else {
0199         for (int i = 0; i < m_centralLayouts.size(); ++i) {
0200             CentralLayout *layout = m_centralLayouts.at(i);
0201             names << layout->name();
0202         }
0203     }
0204 
0205     return names;
0206 }
0207 
0208 QStringList Synchronizer::currentLayoutsNames() const
0209 {
0210     QList<CentralLayout *> currents = currentLayouts();
0211     QStringList currentNames;
0212 
0213     for (int i = 0; i < currents.size(); ++i) {
0214         CentralLayout *layout = currents.at(i);
0215         currentNames << layout->name();
0216     }
0217 
0218     return currentNames;
0219 }
0220 
0221 QStringList Synchronizer::layouts() const
0222 {
0223     return m_layouts.names();
0224 }
0225 
0226 QStringList Synchronizer::menuLayouts() const
0227 {
0228     QStringList menulayouts;
0229 
0230     for (int i=0; i<m_layouts.rowCount(); ++i) {
0231         if (!m_layouts[i].isShownInMenu) {
0232             continue;
0233         }
0234 
0235         menulayouts << m_layouts[i].name;
0236     }
0237 
0238     for (const auto layout : m_centralLayouts) {
0239         if (!menulayouts.contains(layout->name())) {
0240             menulayouts.prepend(layout->name());
0241         }
0242     }
0243 
0244     menulayouts.sort(Qt::CaseInsensitive);
0245 
0246     return menulayouts;
0247 }
0248 
0249 void Synchronizer::setIsSingleLayoutInDeprecatedRenaming(const bool &enabled)
0250 {
0251     m_isSingleLayoutInDeprecatedRenaming = enabled;
0252 }
0253 
0254 Data::Layout Synchronizer::data(const QString &storedLayoutName) const
0255 {
0256     Data::Layout l;
0257 
0258     if (m_layouts.containsName(storedLayoutName)) {
0259         QString lid = m_layouts.idForName(storedLayoutName);
0260         return m_layouts[lid];
0261     }
0262 
0263     return l;
0264 }
0265 
0266 Data::LayoutsTable Synchronizer::layoutsTable() const
0267 {
0268     return m_layouts;
0269 }
0270 
0271 void Synchronizer::setLayoutsTable(const Data::LayoutsTable &table)
0272 {
0273     if (m_layouts == table) {
0274         return;
0275     }
0276 
0277     m_layouts = table;
0278     emit layoutsChanged();
0279 }
0280 
0281 void Synchronizer::updateLayoutsTable()
0282 {
0283     for (int i = 0; i < m_centralLayouts.size(); ++i) {
0284         CentralLayout *layout = m_centralLayouts.at(i);
0285 
0286         if (m_layouts.containsId(layout->file())) {
0287             m_layouts[layout->file()] = layout->data();
0288         }
0289     }
0290 
0291     for (int i = 0; i < m_layouts.rowCount(); ++i) {
0292         if ((m_layouts[i].errors>0 || m_layouts[i].warnings>0) && !m_layouts[i].isActive) {
0293             CentralLayout central(this, m_layouts[i].id);
0294             m_layouts[i].errors = central.errors().count();
0295             m_layouts[i].warnings = central.warnings().count();
0296         }
0297     }
0298 }
0299 
0300 CentralLayout *Synchronizer::centralLayout(QString layoutname) const
0301 {
0302     for (int i = 0; i < m_centralLayouts.size(); ++i) {
0303         CentralLayout *layout = m_centralLayouts.at(i);
0304 
0305         if (layout->name() == layoutname) {
0306             return layout;
0307         }
0308     }
0309 
0310     return nullptr;
0311 }
0312 
0313 QList<CentralLayout *> Synchronizer::currentLayouts() const
0314 {
0315     QList<CentralLayout *> layouts;
0316     layouts.clear();
0317 
0318     if (m_centralLayouts.isEmpty()) {
0319         return layouts;
0320     }
0321 
0322     if (m_manager->memoryUsage() == MemoryUsage::SingleLayout) {
0323         layouts << m_centralLayouts[0];
0324     } else {
0325         for (auto layout : m_centralLayouts) {
0326             if (layout->isOnAllActivities() || layout->appliedActivities().contains(m_manager->corona()->activitiesConsumer()->currentActivity())) {
0327                 layouts << layout;
0328             }
0329         }
0330     }
0331 
0332     return layouts;
0333 }
0334 
0335 QList<CentralLayout *> Synchronizer::centralLayoutsForActivity(const QString activityid) const
0336 {
0337     QList<CentralLayout *> layouts;
0338 
0339     if (m_manager->memoryUsage() == MemoryUsage::SingleLayout && m_centralLayouts.count() >= 1) {
0340         layouts << m_centralLayouts.at(0);
0341     } else {
0342         for (auto layout : m_centralLayouts) {
0343             if (layout->isOnAllActivities() || layout->appliedActivities().contains(activityid)) {
0344                 layouts << layout;
0345             }
0346         }
0347     }
0348 
0349     return layouts;
0350 }
0351 
0352 QList<Latte::View *> Synchronizer::currentViews() const
0353 {
0354     QList<Latte::View *> views;
0355 
0356     for(auto layout : currentLayouts()) {
0357         views << layout->latteViews();
0358     }
0359 
0360     return views;
0361 }
0362 
0363 QList<Latte::View *> Synchronizer::currentOriginalViews() const
0364 {
0365     QList<Latte::View *> views;
0366 
0367     for(auto layout : currentLayouts()) {
0368         views << layout->onlyOriginalViews();
0369     }
0370 
0371     return views;
0372 }
0373 
0374 QList<Latte::View *> Synchronizer::currentViewsWithPlasmaShortcuts() const
0375 {
0376     QList<Latte::View *> views;
0377 
0378     for(auto layout : currentLayouts()) {
0379         views << layout->viewsWithPlasmaShortcuts();
0380     }
0381 
0382     return views;
0383 }
0384 
0385 QList<Latte::View *> Synchronizer::sortedCurrentViews() const
0386 {
0387     return Layout::GenericLayout::sortedLatteViews(currentViews(), m_manager->corona()->screenPool()->primaryScreen());
0388 }
0389 
0390 QList<Latte::View *> Synchronizer::sortedCurrentOriginalViews() const
0391 {
0392     return Layout::GenericLayout::sortedLatteViews(currentOriginalViews(), m_manager->corona()->screenPool()->primaryScreen());
0393 }
0394 
0395 QList<Latte::View *> Synchronizer::viewsBasedOnActivityId(const QString &id) const
0396 {
0397     QList<Latte::View *> views;
0398 
0399     for(auto layout : centralLayoutsForActivity(id)) {
0400         if (m_centralLayouts.contains(layout)) {
0401             views << layout->latteViews();
0402         }
0403     }
0404 
0405     return views;
0406 }
0407 
0408 Layout::GenericLayout *Synchronizer::layout(QString layoutname) const
0409 {
0410     Layout::GenericLayout *l = centralLayout(layoutname);
0411 
0412     return l;
0413 }
0414 
0415 int Synchronizer::screenForContainment(Plasma::Containment *containment)
0416 {
0417     for (auto layout : m_centralLayouts) {
0418         if (layout->contains(containment)) {
0419             return layout->screenForContainment(containment);
0420         }
0421     }
0422 
0423     return -1;
0424 }
0425 
0426 Latte::View *Synchronizer::viewForContainment(uint id)
0427 {
0428     for (auto layout : m_centralLayouts) {
0429         Latte::View *view = layout->viewForContainment(id);
0430 
0431         if (view) {
0432             return view;
0433         }
0434     }
0435 
0436     return nullptr;
0437 }
0438 
0439 Latte::View *Synchronizer::viewForContainment(Plasma::Containment *containment)
0440 {
0441     for (auto layout : m_centralLayouts) {
0442         Latte::View *view = layout->viewForContainment(containment);
0443 
0444         if (view) {
0445             return view;
0446         }
0447     }
0448 
0449     return nullptr;
0450 }
0451 
0452 void Synchronizer::addLayout(CentralLayout *layout)
0453 {
0454     if (!m_centralLayouts.contains(layout)) {
0455         m_centralLayouts.append(layout);
0456     }
0457 }
0458 
0459 void Synchronizer::onActivityRemoved(const QString &activityid)
0460 {
0461     if (!m_assignedLayouts.contains(activityid)) {
0462         return;
0463     }
0464 
0465     //! remove any other explicit set layouts for the current activity
0466     QStringList explicits = m_assignedLayouts[activityid];
0467 
0468     for(auto explicitlayoutname : explicits) {
0469         QString explicitlayoutid = m_layouts.idForName(explicitlayoutname);
0470 
0471         m_layouts[explicitlayoutid].activities.removeAll(activityid);
0472         m_manager->setOnActivities(explicitlayoutname, m_layouts[explicitlayoutid].activities);
0473         emit layoutActivitiesChanged(m_layouts[explicitlayoutid]);
0474     }
0475 
0476     QStringList freelayoutnames;
0477 
0478     if (m_assignedLayouts.contains(Data::Layout::FREEACTIVITIESID)) {
0479         freelayoutnames = m_assignedLayouts[Data::Layout::FREEACTIVITIESID];
0480     }
0481 
0482     reloadAssignedLayouts();
0483 
0484     for(auto freelayoutname : freelayoutnames) {
0485         //! inform free activities layouts that their activities probably changed
0486         CentralLayout *central = centralLayout(freelayoutname);
0487 
0488         if (central) {
0489             emit central->activitiesChanged();
0490         }
0491     }
0492 }
0493 
0494 void Synchronizer::updateBorderlessMaximizedAfterTimer()
0495 {
0496     //! this signal is also triggered when runningactivities are changed and actually is received first
0497     //! this is why we need a timer here in order to delay that execution and not activate/deactivate
0498     //! maximizedborders faulty because syncMultipleLayoutsToActivities(); has not been executed yet
0499     m_updateBorderlessMaximized.start();
0500 }
0501 
0502 void Synchronizer::hideAllViews()
0503 {
0504     for (const auto layout : m_centralLayouts) {
0505         emit currentLayoutIsSwitching(layout->name());
0506     }
0507 }
0508 
0509 void Synchronizer::pauseLayout(QString layoutName)
0510 {
0511     if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
0512         CentralLayout *layout = centralLayout(layoutName);
0513 
0514         if (layout->isOnAllActivities()) {
0515             return;
0516         }
0517 
0518         QStringList appliedactivities = layout->appliedActivities();
0519 
0520         if (layout && !appliedactivities.isEmpty()) {
0521             int i = 0;
0522 
0523             for (const auto &activityid : appliedactivities) {
0524                 //! Stopping the activities must be done asynchronous because otherwise
0525                 //! the activity manager cant close multiple activities
0526                 QTimer::singleShot(i * 1000, [this, activityid]() {
0527                     m_activitiesController->stopActivity(activityid);
0528                 });
0529 
0530                 i = i + 1;
0531             }
0532         }
0533     }
0534 }
0535 
0536 void Synchronizer::syncActiveLayoutsToOriginalFiles()
0537 {
0538     if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
0539         for (const auto layout : m_centralLayouts) {
0540             layout->syncToLayoutFile();
0541         }
0542     }
0543 }
0544 
0545 void Synchronizer::syncLatteViewsToScreens()
0546 {
0547     for (const auto layout : m_centralLayouts) {
0548         layout->syncLatteViewsToScreens();
0549     }
0550 }
0551 
0552 void Synchronizer::unloadCentralLayout(CentralLayout *layout)
0553 {
0554     int pos = m_centralLayouts.indexOf(layout);
0555 
0556     if (pos>=0) {
0557         CentralLayout *central = m_centralLayouts.takeAt(pos);
0558 
0559         if (m_multipleModeInitialized && !m_manager->corona()->inQuit()) {
0560             central->syncToLayoutFile(true);
0561         }
0562 
0563         central->unloadLatteViews();
0564         central->unloadContainments();
0565 
0566         if (m_multipleModeInitialized && !m_manager->corona()->inQuit()) {
0567             m_manager->clearUnloadedContainmentsFromLinkedFile(central->unloadedContainmentsIds(), true);
0568         }
0569 
0570         delete central;
0571     }
0572 }
0573 
0574 void Synchronizer::initLayouts()
0575 {
0576     m_layouts.clear();
0577 
0578     QDir layoutDir(Layouts::Importer::layoutUserDir());
0579     QStringList filter;
0580     filter.append(QString("*.layout.latte"));
0581     QStringList files = layoutDir.entryList(filter, QDir::Files | QDir::NoSymLinks);
0582 
0583     for (const auto &layout : files) {
0584         if (layout.contains(Layout::MULTIPLELAYOUTSHIDDENNAME)) {
0585             //! IMPORTANT: DON'T ADD MultipleLayouts hidden file in layouts list
0586             continue;
0587         }
0588 
0589         QString layoutpath = layoutDir.absolutePath() + "/" + layout;
0590         onLayoutAdded(layoutpath);
0591     }
0592 
0593     emit layoutsChanged();
0594 
0595     if (!m_isLoaded) {
0596         m_isLoaded = true;
0597         connect(m_manager->corona()->templatesManager(), &Latte::Templates::Manager::newLayoutAdded, this, &Synchronizer::onLayoutAdded);
0598         connect(m_manager->importer(), &Latte::Layouts::Importer::newLayoutAdded, this, &Synchronizer::onLayoutAdded);
0599     }
0600 }
0601 
0602 void Synchronizer::onLayoutAdded(const QString &layout)
0603 {
0604     CentralLayout centrallayout(this, layout);
0605     m_layouts.insertBasedOnName(centrallayout.data());
0606 
0607     if (m_isLoaded) {
0608         emit layoutsChanged();
0609     }
0610 }
0611 
0612 void Synchronizer::reloadAssignedLayouts()
0613 {
0614     m_assignedLayouts.clear();
0615 
0616     for (int i=0; i< m_layouts.rowCount(); ++i) {
0617         for (const auto &activity : m_layouts[i].activities) {
0618             if (m_assignedLayouts.contains(activity)) {
0619                 m_assignedLayouts[activity] << m_layouts[i].name;
0620             } else {
0621                 m_assignedLayouts[activity] = QStringList(m_layouts[i].name);
0622             }
0623         }
0624     }
0625 }
0626 
0627 void Synchronizer::unloadLayouts()
0628 {
0629     //! Unload all CentralLayouts
0630     while (!m_centralLayouts.isEmpty()) {
0631         CentralLayout *layout = m_centralLayouts.at(0);
0632         unloadCentralLayout(layout);
0633     }
0634 
0635     m_multipleModeInitialized = false;
0636 }
0637 
0638 void Synchronizer::unloadPreloadedLayouts()
0639 {
0640     QStringList currentnames;
0641     QStringList preloadednames = Layouts::Storage::self()->storedLayoutsInMultipleFile();
0642 
0643     for(auto l : m_centralLayouts) {
0644         if (l) {
0645             currentnames << l->name();
0646         }
0647     }
0648 
0649     for(auto lname : preloadednames) {
0650         if (!currentnames.contains(lname)) {
0651             Layouts::Storage::self()->moveToLayoutFile(lname);
0652         }
0653     }
0654 }
0655 
0656 bool Synchronizer::memoryInitialized() const
0657 {
0658     return ((m_manager->memoryUsage() == MemoryUsage::SingleLayout && m_centralLayouts.size()>0)
0659             || (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts && m_multipleModeInitialized));
0660 }
0661 
0662 bool Synchronizer::initSingleMode(QString layoutName)
0663 {
0664     QString layoutpath = layoutName.isEmpty() ? layoutPath(m_manager->corona()->universalSettings()->singleModeLayoutName()) : layoutPath(layoutName);
0665 
0666     if (layoutpath.isEmpty()) {
0667         qDebug() << "Layout : " << layoutName << " was not found...";
0668         return false;
0669     }
0670 
0671     if (m_centralLayouts.size() > 0) {
0672         emit currentLayoutIsSwitching(m_centralLayouts[0]->name());
0673     }
0674 
0675     //! this code must be called asynchronously because it can create crashes otherwise.
0676     //! Tasks plasmoid case that triggers layouts switching through its context menu
0677     QTimer::singleShot(LAYOUTSINITINTERVAL, [this, layoutName, layoutpath]() {
0678         qDebug() << " ... initializing layout in single mode : " << layoutName << " - " << layoutpath;
0679         unloadPreloadedLayouts();
0680         unloadLayouts();
0681 
0682         //! load the main single layout/corona file
0683         CentralLayout *newLayout = new CentralLayout(this, layoutpath, layoutName);
0684 
0685         //! Order of initialization steps is very important and guarantees correct startup initialization
0686         //! Step1: corona is set for the layout
0687         //! Step2: containments from file are loaded into main corona
0688         //! Step3: layout connects to corona signals and slots
0689         //! Step4: layout is added in manager and is accesible for others to find
0690         //! Step5: layout is attaching its initial containmens and is now considered ACTIVE
0691         newLayout->setCorona(m_manager->corona()); //step1
0692         m_manager->loadLatteLayout(layoutpath);    //step2
0693         newLayout->initCorona();                   //step3
0694         addLayout(newLayout);                      //step4
0695         newLayout->initContainments();             //step5
0696 
0697         emit centralLayoutsChanged();
0698 
0699         if (m_isSingleLayoutInDeprecatedRenaming) {
0700             QString deprecatedlayoutpath = layoutPath(m_manager->corona()->universalSettings()->singleModeLayoutName());
0701 
0702             if (!deprecatedlayoutpath.isEmpty()) {
0703                 qDebug() << "Removing Deprecated single layout after renaming:: " << m_manager->corona()->universalSettings()->singleModeLayoutName();
0704                 QFile(deprecatedlayoutpath).remove();
0705             }
0706 
0707             m_isSingleLayoutInDeprecatedRenaming = false;
0708         }
0709 
0710         m_manager->corona()->universalSettings()->setSingleModeLayoutName(layoutName);
0711         m_manager->importer()->setMultipleLayoutsStatus(Latte::MultipleLayouts::Uninitialized);
0712         emit initializationFinished();
0713     });
0714 
0715     return true;
0716 }
0717 
0718 bool Synchronizer::initMultipleMode(QString layoutName)
0719 {
0720     if (m_multipleModeInitialized) {
0721         return false;
0722     }
0723 
0724     for (const auto layout : m_centralLayouts) {
0725         emit currentLayoutIsSwitching(layout->name());
0726     }
0727 
0728     //! this code must be called asynchronously because it can create crashes otherwise.
0729     //! Tasks plasmoid case that triggers layouts switching through its context menu
0730     QTimer::singleShot(LAYOUTSINITINTERVAL, [this, layoutName]() {
0731         qDebug() << " ... initializing layout in multiple mode : " << layoutName ;
0732         unloadLayouts();
0733 
0734         QStringList layoutsinmultiplestorage = Layouts::Storage::self()->storedLayoutsInMultipleFile();
0735         qDebug() << "Preloaded Multiple Layouts in Storage :: " << layoutsinmultiplestorage;
0736 
0737         m_manager->loadLatteLayout(layoutPath(QString(Layout::MULTIPLELAYOUTSHIDDENNAME)));
0738 
0739         m_multipleModeInitialized = true;
0740 
0741         emit centralLayoutsChanged();
0742 
0743         if (!layoutName.isEmpty()) {
0744             switchToLayoutInMultipleModeBasedOnActivities(layoutName);
0745         }
0746 
0747         syncMultipleLayoutsToActivities(layoutsinmultiplestorage);
0748         m_manager->importer()->setMultipleLayoutsStatus(Latte::MultipleLayouts::Running);
0749         emit initializationFinished();
0750     });
0751 
0752     return true;
0753 }
0754 
0755 bool Synchronizer::switchToLayoutInSingleMode(QString layoutName)
0756 {
0757     if (!memoryInitialized() || m_manager->memoryUsage() != MemoryUsage::SingleLayout) {
0758         return false;
0759     }
0760 
0761     if (m_centralLayouts.size()>0 && m_centralLayouts[0]->name() == layoutName) {
0762         return true;
0763     }
0764 
0765     return initSingleMode(layoutName);
0766 }
0767 
0768 bool Synchronizer::switchToLayoutInMultipleModeBasedOnActivities(const QString &layoutName)
0769 {
0770     Data::Layout layoutdata;
0771     CentralLayout *central = centralLayout(layoutName);
0772 
0773     if (central) {
0774         layoutdata = central->data();
0775     } else if (m_layouts.containsName(layoutName)) {
0776         QString layoutid = m_layouts.idForName(layoutName);
0777         CentralLayout storagedlayout(this, layoutid);
0778         layoutdata = storagedlayout.data();
0779 
0780         m_layouts[layoutid] = layoutdata;
0781     }
0782 
0783     if (layoutdata.isEmpty()) {
0784         return false;
0785     }
0786 
0787     QString switchToActivity;
0788 
0789     //! try to not remove activityids that belong to different machines that are not currently present
0790     QStringList validlayoutactivities = validActivities(layoutdata.activities);
0791 
0792     if (layoutdata.isOnAllActivities()) {
0793         //! no reason to switch in any activity;
0794     } else if (layoutdata.isForFreeActivities()) {
0795         //! free-activities case
0796         QStringList freerunningactivities = freeRunningActivities();
0797 
0798         if (freerunningactivities.count() > 0) {
0799             if (freerunningactivities.contains(layoutdata.lastUsedActivity)) {
0800                 switchToActivity = layoutdata.lastUsedActivity;
0801             } else {
0802                 switchToActivity = freerunningactivities[0];
0803             }
0804         } else {
0805             QStringList freepausedactivities = freeActivities();
0806 
0807             if (freepausedactivities.count() > 0) {
0808                 switchToActivity = freepausedactivities[0];
0809             }
0810         }
0811     } else if (!validlayoutactivities.isEmpty())  {
0812         //! set on-explicit activities
0813         QStringList allactivities = activities();
0814 
0815         if (validlayoutactivities.contains(layoutdata.lastUsedActivity)) {
0816             switchToActivity = layoutdata.lastUsedActivity;
0817         } else {
0818             switchToActivity = validlayoutactivities[0];
0819         }
0820     } else if (validlayoutactivities.isEmpty() && m_layouts.containsName(layoutName)) {
0821         //! no-activities are set
0822         //! has not been set in any activities but nonetheless it is requested probably by the user
0823         //! requested layout is assigned explicitly in current activity and any remaining explicit layouts
0824         //! are removing current activity from their activities list
0825         QString layoutid = m_layouts.idForName(layoutName);
0826         QString currentactivityid = m_activitiesController->currentActivity();
0827 
0828         QStringList layoutIdsChanged;
0829 
0830         m_layouts[layoutid].activities.append(currentactivityid);
0831         m_manager->setOnActivities(layoutName, m_layouts[layoutid].activities);
0832         emit layoutActivitiesChanged(m_layouts[layoutid]);
0833 
0834         layoutIdsChanged << layoutid;
0835 
0836         if (m_assignedLayouts.contains(currentactivityid)) {
0837             //! remove any other explicit set layouts for the current activity
0838             QStringList explicits = m_assignedLayouts[currentactivityid];
0839 
0840             for(auto explicitlayoutname : explicits) {
0841                 QString explicitlayoutid = m_layouts.idForName(explicitlayoutname);
0842 
0843                 m_layouts[explicitlayoutid].activities.removeAll(currentactivityid);
0844                 m_manager->setOnActivities(explicitlayoutname, m_layouts[explicitlayoutid].activities);
0845                 emit layoutActivitiesChanged(m_layouts[explicitlayoutid]);
0846             }
0847         }
0848 
0849         QStringList freelayoutnames;
0850         if (m_assignedLayouts.contains(Data::Layout::FREEACTIVITIESID)) {
0851             freelayoutnames = m_assignedLayouts[Data::Layout::FREEACTIVITIESID];
0852         }
0853 
0854         reloadAssignedLayouts();
0855 
0856         for(auto freelayoutname : freelayoutnames) {
0857             //! inform free activities layouts that their activities probably changed
0858             CentralLayout *central = centralLayout(freelayoutname);
0859 
0860             if (central) {
0861                 emit central->activitiesChanged();
0862             }
0863         }
0864     }
0865 
0866     if (!switchToActivity.isEmpty()) {
0867         if (!m_manager->corona()->activitiesConsumer()->runningActivities().contains(switchToActivity)) {
0868             m_activitiesController->startActivity(switchToActivity);
0869         }
0870 
0871         m_activitiesController->setCurrentActivity(switchToActivity);
0872     }
0873 
0874     return true;
0875 }
0876 
0877 bool Synchronizer::switchToLayoutInMultipleMode(QString layoutName)
0878 {
0879     if (!memoryInitialized() || m_manager->memoryUsage() != MemoryUsage::MultipleLayouts) {
0880         return false;
0881     }
0882 
0883     CentralLayout *layout = centralLayout(layoutName);
0884 
0885     if (layout) {
0886         QStringList appliedActivities = layout->appliedActivities();
0887         QString nextActivity = !layout->lastUsedActivity().isEmpty() ? layout->lastUsedActivity() : appliedActivities[0];
0888 
0889         if (!appliedActivities.contains(m_manager->corona()->activitiesConsumer()->currentActivity())) {
0890             //! it means we are at a foreign activity and we can switch to correct one
0891             m_activitiesController->setCurrentActivity(nextActivity);
0892             return true;
0893         }
0894     } else {
0895         if (!layoutName.isEmpty()) {
0896             switchToLayoutInMultipleModeBasedOnActivities(layoutName);
0897         }
0898 
0899         syncMultipleLayoutsToActivities();
0900     }
0901 
0902     return true;
0903 }
0904 
0905 
0906 bool Synchronizer::switchToLayout(QString layoutName, MemoryUsage::LayoutsMemory newMemoryUsage)
0907 {
0908     qDebug() << " >>>>> SWITCHING >> " << layoutName << " __ from memory: " << m_manager->memoryUsage() << " to memory: " << newMemoryUsage;
0909 
0910     if (newMemoryUsage == MemoryUsage::Current) {
0911         newMemoryUsage = m_manager->memoryUsage();
0912     }
0913 
0914     if (!memoryInitialized() || newMemoryUsage != m_manager->memoryUsage()) {
0915         //! Initiate Layouts memory properly
0916         m_manager->setMemoryUsage(newMemoryUsage);
0917 
0918         return (newMemoryUsage == MemoryUsage::SingleLayout ? initSingleMode(layoutName) : initMultipleMode(layoutName));
0919     }
0920 
0921     if (m_manager->memoryUsage() == MemoryUsage::SingleLayout) {
0922         return switchToLayoutInSingleMode(layoutName);
0923     } else {
0924         return switchToLayoutInMultipleMode(layoutName);
0925     }
0926 }
0927 
0928 void Synchronizer::syncMultipleLayoutsToActivities(QStringList preloadedLayouts)
0929 {
0930     qDebug() << "   ----  --------- ------    syncMultipleLayoutsToActivities       -------   ";
0931     qDebug() << "   ----  --------- ------    -------------------------------       -------   ";
0932 
0933     QStringList layoutNamesToUnload;
0934     QStringList layoutNamesToLoad;
0935     QStringList currentNames = centralLayoutsNames();
0936 
0937     //! discover OnAllActivities layouts
0938     if (m_assignedLayouts.contains(Data::Layout::ALLACTIVITIESID)) {
0939         layoutNamesToLoad << m_assignedLayouts[Data::Layout::ALLACTIVITIESID];
0940     }
0941 
0942     //! discover ForFreeActivities layouts
0943     if (m_assignedLayouts.contains(Data::Layout::FREEACTIVITIESID) && freeRunningActivities().count()>0) {
0944         layoutNamesToLoad << m_assignedLayouts[Data::Layout::FREEACTIVITIESID];
0945     }
0946 
0947     //! discover layouts assigned to explicit activities based on running activities
0948     for (const auto &activity : runningActivities()) {
0949         if (m_assignedLayouts.contains(activity)) {
0950             layoutNamesToLoad << m_assignedLayouts[activity];
0951         }
0952     }
0953 
0954     //! discover layouts that must be unloaded because of running activities changes
0955     for (const auto layout : m_centralLayouts) {
0956         if (!layoutNamesToLoad.contains(layout->name())) {
0957             layoutNamesToUnload << layout->name();
0958         }
0959     }
0960 
0961     for (const auto lname : preloadedLayouts) {
0962         if (!layoutNamesToLoad.contains(lname)) {
0963             layoutNamesToUnload << lname;
0964         }
0965     }
0966 
0967     QString defaultForcedLayout;
0968 
0969     //! Safety
0970     if (layoutNamesToLoad.isEmpty()) {
0971         //! If no layout is found then force loading Default Layout
0972         QString layoutPath = m_manager->corona()->templatesManager()->newLayout("", i18n(Templates::DEFAULTLAYOUTTEMPLATENAME));
0973         layoutNamesToLoad << Layout::AbstractLayout::layoutName(layoutPath);
0974         m_manager->setOnAllActivities(layoutNamesToLoad[0]);
0975         defaultForcedLayout = layoutNamesToLoad[0];
0976     }
0977 
0978     QStringList newlyActivatedLayouts;
0979 
0980     //! Add needed Layouts based on Activities settings
0981     for (const auto &layoutname : layoutNamesToLoad) {
0982         if (!centralLayout(layoutname)) {
0983             CentralLayout *newLayout = new CentralLayout(this, QString(layoutPath(layoutname)), layoutname);
0984 
0985             if (newLayout) {
0986                 qDebug() << "ACTIVATING LAYOUT ::::: " << layoutname;
0987 
0988                 //! Order of initialization steps is very important and guarantees correct startup initialization
0989                 //! Step1: corona is set for the layout
0990                 //! Step2: containments from the layout file are adjusted and are imported into main corona
0991                 //! Step3: layout connects to corona signals and slots
0992                 //! Step4: layout is added in manager and is accesible for others to find
0993                 //! Step5: layout is attaching its initial containmens and is now considered ACTIVE
0994                 newLayout->setCorona(m_manager->corona()); //step1
0995                 if (!preloadedLayouts.contains(layoutname)) {
0996                     newLayout->importToCorona();           //step2
0997                 }
0998                 newLayout->initCorona();                   //step3
0999                 addLayout(newLayout);                      //step4
1000                 newLayout->initContainments();             //step5
1001 
1002                 if (!defaultForcedLayout.isEmpty() && defaultForcedLayout == layoutname) {
1003                     emit newLayoutAdded(newLayout->data());
1004                 }
1005 
1006                 newlyActivatedLayouts << newLayout->name();
1007             }
1008         }
1009     }
1010 
1011     if (newlyActivatedLayouts.count()>0 && m_manager->corona()->universalSettings()->showInfoWindow()) {
1012         m_manager->showInfoWindow(i18np("Activating layout: <b>%2</b> ...",
1013                                         "Activating layouts: <b>%2</b> ...",
1014                                         newlyActivatedLayouts.count(),
1015                                         newlyActivatedLayouts.join(", ")),
1016                                   4000, QStringList(Data::Layout::ALLACTIVITIESID));
1017     }
1018 
1019     //! Unload no needed Layouts
1020 
1021     //! hide layouts that will be removed in the end
1022     if (!layoutNamesToUnload.isEmpty()) {
1023         for (const auto layoutname : layoutNamesToUnload) {
1024             emit currentLayoutIsSwitching(layoutname);
1025         }
1026 
1027         QTimer::singleShot(LAYOUTSINITINTERVAL, [this, layoutNamesToUnload, preloadedLayouts]() {
1028             unloadLayouts(layoutNamesToUnload, preloadedLayouts);
1029         });
1030     }
1031 
1032     currentNames.sort();
1033     layoutNamesToLoad.sort();
1034 
1035     if (currentNames != layoutNamesToLoad) {
1036         emit centralLayoutsChanged();
1037     }
1038 }
1039 
1040 void Synchronizer::unloadLayouts(const QStringList &layoutNames, const QStringList &preloadedLayouts)
1041 {
1042     if (layoutNames.isEmpty()) {
1043         return;
1044     }
1045 
1046     //! Unload no needed Layouts
1047     for (const auto &layoutname : layoutNames) {
1048         CentralLayout *layout = centralLayout(layoutname);
1049         int posLayout = centralLayoutPos(layoutname);
1050 
1051         if (posLayout >= 0) {
1052             qDebug() << "REMOVING LAYOUT ::::: " << layoutname;
1053             m_centralLayouts.removeAt(posLayout);
1054 
1055             if (!m_manager->corona()->inQuit()) {
1056                 layout->syncToLayoutFile(true);
1057             }
1058 
1059             layout->unloadContainments();
1060             layout->unloadLatteViews();
1061             if (!m_manager->corona()->inQuit()) {
1062                 m_manager->clearUnloadedContainmentsFromLinkedFile(layout->unloadedContainmentsIds());
1063             }
1064             delete layout;
1065         } else if (preloadedLayouts.contains(layoutname)) {
1066             Layouts::Storage::self()->moveToLayoutFile(layoutname);
1067             //! just make sure that
1068         }
1069     }
1070 
1071     emit centralLayoutsChanged();
1072 }
1073 
1074 void Synchronizer::updateKWinDisabledBorders()
1075 {
1076     if (KWindowSystem::isPlatformWayland()) {
1077         // BUG: https://bugs.kde.org/show_bug.cgi?id=428202
1078         // KWin::reconfigure() function blocks/freezes Latte under wayland
1079         return;
1080     }
1081 
1082     if (!m_manager->corona()->universalSettings()->canDisableBorders()) {
1083         m_manager->corona()->universalSettings()->kwin_setDisabledMaximizedBorders(false);
1084     } else {
1085         if (m_manager->corona()->layoutsManager()->memoryUsage() == MemoryUsage::SingleLayout && m_centralLayouts.size() > 0) {
1086             m_manager->corona()->universalSettings()->kwin_setDisabledMaximizedBorders(m_centralLayouts.at(0)->disableBordersForMaximizedWindows());
1087         } else if (m_manager->corona()->layoutsManager()->memoryUsage() == MemoryUsage::MultipleLayouts) {
1088             QList<CentralLayout *> centrals = centralLayoutsForActivity(m_manager->corona()->activitiesConsumer()->currentActivity());
1089 
1090             for (int i = 0; i < centrals.size(); ++i) {
1091                 CentralLayout *layout = centrals.at(i);
1092 
1093                 if (layout->disableBordersForMaximizedWindows()) {
1094                     m_manager->corona()->universalSettings()->kwin_setDisabledMaximizedBorders(true);
1095                     return;
1096                 }
1097             }
1098 
1099             //! avoid initialization step for example during startup that no layouts have been loaded yet
1100             if (centrals.size() > 0) {
1101                 m_manager->corona()->universalSettings()->kwin_setDisabledMaximizedBorders(false);
1102             }
1103 
1104         }
1105     }
1106 }
1107 
1108 }
1109 } // end of namespace