1
0
mirror of https://github.com/KDE/latte-dock.git synced 2024-12-24 17:33:50 +03:00
latte-dock/app/layouts/synchronizer.cpp
2020-08-28 17:58:39 +03:00

716 lines
21 KiB
C++

/*
* Copyright 2019 Michail Vourlakos <mvourlakos@gmail.com>
*
* This file is part of Latte-Dock
*
* Latte-Dock is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* Latte-Dock is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "synchronizer.h"
//! local
#include "importer.h"
#include "manager.h"
#include "../apptypes.h"
#include "../data/layoutdata.h"
#include "../lattecorona.h"
#include "../layout/centrallayout.h"
#include "../layout/genericlayout.h"
#include "../settings/universalsettings.h"
#include "../templates/templatesmanager.h"
#include "../view/view.h"
// Qt
#include <QDir>
#include <QFile>
// Plasma
#include <Plasma/Containment>
// KDE
#include <KActivities/Consumer>
#include <KActivities/Controller>
#include <KWindowSystem>
namespace Latte {
namespace Layouts {
Synchronizer::Synchronizer(QObject *parent)
: QObject(parent),
m_activitiesController(new KActivities::Controller)
{
m_manager = qobject_cast<Manager *>(parent);
//! KWin update Disabled Borders
connect(this, &Synchronizer::centralLayoutsChanged, this, &Synchronizer::updateKWinDisabledBorders);
connect(m_manager->corona()->universalSettings(), &UniversalSettings::canDisableBordersChanged, this, &Synchronizer::updateKWinDisabledBorders);
//! KActivities tracking
connect(m_manager->corona()->activitiesConsumer(), &KActivities::Consumer::currentActivityChanged,
this, &Synchronizer::onCurrentActivityChanged);
connect(m_manager->corona()->activitiesConsumer(), &KActivities::Consumer::runningActivitiesChanged,
this, [&]() {
if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
syncMultipleLayoutsToActivities();
}
});
}
Synchronizer::~Synchronizer()
{
m_activitiesController->deleteLater();
}
KActivities::Controller *Synchronizer::activitiesController() const
{
return m_activitiesController;
}
bool Synchronizer::latteViewExists(Latte::View *view) const
{
for (const auto layout : m_centralLayouts) {
for (const auto &v : layout->latteViews()) {
if (v == view) {
return true;
}
}
}
return false;
}
bool Synchronizer::layoutExists(QString layoutName) const
{
return m_layouts.contains(layoutName);
}
bool Synchronizer::isAssigned(QString layoutName) const
{
for(auto activityid : m_assignedLayouts.keys()) {
if (m_assignedLayouts[activityid].contains(layoutName)) {
return true;
}
}
return false;
}
int Synchronizer::centralLayoutPos(QString id) const
{
for (int i = 0; i < m_centralLayouts.size(); ++i) {
CentralLayout *layout = m_centralLayouts.at(i);
if (layout->name() == id) {
return i;
}
}
return -1;
}
QString Synchronizer::layoutPath(QString layoutName)
{
QString path = Layouts::Importer::layoutUserFilePath(layoutName);
if (!QFile(path).exists()) {
path = "";
}
return path;
}
QStringList Synchronizer::activities()
{
return m_manager->corona()->activitiesConsumer()->activities();
}
QStringList Synchronizer::freeActivities()
{
QStringList frees = activities();
for(auto assigned : m_assignedLayouts.keys()) {
frees.removeAll(assigned);
}
return frees;
}
QStringList Synchronizer::runningActivities()
{
return m_manager->corona()->activitiesConsumer()->runningActivities();
}
QStringList Synchronizer::freeRunningActivities()
{
QStringList fActivities;
for (const auto &activity : runningActivities()) {
if (!m_assignedLayouts.contains(activity)) {
fActivities.append(activity);
}
}
return fActivities;
}
QStringList Synchronizer::centralLayoutsNames()
{
QStringList names;
if (m_manager->memoryUsage() == MemoryUsage::SingleLayout) {
names << m_centralLayouts.at(0)->name();
} else {
for (int i = 0; i < m_centralLayouts.size(); ++i) {
CentralLayout *layout = m_centralLayouts.at(i);
names << layout->name();
}
}
return names;
}
QStringList Synchronizer::currentLayoutsNames() const
{
QList<CentralLayout *> currents = currentLayouts();
QStringList currentNames;
for (int i = 0; i < currents.size(); ++i) {
CentralLayout *layout = currents.at(i);
currentNames << layout->name();
}
return currentNames;
}
QStringList Synchronizer::layouts() const
{
return m_layouts;
}
QStringList Synchronizer::menuLayouts() const
{
QStringList fixedMenuLayouts = m_menuLayouts;
for (const auto layout : m_centralLayouts) {
if (!fixedMenuLayouts.contains(layout->name())) {
fixedMenuLayouts.prepend(layout->name());
}
}
return fixedMenuLayouts;
}
void Synchronizer::setMenuLayouts(QStringList layouts)
{
if (m_menuLayouts == layouts) {
return;
}
m_menuLayouts = layouts;
emit menuLayoutsChanged();
}
CentralLayout *Synchronizer::centralLayout(QString layoutname) const
{
for (int i = 0; i < m_centralLayouts.size(); ++i) {
CentralLayout *layout = m_centralLayouts.at(i);
if (layout->name() == layoutname) {
return layout;
}
}
return nullptr;
}
QList<CentralLayout *> Synchronizer::currentLayouts() const
{
QList<CentralLayout *> layouts;
if (m_manager->memoryUsage() == MemoryUsage::SingleLayout) {
layouts << m_centralLayouts.at(0);
} else {
for (auto layout : m_centralLayouts) {
if (layout->isOnAllActivities() || layout->appliedActivities().contains(m_manager->corona()->activitiesConsumer()->currentActivity())) {
layouts << layout;
}
}
}
return layouts;
}
QList<CentralLayout *> Synchronizer::centralLayoutsForActivity(const QString activityid) const
{
QList<CentralLayout *> layouts;
if (m_manager->memoryUsage() == MemoryUsage::SingleLayout) {
layouts << m_centralLayouts.at(0);
} else {
for (auto layout : m_centralLayouts) {
if (layout->isOnAllActivities() || layout->appliedActivities().contains(activityid)) {
layouts << layout;
}
}
}
return layouts;
}
QList<Latte::View *> Synchronizer::currentViews() const
{
QList<Latte::View *> views;
for(auto layout : currentLayouts()) {
views << layout->latteViews();
}
return views;
}
QList<Latte::View *> Synchronizer::currentViewsWithPlasmaShortcuts() const
{
QList<Latte::View *> views;
for(auto layout : currentLayouts()) {
views << layout->viewsWithPlasmaShortcuts();
}
return views;
}
QList<Latte::View *> Synchronizer::sortedCurrentViews() const
{
QList<Latte::View *> views = currentViews();
return Layout::GenericLayout::sortedLatteViews(views);
}
QList<Latte::View *> Synchronizer::viewsBasedOnActivityId(const QString &id) const
{
QList<Latte::View *> views;
for(auto layout : centralLayoutsForActivity(id)) {
views << layout->latteViews();
}
return views;
}
Layout::GenericLayout *Synchronizer::layout(QString layoutname) const
{
Layout::GenericLayout *l = centralLayout(layoutname);
return l;
}
Latte::View *Synchronizer::viewForContainment(uint id)
{
for (auto layout : m_centralLayouts) {
Latte::View *view = layout->viewForContainment(id);
if (view) {
return view;
}
}
return nullptr;
}
Latte::View *Synchronizer::viewForContainment(Plasma::Containment *containment)
{
for (auto layout : m_centralLayouts) {
Latte::View *view = layout->viewForContainment(containment);
if (view) {
return view;
}
}
return nullptr;
}
void Synchronizer::addLayout(CentralLayout *layout)
{
if (!m_centralLayouts.contains(layout)) {
m_centralLayouts.append(layout);
layout->initToCorona(m_manager->corona());
}
}
void Synchronizer::onCurrentActivityChanged(const QString &id)
{
if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
updateKWinDisabledBorders();
}
}
void Synchronizer::hideAllViews()
{
for (const auto layout : m_centralLayouts) {
emit currentLayoutIsSwitching(layout->name());
}
}
void Synchronizer::pauseLayout(QString layoutName)
{
if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
CentralLayout *layout = centralLayout(layoutName);
if (layout && !layout->activities().isEmpty()) {
int i = 0;
for (const auto &activityId : layout->activities()) {
//! Stopping the activities must be done asynchronous because otherwise
//! the activity manager cant close multiple activities
QTimer::singleShot(i * 1000, [this, activityId]() {
m_activitiesController->stopActivity(activityId);
});
i = i + 1;
}
}
}
}
void Synchronizer::syncActiveLayoutsToOriginalFiles()
{
if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
for (const auto layout : m_centralLayouts) {
layout->syncToLayoutFile();
}
}
}
void Synchronizer::syncLatteViewsToScreens()
{
for (const auto layout : m_centralLayouts) {
layout->syncLatteViewsToScreens();
}
}
void Synchronizer::unloadCentralLayout(CentralLayout *layout)
{
int pos = m_centralLayouts.indexOf(layout);
if (pos>=0) {
CentralLayout *central = m_centralLayouts.takeAt(0);
if (m_multipleModeInitialized) {
central->syncToLayoutFile(true);
}
central->unloadLatteViews();
central->unloadContainments();
if (m_multipleModeInitialized) {
m_manager->clearUnloadedContainmentsFromLinkedFile(central->unloadedContainmentsIds(), true);
}
delete central;
}
}
void Synchronizer::loadLayouts()
{
m_layouts.clear();
m_menuLayouts.clear();
m_assignedLayouts.clear();
QDir layoutDir(Layouts::Importer::layoutUserDir());
QStringList filter;
filter.append(QString("*.layout.latte"));
QStringList files = layoutDir.entryList(filter, QDir::Files | QDir::NoSymLinks);
for (const auto &layout : files) {
if (layout.contains(Layout::MULTIPLELAYOUTSHIDDENNAME)) {
//! IMPORTANT: DON'T ADD MultipleLayouts hidden file in layouts list
continue;
}
QString layoutpath = layoutDir.absolutePath() + "/" + layout;
onLayoutAdded(layoutpath);
}
m_layouts.sort(Qt::CaseInsensitive);
m_menuLayouts.sort(Qt::CaseInsensitive);
emit layoutsChanged();
emit menuLayoutsChanged();
if (!m_isLoaded) {
m_isLoaded = true;
connect(m_manager->corona()->templatesManager(), &Latte::Templates::Manager::newLayoutAdded, this, &Synchronizer::onLayoutAdded);
}
}
void Synchronizer::onLayoutAdded(const QString &layout)
{
CentralLayout centralLayout(this, layout);
for (const auto &activity : centralLayout.activities()) {
if (m_assignedLayouts.contains(activity)) {
m_assignedLayouts[activity] << centralLayout.name();
} else {
m_assignedLayouts[activity] = QStringList(centralLayout.name());
}
}
m_layouts.append(centralLayout.name());
if (centralLayout.showInMenu()) {
m_menuLayouts.append(centralLayout.name());
}
if (m_isLoaded) {
m_layouts.sort(Qt::CaseInsensitive);
m_menuLayouts.sort(Qt::CaseInsensitive);
emit layoutsChanged();
emit menuLayoutsChanged();
}
}
void Synchronizer::unloadLayouts()
{
//! Unload all CentralLayouts
while (!m_centralLayouts.isEmpty()) {
CentralLayout *layout = m_centralLayouts.at(0);
unloadCentralLayout(layout);
}
m_multipleModeInitialized = false;
}
bool Synchronizer::switchToLayout(QString layoutName, int previousMemoryUsage)
{
qDebug() << " >>>>> SWITCHING >> " << layoutName << " __ " << previousMemoryUsage;
//! First Check If that Layout is already present and in that case
//! we can just switch to the proper Activity
if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts && previousMemoryUsage == -1) {
CentralLayout *layout = centralLayout(layoutName);
if (layout) {
QStringList appliedActivities = layout->appliedActivities();
QString nextActivity = !layout->lastUsedActivity().isEmpty() ? layout->lastUsedActivity() : appliedActivities[0];
//! it means we are at a foreign activity
if (!appliedActivities.contains(m_manager->corona()->activitiesConsumer()->currentActivity())) {
m_activitiesController->setCurrentActivity(nextActivity);
return true;
}
}
} else if (m_manager->memoryUsage() == MemoryUsage::SingleLayout && m_centralLayouts.size()>0 && m_centralLayouts[0]->name() == layoutName) {
return false;
}
//! When going from memory usage to different memory usage we first
//! send the layouts that will be changed. This signal creates the
//! nice animation that hides these docks/panels
if (previousMemoryUsage != -1) {
for (const auto layout : m_centralLayouts) {
emit currentLayoutIsSwitching(layout->name());
}
}
QString lPath = layoutPath(layoutName);
if ((m_manager->memoryUsage() == MemoryUsage::SingleLayout && !lPath.isEmpty()) || m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
if (m_manager->memoryUsage() == MemoryUsage::SingleLayout && m_centralLayouts.size()>0) {
emit currentLayoutIsSwitching(m_centralLayouts[0]->name());
} else if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts && layoutName != Layout::MULTIPLELAYOUTSHIDDENNAME) {
//! do nothing
}
//! this code must be called asynchronously because it is called
//! also from qml (Tasks plasmoid). This change fixes a very important
//! crash when switching sessions through the Tasks plasmoid Context menu
//! Latte was unstable and was crashing very often during changing
//! sessions.
QTimer::singleShot(350, [this, layoutName, lPath, previousMemoryUsage]() {
qDebug() << layoutName << " - " << lPath;
QString fixedLPath = lPath;
QString fixedLayoutName = layoutName;
bool initializingMultipleLayouts{(m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) && !m_multipleModeInitialized};
if (m_manager->memoryUsage() == MemoryUsage::SingleLayout || initializingMultipleLayouts || previousMemoryUsage == MemoryUsage::MultipleLayouts) {
unloadLayouts();
//! load the main layout/corona file
if (initializingMultipleLayouts) {
fixedLayoutName = QString(Layout::MULTIPLELAYOUTSHIDDENNAME);
fixedLPath = layoutPath(fixedLayoutName);
} else if (fixedLayoutName != Layout::MULTIPLELAYOUTSHIDDENNAME) {
CentralLayout *newLayout = new CentralLayout(this, fixedLPath, fixedLayoutName);
addLayout(newLayout);
}
m_manager->loadLatteLayout(fixedLPath);
if (initializingMultipleLayouts) {
m_multipleModeInitialized = true;
}
emit centralLayoutsChanged();
}
if (m_manager->memoryUsage() == MemoryUsage::MultipleLayouts) {
syncMultipleLayoutsToActivities();
} else {
//! single layout
m_manager->corona()->universalSettings()->setCurrentLayoutName(layoutName);
}
});
} else {
qDebug() << "Layout : " << layoutName << " was not found...";
}
return true;
}
void Synchronizer::syncMultipleLayoutsToActivities()
{
qDebug() << " ---- --------- ------ syncMultipleLayoutsToActivities ------- ";
qDebug() << " ---- --------- ------ ------------------------------- ------- ";
QStringList layoutsToUnload;
QStringList layoutsToLoad;
QStringList currents = centralLayoutsNames();
//! discover OnAllActivities layouts
if (m_assignedLayouts.contains(Data::Layout::ALLACTIVITIESID)) {
layoutsToLoad << m_assignedLayouts[Data::Layout::ALLACTIVITIESID];
}
//! discover ForFreeActivities layouts
if (m_assignedLayouts.contains(Data::Layout::FREEACTIVITIESID)) {
layoutsToLoad << m_assignedLayouts[Data::Layout::FREEACTIVITIESID];
}
//! discover layouts assigned to explicit activities based on running activities
for (const auto &activity : runningActivities()) {
if (KWindowSystem::isPlatformWayland() && (m_activitiesController->currentActivity() != activity)){
//! Wayland Protection: Plasma wayland does not support yet Activities for windows
//! but we can load the layouts that belong OnAllActivities + (ForFreeActivities OR SpecificActivity)
continue;
}
if (m_assignedLayouts.contains(activity)) {
layoutsToLoad << m_assignedLayouts[activity];
}
}
//! discover layouts that must be unloaded because of running activities changes
for (const auto layout : m_centralLayouts) {
if (!layoutsToLoad.contains(layout->name())) {
layoutsToUnload << layout->name();
}
}
QString defaultForcedLayout;
//! Safety
if (layoutsToLoad.isEmpty()) {
//! If no layout is found then force loading Default Layout
QString layoutPath = m_manager->corona()->templatesManager()->newLayout("", i18n(Templates::DEFAULTLAYOUTTEMPLATENAME));
layoutsToLoad << Layout::AbstractLayout::layoutName(layoutPath);
defaultForcedLayout = layoutsToLoad[0];
}
QStringList newlyActivatedLayouts;
//! Add needed Layouts based on Activities settings
for (const auto &layoutName : layoutsToLoad) {
if (!centralLayout(layoutName)) {
CentralLayout *newLayout = new CentralLayout(this, QString(layoutPath(layoutName)), layoutName);
if (newLayout) {
qDebug() << "ACTIVATING LAYOUT ::::: " << layoutName;
addLayout(newLayout);
newLayout->importToCorona();
if (!defaultForcedLayout.isEmpty() && newLayout->name() == defaultForcedLayout) {
newLayout->setActivities(QStringList(Data::Layout::ALLACTIVITIESID));
}
newlyActivatedLayouts << newLayout->name();
}
}
}
if (m_manager->corona()->universalSettings()->showInfoWindow()) {
if (newlyActivatedLayouts.count() == 1) {
m_manager->showInfoWindow(i18n("Activating layout: <b>%0</b> ...").arg(newlyActivatedLayouts[0]), 4000, QStringList(Data::Layout::ALLACTIVITIESID));
} else if (newlyActivatedLayouts.count() > 1) {
m_manager->showInfoWindow(i18n("Activating layouts: <b>%0</b> ...").arg(newlyActivatedLayouts.join(", ")), 4000, QStringList(Data::Layout::ALLACTIVITIESID));
}
}
//! Unload no needed Layouts
for (const auto &layoutName : layoutsToUnload) {
CentralLayout *layout = centralLayout(layoutName);
int posLayout = centralLayoutPos(layoutName);
if (posLayout >= 0) {
qDebug() << "REMOVING LAYOUT ::::: " << layoutName;
m_centralLayouts.removeAt(posLayout);
layout->syncToLayoutFile(true);
layout->unloadContainments();
layout->unloadLatteViews();
m_manager->clearUnloadedContainmentsFromLinkedFile(layout->unloadedContainmentsIds());
delete layout;
}
}
qSort(currents);
qSort(layoutsToLoad);
if (currents != layoutsToLoad) {
emit centralLayoutsChanged();
}
}
void Synchronizer::updateKWinDisabledBorders()
{
if (!m_manager->corona()->universalSettings()->canDisableBorders()) {
m_manager->corona()->universalSettings()->kwin_setDisabledMaximizedBorders(false);
} else {
if (m_manager->corona()->layoutsManager()->memoryUsage() == MemoryUsage::SingleLayout) {
m_manager->corona()->universalSettings()->kwin_setDisabledMaximizedBorders(m_centralLayouts.at(0)->disableBordersForMaximizedWindows());
} else if (m_manager->corona()->layoutsManager()->memoryUsage() == MemoryUsage::MultipleLayouts) {
QList<CentralLayout *> centrals = centralLayoutsForActivity(m_manager->corona()->activitiesConsumer()->currentActivity());
for (int i = 0; i < centrals.size(); ++i) {
CentralLayout *layout = centrals.at(i);
if (layout->disableBordersForMaximizedWindows()) {
m_manager->corona()->universalSettings()->kwin_setDisabledMaximizedBorders(true);
return;
}
}
m_manager->corona()->universalSettings()->kwin_setDisabledMaximizedBorders(false);
}
}
}
}
} // end of namespace