1 /*
2     SPDX-FileCopyrightText: 2019 Michail Vourlakos <mvourlakos@gmail.com>
3     SPDX-License-Identifier: GPL-2.0-or-later
4 */
5 
6 #include "synchronizer.h"
7 
8 //! local
9 #include <config-latte.h>
10 #include "importer.h"
11 #include "manager.h"
12 #include "../apptypes.h"
13 #include "../data/layoutdata.h"
14 #include "../lattecorona.h"
15 #include "../layout/centrallayout.h"
16 #include "../layout/genericlayout.h"
17 #include "../settings/universalsettings.h"
18 #include "../templates/templatesmanager.h"
19 #include "../view/view.h"
20 
21 // Qt
22 #include <QDir>
23 #include <QFile>
24 #include <QStringList>
25 
26 // Plasma
27 #include <Plasma/Containment>
28 
29 // KDE
30 #include <KActivities/Consumer>
31 #include <KActivities/Controller>
32 #include <KWindowSystem>
33 
34 #define LAYOUTSINITINTERVAL 350
35 
36 namespace Latte {
37 namespace Layouts {
38 
Synchronizer(QObject * parent)39 Synchronizer::Synchronizer(QObject *parent)
40     : QObject(parent),
41       m_activitiesController(new KActivities::Controller)
42 {
43     m_manager = qobject_cast<Manager *>(parent);
44 
45     connect(this, &Synchronizer::layoutsChanged, this, &Synchronizer::reloadAssignedLayouts);
46 
47     //! KWin update Disabled Borders
48     connect(this, &Synchronizer::centralLayoutsChanged, this, &Synchronizer::updateBorderlessMaximizedAfterTimer);
49     connect(m_manager->corona()->universalSettings(), &UniversalSettings::canDisableBordersChanged, this, &Synchronizer::updateKWinDisabledBorders);
50 
51     m_updateBorderlessMaximized.setInterval(500);
52     m_updateBorderlessMaximized.setSingleShot(true);
53     connect(&m_updateBorderlessMaximized, &QTimer::timeout, this, &Synchronizer::updateKWinDisabledBorders);
54 
55     //! KActivities tracking
56     connect(m_manager->corona()->activitiesConsumer(), &KActivities::Consumer::activityRemoved,
57             this, &Synchronizer::onActivityRemoved);
58 
59     connect(m_manager->corona()->activitiesConsumer(), &KActivities::Consumer::currentActivityChanged,
60             this, [&]() {
61         if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
62             //! this signal is also triggered when runningactivities are changed and actually is received first
63             //! this is why we need a timer here in order to delay that execution and not activate/deactivate
64             //! maximizedborders faulty because syncMultipleLayoutsToActivities(); has not been executed yet
65             updateBorderlessMaximizedAfterTimer();
66         }
67     });
68 
69     connect(m_manager->corona()->activitiesConsumer(), &KActivities::Consumer::runningActivitiesChanged,
70             this, [&]() {
71         if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
72             syncMultipleLayoutsToActivities();
73         }
74     });
75 }
76 
~Synchronizer()77 Synchronizer::~Synchronizer()
78 {
79     m_activitiesController->deleteLater();
80 }
81 
activitiesController() const82 KActivities::Controller *Synchronizer::activitiesController() const
83 {
84     return m_activitiesController;
85 }
86 
latteViewExists(Latte::View * view) const87 bool Synchronizer::latteViewExists(Latte::View *view) const
88 {
89     for (const auto layout : m_centralLayouts) {
90         for (const auto &v : layout->latteViews()) {
91             if (v == view) {
92                 return true;
93             }
94         }
95     }
96 
97     return false;
98 }
99 
layoutExists(QString layoutName) const100 bool Synchronizer::layoutExists(QString layoutName) const
101 {
102     return m_layouts.containsName(layoutName);
103 }
104 
105 
isAssigned(QString layoutName) const106 bool Synchronizer::isAssigned(QString layoutName) const
107 {
108     for(auto activityid : m_assignedLayouts.keys()) {
109         if (m_assignedLayouts[activityid].contains(layoutName)) {
110             return true;
111         }
112     }
113 
114     return false;
115 }
116 
centralLayoutPos(QString id) const117 int Synchronizer::centralLayoutPos(QString id) const
118 {
119     for (int i = 0; i < m_centralLayouts.size(); ++i) {
120         CentralLayout *layout = m_centralLayouts.at(i);
121 
122         if (layout->name() == id) {
123             return i;
124         }
125     }
126 
127     return -1;
128 }
129 
layoutPath(QString layoutName)130 QString Synchronizer::layoutPath(QString layoutName)
131 {
132     QString path = Layouts::Importer::layoutUserFilePath(layoutName);
133 
134     if (!QFile(path).exists()) {
135         path = "";
136     }
137 
138     return path;
139 }
140 
activities()141 QStringList Synchronizer::activities()
142 {
143     return m_manager->corona()->activitiesConsumer()->activities();
144 }
145 
freeActivities()146 QStringList Synchronizer::freeActivities()
147 {
148     QStringList frees = activities();
149 
150     for(auto assigned : m_assignedLayouts.keys()) {
151         frees.removeAll(assigned);
152     }
153 
154     return frees;
155 }
156 
runningActivities()157 QStringList Synchronizer::runningActivities()
158 {
159     return m_manager->corona()->activitiesConsumer()->runningActivities();
160 }
161 
freeRunningActivities()162 QStringList Synchronizer::freeRunningActivities()
163 {
164     QStringList fActivities;
165 
166     for (const auto &activity : runningActivities()) {
167         if (!m_assignedLayouts.contains(activity)) {
168             fActivities.append(activity);
169         }
170     }
171 
172     return fActivities;
173 }
174 
validActivities(const QStringList & layoutActivities)175 QStringList Synchronizer::validActivities(const QStringList &layoutActivities)
176 {
177     QStringList valids;
178     QStringList allactivities = activities();
179 
180     for(auto activity : layoutActivities) {
181         if (allactivities.contains(activity)) {
182             valids << activity;
183         }
184     }
185 
186     return valids;
187 }
188 
centralLayoutsNames()189 QStringList Synchronizer::centralLayoutsNames()
190 {
191     QStringList names;
192 
193     if (m_manager->memoryUsage() == MemoryUsage::SingleLayout) {
194         names << m_centralLayouts.at(0)->name();
195     } else {
196         for (int i = 0; i < m_centralLayouts.size(); ++i) {
197             CentralLayout *layout = m_centralLayouts.at(i);
198             names << layout->name();
199         }
200     }
201 
202     return names;
203 }
204 
currentLayoutsNames() const205 QStringList Synchronizer::currentLayoutsNames() const
206 {
207     QList<CentralLayout *> currents = currentLayouts();
208     QStringList currentNames;
209 
210     for (int i = 0; i < currents.size(); ++i) {
211         CentralLayout *layout = currents.at(i);
212         currentNames << layout->name();
213     }
214 
215     return currentNames;
216 }
217 
layouts() const218 QStringList Synchronizer::layouts() const
219 {
220     return m_layouts.names();
221 }
222 
menuLayouts() const223 QStringList Synchronizer::menuLayouts() const
224 {
225     QStringList menulayouts;
226 
227     for (int i=0; i<m_layouts.rowCount(); ++i) {
228         if (!m_layouts[i].isShownInMenu) {
229             continue;
230         }
231 
232         menulayouts << m_layouts[i].name;
233     }
234 
235     for (const auto layout : m_centralLayouts) {
236         if (!menulayouts.contains(layout->name())) {
237             menulayouts.prepend(layout->name());
238         }
239     }
240 
241     menulayouts.sort(Qt::CaseInsensitive);
242 
243     return menulayouts;
244 }
245 
setIsSingleLayoutInDeprecatedRenaming(const bool & enabled)246 void Synchronizer::setIsSingleLayoutInDeprecatedRenaming(const bool &enabled)
247 {
248     m_isSingleLayoutInDeprecatedRenaming = enabled;
249 }
250 
data(const QString & storedLayoutName) const251 Data::Layout Synchronizer::data(const QString &storedLayoutName) const
252 {
253     Data::Layout l;
254 
255     if (m_layouts.containsName(storedLayoutName)) {
256         QString lid = m_layouts.idForName(storedLayoutName);
257         return m_layouts[lid];
258     }
259 
260     return l;
261 }
262 
layoutsTable() const263 Data::LayoutsTable Synchronizer::layoutsTable() const
264 {
265     return m_layouts;
266 }
267 
setLayoutsTable(const Data::LayoutsTable & table)268 void Synchronizer::setLayoutsTable(const Data::LayoutsTable &table)
269 {
270     if (m_layouts == table) {
271         return;
272     }
273 
274     m_layouts = table;
275     emit layoutsChanged();
276 }
277 
updateLayoutsTable()278 void Synchronizer::updateLayoutsTable()
279 {
280     for (int i = 0; i < m_centralLayouts.size(); ++i) {
281         CentralLayout *layout = m_centralLayouts.at(i);
282 
283         if (m_layouts.containsId(layout->file())) {
284             m_layouts[layout->file()] = layout->data();
285         }
286     }
287 
288     for (int i = 0; i < m_layouts.rowCount(); ++i) {
289         if ((m_layouts[i].errors>0 || m_layouts[i].warnings>0) && !m_layouts[i].isActive) {
290             CentralLayout central(this, m_layouts[i].id);
291             m_layouts[i].errors = central.errors().count();
292             m_layouts[i].warnings = central.warnings().count();
293         }
294     }
295 }
296 
centralLayout(QString layoutname) const297 CentralLayout *Synchronizer::centralLayout(QString layoutname) const
298 {
299     for (int i = 0; i < m_centralLayouts.size(); ++i) {
300         CentralLayout *layout = m_centralLayouts.at(i);
301 
302         if (layout->name() == layoutname) {
303             return layout;
304         }
305     }
306 
307     return nullptr;
308 }
309 
currentLayouts() const310 QList<CentralLayout *> Synchronizer::currentLayouts() const
311 {
312     QList<CentralLayout *> layouts;
313     layouts.clear();
314 
315     if (m_centralLayouts.isEmpty()) {
316         return layouts;
317     }
318 
319     if (m_manager->memoryUsage() == MemoryUsage::SingleLayout) {
320         layouts << m_centralLayouts[0];
321     } else {
322         for (auto layout : m_centralLayouts) {
323             if (layout->isOnAllActivities() || layout->appliedActivities().contains(m_manager->corona()->activitiesConsumer()->currentActivity())) {
324                 layouts << layout;
325             }
326         }
327     }
328 
329     return layouts;
330 }
331 
centralLayoutsForActivity(const QString activityid) const332 QList<CentralLayout *> Synchronizer::centralLayoutsForActivity(const QString activityid) const
333 {
334     QList<CentralLayout *> layouts;
335 
336     if (m_manager->memoryUsage() == MemoryUsage::SingleLayout && m_centralLayouts.count() >= 1) {
337         layouts << m_centralLayouts.at(0);
338     } else {
339         for (auto layout : m_centralLayouts) {
340             if (layout->isOnAllActivities() || layout->appliedActivities().contains(activityid)) {
341                 layouts << layout;
342             }
343         }
344     }
345 
346     return layouts;
347 }
348 
currentViews() const349 QList<Latte::View *> Synchronizer::currentViews() const
350 {
351     QList<Latte::View *> views;
352 
353     for(auto layout : currentLayouts()) {
354         views << layout->latteViews();
355     }
356 
357     return views;
358 }
359 
currentViewsWithPlasmaShortcuts() const360 QList<Latte::View *> Synchronizer::currentViewsWithPlasmaShortcuts() const
361 {
362     QList<Latte::View *> views;
363 
364     for(auto layout : currentLayouts()) {
365         views << layout->viewsWithPlasmaShortcuts();
366     }
367 
368     return views;
369 }
370 
sortedCurrentViews() const371 QList<Latte::View *> Synchronizer::sortedCurrentViews() const
372 {
373     QList<Latte::View *> views = currentViews();
374 
375     return Layout::GenericLayout::sortedLatteViews(views);
376 }
377 
viewsBasedOnActivityId(const QString & id) const378 QList<Latte::View *> Synchronizer::viewsBasedOnActivityId(const QString &id) const
379 {
380     QList<Latte::View *> views;
381 
382     for(auto layout : centralLayoutsForActivity(id)) {
383         if (m_centralLayouts.contains(layout)) {
384             views << layout->latteViews();
385         }
386     }
387 
388     return views;
389 }
390 
layout(QString layoutname) const391 Layout::GenericLayout *Synchronizer::layout(QString layoutname) const
392 {
393     Layout::GenericLayout *l = centralLayout(layoutname);
394 
395     return l;
396 }
397 
screenForContainment(Plasma::Containment * containment)398 int Synchronizer::screenForContainment(Plasma::Containment *containment)
399 {
400     for (auto layout : m_centralLayouts) {
401         if (layout->contains(containment)) {
402             return layout->screenForContainment(containment);
403         }
404     }
405 
406     return -1;
407 }
408 
viewForContainment(uint id)409 Latte::View *Synchronizer::viewForContainment(uint id)
410 {
411     for (auto layout : m_centralLayouts) {
412         Latte::View *view = layout->viewForContainment(id);
413 
414         if (view) {
415             return view;
416         }
417     }
418 
419     return nullptr;
420 }
421 
viewForContainment(Plasma::Containment * containment)422 Latte::View *Synchronizer::viewForContainment(Plasma::Containment *containment)
423 {
424     for (auto layout : m_centralLayouts) {
425         Latte::View *view = layout->viewForContainment(containment);
426 
427         if (view) {
428             return view;
429         }
430     }
431 
432     return nullptr;
433 }
434 
addLayout(CentralLayout * layout)435 void Synchronizer::addLayout(CentralLayout *layout)
436 {
437     if (!m_centralLayouts.contains(layout)) {
438         m_centralLayouts.append(layout);
439         layout->initToCorona(m_manager->corona());
440     }
441 }
442 
onActivityRemoved(const QString & activityid)443 void Synchronizer::onActivityRemoved(const QString &activityid)
444 {
445     if (!m_assignedLayouts.contains(activityid)) {
446         return;
447     }
448 
449     //! remove any other explicit set layouts for the current activity
450     QStringList explicits = m_assignedLayouts[activityid];
451 
452     for(auto explicitlayoutname : explicits) {
453         QString explicitlayoutid = m_layouts.idForName(explicitlayoutname);
454 
455         m_layouts[explicitlayoutid].activities.removeAll(activityid);
456         m_manager->setOnActivities(explicitlayoutname, m_layouts[explicitlayoutid].activities);
457         emit layoutActivitiesChanged(m_layouts[explicitlayoutid]);
458     }
459 
460     QStringList freelayoutnames;
461 
462     if (m_assignedLayouts.contains(Data::Layout::FREEACTIVITIESID)) {
463         freelayoutnames = m_assignedLayouts[Data::Layout::FREEACTIVITIESID];
464     }
465 
466     reloadAssignedLayouts();
467 
468     for(auto freelayoutname : freelayoutnames) {
469         //! inform free activities layouts that their activities probably changed
470         CentralLayout *central = centralLayout(freelayoutname);
471 
472         if (central) {
473             emit central->activitiesChanged();
474         }
475     }
476 }
477 
updateBorderlessMaximizedAfterTimer()478 void Synchronizer::updateBorderlessMaximizedAfterTimer()
479 {
480     //! this signal is also triggered when runningactivities are changed and actually is received first
481     //! this is why we need a timer here in order to delay that execution and not activate/deactivate
482     //! maximizedborders faulty because syncMultipleLayoutsToActivities(); has not been executed yet
483     m_updateBorderlessMaximized.start();
484 }
485 
hideAllViews()486 void Synchronizer::hideAllViews()
487 {
488     for (const auto layout : m_centralLayouts) {
489         emit currentLayoutIsSwitching(layout->name());
490     }
491 }
492 
pauseLayout(QString layoutName)493 void Synchronizer::pauseLayout(QString layoutName)
494 {
495     if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
496         CentralLayout *layout = centralLayout(layoutName);
497 
498         if (layout->isOnAllActivities()) {
499             return;
500         }
501 
502         QStringList appliedactivities = layout->appliedActivities();
503 
504         if (layout && !appliedactivities.isEmpty()) {
505             int i = 0;
506 
507             for (const auto &activityid : appliedactivities) {
508                 //! Stopping the activities must be done asynchronous because otherwise
509                 //! the activity manager cant close multiple activities
510                 QTimer::singleShot(i * 1000, [this, activityid]() {
511                     m_activitiesController->stopActivity(activityid);
512                 });
513 
514                 i = i + 1;
515             }
516         }
517     }
518 }
519 
syncActiveLayoutsToOriginalFiles()520 void Synchronizer::syncActiveLayoutsToOriginalFiles()
521 {
522     if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
523         for (const auto layout : m_centralLayouts) {
524             layout->syncToLayoutFile();
525         }
526     }
527 }
528 
syncLatteViewsToScreens()529 void Synchronizer::syncLatteViewsToScreens()
530 {
531     for (const auto layout : m_centralLayouts) {
532         layout->syncLatteViewsToScreens();
533     }
534 }
535 
unloadCentralLayout(CentralLayout * layout)536 void Synchronizer::unloadCentralLayout(CentralLayout *layout)
537 {
538     int pos = m_centralLayouts.indexOf(layout);
539 
540     if (pos>=0) {
541         CentralLayout *central = m_centralLayouts.takeAt(pos);
542 
543         if (m_multipleModeInitialized) {
544             central->syncToLayoutFile(true);
545         }
546 
547         central->unloadLatteViews();
548         central->unloadContainments();
549 
550         if (m_multipleModeInitialized) {
551             m_manager->clearUnloadedContainmentsFromLinkedFile(central->unloadedContainmentsIds(), true);
552         }
553 
554         delete central;
555     }
556 }
557 
initLayouts()558 void Synchronizer::initLayouts()
559 {
560     m_layouts.clear();
561 
562     QDir layoutDir(Layouts::Importer::layoutUserDir());
563     QStringList filter;
564     filter.append(QString("*.layout.latte"));
565     QStringList files = layoutDir.entryList(filter, QDir::Files | QDir::NoSymLinks);
566 
567     for (const auto &layout : files) {
568         if (layout.contains(Layout::MULTIPLELAYOUTSHIDDENNAME)) {
569             //! IMPORTANT: DON'T ADD MultipleLayouts hidden file in layouts list
570             continue;
571         }
572 
573         QString layoutpath = layoutDir.absolutePath() + "/" + layout;
574         onLayoutAdded(layoutpath);
575     }
576 
577     emit layoutsChanged();
578 
579     if (!m_isLoaded) {
580         m_isLoaded = true;
581         connect(m_manager->corona()->templatesManager(), &Latte::Templates::Manager::newLayoutAdded, this, &Synchronizer::onLayoutAdded);
582         connect(m_manager->importer(), &Latte::Layouts::Importer::newLayoutAdded, this, &Synchronizer::onLayoutAdded);
583     }
584 }
585 
onLayoutAdded(const QString & layout)586 void Synchronizer::onLayoutAdded(const QString &layout)
587 {
588     CentralLayout centrallayout(this, layout);
589     m_layouts.insertBasedOnName(centrallayout.data());
590 
591     if (m_isLoaded) {
592         emit layoutsChanged();
593     }
594 }
595 
reloadAssignedLayouts()596 void Synchronizer::reloadAssignedLayouts()
597 {
598     m_assignedLayouts.clear();
599 
600     for (int i=0; i< m_layouts.rowCount(); ++i) {
601         for (const auto &activity : m_layouts[i].activities) {
602             if (m_assignedLayouts.contains(activity)) {
603                 m_assignedLayouts[activity] << m_layouts[i].name;
604             } else {
605                 m_assignedLayouts[activity] = QStringList(m_layouts[i].name);
606             }
607         }
608     }
609 }
610 
unloadLayouts()611 void Synchronizer::unloadLayouts()
612 {
613     //! Unload all CentralLayouts
614     while (!m_centralLayouts.isEmpty()) {
615         CentralLayout *layout = m_centralLayouts.at(0);
616         unloadCentralLayout(layout);
617     }
618 
619     m_multipleModeInitialized = false;
620 }
621 
memoryInitialized() const622 bool Synchronizer::memoryInitialized() const
623 {
624     return ((m_manager->memoryUsage() == MemoryUsage::SingleLayout && m_centralLayouts.size()>0)
625             || (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts && m_multipleModeInitialized));
626 }
627 
initSingleMode(QString layoutName)628 bool Synchronizer::initSingleMode(QString layoutName)
629 {
630     QString layoutpath = layoutName.isEmpty() ? layoutPath(m_manager->corona()->universalSettings()->singleModeLayoutName()) : layoutPath(layoutName);
631 
632     if (layoutpath.isEmpty()) {
633         qDebug() << "Layout : " << layoutName << " was not found...";
634         return false;
635     }
636 
637     if (m_centralLayouts.size() > 0) {
638         emit currentLayoutIsSwitching(m_centralLayouts[0]->name());
639     }
640 
641     //! this code must be called asynchronously because it can create crashes otherwise.
642     //! Tasks plasmoid case that triggers layouts switching through its context menu
643     QTimer::singleShot(LAYOUTSINITINTERVAL, [this, layoutName, layoutpath]() {
644         qDebug() << " ... initializing layout in single mode : " << layoutName << " - " << layoutpath;
645         unloadLayouts();
646 
647         //! load the main layout/corona file
648         CentralLayout *newLayout = new CentralLayout(this, layoutpath, layoutName);
649         addLayout(newLayout);
650 
651         m_manager->loadLatteLayout(layoutpath);
652 
653         emit centralLayoutsChanged();
654 
655         if (m_isSingleLayoutInDeprecatedRenaming) {
656             QString deprecatedlayoutpath = layoutPath(m_manager->corona()->universalSettings()->singleModeLayoutName());
657 
658             if (!deprecatedlayoutpath.isEmpty()) {
659                 qDebug() << "Removing Deprecated single layout after renaming:: " << m_manager->corona()->universalSettings()->singleModeLayoutName();
660                 QFile(deprecatedlayoutpath).remove();
661             }
662 
663             m_isSingleLayoutInDeprecatedRenaming = false;
664         }
665 
666         m_manager->corona()->universalSettings()->setSingleModeLayoutName(layoutName);
667 
668         emit initializationFinished();
669     });
670 
671     return true;
672 }
673 
initMultipleMode(QString layoutName)674 bool Synchronizer::initMultipleMode(QString layoutName)
675 {
676     if (m_multipleModeInitialized) {
677         return false;
678     }
679 
680     for (const auto layout : m_centralLayouts) {
681         emit currentLayoutIsSwitching(layout->name());
682     }
683 
684     //! this code must be called asynchronously because it can create crashes otherwise.
685     //! Tasks plasmoid case that triggers layouts switching through its context menu
686     QTimer::singleShot(LAYOUTSINITINTERVAL, [this, layoutName]() {
687         qDebug() << " ... initializing layout in multiple mode : " << layoutName ;
688         unloadLayouts();
689 
690         m_manager->loadLatteLayout(layoutPath(QString(Layout::MULTIPLELAYOUTSHIDDENNAME)));
691 
692         m_multipleModeInitialized = true;
693 
694         emit centralLayoutsChanged();
695 
696         if (!layoutName.isEmpty()) {
697             switchToLayoutInMultipleModeBasedOnActivities(layoutName);
698         }
699 
700         syncMultipleLayoutsToActivities();
701 
702         emit initializationFinished();
703     });
704 
705     return true;
706 }
707 
switchToLayoutInSingleMode(QString layoutName)708 bool Synchronizer::switchToLayoutInSingleMode(QString layoutName)
709 {
710     if (!memoryInitialized() || m_manager->memoryUsage() != MemoryUsage::SingleLayout) {
711         return false;
712     }
713 
714     if (m_centralLayouts.size()>0 && m_centralLayouts[0]->name() == layoutName) {
715         return true;
716     }
717 
718     return initSingleMode(layoutName);
719 }
720 
switchToLayoutInMultipleModeBasedOnActivities(const QString & layoutName)721 bool Synchronizer::switchToLayoutInMultipleModeBasedOnActivities(const QString &layoutName)
722 {
723     Data::Layout layoutdata;
724     CentralLayout *central = centralLayout(layoutName);
725 
726     if (central) {
727         layoutdata = central->data();
728     } else if (m_layouts.containsName(layoutName)) {
729         QString layoutid = m_layouts.idForName(layoutName);
730         CentralLayout storagedlayout(this, layoutid);
731         layoutdata = storagedlayout.data();
732 
733         m_layouts[layoutid] = layoutdata;
734     }
735 
736     if (layoutdata.isEmpty()) {
737         return false;
738     }
739 
740     QString switchToActivity;
741 
742     //! try to not remove activityids that belong to different machines that are not currently present
743     QStringList validlayoutactivities = validActivities(layoutdata.activities);
744 
745     if (layoutdata.isOnAllActivities()) {
746         //! no reason to switch in any activity;
747     } else if (layoutdata.isForFreeActivities()) {
748         //! free-activities case
749         QStringList freerunningactivities = freeRunningActivities();
750 
751         if (freerunningactivities.count() > 0) {
752             if (freerunningactivities.contains(layoutdata.lastUsedActivity)) {
753                 switchToActivity = layoutdata.lastUsedActivity;
754             } else {
755                 switchToActivity = freerunningactivities[0];
756             }
757         } else {
758             QStringList freepausedactivities = freeActivities();
759 
760             if (freepausedactivities.count() > 0) {
761                 switchToActivity = freepausedactivities[0];
762             }
763         }
764     } else if (!validlayoutactivities.isEmpty())  {
765         //! set on-explicit activities
766         QStringList allactivities = activities();
767 
768         if (validlayoutactivities.contains(layoutdata.lastUsedActivity)) {
769             switchToActivity = layoutdata.lastUsedActivity;
770         } else {
771             switchToActivity = validlayoutactivities[0];
772         }
773     } else if (validlayoutactivities.isEmpty() && m_layouts.containsName(layoutName)) {
774         //! no-activities are set
775         //! has not been set in any activities but nonetheless it is requested probably by the user
776         //! requested layout is assigned explicitly in current activity and any remaining explicit layouts
777         //! are removing current activity from their activities list
778         QString layoutid = m_layouts.idForName(layoutName);
779         QString currentactivityid = m_activitiesController->currentActivity();
780 
781         QStringList layoutIdsChanged;
782 
783         m_layouts[layoutid].activities.append(currentactivityid);
784         m_manager->setOnActivities(layoutName, m_layouts[layoutid].activities);
785         emit layoutActivitiesChanged(m_layouts[layoutid]);
786 
787         layoutIdsChanged << layoutid;
788 
789         if (m_assignedLayouts.contains(currentactivityid)) {
790             //! remove any other explicit set layouts for the current activity
791             QStringList explicits = m_assignedLayouts[currentactivityid];
792 
793             for(auto explicitlayoutname : explicits) {
794                 QString explicitlayoutid = m_layouts.idForName(explicitlayoutname);
795 
796                 m_layouts[explicitlayoutid].activities.removeAll(currentactivityid);
797                 m_manager->setOnActivities(explicitlayoutname, m_layouts[explicitlayoutid].activities);
798                 emit layoutActivitiesChanged(m_layouts[explicitlayoutid]);
799             }
800         }
801 
802         QStringList freelayoutnames;
803         if (m_assignedLayouts.contains(Data::Layout::FREEACTIVITIESID)) {
804             freelayoutnames = m_assignedLayouts[Data::Layout::FREEACTIVITIESID];
805         }
806 
807         reloadAssignedLayouts();
808 
809         for(auto freelayoutname : freelayoutnames) {
810             //! inform free activities layouts that their activities probably changed
811             CentralLayout *central = centralLayout(freelayoutname);
812 
813             if (central) {
814                 emit central->activitiesChanged();
815             }
816         }
817     }
818 
819     if (!switchToActivity.isEmpty()) {
820         if (!m_manager->corona()->activitiesConsumer()->runningActivities().contains(switchToActivity)) {
821             m_activitiesController->startActivity(switchToActivity);
822         }
823 
824         m_activitiesController->setCurrentActivity(switchToActivity);
825     }
826 
827     return true;
828 }
829 
switchToLayoutInMultipleMode(QString layoutName)830 bool Synchronizer::switchToLayoutInMultipleMode(QString layoutName)
831 {
832     if (!memoryInitialized() || m_manager->memoryUsage() != MemoryUsage::MultipleLayouts) {
833         return false;
834     }
835 
836     CentralLayout *layout = centralLayout(layoutName);
837 
838     if (layout) {
839         QStringList appliedActivities = layout->appliedActivities();
840         QString nextActivity = !layout->lastUsedActivity().isEmpty() ? layout->lastUsedActivity() : appliedActivities[0];
841 
842         if (!appliedActivities.contains(m_manager->corona()->activitiesConsumer()->currentActivity())) {
843             //! it means we are at a foreign activity and we can switch to correct one
844             m_activitiesController->setCurrentActivity(nextActivity);
845             return true;
846         }
847     } else {
848         if (!layoutName.isEmpty()) {
849             switchToLayoutInMultipleModeBasedOnActivities(layoutName);
850         }
851 
852         syncMultipleLayoutsToActivities();
853     }
854 
855     return true;
856 }
857 
858 
switchToLayout(QString layoutName,MemoryUsage::LayoutsMemory newMemoryUsage)859 bool Synchronizer::switchToLayout(QString layoutName, MemoryUsage::LayoutsMemory newMemoryUsage)
860 {
861     qDebug() << " >>>>> SWITCHING >> " << layoutName << " __ from memory: " << m_manager->memoryUsage() << " to memory: " << newMemoryUsage;
862 
863     if (newMemoryUsage == MemoryUsage::Current) {
864         newMemoryUsage = m_manager->memoryUsage();
865     }
866 
867     if (!memoryInitialized() || newMemoryUsage != m_manager->memoryUsage()) {
868         //! Initiate Layouts memory properly
869         m_manager->setMemoryUsage(newMemoryUsage);
870 
871         return (newMemoryUsage == MemoryUsage::SingleLayout ? initSingleMode(layoutName) : initMultipleMode(layoutName));
872     }
873 
874     if (m_manager->memoryUsage() == MemoryUsage::SingleLayout) {
875         return switchToLayoutInSingleMode(layoutName);
876     } else {
877         return switchToLayoutInMultipleMode(layoutName);
878     }
879 }
880 
syncMultipleLayoutsToActivities()881 void Synchronizer::syncMultipleLayoutsToActivities()
882 {
883     qDebug() << "   ----  --------- ------    syncMultipleLayoutsToActivities       -------   ";
884     qDebug() << "   ----  --------- ------    -------------------------------       -------   ";
885 
886     QStringList layoutNamesToUnload;
887     QStringList layoutNamesToLoad;
888     QStringList currentNames = centralLayoutsNames();
889 
890     //! discover OnAllActivities layouts
891     if (m_assignedLayouts.contains(Data::Layout::ALLACTIVITIESID)) {
892         layoutNamesToLoad << m_assignedLayouts[Data::Layout::ALLACTIVITIESID];
893     }
894 
895     //! discover ForFreeActivities layouts
896     if (m_assignedLayouts.contains(Data::Layout::FREEACTIVITIESID) && freeRunningActivities().count()>0) {
897         layoutNamesToLoad << m_assignedLayouts[Data::Layout::FREEACTIVITIESID];
898     }
899 
900     //! discover layouts assigned to explicit activities based on running activities
901     for (const auto &activity : runningActivities()) {
902 #if KF5_VERSION_MINOR < 81
903         if (KWindowSystem::isPlatformWayland() && (m_activitiesController->currentActivity() != activity)){
904             //! Wayland Protection: Plasma wayland does not support Activities for windows before kde frameworks 5.81
905             //! In that scenario we can load the layouts that belong OnAllActivities + (ForFreeActivities OR SpecificActivity)
906             continue;
907         }
908 #endif
909 
910         if (m_assignedLayouts.contains(activity)) {
911             layoutNamesToLoad << m_assignedLayouts[activity];
912         }
913     }
914 
915     //! discover layouts that must be unloaded because of running activities changes
916     for (const auto layout : m_centralLayouts) {
917         if (!layoutNamesToLoad.contains(layout->name())) {
918             layoutNamesToUnload << layout->name();
919         }
920     }
921 
922     QString defaultForcedLayout;
923 
924     //! Safety
925     if (layoutNamesToLoad.isEmpty()) {
926         //! If no layout is found then force loading Default Layout
927         QString layoutPath = m_manager->corona()->templatesManager()->newLayout("", i18n(Templates::DEFAULTLAYOUTTEMPLATENAME));
928         layoutNamesToLoad << Layout::AbstractLayout::layoutName(layoutPath);
929         m_manager->setOnAllActivities(layoutNamesToLoad[0]);
930         defaultForcedLayout = layoutNamesToLoad[0];
931     }
932 
933     QStringList newlyActivatedLayouts;
934 
935     //! Add needed Layouts based on Activities settings
936     for (const auto &layoutname : layoutNamesToLoad) {
937         if (!centralLayout(layoutname)) {
938             CentralLayout *newLayout = new CentralLayout(this, QString(layoutPath(layoutname)), layoutname);
939 
940             if (newLayout) {
941                 qDebug() << "ACTIVATING LAYOUT ::::: " << layoutname;
942                 addLayout(newLayout);
943                 newLayout->importToCorona();
944 
945                 if (!defaultForcedLayout.isEmpty() && defaultForcedLayout == layoutname) {
946                     emit newLayoutAdded(newLayout->data());
947                 }
948 
949                 newlyActivatedLayouts << newLayout->name();
950             }
951         }
952     }
953 
954     if (newlyActivatedLayouts.count()>0 && m_manager->corona()->universalSettings()->showInfoWindow()) {
955         m_manager->showInfoWindow(i18np("Activating layout: <b>%2</b> ...",
956                                         "Activating layouts: <b>%2</b> ...",
957                                         newlyActivatedLayouts.count(),
958                                         newlyActivatedLayouts.join(", ")),
959                                   4000, QStringList(Data::Layout::ALLACTIVITIESID));
960     }
961 
962     //! Unload no needed Layouts
963 
964     //! hide layouts that will be removed in the end
965     if (!layoutNamesToUnload.isEmpty()) {
966         for (const auto layoutname : layoutNamesToUnload) {
967             emit currentLayoutIsSwitching(layoutname);
968         }
969 
970         QTimer::singleShot(LAYOUTSINITINTERVAL, [this, layoutNamesToUnload]() {
971             unloadLayouts(layoutNamesToUnload);
972         });
973     }
974 
975     currentNames.sort();
976     layoutNamesToLoad.sort();
977 
978     if (currentNames != layoutNamesToLoad) {
979         emit centralLayoutsChanged();
980     }
981 }
982 
unloadLayouts(const QStringList & layoutNames)983 void Synchronizer::unloadLayouts(const QStringList &layoutNames)
984 {
985     if (layoutNames.isEmpty()) {
986         return;
987     }
988 
989     //! Unload no needed Layouts
990     for (const auto &layoutname : layoutNames) {
991         CentralLayout *layout = centralLayout(layoutname);
992         int posLayout = centralLayoutPos(layoutname);
993 
994         if (posLayout >= 0) {
995             qDebug() << "REMOVING LAYOUT ::::: " << layoutname;
996             m_centralLayouts.removeAt(posLayout);
997 
998             layout->syncToLayoutFile(true);
999             layout->unloadContainments();
1000             layout->unloadLatteViews();
1001             m_manager->clearUnloadedContainmentsFromLinkedFile(layout->unloadedContainmentsIds());
1002             delete layout;
1003         }
1004     }
1005 
1006     emit centralLayoutsChanged();
1007 }
1008 
updateKWinDisabledBorders()1009 void Synchronizer::updateKWinDisabledBorders()
1010 {
1011     if (KWindowSystem::isPlatformWayland()) {
1012         // BUG: https://bugs.kde.org/show_bug.cgi?id=428202
1013         // KWin::reconfigure() function blocks/freezes Latte under wayland
1014         return;
1015     }
1016 
1017     if (!m_manager->corona()->universalSettings()->canDisableBorders()) {
1018         m_manager->corona()->universalSettings()->kwin_setDisabledMaximizedBorders(false);
1019     } else {
1020         if (m_manager->corona()->layoutsManager()->memoryUsage() == MemoryUsage::SingleLayout) {
1021             m_manager->corona()->universalSettings()->kwin_setDisabledMaximizedBorders(m_centralLayouts.at(0)->disableBordersForMaximizedWindows());
1022         } else if (m_manager->corona()->layoutsManager()->memoryUsage() == MemoryUsage::MultipleLayouts) {
1023             QList<CentralLayout *> centrals = centralLayoutsForActivity(m_manager->corona()->activitiesConsumer()->currentActivity());
1024 
1025             for (int i = 0; i < centrals.size(); ++i) {
1026                 CentralLayout *layout = centrals.at(i);
1027 
1028                 if (layout->disableBordersForMaximizedWindows()) {
1029                     m_manager->corona()->universalSettings()->kwin_setDisabledMaximizedBorders(true);
1030                     return;
1031                 }
1032             }
1033 
1034             //! avoid initialization step for example during startup that no layouts have been loaded yet
1035             if (centrals.size() > 0) {
1036                 m_manager->corona()->universalSettings()->kwin_setDisabledMaximizedBorders(false);
1037             }
1038 
1039         }
1040     }
1041 }
1042 
1043 }
1044 } // end of namespace
1045