diff --git a/app/dock/dockview.cpp b/app/dock/dockview.cpp index b1873e52..270ec891 100644 --- a/app/dock/dockview.cpp +++ b/app/dock/dockview.cpp @@ -1,1944 +1,1952 @@ /* * Copyright 2016 Smith AR * Michail Vourlakos * * 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 . */ #include "dockview.h" #include "dockconfigview.h" #include "dockmenumanager.h" #include "panelshadows_p.h" #include "visibilitymanager.h" #include "../dockcorona.h" #include "../layout.h" #include "../layoutmanager.h" #include "../screenpool.h" #include "../universalsettings.h" #include "../../liblattedock/extras.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Latte { //! both alwaysVisible and dockWinBehavior are passed through corona because //! during the dock window creation containment hasnt been set, but these variables //! are needed in order for window flags to be set correctly DockView::DockView(Plasma::Corona *corona, QScreen *targetScreen, bool dockWindowBehavior) : PlasmaQuick::ContainmentView(corona), m_menuManager(new DockMenuManager(this)) { setTitle(corona->kPackage().metadata().name()); setIcon(qGuiApp->windowIcon()); setResizeMode(QuickViewSharedEngine::SizeRootObjectToView); setColor(QColor(Qt::transparent)); setClearBeforeRendering(true); const auto flags = Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::NoDropShadowWindowHint | Qt::WindowDoesNotAcceptFocus; if (dockWindowBehavior) { setFlags(flags); } else { setFlags(flags | Qt::BypassWindowManagerHint); } KWindowSystem::setOnAllDesktops(winId(), true); if (targetScreen) setScreenToFollow(targetScreen); else setScreenToFollow(qGuiApp->primaryScreen()); connect(this, &DockView::containmentChanged , this, [ &, dockWindowBehavior]() { qDebug() << "dock view c++ containment changed 1..."; if (!this->containment()) return; qDebug() << "dock view c++ containment changed 2..."; setDockWinBehavior(dockWindowBehavior); restoreConfig(); reconsiderScreen(); if (!m_visibility) { m_visibility = new VisibilityManager(this); connect(m_visibility, &VisibilityManager::isHiddenChanged, this, [&]() { if (m_visibility->isHidden()) { deactivateApplets(); } }); } connect(this->containment(), SIGNAL(statusChanged(Plasma::Types::ItemStatus)), SLOT(statusChanged(Plasma::Types::ItemStatus))); }, Qt::DirectConnection); m_screenSyncTimer.setSingleShot(true); m_screenSyncTimer.setInterval(2000); connect(&m_screenSyncTimer, &QTimer::timeout, this, &DockView::reconsiderScreen); //! under X11 it was identified that windows many times especially under screen changes //! dont end up at the correct position and size. This timer will enforce repositionings //! and resizes every 500ms if the window hasnt end up to correct values and until this //! is achieved m_validateGeometryTimer.setSingleShot(true); m_validateGeometryTimer.setInterval(500); connect(&m_validateGeometryTimer, &QTimer::timeout, this, &DockView::syncGeometry); auto *dockCorona = qobject_cast(this->corona()); if (dockCorona) { m_screenSyncTimer.setInterval(qMax(dockCorona->universalSettings()->screenTrackerInterval() - 500, 1000)); connect(dockCorona->universalSettings(), &UniversalSettings::screenTrackerIntervalChanged, this, [this, dockCorona]() { m_screenSyncTimer.setInterval(qMax(dockCorona->universalSettings()->screenTrackerInterval() - 500, 1000)); }); connect(dockCorona, &DockCorona::docksCountChanged, this, &DockView::docksCountChanged); connect(this, &DockView::docksCountChanged, this, &DockView::totalDocksCountChanged); connect(dockCorona, &DockCorona::dockLocationChanged, this, &DockView::dockLocationChanged); connect(dockCorona, &DockCorona::dockLocationChanged, this, [&]() { //! check if an edge has been freed for a primary dock //! from another screen if (m_onPrimary) { m_screenSyncTimer.start(); } }); } } DockView::~DockView() { m_inDelete = true; disconnect(corona(), &Plasma::Corona::availableScreenRectChanged, this, &DockView::availableScreenRectChanged); disconnect(containment(), SIGNAL(statusChanged(Plasma::Types::ItemStatus)), this, SLOT(statusChanged(Plasma::Types::ItemStatus))); m_screenSyncTimer.stop(); qDebug() << "dock view deleting..."; rootContext()->setContextProperty(QStringLiteral("dock"), nullptr); //! this disconnect does not free up connections correctly when //! dockView is deleted. A crash for this example is the following: //! switch to Alternative Session and disable compositing, //! the signal creating the crash was probably from deleted //! windows. //! this->disconnect(); if (m_configView) { m_configView->setVisible(false);//hide(); } if (m_menuManager) { m_menuManager->deleteLater(); } if (m_visibility) delete m_visibility; } void DockView::init() { connect(this, &QQuickWindow::screenChanged, this, &DockView::screenChanged); connect(this, &QQuickWindow::screenChanged, this, &DockView::docksCountChanged); connect(qGuiApp, &QGuiApplication::screenAdded, this, &DockView::screenChanged); connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &DockView::screenChanged); connect(this, &DockView::screenGeometryChanged, this, &DockView::syncGeometry); connect(this, &QQuickWindow::xChanged, this, &DockView::xChanged); connect(this, &QQuickWindow::xChanged, this, &DockView::validateDockGeometry); connect(this, &QQuickWindow::xChanged, this, &DockView::updateAbsDockGeometry); connect(this, &QQuickWindow::yChanged, this, &DockView::yChanged); connect(this, &QQuickWindow::yChanged, this, &DockView::validateDockGeometry); connect(this, &QQuickWindow::yChanged, this, &DockView::updateAbsDockGeometry); connect(this, &QQuickWindow::widthChanged, this, &DockView::widthChanged); connect(this, &QQuickWindow::widthChanged, this, &DockView::validateDockGeometry); connect(this, &QQuickWindow::widthChanged, this, &DockView::updateAbsDockGeometry); connect(this, &QQuickWindow::heightChanged, this, &DockView::heightChanged); connect(this, &QQuickWindow::heightChanged, this, &DockView::validateDockGeometry); connect(this, &QQuickWindow::heightChanged, this, &DockView::updateAbsDockGeometry); connect(corona(), &Plasma::Corona::availableScreenRectChanged, this, &DockView::availableScreenRectChanged); connect(this, &DockView::behaveAsPlasmaPanelChanged, this, &DockView::syncGeometry); connect(this, &DockView::drawShadowsChanged, this, &DockView::syncGeometry); connect(this, &DockView::maxLengthChanged, this, &DockView::syncGeometry); connect(this, &DockView::offsetChanged, this, &DockView::syncGeometry); connect(this, &DockView::alignmentChanged, this, &DockView::updateEnabledBorders); connect(this, &DockView::dockWinBehaviorChanged, this, &DockView::saveConfig); connect(this, &DockView::onPrimaryChanged, this, &DockView::saveConfig); connect(this, &DockView::locationChanged, this, [&]() { updateFormFactor(); syncGeometry(); }); connect(this, &DockView::dockTransparencyChanged, this, &DockView::updateEffects); connect(this, &DockView::drawEffectsChanged, this, &DockView::updateEffects); connect(this, &DockView::effectsAreaChanged, this, &DockView::updateEffects); connect(&m_theme, &Plasma::Theme::themeChanged, this, &DockView::themeChanged); connect(this, &DockView::normalThicknessChanged, this, [&]() { if (m_behaveAsPlasmaPanel) { syncGeometry(); } }); connect(this, SIGNAL(normalThicknessChanged()), corona(), SIGNAL(availableScreenRectChanged())); connect(this, SIGNAL(shadowChanged()), corona(), SIGNAL(availableScreenRectChanged())); connect(m_menuManager, &DockMenuManager::contextMenuChanged, this, &DockView::contextMenuIsShownChanged); initSignalingForLocationChangeSliding(); ///!!!!! rootContext()->setContextProperty(QStringLiteral("dock"), this); auto *dockCorona = qobject_cast(this->corona()); if (dockCorona) { rootContext()->setContextProperty(QStringLiteral("universalSettings"), dockCorona->universalSettings()); rootContext()->setContextProperty(QStringLiteral("layoutManager"), dockCorona->layoutManager()); } setSource(corona()->kPackage().filePath("lattedockui")); // setVisible(true); syncGeometry(); if (!KWindowSystem::isPlatformWayland()) { setVisible(true); } qDebug() << "SOURCE:" << source(); } void DockView::hideWindowsForSlidingOut() { setBlockHiding(false); if (m_configView) { auto configDialog = qobject_cast(m_configView); if (configDialog) { configDialog->hideConfigWindow(); } } } void DockView::initSignalingForLocationChangeSliding() { //! signals to handle the sliding-in/out during location changes connect(this, &DockView::hideDockDuringLocationChangeStarted, this, [&]() { hideWindowsForSlidingOut(); }); connect(this, &DockView::locationChanged, this, [&]() { if (m_goToLocation != Plasma::Types::Floating) { m_goToLocation = Plasma::Types::Floating; QTimer::singleShot(100, [this]() { setBlockAnimations(false); emit showDockAfterLocationChangeFinished(); showSettingsWindow(); + + if (m_managedLayout) { + m_managedLayout->syncDockViewsToScreens(); + } }); } }); //! signals to handle the sliding-in/out during screen changes connect(this, &DockView::hideDockDuringScreenChangeStarted, this, [&]() { hideWindowsForSlidingOut(); }); connect(this, &DockView::currentScreenChanged, this, [&]() { if (m_goToScreen) { m_goToScreen = nullptr; QTimer::singleShot(100, [this]() { setBlockAnimations(false); emit showDockAfterScreenChangeFinished(); showSettingsWindow(); + + if (m_managedLayout) { + m_managedLayout->syncDockViewsToScreens(); + } }); } }); //! signals to handle the sliding-in/out during moving to another layout connect(this, &DockView::hideDockDuringMovingToLayoutStarted, this, [&]() { hideWindowsForSlidingOut(); }); connect(this, &DockView::managedLayoutChanged, this, [&]() { if (!m_moveToLayout.isEmpty() && m_managedLayout) { m_moveToLayout = ""; QTimer::singleShot(100, [this]() { setBlockAnimations(false); emit showDockAfterMovingToLayoutFinished(); showSettingsWindow(); }); } }); //! ---- both cases ---- !// //! this is used for both location and screen change cases, this signal //! is send when the sliding-out animation has finished connect(this, &DockView::hideDockDuringLocationChangeFinished, this, [&]() { setBlockAnimations(true); if (m_goToLocation != Plasma::Types::Floating) { setLocation(m_goToLocation); } else if (m_goToScreen) { setScreenToFollow(m_goToScreen); } else if (!m_moveToLayout.isEmpty()) { moveToLayout(m_moveToLayout); } }); } void DockView::disconnectSensitiveSignals() { disconnect(corona(), &Plasma::Corona::availableScreenRectChanged, this, &DockView::availableScreenRectChanged); setManagedLayout(nullptr); if (visibility()) { visibility()->setEnabledDynamicBackground(false); } } void DockView::availableScreenRectChanged() { if (m_inDelete) return; if (formFactor() == Plasma::Types::Vertical) syncGeometry(); } void DockView::setupWaylandIntegration() { if (m_shellSurface) return; if (DockCorona *c = qobject_cast(corona())) { using namespace KWayland::Client; PlasmaShell *interface {c->waylandDockCoronaInterface()}; if (!interface) return; Surface *s{Surface::fromWindow(this)}; if (!s) return; m_shellSurface = interface->createSurface(s, this); qDebug() << "WAYLAND dock window surface was created..."; m_shellSurface->setSkipTaskbar(true); m_shellSurface->setRole(PlasmaShellSurface::Role::Panel); m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::WindowsGoBelow); } } KWayland::Client::PlasmaShellSurface *DockView::surface() { return m_shellSurface; } bool DockView::setCurrentScreen(const QString id) { QScreen *nextScreen{qGuiApp->primaryScreen()}; if (id != "primary") { foreach (auto scr, qGuiApp->screens()) { if (scr && scr->name() == id) { nextScreen = scr; break; } } } if (m_screenToFollow == nextScreen) { return true; } if (nextScreen) { if (m_managedLayout) { auto freeEdges = m_managedLayout->freeEdges(nextScreen); if (!freeEdges.contains(location())) { return false; } else { m_goToScreen = nextScreen; //! asynchronous call in order to not crash from configwindow //! deletion from sliding out animation QTimer::singleShot(100, [this]() { emit hideDockDuringScreenChangeStarted(); }); } } } return true; } //! this function updates the dock's associated screen. //! updateScreenId = true, update also the m_screenToFollowId //! updateScreenId = false, do not update the m_screenToFollowId //! that way an explicit dock can be shown in another screen when //! there isnt a tasks dock running in the system and for that //! dock its first origin screen is stored and that way when //! that screen is reconnected the dock will return to its original //! place void DockView::setScreenToFollow(QScreen *screen, bool updateScreenId) { if (!screen || m_screenToFollow == screen) { return; } m_screenToFollow = screen; if (updateScreenId) { m_screenToFollowId = screen->name(); } qDebug() << "adapting to screen..."; setScreen(screen); if (this->containment()) this->containment()->reactToScreenChange(); connect(screen, &QScreen::geometryChanged, this, &DockView::screenGeometryChanged); syncGeometry(); updateAbsDockGeometry(true); emit screenGeometryChanged(); emit currentScreenChanged(); } //! the main function which decides if this dock is at the //! correct screen void DockView::reconsiderScreen() { if (m_inDelete) { return; } qDebug() << " Delayer "; foreach (auto scr, qGuiApp->screens()) { qDebug() << " D, found screen: " << scr->name(); } bool screenExists{false}; //!check if the associated screen is running foreach (auto scr, qGuiApp->screens()) { if (m_screenToFollowId == scr->name() || (onPrimary() && scr == qGuiApp->primaryScreen())) { screenExists = true; } } qDebug() << "dock screen exists ::: " << screenExists; int docksWithTasks = m_managedLayout ? m_managedLayout->noDocksWithTasks() : 0; //! 1.a primary dock must be always on the primary screen //! 2.the last tasks dock must also always on the primary screen //! even though it has been configured as an explicit if ((m_onPrimary || (tasksPresent() && docksWithTasks == 1 && !screenExists)) && (m_screenToFollowId != qGuiApp->primaryScreen()->name() || m_screenToFollow != qGuiApp->primaryScreen())) { using Plasma::Types; QList edges{Types::BottomEdge, Types::LeftEdge, Types::TopEdge, Types::RightEdge}; edges = m_managedLayout ? m_managedLayout->freeEdges(qGuiApp->primaryScreen()) : edges; //change to primary screen only if the specific edge is free qDebug() << "updating the primary screen for dock..."; qDebug() << "available primary screen edges:" << edges; qDebug() << "dock location:" << location(); if (edges.contains(location())) { //! case 2 if (!m_onPrimary && !screenExists && tasksPresent() && (docksWithTasks == 1)) { qDebug() << "reached case 2 of updating dock primary screen..."; setScreenToFollow(qGuiApp->primaryScreen(), false); } else { //! case 1 qDebug() << "reached case 1 of updating dock primary screen..."; setScreenToFollow(qGuiApp->primaryScreen()); } syncGeometry(); } } else if (!m_onPrimary) { //! 3.an explicit dock must be always on the correct associated screen //! there are cases that window manager misplaces the dock, this function //! ensures that this dock will return at its correct screen foreach (auto scr, qGuiApp->screens()) { if (scr && scr->name() == m_screenToFollowId) { qDebug() << "updating the explicit screen for dock..."; setScreenToFollow(scr); syncGeometry(); break; } } } emit docksCountChanged(); } void DockView::screenChanged(QScreen *scr) { m_screenSyncTimer.start(); //! this is needed in order to update the struts on screen change //! and even though the geometry has been set correctly the offsets //! of the screen must be updated to the new ones if (m_visibility && m_visibility->mode() == Latte::Dock::AlwaysVisible) { updateAbsDockGeometry(true); } } void DockView::addNewDock() { auto *dockCorona = qobject_cast(this->corona()); if (dockCorona) { dockCorona->loadDefaultLayout(); } } void DockView::copyDock() { m_managedLayout->copyDock(containment()); } void DockView::removeDock() { if (totalDocksCount() > 1) { QAction *removeAct = this->containment()->actions()->action(QStringLiteral("remove")); if (removeAct) { removeAct->trigger(); } } } QQmlListProperty DockView::screens() { return QQmlListProperty(this, nullptr, &countScreens, &atScreens); } int DockView::countScreens(QQmlListProperty *property) { Q_UNUSED(property) return qGuiApp->screens().count(); } QScreen *DockView::atScreens(QQmlListProperty *property, int index) { Q_UNUSED(property) return qGuiApp->screens().at(index); } QString DockView::currentScreen() const { return m_screenToFollowId; } bool DockView::settingsWindowIsShown() { auto configView = qobject_cast(m_configView); return (configView != nullptr); } void DockView::showSettingsWindow() { showConfigurationInterface(containment()); applyActivitiesToWindows(); } void DockView::showConfigurationInterface(Plasma::Applet *applet) { if (!applet || !applet->containment()) return; Plasma::Containment *c = qobject_cast(applet); if (m_configView && c && c->isContainment() && c == this->containment()) { if (m_configView->isVisible()) { m_configView->setVisible(false); //m_configView->hide(); } else { m_configView->setVisible(true); //m_configView->show(); } return; } else if (m_configView) { if (m_configView->applet() == applet) { m_configView->setVisible(true); //m_configView->show(); m_configView->requestActivate(); return; } else { m_configView->setVisible(false); //m_configView->hide(); m_configView->deleteLater(); } } bool delayConfigView = false; if (c && containment() && c->isContainment() && c->id() == this->containment()->id()) { m_configView = new DockConfigView(c, this); delayConfigView = true; } else { m_configView = new PlasmaQuick::ConfigView(applet); } m_configView.data()->init(); if (!delayConfigView) { m_configView->setVisible(true); //m_configView.data()->show(); } else { //add a timer for showing the configuration window the first time it is //created in order to give the containmnent's layouts the time to //calculate the window's height if (!KWindowSystem::isPlatformWayland()) { QTimer::singleShot(150, m_configView, SLOT(show())); } else { QTimer::singleShot(150, [this]() { m_configView->setVisible(true); }); } } } //! this is used mainly from vertical panels in order to //! to get the maximum geometry that can be used from the dock //! based on their alignment type and the location dock QRect DockView::maximumNormalGeometry() { int xPos = 0; int yPos = 0; int maxHeight = maxLength() * screen()->geometry().height(); int maxWidth = normalThickness(); QRect maxGeometry; maxGeometry.setRect(0, 0, maxWidth, maxHeight); switch (location()) { case Plasma::Types::LeftEdge: xPos = screen()->geometry().x(); switch (alignment()) { case Latte::Dock::Top: yPos = screen()->geometry().y(); break; case Latte::Dock::Center: case Latte::Dock::Justify: yPos = qMax(screen()->geometry().center().y() - maxHeight / 2, screen()->geometry().y()); break; case Latte::Dock::Bottom: yPos = screen()->geometry().bottom() - maxHeight + 1; break; } maxGeometry.setRect(xPos, yPos, maxWidth, maxHeight); break; case Plasma::Types::RightEdge: xPos = screen()->geometry().right() - maxWidth + 1; switch (alignment()) { case Latte::Dock::Top: yPos = screen()->geometry().y(); break; case Latte::Dock::Center: case Latte::Dock::Justify: yPos = qMax(screen()->geometry().center().y() - maxHeight / 2, screen()->geometry().y()); break; case Latte::Dock::Bottom: yPos = screen()->geometry().bottom() - maxHeight + 1; break; } maxGeometry.setRect(xPos, yPos, maxWidth, maxHeight); break; default: //! bypass clang warnings break; } //! this is needed in order to preserve that the top dock will be above //! the others in case flag bypasswindowmanagerhint hasnt be set, //! such a case is the AlwaysVisible mode if (location() == Plasma::Types::TopEdge) { KWindowSystem::setState(winId(), NET::KeepAbove); } else { KWindowSystem::clearState(winId(), NET::KeepAbove); } return maxGeometry; } void DockView::resizeWindow(QRect availableScreenRect) { QSize screenSize = this->screen()->size(); QSize size = (formFactor() == Plasma::Types::Vertical) ? QSize(maxThickness(), availableScreenRect.height()) : QSize(screenSize.width(), maxThickness()); if (formFactor() == Plasma::Types::Vertical) { //qDebug() << "MAXIMUM RECT :: " << maximumRect << " - AVAILABLE RECT :: " << availableRect; if (m_behaveAsPlasmaPanel) { size.setWidth(normalThickness()); size.setHeight(static_cast(maxLength() * availableScreenRect.height())); } } else { if (m_behaveAsPlasmaPanel) { size.setWidth(static_cast(maxLength() * screenSize.width())); size.setHeight(normalThickness()); } } m_validGeometry.setSize(size); setMinimumSize(size); setMaximumSize(size); resize(size); if (formFactor() == Plasma::Types::Horizontal && corona()) { emit corona()->availableScreenRectChanged(); } } void DockView::validateDockGeometry() { if (geometry() != m_validGeometry) { m_validateGeometryTimer.start(); } } QRect DockView::localGeometry() const { return m_localGeometry; } void DockView::setLocalGeometry(const QRect &geometry) { if (m_localGeometry == geometry) { return; } m_localGeometry = geometry; emit localGeometryChanged(); updateAbsDockGeometry(); } void DockView::updateAbsDockGeometry(bool bypassChecks) { //! there was a -1 in height and width here. The reason of this //! if I remember correctly was related to multi-screen but I cant //! remember exactly the reason, something related to rigth edge in //! multi screen environment. BUT this was breaking the entire AlwaysVisible //! experience with struts. Removing them in order to restore correct //! behavior and keeping this comment in order to check for //! multi-screen breakage QRect absGeometry {x() + m_localGeometry.x(), y() + m_localGeometry.y() , m_localGeometry.width(), m_localGeometry.height()}; if (m_absGeometry == absGeometry && !bypassChecks) return; m_absGeometry = absGeometry; syncGeometry(); emit absGeometryChanged(m_absGeometry); //! this is needed in order to update correctly the screenGeometries if (visibility() && corona() && visibility()->mode() == Dock::AlwaysVisible) { emit corona()->availableScreenRectChanged(); emit corona()->availableScreenRegionChanged(); } } void DockView::updatePosition(QRect availableScreenRect) { QRect screenGeometry{availableScreenRect}; QPoint position; position = {0, 0}; const auto length = [&](int length) -> int { float offs = static_cast(offset()); return static_cast(length * ((1 - maxLength()) / 2) + length * (offs / 100)); }; int cleanThickness = normalThickness() - shadow(); switch (location()) { case Plasma::Types::TopEdge: if (m_behaveAsPlasmaPanel) { position = {screenGeometry.x() + length(screenGeometry.width()), screenGeometry.y()}; } else { position = {screenGeometry.x(), screenGeometry.y()}; } break; case Plasma::Types::BottomEdge: if (m_behaveAsPlasmaPanel) { position = {screenGeometry.x() + length(screenGeometry.width()), screenGeometry.y() + screenGeometry.height() - cleanThickness }; } else { position = {screenGeometry.x(), screenGeometry.y() + screenGeometry.height() - height()}; } break; case Plasma::Types::RightEdge: if (m_behaveAsPlasmaPanel && !mask().isNull()) { position = {availableScreenRect.right() - cleanThickness + 1, availableScreenRect.y() + length(availableScreenRect.height()) }; } else { position = {availableScreenRect.right() - width() + 1, availableScreenRect.y()}; } break; case Plasma::Types::LeftEdge: if (m_behaveAsPlasmaPanel && !mask().isNull()) { position = {availableScreenRect.x(), availableScreenRect.y() + length(availableScreenRect.height())}; } else { position = {availableScreenRect.x(), availableScreenRect.y()}; } break; default: qWarning() << "wrong location, couldn't update the panel position" << location(); } m_validGeometry.setTopLeft(position); setPosition(position); if (m_shellSurface) { m_shellSurface->setPosition(position); } } inline void DockView::syncGeometry() { if (!(this->screen() && this->containment()) || m_inDelete) return; bool found{false}; //! before updating the positioning and geometry of the dock //! we make sure that the dock is at the correct screen if (this->screen() != m_screenToFollow) { qDebug() << "Sync Geometry screens inconsistent!!!!"; m_screenSyncTimer.start(); } else { found = true; } //! if the dock isnt at the correct screen the calculations //! are not executed if (found) { //! compute the free screen rectangle for vertical panels only once //! this way the costly QRegion computations are calculated only once //! instead of two times (both inside the resizeWindow and the updatePosition) QRegion freeRegion;; QRect maximumRect; QRect availableScreenRect{this->screen()->geometry()}; if (formFactor() == Plasma::Types::Vertical) { QString layoutName = m_managedLayout ? m_managedLayout->name() : QString(); auto dockCorona = qobject_cast(corona()); int fixedScreen = onPrimary() ? dockCorona->screenPool()->primaryScreenId() : this->containment()->screen(); freeRegion = dockCorona->availableScreenRegionWithCriteria(fixedScreen, layoutName); maximumRect = maximumNormalGeometry(); QRegion availableRegion = freeRegion.intersected(maximumRect); availableScreenRect = freeRegion.intersected(maximumRect).boundingRect(); float area = 0; //! it is used to choose which or the availableRegion rectangles will //! be the one representing dock geometry for (int i = 0; i < availableRegion.rectCount(); ++i) { QRect rect = availableRegion.rects().at(i); //! the area of each rectangle in calculated in squares of 50x50 //! this is a way to avoid enourmous numbers for area value float tempArea = (float)(rect.width() * rect.height()) / 2500; if (tempArea > area) { availableScreenRect = rect; area = tempArea; } } if (availableRegion.rectCount() > 1 && m_behaveAsPlasmaPanel) m_forceDrawCenteredBorders = true; else m_forceDrawCenteredBorders = false; } else { m_forceDrawCenteredBorders = false; } updateEnabledBorders(); resizeWindow(availableScreenRect); updatePosition(availableScreenRect); } // qDebug() << "dock geometry:" << qRectToStr(geometry()); } void DockView::statusChanged(Plasma::Types::ItemStatus status) { if (containment()) { if (containment()->status() >= Plasma::Types::NeedsAttentionStatus && containment()->status() != Plasma::Types::HiddenStatus) { setBlockHiding(true); } else { setBlockHiding(false); } } } bool DockView::alternativesIsShown() const { return m_alternativesIsShown; } void DockView::setAlternativesIsShown(bool show) { if (m_alternativesIsShown == show) { return; } m_alternativesIsShown = show; setBlockHiding(show); emit alternativesIsShownChanged(); } bool DockView::contextMenuIsShown() const { if (!m_menuManager) { return false; } return m_menuManager->contextMenu(); } int DockView::currentThickness() const { if (formFactor() == Plasma::Types::Vertical) { return m_maskArea.isNull() ? width() : m_maskArea.width() - m_shadow; } else { return m_maskArea.isNull() ? height() : m_maskArea.height() - m_shadow; } } int DockView::normalThickness() const { return m_normalThickness; } void DockView::setNormalThickness(int thickness) { if (m_normalThickness == thickness) { return; } m_normalThickness = thickness; emit normalThicknessChanged(); } int DockView::docksCount() const { if (!m_managedLayout) { return 0; } return m_managedLayout->docksCount(screen()); } int DockView::totalDocksCount() const { if (!m_managedLayout) { return 0; } return m_managedLayout->docksCount(); } int DockView::docksWithTasks() { if (!m_managedLayout) return 0; return m_managedLayout->noDocksWithTasks(); } void DockView::updateFormFactor() { if (!this->containment()) return; switch (location()) { case Plasma::Types::TopEdge: case Plasma::Types::BottomEdge: this->containment()->setFormFactor(Plasma::Types::Horizontal); break; case Plasma::Types::LeftEdge: case Plasma::Types::RightEdge: this->containment()->setFormFactor(Plasma::Types::Vertical); break; default: qWarning() << "wrong location, couldn't update the panel position" << location(); } } bool DockView::dockWinBehavior() const { return m_dockWinBehavior; } void DockView::setDockWinBehavior(bool dock) { if (m_dockWinBehavior == dock) { return; } m_dockWinBehavior = dock; emit dockWinBehaviorChanged(); } bool DockView::behaveAsPlasmaPanel() const { return m_behaveAsPlasmaPanel; } void DockView::setBehaveAsPlasmaPanel(bool behavior) { if (m_behaveAsPlasmaPanel == behavior) { return; } m_behaveAsPlasmaPanel = behavior; if (m_behaveAsPlasmaPanel && m_drawShadows) { PanelShadows::self()->addWindow(this, enabledBorders()); } else { PanelShadows::self()->removeWindow(this); // m_enabledBorders = Plasma::FrameSvg::AllBorders; // emit enabledBordersChanged(); } updateEffects(); emit behaveAsPlasmaPanelChanged(); } bool DockView::drawShadows() const { return m_drawShadows; } void DockView::setDrawShadows(bool draw) { if (m_drawShadows == draw) { return; } m_drawShadows = draw; if (m_behaveAsPlasmaPanel && m_drawShadows) { PanelShadows::self()->addWindow(this, enabledBorders()); } else { PanelShadows::self()->removeWindow(this); //m_enabledBorders = Plasma::FrameSvg::AllBorders; //emit enabledBordersChanged(); } emit drawShadowsChanged(); } bool DockView::drawEffects() const { return m_drawEffects; } void DockView::setDrawEffects(bool draw) { if (m_drawEffects == draw) { return; } m_drawEffects = draw; emit drawEffectsChanged(); } bool DockView::inEditMode() const { return m_inEditMode; } void DockView::setInEditMode(bool edit) { if (m_inEditMode == edit) { return; } m_inEditMode = edit; emit inEditModeChanged(); } bool DockView::onPrimary() const { return m_onPrimary; } void DockView::setOnPrimary(bool flag) { if (m_onPrimary == flag) { return; } m_onPrimary = flag; emit onPrimaryChanged(); } float DockView::maxLength() const { return m_maxLength; } void DockView::setMaxLength(float length) { if (m_maxLength == length) { return; } m_maxLength = length; emit maxLengthChanged(); } int DockView::maxThickness() const { return m_maxThickness; } void DockView::setMaxThickness(int thickness) { if (m_maxThickness == thickness) return; m_maxThickness = thickness; syncGeometry(); emit maxThicknessChanged(); } int DockView::alignment() const { return m_alignment; } void DockView::setAlignment(int alignment) { Dock::Alignment align = static_cast(alignment); if (m_alignment == alignment) { return; } m_alignment = align; emit alignmentChanged(); } bool DockView::blockAnimations() const { return m_blockAnimations; } void DockView::setBlockAnimations(bool block) { if (m_blockAnimations == block) { return; } m_blockAnimations = block; emit blockAnimationsChanged(); } bool DockView::colorizerSupport() const { return m_colorizerSupport; } void DockView::setColorizerSupport(bool support) { if (m_colorizerSupport == support) { return; } m_colorizerSupport = support; emit colorizerSupportChanged(); } QRect DockView::maskArea() const { return m_maskArea; } void DockView::setMaskArea(QRect area) { if (m_maskArea == area) return; m_maskArea = area; if (KWindowSystem::compositingActive()) { if (m_behaveAsPlasmaPanel) { setMask(QRect()); } else { setMask(m_maskArea); } } else { //! this is used when compositing is disabled and provides //! the correct way for the mask to be painted in order for //! rounded corners to be shown correctly //! the enabledBorders check was added because there was cases //! that the mask region wasnt calculated correctly after location changes if (!m_background || m_background->enabledBorders() != enabledBorders()) { m_background = new Plasma::FrameSvg(this); } if (m_background->imagePath() != "opaque/dialogs/background") { m_background->setImagePath(QStringLiteral("opaque/dialogs/background")); } m_background->setEnabledBorders(enabledBorders()); m_background->resizeFrame(area.size()); QRegion fixedMask = m_background->mask(); fixedMask.translate(m_maskArea.x(), m_maskArea.y()); //! fix for KF5.32 that return empty QRegion's for the mask if (fixedMask.isEmpty()) { fixedMask = QRegion(m_maskArea); } setMask(fixedMask); } // qDebug() << "dock mask set:" << m_maskArea; emit maskAreaChanged(); } QRect DockView::effectsArea() const { return m_effectsArea; } void DockView::setEffectsArea(QRect area) { QRect inWindowRect = area.intersected(QRect(0, 0, width(), height())); if (m_effectsArea == inWindowRect) { return; } m_effectsArea = inWindowRect; emit effectsAreaChanged(); } QRect DockView::absGeometry() const { return m_absGeometry; } QRect DockView::screenGeometry() const { if (this->screen()) { QRect geom = this->screen()->geometry(); return geom; } return QRect(); } int DockView::offset() const { return m_offset; } void DockView::setOffset(int offset) { if (m_offset == offset) { return; } m_offset = offset; emit offsetChanged(); } int DockView::dockTransparency() const { return m_dockTransparency; } void DockView::setDockTransparency(int transparency) { if (m_dockTransparency == transparency) { return; } m_dockTransparency = transparency; emit dockTransparencyChanged(); } int DockView::shadow() const { return m_shadow; } void DockView::setShadow(int shadow) { if (m_shadow == shadow) return; m_shadow = shadow; if (m_behaveAsPlasmaPanel) { syncGeometry(); } emit shadowChanged(); } int DockView::fontPixelSize() const { return m_fontPixelSize; } void DockView::setFontPixelSize(int size) { if (m_fontPixelSize == size) { return; } m_fontPixelSize = size; emit fontPixelSizeChanged(); } void DockView::applyActivitiesToWindows() { if (m_visibility) { QStringList activities = m_managedLayout->appliedActivities(); m_visibility->setWindowOnActivities(*this, activities); if (m_configView) { m_visibility->setWindowOnActivities(*m_configView, activities); auto configView = qobject_cast(m_configView); if (configView && configView->secondaryWindow()) { m_visibility->setWindowOnActivities(*configView->secondaryWindow(), activities); } } if (m_visibility->supportsKWinEdges()) { m_visibility->applyActivitiesToHiddenWindows(activities); } } } Layout *DockView::managedLayout() const { return m_managedLayout; } void DockView::setManagedLayout(Layout *layout) { if (m_managedLayout == layout) { return; } // clear mode for (auto &c : connectionsManagedLayout) { disconnect(c); } m_managedLayout = layout; if (m_managedLayout) { //! Sometimes the activity isnt completely ready, by adding a delay //! we try to catch up QTimer::singleShot(100, [this]() { - if (m_managedLayout) { + if (m_managedLayout && m_visibility) { qDebug() << "DOCK VIEW FROM LAYOUT ::: " << m_managedLayout->name() << " - activities: " << m_managedLayout->appliedActivities(); applyActivitiesToWindows(); emit activitiesChanged(); } }); } DockCorona *dockCorona = qobject_cast(this->corona()); if (dockCorona->layoutManager()->memoryUsage() == Dock::MultipleLayouts) { connectionsManagedLayout[0] = connect(dockCorona->activitiesConsumer(), &KActivities::Consumer::runningActivitiesChanged, this, [&]() { - if (m_managedLayout) { + if (m_managedLayout && m_visibility) { qDebug() << "DOCK VIEW FROM LAYOUT (runningActivitiesChanged) ::: " << m_managedLayout->name() << " - activities: " << m_managedLayout->appliedActivities(); applyActivitiesToWindows(); emit activitiesChanged(); } }); connectionsManagedLayout[1] = connect(m_managedLayout, &Layout::activitiesChanged, this, [&]() { if (m_managedLayout) { applyActivitiesToWindows(); emit activitiesChanged(); } }); connectionsManagedLayout[2] = connect(dockCorona->layoutManager(), &LayoutManager::layoutsChanged, this, [&]() { if (m_managedLayout) { applyActivitiesToWindows(); emit activitiesChanged(); } }); //!IMPORTANT!!! ::: This fixes a bug when closing an Activity all docks from all Activities are //! disappearing! With this they reappear!!! connectionsManagedLayout[3] = connect(this, &QWindow::visibleChanged, this, [&]() { if (!isVisible() && m_managedLayout) { QTimer::singleShot(100, [this]() { if (m_managedLayout && containment() && !containment()->destroyed()) { setVisible(true); applyActivitiesToWindows(); emit activitiesChanged(); } }); QTimer::singleShot(1500, [this]() { if (m_managedLayout && containment() && !containment()->destroyed()) { setVisible(true); applyActivitiesToWindows(); emit activitiesChanged(); } }); } }); } emit managedLayoutChanged(); } void DockView::moveToLayout(QString layoutName) { if (!m_managedLayout) { return; } QList containments = m_managedLayout->unassignFromLayout(this); DockCorona *dockCorona = qobject_cast(this->corona()); if (dockCorona && containments.size() > 0) { Layout *newLayout = dockCorona->layoutManager()->activeLayout(layoutName); if (newLayout) { newLayout->assignToLayout(this, containments); } } } bool DockView::inLocationChangeAnimation() { return ((m_goToLocation != Plasma::Types::Floating) || (m_moveToLayout != "") || m_goToScreen); } void DockView::hideDockDuringLocationChange(int goToLocation) { m_goToLocation = static_cast(goToLocation); emit hideDockDuringLocationChangeStarted(); } void DockView::hideDockDuringMovingToLayout(QString layoutName) { m_moveToLayout = layoutName; emit hideDockDuringMovingToLayoutStarted(); } void DockView::setBlockHiding(bool block) { if (!block) { auto *configView = qobject_cast(m_configView); if (m_alternativesIsShown || (configView && configView->sticker() && configView->isVisible())) { return; } if (m_visibility) { m_visibility->setBlockHiding(false); } } else { if (m_visibility) { m_visibility->setBlockHiding(true); } } } void DockView::updateEffects() { //! Dont apply any effect before the wayland surface is created under wayland //! https://bugs.kde.org/show_bug.cgi?id=392890 if (KWindowSystem::isPlatformWayland() && !m_shellSurface) { return; } if (!m_behaveAsPlasmaPanel) { if (m_drawEffects && !m_effectsArea.isNull() && !m_effectsArea.isEmpty()) { //! this is used when compositing is disabled and provides //! the correct way for the mask to be painted in order for //! rounded corners to be shown correctly if (!m_background) { m_background = new Plasma::FrameSvg(this); } if (m_background->imagePath() != "widgets/panel-background") { m_background->setImagePath(QStringLiteral("widgets/panel-background")); } m_background->setEnabledBorders(enabledBorders()); m_background->resizeFrame(m_effectsArea.size()); QRegion fixedMask = m_background->mask(); fixedMask.translate(m_effectsArea.x(), m_effectsArea.y()); //! fix1, for KF5.32 that return empty QRegion's for the mask if (fixedMask.isEmpty()) { fixedMask = QRegion(m_effectsArea); } KWindowEffects::enableBlurBehind(winId(), true, fixedMask); bool drawBackgroundEffect = m_theme.backgroundContrastEnabled() && (m_dockTransparency == 100); //based on Breeze Dark theme behavior the enableBackgroundContrast even though it does accept //a QRegion it uses only the first rect. The bug was that for Breeze Dark there was a line //at the dock bottom that was distinguishing it from other themes KWindowEffects::enableBackgroundContrast(winId(), drawBackgroundEffect, m_theme.backgroundContrast(), m_theme.backgroundIntensity(), m_theme.backgroundSaturation(), fixedMask.boundingRect()); } else { KWindowEffects::enableBlurBehind(winId(), false); KWindowEffects::enableBackgroundContrast(winId(), false); } } else if (m_behaveAsPlasmaPanel && m_drawEffects) { KWindowEffects::enableBlurBehind(winId(), true); KWindowEffects::enableBackgroundContrast(winId(), m_theme.backgroundContrastEnabled(), m_theme.backgroundContrast(), m_theme.backgroundIntensity(), m_theme.backgroundSaturation()); } else { KWindowEffects::enableBlurBehind(winId(), false); KWindowEffects::enableBackgroundContrast(winId(), false); } } //! remove latte tasks plasmoid void DockView::removeTasksPlasmoid() { if (!tasksPresent() || !containment()) { return; } foreach (Plasma::Applet *applet, containment()->applets()) { KPluginMetaData meta = applet->kPackage().metadata(); if (meta.pluginId() == "org.kde.latte.plasmoid") { QAction *closeApplet = applet->actions()->action(QStringLiteral("remove")); if (closeApplet) { closeApplet->trigger(); //! remove only the first found return; } } } } //! check if the tasks plasmoid exist in the dock bool DockView::tasksPresent() { if (!this->containment()) { return false; } foreach (Plasma::Applet *applet, this->containment()->applets()) { const auto &provides = KPluginMetaData::readStringList(applet->pluginMetaData().rawData(), QStringLiteral("X-Plasma-Provides")); if (provides.contains(QLatin1String("org.kde.plasma.multitasking"))) { return true; } } return false; } //! check if the tasks plasmoid exist in the dock bool DockView::latteTasksPresent() { if (!this->containment()) { return false; } foreach (Plasma::Applet *applet, this->containment()->applets()) { KPluginMetaData metadata = applet->pluginMetaData(); if (metadata.pluginId() == "org.kde.latte.plasmoid") { return true; } } return false; } //!check if the plasmoid with _name_ exists in the midedata bool DockView::mimeContainsPlasmoid(QMimeData *mimeData, QString name) { if (!mimeData) { return false; } if (mimeData->hasFormat(QStringLiteral("text/x-plasmoidservicename"))) { QString data = mimeData->data(QStringLiteral("text/x-plasmoidservicename")); const QStringList appletNames = data.split('\n', QString::SkipEmptyParts); foreach (const QString &appletName, appletNames) { if (appletName == name) return true; } } return false; } VisibilityManager *DockView::visibility() const { return m_visibility; } bool DockView::event(QEvent *e) { if (!m_inDelete) { emit eventTriggered(e); switch (e->type()) { case QEvent::Leave: engine()->trimComponentCache(); break; case QEvent::PlatformSurface: if (auto pe = dynamic_cast(e)) { switch (pe->surfaceEventType()) { case QPlatformSurfaceEvent::SurfaceCreated: setupWaylandIntegration(); if (m_shellSurface) { syncGeometry(); if (m_drawShadows) { PanelShadows::self()->addWindow(this, enabledBorders()); } } break; case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed: if (m_shellSurface) { delete m_shellSurface; m_shellSurface = nullptr; qDebug() << "WAYLAND dock window surface was deleted..."; PanelShadows::self()->removeWindow(this); } break; } } break; default: break; } } return ContainmentView::event(e);; } QList DockView::freeEdges() const { if (!m_managedLayout) { const QList emptyEdges; return emptyEdges; } const auto edges = m_managedLayout->freeEdges(screen()); QList edgesInt; foreach (Plasma::Types::Location edge, edges) { edgesInt.append(static_cast(edge)); } return edgesInt; } void DockView::deactivateApplets() { if (!containment()) { return; } foreach (auto applet, containment()->applets()) { PlasmaQuick::AppletQuickItem *ai = applet->property("_plasma_graphicObject").value(); if (ai) { ai->setExpanded(false); } } } void DockView::toggleAppletExpanded(const int id) { if (!containment()) { return; } foreach (auto applet, containment()->applets()) { if (applet->id() == id) { PlasmaQuick::AppletQuickItem *ai = applet->property("_plasma_graphicObject").value(); if (ai) { if (!ai->isActivationTogglesExpanded()) { ai->setActivationTogglesExpanded(true); } emit applet->activated(); } } } } QVariantList DockView::containmentActions() { QVariantList actions; /*if (containment()->corona()->immutability() != Plasma::Types::Mutable) { return actions; }*/ //FIXME: the trigger string it should be better to be supported this way //const QString trigger = Plasma::ContainmentActions::eventToString(event); const QString trigger = "RightButton;NoModifier"; Plasma::ContainmentActions *plugin = this->containment()->containmentActions().value(trigger); if (!plugin) { return actions; } if (plugin->containment() != this->containment()) { plugin->setContainment(this->containment()); // now configure it KConfigGroup cfg(this->containment()->corona()->config(), "ActionPlugins"); cfg = KConfigGroup(&cfg, QString::number(this->containment()->containmentType())); KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); plugin->restore(pluginConfig); } foreach (QAction *ac, plugin->contextualActions()) { actions << QVariant::fromValue(ac); } return actions; } //!BEGIN overriding context menus behavior void DockView::mousePressEvent(QMouseEvent *event) { bool result = m_menuManager->mousePressEvent(event); emit contextMenuIsShownChanged(); if (result) { PlasmaQuick::ContainmentView::mousePressEvent(event); } } //!END overriding context menus behavior //!BEGIN draw panel shadows outside the dock window Plasma::FrameSvg::EnabledBorders DockView::enabledBorders() const { return m_enabledBorders; } void DockView::updateEnabledBorders() { if (!this->screen()) { return; } Plasma::FrameSvg::EnabledBorders borders = Plasma::FrameSvg::AllBorders; switch (location()) { case Plasma::Types::TopEdge: borders &= ~Plasma::FrameSvg::TopBorder; break; case Plasma::Types::LeftEdge: borders &= ~Plasma::FrameSvg::LeftBorder; break; case Plasma::Types::RightEdge: borders &= ~Plasma::FrameSvg::RightBorder; break; case Plasma::Types::BottomEdge: borders &= ~Plasma::FrameSvg::BottomBorder; break; default: break; } if ((location() == Plasma::Types::LeftEdge || location() == Plasma::Types::RightEdge)) { if (maxLength() == 1 && m_alignment == Dock::Justify && !m_forceDrawCenteredBorders) { borders &= ~Plasma::FrameSvg::TopBorder; borders &= ~Plasma::FrameSvg::BottomBorder; } if (m_alignment == Dock::Top && !m_forceDrawCenteredBorders && m_offset == 0) { borders &= ~Plasma::FrameSvg::TopBorder; } if (m_alignment == Dock::Bottom && !m_forceDrawCenteredBorders && m_offset == 0) { borders &= ~Plasma::FrameSvg::BottomBorder; } } if (location() == Plasma::Types::TopEdge || location() == Plasma::Types::BottomEdge) { if (maxLength() == 1 && m_alignment == Dock::Justify) { borders &= ~Plasma::FrameSvg::LeftBorder; borders &= ~Plasma::FrameSvg::RightBorder; } if (m_alignment == Dock::Left && m_offset == 0) { borders &= ~Plasma::FrameSvg::LeftBorder; } if (m_alignment == Dock::Right && m_offset == 0) { borders &= ~Plasma::FrameSvg::RightBorder; } } if (m_enabledBorders != borders) { m_enabledBorders = borders; emit enabledBordersChanged(); } if (!m_behaveAsPlasmaPanel || !m_drawShadows) { PanelShadows::self()->removeWindow(this); } else { PanelShadows::self()->setEnabledBorders(this, borders); } } //!END draw panel shadows outside the dock window //!BEGIN configuration functions void DockView::saveConfig() { if (!this->containment()) return; auto config = this->containment()->config(); config.writeEntry("onPrimary", m_onPrimary); config.writeEntry("dockWindowBehavior", m_dockWinBehavior); config.sync(); } void DockView::restoreConfig() { if (!this->containment()) return; auto config = this->containment()->config(); setOnPrimary(config.readEntry("onPrimary", true)); setDockWinBehavior(config.readEntry("dockWindowBehavior", true)); } //!END configuration functions } //!END namespace diff --git a/app/layout.cpp b/app/layout.cpp index 4fcdbaec..58d18d6e 100644 --- a/app/layout.cpp +++ b/app/layout.cpp @@ -1,1748 +1,1890 @@ /* * Copyright 2017 Smith AR * Michail Vourlakos * * 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 . */ #include "layout.h" #include "dockcorona.h" #include "importer.h" #include "layoutmanager.h" #include "screenpool.h" #include "universalsettings.h" #include "dock/dockview.h" #include #include #include #include #include #include #include #include namespace Latte { const QString Layout::MultipleLayoutsName = ".multiple-layouts_hidden"; Layout::Layout(QObject *parent, QString layoutFile, QString assignedName) : QObject(parent) { qDebug() << "Layout file to create object: " << layoutFile << " with name: " << assignedName; if (QFile(layoutFile).exists()) { if (assignedName.isEmpty()) { assignedName = layoutName(layoutFile); } //!this order is important because setFile initializes also the m_layoutGroup setFile(layoutFile); setName(assignedName); loadConfig(); init(); } } Layout::~Layout() { if (!m_layoutFile.isEmpty()) { m_layoutGroup.sync(); } } void Layout::syncToLayoutFile(bool removeLayoutId) { if (!m_corona || !isWritable()) { return; } KSharedConfigPtr filePtr = KSharedConfig::openConfig(m_layoutFile); KConfigGroup oldContainments = KConfigGroup(filePtr, "Containments"); oldContainments.deleteGroup(); oldContainments.sync(); qDebug() << " LAYOUT :: " << m_layoutName << " is syncing its original file."; foreach (auto containment, m_containments) { if (removeLayoutId) { containment->config().writeEntry("layoutId", ""); } KConfigGroup newGroup = oldContainments.group(QString::number(containment->id())); containment->config().copyTo(&newGroup); if (!removeLayoutId) { newGroup.writeEntry("layoutId", ""); newGroup.sync(); } } oldContainments.sync(); } void Layout::unloadContainments() { if (!m_corona) { return; } qDebug() << "Layout - " + name() + " unload: containments ... size ::: " << m_containments.size() << " ,dockViews in memory ::: " << m_dockViews.size() << " ,hidden dockViews in memory ::: " << m_waitingDockViews.size(); foreach (auto view, m_dockViews) { view->disconnectSensitiveSignals(); } foreach (auto view, m_waitingDockViews) { view->disconnectSensitiveSignals(); } m_unloadedContainmentsIds.clear(); QList systrays; //!identify systrays and unload them first foreach (auto containment, m_containments) { if (Plasma::Applet *parentApplet = qobject_cast(containment->parent())) { systrays.append(containment); } } while (!systrays.isEmpty()) { Plasma::Containment *systray = systrays.at(0); m_unloadedContainmentsIds << QString::number(systray->id()); systrays.removeFirst(); m_containments.removeAll(systray); delete systray; } while (!m_containments.isEmpty()) { Plasma::Containment *containment = m_containments.at(0); m_unloadedContainmentsIds << QString::number(containment->id()); m_containments.removeFirst(); delete containment; } } void Layout::unloadDockViews() { if (!m_corona) { return; } qDebug() << "Layout - " + name() + " unload: dockViews ... size: " << m_dockViews.size(); qDeleteAll(m_dockViews); qDeleteAll(m_waitingDockViews); m_dockViews.clear(); m_waitingDockViews.clear(); } void Layout::init() { connect(this, &Layout::activitiesChanged, this, &Layout::saveConfig); connect(this, &Layout::backgroundChanged, this, &Layout::saveConfig); connect(this, &Layout::versionChanged, this, &Layout::saveConfig); connect(this, &Layout::colorChanged, this, &Layout::textColorChanged); connect(this, &Layout::disableBordersForMaximizedWindowsChanged, this, &Layout::saveConfig); connect(this, &Layout::showInMenuChanged, this, &Layout::saveConfig); connect(this, &Layout::textColorChanged, this, &Layout::saveConfig); connect(this, &Layout::launchersChanged, this, &Layout::saveConfig); connect(this, &Layout::lastUsedActivityChanged, this, &Layout::saveConfig); } void Layout::initToCorona(DockCorona *corona) { m_corona = corona; connect(this, &Layout::dockColorizerSupportChanged, m_corona->layoutManager(), &LayoutManager::updateColorizerSupport); foreach (auto containment, m_corona->containments()) { if (m_corona->layoutManager()->memoryUsage() == Dock::SingleLayout) { addContainment(containment); } else if (m_corona->layoutManager()->memoryUsage() == Dock::MultipleLayouts) { QString layoutId = containment->config().readEntry("layoutId", QString()); if (!layoutId.isEmpty() && (layoutId == m_layoutName)) { addContainment(containment); } } } qDebug() << "Layout ::::: " << name() << " added contaiments ::: " << m_containments.size(); connect(m_corona->universalSettings(), &UniversalSettings::canDisableBordersChanged, this, [&]() { if (m_corona->universalSettings()->canDisableBorders()) { kwin_setDisabledMaximizedBorders(disableBordersForMaximizedWindows()); } else { kwin_setDisabledMaximizedBorders(false); } }); if (m_corona->layoutManager()->memoryUsage() == Dock::SingleLayout) { kwin_setDisabledMaximizedBorders(disableBordersForMaximizedWindows()); } else if (m_corona->layoutManager()->memoryUsage() == Dock::MultipleLayouts) { connect(m_corona->layoutManager(), &LayoutManager::currentLayoutNameChanged, this, [&]() { if (m_corona->universalSettings()->canDisableBorders() && m_corona->layoutManager()->currentLayoutName() == name()) { kwin_setDisabledMaximizedBorders(disableBordersForMaximizedWindows()); } }); } if (m_layoutName != MultipleLayoutsName) { updateLastUsedActivity(); } connect(m_corona, &Plasma::Corona::containmentAdded, this, &Layout::addContainment); connect(m_corona->m_activityConsumer, &KActivities::Consumer::currentActivityChanged, this, &Layout::updateLastUsedActivity); } int Layout::version() const { return m_version; } void Layout::setVersion(int ver) { if (m_version == ver) { return; } m_version = ver; emit versionChanged(); } bool Layout::disableBordersForMaximizedWindows() const { return m_disableBordersForMaximizedWindows; } void Layout::setDisableBordersForMaximizedWindows(bool disable) { if (m_disableBordersForMaximizedWindows == disable) { return; } m_disableBordersForMaximizedWindows = disable; kwin_setDisabledMaximizedBorders(disable); emit disableBordersForMaximizedWindowsChanged(); } bool Layout::kwin_disabledMaximizedBorders() const { //! Indentify Plasma Desktop version QProcess process; process.start("kreadconfig5 --file kwinrc --group Windows --key BorderlessMaximizedWindows"); process.waitForFinished(); QString output(process.readAllStandardOutput()); output = output.remove("\n"); return (output == "true"); } void Layout::kwin_setDisabledMaximizedBorders(bool disable) { if (kwin_disabledMaximizedBorders() == disable) { return; } QString disableText = disable ? "true" : "false"; QProcess process; QString commandStr = "kwriteconfig5 --file kwinrc --group Windows --key BorderlessMaximizedWindows --type bool " + disableText; process.start(commandStr); process.waitForFinished(); QDBusInterface iface("org.kde.KWin", "/KWin", "", QDBusConnection::sessionBus()); if (iface.isValid()) { iface.call("reconfigure"); } } bool Layout::showInMenu() const { return m_showInMenu; } void Layout::setShowInMenu(bool show) { if (m_showInMenu == show) { return; } m_showInMenu = show; emit showInMenuChanged(); } bool Layout::isWritable() const { QFileInfo layoutFileInfo(m_layoutFile); if (layoutFileInfo.exists() && !layoutFileInfo.isWritable()) { return false; } else { return true; } } void Layout::lock() { QFileInfo layoutFileInfo(m_layoutFile); if (layoutFileInfo.exists() && layoutFileInfo.isWritable()) { QFile(m_layoutFile).setPermissions(QFileDevice::ReadUser | QFileDevice::ReadGroup | QFileDevice::ReadOther); } } void Layout::unlock() { QFileInfo layoutFileInfo(m_layoutFile); if (layoutFileInfo.exists() && !layoutFileInfo.isWritable()) { QFile(m_layoutFile).setPermissions(QFileDevice::ReadUser | QFileDevice::WriteUser | QFileDevice::ReadGroup | QFileDevice::ReadOther); } } QString Layout::background() const { return m_background; } void Layout::setBackground(QString path) { if (path == m_background) { return; } if (!path.isEmpty() && !QFileInfo(path).exists()) { return; } m_background = path; //! initialize the text color also if (path.isEmpty()) { setTextColor(QString()); } emit backgroundChanged(); } QString Layout::name() const { return m_layoutName; } void Layout::setName(QString name) { if (m_layoutName == name) { return; } qDebug() << "Layout name:" << name; m_layoutName = name; emit nameChanged(); } void Layout::renameLayout(QString newName) { if (m_layoutFile != Importer::layoutFilePath(newName)) { setFile(Importer::layoutFilePath(newName)); } if (m_layoutName != newName) { setName(newName); } //! thus this is a linked file if (m_corona) { foreach (auto containment, m_containments) { containment->config().writeEntry("layoutId", m_layoutName); } } } QString Layout::color() const { return m_color; } void Layout::setColor(QString color) { if (m_color == color) { return; } m_color = color; emit colorChanged(); } QString Layout::textColor() const { //! the user is in default layout theme if (m_background.isEmpty()) { if (m_color == "blue") { return "#D7E3FF"; } else if (m_color == "brown") { return "#F1DECB"; } else if (m_color == "darkgrey") { return "#ECECEC"; } else if (m_color == "gold") { return "#7C3636"; } else if (m_color == "green") { return "#4D7549"; } else if (m_color == "lightskyblue") { return "#0C2A43"; } else if (m_color == "orange") { return "#6F3902"; } else if (m_color == "pink") { return "#743C46"; } else if (m_color == "purple") { return "#ECD9FF"; } else if (m_color == "red") { return "#F3E4E4"; } else if (m_color == "wheat") { return "#6A4E25"; } else { return "#FCFCFC"; } } return "#" + m_textColor; } void Layout::setTextColor(QString color) { //! remove # if someone is trying to set it this way if (color.startsWith("#")) { color.remove(0, 1); } if (m_textColor == color) { return; } m_textColor = color; emit textColorChanged(); } QString Layout::file() const { return m_layoutFile; } void Layout::setFile(QString file) { if (m_layoutFile == file) { return; } qDebug() << "Layout file:" << file; m_layoutFile = file; KSharedConfigPtr filePtr = KSharedConfig::openConfig(m_layoutFile); m_layoutGroup = KConfigGroup(filePtr, "LayoutSettings"); emit fileChanged(); } QStringList Layout::launchers() const { return m_launchers; } void Layout::setLaunchers(QStringList launcherList) { if (m_launchers == launcherList) return; m_launchers = launcherList; emit launchersChanged(); } QStringList Layout::activities() const { return m_activities; } void Layout::setActivities(QStringList activities) { if (m_activities == activities) { return; } m_activities = activities; emit activitiesChanged(); } QStringList Layout::unloadedContainmentsIds() { return m_unloadedContainmentsIds; } bool Layout::isActiveLayout() const { if (!m_corona) { return false; } Layout *activeLayout = m_corona->layoutManager()->activeLayout(m_layoutName); if (activeLayout) { return true; } else { return false; } } bool Layout::isOriginalLayout() const { return m_layoutName != MultipleLayoutsName; } bool Layout::layoutIsBroken() const { if (m_layoutFile.isEmpty() || !QFile(m_layoutFile).exists()) { return false; } QStringList ids; QStringList conts; QStringList applets; KSharedConfigPtr lFile = KSharedConfig::openConfig(m_layoutFile); if (!m_corona) { KConfigGroup containmentsEntries = KConfigGroup(lFile, "Containments"); ids << containmentsEntries.groupList(); conts << ids; foreach (auto cId, containmentsEntries.groupList()) { auto appletsEntries = containmentsEntries.group(cId).group("Applets"); ids << appletsEntries.groupList(); applets << appletsEntries.groupList(); } } else { foreach (auto containment, m_containments) { ids << QString::number(containment->id()); conts << ids; foreach (auto applet, containment->applets()) { ids << QString::number(applet->id()); applets << QString::number(applet->id()); } } } QSet idsSet = QSet::fromList(ids); /* a different way to count duplicates QMap countOfStrings; for (int i = 0; i < ids.count(); i++) { countOfStrings[ids[i]]++; }*/ if (idsSet.count() != ids.count()) { qDebug() << " ---- ERROR - BROKEN LAYOUT :: " << m_layoutName << " ----"; if (!m_corona) { qDebug() << " ---- file : " << m_layoutFile; } else { qDebug() << " ---- in multiple layouts hidden file : " << Importer::layoutFilePath(Layout::MultipleLayoutsName); } qDebug() << "Contaiments :: " << conts; qDebug() << "Applets :: " << applets; foreach (QString c, conts) { if (applets.contains(c)) { qDebug() << "Error: Same applet and containment id found ::: " << c; } } for (int i = 0; i < ids.count(); ++i) { for (int j = i + 1; j < ids.count(); ++j) { if (ids[i] == ids[j]) { qDebug() << "Error: Applets with same id ::: " << ids[i]; } } } qDebug() << " -- - -- - -- - -- - - -- - - - - -- - - - - "; if (!m_corona) { KConfigGroup containmentsEntries = KConfigGroup(lFile, "Containments"); foreach (auto cId, containmentsEntries.groupList()) { auto appletsEntries = containmentsEntries.group(cId).group("Applets"); qDebug() << " CONTAINMENT : " << cId << " APPLETS : " << appletsEntries.groupList(); } } else { foreach (auto containment, m_containments) { QStringList appletsIds; foreach (auto applet, containment->applets()) { appletsIds << QString::number(applet->id()); } qDebug() << " CONTAINMENT : " << containment->id() << " APPLETS : " << appletsIds.join(","); } } return true; } return false; } QString Layout::layoutName(const QString &fileName) { int lastSlash = fileName.lastIndexOf("/"); QString tempLayoutFile = fileName; QString layoutName = tempLayoutFile.remove(0, lastSlash + 1); int ext = layoutName.lastIndexOf(".layout.latte"); layoutName = layoutName.remove(ext, 13); return layoutName; } void Layout::loadConfig() { m_version = m_layoutGroup.readEntry("version", 2); m_color = m_layoutGroup.readEntry("color", QString("blue")); m_disableBordersForMaximizedWindows = m_layoutGroup.readEntry("disableBordersForMaximizedWindows", false); m_showInMenu = m_layoutGroup.readEntry("showInMenu", false); m_textColor = m_layoutGroup.readEntry("textColor", QString("fcfcfc")); m_activities = m_layoutGroup.readEntry("activities", QStringList()); m_launchers = m_layoutGroup.readEntry("launchers", QStringList()); m_lastUsedActivity = m_layoutGroup.readEntry("lastUsedActivity", QString()); QString back = m_layoutGroup.readEntry("background", ""); if (!back.isEmpty()) { if (QFileInfo(back).exists()) { m_background = back; } else { m_layoutGroup.writeEntry("background", QString()); } } emit activitiesChanged(); } void Layout::saveConfig() { qDebug() << "layout is saving... for layout:" << m_layoutName; m_layoutGroup.writeEntry("version", m_version); m_layoutGroup.writeEntry("showInMenu", m_showInMenu); m_layoutGroup.writeEntry("color", m_color); m_layoutGroup.writeEntry("disableBordersForMaximizedWindows", m_disableBordersForMaximizedWindows); m_layoutGroup.writeEntry("launchers", m_launchers); m_layoutGroup.writeEntry("background", m_background); m_layoutGroup.writeEntry("activities", m_activities); m_layoutGroup.writeEntry("lastUsedActivity", m_lastUsedActivity); m_layoutGroup.writeEntry("textColor", m_textColor); m_layoutGroup.sync(); } //! Containments Actions void Layout::addContainment(Plasma::Containment *containment) { if (!containment || m_containments.contains(containment)) { return; } bool containmentInLayout{false}; if (m_corona->layoutManager()->memoryUsage() == Dock::SingleLayout) { m_containments.append(containment); containmentInLayout = true; } else if (m_corona->layoutManager()->memoryUsage() == Dock::MultipleLayouts) { QString layoutId = containment->config().readEntry("layoutId", QString()); if (!layoutId.isEmpty() && (layoutId == m_layoutName)) { m_containments.append(containment); containmentInLayout = true; } } if (containmentInLayout) { addDock(containment); connect(containment, &QObject::destroyed, this, &Layout::containmentDestroyed); } } QHash *Layout::dockViews() { return &m_dockViews; } QList *Layout::containments() { return &m_containments; } const QStringList Layout::appliedActivities() { if (!m_corona) { return {}; } if (m_corona->layoutManager()->memoryUsage() == Dock::SingleLayout) { return {"0"}; } else if (m_corona->layoutManager()->memoryUsage() == Dock::MultipleLayouts) { if (m_activities.isEmpty()) { return m_corona->layoutManager()->orphanedActivities(); } else { return m_activities; } } else { return {"0"}; } } QString Layout::lastUsedActivity() { return m_lastUsedActivity; } void Layout::clearLastUsedActivity() { m_lastUsedActivity = ""; emit lastUsedActivityChanged(); } void Layout::updateLastUsedActivity() { if (!m_corona) { return; } if (!m_lastUsedActivity.isEmpty() && !m_corona->layoutManager()->activities().contains(m_lastUsedActivity)) { clearLastUsedActivity(); } QString currentId = m_corona->activitiesConsumer()->currentActivity(); QStringList appliedActivitiesIds = appliedActivities(); if (m_lastUsedActivity != currentId && (appliedActivitiesIds.contains(currentId) || m_corona->layoutManager()->memoryUsage() == Dock::SingleLayout)) { m_lastUsedActivity = currentId; emit lastUsedActivityChanged(); } } void Layout::destroyedChanged(bool destroyed) { if (!m_corona) { return; } qDebug() << "dock containment destroyed changed!!!!"; Plasma::Containment *sender = qobject_cast(QObject::sender()); if (!sender) { return; } if (destroyed) { m_waitingDockViews[sender] = m_dockViews.take(static_cast(sender)); } else { m_dockViews[sender] = m_waitingDockViews.take(static_cast(sender)); } emit m_corona->docksCountChanged(); emit m_corona->availableScreenRectChanged(); emit m_corona->availableScreenRegionChanged(); } void Layout::containmentDestroyed(QObject *cont) { if (!m_corona) { return; } Plasma::Containment *containment = static_cast(cont); if (containment) { int containmentIndex = m_containments.indexOf(containment); if (containmentIndex >= 0) { m_containments.removeAt(containmentIndex); } qDebug() << "Layout " << name() << " :: containment destroyed!!!!"; auto view = m_dockViews.take(containment); if (!view) { view = m_waitingDockViews.take(containment); } if (view) { view->disconnectSensitiveSignals(); view->deleteLater(); emit m_corona->docksCountChanged(); emit m_corona->availableScreenRectChanged(); emit m_corona->availableScreenRegionChanged(); emit dockColorizerSupportChanged(); } } } void Layout::addDock(Plasma::Containment *containment, bool forceLoading, int expDockScreen) { qDebug() << "Layout :::: " << m_layoutName << " ::: addDock was called... m_containments :: " << m_containments.size(); if (!containment || !m_corona || !containment->kPackage().isValid()) { qWarning() << "the requested containment plugin can not be located or loaded"; return; } auto metadata = containment->kPackage().metadata(); qDebug() << "step 1..."; if (metadata.pluginId() != "org.kde.latte.containment") return; qDebug() << "step 2..."; for (auto *dock : m_dockViews) { if (dock->containment() == containment) return; } qDebug() << "step 3..."; QScreen *nextScreen{qGuiApp->primaryScreen()}; bool onPrimary = containment->config().readEntry("onPrimary", true); int id = containment->screen(); if (id == -1 && expDockScreen == -1) { id = containment->lastScreen(); } if (expDockScreen > -1) { id = expDockScreen; } qDebug() << "add dock - containment id: " << containment->id() << " ,screen id : " << id << " ,onprimary:" << onPrimary << " ,forceDockLoad:" << forceLoading; if (id >= 0 && !onPrimary && !forceLoading) { QString connector = m_corona->screenPool()->connector(id); qDebug() << "add dock - connector : " << connector; bool found{false}; foreach (auto scr, qGuiApp->screens()) { if (scr && scr->name() == connector) { found = true; nextScreen = scr; break; } } if (!found) { - qDebug() << "adding dock rejected, screen not available ! : " << connector; + qDebug() << "reject : adding explicit dock, screen not available ! : " << connector; return; } - } else if (onPrimary) { + + if (primaryDockOccupyEdge(containment->location())) { + qDebug() << "reject : adding explicit dock, primary dock occupies edge at screen ! : " << connector; + } + } + + if (id >= 0 && onPrimary) { + QString connector = m_corona->screenPool()->connector(id); + qDebug() << "add dock - connector : " << connector; + + foreach (auto view, m_dockViews) { + auto testContainment = view->containment(); + + int testScreenId = testContainment->screen(); + + if (testScreenId == -1) { + testScreenId = testContainment->lastScreen(); + } + + bool testOnPrimary = testContainment->config().readEntry("onPrimary", true); + Plasma::Types::Location testLocation = static_cast((int)testContainment->config().readEntry("location", (int)Plasma::Types::BottomEdge)); + + if (!testOnPrimary && m_corona->screenPool()->primaryScreenId() == testScreenId && testLocation == containment->location()) { + qDebug() << "Rejected explicit dockView and removing it in order add an onPrimary with higher priority at screen: " << connector; + auto viewToDelete = m_dockViews.take(testContainment); + viewToDelete->disconnectSensitiveSignals(); + viewToDelete->deleteLater(); + } + } + } + + /* old behavior to not add primary docks on explicit one + else if (onPrimary) { if (explicitDockOccupyEdge(m_corona->screenPool()->primaryScreenId(), containment->location())) { qDebug() << "CORONA ::: adding dock rejected, the edge is occupied by explicit dock ! : " << containment->location(); //we must check that an onPrimary dock should never catch up the same edge on //the same screen with an explicit dock return; } - } + }*/ qDebug() << "Adding dock for container..."; qDebug() << "onPrimary: " << onPrimary << "screen!!! :" << nextScreen->name(); //! it is used to set the correct flag during the creation //! of the window... This of course is also used during //! recreations of the window between different visibility modes auto mode = static_cast(containment->config().readEntry("visibility", static_cast(Dock::DodgeActive))); bool dockWin{true}; if (mode == Dock::AlwaysVisible || mode == Dock::WindowsGoBelow) { dockWin = true; } else { dockWin = containment->config().readEntry("dockWindowBehavior", true); } auto dockView = new DockView(m_corona, nextScreen, dockWin); dockView->init(); dockView->setContainment(containment); dockView->setManagedLayout(this); //! force this special dock case to become primary //! even though it isnt if (forceLoading) { dockView->setOnPrimary(true); } // connect(containment, &QObject::destroyed, this, &Layout::containmentDestroyed); connect(containment, &Plasma::Applet::destroyedChanged, this, &Layout::destroyedChanged); connect(containment, &Plasma::Applet::locationChanged, m_corona, &DockCorona::dockLocationChanged); connect(containment, &Plasma::Containment::appletAlternativesRequested , m_corona, &DockCorona::showAlternativesForApplet, Qt::QueuedConnection); if (m_corona->layoutManager()->memoryUsage() == Dock::MultipleLayouts) { connect(containment, &Plasma::Containment::appletCreated, this, &Layout::appletCreated); } connect(dockView, &DockView::colorizerSupportChanged, this, &Layout::dockColorizerSupportChanged); //! Qt 5.9 creates a crash for this in wayland, that is why the check is used //! but on the other hand we need this for copy to work correctly and show //! the copied dock under X11 //if (!KWindowSystem::isPlatformWayland()) { dockView->show(); //} m_dockViews[containment] = dockView; emit dockColorizerSupportChanged(); emit m_corona->docksCountChanged(); } void Layout::copyDock(Plasma::Containment *containment) { if (!containment || !m_corona) return; qDebug() << "copying containment layout"; //! Settting mutable for create a containment m_corona->setImmutability(Plasma::Types::Mutable); QString temp1File = QDir::homePath() + "/.config/lattedock.copy1.bak"; //! WE NEED A WAY TO COPY A CONTAINMENT!!!! QFile copyFile(temp1File); if (copyFile.exists()) copyFile.remove(); KSharedConfigPtr newFile = KSharedConfig::openConfig(temp1File); KConfigGroup copied_conts = KConfigGroup(newFile, "Containments"); KConfigGroup copied_c1 = KConfigGroup(&copied_conts, QString::number(containment->id())); KConfigGroup copied_systray; // toCopyContainmentIds << QString::number(containment->id()); // toCopyAppletIds << containment->config().group("Applets").groupList(); containment->config().copyTo(&copied_c1); //!investigate if there is a systray in the containment to copy also int systrayId = -1; QString systrayAppletId; auto applets = containment->config().group("Applets"); foreach (auto applet, applets.groupList()) { KConfigGroup appletSettings = applets.group(applet).group("Configuration"); int tSysId = appletSettings.readEntry("SystrayContainmentId", -1); if (tSysId != -1) { systrayId = tSysId; systrayAppletId = applet; qDebug() << "systray was found in the containment... ::: " << tSysId; break; } } if (systrayId != -1) { Plasma::Containment *systray{nullptr}; foreach (auto containment, m_corona->containments()) { if (containment->id() == systrayId) { systray = containment; break; } } if (systray) { copied_systray = KConfigGroup(&copied_conts, QString::number(systray->id())); // toCopyContainmentIds << QString::number(systray->id()); // toCopyAppletIds << systray->config().group("Applets").groupList(); systray->config().copyTo(&copied_systray); } } //! end of systray specific code //! update ids to unique ones QString temp2File = newUniqueIdsLayoutFromFile(temp1File); //! Finally import the configuration QList importedDocks = importLayoutFile(temp2File); Plasma::Containment *newContainment{nullptr}; if (importedDocks.size() == 1) { newContainment = importedDocks[0]; } if (!newContainment || !newContainment->kPackage().isValid()) { qWarning() << "the requested containment plugin can not be located or loaded"; return; } auto config = newContainment->config(); //in multi-screen environment the copied dock is moved to alternative screens first const auto screens = qGuiApp->screens(); auto dock = m_dockViews[containment]; bool setOnExplicitScreen = false; int dockScrId = -1; int copyScrId = -1; if (dock) { dockScrId = m_corona->screenPool()->id(dock->currentScreen()); qDebug() << "COPY DOCK SCREEN ::: " << dockScrId; if (dockScrId != -1 && screens.count() > 1) { foreach (auto scr, screens) { copyScrId = m_corona->screenPool()->id(scr->name()); //the screen must exist and not be the same with the original dock if (copyScrId > -1 && copyScrId != dockScrId) { QList fEdges = freeEdges(copyScrId); if (fEdges.contains((Plasma::Types::Location)containment->location())) { ///set this containment to an explicit screen config.writeEntry("onPrimary", false); config.writeEntry("lastScreen", copyScrId); newContainment->setLocation(containment->location()); qDebug() << "COPY DOCK SCREEN NEW SCREEN ::: " << copyScrId; setOnExplicitScreen = true; break; } } } } } if (!setOnExplicitScreen) { QList edges = freeEdges(newContainment->screen()); if (edges.count() > 0) { newContainment->setLocation(edges.at(0)); } else { newContainment->setLocation(Plasma::Types::BottomEdge); } config.writeEntry("onPrimary", false); config.writeEntry("lastScreen", dockScrId); } newContainment->config().sync(); if (setOnExplicitScreen && copyScrId > -1) { qDebug() << "Copy Dock in explicit screen ::: " << copyScrId; addDock(newContainment, copyScrId); newContainment->reactToScreenChange(); } else { qDebug() << "Copy Dock in current screen..."; addDock(newContainment, dockScrId); } } void Layout::appletCreated(Plasma::Applet *applet) { //! In Multiple Layout the orphaned systrays must be assigned to layouts //! when the user adds them KConfigGroup appletSettings = applet->containment()->config().group("Applets").group(QString::number(applet->id())).group("Configuration"); int systrayId = appletSettings.readEntry("SystrayContainmentId", -1); if (systrayId != -1) { uint sId = (uint)systrayId; foreach (auto containment, m_corona->containments()) { if (containment->id() == sId) { containment->config().writeEntry("layoutId", m_layoutName); } addContainment(containment); } } } void Layout::importToCorona() { if (!m_corona) { return; } //! Settting mutable for create a containment m_corona->setImmutability(Plasma::Types::Mutable); QString temp1FilePath = QDir::homePath() + "/.config/lattedock.copy1.bak"; //! we need to copy first the layout file because the kde cache //! may not have yet been updated (KSharedConfigPtr) //! this way we make sure at the latest changes stored in the layout file //! will be also available when changing to Myltiple Layouts QString tempLayoutFilePath = QDir::homePath() + "/.config/lattedock.layout.bak"; //! WE NEED A WAY TO COPY A CONTAINMENT!!!! QFile tempLayoutFile(tempLayoutFilePath); QFile copyFile(temp1FilePath); QFile layoutOriginalFile(m_layoutFile); if (tempLayoutFile.exists()) { tempLayoutFile.remove(); } if (copyFile.exists()) copyFile.remove(); layoutOriginalFile.copy(tempLayoutFilePath); KSharedConfigPtr filePtr = KSharedConfig::openConfig(tempLayoutFilePath); KSharedConfigPtr newFile = KSharedConfig::openConfig(temp1FilePath); KConfigGroup copyGroup = KConfigGroup(newFile, "Containments"); KConfigGroup current_containments = KConfigGroup(filePtr, "Containments"); current_containments.copyTo(©Group); copyGroup.sync(); //! update ids to unique ones QString temp2File = newUniqueIdsLayoutFromFile(temp1FilePath); //! Finally import the configuration importLayoutFile(temp2File); } QString Layout::availableId(QStringList all, QStringList assigned, int base) { bool found = false; int i = base; while (!found && i < 32000) { QString iStr = QString::number(i); if (!all.contains(iStr) && !assigned.contains(iStr)) { return iStr; } i++; } return QString(""); } QString Layout::newUniqueIdsLayoutFromFile(QString file) { if (!m_corona) { return QString(); } QString tempFile = QDir::homePath() + "/.config/lattedock.copy2.bak"; QFile copyFile(tempFile); if (copyFile.exists()) copyFile.remove(); //! BEGIN updating the ids in the temp file QStringList allIds; allIds << m_corona->containmentsIds(); allIds << m_corona->appletsIds(); QStringList toInvestigateContainmentIds; QStringList toInvestigateAppletIds; QStringList toInvestigateSystrayContIds; //! first is the systray containment id QHash systrayParentContainmentIds; QHash systrayAppletIds; //qDebug() << "Ids:" << allIds; //qDebug() << "to copy containments: " << toCopyContainmentIds; //qDebug() << "to copy applets: " << toCopyAppletIds; QStringList assignedIds; QHash assigned; KSharedConfigPtr filePtr = KSharedConfig::openConfig(file); KConfigGroup investigate_conts = KConfigGroup(filePtr, "Containments"); //KConfigGroup copied_c1 = KConfigGroup(&copied_conts, QString::number(containment->id())); //! Record the containment and applet ids foreach (auto cId, investigate_conts.groupList()) { toInvestigateContainmentIds << cId; auto appletsEntries = investigate_conts.group(cId).group("Applets"); toInvestigateAppletIds << appletsEntries.groupList(); //! investigate for systrays foreach (auto appletId, appletsEntries.groupList()) { KConfigGroup appletSettings = appletsEntries.group(appletId).group("Configuration"); int tSysId = appletSettings.readEntry("SystrayContainmentId", -1); //! It is a systray !!! if (tSysId != -1) { QString tSysIdStr = QString::number(tSysId); toInvestigateSystrayContIds << tSysIdStr; systrayParentContainmentIds[tSysIdStr] = cId; systrayAppletIds[tSysIdStr] = appletId; qDebug() << "systray was found in the containment..."; } } } //! Reassign containment and applet ids to unique ones foreach (auto contId, toInvestigateContainmentIds) { QString newId = availableId(allIds, assignedIds, 12); assignedIds << newId; assigned[contId] = newId; } foreach (auto appId, toInvestigateAppletIds) { QString newId = availableId(allIds, assignedIds, 40); assignedIds << newId; assigned[appId] = newId; } qDebug() << "ALL CORONA IDS ::: " << allIds; qDebug() << "FULL ASSIGNMENTS ::: " << assigned; foreach (auto cId, toInvestigateContainmentIds) { QString value = assigned[cId]; if (assigned.contains(value)) { QString value2 = assigned[value]; if (cId != assigned[cId] && !value2.isEmpty() && cId == value2) { qDebug() << "PROBLEM APPEARED !!!! FOR :::: " << cId << " .. fixed .."; assigned[cId] = cId; assigned[value] = value; } } } foreach (auto aId, toInvestigateAppletIds) { QString value = assigned[aId]; if (assigned.contains(value)) { QString value2 = assigned[value]; if (aId != assigned[aId] && !value2.isEmpty() && aId == value2) { qDebug() << "PROBLEM APPEARED !!!! FOR :::: " << aId << " .. fixed .."; assigned[aId] = aId; assigned[value] = value; } } } qDebug() << "FIXED FULL ASSIGNMENTS ::: " << assigned; //! update applet ids in their contaiment order and in MultipleLayouts update also the layoutId foreach (auto cId, investigate_conts.groupList()) { //! Update (appletOrder) and (lockedZoomApplets) for (int i = 1; i <= 2; ++i) { QString settingStr = (i == 1) ? "appletOrder" : "lockedZoomApplets"; QString order1 = investigate_conts.group(cId).group("General").readEntry(settingStr, QString()); if (!order1.isEmpty()) { QStringList order1Ids = order1.split(";"); QStringList fixedOrder1Ids; for (int i = 0; i < order1Ids.count(); ++i) { fixedOrder1Ids.append(assigned[order1Ids[i]]); } QString fixedOrder1 = fixedOrder1Ids.join(";"); investigate_conts.group(cId).group("General").writeEntry(settingStr, fixedOrder1); } } if (m_corona->layoutManager()->memoryUsage() == Dock::MultipleLayouts) { investigate_conts.group(cId).writeEntry("layoutId", m_layoutName); } } //! must update also the systray id in its applet foreach (auto systrayId, toInvestigateSystrayContIds) { KConfigGroup systrayParentContainment = investigate_conts.group(systrayParentContainmentIds[systrayId]); systrayParentContainment.group("Applets").group(systrayAppletIds[systrayId]).group("Configuration").writeEntry("SystrayContainmentId", assigned[systrayId]); systrayParentContainment.sync(); } investigate_conts.sync(); //! Copy To Temp 2 File And Update Correctly The Ids KSharedConfigPtr file2Ptr = KSharedConfig::openConfig(tempFile); KConfigGroup fixedNewContainmets = KConfigGroup(file2Ptr, "Containments"); foreach (auto contId, investigate_conts.groupList()) { QString pluginId = investigate_conts.group(contId).readEntry("plugin", ""); if (pluginId != "org.kde.desktopcontainment") { //!dont add ghost containments KConfigGroup newContainmentGroup = fixedNewContainmets.group(assigned[contId]); investigate_conts.group(contId).copyTo(&newContainmentGroup); newContainmentGroup.group("Applets").deleteGroup(); foreach (auto appId, investigate_conts.group(contId).group("Applets").groupList()) { KConfigGroup appletGroup = investigate_conts.group(contId).group("Applets").group(appId); KConfigGroup newAppletGroup = fixedNewContainmets.group(assigned[contId]).group("Applets").group(assigned[appId]); appletGroup.copyTo(&newAppletGroup); } } } fixedNewContainmets.sync(); return tempFile; } QList Layout::importLayoutFile(QString file) { KSharedConfigPtr filePtr = KSharedConfig::openConfig(file); auto newContainments = m_corona->importLayout(KConfigGroup(filePtr, "")); ///Find latte and systray containments qDebug() << " imported containments ::: " << newContainments.length(); QList importedDocks; //QList systrays; foreach (auto containment, newContainments) { KPluginMetaData meta = containment->kPackage().metadata(); if (meta.pluginId() == "org.kde.latte.containment") { qDebug() << "new latte containment id: " << containment->id(); importedDocks << containment; } } ///after systrays were found we must update in latte the relevant ids /*if (!systrays.isEmpty()) { foreach (auto systray, systrays) { qDebug() << "systray found with id : " << systray->id(); Plasma::Applet *parentApplet = qobject_cast(systray->parent()); if (parentApplet) { KConfigGroup appletSettings = parentApplet->config().group("Configuration"); if (appletSettings.hasKey("SystrayContainmentId")) { qDebug() << "!!! updating systray id to : " << systray->id(); appletSettings.writeEntry("SystrayContainmentId", systray->id()); } } } }*/ return importedDocks; } void Layout::recreateDock(Plasma::Containment *containment) { if (!m_corona) { return; } //! give the time to config window to close itself first and then recreate the dock //! step:1 remove the dockview QTimer::singleShot(350, [this, containment]() { auto view = m_dockViews.take(containment); if (view) { qDebug() << "recreate - step 1: removing dock for containment:" << containment->id(); //! step:2 add the new dockview connect(view, &QObject::destroyed, this, [this, containment]() { QTimer::singleShot(250, this, [this, containment]() { if (!m_dockViews.contains(containment)) { qDebug() << "recreate - step 2: adding dock for containment:" << containment->id(); addDock(containment); } }); }); view->deleteLater(); } }); } //! the central functions that updates loading/unloading dockviews //! concerning screen changed (for multi-screen setups mainly) void Layout::syncDockViewsToScreens() { if (!m_corona) { return; } + qDebug() << "start of, syncDockViewsToScreens ...."; qDebug() << "LAYOUT ::: " << name(); qDebug() << "screen count changed -+-+ " << qGuiApp->screens().size(); - qDebug() << "adding consideration...."; - qDebug() << "dock view running : " << m_dockViews.count(); + QHash> futureDocksLocations; + QList futureShownViews; + QString prmScreenName = qGuiApp->primaryScreen()->name(); - foreach (auto scr, qGuiApp->screens()) { - qDebug() << "Found screen: " << scr->name(); + //! first step: primary docks must be placed in primary screen free edges + foreach (auto containment, m_containments) { + int screenId = containment->screen(); - foreach (auto cont, m_containments) { - int id = cont->screen(); + if (screenId == -1) { + screenId = containment->lastScreen(); + } - if (id == -1) { - id = cont->lastScreen(); - } + bool onPrimary = containment->config().readEntry("onPrimary", true); + Plasma::Types::Location location = static_cast((int)containment->config().readEntry("location", (int)Plasma::Types::BottomEdge)); - bool onPrimary = cont->config().readEntry("onPrimary", true); - Plasma::Types::Location location = static_cast((int)cont->config().readEntry("location", (int)Plasma::Types::BottomEdge)); - - //! two main situations that a dock must be added when it is not already running - //! 1. when a dock is primary, not running and the edge for which is associated is free - //! 2. when a dock in explicit, not running and the associated screen currently exists - //! e.g. the screen has just been added - if (((onPrimary && freeEdges(qGuiApp->primaryScreen()).contains(location)) || (!onPrimary && (m_corona->screenPool()->connector(id) == scr->name()))) - && (!m_dockViews.contains(cont))) { - qDebug() << "screen Count signal: view must be added... for:" << scr->name(); - addDock(cont); - } + if (onPrimary && !futureDocksLocations[prmScreenName].contains(location)) { + futureDocksLocations[prmScreenName].append(location); + futureShownViews.append(containment->id()); } } - qDebug() << "removing consideration & updating screen for always on primary docks...."; + //! second step: explicit docks must be placed in their screens if the screen edge is free + foreach (auto containment, m_containments) { + int screenId = containment->screen(); - //! this code tries to find a containment that must not be deleted by - //! automatic algorithm. Currently the containment with the minimum id - //! containing tasks plasmoid wins - int preserveContainmentId{ -1}; - bool dockWithTasksWillBeShown{false}; + if (screenId == -1) { + screenId = containment->lastScreen(); + } - //! associate correct values for preserveContainmentId and - //! dockWithTasksWillBeShown - foreach (auto view, m_dockViews) { - bool found{false}; + bool onPrimary = containment->config().readEntry("onPrimary", true); + Plasma::Types::Location location = static_cast((int)containment->config().readEntry("location", (int)Plasma::Types::BottomEdge)); - foreach (auto scr, qGuiApp->screens()) { - if (scr->name() == view->currentScreen() - || (view->onPrimary() && scr == qGuiApp->primaryScreen())) { - found = true; - break; + if (!onPrimary) { + QString expScreenName = m_corona->screenPool()->connector(screenId); + + if (m_corona->screenPool()->screenExists(screenId) && !futureDocksLocations[expScreenName].contains(location)) { + futureDocksLocations[expScreenName].append(location); + futureShownViews.append(containment->id()); } } + } + + qDebug() << "PRIMARY SCREEN :: " << prmScreenName; + qDebug() << "DOCKVIEWS MUST BE PRESENT AT :: " << futureDocksLocations; - //!check if a tasks dock will be shown (try to prevent its deletion) - if (found && view->tasksPresent()) { - dockWithTasksWillBeShown = true; + //! add views + foreach (auto containment, m_containments) { + int screenId = containment->screen(); + + if (screenId == -1) { + screenId = containment->lastScreen(); } - if (!found && !view->onPrimary() && (m_dockViews.size() > 1) && m_dockViews.contains(view->containment()) - && !(view->tasksPresent() && noDocksWithTasks() == 1)) { //do not delete last dock containing tasks - if (view->tasksPresent()) { - if (preserveContainmentId == -1) - preserveContainmentId = view->containment()->id(); - else if (view->containment()->id() < preserveContainmentId) - preserveContainmentId = view->containment()->id(); - } + if (!dockViewExists(containment) && futureShownViews.contains(containment->id())) { + qDebug() << "syncDockViewsToScreens: view must be added... for:" << m_corona->screenPool()->connector(screenId); + addDock(containment); } } - //! check which docks must be deleted e.g. when the corresponding - //! screen does not exist any more. - //! The code is smart enough in order - //! to never delete the last tasks dock and also it makes sure that - //! the last tasks dock which will exist in the end will be the one - //! with the lowest containment id + //! remove views foreach (auto view, m_dockViews) { - bool found{false}; + if (view->containment() && !futureShownViews.contains(view->containment()->id())) { + qDebug() << "syncDockViewsToScreens: view must be deleted... for:" << view->currentScreen(); + auto viewToDelete = m_dockViews.take(view->containment()); + viewToDelete->disconnectSensitiveSignals(); + viewToDelete->deleteLater(); + } + } + + //! reconsider views + foreach (auto view, m_dockViews) { + if (view->containment() && futureShownViews.contains(view->containment()->id())) { + //! if the dock will not be deleted its a very good point to reconsider + //! if the screen in which is running is the correct one + view->reconsiderScreen(); + } + } + + /* + qDebug() << "adding consideration...."; + qDebug() << "dock view running : " << m_dockViews.count(); foreach (auto scr, qGuiApp->screens()) { - if (scr->name() == view->currentScreen() - || (view->onPrimary() && scr == qGuiApp->primaryScreen())) { - found = true; - break; + qDebug() << "Found screen: " << scr->name(); + + foreach (auto cont, m_containments) { + int id = cont->screen(); + + if (id == -1) { + id = cont->lastScreen(); + } + + bool onPrimary = cont->config().readEntry("onPrimary", true); + Plasma::Types::Location location = static_cast((int)cont->config().readEntry("location", (int)Plasma::Types::BottomEdge)); + + //! two main situations that a dock must be added when it is not already running + //! 1. when a dock is primary, not running and the edge for which is associated is free + //! 2. when a dock in explicit, not running and the associated screen currently exists + //! e.g. the screen has just been added + if (((onPrimary && freeEdges(qGuiApp->primaryScreen()).contains(location)) || (!onPrimary && (m_corona->screenPool()->connector(id) == scr->name()))) + && (!m_dockViews.contains(cont))) { + qDebug() << "screen Count signal: view must be added... for:" << scr->name(); + addDock(cont); + } } } + qDebug() << "removing consideration & updating screen for always on primary docks...."; - //! which explicit docks can be deleted - if (!found && !view->onPrimary() && (m_dockViews.size() > 1) && m_dockViews.contains(view->containment()) - && !(view->tasksPresent() && noDocksWithTasks() == 1)) { - //do not delete last dock containing tasks - if (dockWithTasksWillBeShown || preserveContainmentId != view->containment()->id()) { - qDebug() << "screen Count signal: view must be deleted... for:" << view->currentScreen(); - auto viewToDelete = m_dockViews.take(view->containment()); - viewToDelete->deleteLater(); + //! this code tries to find a containment that must not be deleted by + //! automatic algorithm. Currently the containment with the minimum id + //! containing tasks plasmoid wins + int preserveContainmentId{ -1}; + bool dockWithTasksWillBeShown{false}; + + //! associate correct values for preserveContainmentId and + //! dockWithTasksWillBeShown + foreach (auto view, m_dockViews) { + bool found{false}; + + foreach (auto scr, qGuiApp->screens()) { + if (scr->name() == view->currentScreen() + || (view->onPrimary() && scr == qGuiApp->primaryScreen())) { + found = true; + break; + } } - //!which primary docks can be deleted - } else if (view->onPrimary() && !found - && !freeEdges(qGuiApp->primaryScreen()).contains(view->location())) { - qDebug() << "screen Count signal: primary view must be deleted... for:" << view->currentScreen(); - auto viewToDelete = m_dockViews.take(view->containment()); - viewToDelete->deleteLater(); - } else { - //! if the dock will not be deleted its a very good point to reconsider - //! if the screen in which is running is the correct one - view->reconsiderScreen(); + //!check if a tasks dock will be shown (try to prevent its deletion) + if (found && view->tasksPresent()) { + dockWithTasksWillBeShown = true; + } + + if (!found && !view->onPrimary() && (m_dockViews.size() > 1) && m_dockViews.contains(view->containment()) + && !(view->tasksPresent() && noDocksWithTasks() == 1)) { //do not delete last dock containing tasks + if (view->tasksPresent()) { + if (preserveContainmentId == -1) + preserveContainmentId = view->containment()->id(); + else if (view->containment()->id() < preserveContainmentId) + preserveContainmentId = view->containment()->id(); + } + } } - } - qDebug() << "end of screens count change...."; + //! check which docks must be deleted e.g. when the corresponding + //! screen does not exist any more. + //! The code is smart enough in order + //! to never delete the last tasks dock and also it makes sure that + //! the last tasks dock which will exist in the end will be the one + //! with the lowest containment id + foreach (auto view, m_dockViews) { + bool found{false}; + + foreach (auto scr, qGuiApp->screens()) { + if (scr->name() == view->currentScreen() + || (view->onPrimary() && scr == qGuiApp->primaryScreen())) { + found = true; + break; + } + } + + + //! which explicit docks can be deleted + if (!found && !view->onPrimary() && (m_dockViews.size() > 1) && m_dockViews.contains(view->containment()) + && !(view->tasksPresent() && noDocksWithTasks() == 1)) { + //do not delete last dock containing tasks + if (dockWithTasksWillBeShown || preserveContainmentId != view->containment()->id()) { + qDebug() << "screen Count signal: view must be deleted... for:" << view->currentScreen(); + auto viewToDelete = m_dockViews.take(view->containment()); + viewToDelete->disconnectSensitiveSignals(); + viewToDelete->deleteLater(); + } + + //!which primary docks can be deleted + } else if (view->onPrimary() && !found + && !freeEdges(qGuiApp->primaryScreen()).contains(view->location())) { + qDebug() << "screen Count signal: primary view must be deleted... for:" << view->currentScreen(); + auto viewToDelete = m_dockViews.take(view->containment()); + viewToDelete->disconnectSensitiveSignals(); + viewToDelete->deleteLater(); + } else { + //! if the dock will not be deleted its a very good point to reconsider + //! if the screen in which is running is the correct one + view->reconsiderScreen(); + } + } + */ + qDebug() << "end of, syncDockViewsToScreens ...."; } void Layout::assignToLayout(DockView *dockView, QList containments) { if (!m_corona) { return; } if (dockView) { m_dockViews[dockView->containment()] = dockView; m_containments << containments; foreach (auto containment, containments) { containment->config().writeEntry("layoutId", name()); connect(containment, &QObject::destroyed, this, &Layout::containmentDestroyed); connect(containment, &Plasma::Applet::destroyedChanged, this, &Layout::destroyedChanged); connect(containment, &Plasma::Containment::appletCreated, this, &Layout::appletCreated); } dockView->setManagedLayout(this); emit m_corona->docksCountChanged(); emit m_corona->availableScreenRectChanged(); emit m_corona->availableScreenRegionChanged(); } //! sync the original layout file for integrity if (m_corona && m_corona->layoutManager()->memoryUsage() == Dock::MultipleLayouts) { syncToLayoutFile(false); } } QList Layout::unassignFromLayout(DockView *dockView) { QList containments; if (!m_corona) { return containments; } containments << dockView->containment(); foreach (auto containment, m_containments) { Plasma::Applet *parentApplet = qobject_cast(containment->parent()); //! add systrays from that dockView if (parentApplet && parentApplet->containment() && parentApplet->containment() == dockView->containment()) { containments << containment; disconnect(containment, &QObject::destroyed, this, &Layout::containmentDestroyed); disconnect(containment, &Plasma::Applet::destroyedChanged, this, &Layout::destroyedChanged); disconnect(containment, &Plasma::Containment::appletCreated, this, &Layout::appletCreated); } } foreach (auto containment, containments) { m_containments.removeAll(containment); } if (containments.size() > 0) { m_dockViews.remove(dockView->containment()); } //! sync the original layout file for integrity if (m_corona && m_corona->layoutManager()->memoryUsage() == Dock::MultipleLayouts) { syncToLayoutFile(false); } return containments; } +bool Layout::dockViewExists(Plasma::Containment *containment) +{ + if (!m_corona) { + return false; + } + + return m_dockViews.keys().contains(containment); +} + QList Layout::freeEdges(QScreen *screen) const { using Plasma::Types; QList edges{Types::BottomEdge, Types::LeftEdge, Types::TopEdge, Types::RightEdge}; if (!m_corona) { return edges; } foreach (auto view, m_dockViews) { if (view && view->currentScreen() == screen->name()) { edges.removeOne(view->location()); } } return edges; } QList Layout::freeEdges(int screen) const { using Plasma::Types; QList edges{Types::BottomEdge, Types::LeftEdge, Types::TopEdge, Types::RightEdge}; if (!m_corona) { return edges; } QScreen *scr = m_corona->screenPool()->screenForId(screen); foreach (auto view, m_dockViews) { if (view && scr && view->currentScreen() == scr->name()) { edges.removeOne(view->location()); } } return edges; } bool Layout::explicitDockOccupyEdge(int screen, Plasma::Types::Location location) const { if (!m_corona) { return false; } foreach (auto containment, m_containments) { if (containment->pluginMetaData().pluginId() == "org.kde.latte.containment") { bool onPrimary = containment->config().readEntry("onPrimary", true); int id = containment->lastScreen(); Plasma::Types::Location contLocation = containment->location(); if (!onPrimary && id == screen && contLocation == location) { return true; } } } return false; } +bool Layout::primaryDockOccupyEdge(Plasma::Types::Location location) const +{ + if (!m_corona) { + return false; + } + + foreach (auto containment, m_containments) { + if (containment->pluginMetaData().pluginId() == "org.kde.latte.containment") { + bool onPrimary = containment->config().readEntry("onPrimary", true); + Plasma::Types::Location contLocation = containment->location(); + + if (onPrimary && contLocation == location) { + return true; + } + } + } + + return false; +} int Layout::noDocksWithTasks() const { if (!m_corona) { return 0; } int result = 0; foreach (auto view, m_dockViews) { if (view->tasksPresent()) { result++; } } return result; } int Layout::docksCount(int screen) const { if (!m_corona) { return 0; } QScreen *scr = m_corona->screenPool()->screenForId(screen); int docks{0}; foreach (auto view, m_dockViews) { if (view && view->screen() == scr && !view->containment()->destroyed()) { ++docks; } } return docks; } int Layout::docksCount() const { if (!m_corona) { return 0; } int docks{0}; foreach (auto view, m_dockViews) { if (view && view->containment() && !view->containment()->destroyed()) { ++docks; } } return docks; } int Layout::docksCount(QScreen *screen) const { if (!m_corona) { return 0; } int docks{0}; foreach (auto view, m_dockViews) { if (view && view->screen() == screen && !view->containment()->destroyed()) { ++docks; } } return docks; } } diff --git a/app/layout.h b/app/layout.h index 7ac23b25..940c8c07 100644 --- a/app/layout.h +++ b/app/layout.h @@ -1,222 +1,225 @@ /* * Copyright 2017 Smith AR * Michail Vourlakos * * 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 . */ #ifndef LAYOUT_H #define LAYOUT_H #include #include #include #include #include namespace Plasma { class Applet; class Containment; class Types; } namespace Latte { class DockCorona; class DockView; } namespace Latte { //! This class is responsible to hold the settings for a specific layout. //! It also updates always the relevant layout configuration concerning //! its general settings (no the containments) class Layout : public QObject { Q_OBJECT Q_PROPERTY(bool showInMenu READ showInMenu WRITE setShowInMenu NOTIFY showInMenuChanged) Q_PROPERTY(QString background READ background NOTIFY backgroundChanged) Q_PROPERTY(QString color READ color WRITE setColor NOTIFY colorChanged) Q_PROPERTY(QString lastUsedActivity READ lastUsedActivity NOTIFY lastUsedActivityChanged) Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString textColor READ textColor NOTIFY textColorChanged) Q_PROPERTY(QStringList launchers READ launchers WRITE setLaunchers NOTIFY launchersChanged) Q_PROPERTY(QStringList activities READ activities WRITE setActivities NOTIFY activitiesChanged) public: Layout(QObject *parent, QString layoutFile, QString layoutName = QString()); ~Layout() override; static const QString MultipleLayoutsName; void initToCorona(DockCorona *corona); void syncToLayoutFile(bool removeLayoutId = false); void unloadContainments(); void unloadDockViews(); bool disableBordersForMaximizedWindows() const; void setDisableBordersForMaximizedWindows(bool disable); bool showInMenu() const; void setShowInMenu(bool show); bool layoutIsBroken() const; //!this layout is loaded and running bool isActiveLayout() const; //!it is original layout compared to pseudo-layouts that are combinations of multiple-original layouts bool isOriginalLayout() const; bool isWritable() const; + bool dockViewExists(Plasma::Containment *containment); + int version() const; void setVersion(int ver); QString background() const; void setBackground(QString path); QString color() const; void setColor(QString color); QString lastUsedActivity(); void clearLastUsedActivity(); //!e.g. when we export a layout QString name() const; QString file() const; QString textColor() const; void setTextColor(QString color); QStringList activities() const; void setActivities(QStringList activities); QStringList launchers() const; void setLaunchers(QStringList launcherList); static QString layoutName(const QString &fileName); void renameLayout(QString newName); QStringList unloadedContainmentsIds(); //! this function needs the layout to have first set the corona through initToCorona() function void addDock(Plasma::Containment *containment, bool forceLoading = false, int expDockScreen = -1); void copyDock(Plasma::Containment *containment); void recreateDock(Plasma::Containment *containment); void syncDockViewsToScreens(); void importToCorona(); const QStringList appliedActivities(); QList *containments(); QHash *dockViews(); //! Bind this dockView and its relevant containments(including systrays) //! to this layout. It is used for moving a dockView from layout to layout) void assignToLayout(DockView *dockView, QList containments); //! Unassign that dockView from this layout (this is used for moving a dockView //! from layout to layout) and returns all the containments relevant to //! that dockView QList unassignFromLayout(DockView *dockView); QList freeEdges(QScreen *screen) const; QList freeEdges(int screen) const; //! make it only read-only void lock(); //! make it writable which it should be the default void unlock(); int noDocksWithTasks() const; int docksCount() const; int docksCount(int screen) const; int docksCount(QScreen *screen) const; signals: void activitiesChanged(); void backgroundChanged(); void colorChanged(); void disableBordersForMaximizedWindowsChanged(); void dockColorizerSupportChanged(); void fileChanged(); void lastUsedActivityChanged(); void launchersChanged(); void nameChanged(); void versionChanged(); void showInMenuChanged(); void textColorChanged(); private slots: void loadConfig(); void saveConfig(); void addContainment(Plasma::Containment *containment); void appletCreated(Plasma::Applet *applet); void destroyedChanged(bool destroyed); void containmentDestroyed(QObject *cont); void updateLastUsedActivity(); private: void importLocalLayout(QString file); void init(); void setName(QString name); void setFile(QString file); bool explicitDockOccupyEdge(int screen, Plasma::Types::Location location) const; + bool primaryDockOccupyEdge(Plasma::Types::Location location) const; bool kwin_disabledMaximizedBorders() const; void kwin_setDisabledMaximizedBorders(bool disable); QString availableId(QStringList all, QStringList assigned, int base); //! provides a new file path based the provided file. The new file //! has updated ids for containments and applets based on the corona //! loaded ones QString newUniqueIdsLayoutFromFile(QString file); //! imports a layout file and returns the containments for the docks QList importLayoutFile(QString file); private: bool m_disableBordersForMaximizedWindows{false}; bool m_showInMenu{false}; //if version doesnt exist it is and old layout file int m_version{2}; QString m_background; QString m_color; QString m_lastUsedActivity; //the last used activity for this layout QString m_layoutFile; QString m_layoutName; QString m_textColor; QStringList m_activities; QStringList m_launchers; QStringList m_unloadedContainmentsIds; DockCorona *m_corona{nullptr}; KConfigGroup m_layoutGroup; QList m_containments; QHash m_dockViews; QHash m_waitingDockViews; }; } #endif // LAYOUT_H diff --git a/app/layoutmanager.cpp b/app/layoutmanager.cpp index adf8789f..44c02dcc 100644 --- a/app/layoutmanager.cpp +++ b/app/layoutmanager.cpp @@ -1,1219 +1,1219 @@ /* * Copyright 2017 Smith AR * Michail Vourlakos * * 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 . */ #include "layoutmanager.h" #include "importer.h" #include "infoview.h" #include "settingsdialog.h" #include "launcherssignals.h" #include "layout.h" #include "screenpool.h" #include "universalsettings.h" #include "dock/dockview.h" #include #include #include #include #include #include #include #include #include namespace Latte { const int MultipleLayoutsPresetId = 10; LayoutManager::LayoutManager(QObject *parent) : QObject(parent), m_importer(new Importer(this)), m_launchersSignals(new LaunchersSignals(this)), m_activitiesController(new KActivities::Controller(this)) { m_corona = qobject_cast(parent); if (m_corona) { connect(m_corona->universalSettings(), &UniversalSettings::currentLayoutNameChanged, this, &LayoutManager::currentLayoutNameChanged); connect(m_corona->universalSettings(), &UniversalSettings::showInfoWindowChanged, this, &LayoutManager::showInfoWindowChanged); m_dynamicSwitchTimer.setSingleShot(true); showInfoWindowChanged(); connect(&m_dynamicSwitchTimer, &QTimer::timeout, this, &LayoutManager::confirmDynamicSwitch); } } LayoutManager::~LayoutManager() { m_importer->deleteLater(); m_launchersSignals->deleteLater(); while (!m_activeLayouts.isEmpty()) { Layout *layout = m_activeLayouts.at(0); m_activeLayouts.removeFirst(); layout->unloadContainments(); layout->unloadDockViews(); layout->deleteLater(); } m_activitiesController->deleteLater(); } void LayoutManager::load() { int configVer = m_corona->universalSettings()->version(); qDebug() << "Universal Settings version : " << configVer; if (configVer < 2 && QFile(QDir::homePath() + "/.config/lattedockrc").exists()) { qDebug() << "Latte must update its configuration..."; m_importer->updateOldConfiguration(); importPresets(false); } else if (!QFile(QDir::homePath() + "/.config/lattedockrc").exists()) { //startup create what is necessary.... QDir layoutDir(QDir::homePath() + "/.config/latte"); if (!layoutDir.exists()) { QDir(QDir::homePath() + "/.config").mkdir("latte"); } newLayout(i18n("My Layout")); importPresets(false); m_corona->universalSettings()->setCurrentLayoutName(i18n("My Layout")); m_corona->universalSettings()->setVersion(2); } //! Check if the multiple-layouts hidden file is present, add it if it isnt if (!QFile(QDir::homePath() + "/.config/latte/" + Layout::MultipleLayoutsName + ".layout.latte").exists()) { importPreset(MultipleLayoutsPresetId, false); } qDebug() << "Latte is loading its layouts..."; connect(m_corona->m_activityConsumer, &KActivities::Consumer::currentActivityChanged, this, &LayoutManager::currentActivityChanged); connect(m_corona->m_activityConsumer, &KActivities::Consumer::runningActivitiesChanged, this, [&]() { if (memoryUsage() == Dock::MultipleLayouts) { syncMultipleLayoutsToActivities(); } }); loadLayouts(); } void LayoutManager::unload() { //! Unload all Layouts foreach (auto layout, m_activeLayouts) { if (memoryUsage() == Dock::MultipleLayouts && layout->isOriginalLayout()) { layout->syncToLayoutFile(true); } layout->unloadContainments(); layout->unloadDockViews(); if (memoryUsage() == Dock::MultipleLayouts && layout->isOriginalLayout()) { clearUnloadedContainmentsFromLinkedFile(layout->unloadedContainmentsIds()); } layout->deleteLater(); } //! Cleanup pseudo-layout from Containments if (memoryUsage() == Dock::MultipleLayouts) { // auto containmentsEntries = m_corona->config()->group("Containments"); // containmentsEntries.deleteGroup(); // containmentsEntries.sync(); } //! Remove no-needed temp files QString temp1File = QDir::homePath() + "/.config/lattedock.copy1.bak"; QString temp2File = QDir::homePath() + "/.config/lattedock.copy2.bak"; QFile file1(temp1File); QFile file2(temp2File); if (file1.exists()) file1.remove(); if (file2.exists()) file2.remove(); } DockCorona *LayoutManager::corona() { return m_corona; } Importer *LayoutManager::importer() { return m_importer; } LaunchersSignals *LayoutManager::launchersSignals() { return m_launchersSignals; } QString LayoutManager::currentLayoutName() const { if (memoryUsage() == Dock::SingleLayout) { return m_corona->universalSettings()->currentLayoutName(); } else if (memoryUsage() == Dock::MultipleLayouts) { return m_currentLayoutNameInMultiEnvironment; } return QString(); } QString LayoutManager::defaultLayoutName() const { QByteArray presetNameOrig = QString("preset" + QString::number(1)).toUtf8(); QString presetPath = m_corona->kPackage().filePath(presetNameOrig); QString presetName = Layout::layoutName(presetPath); QByteArray presetNameChars = presetName.toUtf8(); presetName = i18n(presetNameChars); return presetName; } bool LayoutManager::layoutExists(QString layoutName) const { return m_layouts.contains(layoutName); } QStringList LayoutManager::layouts() const { return m_layouts; } QStringList LayoutManager::menuLayouts() const { QStringList fixedMenuLayouts = m_menuLayouts; //! in case the current layout isnt checked to be shown in the menus //! we must add it on top if (!fixedMenuLayouts.contains(currentLayoutName()) && memoryUsage() == Dock::SingleLayout) { fixedMenuLayouts.prepend(currentLayoutName()); } else if (memoryUsage() == Dock::MultipleLayouts) { foreach (auto layout, m_activeLayouts) { if (layout->isOriginalLayout() && !fixedMenuLayouts.contains(layout->name())) { fixedMenuLayouts.prepend(layout->name()); } } } return fixedMenuLayouts; } void LayoutManager::setMenuLayouts(QStringList layouts) { if (m_menuLayouts == layouts) { return; } m_menuLayouts = layouts; emit menuLayoutsChanged(); } QStringList LayoutManager::activities() { return m_corona->m_activityConsumer->activities(); } QStringList LayoutManager::runningActivities() { return m_corona->m_activityConsumer->runningActivities(); } QStringList LayoutManager::orphanedActivities() { QStringList orphans; foreach (auto activity, activities()) { if (m_assignedLayouts[activity].isEmpty()) { orphans.append(activity); } } return orphans; } QStringList LayoutManager::presetsPaths() const { return m_presetsPaths; } QString LayoutManager::layoutPath(QString layoutName) { QString path = QDir::homePath() + "/.config/latte/" + layoutName + ".layout.latte"; if (!QFile(path).exists()) { path = ""; } return path; } Dock::LayoutsMemoryUsage LayoutManager::memoryUsage() const { return m_corona->universalSettings()->layoutsMemoryUsage(); } int LayoutManager::layoutsMemoryUsage() { return (int)m_corona->universalSettings()->layoutsMemoryUsage(); } void LayoutManager::setMemoryUsage(Dock::LayoutsMemoryUsage memoryUsage) { m_corona->universalSettings()->setLayoutsMemoryUsage(memoryUsage); } void LayoutManager::addDock(Plasma::Containment *containment, bool forceLoading, int expDockScreen) { if (memoryUsage() == Dock::SingleLayout) { m_activeLayouts.at(0)->addDock(containment, forceLoading, expDockScreen); } else if (memoryUsage() == Dock::MultipleLayouts) { QString layoutId = containment->config().readEntry("layoutId", QString()); if (!layoutId.isEmpty()) { auto layout = activeLayout(layoutId); if (layout) { layout->addDock(containment, forceLoading, expDockScreen); } } } } bool LayoutManager::dockViewExists(DockView *view) const { foreach (auto layout, m_activeLayouts) { for (auto it = layout->dockViews()->constBegin(), end = layout->dockViews()->constEnd(); it != end; ++it) { if (it.value() == view) { return true; } } } return false; } QHash *LayoutManager::currentDockViews() const { if (memoryUsage() == Dock::SingleLayout) { return m_activeLayouts.at(0)->dockViews(); } else { foreach (auto layout, m_activeLayouts) { if (layout->activities().contains(m_corona->m_activityConsumer->currentActivity())) { return layout->dockViews(); } } foreach (auto layout, m_activeLayouts) { if ((layout->name() != Layout::MultipleLayoutsName) && (layout->activities().isEmpty())) { return layout->dockViews(); } } } return nullptr; } QHash *LayoutManager::layoutDockViews(const QString &layoutName) const { Layout *layout = activeLayout(layoutName); if (layout) { return layout->dockViews(); } return nullptr; } QStringList LayoutManager::activeLayoutsNames() { QStringList names; if (memoryUsage() == Dock::SingleLayout) { names << currentLayoutName(); } else { for (int i = 0; i < m_activeLayouts.size(); ++i) { Layout *layout = m_activeLayouts.at(i); if (layout->isOriginalLayout()) { names << layout->name(); } } } return names; } Layout *LayoutManager::activeLayout(QString id) const { for (int i = 0; i < m_activeLayouts.size(); ++i) { Layout *layout = m_activeLayouts.at(i); if (layout->name() == id) { return layout; } } return nullptr; } int LayoutManager::activeLayoutPos(QString id) const { for (int i = 0; i < m_activeLayouts.size(); ++i) { Layout *layout = m_activeLayouts.at(i); if (layout->name() == id) { return i; } } return -1; } void LayoutManager::updateCurrentLayoutNameInMultiEnvironment() { foreach (auto layout, m_activeLayouts) { if (layout->isOriginalLayout() && layout->activities().contains(m_corona->activitiesConsumer()->currentActivity())) { m_currentLayoutNameInMultiEnvironment = layout->name(); emit currentLayoutNameChanged(); return; } } foreach (auto layout, m_activeLayouts) { if (layout->isOriginalLayout() && layout->activities().isEmpty()) { m_currentLayoutNameInMultiEnvironment = layout->name(); emit currentLayoutNameChanged(); return; } } } void LayoutManager::currentActivityChanged(const QString &id) { if (memoryUsage() == Dock::SingleLayout) { qDebug() << "activity changed :: " << id; m_shouldSwitchToLayout = shouldSwitchToLayout(id); m_dynamicSwitchTimer.start(); } else if (memoryUsage() == Dock::MultipleLayouts) { updateCurrentLayoutNameInMultiEnvironment(); } } void LayoutManager::showInfoWindowChanged() { if (m_corona->universalSettings()->showInfoWindow()) { m_dynamicSwitchTimer.setInterval(1800); } else { m_dynamicSwitchTimer.setInterval(2300); } } QString LayoutManager::shouldSwitchToLayout(QString activityId) { if (m_assignedLayouts.contains(activityId) && m_assignedLayouts[activityId] != currentLayoutName()) { return m_assignedLayouts[activityId]; } else if (!m_assignedLayouts.contains(activityId) && !m_corona->universalSettings()->lastNonAssignedLayoutName().isEmpty() && m_corona->universalSettings()->lastNonAssignedLayoutName() != currentLayoutName()) { return m_corona->universalSettings()->lastNonAssignedLayoutName(); } return QString(); } void LayoutManager::confirmDynamicSwitch() { QString tempShouldSwitch = shouldSwitchToLayout(m_corona->m_activityConsumer->currentActivity()); if (tempShouldSwitch.isEmpty()) { return; } if (m_shouldSwitchToLayout == tempShouldSwitch && m_shouldSwitchToLayout != currentLayoutName()) { qDebug() << "dynamic switch to layout :: " << m_shouldSwitchToLayout; emit currentLayoutIsSwitching(currentLayoutName()); if (m_corona->universalSettings()->showInfoWindow()) { showInfoWindow(i18n("Switching to layout %0 ...").arg(m_shouldSwitchToLayout), 4000); } QTimer::singleShot(500, [this, tempShouldSwitch]() { switchToLayout(tempShouldSwitch); }); } else { m_shouldSwitchToLayout = tempShouldSwitch; m_dynamicSwitchTimer.start(); } } void LayoutManager::loadLayouts() { m_layouts.clear(); m_menuLayouts.clear(); m_presetsPaths.clear(); m_assignedLayouts.clear(); QDir layoutDir(QDir::homePath() + "/.config/latte"); QStringList filter; filter.append(QString("*.layout.latte")); QStringList files = layoutDir.entryList(filter, QDir::Files | QDir::NoSymLinks); foreach (auto layout, files) { Layout layoutSets(this, layoutDir.absolutePath() + "/" + layout); QStringList validActivityIds = validActivities(layoutSets.activities()); layoutSets.setActivities(validActivityIds); foreach (auto activity, validActivityIds) { m_assignedLayouts[activity] = layoutSets.name(); } m_layouts.append(layoutSets.name()); if (layoutSets.showInMenu()) { m_menuLayouts.append(layoutSets.name()); } } m_presetsPaths.append(m_corona->kPackage().filePath("preset1")); m_presetsPaths.append(m_corona->kPackage().filePath("preset2")); m_presetsPaths.append(m_corona->kPackage().filePath("preset3")); m_presetsPaths.append(m_corona->kPackage().filePath("preset4")); emit layoutsChanged(); emit menuLayoutsChanged(); } void LayoutManager::loadLayoutOnStartup(QString layoutName) { // if (memoryUsage() == Dock::MultipleLayouts) { QStringList layouts = m_importer->checkRepairMultipleLayoutsLinkedFile(); //! Latte didnt close correctly, maybe a crash if (layouts.size() > 0) { QMessageBox *msg = new QMessageBox(); msg->setAttribute(Qt::WA_DeleteOnClose); msg->setIcon(QMessageBox::Warning); msg->setWindowTitle(i18n("Multiple Layouts Warning")); msg->setText(i18n("Latte did not close properly in the previous session. The following layout(s) [%0] were updated for consistency!!!").arg(layouts.join(","))); msg->setStandardButtons(QMessageBox::Ok); msg->open(); } //} switchToLayout(layoutName); } void LayoutManager::loadLatteLayout(QString layoutPath) { qDebug() << " -------------------------------------------------------------------- "; qDebug() << " -------------------------------------------------------------------- "; if (m_corona->containments().size() > 0) { qDebug() << "LOAD LATTE LAYOUT ::: There are still containments present !!!! :: " << m_corona->containments().size(); } if (!layoutPath.isEmpty() && m_corona->containments().size() == 0) { cleanupOnStartup(layoutPath); qDebug() << "LOADING CORONA LAYOUT:" << layoutPath; m_corona->loadLayout(layoutPath); //! ~~~ ADDING DOCKVIEWS AND ENFORCE LOADING IF TASKS ARENT PRESENT BASED ON SCREENS ~~~ !// //! this is used to record the first dock having tasks in it. It is used //! to specify which dock will be loaded on startup if a case that no "dock //! with tasks" will be loaded otherwise. Currently the older one dock wins - int firstContainmentWithTasks = -1; + /*int firstContainmentWithTasks = -1; //! this is used to check if a dock with tasks in it will be loaded on startup bool tasksWillBeLoaded = heuresticForLoadingDockWithTasks(&firstContainmentWithTasks); qDebug() << "TASKS WILL BE PRESENT AFTER LOADING ::: " << tasksWillBeLoaded; foreach (auto containment, m_corona->containments()) { //! forceDockLoading is used when a latte configuration based on the //! current running screens does not provide a dock containing tasks. //! in such case the lowest latte containment containing tasks is loaded //! and it forcefully becomes primary dock if (!tasksWillBeLoaded && firstContainmentWithTasks == containment->id()) { tasksWillBeLoaded = true; //this protects by loading more than one dock at startup addDock(containment, true); } else { addDock(containment); } - } + }*/ } } void LayoutManager::cleanupOnStartup(QString path) { KSharedConfigPtr filePtr = KSharedConfig::openConfig(path); KConfigGroup actionGroups = KConfigGroup(filePtr, "ActionPlugins"); QStringList deprecatedActionGroup; foreach (auto actId, actionGroups.groupList()) { QString pluginId = actionGroups.group(actId).readEntry("RightButton;NoModifier", ""); if (pluginId == "org.kde.contextmenu") { deprecatedActionGroup << actId; } } foreach (auto pId, deprecatedActionGroup) { qDebug() << "!!!!!!!!!!!!!!!! !!!!!!!!!!!! !!!!!!! REMOVING :::: " << pId; actionGroups.group(pId).deleteGroup(); } KConfigGroup containmentGroups = KConfigGroup(filePtr, "Containments"); QStringList removeContaimentsList; foreach (auto cId, containmentGroups.groupList()) { QString pluginId = containmentGroups.group(cId).readEntry("plugin", ""); if (pluginId == "org.kde.desktopcontainment") { //!must remove ghost containments first removeContaimentsList << cId; } } foreach (auto cId, removeContaimentsList) { containmentGroups.group(cId).deleteGroup(); } actionGroups.sync(); containmentGroups.sync(); } void LayoutManager::showAboutDialog() { m_corona->aboutApplication(); } void LayoutManager::importLatteLayout(QString layoutPath) { //! This might not be needed as it is Layout responsibility } void LayoutManager::hideAllDocks() { foreach (auto layout, m_activeLayouts) { if (layout->isOriginalLayout()) { emit currentLayoutIsSwitching(layout->name()); } } } bool LayoutManager::switchToLayout(QString layoutName, int previousMemoryUsage) { if (m_activeLayouts.size() > 0 && currentLayoutName() == layoutName && previousMemoryUsage == -1) { return false; } //! First Check If that Layout is already present if (memoryUsage() == Dock::MultipleLayouts && previousMemoryUsage == -1) { Layout *layout = activeLayout(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_corona->activitiesConsumer()->currentActivity())) { m_activitiesController->setCurrentActivity(nextActivity); return true; } } } //! 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) { foreach (auto layout, m_activeLayouts) { if (layout->isOriginalLayout()) { emit currentLayoutIsSwitching(layout->name()); } } } QString lPath = layoutPath(layoutName); if (lPath.isEmpty() && layoutName == i18n("Alternative")) { lPath = newLayout(i18n("Alternative"), i18n("Default")); } if (!lPath.isEmpty()) { if (memoryUsage() == Dock::SingleLayout) { emit currentLayoutIsSwitching(currentLayoutName()); } else if (memoryUsage() == Dock::MultipleLayouts && layoutName != Layout::MultipleLayoutsName) { Layout toLayout(this, lPath); QStringList toActivities = toLayout.activities(); Layout *activeForOrphans{nullptr}; foreach (auto fromLayout, m_activeLayouts) { if (fromLayout->isOriginalLayout() && fromLayout->activities().isEmpty()) { activeForOrphans = fromLayout; break; } } if (toActivities.isEmpty() && activeForOrphans && (toLayout.name() != activeForOrphans->name())) { emit currentLayoutIsSwitching(activeForOrphans->name()); } } //! 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{false}; if (memoryUsage() == Dock::MultipleLayouts && !activeLayout(Layout::MultipleLayoutsName)) { initializingMultipleLayouts = true; } if (memoryUsage() == Dock::SingleLayout || initializingMultipleLayouts || previousMemoryUsage == Dock::MultipleLayouts) { while (!m_activeLayouts.isEmpty()) { Layout *layout = m_activeLayouts.at(0); m_activeLayouts.removeFirst(); if (layout->isOriginalLayout() && previousMemoryUsage == Dock::MultipleLayouts) { layout->syncToLayoutFile(true); } layout->unloadContainments(); layout->unloadDockViews(); if (layout->isOriginalLayout() && previousMemoryUsage == Dock::MultipleLayouts) { clearUnloadedContainmentsFromLinkedFile(layout->unloadedContainmentsIds(), true); } delete layout; } if (initializingMultipleLayouts) { fixedLayoutName = QString(Layout::MultipleLayoutsName); fixedLPath = layoutPath(fixedLayoutName); } Layout *newLayout = new Layout(this, fixedLPath, fixedLayoutName); m_activeLayouts.append(newLayout); newLayout->initToCorona(m_corona); loadLatteLayout(fixedLPath); emit activeLayoutsChanged(); } if (memoryUsage() == Dock::MultipleLayouts) { if (!initializingMultipleLayouts && !activeLayout(layoutName)) { //! When we are in Multiple Layouts Environment and the user activates //! a Layout that is assigned to specific activities but this //! layout isnt loaded (this means neither of its activities are running) //! is such case we just activate these Activities Layout layout(this, Importer::layoutFilePath(layoutName)); int i = 0; bool lastUsedActivityFound{false}; QString lastUsedActivity = layout.lastUsedActivity(); bool orphanedLayout = !layoutIsAssigned(layoutName); QStringList assignedActivities = orphanedLayout ? orphanedActivities() : layout.activities(); if (!orphanedLayout) { foreach (auto assignedActivity, assignedActivities) { //! Starting the activities must be done asynchronous because otherwise //! the activity manager cant close multiple activities QTimer::singleShot(i * 1000, [this, assignedActivity, lastUsedActivity]() { m_activitiesController->startActivity(assignedActivity); if (lastUsedActivity == assignedActivity) { m_activitiesController->setCurrentActivity(lastUsedActivity); } }); if (lastUsedActivity == assignedActivity) { lastUsedActivityFound = true; } i = i + 1; } } else { //! orphaned layout foreach (auto assignedActivity, assignedActivities) { if (lastUsedActivity == assignedActivity) { lastUsedActivityFound = true; } } if ((!lastUsedActivityFound && assignedActivities.count() == 0) || !assignedActivities.contains(m_corona->m_activityConsumer->currentActivity())) { //! Starting the activities must be done asynchronous because otherwise //! the activity manager cant close multiple activities QTimer::singleShot(1000, [this, lastUsedActivity, lastUsedActivityFound]() { m_activitiesController->startActivity(lastUsedActivity); m_activitiesController->setCurrentActivity(lastUsedActivity); }); } } if (orphanedLayout) { syncMultipleLayoutsToActivities(layoutName); } else if (!orphanedLayout && !lastUsedActivityFound) { m_activitiesController->setCurrentActivity(layout.activities()[0]); } } else { syncMultipleLayoutsToActivities(layoutName); } } m_corona->universalSettings()->setCurrentLayoutName(layoutName); if (!layoutIsAssigned(layoutName)) { m_corona->universalSettings()->setLastNonAssignedLayoutName(layoutName); } }); } else { qDebug() << "Layout : " << layoutName << " was not found..."; } return true; } void LayoutManager::syncMultipleLayoutsToActivities(QString layoutForOrphans) { qDebug() << " ---- --------- ------ syncMultipleLayoutsToActivities ------- "; qDebug() << " ---- --------- ------ ------------------------------- ------- "; QStringList layoutsToUnload; QStringList layoutsToLoad; layoutsToLoad << Layout::MultipleLayoutsName; bool allRunningActivitiesWillBeReserved{true}; if (layoutForOrphans.isEmpty() || m_assignedLayouts.values().contains(layoutForOrphans)) { layoutForOrphans = m_corona->universalSettings()->lastNonAssignedLayoutName(); } foreach (auto activity, runningActivities()) { if (!m_assignedLayouts[activity].isEmpty()) { if (!layoutsToLoad.contains(m_assignedLayouts[activity])) { layoutsToLoad.append(m_assignedLayouts[activity]); } } else { allRunningActivitiesWillBeReserved = false; } } foreach (auto layout, m_activeLayouts) { QString tempLayoutName; if (!layoutsToLoad.contains(layout->name()) && layout->name() != layoutForOrphans) { tempLayoutName = layout->name(); } else if (layout->activities().isEmpty() && allRunningActivitiesWillBeReserved) { //! in such case the layout for the orphaned must be unloaded tempLayoutName = layout->name(); } if (!tempLayoutName.isEmpty() && !layoutsToUnload.contains(tempLayoutName)) { layoutsToUnload << tempLayoutName; } } //! Unload no needed Layouts foreach (auto layoutName, layoutsToUnload) { if (layoutName != Layout::MultipleLayoutsName) { Layout *layout = activeLayout(layoutName); int posLayout = activeLayoutPos(layoutName); if (posLayout >= 0) { qDebug() << "REMOVING LAYOUT ::::: " << layoutName; m_activeLayouts.removeAt(posLayout); if (layout->isOriginalLayout()) { layout->syncToLayoutFile(true); } layout->unloadContainments(); layout->unloadDockViews(); clearUnloadedContainmentsFromLinkedFile(layout->unloadedContainmentsIds()); delete layout; } } } //! Add Layout for orphan activities if (!allRunningActivitiesWillBeReserved) { if (!activeLayout(layoutForOrphans)) { Layout *newLayout = new Layout(this, layoutPath(layoutForOrphans), layoutForOrphans); if (newLayout) { qDebug() << "ACTIVATING ORPHANED LAYOUT ::::: " << layoutForOrphans; m_activeLayouts.append(newLayout); newLayout->initToCorona(m_corona); newLayout->importToCorona(); } } } //! Add needed Layouts based on Activities foreach (auto layoutName, layoutsToLoad) { if (!activeLayout(layoutName)) { Layout *newLayout = new Layout(this, QString(layoutPath(layoutName)), layoutName); if (newLayout) { qDebug() << "ACTIVATING LAYOUT ::::: " << layoutName; m_activeLayouts.append(newLayout); newLayout->initToCorona(m_corona); newLayout->importToCorona(); if (newLayout->isOriginalLayout() && m_corona->universalSettings()->showInfoWindow()) { showInfoWindow(i18n("Activating layout: %0 ...").arg(newLayout->name()), 5000, newLayout->appliedActivities()); } } } } updateCurrentLayoutNameInMultiEnvironment(); emit activeLayoutsChanged(); } void LayoutManager::pauseLayout(QString layoutName) { if (memoryUsage() == Dock::MultipleLayouts) { Layout *layout = activeLayout(layoutName); if (layout && !layout->activities().isEmpty()) { int i = 0; foreach (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 LayoutManager::syncActiveLayoutsToOriginalFiles() { if (memoryUsage() == Dock::MultipleLayouts) { foreach (auto layout, m_activeLayouts) { if (layout->isOriginalLayout()) { layout->syncToLayoutFile(); } } } } void LayoutManager::clearUnloadedContainmentsFromLinkedFile(QStringList containmentsIds, bool bypassChecks) { if (!m_corona || (memoryUsage() == Dock::SingleLayout && !bypassChecks)) { return; } auto containments = m_corona->config()->group("Containments"); foreach (auto conId, containmentsIds) { qDebug() << "unloads ::: " << conId; KConfigGroup containment = containments.group(conId); containment.deleteGroup(); } containments.sync(); } void LayoutManager::syncDockViewsToScreens() { foreach (auto layout, m_activeLayouts) { layout->syncDockViewsToScreens(); } } QString LayoutManager::newLayout(QString layoutName, QString preset) { QDir layoutDir(QDir::homePath() + "/.config/latte"); QStringList filter; filter.append(QString(layoutName + "*.layout.latte")); QStringList files = layoutDir.entryList(filter, QDir::Files | QDir::NoSymLinks); //! if the newLayout already exists provide a newName that doesnt if (files.count() >= 1) { int newCounter = files.count() + 1; layoutName = layoutName + "-" + QString::number(newCounter); } QString newLayoutPath = layoutDir.absolutePath() + "/" + layoutName + ".layout.latte"; qDebug() << "adding layout : " << layoutName << " based on preset:" << preset; if (preset == i18n("Default") && !QFile(newLayoutPath).exists()) { qDebug() << "adding layout : succeed"; QFile(m_corona->kPackage().filePath("preset1")).copy(newLayoutPath); } return newLayoutPath; } //! This function figures in the beginning if a dock with tasks //! in it will be loaded taking into account also the screens are present. bool LayoutManager::heuresticForLoadingDockWithTasks(int *firstContainmentWithTasks) { foreach (auto containment, m_corona->containments()) { QString plugin = containment->pluginMetaData().pluginId(); if (plugin == "org.kde.latte.containment") { bool onPrimary = containment->config().readEntry("onPrimary", true); int lastScreen = containment->lastScreen(); qDebug() << "containment values: " << onPrimary << " - " << lastScreen; bool containsTasks = false; foreach (auto applet, containment->applets()) { const auto &provides = KPluginMetaData::readStringList(applet->pluginMetaData().rawData(), QStringLiteral("X-Plasma-Provides")); if (provides.contains(QLatin1String("org.kde.plasma.multitasking"))) { containsTasks = true; break; } } if (containsTasks) { *firstContainmentWithTasks = containment->id(); if (onPrimary) { return true; } else { if (lastScreen >= 0) { QString connector = m_corona->screenPool()->connector(lastScreen); foreach (auto scr, qGuiApp->screens()) { if (scr && scr->name() == connector) { return true; break; } } } } } } } return false; } void LayoutManager::importDefaultLayout(bool newInstanceIfPresent) { importPreset(1, newInstanceIfPresent); if (newInstanceIfPresent) { loadLayouts(); } } void LayoutManager::importPresets(bool includeDefault) { int start = 1; if (!includeDefault) { start = 2; } for (int i = start; i <= 4; ++i) { importPreset(i, false); } } void LayoutManager::importPreset(int presetNo, bool newInstanceIfPresent) { QByteArray presetNameOrig = QString("preset" + QString::number(presetNo)).toUtf8(); QString presetPath = m_corona->kPackage().filePath(presetNameOrig); QString presetName = Layout::layoutName(presetPath); QByteArray presetNameChars = presetName.toUtf8(); presetName = i18n(presetNameChars); //! hide the multiple layouts layout file from user presetName = (presetNo == MultipleLayoutsPresetId) ? "." + presetName : presetName; QString newLayoutFile = ""; if (newInstanceIfPresent) { newLayoutFile = QDir::homePath() + "/.config/latte/" + m_importer->uniqueLayoutName(presetName) + ".layout.latte"; } else { newLayoutFile = QDir::homePath() + "/.config/latte/" + presetName + ".layout.latte"; } if (!QFile(newLayoutFile).exists()) { QFile(presetPath).copy(newLayoutFile); QFileInfo newFileInfo(newLayoutFile); if (newFileInfo.exists() && !newFileInfo.isWritable()) { QFile(newLayoutFile).setPermissions(QFileDevice::ReadUser | QFileDevice::WriteUser | QFileDevice::ReadGroup | QFileDevice::ReadOther); } } } QStringList LayoutManager::validActivities(QStringList currentList) { QStringList validIds; foreach (auto activity, currentList) { if (activities().contains(activity)) { validIds.append(activity); } } return validIds; } bool LayoutManager::layoutIsAssigned(QString layoutName) { QHashIterator i(m_assignedLayouts); while (i.hasNext()) { i.next(); if (i.value() == layoutName) { return true; } } return false; } void LayoutManager::showLatteSettingsDialog(int page) { if (!m_latteSettingsDialog) { m_latteSettingsDialog = new SettingsDialog(nullptr, m_corona); } m_latteSettingsDialog->show(); if (m_latteSettingsDialog->isMinimized()) { m_latteSettingsDialog->showNormal(); } Dock::LatteConfigPage configPage = static_cast(page); m_latteSettingsDialog->setCurrentPage(configPage); m_latteSettingsDialog->activateWindow(); } void LayoutManager::hideLatteSettingsDialog() { if (m_latteSettingsDialog) { m_latteSettingsDialog->deleteLater(); m_latteSettingsDialog = nullptr; } } void LayoutManager::showInfoWindow(QString info, int duration, QStringList activities) { foreach (auto screen, qGuiApp->screens()) { InfoView *infoView = new InfoView(m_corona, info, screen); infoView->show(); infoView->setOnActivities(activities); QTimer::singleShot(duration, [this, infoView]() { infoView->deleteLater(); }); } } void LayoutManager::updateColorizerSupport() { bool enable{false}; foreach (auto layout, m_activeLayouts) { for (const auto *view : *layout->dockViews()) { if (view->colorizerSupport()) { enable = true; break; } } if (enable) { break; } } if (enable) { m_corona->universalSettings()->enableActivitiesModel(); } else { m_corona->universalSettings()->disableActivitiesModel(); } } //! it is used just in order to provide translations for the presets void LayoutManager::ghostForTranslatedPresets() { QString preset1 = i18n("Default"); QString preset2 = i18n("Plasma"); QString preset3 = i18n("Unity"); QString preset4 = i18n("Extended"); } } diff --git a/app/screenpool.cpp b/app/screenpool.cpp index 7b401897..1904ae06 100644 --- a/app/screenpool.cpp +++ b/app/screenpool.cpp @@ -1,283 +1,298 @@ /* * Copyright 2016 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program 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 Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "screenpool.h" #include #include #include #include #include #if HAVE_X11 #include #include #include #include #endif namespace Latte { ScreenPool::ScreenPool(KSharedConfig::Ptr config, QObject *parent) : QObject(parent), m_configGroup(KConfigGroup(config, QStringLiteral("ScreenConnectors"))) { qApp->installNativeEventFilter(this); m_configSaveTimer.setSingleShot(true); connect(&m_configSaveTimer, &QTimer::timeout, this, [this]() { m_configGroup.sync(); }); } void ScreenPool::load() { m_primaryConnector = QString(); m_connectorForId.clear(); m_idForConnector.clear(); QScreen *primary = qGuiApp->primaryScreen(); if (primary) { m_primaryConnector = primary->name(); if (!m_primaryConnector.isEmpty()) { //m_connectorForId[0] = m_primaryConnector; //m_idForConnector[m_primaryConnector] = 0; } } //restore the known ids to connector mappings foreach (const QString &key, m_configGroup.keyList()) { QString connector = m_configGroup.readEntry(key, QString()); qDebug() << "connector :" << connector << " - " << key; if (!key.isEmpty() && !connector.isEmpty() && !m_connectorForId.contains(key.toInt()) && !m_idForConnector.contains(connector)) { m_connectorForId[key.toInt()] = connector; m_idForConnector[connector] = key.toInt(); qDebug() << "Known Screen - " << connector << " - " << key.toInt(); } else if (m_idForConnector.value(connector) != key.toInt()) { m_configGroup.deleteEntry(key); } } // if there are already connected unknown screens, map those // all needs to be populated as soon as possible, otherwise // containment->screen() will return an incorrect -1 // at startup, if it' asked before corona::addOutput() // is performed, driving to the creation of a new containment for (QScreen *screen : qGuiApp->screens()) { if (!m_idForConnector.contains(screen->name())) { insertScreenMapping(firstAvailableId(), screen->name()); } } } ScreenPool::~ScreenPool() { m_configGroup.sync(); } void ScreenPool::reload(QString path) { QFile rcfile(QString(path + "/lattedockrc")); if (rcfile.exists()) { qDebug() << "load screen connectors from ::: " << rcfile.fileName(); KSharedConfigPtr newFile = KSharedConfig::openConfig(rcfile.fileName()); m_configGroup = KConfigGroup(newFile, QStringLiteral("ScreenConnectors")); load(); } } int ScreenPool::primaryScreenId() const { return id(qGuiApp->primaryScreen()->name()); } QString ScreenPool::primaryConnector() const { return m_primaryConnector; } void ScreenPool::setPrimaryConnector(const QString &primary) { //the ":" check fixes the strange plasma/qt issues when changing layouts //there are case that the QScreen instead of the correct screen name //returns "0:0", this check prevents from breaking the screens database //from garbage ids if ((m_primaryConnector == primary) || primary.startsWith(":")) { return; } Q_ASSERT(m_idForConnector.contains(primary)); /* int oldIdForPrimary = m_idForConnector.value(primary); m_idForConnector[primary] = 0; m_connectorForId[0] = primary; m_idForConnector[m_primaryConnector] = oldIdForPrimary; m_connectorForId[oldIdForPrimary] = m_primaryConnector; m_primaryConnector = primary; */ save(); } void ScreenPool::save() { QMap::const_iterator i; for (i = m_connectorForId.constBegin(); i != m_connectorForId.constEnd(); ++i) { m_configGroup.writeEntry(QString::number(i.key()), i.value()); } //write to disck every 30 seconds at most m_configSaveTimer.start(30000); } void ScreenPool::insertScreenMapping(int id, const QString &connector) { //Q_ASSERT(!m_connectorForId.contains(id) || m_connectorForId.value(id) == connector); //Q_ASSERT(!m_idForConnector.contains(connector) || m_idForConnector.value(connector) == id); //the ":" check fixes the strange plasma/qt issues when changing layouts //there are case that the QScreen instead of the correct screen name //returns "0:0", this check prevents from breaking the screens database //from garbage ids if (connector.startsWith(":")) return; qDebug() << "add connector..." << connector; if (id == 0) { m_primaryConnector = connector; } else { m_connectorForId[id] = connector; m_idForConnector[connector] = id; } save(); } int ScreenPool::id(const QString &connector) const { if (!m_idForConnector.contains(connector)) { return -1; } return m_idForConnector.value(connector); } QString ScreenPool::connector(int id) const { Q_ASSERT(m_connectorForId.contains(id)); return m_connectorForId.value(id); } int ScreenPool::firstAvailableId() const { //start counting from 10, first numbers will //be used for special cases. //e.g primaryScreen, id=0 int i = 10; //find the first integer not stored in m_connectorForId //m_connectorForId is the only map, so the ids are sorted foreach (int existingId, m_connectorForId.keys()) { if (i != existingId) { return i; } ++i; } return i; } QList ScreenPool::knownIds() const { return m_connectorForId.keys(); } +bool ScreenPool::screenExists(int id) +{ + if (id != -1 && knownIds().contains(id)) { + QString scrName = connector(id); + + foreach (auto scr, qGuiApp->screens()) { + if (scr->name() == scrName) { + return true; + } + } + } + + return false; +} + QScreen *ScreenPool::screenForId(int id) { const auto screens = qGuiApp->screens(); QScreen *screen{qGuiApp->primaryScreen()}; if (id != -1 && knownIds().contains(id)) { QString scrName = connector(id); foreach (auto scr, screens) { if (scr->name() == scrName) { return scr; } } } return screen; } bool ScreenPool::nativeEventFilter(const QByteArray &eventType, void *message, long int *result) { Q_UNUSED(result); #if HAVE_X11 // a particular edge case: when we switch the only enabled screen // we don't have any signal about it, the primary screen changes but we have the same old QScreen* getting recycled // see https://bugs.kde.org/show_bug.cgi?id=373880 // if this slot will be invoked many times, their//second time on will do nothing as name and primaryconnector will be the same by then if (eventType != "xcb_generic_event_t") { return false; } xcb_generic_event_t *ev = static_cast(message); const auto responseType = XCB_EVENT_RESPONSE_TYPE(ev); const xcb_query_extension_reply_t *reply = xcb_get_extension_data(QX11Info::connection(), &xcb_randr_id); if (responseType == reply->first_event + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { if (qGuiApp->primaryScreen()->name() != primaryConnector()) { //new screen? if (id(qGuiApp->primaryScreen()->name()) < 0) { insertScreenMapping(firstAvailableId(), qGuiApp->primaryScreen()->name()); } //switch the primary screen in the pool setPrimaryConnector(qGuiApp->primaryScreen()->name()); emit primaryPoolChanged(); } } #endif return false; } } #include "moc_screenpool.cpp" diff --git a/app/screenpool.h b/app/screenpool.h index d9d20ac0..826c467b 100644 --- a/app/screenpool.h +++ b/app/screenpool.h @@ -1,83 +1,84 @@ /* * Copyright 2016 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program 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 Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef SCREENPOOL_H #define SCREENPOOL_H #include #include #include #include #include #include #include #include namespace Latte { class ScreenPool : public QObject, public QAbstractNativeEventFilter { Q_OBJECT public: ScreenPool(KSharedConfig::Ptr config, QObject *parent = nullptr); void load(); ~ScreenPool() override; + bool screenExists(int id); int primaryScreenId() const; QString primaryConnector() const; void setPrimaryConnector(const QString &primary); void insertScreenMapping(int id, const QString &connector); void reload(QString path); int id(const QString &connector) const; QString connector(int id) const; int firstAvailableId() const; //all ids that are known, included screens not enabled at the moment QList knownIds() const; QScreen *screenForId(int id); signals: void primaryPoolChanged(); protected: bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) Q_DECL_OVERRIDE; private: void save(); KConfigGroup m_configGroup; QString m_primaryConnector; //order is important QMap m_connectorForId; QHash m_idForConnector; QTimer m_configSaveTimer; }; } #endif // SCREENPOOL_H