File indexing completed on 2025-02-16 14:20:27
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 }