File indexing completed on 2024-06-16 14:04:38

0001 /*
0002 *  Copyright 2018 Michail Vourlakos <mvourlakos@gmail.com>
0003 *
0004 *  This file is part of Latte-Dock
0005 *
0006 *  Latte-Dock is free software; you can redistribute it and/or
0007 *  modify it under the terms of the GNU General Public License as
0008 *  published by the Free Software Foundation; either version 2 of
0009 *  the License, or (at your option) any later version.
0010 *
0011 *  Latte-Dock is distributed in the hope that it will be useful,
0012 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 *  GNU General Public License for more details.
0015 *
0016 *  You should have received a copy of the GNU General Public License
0017 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
0018 */
0019 
0020 #include "screenedgeghostwindow.h"
0021 
0022 // local
0023 #include "view.h"
0024 
0025 // Qt
0026 #include <QDebug>
0027 #include <QSurfaceFormat>
0028 #include <QQuickView>
0029 #include <QTimer>
0030 
0031 // KDE
0032 #include <KWayland/Client/plasmashell.h>
0033 #include <KWayland/Client/surface.h>
0034 #include <KWindowSystem>
0035 
0036 // X11
0037 #include <NETWM>
0038 
0039 namespace Latte {
0040 namespace ViewPart {
0041 
0042 ScreenEdgeGhostWindow::ScreenEdgeGhostWindow(Latte::View *view) :
0043     m_latteView(view)
0044 {
0045     m_corona = qobject_cast<Latte::Corona *>(view->corona());
0046 
0047     bool debugEdge = (qApp->arguments().contains("-d") && qApp->arguments().contains("--kwinedges"));
0048 
0049     setColor(debugEdge ? QColor("purple") : QColor(Qt::transparent));
0050     setDefaultAlphaBuffer(true);
0051 
0052     setFlags(Qt::FramelessWindowHint
0053              | Qt::WindowStaysOnTopHint
0054              | Qt::NoDropShadowWindowHint
0055              | Qt::WindowDoesNotAcceptFocus);
0056 
0057     m_fixGeometryTimer.setSingleShot(true);
0058     m_fixGeometryTimer.setInterval(500);
0059     connect(&m_fixGeometryTimer, &QTimer::timeout, this, &ScreenEdgeGhostWindow::fixGeometry);
0060 
0061     //! this timer is used in order to avoid fast enter/exit signals during first
0062     //! appearing after edge activation
0063     m_delayedMouseTimer.setSingleShot(true);
0064     m_delayedMouseTimer.setInterval(50);
0065     connect(&m_delayedMouseTimer, &QTimer::timeout, this, [this]() {
0066         if (m_delayedContainsMouse) {
0067             setContainsMouse(true);
0068         } else {
0069             setContainsMouse(false);
0070         }
0071     });
0072 
0073     connect(this, &QQuickView::xChanged, this, &ScreenEdgeGhostWindow::startGeometryTimer);
0074     connect(this, &QQuickView::yChanged, this, &ScreenEdgeGhostWindow::startGeometryTimer);
0075     connect(this, &QQuickView::widthChanged, this, &ScreenEdgeGhostWindow::startGeometryTimer);
0076     connect(this, &QQuickView::heightChanged, this, &ScreenEdgeGhostWindow::startGeometryTimer);
0077 
0078     connect(m_latteView, &Latte::View::absoluteGeometryChanged, this, &ScreenEdgeGhostWindow::updateGeometry);
0079     connect(m_latteView, &Latte::View::screenGeometryChanged, this, &ScreenEdgeGhostWindow::updateGeometry);
0080     connect(m_latteView, &Latte::View::locationChanged, this, &ScreenEdgeGhostWindow::updateGeometry);
0081     connect(m_latteView, &QQuickView::screenChanged, this, [this]() {
0082         setScreen(m_latteView->screen());
0083         updateGeometry();
0084     });
0085 
0086     if (!KWindowSystem::isPlatformWayland()) {
0087         //! IMPORTANT!!! ::: This fixes a bug when closing an Activity all views from all Activities are
0088         //!  disappearing! With this code parts they reappear!!!
0089         m_visibleHackTimer1.setInterval(400);
0090         m_visibleHackTimer2.setInterval(2500);
0091         m_visibleHackTimer1.setSingleShot(true);
0092         m_visibleHackTimer2.setSingleShot(true);
0093 
0094         connectionsHack << connect(this, &QWindow::visibleChanged, this, [&]() {
0095             if (!m_inDelete && m_latteView && m_latteView->layout() && !isVisible()) {
0096                 m_visibleHackTimer1.start();
0097                 m_visibleHackTimer2.start();
0098             } else if (!m_inDelete) {
0099                 //! For some reason when the window is hidden in the edge under X11 afterwards
0100                 //! is losing its window flags
0101                 KWindowSystem::setType(winId(), NET::Dock);
0102                 KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager);
0103                 KWindowSystem::setOnAllDesktops(winId(), true);
0104             }
0105         });
0106 
0107         connectionsHack << connect(&m_visibleHackTimer1, &QTimer::timeout, this, [&]() {
0108             if (!m_inDelete && m_latteView && m_latteView->layout() && !isVisible()) {
0109                 show();
0110                 emit forcedShown();
0111                 //qDebug() << "Ghost Edge:: Enforce reshow from timer 1...";
0112             } else {
0113                 //qDebug() << "Ghost Edge:: No needed reshow from timer 1...";
0114             }
0115         });
0116 
0117         connectionsHack << connect(&m_visibleHackTimer2, &QTimer::timeout, this, [&]() {
0118             if (!m_inDelete && m_latteView && m_latteView->layout() && !isVisible()) {
0119                 show();
0120                 emit forcedShown();
0121                 //qDebug() << "Ghost Edge:: Enforce reshow from timer 2...";
0122             } else {
0123                 //qDebug() << "Ghost Edge:: No needed reshow from timer 2...";
0124             }
0125         });
0126 
0127         connectionsHack << connect(this, &ScreenEdgeGhostWindow::forcedShown, this, [&]() {
0128             m_corona->wm()->unregisterIgnoredWindow(m_trackedWindowId);
0129             m_trackedWindowId = winId();
0130             m_corona->wm()->registerIgnoredWindow(m_trackedWindowId);
0131         });
0132     }
0133 
0134     setupWaylandIntegration();
0135 
0136     if (KWindowSystem::isPlatformX11()) {
0137         m_trackedWindowId = winId();
0138         m_corona->wm()->registerIgnoredWindow(m_trackedWindowId);
0139     } else {
0140         connect(m_corona->wm(), &WindowSystem::AbstractWindowInterface::latteWindowAdded, this, [&]() {
0141             if (m_trackedWindowId.isNull()) {
0142                 m_trackedWindowId = m_corona->wm()->winIdFor("latte-dock", geometry());
0143                 m_corona->wm()->registerIgnoredWindow(m_trackedWindowId);
0144             }
0145         });
0146     }
0147 
0148     setScreen(m_latteView->screen());
0149     show();
0150     updateGeometry();
0151     hideWithMask();
0152 }
0153 
0154 ScreenEdgeGhostWindow::~ScreenEdgeGhostWindow()
0155 {
0156     m_inDelete = true;
0157 
0158     m_corona->wm()->unregisterIgnoredWindow(KWindowSystem::isPlatformX11() ? winId() : m_trackedWindowId);
0159 
0160     m_latteView = nullptr;
0161 
0162     // clear mode
0163     m_visibleHackTimer1.stop();
0164     m_visibleHackTimer2.stop();
0165     for (auto &c : connectionsHack) {
0166         disconnect(c);
0167     }
0168 
0169     if (m_shellSurface) {
0170         delete m_shellSurface;
0171     }
0172 }
0173 
0174 int ScreenEdgeGhostWindow::location()
0175 {
0176     return (int)m_latteView->location();
0177 }
0178 
0179 Latte::View *ScreenEdgeGhostWindow::parentView()
0180 {
0181     return m_latteView;
0182 }
0183 
0184 KWayland::Client::PlasmaShellSurface *ScreenEdgeGhostWindow::surface()
0185 {
0186     return m_shellSurface;
0187 }
0188 
0189 void ScreenEdgeGhostWindow::updateGeometry()
0190 {
0191     QRect newGeometry;
0192     int thickness{2};
0193 
0194     if (KWindowSystem::compositingActive()) {
0195         thickness = 6;
0196     }
0197 
0198     int length{30};
0199     int lengthDifference{0};
0200 
0201     if (m_latteView->formFactor() == Plasma::Types::Horizontal) {
0202         //! set minimum length to be 25% of screen width
0203         length = qMax(m_latteView->screenGeometry().width()/4,qMin(m_latteView->absoluteGeometry().width(), m_latteView->screenGeometry().width() - 1));
0204         lengthDifference = qMax(0,length - m_latteView->absoluteGeometry().width());
0205     } else {
0206         //! set minimum length to be 25% of screen height
0207         length = qMax(m_latteView->screenGeometry().height()/4,qMin(m_latteView->absoluteGeometry().height(), m_latteView->screenGeometry().height() - 1));
0208         lengthDifference = qMax(0,length - m_latteView->absoluteGeometry().height());
0209     }
0210 
0211     if (m_latteView->location() == Plasma::Types::BottomEdge) {
0212         int xF = qMax(m_latteView->screenGeometry().left(), m_latteView->absoluteGeometry().left() - lengthDifference);
0213         newGeometry.moveLeft(xF);
0214         newGeometry.moveTop(m_latteView->screenGeometry().bottom() - thickness);
0215     } else if (m_latteView->location() == Plasma::Types::TopEdge) {
0216         int xF = qMax(m_latteView->screenGeometry().left(), m_latteView->absoluteGeometry().left() - lengthDifference);
0217         newGeometry.moveLeft(xF);
0218         newGeometry.moveTop(m_latteView->screenGeometry().top());
0219     } else if (m_latteView->location() == Plasma::Types::LeftEdge) {
0220         int yF = qMax(m_latteView->screenGeometry().top(), m_latteView->absoluteGeometry().top() - lengthDifference);
0221         newGeometry.moveLeft(m_latteView->screenGeometry().left());
0222         newGeometry.moveTop(yF);
0223     } else if (m_latteView->location() == Plasma::Types::RightEdge) {
0224         int yF = qMax(m_latteView->screenGeometry().top(), m_latteView->absoluteGeometry().top() - lengthDifference);
0225         newGeometry.moveLeft(m_latteView->screenGeometry().right() - thickness);
0226         newGeometry.moveTop(yF);
0227     }
0228 
0229     if (m_latteView->formFactor() == Plasma::Types::Horizontal) {
0230         newGeometry.setWidth(length);
0231         newGeometry.setHeight(thickness + 1);
0232     } else {
0233         newGeometry.setWidth(thickness + 1);
0234         newGeometry.setHeight(length);
0235     }
0236 
0237     m_calculatedGeometry = newGeometry;
0238 
0239     fixGeometry();
0240 }
0241 
0242 void ScreenEdgeGhostWindow::fixGeometry()
0243 {
0244     if (!m_calculatedGeometry.isEmpty()
0245             && (m_calculatedGeometry.x() != x() || m_calculatedGeometry.y() != y()
0246                 || m_calculatedGeometry.width() != width() || m_calculatedGeometry.height() != height())) {
0247         setMinimumSize(m_calculatedGeometry.size());
0248         setMaximumSize(m_calculatedGeometry.size());
0249         resize(m_calculatedGeometry.size());
0250         setPosition(m_calculatedGeometry.x(), m_calculatedGeometry.y());
0251 
0252         if (m_shellSurface) {
0253             m_shellSurface->setPosition(m_calculatedGeometry.topLeft());
0254         }
0255     }
0256 }
0257 
0258 void ScreenEdgeGhostWindow::startGeometryTimer()
0259 {
0260     m_fixGeometryTimer.start();
0261 }
0262 
0263 void ScreenEdgeGhostWindow::setupWaylandIntegration()
0264 {
0265     if (m_shellSurface || !KWindowSystem::isPlatformWayland() || !m_latteView || !m_latteView->containment()) {
0266         // already setup
0267         return;
0268     }
0269 
0270     if (m_corona) {
0271         using namespace KWayland::Client;
0272 
0273         PlasmaShell *interface = m_corona->waylandCoronaInterface();
0274 
0275         if (!interface) {
0276             return;
0277         }
0278 
0279         Surface *s = Surface::fromWindow(this);
0280 
0281         if (!s) {
0282             return;
0283         }
0284 
0285         qDebug() << "wayland screen edge ghost window surface was created...";
0286         m_shellSurface = interface->createSurface(s, this);
0287         m_shellSurface->setSkipTaskbar(true);
0288         m_shellSurface->setPanelTakesFocus(false);
0289         m_shellSurface->setRole(PlasmaShellSurface::Role::Panel);
0290         m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AutoHide);
0291     }
0292 }
0293 
0294 bool ScreenEdgeGhostWindow::containsMouse() const
0295 {
0296     return m_containsMouse;
0297 }
0298 
0299 void ScreenEdgeGhostWindow::setContainsMouse(bool contains)
0300 {
0301     if (m_containsMouse == contains) {
0302         return;
0303     }
0304 
0305     m_containsMouse = contains;
0306     emit containsMouseChanged(contains);
0307 }
0308 
0309 bool ScreenEdgeGhostWindow::event(QEvent *e)
0310 {
0311     if (e->type() == QEvent::DragEnter || e->type() == QEvent::DragMove) {
0312         if (!m_containsMouse) {
0313             m_delayedContainsMouse = false;
0314             m_delayedMouseTimer.stop();
0315             setContainsMouse(true);
0316             emit dragEntered();
0317         }
0318     } else if (e->type() == QEvent::Enter) {
0319         m_delayedContainsMouse = true;
0320         if (!m_delayedMouseTimer.isActive()) {
0321             m_delayedMouseTimer.start();
0322         }
0323     } else if (e->type() == QEvent::Leave || e->type() == QEvent::DragLeave) {
0324         m_delayedContainsMouse = false;
0325         if (!m_delayedMouseTimer.isActive()) {
0326             m_delayedMouseTimer.start();
0327         }
0328     }
0329 
0330     return QQuickView::event(e);
0331 }
0332 
0333 void ScreenEdgeGhostWindow::hideWithMask()
0334 {
0335     //! old values: 0,0,1,1 were blocking the top-left corner of the window
0336     QRect maskGeometry{-2, 0, 1, 1};
0337 
0338     setMask(maskGeometry);
0339 }
0340 
0341 void ScreenEdgeGhostWindow::showWithMask()
0342 {
0343     setMask(QRect());
0344 }
0345 
0346 }
0347 }