diff --git a/app/view/positioner.cpp b/app/view/positioner.cpp index 8c334463..9fbcdfb6 100644 --- a/app/view/positioner.cpp +++ b/app/view/positioner.cpp @@ -1,647 +1,657 @@ /* * Copyright 2018 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 "positioner.h" // local #include "effects.h" #include "view.h" #include "../lattecorona.h" #include "../screenpool.h" #include "../settings/universalsettings.h" #include "../../liblatte2/types.h" // Qt #include // KDE #include #include #include namespace Latte { namespace ViewPart { Positioner::Positioner(Latte::View *parent) : QObject(parent), m_view(parent) { m_screenSyncTimer.setSingleShot(true); m_screenSyncTimer.setInterval(2000); connect(&m_screenSyncTimer, &QTimer::timeout, this, &Positioner::reconsiderScreen); //! under X11 it was identified that windows many times especially under screen changes //! don't end up at the correct position and size. This timer will enforce repositionings //! and resizes every 500ms if the window hasn't end up to correct values and until this //! is achieved m_validateGeometryTimer.setSingleShot(true); m_validateGeometryTimer.setInterval(500); connect(&m_validateGeometryTimer, &QTimer::timeout, this, &Positioner::syncGeometry); - auto *latteCorona = qobject_cast(m_view->corona()); + m_corona = qobject_cast(m_view->corona()); - if (latteCorona) { - m_screenSyncTimer.setInterval(qMax(latteCorona->universalSettings()->screenTrackerInterval() - 500, 1000)); - connect(latteCorona->universalSettings(), &UniversalSettings::screenTrackerIntervalChanged, this, [this, latteCorona]() { - m_screenSyncTimer.setInterval(qMax(latteCorona->universalSettings()->screenTrackerInterval() - 500, 1000)); + if (m_corona) { + if (KWindowSystem::isPlatformX11()) { + m_corona->wm()->registerIgnoredWindow(m_view->winId()); + } + + m_screenSyncTimer.setInterval(qMax(m_corona->universalSettings()->screenTrackerInterval() - 500, 1000)); + connect(m_corona->universalSettings(), &UniversalSettings::screenTrackerIntervalChanged, this, [&]() { + m_screenSyncTimer.setInterval(qMax(m_corona->universalSettings()->screenTrackerInterval() - 500, 1000)); }); - connect(latteCorona, &Latte::Corona::viewLocationChanged, this, [&]() { + connect(m_corona, &Latte::Corona::viewLocationChanged, this, [&]() { //! check if an edge has been freed for a primary dock //! from another screen if (m_view->onPrimary()) { m_screenSyncTimer.start(); } }); } init(); } Positioner::~Positioner() { m_inDelete = true; + m_corona->wm()->unregisterIgnoredWindow(KWindowSystem::isPlatformX11() ? m_view->winId() : m_waylandWindowId); m_screenSyncTimer.stop(); m_validateGeometryTimer.stop(); } void Positioner::init() { //! connections connect(this, &Positioner::screenGeometryChanged, this, &Positioner::syncGeometry); connect(m_view, &QQuickWindow::xChanged, this, &Positioner::validateDockGeometry); connect(m_view, &QQuickWindow::yChanged, this, &Positioner::validateDockGeometry); connect(m_view, &QQuickWindow::widthChanged, this, &Positioner::validateDockGeometry); connect(m_view, &QQuickWindow::heightChanged, this, &Positioner::validateDockGeometry); connect(m_view, &QQuickWindow::screenChanged, this, &Positioner::currentScreenChanged); connect(m_view, &QQuickWindow::screenChanged, this, &Positioner::screenChanged); connect(m_view, &Latte::View::behaveAsPlasmaPanelChanged, this, &Positioner::syncGeometry); connect(m_view, &Latte::View::maxThicknessChanged, this, &Positioner::syncGeometry); connect(m_view, &Latte::View::maxLengthChanged, this, &Positioner::syncGeometry); connect(m_view, &Latte::View::offsetChanged, this, &Positioner::syncGeometry); connect(m_view, &Latte::View::absoluteGeometryChanged, this, [&]() { if (m_view->behaveAsPlasmaPanel()) { syncGeometry(); } }); connect(m_view, &Latte::View::locationChanged, this, [&]() { updateFormFactor(); syncGeometry(); }); connect(m_view, &Latte::View::normalThicknessChanged, this, [&]() { if (m_view->behaveAsPlasmaPanel()) { syncGeometry(); } }); connect(m_view->effects(), &Latte::ViewPart::Effects::drawShadowsChanged, this, [&]() { if (!m_view->behaveAsPlasmaPanel()) { syncGeometry(); } }); connect(m_view->effects(), &Latte::ViewPart::Effects::innerShadowChanged, this, [&]() { if (m_view->behaveAsPlasmaPanel()) { syncGeometry(); } }); connect(qGuiApp, &QGuiApplication::screenAdded, this, &Positioner::screenChanged); connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &Positioner::screenChanged); initSignalingForLocationChangeSliding(); } int Positioner::currentScreenId() const { auto *latteCorona = qobject_cast(m_view->corona()); if (latteCorona) { return latteCorona->screenPool()->id(m_screenToFollowId); } return -1; } QString Positioner::currentScreenName() const { return m_screenToFollowId; } bool Positioner::setCurrentScreen(const QString id) { QScreen *nextScreen{qGuiApp->primaryScreen()}; if (id != "primary") { for (const auto scr : qGuiApp->screens()) { if (scr && scr->name() == id) { nextScreen = scr; break; } } } if (m_screenToFollow == nextScreen) { return true; } if (nextScreen) { if (m_view->layout()) { auto freeEdges = m_view->layout()->freeEdges(nextScreen); if (!freeEdges.contains(m_view->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 Positioner::setScreenToFollow(QScreen *scr, bool updateScreenId) { if (!scr || (scr && (m_screenToFollow == scr) && (m_view->screen() == scr))) { return; } qDebug() << "setScreenToFollow() called for screen:" << scr->name() << " update:" << updateScreenId; m_screenToFollow = scr; if (updateScreenId) { m_screenToFollowId = scr->name(); } qDebug() << "adapting to screen..."; m_view->setScreen(scr); if (m_view->containment()) { m_view->containment()->reactToScreenChange(); } connect(scr, &QScreen::geometryChanged, this, &Positioner::screenGeometryChanged); syncGeometry(); m_view->updateAbsoluteGeometry(true); qDebug() << "setScreenToFollow() ended..."; emit screenGeometryChanged(); emit currentScreenChanged(); } //! the main function which decides if this dock is at the //! correct screen void Positioner::reconsiderScreen() { if (m_inDelete) { return; } qDebug() << "reconsiderScreen() called..."; qDebug() << " Delayer "; for (const auto scr : qGuiApp->screens()) { qDebug() << " D, found screen: " << scr->name(); } bool screenExists{false}; //!check if the associated screen is running for (const auto scr : qGuiApp->screens()) { if (m_screenToFollowId == scr->name() || (m_view->onPrimary() && scr == qGuiApp->primaryScreen())) { screenExists = true; } } qDebug() << "dock screen exists ::: " << screenExists; //! 1.a primary dock must be always on the primary screen if (m_view->onPrimary() && (m_screenToFollowId != qGuiApp->primaryScreen()->name() || m_screenToFollow != qGuiApp->primaryScreen() || m_view->screen() != qGuiApp->primaryScreen())) { using Plasma::Types; QList edges{Types::BottomEdge, Types::LeftEdge, Types::TopEdge, Types::RightEdge}; edges = m_view->layout() ? m_view->layout()->availableEdgesForView(qGuiApp->primaryScreen(), m_view) : 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:" << m_view->location(); if (edges.contains(m_view->location())) { //! case 1 qDebug() << "reached case 1: of updating dock primary screen..."; setScreenToFollow(qGuiApp->primaryScreen()); } } else if (!m_view->onPrimary()) { //! 2.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 for (const auto scr : qGuiApp->screens()) { if (scr && scr->name() == m_screenToFollowId) { qDebug() << "reached case 2: updating the explicit screen for dock..."; setScreenToFollow(scr); break; } } } syncGeometry(); qDebug() << "reconsiderScreen() ended..."; } void Positioner::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_view->visibility() && m_view->visibility()->mode() == Latte::Types::AlwaysVisible) { m_view->updateAbsoluteGeometry(true); } } void Positioner::syncGeometry() { if (!(m_view->screen() && m_view->containment()) || m_inDelete) { return; } bool found{false}; qDebug() << "syncGeometry() called..."; + if (KWindowSystem::isPlatformWayland() && m_waylandWindowId.isNull()) { + m_waylandWindowId = m_corona->wm()->winIdFor("latte-dock", m_view->geometry()); + m_corona->wm()->registerIgnoredWindow(m_waylandWindowId); + } + //! before updating the positioning and geometry of the dock //! we make sure that the dock is at the correct screen if (m_view->screen() != m_screenToFollow) { qDebug() << "Sync Geometry screens inconsistent!!!! "; if (m_screenToFollow) { qDebug() << "Sync Geometry screens inconsistent for m_screenToFollow:" << m_screenToFollow->name() << " dock screen:" << m_view->screen()->name(); } if (!m_screenSyncTimer.isActive()) { 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{m_view->screen()->geometry()}; if (m_view->formFactor() == Plasma::Types::Vertical) { QString layoutName = m_view->layout() ? m_view->layout()->name() : QString(); auto latteCorona = qobject_cast(m_view->corona()); int fixedScreen = m_view->onPrimary() ? latteCorona->screenPool()->primaryScreenId() : m_view->containment()->screen(); freeRegion = latteCorona->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_view->behaveAsPlasmaPanel()) { m_view->effects()->setForceDrawCenteredBorders(true); } else { m_view->effects()->setForceDrawCenteredBorders(false); } } else { m_view->effects()->setForceDrawCenteredBorders(false); } m_view->effects()->updateEnabledBorders(); resizeWindow(availableScreenRect); updatePosition(availableScreenRect); qDebug() << "syncGeometry() calculations for screen: " << m_view->screen()->name() << " _ " << m_view->screen()->geometry(); qDebug() << "syncGeometry() calculations for edge: " << m_view->location(); } qDebug() << "syncGeometry() ended..."; // qDebug() << "dock geometry:" << qRectToStr(geometry()); } void Positioner::validateDockGeometry() { if (m_view->geometry() != m_validGeometry) { m_validateGeometryTimer.start(); } } //! 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 Positioner::maximumNormalGeometry() { int xPos = 0; int yPos = m_view->screen()->geometry().y();; int maxHeight = m_view->screen()->geometry().height(); int maxWidth = m_view->normalThickness(); QRect maxGeometry; maxGeometry.setRect(0, 0, maxWidth, maxHeight); switch (m_view->location()) { case Plasma::Types::LeftEdge: xPos = m_view->screen()->geometry().x(); maxGeometry.setRect(xPos, yPos, maxWidth, maxHeight); break; case Plasma::Types::RightEdge: xPos = m_view->screen()->geometry().right() - maxWidth + 1; 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 hasn't be set, //! such a case is the AlwaysVisible mode if (m_view->location() == Plasma::Types::TopEdge) { KWindowSystem::setState(m_view->winId(), NET::KeepAbove); } else { KWindowSystem::clearState(m_view->winId(), NET::KeepAbove); } return maxGeometry; } void Positioner::updatePosition(QRect availableScreenRect) { QRect screenGeometry{availableScreenRect}; QPoint position; position = {0, 0}; const auto length = [&](int length) -> int { float offs = static_cast(m_view->offset()); return static_cast(length * ((1 - m_view->maxLength()) / 2) + length * (offs / 100)); }; int cleanThickness = m_view->normalThickness() - m_view->effects()->innerShadow(); switch (m_view->location()) { case Plasma::Types::TopEdge: if (m_view->behaveAsPlasmaPanel()) { position = {screenGeometry.x() + length(screenGeometry.width()), screenGeometry.y()}; } else { position = {screenGeometry.x(), screenGeometry.y()}; } break; case Plasma::Types::BottomEdge: if (m_view->behaveAsPlasmaPanel()) { position = {screenGeometry.x() + length(screenGeometry.width()), screenGeometry.y() + screenGeometry.height() - cleanThickness }; } else { position = {screenGeometry.x(), screenGeometry.y() + screenGeometry.height() - m_view->height()}; } break; case Plasma::Types::RightEdge: if (m_view->behaveAsPlasmaPanel()) { position = {availableScreenRect.right() - cleanThickness + 1, availableScreenRect.y() + length(availableScreenRect.height()) }; } else { position = {availableScreenRect.right() - m_view->width() + 1, availableScreenRect.y()}; } break; case Plasma::Types::LeftEdge: if (m_view->behaveAsPlasmaPanel()) { 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" << m_view->location(); } m_validGeometry.setTopLeft(position); m_view->setPosition(position); if (m_view->surface()) { m_view->surface()->setPosition(position); } } void Positioner::resizeWindow(QRect availableScreenRect) { QSize screenSize = m_view->screen()->size(); QSize size = (m_view->formFactor() == Plasma::Types::Vertical) ? QSize(m_view->maxThickness(), availableScreenRect.height()) : QSize(screenSize.width(), m_view->maxThickness()); if (m_view->formFactor() == Plasma::Types::Vertical) { //qDebug() << "MAXIMUM RECT :: " << maximumRect << " - AVAILABLE RECT :: " << availableRect; if (m_view->behaveAsPlasmaPanel()) { size.setWidth(m_view->normalThickness()); size.setHeight(static_cast(m_view->maxLength() * availableScreenRect.height())); } } else { if (m_view->behaveAsPlasmaPanel()) { size.setWidth(static_cast(m_view->maxLength() * screenSize.width())); size.setHeight(m_view->normalThickness()); } } m_validGeometry.setSize(size); m_view->setMinimumSize(size); m_view->setMaximumSize(size); m_view->resize(size); if (m_view->formFactor() == Plasma::Types::Horizontal) { emit windowSizeChanged(); } } void Positioner::updateFormFactor() { if (!m_view->containment()) return; switch (m_view->location()) { case Plasma::Types::TopEdge: case Plasma::Types::BottomEdge: m_view->containment()->setFormFactor(Plasma::Types::Horizontal); break; case Plasma::Types::LeftEdge: case Plasma::Types::RightEdge: m_view->containment()->setFormFactor(Plasma::Types::Vertical); break; default: qWarning() << "wrong location, couldn't update the panel position" << m_view->location(); } } void Positioner::initSignalingForLocationChangeSliding() { //! signals to handle the sliding-in/out during location changes connect(this, &Positioner::hideDockDuringLocationChangeStarted, this, &Positioner::onHideWindowsForSlidingOut); connect(m_view, &View::locationChanged, this, [&]() { if (m_goToLocation != Plasma::Types::Floating) { m_goToLocation = Plasma::Types::Floating; QTimer::singleShot(100, [this]() { m_view->effects()->setAnimationsBlocked(false); emit showDockAfterLocationChangeFinished(); m_view->showSettingsWindow(); if (m_view->layout()) { //! This is needed in case the edge is occupied and the occupying //! view must be deleted m_view->layout()->syncLatteViewsToScreens(); } emit edgeChanged(); }); } }); //! signals to handle the sliding-in/out during screen changes connect(this, &Positioner::hideDockDuringScreenChangeStarted, this, &Positioner::onHideWindowsForSlidingOut); connect(this, &Positioner::currentScreenChanged, this, [&]() { if (m_goToScreen) { m_goToScreen = nullptr; QTimer::singleShot(100, [this]() { m_view->effects()->setAnimationsBlocked(false); emit showDockAfterScreenChangeFinished(); m_view->showSettingsWindow(); if (m_view->layout()) { //! This is needed in case the edge is occupied and the occupying //! view must be deleted m_view->layout()->syncLatteViewsToScreens(); } emit edgeChanged(); }); } }); //! signals to handle the sliding-in/out during moving to another layout connect(this, &Positioner::hideDockDuringMovingToLayoutStarted, this, &Positioner::onHideWindowsForSlidingOut); connect(m_view, &View::layoutChanged, this, [&]() { if (!m_moveToLayout.isEmpty() && m_view->layout()) { m_moveToLayout = ""; QTimer::singleShot(100, [this]() { m_view->effects()->setAnimationsBlocked(false); emit showDockAfterMovingToLayoutFinished(); m_view->showSettingsWindow(); emit edgeChanged(); }); } }); //! ---- 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, &Positioner::hideDockDuringLocationChangeFinished, this, [&]() { m_view->effects()->setAnimationsBlocked(true); if (m_goToLocation != Plasma::Types::Floating) { m_view->setLocation(m_goToLocation); } else if (m_goToScreen) { setScreenToFollow(m_goToScreen); } else if (!m_moveToLayout.isEmpty()) { m_view->moveToLayout(m_moveToLayout); } }); } bool Positioner::inLocationChangeAnimation() { return ((m_goToLocation != Plasma::Types::Floating) || (m_moveToLayout != "") || m_goToScreen); } void Positioner::hideDockDuringLocationChange(int goToLocation) { m_goToLocation = static_cast(goToLocation); emit hideDockDuringLocationChangeStarted(); } void Positioner::hideDockDuringMovingToLayout(QString layoutName) { m_moveToLayout = layoutName; emit hideDockDuringMovingToLayoutStarted(); } } } diff --git a/app/view/positioner.h b/app/view/positioner.h index df762337..84d8c7ee 100644 --- a/app/view/positioner.h +++ b/app/view/positioner.h @@ -1,127 +1,134 @@ /* * Copyright 2018 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 POSITIONER_H #define POSITIONER_H +//local +#include "../wm/windowinfowrap.h" + // Qt #include #include #include #include // Plasma #include namespace Plasma { class Types; } namespace Latte { +class Corona; class View; } namespace Latte { namespace ViewPart { class Positioner: public QObject { Q_OBJECT Q_PROPERTY(int currentScreenId READ currentScreenId NOTIFY currentScreenChanged) Q_PROPERTY(QString currentScreenName READ currentScreenName NOTIFY currentScreenChanged) public: Positioner(Latte::View *parent); virtual ~Positioner(); int currentScreenId() const; QString currentScreenName() const; bool inLocationChangeAnimation(); void setScreenToFollow(QScreen *scr, bool updateScreenId = true); void reconsiderScreen(); public slots: Q_INVOKABLE void hideDockDuringLocationChange(int goToLocation); Q_INVOKABLE void hideDockDuringMovingToLayout(QString layoutName); Q_INVOKABLE bool setCurrentScreen(const QString id); void syncGeometry(); signals: void currentScreenChanged(); void edgeChanged(); void screenGeometryChanged(); void windowSizeChanged(); //! these two signals are used from config ui and containment ui //! in order to orchestrate an animated hiding/showing of dock //! during changing location void hideDockDuringLocationChangeStarted(); void hideDockDuringLocationChangeFinished(); void hideDockDuringScreenChangeStarted(); void hideDockDuringScreenChangeFinished(); void hideDockDuringMovingToLayoutStarted(); void hideDockDuringMovingToLayoutFinished(); void onHideWindowsForSlidingOut(); void showDockAfterLocationChangeFinished(); void showDockAfterScreenChangeFinished(); void showDockAfterMovingToLayoutFinished(); private slots: void screenChanged(QScreen *screen); void validateDockGeometry(); private: void init(); void initSignalingForLocationChangeSliding(); void resizeWindow(QRect availableScreenRect = QRect()); void updateFormFactor(); void updatePosition(QRect availableScreenRect = QRect()); QRect maximumNormalGeometry(); private: bool m_inDelete{false}; //! it is used in order to enforce X11 to never miss window geometry QRect m_validGeometry; QPointer m_view; + QPointer m_corona; QString m_screenToFollowId; QPointer m_screenToFollow; QTimer m_screenSyncTimer; QTimer m_validateGeometryTimer; //!used at sliding out/in animation QString m_moveToLayout; Plasma::Types::Location m_goToLocation{Plasma::Types::Floating}; QScreen *m_goToScreen{nullptr}; + + Latte::WindowSystem::WindowId m_waylandWindowId; }; } } #endif diff --git a/app/view/screenedgeghostwindow.cpp b/app/view/screenedgeghostwindow.cpp index ea0a0343..04fd4c07 100644 --- a/app/view/screenedgeghostwindow.cpp +++ b/app/view/screenedgeghostwindow.cpp @@ -1,303 +1,316 @@ /* * Copyright 2018 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 "screenedgeghostwindow.h" // local #include "view.h" -#include "../lattecorona.h" // Qt #include #include #include #include // KDE #include #include #include // X11 #include namespace Latte { namespace ViewPart { ScreenEdgeGhostWindow::ScreenEdgeGhostWindow(Latte::View *view) : m_latteView(view) { + m_corona = qobject_cast(view->corona()); + setColor(QColor(Qt::transparent)); setDefaultAlphaBuffer(true); setFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::NoDropShadowWindowHint | Qt::WindowDoesNotAcceptFocus); m_fixGeometryTimer.setSingleShot(true); m_fixGeometryTimer.setInterval(500); connect(&m_fixGeometryTimer, &QTimer::timeout, this, &ScreenEdgeGhostWindow::fixGeometry); //! this timer is used in order to avoid fast enter/exit signals during first //! appearing after edge activation m_delayedMouseTimer.setSingleShot(true); m_delayedMouseTimer.setInterval(50); connect(&m_delayedMouseTimer, &QTimer::timeout, this, [this]() { if (m_delayedContainsMouse) { setContainsMouse(true); } else { setContainsMouse(false); } }); connect(this, &QQuickView::xChanged, this, &ScreenEdgeGhostWindow::startGeometryTimer); connect(this, &QQuickView::yChanged, this, &ScreenEdgeGhostWindow::startGeometryTimer); connect(this, &QQuickView::widthChanged, this, &ScreenEdgeGhostWindow::startGeometryTimer); connect(this, &QQuickView::heightChanged, this, &ScreenEdgeGhostWindow::startGeometryTimer); connect(m_latteView, &Latte::View::absoluteGeometryChanged, this, &ScreenEdgeGhostWindow::updateGeometry); connect(m_latteView, &Latte::View::screenGeometryChanged, this, &ScreenEdgeGhostWindow::updateGeometry); connect(m_latteView, &Latte::View::locationChanged, this, &ScreenEdgeGhostWindow::updateGeometry); connect(m_latteView, &QQuickView::screenChanged, this, [this]() { setScreen(m_latteView->screen()); updateGeometry(); }); if (!KWindowSystem::isPlatformWayland()) { //! IMPORTANT!!! ::: This fixes a bug when closing an Activity all views from all Activities are //! disappearing! With this code parts they reappear!!! m_visibleHackTimer1.setInterval(400); m_visibleHackTimer2.setInterval(2500); m_visibleHackTimer1.setSingleShot(true); m_visibleHackTimer2.setSingleShot(true); connectionsHack << connect(this, &QWindow::visibleChanged, this, [&]() { if (!m_inDelete && m_latteView && m_latteView->layout() && !isVisible()) { m_visibleHackTimer1.start(); m_visibleHackTimer2.start(); } else if (!m_inDelete) { //! For some reason when the window is hidden in the edge under X11 afterwards //! is losing its window flags KWindowSystem::setType(winId(), NET::Dock); KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager); KWindowSystem::setOnAllDesktops(winId(), true); } }); connectionsHack << connect(&m_visibleHackTimer1, &QTimer::timeout, this, [&]() { if (!m_inDelete && m_latteView && m_latteView->layout() && !isVisible()) { setVisible(true); //qDebug() << "Ghost Edge:: Enforce reshow from timer 1..."; } else { //qDebug() << "Ghost Edge:: No needed reshow from timer 1..."; } }); connectionsHack << connect(&m_visibleHackTimer2, &QTimer::timeout, this, [&]() { if (!m_inDelete && m_latteView && m_latteView->layout() && !isVisible()) { setVisible(true); //qDebug() << "Ghost Edge:: Enforce reshow from timer 2..."; } else { //qDebug() << "Ghost Edge:: No needed reshow from timer 2..."; } }); } setupWaylandIntegration(); + if (KWindowSystem::isPlatformX11()) { + m_corona->wm()->registerIgnoredWindow(winId()); + } + setScreen(m_latteView->screen()); setVisible(true); updateGeometry(); hideWithMask(); } ScreenEdgeGhostWindow::~ScreenEdgeGhostWindow() { m_inDelete = true; + + m_corona->wm()->unregisterIgnoredWindow(KWindowSystem::isPlatformX11() ? winId() : m_waylandWindowId); + m_latteView = nullptr; // clear mode m_visibleHackTimer1.stop(); m_visibleHackTimer2.stop(); for (auto &c : connectionsHack) { disconnect(c); } if (m_shellSurface) { delete m_shellSurface; } } int ScreenEdgeGhostWindow::location() { return (int)m_latteView->location(); } Latte::View *ScreenEdgeGhostWindow::parentView() { return m_latteView; } KWayland::Client::PlasmaShellSurface *ScreenEdgeGhostWindow::surface() { return m_shellSurface; } void ScreenEdgeGhostWindow::updateGeometry() { + if (KWindowSystem::isPlatformWayland() && m_waylandWindowId.isNull()) { + m_waylandWindowId = m_corona->wm()->winIdFor("latte-dock", geometry()); + m_corona->wm()->registerIgnoredWindow(m_waylandWindowId); + } + QRect newGeometry; int thickness; if (KWindowSystem::compositingActive()) { thickness == 6; } else { thickness == 2; }; if (m_latteView->location() == Plasma::Types::BottomEdge) { newGeometry.setX(m_latteView->absoluteGeometry().left()); newGeometry.setY(m_latteView->screenGeometry().bottom() - thickness); } else if (m_latteView->location() == Plasma::Types::TopEdge) { newGeometry.setX(m_latteView->absoluteGeometry().left()); newGeometry.setY(m_latteView->screenGeometry().top()); } else if (m_latteView->location() == Plasma::Types::LeftEdge) { newGeometry.setX(m_latteView->screenGeometry().left()); newGeometry.setY(m_latteView->absoluteGeometry().top()); } else if (m_latteView->location() == Plasma::Types::RightEdge) { newGeometry.setX(m_latteView->screenGeometry().right() - thickness); newGeometry.setY(m_latteView->absoluteGeometry().top()); } if (m_latteView->formFactor() == Plasma::Types::Horizontal) { newGeometry.setWidth(qMin(m_latteView->absoluteGeometry().width(), m_latteView->screenGeometry().width() - 1)); newGeometry.setHeight(thickness + 1); } else { newGeometry.setWidth(thickness + 1); newGeometry.setHeight(qMin(m_latteView->absoluteGeometry().height(), m_latteView->screenGeometry().height() - 1)); } m_calculatedGeometry = newGeometry; fixGeometry(); } void ScreenEdgeGhostWindow::fixGeometry() { if (!m_calculatedGeometry.isEmpty() && (m_calculatedGeometry.x() != x() || m_calculatedGeometry.y() != y() || m_calculatedGeometry.width() != width() || m_calculatedGeometry.height() != height())) { setMinimumSize(m_calculatedGeometry.size()); setMaximumSize(m_calculatedGeometry.size()); resize(m_calculatedGeometry.size()); setPosition(m_calculatedGeometry.x(), m_calculatedGeometry.y()); if (m_shellSurface) { m_shellSurface->setPosition(m_calculatedGeometry.topLeft()); } } } void ScreenEdgeGhostWindow::startGeometryTimer() { m_fixGeometryTimer.start(); } void ScreenEdgeGhostWindow::setupWaylandIntegration() { if (m_shellSurface || !KWindowSystem::isPlatformWayland() || !m_latteView || !m_latteView->containment()) { // already setup return; } - if (Latte::Corona *c = qobject_cast(m_latteView->containment()->corona())) { + if (m_corona) { using namespace KWayland::Client; - PlasmaShell *interface = c->waylandCoronaInterface(); + PlasmaShell *interface = m_corona->waylandCoronaInterface(); if (!interface) { return; } Surface *s = Surface::fromWindow(this); if (!s) { return; } qDebug() << "wayland screen edge ghost window surface was created..."; m_shellSurface = interface->createSurface(s, this); m_shellSurface->setSkipTaskbar(true); m_shellSurface->setPanelTakesFocus(false); m_shellSurface->setRole(PlasmaShellSurface::Role::Panel); m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AutoHide); } } bool ScreenEdgeGhostWindow::containsMouse() const { return m_containsMouse; } void ScreenEdgeGhostWindow::setContainsMouse(bool contains) { if (m_containsMouse == contains) { return; } m_containsMouse = contains; emit containsMouseChanged(contains); } bool ScreenEdgeGhostWindow::event(QEvent *e) { if (e->type() == QEvent::DragEnter) { m_delayedContainsMouse = false; m_delayedMouseTimer.stop(); setContainsMouse(true); emit dragEntered(); } else if (e->type() == QEvent::Enter || e->type() == QEvent::DragEnter) { m_delayedContainsMouse = true; if (!m_delayedMouseTimer.isActive()) { m_delayedMouseTimer.start(); } } else if (e->type() == QEvent::Leave || e->type() == QEvent::DragLeave) { m_delayedContainsMouse = false; if (!m_delayedMouseTimer.isActive()) { m_delayedMouseTimer.start(); } } return QQuickView::event(e); } void ScreenEdgeGhostWindow::hideWithMask() { QRect maskGeometry{0, 0, 1, 1}; setMask(maskGeometry); } void ScreenEdgeGhostWindow::showWithMask() { setMask(QRect()); } } } diff --git a/app/view/screenedgeghostwindow.h b/app/view/screenedgeghostwindow.h index f306338f..80ff4fc6 100644 --- a/app/view/screenedgeghostwindow.h +++ b/app/view/screenedgeghostwindow.h @@ -1,117 +1,125 @@ /* * Copyright 2018 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 SCREENEDGEGHOSTWINDOW_H #define SCREENEDGEGHOSTWINDOW_H +// local +#include "../lattecorona.h" +#include "../wm/windowinfowrap.h" + // Qt #include #include #include namespace KWayland { namespace Client { class PlasmaShellSurface; } } namespace Latte { +class Corona; class View; } namespace Latte { namespace ViewPart { //! What is the importance of this class? //! //! Plasma is activating the screen edges for the main panel window //! unfortunately this isn't possible for the Latte case. //! When a window is hidden at an edge it becomes NOT visible //! unfortunately that means that all the animations are //! stopped (Qt behaviour) and that creates confusion to the user after the window //! reappears because various animations are played (adding-removing tasks/launchers) //! that aren't relevant any more. //! //! In order to workaround the above behaviour Latte is using a //! fake window to communicate with KWin and the MAIN Latte::View window //! continues to use only mask technique to hide //! //! KDE BUGS: https://bugs.kde.org/show_bug.cgi?id=382219 //! https://bugs.kde.org/show_bug.cgi?id=392464 class ScreenEdgeGhostWindow : public QQuickView { Q_OBJECT public: ScreenEdgeGhostWindow(Latte::View *view); ~ScreenEdgeGhostWindow() override; bool containsMouse() const; int location(); void hideWithMask(); void showWithMask(); Latte::View *parentView(); KWayland::Client::PlasmaShellSurface *surface(); signals: void containsMouseChanged(bool contains); void dragEntered(); protected: bool event(QEvent *ev) override; private slots: void startGeometryTimer(); void updateGeometry(); void fixGeometry(); private: void setContainsMouse(bool contains); void setupWaylandIntegration(); private: bool m_delayedContainsMouse{false}; bool m_containsMouse{false}; bool m_inDelete{false}; QRect m_calculatedGeometry; QTimer m_delayedMouseTimer; QTimer m_fixGeometryTimer; //! HACK: Timers in order to handle KWin faulty //! behavior that hides Views when closing Activities //! with no actual reason QTimer m_visibleHackTimer1; QTimer m_visibleHackTimer2; //! Connections for the KWin visibility hack QList connectionsHack; Latte::View *m_latteView{nullptr}; + QPointer m_corona; + + Latte::WindowSystem::WindowId m_waylandWindowId; KWayland::Client::PlasmaShellSurface *m_shellSurface{nullptr}; }; } } #endif diff --git a/app/view/settings/primaryconfigview.cpp b/app/view/settings/primaryconfigview.cpp index f471b7fa..2ed95e4e 100644 --- a/app/view/settings/primaryconfigview.cpp +++ b/app/view/settings/primaryconfigview.cpp @@ -1,728 +1,734 @@ /* * 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 "primaryconfigview.h" // local #include #include "secondaryconfigview.h" #include "../effects.h" #include "../panelshadows_p.h" #include "../view.h" #include "../../lattecorona.h" #include "../../layouts/manager.h" #include "../../layout/genericlayout.h" #include "../../settings/universalsettings.h" #include "../../shortcuts/globalshortcuts.h" #include "../../shortcuts/shortcutstracker.h" #include "../../wm/abstractwindowinterface.h" // Qt #include #include #include #include #include // KDE #include #include #include #include #include // Plasma #include namespace Latte { namespace ViewPart { PrimaryConfigView::PrimaryConfigView(Plasma::Containment *containment, Latte::View *view, QWindow *parent) : PlasmaQuick::ConfigView(containment, parent), m_latteView(view) { m_corona = qobject_cast(m_latteView->containment()->corona()); setupWaylandIntegration(); if (KWindowSystem::isPlatformX11()) { m_corona->wm()->registerIgnoredWindow(winId()); } setScreen(m_latteView->screen()); if (containment) { setIcon(qGuiApp->windowIcon()); } m_screenSyncTimer.setSingleShot(true); m_screenSyncTimer.setInterval(100); connect(this, &PrimaryConfigView::availableScreenGeometryChanged, this, &PrimaryConfigView::syncGeometry); connect(this, &PrimaryConfigView::complexityChanged, this, &PrimaryConfigView::saveConfig); connect(this, &PrimaryConfigView::complexityChanged, this, &PrimaryConfigView::updateShowInlineProperties); connect(this, &PrimaryConfigView::complexityChanged, this, &PrimaryConfigView::syncGeometry); connections << connect(&m_screenSyncTimer, &QTimer::timeout, this, [this]() { setScreen(m_latteView->screen()); setFlags(wFlags()); if (KWindowSystem::isPlatformX11()) { #if KF5_VERSION_MINOR >= 45 KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager | NET::SkipSwitcher); #else KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager); #endif KWindowSystem::setOnAllDesktops(winId(), true); } syncGeometry(); syncSlideEffect(); }); connections << connect(m_latteView->visibility(), &VisibilityManager::modeChanged, this, &PrimaryConfigView::syncGeometry); connections << connect(containment, &Plasma::Containment::immutabilityChanged, this, &PrimaryConfigView::immutabilityChanged); m_thicknessSyncTimer.setSingleShot(true); m_thicknessSyncTimer.setInterval(200); connections << connect(&m_thicknessSyncTimer, &QTimer::timeout, this, [this]() { syncGeometry(); }); connections << connect(m_latteView, &Latte::View::normalThicknessChanged, [&]() { m_thicknessSyncTimer.start(); }); connections << connect(m_latteView, &Latte::View::availableScreenRectChangedForViewParts, this, &PrimaryConfigView::updateAvailableScreenGeometry); if (m_corona) { connections << connect(m_corona, &Latte::Corona::raiseViewsTemporaryChanged, this, &PrimaryConfigView::raiseDocksTemporaryChanged); } if (m_latteView->layout()) { emit m_latteView->layout()->setLastConfigViewFor(m_latteView); } } PrimaryConfigView::~PrimaryConfigView() { qDebug() << "ConfigView deleting ..."; - if (KWindowSystem::isPlatformX11()) { - m_corona->wm()->unregisterIgnoredWindow(winId()); - } + m_corona->wm()->unregisterIgnoredWindow(KWindowSystem::isPlatformX11() ? winId() : m_waylandWindowId); deleteSecondaryWindow(); for (const auto &var : connections) { QObject::disconnect(var); } } void PrimaryConfigView::init() { qDebug() << "dock config view : initialization started..."; m_originalByPassWM = m_latteView->byPassWM(); m_originalMode = m_latteView->visibility()->mode(); loadConfig(); setDefaultAlphaBuffer(true); setColor(Qt::transparent); PanelShadows::self()->addWindow(this); rootContext()->setContextProperty(QStringLiteral("latteView"), m_latteView); rootContext()->setContextProperty(QStringLiteral("shortcutsEngine"), m_corona->globalShortcuts()->shortcutsTracker()); rootContext()->setContextProperty(QStringLiteral("viewConfig"), this); if (m_corona) { rootContext()->setContextProperty(QStringLiteral("universalSettings"), m_corona->universalSettings()); rootContext()->setContextProperty(QStringLiteral("layoutsManager"), m_corona->layoutsManager()); } KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(engine()); kdeclarative.setTranslationDomain(QStringLiteral("latte-dock")); #if KF5_VERSION_MINOR >= 45 kdeclarative.setupContext(); kdeclarative.setupEngine(engine()); #else kdeclarative.setupBindings(); #endif QByteArray tempFilePath = "lattedockconfigurationui"; updateEnabledBorders(); updateAvailableScreenGeometry(); auto source = QUrl::fromLocalFile(m_latteView->containment()->corona()->kPackage().filePath(tempFilePath)); setSource(source); syncGeometry(); syncSlideEffect(); qDebug() << "dock config view : initialization ended..."; } inline Qt::WindowFlags PrimaryConfigView::wFlags() const { return (flags() | Qt::FramelessWindowHint /*| Qt::WindowStaysOnTopHint*/) & ~Qt::WindowDoesNotAcceptFocus; } QQuickView *PrimaryConfigView::secondaryWindow() { return m_secConfigView; } void PrimaryConfigView::createSecondaryWindow() { if (m_secConfigView) { return; } m_secConfigView = new SecondaryConfigView(m_latteView, this); m_secConfigView->init(); } void PrimaryConfigView::deleteSecondaryWindow() { if (m_secConfigView) { auto secWindow = m_secConfigView; m_secConfigView = nullptr; secWindow->deleteLater(); if (KWindowSystem::isPlatformX11()) { //! this is needed in order for subtracked mask of secondary window to //! be released properly when changing for Advanced to Basic mode. //! Under wayland this is not needed because masks do not break any visuals. m_latteView->effects()->updateMask(); } } } void PrimaryConfigView::updateAvailableScreenGeometry() { int currentScrId = m_latteView->positioner()->currentScreenId(); m_availableScreenGeometry = m_corona->availableScreenRect(currentScrId); emit availableScreenGeometryChanged(); } QRect PrimaryConfigView::availableScreenGeometry() const { return m_availableScreenGeometry; } QRect PrimaryConfigView::geometryWhenVisible() const { return m_geometryWhenVisible; } void PrimaryConfigView::requestActivate() { if (KWindowSystem::isPlatformWayland() && m_shellSurface) { - WindowSystem::WindowId wid = m_corona->wm()->winIdFor("latte-dock", geometry()); - m_corona->wm()->requestActivate(wid); + if (m_waylandWindowId.isNull()) { + m_waylandWindowId = m_corona->wm()->winIdFor("latte-dock", geometry()); + } + + m_corona->wm()->requestActivate(m_waylandWindowId); } else { QQuickView::requestActivate(); } } void PrimaryConfigView::syncGeometry() { if (!m_latteView || !m_latteView->layout() || !m_latteView->containment() || !rootObject()) { return; } + if (KWindowSystem::isPlatformWayland() && m_waylandWindowId.isNull()) { + m_waylandWindowId = m_corona->wm()->winIdFor("latte-dock", geometry()); + m_corona->wm()->registerIgnoredWindow(m_waylandWindowId); + } + const QSize size(rootObject()->width(), rootObject()->height()); setMaximumSize(size); setMinimumSize(size); resize(size); const auto location = m_latteView->containment()->location(); const auto scrGeometry = m_latteView->screenGeometry(); const auto availGeometry = m_availableScreenGeometry; int clearThickness = m_latteView->editThickness(); QPoint position{0, 0}; int xPos{0}; int yPos{0}; switch (m_latteView->formFactor()) { case Plasma::Types::Horizontal: { if (m_complexity == Latte::Types::ExpertSettings) { if (qApp->isLeftToRight()) { xPos = availGeometry.x() + availGeometry.width() - size.width(); } else { xPos = availGeometry.x(); } } else { xPos = scrGeometry.center().x() - size.width() / 2; } if (location == Plasma::Types::TopEdge) { yPos = scrGeometry.y() + clearThickness; } else if (location == Plasma::Types::BottomEdge) { yPos = scrGeometry.y() + scrGeometry.height() - clearThickness - size.height(); } } break; case Plasma::Types::Vertical: { if (location == Plasma::Types::LeftEdge) { xPos = scrGeometry.x() + clearThickness; yPos = availGeometry.y() + (availGeometry.height() - size.height())/2; } else if (location == Plasma::Types::RightEdge) { xPos = scrGeometry.x() + scrGeometry.width() - clearThickness - size.width(); yPos = availGeometry.y() + (availGeometry.height() - size.height())/2; } } break; default: qWarning() << "no sync geometry, wrong formFactor"; break; } position = {xPos, yPos}; updateEnabledBorders(); m_geometryWhenVisible = QRect(position.x(), position.y(), size.width(), size.height()); setPosition(position); if (m_shellSurface) { m_shellSurface->setPosition(position); } updateShowInlineProperties(); emit m_latteView->configWindowGeometryChanged(); } void PrimaryConfigView::syncSlideEffect() { if (!m_latteView || !m_latteView->containment()) { return; } auto slideLocation = WindowSystem::AbstractWindowInterface::Slide::None; switch (m_latteView->containment()->location()) { case Plasma::Types::TopEdge: slideLocation = WindowSystem::AbstractWindowInterface::Slide::Top; break; case Plasma::Types::RightEdge: slideLocation = WindowSystem::AbstractWindowInterface::Slide::Right; break; case Plasma::Types::BottomEdge: slideLocation = WindowSystem::AbstractWindowInterface::Slide::Bottom; break; case Plasma::Types::LeftEdge: slideLocation = WindowSystem::AbstractWindowInterface::Slide::Left; break; default: qDebug() << staticMetaObject.className() << "wrong location"; break; } m_corona->wm()->slideWindow(*this, slideLocation); } void PrimaryConfigView::showEvent(QShowEvent *ev) { QQuickWindow::showEvent(ev); if (!m_latteView) { return; } m_corona->wm()->setViewExtraFlags(*this); setFlags(wFlags()); m_corona->wm()->enableBlurBehind(*this); syncGeometry(); syncSlideEffect(); if (m_latteView && m_latteView->containment()) m_latteView->containment()->setUserConfiguring(true); m_screenSyncTimer.start(); QTimer::singleShot(400, this, &PrimaryConfigView::syncGeometry); emit showSignal(); } void PrimaryConfigView::hideEvent(QHideEvent *ev) { if (!m_latteView) { deleteLater(); //QQuickWindow::hideEvent(ev); return; } if (m_latteView->containment()) { m_latteView->containment()->setUserConfiguring(false); } // QQuickWindow::hideEvent(ev); const auto mode = m_latteView->visibility()->mode(); if ((mode == Types::AlwaysVisible || mode == Types::WindowsGoBelow) && !(m_originalMode == Types::AlwaysVisible || m_originalMode == Types::WindowsGoBelow)) { //! mode changed to AlwaysVisible OR WindowsGoBelow FROM Dodge mode if (m_originalByPassWM) { //! if original by pass is active m_latteView->layout()->recreateView(m_latteView->containment()); } } else if (m_latteView->byPassWM() != m_originalByPassWM) { m_latteView->layout()->recreateView(m_latteView->containment()); } deleteLater(); } void PrimaryConfigView::focusOutEvent(QFocusEvent *ev) { Q_UNUSED(ev); const auto *focusWindow = qGuiApp->focusWindow(); if (!m_latteView || (focusWindow && (focusWindow->flags().testFlag(Qt::Popup) || focusWindow->flags().testFlag(Qt::ToolTip))) || m_latteView->alternativesIsShown()) { return; } if (!m_blockFocusLost && !m_latteView->containsMouse() && (!m_secConfigView || (m_secConfigView && !m_secConfigView->isActive()))) { hideConfigWindow(); } } void PrimaryConfigView::setupWaylandIntegration() { if (m_shellSurface || !KWindowSystem::isPlatformWayland() || !m_latteView || !m_latteView->containment()) { // already setup return; } if (m_corona) { using namespace KWayland::Client; PlasmaShell *interface = m_corona->waylandCoronaInterface(); if (!interface) { return; } Surface *s = Surface::fromWindow(this); if (!s) { return; } qDebug() << "wayland primary settings surface was created..."; m_shellSurface = interface->createSurface(s, this); m_shellSurface->setSkipTaskbar(true); #if KF5_VERSION_MINOR >= 47 m_shellSurface->setSkipSwitcher(true); #endif syncGeometry(); } } bool PrimaryConfigView::event(QEvent *e) { if (e->type() == QEvent::PlatformSurface) { if (auto pe = dynamic_cast(e)) { switch (pe->surfaceEventType()) { case QPlatformSurfaceEvent::SurfaceCreated: if (m_shellSurface) { break; } setupWaylandIntegration(); break; case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed: if (m_shellSurface) { delete m_shellSurface; m_shellSurface = nullptr; qDebug() << "WAYLAND config window surface was deleted..."; } break; } } } return PlasmaQuick::ConfigView::event(e); } void PrimaryConfigView::immutabilityChanged(Plasma::Types::ImmutabilityType type) { if (type != Plasma::Types::Mutable && isVisible()) hideConfigWindow(); } bool PrimaryConfigView::sticker() const { return m_blockFocusLost; } void PrimaryConfigView::setSticker(bool blockFocusLost) { if (m_blockFocusLost == blockFocusLost) return; m_blockFocusLost = blockFocusLost; } bool PrimaryConfigView::showInlineProperties() const { return m_showInlineProperties; } void PrimaryConfigView::setShowInlineProperties(bool show) { if (m_showInlineProperties == show) { return; } m_showInlineProperties = show; emit showInlinePropertiesChanged(); } void PrimaryConfigView::updateShowInlineProperties() { if (!m_latteView) { return; } bool showSecWindow{false}; bool complexityApprovedSecWindow{false}; if (m_complexity != Latte::Types::BasicSettings && !(m_complexity == Latte::Types::ExpertSettings && m_latteView->formFactor() == Plasma::Types::Vertical)) { showSecWindow = true; complexityApprovedSecWindow = true; } //! consider screen geometry for showing or not the secondary window if (!geometryWhenVisible().isNull()) { createSecondaryWindow(); if (m_secConfigView->geometryWhenVisible().intersects(geometryWhenVisible())) { showSecWindow = false; } else if (complexityApprovedSecWindow) { showSecWindow = true; } } if (showSecWindow) { if (!m_secConfigView) { createSecondaryWindow(); } if (!KWindowSystem::isPlatformWayland()) { QTimer::singleShot(150, m_secConfigView, SLOT(show())); } else { QTimer::singleShot(150, [this]() { m_secConfigView->setVisible(true); }); } setShowInlineProperties(false); } else { deleteSecondaryWindow(); setShowInlineProperties(true); } // qDebug() << " showSecWindow:" << showSecWindow << " _ " << " inline:"<< !showSecWindow; } int PrimaryConfigView::complexity() const { return (int)m_complexity; } void PrimaryConfigView::setComplexity(int complexity) { if ((int)m_complexity == complexity) { return; } m_complexity = static_cast(complexity); emit complexityChanged(); } void PrimaryConfigView::hideConfigWindow() { if (m_shellSurface) { //!NOTE: Avoid crash in wayland environment with qt5.9 close(); } else { hide(); } } void PrimaryConfigView::updateLaunchersForGroup(int groupInt) { Types::LaunchersGroup group = (Types::LaunchersGroup)groupInt; //! when the layout/global launchers list is empty then the current dock launchers are used for them //! as a start point if (m_corona && m_latteView->layout()) { if ((group == Types::LayoutLaunchers && m_latteView->layout()->launchers().isEmpty()) || (group == Types::GlobalLaunchers && m_corona->universalSettings()->launchers().isEmpty())) { Plasma::Containment *c = m_latteView->containment(); const auto &applets = c->applets(); for (auto *applet : applets) { KPluginMetaData meta = applet->kPackage().metadata(); if (meta.pluginId() == "org.kde.latte.plasmoid") { if (QQuickItem *appletInterface = applet->property("_plasma_graphicObject").value()) { const auto &childItems = appletInterface->childItems(); if (childItems.isEmpty()) { continue; } for (QQuickItem *item : childItems) { if (auto *metaObject = item->metaObject()) { // not using QMetaObject::invokeMethod to avoid warnings when calling // this on applets that don't have it or other child items since this // is pretty much trial and error. // Also, "var" arguments are treated as QVariant in QMetaObject int methodIndex = metaObject->indexOfMethod("getLauncherList()"); if (methodIndex == -1) { continue; } QMetaMethod method = metaObject->method(methodIndex); QVariant launchers; if (method.invoke(item, Q_RETURN_ARG(QVariant, launchers))) { if (group == Types::LayoutLaunchers) { m_latteView->layout()->setLaunchers(launchers.toStringList()); } else if (group == Types::GlobalLaunchers) { m_corona->universalSettings()->setLaunchers(launchers.toStringList()); } } } } } } } } } } //!BEGIN borders Plasma::FrameSvg::EnabledBorders PrimaryConfigView::enabledBorders() const { return m_enabledBorders; } void PrimaryConfigView::updateEnabledBorders() { if (!this->screen()) { return; } Plasma::FrameSvg::EnabledBorders borders = Plasma::FrameSvg::AllBorders; switch (m_latteView->location()) { case Plasma::Types::TopEdge: borders &= m_inReverse ? ~Plasma::FrameSvg::BottomBorder : ~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 &= m_inReverse ? ~Plasma::FrameSvg::TopBorder : ~Plasma::FrameSvg::BottomBorder; break; default: break; } if (m_enabledBorders != borders) { m_enabledBorders = borders; PanelShadows::self()->addWindow(this, m_enabledBorders); emit enabledBordersChanged(); } } //!END borders //!BEGIN configuration void PrimaryConfigView::loadConfig() { if (!m_latteView || !m_latteView->containment()) { return; } auto config = m_latteView->containment()->config(); int complexity = config.readEntry("settingsComplexity", (int)Latte::Types::BasicSettings); setComplexity(static_cast(complexity)); } void PrimaryConfigView::saveConfig() { if (!m_latteView || !m_latteView->containment()) { return; } auto config = m_latteView->containment()->config(); config.writeEntry("settingsComplexity", (int)m_complexity); config.sync(); } //!END configuration } } diff --git a/app/view/settings/primaryconfigview.h b/app/view/settings/primaryconfigview.h index 7791d7f7..0f0c8be2 100644 --- a/app/view/settings/primaryconfigview.h +++ b/app/view/settings/primaryconfigview.h @@ -1,171 +1,173 @@ /* * 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 . */ #ifndef PRIMARYCONFIGVIEW_H #define PRIMARYCONFIGVIEW_H // local #include "../../plasmaquick/configview.h" +#include "../../wm/windowinfowrap.h" #include "../../../liblatte2/types.h" //Qt #include #include #include #include // Plasma #include #include namespace Plasma { class Applet; class Containment; class FrameSvg; class Types; } namespace KWayland { namespace Client { class PlasmaShellSurface; } } namespace Latte { class Corona; class View; } namespace Latte { namespace ViewPart { class SecondaryConfigView; } } namespace Latte { namespace ViewPart { class PrimaryConfigView : public PlasmaQuick::ConfigView { Q_OBJECT //! used when the secondary config window can not be shown Q_PROPERTY(bool showInlineProperties READ showInlineProperties NOTIFY showInlinePropertiesChanged) Q_PROPERTY(int complexity READ complexity WRITE setComplexity NOTIFY complexityChanged) Q_PROPERTY(QRect availableScreenGeometry READ availableScreenGeometry NOTIFY availableScreenGeometryChanged) Q_PROPERTY(Plasma::FrameSvg::EnabledBorders enabledBorders READ enabledBorders NOTIFY enabledBordersChanged) public: enum ConfigViewType { PrimaryConfig = 0, SecondaryConfig }; PrimaryConfigView(Plasma::Containment *containment, Latte::View *view, QWindow *parent = nullptr); ~PrimaryConfigView() override; void init() override; void requestActivate(); Qt::WindowFlags wFlags() const; bool showInlineProperties() const; bool sticker() const; int complexity() const; void setComplexity(int complexity); QRect availableScreenGeometry() const; QRect geometryWhenVisible() const; Plasma::FrameSvg::EnabledBorders enabledBorders() const; QQuickView *secondaryWindow(); public slots: Q_INVOKABLE void hideConfigWindow(); Q_INVOKABLE void setSticker(bool blockFocusLost); Q_INVOKABLE void syncGeometry(); Q_INVOKABLE void updateLaunchersForGroup(int groupInt); signals: void availableScreenGeometryChanged(); void complexityChanged(); void enabledBordersChanged(); void raiseDocksTemporaryChanged(); void showInlinePropertiesChanged(); void showSignal(); protected: void showEvent(QShowEvent *ev) override; void hideEvent(QHideEvent *ev) override; void focusOutEvent(QFocusEvent *ev) override; bool event(QEvent *e) override; void syncSlideEffect(); private slots: void immutabilityChanged(Plasma::Types::ImmutabilityType type); void updateAvailableScreenGeometry(); void updateEnabledBorders(); void updateShowInlineProperties(); void createSecondaryWindow(); void deleteSecondaryWindow(); void setShowInlineProperties(bool show); void loadConfig(); void saveConfig(); private: void setupWaylandIntegration(); bool m_blockFocusLost{false}; bool m_blockFocusLostOnStartup{true}; bool m_originalByPassWM{false}; bool m_inReverse{false}; //! it is used by the borders bool m_showInlineProperties{false}; Latte::Types::Visibility m_originalMode{Latte::Types::DodgeActive}; Latte::Types::SettingsComplexity m_complexity{Latte::Types::BasicSettings}; QRect m_availableScreenGeometry; QRect m_geometryWhenVisible; QPointer m_latteView; QPointer m_secConfigView; QTimer m_screenSyncTimer; QTimer m_thicknessSyncTimer; QList connections; Plasma::FrameSvg::EnabledBorders m_enabledBorders{Plasma::FrameSvg::AllBorders}; Latte::Corona *m_corona{nullptr}; + Latte::WindowSystem::WindowId m_waylandWindowId; KWayland::Client::PlasmaShellSurface *m_shellSurface{nullptr}; }; } } #endif //PRIMARYCONFIGVIEW_H diff --git a/app/view/settings/secondaryconfigview.cpp b/app/view/settings/secondaryconfigview.cpp index bffd76d3..b792b948 100644 --- a/app/view/settings/secondaryconfigview.cpp +++ b/app/view/settings/secondaryconfigview.cpp @@ -1,444 +1,450 @@ /* * Copyright 2018 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 "secondaryconfigview.h" // local #include #include "primaryconfigview.h" #include "../panelshadows_p.h" #include "../view.h" #include "../../lattecorona.h" #include "../../wm/abstractwindowinterface.h" // Qt #include #include #include #include #include // KDE #include #include #include #include #include // Plasma #include namespace Latte { namespace ViewPart { SecondaryConfigView::SecondaryConfigView(Latte::View *view, QWindow *parent) : QQuickView(nullptr), m_latteView(view) { m_parent = qobject_cast(parent); m_corona = qobject_cast(m_latteView->containment()->corona()); setupWaylandIntegration(); if (KWindowSystem::isPlatformX11()) { m_corona->wm()->registerIgnoredWindow(winId()); } setResizeMode(QQuickView::SizeViewToRootObject); setScreen(m_latteView->screen()); if (m_latteView && m_latteView->containment()) { setIcon(qGuiApp->windowIcon()); } m_screenSyncTimer.setSingleShot(true); m_screenSyncTimer.setInterval(100); connections << connect(m_parent, &PrimaryConfigView::availableScreenGeometryChanged, this, &SecondaryConfigView::syncGeometry); connections << connect(&m_screenSyncTimer, &QTimer::timeout, this, [this]() { setScreen(m_latteView->screen()); setFlags(wFlags()); if (KWindowSystem::isPlatformX11()) { #if KF5_VERSION_MINOR >= 45 KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager | NET::SkipSwitcher); #else KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager); #endif KWindowSystem::setOnAllDesktops(winId(), true); } syncGeometry(); syncSlideEffect(); }); connections << connect(m_latteView->visibility(), &VisibilityManager::modeChanged, this, &SecondaryConfigView::syncGeometry); m_thicknessSyncTimer.setSingleShot(true); m_thicknessSyncTimer.setInterval(200); connections << connect(&m_thicknessSyncTimer, &QTimer::timeout, this, [this]() { syncGeometry(); }); connections << connect(m_latteView, &Latte::View::normalThicknessChanged, [&]() { m_thicknessSyncTimer.start(); }); } SecondaryConfigView::~SecondaryConfigView() { qDebug() << "SecDockConfigView deleting ..."; - if (KWindowSystem::isPlatformX11()) { - m_corona->wm()->unregisterIgnoredWindow(winId()); - } + m_corona->wm()->unregisterIgnoredWindow(KWindowSystem::isPlatformX11() ? winId() : m_waylandWindowId); for (const auto &var : connections) { QObject::disconnect(var); } } void SecondaryConfigView::init() { qDebug() << "dock secondary config view : initialization started..."; setDefaultAlphaBuffer(true); setColor(Qt::transparent); PanelShadows::self()->addWindow(this); rootContext()->setContextProperty(QStringLiteral("latteView"), m_latteView); rootContext()->setContextProperty(QStringLiteral("viewConfig"), this); rootContext()->setContextProperty(QStringLiteral("plasmoid"), m_latteView->containment()->property("_plasma_graphicObject").value()); KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(engine()); kdeclarative.setTranslationDomain(QStringLiteral("latte-dock")); #if KF5_VERSION_MINOR >= 45 kdeclarative.setupContext(); kdeclarative.setupEngine(engine()); #else kdeclarative.setupBindings(); #endif QByteArray tempFilePath = "lattedocksecondaryconfigurationui"; updateEnabledBorders(); auto source = QUrl::fromLocalFile(m_latteView->containment()->corona()->kPackage().filePath(tempFilePath)); setSource(source); syncGeometry(); syncSlideEffect(); if (m_parent && KWindowSystem::isPlatformX11()) { m_parent->requestActivate(); } qDebug() << "dock secondary config view : initialization ended..."; } inline Qt::WindowFlags SecondaryConfigView::wFlags() const { return (flags() | Qt::FramelessWindowHint /*| Qt::WindowStaysOnTopHint*/) & ~Qt::WindowDoesNotAcceptFocus; } QRect SecondaryConfigView::geometryWhenVisible() const { return m_geometryWhenVisible; } void SecondaryConfigView::requestActivate() { if (KWindowSystem::isPlatformWayland() && m_shellSurface) { - WindowSystem::WindowId wid = m_corona->wm()->winIdFor("latte-dock", geometry()); - m_corona->wm()->requestActivate(wid); + if (m_waylandWindowId.isNull()) { + m_waylandWindowId = m_corona->wm()->winIdFor("latte-dock", geometry()); + } + + m_corona->wm()->requestActivate(m_waylandWindowId); } else { QQuickView::requestActivate(); } } void SecondaryConfigView::syncGeometry() { if (!m_latteView || !m_latteView->layout() || !m_latteView->containment() || !m_parent || !rootObject()) { return; } + if (KWindowSystem::isPlatformWayland() && m_waylandWindowId.isNull()) { + m_waylandWindowId = m_corona->wm()->winIdFor("latte-dock", geometry()); + m_corona->wm()->registerIgnoredWindow(m_waylandWindowId); + } + const QSize size(rootObject()->width(), rootObject()->height()); setMaximumSize(size); setMinimumSize(size); resize(size); const auto location = m_latteView->containment()->location(); const auto scrGeometry = m_latteView->screenGeometry(); const auto availGeometry = m_parent->availableScreenGeometry(); int clearThickness = m_latteView->editThickness(); int secondaryConfigSpacing = 2 * m_latteView->fontPixelSize(); QPoint position{0, 0}; int xPos{0}; int yPos{0}; switch (m_latteView->containment()->formFactor()) { case Plasma::Types::Horizontal: { if (qApp->isLeftToRight()) { xPos = availGeometry.x() + secondaryConfigSpacing; } else { xPos = availGeometry.x() + availGeometry.width() - size.width() - secondaryConfigSpacing; } if (location == Plasma::Types::TopEdge) { yPos = scrGeometry.y() + clearThickness; } else if (location == Plasma::Types::BottomEdge) { yPos = scrGeometry.y() + scrGeometry.height() - clearThickness - size.height(); } } break; case Plasma::Types::Vertical: { yPos = availGeometry.y() + secondaryConfigSpacing; if (location == Plasma::Types::LeftEdge) { xPos = scrGeometry.x() + clearThickness; } else if (location == Plasma::Types::RightEdge) { xPos = scrGeometry.x() + scrGeometry.width() - clearThickness - size.width(); } } break; default: qWarning() << "no sync geometry, wrong formFactor"; break; } position = {xPos, yPos}; updateEnabledBorders(); m_geometryWhenVisible = QRect(position.x(), position.y(), size.width(), size.height()); setPosition(position); if (m_shellSurface) { m_shellSurface->setPosition(position); } //! after placement request to activate the main config window in order to avoid //! rare cases of closing settings window from secondaryConfigView->focusOutEvent if (m_parent && KWindowSystem::isPlatformX11()) { m_parent->requestActivate(); } } void SecondaryConfigView::syncSlideEffect() { if (!m_latteView || !m_latteView->containment()) { return; } auto slideLocation = WindowSystem::AbstractWindowInterface::Slide::None; switch (m_latteView->containment()->location()) { case Plasma::Types::TopEdge: slideLocation = WindowSystem::AbstractWindowInterface::Slide::Top; break; case Plasma::Types::RightEdge: slideLocation = WindowSystem::AbstractWindowInterface::Slide::Right; break; case Plasma::Types::BottomEdge: slideLocation = WindowSystem::AbstractWindowInterface::Slide::Bottom; break; case Plasma::Types::LeftEdge: slideLocation = WindowSystem::AbstractWindowInterface::Slide::Left; break; default: qDebug() << staticMetaObject.className() << "wrong location"; break; } m_corona->wm()->slideWindow(*this, slideLocation); } void SecondaryConfigView::showEvent(QShowEvent *ev) { QQuickWindow::showEvent(ev); if (!m_latteView) { return; } m_corona->wm()->setViewExtraFlags(*this); setFlags(wFlags()); m_corona->wm()->enableBlurBehind(*this); syncGeometry(); syncSlideEffect(); m_screenSyncTimer.start(); QTimer::singleShot(400, this, &SecondaryConfigView::syncGeometry); emit showSignal(); } void SecondaryConfigView::focusOutEvent(QFocusEvent *ev) { Q_UNUSED(ev); const auto *focusWindow = qGuiApp->focusWindow(); if ((focusWindow && (focusWindow->flags().testFlag(Qt::Popup) || focusWindow->flags().testFlag(Qt::ToolTip))) || m_latteView->alternativesIsShown()) { return; } const auto parent = qobject_cast(m_parent); if (!m_latteView->containsMouse() && parent && !parent->sticker() && !parent->isActive()) { parent->hideConfigWindow(); } } void SecondaryConfigView::setupWaylandIntegration() { if (m_shellSurface || !KWindowSystem::isPlatformWayland() || !m_latteView || !m_latteView->containment()) { // already setup return; } if (m_corona) { using namespace KWayland::Client; PlasmaShell *interface = m_corona->waylandCoronaInterface(); if (!interface) { return; } Surface *s = Surface::fromWindow(this); if (!s) { return; } qDebug() << "wayland secondary settings surface was created..."; m_shellSurface = interface->createSurface(s, this); m_shellSurface->setSkipTaskbar(true); #if KF5_VERSION_MINOR >= 47 m_shellSurface->setSkipSwitcher(true); #endif syncGeometry(); } } bool SecondaryConfigView::event(QEvent *e) { if (e->type() == QEvent::PlatformSurface) { if (auto pe = dynamic_cast(e)) { switch (pe->surfaceEventType()) { case QPlatformSurfaceEvent::SurfaceCreated: if (m_shellSurface) { break; } setupWaylandIntegration(); break; case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed: if (m_shellSurface) { delete m_shellSurface; m_shellSurface = nullptr; qDebug() << "WAYLAND secondary config window surface was deleted..."; } break; } } } return QQuickView::event(e); } void SecondaryConfigView::hideConfigWindow() { if (m_shellSurface) { //!NOTE: Avoid crash in wayland environment with qt5.9 close(); } else { hide(); } } //!BEGIN borders Plasma::FrameSvg::EnabledBorders SecondaryConfigView::enabledBorders() const { return m_enabledBorders; } void SecondaryConfigView::updateEnabledBorders() { if (!this->screen()) { return; } Plasma::FrameSvg::EnabledBorders borders = Plasma::FrameSvg::AllBorders; switch (m_latteView->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 (m_enabledBorders != borders) { m_enabledBorders = borders; PanelShadows::self()->addWindow(this, m_enabledBorders); emit enabledBordersChanged(); } } //!END borders } } diff --git a/app/view/settings/secondaryconfigview.h b/app/view/settings/secondaryconfigview.h index 2d86e443..6e7c5180 100644 --- a/app/view/settings/secondaryconfigview.h +++ b/app/view/settings/secondaryconfigview.h @@ -1,119 +1,121 @@ /* * Copyright 2018 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 SECONDARYCONFIGVIEW_H #define SECONDARYCONFIGVIEW_H // local +#include "../../wm/windowinfowrap.h" #include "../../../liblatte2/types.h" //Qt #include #include #include #include // Plasma #include #include namespace Plasma { class Applet; class Containment; class FrameSvg; class Types; } namespace KWayland { namespace Client { class PlasmaShellSurface; } } namespace Latte { class Corona; class View; } namespace Latte { namespace ViewPart { class PrimaryConfigView; } } namespace Latte { namespace ViewPart { class SecondaryConfigView : public QQuickView { Q_OBJECT Q_PROPERTY(Plasma::FrameSvg::EnabledBorders enabledBorders READ enabledBorders NOTIFY enabledBordersChanged) public: SecondaryConfigView(Latte::View *view, QWindow *parent); ~SecondaryConfigView() override; void init(); void requestActivate(); Qt::WindowFlags wFlags() const; QRect geometryWhenVisible() const; Plasma::FrameSvg::EnabledBorders enabledBorders() const; public slots: Q_INVOKABLE void hideConfigWindow(); Q_INVOKABLE void syncGeometry(); signals: void enabledBordersChanged(); void showSignal(); protected: void showEvent(QShowEvent *ev) override; void focusOutEvent(QFocusEvent *ev) override; bool event(QEvent *e) override; void syncSlideEffect(); private slots: void updateEnabledBorders(); private: void setupWaylandIntegration(); QRect m_geometryWhenVisible; QPointer m_latteView; QPointer m_parent; QTimer m_screenSyncTimer; QTimer m_thicknessSyncTimer; QList connections; Plasma::FrameSvg::EnabledBorders m_enabledBorders{Plasma::FrameSvg::AllBorders}; Latte::Corona *m_corona{nullptr}; + Latte::WindowSystem::WindowId m_waylandWindowId; KWayland::Client::PlasmaShellSurface *m_shellSurface{nullptr}; }; } } #endif //SECONDARYCONFIGVIEW_H diff --git a/app/view/view.cpp b/app/view/view.cpp index 6fbed189..d9716ed1 100644 --- a/app/view/view.cpp +++ b/app/view/view.cpp @@ -1,1332 +1,1327 @@ /* * 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 "view.h" // local #include "contextmenu.h" #include "effects.h" #include "positioner.h" #include "visibilitymanager.h" #include "settings/primaryconfigview.h" #include "settings/secondaryconfigview.h" #include "../indicator/factory.h" #include "../lattecorona.h" #include "../layout/genericlayout.h" #include "../layouts/manager.h" #include "../plasma/extended/theme.h" #include "../screenpool.h" #include "../settings/universalsettings.h" #include "../shortcuts/globalshortcuts.h" #include "../shortcuts/shortcutstracker.h" #include "../../liblatte2/extras.h" // Qt #include #include #include #include #include #include #include // KDe #include #include #include #include #include // Plasma #include #include #include namespace Latte { //! both alwaysVisible and byPassWM are passed through corona because //! during the view window creation containment hasn't been set, but these variables //! are needed in order for window flags to be set correctly View::View(Plasma::Corona *corona, QScreen *targetScreen, bool byPassWM) : PlasmaQuick::ContainmentView(corona), m_contextMenu(new ViewPart::ContextMenu(this)), - m_effects(new ViewPart::Effects(this)), - m_positioner(new ViewPart::Positioner(this)) //needs to be created after Effects because it catches some of its signals + m_effects(new ViewPart::Effects(this)) { + //! needs to be created after Effects because it catches some of its signals + //! and avoid a crash from View::winId() at the same time + m_positioner = new ViewPart::Positioner(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 (byPassWM) { setFlags(flags | Qt::BypassWindowManagerHint); } else { setFlags(flags); } if (targetScreen) m_positioner->setScreenToFollow(targetScreen); else m_positioner->setScreenToFollow(qGuiApp->primaryScreen()); connect(this, &View::containmentChanged , this, [ &, byPassWM]() { qDebug() << "dock view c++ containment changed 1..."; if (!this->containment()) return; qDebug() << "dock view c++ containment changed 2..."; //! First load default values from file restoreConfig(); //! Afterwards override that values in case during creation something different is needed setByPassWM(byPassWM); //! Check the screen assigned to this dock reconsiderScreen(); //! needs to be created before visibility creation because visibility uses it if (!m_windowsTracker) { m_windowsTracker = new ViewPart::WindowsTracker(this); emit windowsTrackerChanged(); } if (!m_visibility) { m_visibility = new ViewPart::VisibilityManager(this); connect(m_visibility, &ViewPart::VisibilityManager::isHiddenChanged, this, [&]() { if (m_visibility->isHidden()) { deactivateApplets(); } }); emit visibilityChanged(); } if (!m_indicator) { m_indicator = new ViewPart::Indicator(this); emit indicatorChanged(); } connect(this->containment(), SIGNAL(statusChanged(Plasma::Types::ItemStatus)), SLOT(statusChanged(Plasma::Types::ItemStatus))); }, Qt::DirectConnection); m_corona = qobject_cast(this->corona()); if (m_corona) { - if (KWindowSystem::isPlatformX11()) { - m_corona->wm()->registerIgnoredWindow(winId()); - } - connect(m_corona, &Latte::Corona::viewLocationChanged, this, &View::dockLocationChanged); } } View::~View() { m_inDelete = true; - if (KWindowSystem::isPlatformX11()) { - m_corona->wm()->unregisterIgnoredWindow(winId()); - } - //! clear Layout connections m_visibleHackTimer1.stop(); m_visibleHackTimer2.stop(); for (auto &c : connectionsLayout) { disconnect(c); } //! unload indicators if (m_indicator) { m_indicator->unloadIndicators(); } disconnect(m_corona, &Latte::Corona::availableScreenRectChangedFrom, this, &View::availableScreenRectChangedFrom); disconnect(containment(), SIGNAL(statusChanged(Plasma::Types::ItemStatus)), this, SLOT(statusChanged(Plasma::Types::ItemStatus))); qDebug() << "dock view deleting..."; rootContext()->setContextProperty(QStringLiteral("dock"), nullptr); rootContext()->setContextProperty(QStringLiteral("layoutsManager"), nullptr); rootContext()->setContextProperty(QStringLiteral("shortcutsEngine"), nullptr); rootContext()->setContextProperty(QStringLiteral("themeExtended"), nullptr); rootContext()->setContextProperty(QStringLiteral("universalSettings"), nullptr); //! this disconnect does not free up connections correctly when //! latteView 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_contextMenu) { delete m_contextMenu; } //needs to be deleted before Effects because it catches some of its signals if (m_positioner) { delete m_positioner; } if (m_effects) { delete m_effects; } if (m_indicator) { delete m_indicator; } if (m_visibility) { delete m_visibility; } if (m_windowsTracker) { delete m_windowsTracker; } } void View::init() { connect(this, &QQuickWindow::xChanged, this, &View::xChanged); connect(this, &QQuickWindow::xChanged, this, &View::updateAbsoluteGeometry); connect(this, &QQuickWindow::yChanged, this, &View::yChanged); connect(this, &QQuickWindow::yChanged, this, &View::updateAbsoluteGeometry); connect(this, &QQuickWindow::widthChanged, this, &View::widthChanged); connect(this, &QQuickWindow::widthChanged, this, &View::updateAbsoluteGeometry); connect(this, &QQuickWindow::heightChanged, this, &View::heightChanged); connect(this, &QQuickWindow::heightChanged, this, &View::updateAbsoluteGeometry); connect(m_corona, &Latte::Corona::availableScreenRectChangedFrom, this, &View::availableScreenRectChangedFrom); connect(this, &View::byPassWMChanged, this, &View::saveConfig); connect(this, &View::isPreferredForShortcutsChanged, this, &View::saveConfig); connect(this, &View::onPrimaryChanged, this, &View::saveConfig); connect(this, &View::typeChanged, this, &View::saveConfig); connect(this, &View::normalThicknessChanged, this, [&]() { emit m_corona->availableScreenRectChangedFrom(this); }); connect(m_effects, &ViewPart::Effects::innerShadowChanged, this, [&]() { emit m_corona->availableScreenRectChangedFrom(this); }); connect(m_positioner, &ViewPart::Positioner::onHideWindowsForSlidingOut, this, &View::hideWindowsForSlidingOut); connect(m_positioner, &ViewPart::Positioner::screenGeometryChanged, this, &View::screenGeometryChanged); connect(m_positioner, &ViewPart::Positioner::windowSizeChanged, this, [&]() { emit m_corona->availableScreenRectChangedFrom(this); }); connect(m_contextMenu, &ViewPart::ContextMenu::menuChanged, this, &View::contextMenuIsShownChanged); connect(m_corona->indicatorFactory(), &Latte::Indicator::Factory::pluginsUpdated, this, &View::reloadSource); //! View sends this signal in order to avoid crashes from ViewPart::Indicator when the view is recreated connect(m_corona->indicatorFactory(), &Latte::Indicator::Factory::customPluginsChanged, this, &View::customPluginsChanged); connect(m_corona, &Latte::Corona::availableScreenRectChanged, this, &View::availableScreenRectChangedForViewParts); ///!!!!! rootContext()->setContextProperty(QStringLiteral("latteView"), this); if (m_corona) { rootContext()->setContextProperty(QStringLiteral("layoutsManager"), m_corona->layoutsManager()); rootContext()->setContextProperty(QStringLiteral("shortcutsEngine"), m_corona->globalShortcuts()->shortcutsTracker()); rootContext()->setContextProperty(QStringLiteral("themeExtended"), m_corona->themeExtended()); rootContext()->setContextProperty(QStringLiteral("universalSettings"), m_corona->universalSettings()); } setSource(corona()->kPackage().filePath("lattedockui")); // setVisible(true); m_positioner->syncGeometry(); if (!KWindowSystem::isPlatformWayland()) { setVisible(true); } qDebug() << "SOURCE:" << source(); } void View::reloadSource() { if (m_layout && containment()) { if (settingsWindowIsShown()) { m_configView->deleteLater(); } engine()->clearComponentCache(); m_layout->recreateView(containment(), settingsWindowIsShown()); } } bool View::inDelete() const { return m_inDelete; } void View::disconnectSensitiveSignals() { disconnect(m_corona, &Latte::Corona::availableScreenRectChangedFrom, this, &View::availableScreenRectChangedFrom); setLayout(nullptr); if (m_windowsTracker) { // m_windowsTracker->setEnabled(false); } } void View::availableScreenRectChangedFrom(View *origin) { if (m_inDelete || origin == this) return; if (formFactor() == Plasma::Types::Vertical) { m_positioner->syncGeometry(); } } void View::setupWaylandIntegration() { if (m_shellSurface) return; if (Latte::Corona *c = qobject_cast(corona())) { using namespace KWayland::Client; PlasmaShell *interface {c->waylandCoronaInterface()}; 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 *View::surface() { return m_shellSurface; } //! the main function which decides if this dock is at the //! correct screen void View::reconsiderScreen() { m_positioner->reconsiderScreen(); } void View::copyView() { m_layout->copyView(containment()); } void View::removeView() { if (m_layout && m_layout->viewsCount() > 1) { QAction *removeAct = this->containment()->actions()->action(QStringLiteral("remove")); if (removeAct) { removeAct->trigger(); } } } bool View::settingsWindowIsShown() { auto configView = qobject_cast(m_configView); return (configView != nullptr); } void View::showSettingsWindow() { if (!settingsWindowIsShown()) { emit m_visibility->mustBeShown(); showConfigurationInterface(containment()); applyActivitiesToWindows(); } } PlasmaQuick::ConfigView *View::configView() { return m_configView; } void View::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(); if (KWindowSystem::isPlatformX11()) { 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 ViewPart::PrimaryConfigView(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 containment'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); }); } } } QRect View::localGeometry() const { return m_localGeometry; } void View::setLocalGeometry(const QRect &geometry) { if (m_localGeometry == geometry) { return; } m_localGeometry = geometry; emit localGeometryChanged(); updateAbsoluteGeometry(); } void View::updateAbsoluteGeometry(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 right 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_absoluteGeometry == absGeometry && !bypassChecks) { return; } if (m_absoluteGeometry != absGeometry) { m_absoluteGeometry = absGeometry; emit absoluteGeometryChanged(m_absoluteGeometry); } //! this is needed in order to update correctly the screenGeometries if (visibility() && corona() && visibility()->mode() == Types::AlwaysVisible) { //! main use of BYPASSCKECKS is from Positioner when the view changes screens emit m_corona->availableScreenRectChangedFrom(this); emit m_corona->availableScreenRegionChangedFrom(this); } } void View::statusChanged(Plasma::Types::ItemStatus status) { if (containment()) { if (containment()->status() >= Plasma::Types::NeedsAttentionStatus && containment()->status() != Plasma::Types::HiddenStatus) { setBlockHiding(true); } else if (!containment()->isUserConfiguring()){ setBlockHiding(false); } } } Types::ViewType View::type() const { return m_type; } void View::setType(Types::ViewType type) { if (m_type == type) { return; } m_type = type; emit typeChanged(); } bool View::alternativesIsShown() const { return m_alternativesIsShown; } void View::setAlternativesIsShown(bool show) { if (m_alternativesIsShown == show) { return; } m_alternativesIsShown = show; setBlockHiding(show); emit alternativesIsShownChanged(); } bool View::containsDrag() const { return m_containsDrag; } void View::setContainsDrag(bool contains) { if (m_containsDrag == contains) { return; } m_containsDrag = contains; emit containsDragChanged(); } bool View::containsMouse() const { return m_containsMouse; } bool View::contextMenuIsShown() const { if (!m_contextMenu) { return false; } return m_contextMenu->menu(); } int View::currentThickness() const { if (formFactor() == Plasma::Types::Vertical) { return m_effects->mask().isNull() ? width() : m_effects->mask().width() - m_effects->innerShadow(); } else { return m_effects->mask().isNull() ? height() : m_effects->mask().height() - m_effects->innerShadow(); } } int View::normalThickness() const { return m_normalThickness; } void View::setNormalThickness(int thickness) { if (m_normalThickness == thickness) { return; } m_normalThickness = thickness; emit normalThicknessChanged(); } bool View::byPassWM() const { return m_byPassWM; } void View::setByPassWM(bool bypass) { if (m_byPassWM == bypass) { return; } m_byPassWM = bypass; emit byPassWMChanged(); } bool View::behaveAsPlasmaPanel() const { return m_behaveAsPlasmaPanel; } void View::setBehaveAsPlasmaPanel(bool behavior) { if (m_behaveAsPlasmaPanel == behavior) { return; } m_behaveAsPlasmaPanel = behavior; emit behaveAsPlasmaPanelChanged(); } bool View::inEditMode() const { return m_inEditMode; } void View::setInEditMode(bool edit) { if (m_inEditMode == edit) { return; } m_inEditMode = edit; emit inEditModeChanged(); } bool View::isPreferredForShortcuts() const { return m_isPreferredForShortcuts; } void View::setIsPreferredForShortcuts(bool preferred) { if (m_isPreferredForShortcuts == preferred) { return; } m_isPreferredForShortcuts = preferred; emit isPreferredForShortcutsChanged(); if (m_isPreferredForShortcuts && m_layout) { emit m_layout->preferredViewForShortcutsChanged(this); } } bool View::latteTasksArePresent() const { return m_latteTasksArePresent; } void View::setLatteTasksArePresent(bool present) { if (m_latteTasksArePresent == present) { return; } m_latteTasksArePresent = present; emit latteTasksArePresentChanged(); } void View::preferredViewForShortcutsChangedSlot(Latte::View *view) { if (view != this) { setIsPreferredForShortcuts(false); } } bool View::onPrimary() const { return m_onPrimary; } void View::setOnPrimary(bool flag) { if (m_onPrimary == flag) { return; } m_onPrimary = flag; emit onPrimaryChanged(); } float View::maxLength() const { return m_maxLength; } void View::setMaxLength(float length) { if (m_maxLength == length) { return; } m_maxLength = length; emit maxLengthChanged(); } int View::editThickness() const { return m_editThickness; } void View::setEditThickness(int thickness) { if (m_editThickness == thickness) { return; } m_editThickness = thickness; emit editThicknessChanged(); } int View::maxThickness() const { return m_maxThickness; } void View::setMaxThickness(int thickness) { if (m_maxThickness == thickness) return; m_maxThickness = thickness; emit maxThicknessChanged(); } int View::alignment() const { return m_alignment; } void View::setAlignment(int alignment) { Types::Alignment align = static_cast(alignment); if (m_alignment == alignment) { return; } m_alignment = align; emit alignmentChanged(); } QRect View::absoluteGeometry() const { return m_absoluteGeometry; } QRect View::screenGeometry() const { if (this->screen()) { QRect geom = this->screen()->geometry(); return geom; } return QRect(); } int View::offset() const { return m_offset; } void View::setOffset(int offset) { if (m_offset == offset) { return; } m_offset = offset; emit offsetChanged(); } int View::fontPixelSize() const { return m_fontPixelSize; } void View::setFontPixelSize(int size) { if (m_fontPixelSize == size) { return; } m_fontPixelSize = size; emit fontPixelSizeChanged(); } bool View::isOnAllActivities() const { return m_activities.isEmpty() || m_activities[0] == "0"; } bool View::isOnActivity(const QString &activity) const { return isOnAllActivities() || m_activities.contains(activity); } QStringList View::activities() const { return m_activities; } void View::applyActivitiesToWindows() { if (m_visibility && m_layout) { m_windowsTracker->setWindowOnActivities(*this, m_activities); if (m_configView) { m_windowsTracker->setWindowOnActivities(*m_configView, m_activities); auto configView = qobject_cast(m_configView); if (configView && configView->secondaryWindow()) { m_windowsTracker->setWindowOnActivities(*configView->secondaryWindow(), m_activities); } } if (m_visibility->supportsKWinEdges()) { m_visibility->applyActivitiesToHiddenWindows(m_activities); } } } Layout::GenericLayout *View::layout() const { return m_layout; } void View::setLayout(Layout::GenericLayout *layout) { if (m_layout == layout) { return; } // clear mode for (auto &c : connectionsLayout) { disconnect(c); } m_layout = layout; if (m_layout) { connectionsLayout << connect(containment(), &Plasma::Applet::destroyedChanged, m_layout, &Layout::GenericLayout::destroyedChanged); connectionsLayout << connect(containment(), &Plasma::Applet::locationChanged, m_corona, &Latte::Corona::viewLocationChanged); connectionsLayout << connect(containment(), &Plasma::Containment::appletAlternativesRequested, m_corona, &Latte::Corona::showAlternativesForApplet, Qt::QueuedConnection); if (m_corona->layoutsManager()->memoryUsage() == Types::MultipleLayouts) { connectionsLayout << connect(containment(), &Plasma::Containment::appletCreated, m_layout, &Layout::GenericLayout::appletCreated); } connectionsLayout << connect(m_positioner, &Latte::ViewPart::Positioner::edgeChanged, m_layout, &Layout::GenericLayout::viewEdgeChanged); //! Sometimes the activity isnt completely ready, by adding a delay //! we try to catch up QTimer::singleShot(100, [this]() { if (m_layout && m_visibility) { m_activities = m_layout->appliedActivities(); qDebug() << "DOCK VIEW FROM LAYOUT ::: " << m_layout->name() << " - activities: " << m_activities; applyActivitiesToWindows(); emit activitiesChanged(); } }); connectionsLayout << connect(m_layout, &Layout::GenericLayout::preferredViewForShortcutsChanged, this, &View::preferredViewForShortcutsChangedSlot); connectionsLayout << connect(m_layout, &Layout::GenericLayout::lastConfigViewForChanged, this, &View::configViewCreatedFor); Latte::Corona *latteCorona = qobject_cast(this->corona()); if (latteCorona->layoutsManager()->memoryUsage() == Types::MultipleLayouts) { connectionsLayout << connect(latteCorona->activitiesConsumer(), &KActivities::Consumer::runningActivitiesChanged, this, [&]() { if (m_layout && m_visibility) { m_activities = m_layout->appliedActivities(); qDebug() << "DOCK VIEW FROM LAYOUT (runningActivitiesChanged) ::: " << m_layout->name() << " - activities: " << m_activities; applyActivitiesToWindows(); emit activitiesChanged(); } }); connectionsLayout << connect(m_layout, &Layout::GenericLayout::activitiesChanged, this, [&]() { if (m_layout) { m_activities = m_layout->appliedActivities(); applyActivitiesToWindows(); emit activitiesChanged(); } }); connectionsLayout << connect(latteCorona->layoutsManager(), &Layouts::Manager::layoutsChanged, this, [&]() { if (m_layout) { m_activities = m_layout->appliedActivities(); applyActivitiesToWindows(); emit activitiesChanged(); } }); //! BEGIN OF KWIN HACK //! IMPORTANT ::: Fixing KWin Faulty Behavior that KWin hides ALL Views when an Activity stops //! with no reason!! m_visibleHackTimer1.setInterval(400); m_visibleHackTimer2.setInterval(2500); m_visibleHackTimer1.setSingleShot(true); m_visibleHackTimer2.setSingleShot(true); connectionsLayout << connect(this, &QWindow::visibleChanged, this, [&]() { if (m_layout && !inDelete() & !isVisible()) { m_visibleHackTimer1.start(); m_visibleHackTimer2.start(); } }); connectionsLayout << connect(&m_visibleHackTimer1, &QTimer::timeout, this, [&]() { if (m_layout && !inDelete() & !isVisible()) { setVisible(true); applyActivitiesToWindows(); //qDebug() << "View:: Enforce reshow from timer 1..."; emit activitiesChanged(); } else { //qDebug() << "View:: No needed reshow from timer 1..."; } }); connectionsLayout << connect(&m_visibleHackTimer2, &QTimer::timeout, this, [&]() { if (m_layout && !inDelete() && !isVisible()) { setVisible(true); applyActivitiesToWindows(); //qDebug() << "View:: Enforce reshow from timer 2..."; emit activitiesChanged(); } else { //qDebug() << "View:: No needed reshow from timer 2..."; } }); //! END OF KWIN HACK } emit layoutChanged(); } else { m_activities.clear(); } } void View::moveToLayout(QString layoutName) { if (!m_layout) { return; } QList containments = m_layout->unassignFromLayout(this); Latte::Corona *latteCorona = qobject_cast(this->corona()); if (latteCorona && containments.size() > 0) { Layout::GenericLayout *newLayout = latteCorona->layoutsManager()->synchronizer()->layout(layoutName); if (newLayout) { newLayout->assignToLayout(this, containments); } } } void View::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 View::configViewCreatedFor(Latte::View *view) { if (view!=this && m_configView) { //! for each layout only one dock should show its configuration windows //! otherwise we could reach a point that because a settings window //! is below another Latte View its options are not reachable auto configDialog = qobject_cast(m_configView); if (configDialog) { configDialog->hideConfigWindow(); } } } void View::hideWindowsForSlidingOut() { setBlockHiding(false); if (m_configView) { auto configDialog = qobject_cast(m_configView); if (configDialog) { configDialog->hideConfigWindow(); } } } //! remove latte tasks plasmoid void View::removeTasksPlasmoid() { if (!tasksPresent() || !containment()) { return; } for (const 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 View::tasksPresent() { if (!this->containment()) { return false; } for (const 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 plasmoid with _name_ exists in the midedata bool View::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); for (const QString &appletName : appletNames) { if (appletName == name) return true; } } return false; } ViewPart::Effects *View::effects() const { return m_effects; } ViewPart::Indicator *View::indicator() const { return m_indicator; } ViewPart::Positioner *View::positioner() const { return m_positioner; } ViewPart::VisibilityManager *View::visibility() const { return m_visibility; } ViewPart::WindowsTracker *View::windowsTracker() const { return m_windowsTracker; } bool View::event(QEvent *e) { if (!m_inDelete) { emit eventTriggered(e); switch (e->type()) { case QEvent::Enter: m_containsMouse = true; if (m_configView) { ViewPart::PrimaryConfigView *primaryConfigView = qobject_cast(m_configView); if (primaryConfigView) { if (primaryConfigView->secondaryWindow()) { ViewPart::SecondaryConfigView *secConfigView = qobject_cast(primaryConfigView->secondaryWindow()); if (secConfigView) { secConfigView->requestActivate(); } } primaryConfigView->requestActivate(); } } break; case QEvent::Leave: m_containsMouse = false; setContainsDrag(false); engine()->trimComponentCache(); break; case QEvent::DragEnter: setContainsDrag(true); break; case QEvent::DragLeave: case QEvent::Drop: setContainsDrag(false); break; case QEvent::MouseButtonPress: if (auto mouseEvent = dynamic_cast(e)) { emit mousePressed(mouseEvent->pos(), mouseEvent->button()); } break; case QEvent::MouseButtonRelease: if (auto mouseEvent = dynamic_cast(e)) { emit mouseReleased(mouseEvent->pos(), mouseEvent->button()); } break; /* case QEvent::DragMove: qDebug() << "DRAG MOVING>>>>>>"; break;*/ case QEvent::PlatformSurface: if (auto pe = dynamic_cast(e)) { switch (pe->surfaceEventType()) { case QPlatformSurfaceEvent::SurfaceCreated: setupWaylandIntegration(); if (m_shellSurface) { m_positioner->syncGeometry(); m_effects->updateShadows(); } break; case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed: if (m_shellSurface) { delete m_shellSurface; m_shellSurface = nullptr; qDebug() << "WAYLAND dock window surface was deleted..."; m_effects->clearShadows(); } break; } } break; case QEvent::Show: m_corona->wm()->setViewExtraFlags(*this); break; default: break; } } return ContainmentView::event(e); } void View::deactivateApplets() { if (!containment()) { return; } for (const auto applet : containment()->applets()) { PlasmaQuick::AppletQuickItem *ai = applet->property("_plasma_graphicObject").value(); if (ai) { ai->setExpanded(false); } } } void View::toggleAppletExpanded(const int id) { if (!containment()) { return; } for (const 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 View::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); } for (QAction *ac : plugin->contextualActions()) { actions << QVariant::fromValue(ac); } return actions; } void View::disableGrabItemBehavior() { setMouseGrabEnabled(false); } void View::restoreGrabItemBehavior() { if (mouseGrabberItem()) { mouseGrabberItem()->ungrabMouse(); } } bool View::isHighestPriorityView() { if (m_layout) { return this == m_layout->highestPriorityView(); } return false; } //!BEGIN overriding context menus behavior void View::mousePressEvent(QMouseEvent *event) { bool result = m_contextMenu->mousePressEvent(event); emit contextMenuIsShownChanged(); if (result) { PlasmaQuick::ContainmentView::mousePressEvent(event); } } //!END overriding context menus behavior //!BEGIN configuration functions void View::saveConfig() { if (!this->containment()) return; auto config = this->containment()->config(); config.writeEntry("onPrimary", onPrimary()); config.writeEntry("byPassWM", byPassWM()); config.writeEntry("isPreferredForShortcuts", isPreferredForShortcuts()); config.writeEntry("viewType", (int)m_type); config.sync(); } void View::restoreConfig() { if (!this->containment()) return; auto config = this->containment()->config(); m_onPrimary = config.readEntry("onPrimary", true); m_byPassWM = config.readEntry("byPassWM", false); m_isPreferredForShortcuts = config.readEntry("isPreferredForShortcuts", false); //! Send changed signals at the end in order to be sure that saveConfig //! wont rewrite default/invalid values emit onPrimaryChanged(); emit byPassWMChanged(); } //!END configuration functions } //!END namespace diff --git a/app/wm/abstractwindowinterface.cpp b/app/wm/abstractwindowinterface.cpp index 76739fee..ce6ab212 100644 --- a/app/wm/abstractwindowinterface.cpp +++ b/app/wm/abstractwindowinterface.cpp @@ -1,184 +1,184 @@ /* * 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 "abstractwindowinterface.h" // local #include "tracker/schemes.h" #include "tracker/trackerwindows.h" #include "../lattecorona.h" // KDE #include namespace Latte { namespace WindowSystem { AbstractWindowInterface::AbstractWindowInterface(QObject *parent) : QObject(parent) { m_activities = new KActivities::Consumer(this); m_currentActivity = m_activities->currentActivity(); m_corona = qobject_cast(parent); m_windowsTracker = new Tracker::Windows(this); m_schemesTracker = new Tracker::Schemes(this); rulesConfig = KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc")); m_windowWaitingTimer.setInterval(150); m_windowWaitingTimer.setSingleShot(true); connect(&m_windowWaitingTimer, &QTimer::timeout, this, [&]() { WindowId wid = m_windowChangedWaiting; m_windowChangedWaiting = QVariant(); emit windowChanged(wid); }); // connect(this, &AbstractWindowInterface::windowChanged, this, [&](WindowId wid) { // qDebug() << "WINDOW CHANGED ::: " << wid; // }); connect(m_activities.data(), &KActivities::Consumer::currentActivityChanged, this, [&](const QString &id) { m_currentActivity = id; emit currentActivityChanged(); }); } AbstractWindowInterface::~AbstractWindowInterface() { m_windowWaitingTimer.stop(); m_schemesTracker->deleteLater(); m_windowsTracker->deleteLater(); } QString AbstractWindowInterface::currentDesktop() const { return m_currentDesktop; } QString AbstractWindowInterface::currentActivity() const { return m_currentActivity; } Latte::Corona *AbstractWindowInterface::corona() { return m_corona; } Tracker::Schemes *AbstractWindowInterface::schemesTracker() { return m_schemesTracker; } Tracker::Windows *AbstractWindowInterface::windowsTracker() const { return m_windowsTracker; } -//! Register Latte Normal Windows in order to be tracked +//! Register Latte Ignored Windows in order to NOT be tracked void AbstractWindowInterface::registerIgnoredWindow(WindowId wid) { - if (!m_ignoredWindows.contains(wid)) { + if (!wid.isNull() && !m_ignoredWindows.contains(wid)) { m_ignoredWindows.append(wid); emit windowChanged(wid); } } void AbstractWindowInterface::unregisterIgnoredWindow(WindowId wid) { - if (!m_ignoredWindows.contains(wid)) { + if (m_ignoredWindows.contains(wid)) { m_ignoredWindows.removeAll(wid); emit windowRemoved(wid); } } //! Activities switching void AbstractWindowInterface::switchToNextActivity() { QStringList runningActivities = m_activities->activities(KActivities::Info::State::Running); if (runningActivities.count() <= 1) { return; } int curPos = runningActivities.indexOf(m_currentActivity); int nextPos = curPos + 1; if (curPos == runningActivities.count() -1) { nextPos = 0; } KActivities::Controller activitiesController; activitiesController.setCurrentActivity(runningActivities.at(nextPos)); } void AbstractWindowInterface::switchToPreviousActivity() { QStringList runningActivities = m_activities->activities(KActivities::Info::State::Running); if (runningActivities.count() <= 1) { return; } int curPos = runningActivities.indexOf(m_currentActivity); int nextPos = curPos - 1; if (curPos == 0) { nextPos = runningActivities.count() - 1; } KActivities::Controller activitiesController; activitiesController.setCurrentActivity(runningActivities.at(nextPos)); } //! Delay window changed trigerring void AbstractWindowInterface::considerWindowChanged(WindowId wid) { //! Consider if the windowChanged signal should be sent DIRECTLY or WAIT if (m_windowChangedWaiting == wid && m_windowWaitingTimer.isActive()) { //! window should be sent later m_windowWaitingTimer.start(); return; } if (m_windowChangedWaiting != wid && !m_windowWaitingTimer.isActive()) { //! window should be sent later m_windowChangedWaiting = wid; m_windowWaitingTimer.start(); } if (m_windowChangedWaiting != wid && m_windowWaitingTimer.isActive()) { m_windowWaitingTimer.stop(); //! sent previous waiting window emit windowChanged(m_windowChangedWaiting); //! retrigger waiting for the upcoming window m_windowChangedWaiting = wid; m_windowWaitingTimer.start(); } } } } diff --git a/app/wm/abstractwindowinterface.h b/app/wm/abstractwindowinterface.h index a710129f..ecb2c383 100644 --- a/app/wm/abstractwindowinterface.h +++ b/app/wm/abstractwindowinterface.h @@ -1,171 +1,171 @@ /* * 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 . */ #ifndef ABSTRACTWINDOWINTERFACE_H #define ABSTRACTWINDOWINTERFACE_H // local #include "schemecolors.h" #include "tasktools.h" #include "windowinfowrap.h" #include "tracker/trackerwindows.h" #include "../liblatte2/types.h" #include "../liblatte2/extras.h" // C++ #include #include // Qt #include #include #include #include #include #include #include #include #include // KDE #include #include // Plasma #include namespace Latte { class Corona; namespace WindowSystem { namespace Tracker { class Schemes; class Windows; } } } namespace Latte { namespace WindowSystem { class AbstractWindowInterface : public QObject { Q_OBJECT public: enum class Slide { None, Top, Left, Bottom, Right, }; explicit AbstractWindowInterface(QObject *parent = nullptr); virtual ~AbstractWindowInterface(); virtual void setViewExtraFlags(QWindow &view) = 0; virtual void setViewStruts(QWindow &view, const QRect &rect , Plasma::Types::Location location) = 0; virtual void setWindowOnActivities(QWindow &window, const QStringList &activities) = 0; virtual void removeViewStruts(QWindow &view) const = 0; virtual WindowId activeWindow() const = 0; virtual WindowInfoWrap requestInfo(WindowId wid) const = 0; virtual WindowInfoWrap requestInfoActive() const = 0; virtual void setKeepAbove(const QDialog &dialog, bool above = true) const = 0; virtual void skipTaskBar(const QDialog &dialog) const = 0; virtual void slideWindow(QWindow &view, Slide location) const = 0; virtual void enableBlurBehind(QWindow &view) const = 0; virtual void setEdgeStateFor(QWindow *view, bool active) const = 0; virtual void releaseMouseEventFor(WindowId wid) const = 0; virtual void requestActivate(WindowId wid) const = 0; virtual void requestClose(WindowId wid) const = 0; virtual void requestMoveWindow(WindowId wid, QPoint from) const = 0; virtual void requestToggleIsOnAllDesktops(WindowId wid) const = 0; virtual void requestToggleKeepAbove(WindowId wid) const = 0; virtual void requestToggleMinimized(WindowId wid) const = 0; virtual void requestToggleMaximized(WindowId wid) const = 0; virtual bool windowCanBeDragged(WindowId wid) const = 0; virtual QIcon iconFor(WindowId wid) const = 0; virtual WindowId winIdFor(QString appId, QRect geometry) const = 0; virtual AppData appDataFor(WindowId wid) const = 0; QString currentDesktop() const; QString currentActivity() const; - void registerIgnoredWindow(WindowId wid); - void unregisterIgnoredWindow(WindowId wid); + virtual void registerIgnoredWindow(WindowId wid); + virtual void unregisterIgnoredWindow(WindowId wid); void switchToNextActivity(); void switchToPreviousActivity(); virtual void switchToNextVirtualDesktop() const = 0; virtual void switchToPreviousVirtualDesktop() const = 0; Latte::Corona *corona(); Tracker::Schemes *schemesTracker(); Tracker::Windows *windowsTracker() const; signals: void activeWindowChanged(WindowId wid); void windowChanged(WindowId winfo); void windowAdded(WindowId wid); void windowRemoved(WindowId wid); void currentDesktopChanged(); void currentActivityChanged(); protected: QString m_currentDesktop; QString m_currentActivity; //! windows that must be ignored from tracking, a good example are Latte::Views and //! their Configuration windows QList m_ignoredWindows; QPointer m_activities; //! Sending too fast plenty of signals for the same window //! has no reason and can create HIGH CPU usage. This Timer //! can delay the batch sending of signals for the same window WindowId m_windowChangedWaiting; QTimer m_windowWaitingTimer; //! Plasma taskmanager rules ile KSharedConfig::Ptr rulesConfig; void considerWindowChanged(WindowId wid); private: Latte::Corona *m_corona; Tracker::Schemes *m_schemesTracker; Tracker::Windows *m_windowsTracker; }; } } #endif // ABSTRACTWINDOWINTERFACE_H diff --git a/app/wm/waylandinterface.cpp b/app/wm/waylandinterface.cpp index 8027e527..642ec5ec 100644 --- a/app/wm/waylandinterface.cpp +++ b/app/wm/waylandinterface.cpp @@ -1,648 +1,679 @@ /* * 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 "waylandinterface.h" // local #include "view/screenedgeghostwindow.h" #include "view/view.h" #include "../lattecorona.h" #include "../liblatte2/extras.h" // Qt #include #include #include #include #include #include // KDE #include #include #include #if KF5_VERSION_MINOR >= 52 #include #endif // X11 #include using namespace KWayland::Client; namespace Latte { class Private::GhostWindow : public QRasterWindow { Q_OBJECT public: + WindowSystem::WindowId m_winId; + GhostWindow(WindowSystem::WaylandInterface *waylandInterface) : m_waylandInterface(waylandInterface) { setFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::NoDropShadowWindowHint | Qt::WindowDoesNotAcceptFocus); setupWaylandIntegration(); show(); } ~GhostWindow() { + m_waylandInterface->unregisterIgnoredWindow(m_winId); delete m_shellSurface; } void setGeometry(const QRect &rect) { if (geometry() == rect) { return; } + if (m_winId.isNull()) { + m_winId = m_waylandInterface->winIdFor("latte-dock", geometry()); + m_waylandInterface->registerIgnoredWindow(m_winId); + } + setMinimumSize(rect.size()); setMaximumSize(rect.size()); resize(rect.size()); m_shellSurface->setPosition(rect.topLeft()); } void setupWaylandIntegration() { using namespace KWayland::Client; if (m_shellSurface) return; Surface *s{Surface::fromWindow(this)}; if (!s) return; m_shellSurface = m_waylandInterface->waylandCoronaInterface()->createSurface(s, this); qDebug() << "wayland ghost window surface was created..."; m_shellSurface->setSkipTaskbar(true); m_shellSurface->setPanelTakesFocus(false); m_shellSurface->setRole(PlasmaShellSurface::Role::Panel); m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AlwaysVisible); } KWayland::Client::PlasmaShellSurface *m_shellSurface{nullptr}; WindowSystem::WaylandInterface *m_waylandInterface{nullptr}; }; namespace WindowSystem { WaylandInterface::WaylandInterface(QObject *parent) : AbstractWindowInterface(parent) { m_corona = qobject_cast(parent); } WaylandInterface::~WaylandInterface() { } void WaylandInterface::init() { } void WaylandInterface::initWindowManagement(KWayland::Client::PlasmaWindowManagement *windowManagement) { if (m_windowManagement == windowManagement) { return; } m_windowManagement = windowManagement; connect(m_windowManagement, &PlasmaWindowManagement::windowCreated, this, &WaylandInterface::windowCreatedProxy); connect(m_windowManagement, &PlasmaWindowManagement::activeWindowChanged, this, [&]() noexcept { auto w = m_windowManagement->activeWindow(); - if (!w || (w && (!isPlasmaDesktop(w) && w->appId() != QLatin1String("latte-dock")))) { + if (!w || (w && (!m_ignoredWindows.contains(w->internalId() && !isPlasmaDesktop(w)))) ) { emit activeWindowChanged(w ? w->internalId() : 0); } }, Qt::QueuedConnection); } #if KF5_VERSION_MINOR >= 52 void WaylandInterface::initVirtualDesktopManagement(KWayland::Client::PlasmaVirtualDesktopManagement *virtualDesktopManagement) { if (m_virtualDesktopManagement == virtualDesktopManagement) { return; } m_virtualDesktopManagement = virtualDesktopManagement; connect(m_virtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::desktopCreated, this, [this](const QString &id, quint32 position) { addDesktop(id, position); }); connect(m_virtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::desktopRemoved, this, [this](const QString &id) { m_desktops.removeAll(id); if (m_currentDesktop == id) { setCurrentDesktop(QString()); } }); } void WaylandInterface::addDesktop(const QString &id, quint32 position) { if (m_desktops.contains(id)) { return; } m_desktops.append(id); const KWayland::Client::PlasmaVirtualDesktop *desktop = m_virtualDesktopManagement->getVirtualDesktop(id); QObject::connect(desktop, &KWayland::Client::PlasmaVirtualDesktop::activated, this, [desktop, this]() { setCurrentDesktop(desktop->id()); } ); if (desktop->isActive()) { setCurrentDesktop(id); } } void WaylandInterface::setCurrentDesktop(QString desktop) { if (m_currentDesktop == desktop) { return; } m_currentDesktop = desktop; emit currentDesktopChanged(); } #endif KWayland::Client::PlasmaShell *WaylandInterface::waylandCoronaInterface() const { return m_corona->waylandCoronaInterface(); } +//! Register Latte Ignored Windows in order to NOT be tracked +void WaylandInterface::registerIgnoredWindow(WindowId wid) +{ + if (!wid.isNull() && !m_ignoredWindows.contains(wid)) { + m_ignoredWindows.append(wid); + + KWayland::Client::PlasmaWindow *w = windowFor(wid); + + if (mapper && w) { + mapper->removeMappings(w); + } + + emit windowChanged(wid); + } +} + +void WaylandInterface::unregisterIgnoredWindow(WindowId wid) +{ + if (m_ignoredWindows.contains(wid)) { + m_ignoredWindows.removeAll(wid); + emit windowRemoved(wid); + } +} + void WaylandInterface::setViewExtraFlags(QWindow &view) { Q_UNUSED(view) } void WaylandInterface::setViewStruts(QWindow &view, const QRect &rect, Plasma::Types::Location location) { if (!m_ghostWindows.contains(view.winId())) { m_ghostWindows[view.winId()] = new Private::GhostWindow(this); } auto w = m_ghostWindows[view.winId()]; switch (location) { case Plasma::Types::TopEdge: case Plasma::Types::BottomEdge: w->setGeometry({rect.x() + rect.width() / 2, rect.y(), 1, rect.height()}); break; case Plasma::Types::LeftEdge: case Plasma::Types::RightEdge: w->setGeometry({rect.x(), rect.y() + rect.height() / 2, rect.width(), 1}); break; default: break; } - } void WaylandInterface::switchToNextVirtualDesktop() const { #if KF5_VERSION_MINOR >= 52 if (!m_virtualDesktopManagement || m_desktops.count() <= 1) { return; } int curPos = m_desktops.indexOf(m_currentDesktop); int nextPos = curPos + 1; if (curPos == m_desktops.count()-1) { nextPos = 0; } KWayland::Client::PlasmaVirtualDesktop *desktopObj = m_virtualDesktopManagement->getVirtualDesktop(m_desktops[nextPos]); if (desktopObj) { desktopObj->requestActivate(); } #endif } void WaylandInterface::switchToPreviousVirtualDesktop() const { #if KF5_VERSION_MINOR >= 52 if (!m_virtualDesktopManagement || m_desktops.count() <= 1) { return; } int curPos = m_desktops.indexOf(m_currentDesktop); int nextPos = curPos - 1; if (curPos == 0) { nextPos = m_desktops.count()-1; } KWayland::Client::PlasmaVirtualDesktop *desktopObj = m_virtualDesktopManagement->getVirtualDesktop(m_desktops[nextPos]); if (desktopObj) { desktopObj->requestActivate(); } #endif } void WaylandInterface::setWindowOnActivities(QWindow &window, const QStringList &activities) { //! needs to updated to wayland case // KWindowSystem::setOnActivities(view.winId(), activities); } void WaylandInterface::removeViewStruts(QWindow &view) const { delete m_ghostWindows.take(view.winId()); } WindowId WaylandInterface::activeWindow() const { if (!m_windowManagement) { return 0; } auto wid = m_windowManagement->activeWindow(); return wid ? wid->internalId() : 0; } void WaylandInterface::setKeepAbove(const QDialog &dialog, bool above) const { if (above) { KWindowSystem::setState(dialog.winId(), NET::KeepAbove); } else { KWindowSystem::clearState(dialog.winId(), NET::KeepAbove); } } void WaylandInterface::skipTaskBar(const QDialog &dialog) const { KWindowSystem::setState(dialog.winId(), NET::SkipTaskbar); } void WaylandInterface::slideWindow(QWindow &view, AbstractWindowInterface::Slide location) const { auto slideLocation = KWindowEffects::NoEdge; switch (location) { case Slide::Top: slideLocation = KWindowEffects::TopEdge; break; case Slide::Bottom: slideLocation = KWindowEffects::BottomEdge; break; case Slide::Left: slideLocation = KWindowEffects::LeftEdge; break; case Slide::Right: slideLocation = KWindowEffects::RightEdge; break; default: break; } KWindowEffects::slideWindow(view.winId(), slideLocation, -1); } void WaylandInterface::enableBlurBehind(QWindow &view) const { KWindowEffects::enableBlurBehind(view.winId()); } void WaylandInterface::setEdgeStateFor(QWindow *view, bool active) const { ViewPart::ScreenEdgeGhostWindow *window = qobject_cast(view); if (!window) { return; } if (window->parentView()->surface() && window->parentView()->visibility() && (window->parentView()->visibility()->mode() == Types::DodgeActive || window->parentView()->visibility()->mode() == Types::DodgeMaximized || window->parentView()->visibility()->mode() == Types::DodgeAllWindows || window->parentView()->visibility()->mode() == Types::AutoHide)) { if (active) { window->showWithMask(); window->surface()->requestHideAutoHidingPanel(); } else { window->hideWithMask(); window->surface()->requestShowAutoHidingPanel(); } } } WindowInfoWrap WaylandInterface::requestInfoActive() const { if (!m_windowManagement) { return {}; } auto w = m_windowManagement->activeWindow(); if (!w) return {}; return requestInfo(w->internalId()); } WindowInfoWrap WaylandInterface::requestInfo(WindowId wid) const { WindowInfoWrap winfoWrap; auto w = windowFor(wid); if (w) { if (isPlasmaDesktop(w)) { winfoWrap.setIsValid(true); winfoWrap.setIsPlasmaDesktop(true); winfoWrap.setWid(wid); } else if (isValidWindow(w)) { winfoWrap.setIsValid(true); winfoWrap.setWid(wid); winfoWrap.setIsActive(w->isActive()); winfoWrap.setIsMinimized(w->isMinimized()); winfoWrap.setIsMaxVert(w->isMaximized()); winfoWrap.setIsMaxHoriz(w->isMaximized()); winfoWrap.setIsFullscreen(w->isFullscreen()); winfoWrap.setIsShaded(w->isShaded()); winfoWrap.setIsOnAllDesktops(w->isOnAllDesktops()); winfoWrap.setIsOnAllActivities(true); winfoWrap.setGeometry(w->geometry()); winfoWrap.setHasSkipTaskbar(w->skipTaskbar()); winfoWrap.setDisplay(w->title()); #if KF5_VERSION_MINOR >= 52 winfoWrap.setDesktops(w->plasmaVirtualDesktops()); #endif winfoWrap.setActivities(QStringList()); } } else { return {}; } return winfoWrap; } AppData WaylandInterface::appDataFor(WindowId wid) const { auto window = windowFor(wid); const AppData &data = appDataFromUrl(windowUrlFromMetadata(window->appId(), window->pid(), rulesConfig)); return data; } KWayland::Client::PlasmaWindow *WaylandInterface::windowFor(WindowId wid) const { auto it = std::find_if(m_windowManagement->windows().constBegin(), m_windowManagement->windows().constEnd(), [&wid](PlasmaWindow * w) noexcept { return w->isValid() && w->internalId() == wid; }); if (it == m_windowManagement->windows().constEnd()) { return nullptr; } return *it; } QIcon WaylandInterface::iconFor(WindowId wid) const { auto window = windowFor(wid); if (window) { return window->icon(); } return QIcon(); } WindowId WaylandInterface::winIdFor(QString appId, QRect geometry) const { auto it = std::find_if(m_windowManagement->windows().constBegin(), m_windowManagement->windows().constEnd(), [&appId, &geometry](PlasmaWindow * w) noexcept { return w->isValid() && w->appId() == appId && w->geometry() == geometry; }); if (it == m_windowManagement->windows().constEnd()) { - return 0; + return QVariant(); } return (*it)->internalId(); } bool WaylandInterface::windowCanBeDragged(WindowId wid) const { WindowInfoWrap winfo = requestInfo(wid); return (winfo.isValid() && !winfo.isMinimized() && !winfo.isPlasmaDesktop()); } void WaylandInterface::releaseMouseEventFor(WindowId wid) const { // this isnt really needed under wayland } void WaylandInterface::requestActivate(WindowId wid) const { auto w = windowFor(wid); if (w) { w->requestActivate(); } } void WaylandInterface::requestClose(WindowId wid) const { auto w = windowFor(wid); if (w) { w->requestClose(); } } void WaylandInterface::requestMoveWindow(WindowId wid, QPoint from) const { if (windowCanBeDragged(wid)) { auto w = windowFor(wid); if (w && isValidWindow(w)) { w->requestMove(); } } } void WaylandInterface::requestToggleIsOnAllDesktops(WindowId wid) const { #if KF5_VERSION_MINOR >= 52 auto w = windowFor(wid); if (w && isValidWindow(w) && m_desktops.count() > 1) { if (w->isOnAllDesktops()) { w->requestEnterVirtualDesktop(m_currentDesktop); } else { const QStringList &now = w->plasmaVirtualDesktops(); foreach (const QString &desktop, now) { w->requestLeaveVirtualDesktop(desktop); } } } #endif } void WaylandInterface::requestToggleKeepAbove(WindowId wid) const { auto w = windowFor(wid); if (w) { w->requestToggleKeepAbove(); } } void WaylandInterface::requestToggleMinimized(WindowId wid) const { auto w = windowFor(wid); if (w && isValidWindow(w)) { #if KF5_VERSION_MINOR >= 52 if (!m_currentDesktop.isEmpty()) { w->requestEnterVirtualDesktop(m_currentDesktop); } #endif w->requestToggleMinimized(); } } void WaylandInterface::requestToggleMaximized(WindowId wid) const { auto w = windowFor(wid); if (w && isValidWindow(w)) { #if KF5_VERSION_MINOR >= 52 if (!m_currentDesktop.isEmpty()) { w->requestEnterVirtualDesktop(m_currentDesktop); } #endif w->requestToggleMaximized(); } } bool WaylandInterface::isPlasmaDesktop(const KWayland::Client::PlasmaWindow *w) const { if (!w || (w->appId() != QLatin1String("org.kde.plasmashell"))) { return false; } bool hasScreenGeometry{false}; for (const auto scr : qGuiApp->screens()) { if (!w->geometry().isEmpty() && w->geometry() == scr->geometry()) { hasScreenGeometry = true; break; } } return hasScreenGeometry; } bool WaylandInterface::isValidWindow(const KWayland::Client::PlasmaWindow *w) const { //! DEPRECATED comment is case we must reenable this //! because wayland does not have any way yet to identify the window type //! a trick is to just consider windows as valid when they can be shown in the //! taskbar. Of course that creates issues with plasma native dialogs //! e.g. widgets explorer, Activities etc. that are not used to hide //! the dodge views appropriately - return w->isValid() && !isPlasmaDesktop(w) && w->appId()!=QLatin1String("latte-dock");// && !w->skipTaskbar(); + return w->isValid() && !isPlasmaDesktop(w) && !m_ignoredWindows.contains(w->internalId()); } void WaylandInterface::windowCreatedProxy(KWayland::Client::PlasmaWindow *w) { if (!isValidWindow(w)) return; if (!mapper) mapper = new QSignalMapper(this); mapper->setMapping(w, w); connect(w, &PlasmaWindow::unmapped, this, [ &, win = w]() noexcept { mapper->removeMappings(win); emit windowRemoved(win->internalId()); }); connect(w, SIGNAL(activeChanged()), mapper, SLOT(map()) ); connect(w, SIGNAL(titleChanged()), mapper, SLOT(map()) ); connect(w, SIGNAL(fullscreenChanged()), mapper, SLOT(map()) ); connect(w, SIGNAL(geometryChanged()), mapper, SLOT(map()) ); connect(w, SIGNAL(maximizedChanged()), mapper, SLOT(map()) ); connect(w, SIGNAL(minimizedChanged()), mapper, SLOT(map()) ); connect(w, SIGNAL(shadedChanged()), mapper, SLOT(map()) ); connect(w, SIGNAL(skipTaskbarChanged()), mapper, SLOT(map()) ); connect(w, SIGNAL(onAllDesktopsChanged()), mapper, SLOT(map()) ); connect(w, SIGNAL(virtualDesktopChanged()), mapper, SLOT(map()) ); #if KF5_VERSION_MINOR >= 52 connect(w, &KWayland::Client::PlasmaWindow::plasmaVirtualDesktopEntered, this, [w, this] { mapper->map(w); }); connect(w, &KWayland::Client::PlasmaWindow::plasmaVirtualDesktopLeft, this, [w, this] { mapper->map(w); }); #endif connect(mapper, static_cast(&QSignalMapper::mapped) , this, [&](QObject * w) noexcept { //qDebug() << "window changed:" << qobject_cast(w)->appId(); PlasmaWindow *pW = qobject_cast(w); - if (pW && !isPlasmaDesktop(pW) && pW->appId() != QLatin1String("latte-dock")) { + if (pW && !m_ignoredWindows.contains(pW->internalId() && !isPlasmaDesktop(pW) )) { considerWindowChanged(pW->internalId()); } }); emit windowAdded(w->internalId()); } } } #include "waylandinterface.moc" diff --git a/app/wm/waylandinterface.h b/app/wm/waylandinterface.h index 0e7a8276..51068ddc 100644 --- a/app/wm/waylandinterface.h +++ b/app/wm/waylandinterface.h @@ -1,143 +1,146 @@ /* * 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 . */ #ifndef WAYLANDINTERFACE_H #define WAYLANDINTERFACE_H // local #include #include "abstractwindowinterface.h" #include "windowinfowrap.h" // Qt #include #include #include // KDE #include #include #include #include #include #include #include namespace Latte { class Corona; namespace Private { //! this class is used to create the struts inside wayland class GhostWindow; } } namespace Latte { namespace WindowSystem { class WaylandInterface : public AbstractWindowInterface { Q_OBJECT public: explicit WaylandInterface(QObject *parent = nullptr); ~WaylandInterface() override; void setViewExtraFlags(QWindow &view) override; void setViewStruts(QWindow &view, const QRect &rect , Plasma::Types::Location location) override; void setWindowOnActivities(QWindow &view, const QStringList &activities) override; void removeViewStruts(QWindow &view) const override; WindowId activeWindow() const override; WindowInfoWrap requestInfo(WindowId wid) const override; WindowInfoWrap requestInfoActive() const override; void setKeepAbove(const QDialog &dialog, bool above = true) const override; void skipTaskBar(const QDialog &dialog) const override; void slideWindow(QWindow &view, Slide location) const override; void enableBlurBehind(QWindow &view) const override; void releaseMouseEventFor(WindowId wid) const override; void requestActivate(WindowId wid) const override; void requestClose(WindowId wid) const override; void requestMoveWindow(WindowId wid, QPoint from) const override; void requestToggleIsOnAllDesktops(WindowId wid) const override; void requestToggleKeepAbove(WindowId wid) const override; void requestToggleMinimized(WindowId wid) const override; void requestToggleMaximized(WindowId wid) const override; bool windowCanBeDragged(WindowId wid) const; QIcon iconFor(WindowId wid) const; WindowId winIdFor(QString appId, QRect geometry) const override; AppData appDataFor(WindowId wid) const override; void setEdgeStateFor(QWindow *view, bool active) const override; void switchToNextVirtualDesktop() const override; void switchToPreviousVirtualDesktop() const override; + void registerIgnoredWindow(WindowId wid) override; + void unregisterIgnoredWindow(WindowId wid) override; + void initWindowManagement(KWayland::Client::PlasmaWindowManagement *windowManagement); #if KF5_VERSION_MINOR >= 52 //! VirtualDesktopsSupport void initVirtualDesktopManagement(KWayland::Client::PlasmaVirtualDesktopManagement *virtualDesktopManagement); #endif private: void init(); bool isValidWindow(const KWayland::Client::PlasmaWindow *w) const; bool isPlasmaDesktop(const KWayland::Client::PlasmaWindow *w) const; void windowCreatedProxy(KWayland::Client::PlasmaWindow *w); KWayland::Client::PlasmaWindow *windowFor(WindowId wid) const; KWayland::Client::PlasmaShell *waylandCoronaInterface() const; #if KF5_VERSION_MINOR >= 52 //! VirtualDesktopsSupport void setCurrentDesktop(QString desktop); void addDesktop(const QString &id, quint32 position); #endif private: QSignalMapper *mapper{nullptr}; friend class Private::GhostWindow; mutable QMap m_ghostWindows; KWayland::Client::PlasmaWindowManagement *m_windowManagement{nullptr}; #if KF5_VERSION_MINOR >= 52 //! VirtualDesktopsSupport KWayland::Client::PlasmaVirtualDesktopManagement *m_virtualDesktopManagement{nullptr}; QStringList m_desktops; #endif Latte::Corona *m_corona{nullptr}; }; } } #endif // WAYLANDINTERFACE_H