File indexing completed on 2024-04-28 16:49: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 "secondaryconfigview.h"
0021 
0022 // local
0023 #include <config-latte.h>
0024 #include "primaryconfigview.h"
0025 #include "../panelshadows_p.h"
0026 #include "../view.h"
0027 #include "../../lattecorona.h"
0028 #include "../../wm/abstractwindowinterface.h"
0029 
0030 // Qt
0031 #include <QQuickItem>
0032 #include <QQmlContext>
0033 #include <QQmlEngine>
0034 #include <QScreen>
0035 
0036 // KDE
0037 #include <KLocalizedContext>
0038 #include <KDeclarative/KDeclarative>
0039 #include <KWayland/Client/plasmashell.h>
0040 #include <KWayland/Client/surface.h>
0041 #include <KWindowEffects>
0042 #include <KWindowSystem>
0043 
0044 // Plasma
0045 #include <Plasma/Package>
0046 
0047 namespace Latte {
0048 namespace ViewPart {
0049 
0050 SecondaryConfigView::SecondaryConfigView(Latte::View *view, QWindow *parent)
0051     : QQuickView(nullptr),
0052       m_latteView(view)
0053 {
0054     m_parent = qobject_cast<PrimaryConfigView *>(parent);
0055     m_corona = qobject_cast<Latte::Corona *>(m_latteView->containment()->corona());
0056 
0057     setupWaylandIntegration();
0058 
0059     if (KWindowSystem::isPlatformX11()) {
0060         m_corona->wm()->registerIgnoredWindow(winId());
0061     } else {
0062         connect(m_corona->wm(), &WindowSystem::AbstractWindowInterface::latteWindowAdded, this, [&]() {
0063             if (m_waylandWindowId.isNull()) {
0064                 m_waylandWindowId = m_corona->wm()->winIdFor("latte-dock", geometry());
0065                 m_corona->wm()->registerIgnoredWindow(m_waylandWindowId);
0066             }
0067         });
0068     }
0069 
0070     setResizeMode(QQuickView::SizeViewToRootObject);
0071     setScreen(m_latteView->screen());
0072 
0073     if (m_latteView && m_latteView->containment()) {
0074         setIcon(qGuiApp->windowIcon());
0075     }
0076 
0077     m_screenSyncTimer.setSingleShot(true);
0078     m_screenSyncTimer.setInterval(100);
0079 
0080     connect(this, &QQuickView::widthChanged, this, &SecondaryConfigView::updateEffects);
0081     connect(this, &QQuickView::heightChanged, this, &SecondaryConfigView::updateEffects);
0082 
0083     connect(this, &QQuickView::statusChanged, [&](QQuickView::Status status) {
0084         if (status == QQuickView::Ready) {
0085             updateEffects();
0086         }
0087     });
0088 
0089     connections << connect(m_parent, &PrimaryConfigView::availableScreenGeometryChanged, this, &SecondaryConfigView::syncGeometry);
0090 
0091     connections << connect(&m_screenSyncTimer, &QTimer::timeout, this, [this]() {
0092         setScreen(m_latteView->screen());
0093         setFlags(wFlags());
0094 
0095         if (KWindowSystem::isPlatformX11()) {
0096 #if KF5_VERSION_MINOR >= 45
0097             KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager | NET::SkipSwitcher);
0098 #else
0099             KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager);
0100 #endif
0101 
0102             KWindowSystem::setOnAllDesktops(winId(), true);
0103         }
0104 
0105         syncGeometry();
0106         syncSlideEffect();
0107     });
0108     connections << connect(m_latteView->visibility(), &VisibilityManager::modeChanged, this, &SecondaryConfigView::syncGeometry);
0109 
0110     m_thicknessSyncTimer.setSingleShot(true);
0111     m_thicknessSyncTimer.setInterval(200);
0112     connections << connect(&m_thicknessSyncTimer, &QTimer::timeout, this, [this]() {
0113         syncGeometry();
0114     });
0115 
0116     connections << connect(m_latteView, &Latte::View::normalThicknessChanged, [&]() {
0117         m_thicknessSyncTimer.start();
0118     });
0119 }
0120 
0121 SecondaryConfigView::~SecondaryConfigView()
0122 {
0123     qDebug() << "SecDockConfigView deleting ...";
0124 
0125     m_corona->dialogShadows()->removeWindow(this);
0126 
0127     m_corona->wm()->unregisterIgnoredWindow(KWindowSystem::isPlatformX11() ? winId() : m_waylandWindowId);
0128 
0129     for (const auto &var : connections) {
0130         QObject::disconnect(var);
0131     }
0132 }
0133 
0134 void SecondaryConfigView::init()
0135 {
0136     qDebug() << "dock secondary config view : initialization started...";
0137 
0138     setDefaultAlphaBuffer(true);
0139     setColor(Qt::transparent);
0140     m_corona->dialogShadows()->addWindow(this);
0141     rootContext()->setContextProperty(QStringLiteral("latteView"), m_latteView);
0142     rootContext()->setContextProperty(QStringLiteral("viewConfig"), this);
0143     rootContext()->setContextProperty(QStringLiteral("plasmoid"), m_latteView->containment()->property("_plasma_graphicObject").value<QObject *>());
0144 
0145     KDeclarative::KDeclarative kdeclarative;
0146     kdeclarative.setDeclarativeEngine(engine());
0147     kdeclarative.setTranslationDomain(QStringLiteral("latte-dock"));
0148 #if KF5_VERSION_MINOR >= 45
0149     kdeclarative.setupContext();
0150     kdeclarative.setupEngine(engine());
0151 #else
0152     kdeclarative.setupBindings();
0153 #endif
0154 
0155     QByteArray tempFilePath = "lattedocksecondaryconfigurationui";
0156 
0157     updateEnabledBorders();
0158 
0159     auto source = QUrl::fromLocalFile(m_latteView->containment()->corona()->kPackage().filePath(tempFilePath));
0160     setSource(source);
0161     syncGeometry();
0162     syncSlideEffect();
0163 
0164     if (m_parent && KWindowSystem::isPlatformX11()) {
0165         m_parent->requestActivate();
0166     }
0167 
0168     qDebug() << "dock secondary config view : initialization ended...";
0169 }
0170 
0171 inline Qt::WindowFlags SecondaryConfigView::wFlags() const
0172 {
0173     return (flags() | Qt::FramelessWindowHint /*| Qt::WindowStaysOnTopHint*/) & ~Qt::WindowDoesNotAcceptFocus;
0174 }
0175 
0176 QRect SecondaryConfigView::geometryWhenVisible() const
0177 {
0178     return m_geometryWhenVisible;
0179 }
0180 
0181 void SecondaryConfigView::requestActivate()
0182 {
0183     if (KWindowSystem::isPlatformWayland() && m_shellSurface) {
0184         if (m_waylandWindowId.isNull()) {
0185             m_waylandWindowId = m_corona->wm()->winIdFor("latte-dock", geometry());
0186         }
0187 
0188         m_corona->wm()->requestActivate(m_waylandWindowId);
0189     } else {
0190         QQuickView::requestActivate();
0191     }
0192 }
0193 
0194 void SecondaryConfigView::syncGeometry()
0195 {
0196     if (!m_latteView || !m_latteView->layout() || !m_latteView->containment() || !m_parent || !rootObject()) {
0197         return;
0198     }
0199 
0200     const QSize size(rootObject()->width(), rootObject()->height());
0201     setMaximumSize(size);
0202     setMinimumSize(size);
0203     resize(size);
0204 
0205     const auto location = m_latteView->containment()->location();
0206     const auto scrGeometry = m_latteView->screenGeometry();
0207     const auto availGeometry = m_parent->availableScreenGeometry();
0208 
0209     int clearThickness = m_latteView->editThickness();
0210 
0211     int secondaryConfigSpacing = 2 * m_latteView->fontPixelSize();
0212 
0213     QPoint position{0, 0};
0214 
0215     int xPos{0};
0216     int yPos{0};
0217 
0218     switch (m_latteView->containment()->formFactor()) {
0219     case Plasma::Types::Horizontal: {
0220         if (qApp->isLeftToRight()) {
0221             xPos = availGeometry.x() + secondaryConfigSpacing;
0222         } else {
0223             xPos = availGeometry.x() + availGeometry.width() - size.width() - secondaryConfigSpacing;
0224         }
0225 
0226         if (location == Plasma::Types::TopEdge) {
0227             yPos = scrGeometry.y() + clearThickness;
0228         } else if (location == Plasma::Types::BottomEdge) {
0229             yPos = scrGeometry.y() + scrGeometry.height() - clearThickness - size.height();
0230         }
0231     }
0232         break;
0233 
0234     case Plasma::Types::Vertical: {
0235         yPos = availGeometry.y() + secondaryConfigSpacing;
0236 
0237         if (location == Plasma::Types::LeftEdge) {
0238             xPos = scrGeometry.x() + clearThickness;
0239         } else if (location == Plasma::Types::RightEdge) {
0240             xPos = scrGeometry.x() + scrGeometry.width() - clearThickness - size.width();
0241         }
0242     }
0243         break;
0244 
0245     default:
0246         qWarning() << "no sync geometry, wrong formFactor";
0247         break;
0248     }
0249 
0250     position = {xPos, yPos};
0251 
0252     updateEnabledBorders();
0253 
0254     m_geometryWhenVisible = QRect(position.x(), position.y(), size.width(), size.height());
0255 
0256     setPosition(position);
0257 
0258     if (m_shellSurface) {
0259         m_shellSurface->setPosition(position);
0260     }
0261 
0262     //! after placement request to activate the main config window in order to avoid
0263     //! rare cases of closing settings window from secondaryConfigView->focusOutEvent
0264     if (m_parent && KWindowSystem::isPlatformX11()) {
0265         m_parent->requestActivate();
0266     }
0267 }
0268 
0269 void SecondaryConfigView::syncSlideEffect()
0270 {
0271     if (!m_latteView || !m_latteView->containment()) {
0272         return;
0273     }
0274 
0275     auto slideLocation = WindowSystem::AbstractWindowInterface::Slide::None;
0276 
0277     switch (m_latteView->containment()->location()) {
0278     case Plasma::Types::TopEdge:
0279         slideLocation = WindowSystem::AbstractWindowInterface::Slide::Top;
0280         break;
0281 
0282     case Plasma::Types::RightEdge:
0283         slideLocation = WindowSystem::AbstractWindowInterface::Slide::Right;
0284         break;
0285 
0286     case Plasma::Types::BottomEdge:
0287         slideLocation = WindowSystem::AbstractWindowInterface::Slide::Bottom;
0288         break;
0289 
0290     case Plasma::Types::LeftEdge:
0291         slideLocation = WindowSystem::AbstractWindowInterface::Slide::Left;
0292         break;
0293 
0294     default:
0295         qDebug() << staticMetaObject.className() << "wrong location";
0296         break;
0297     }
0298 
0299     m_corona->wm()->slideWindow(*this, slideLocation);
0300 }
0301 
0302 void SecondaryConfigView::showEvent(QShowEvent *ev)
0303 {
0304     QQuickWindow::showEvent(ev);
0305 
0306     if (!m_latteView) {
0307         return;
0308     }
0309 
0310     m_corona->wm()->setViewExtraFlags(*this);
0311     setFlags(wFlags());
0312 
0313     syncGeometry();
0314     syncSlideEffect();
0315 
0316     m_screenSyncTimer.start();
0317     QTimer::singleShot(400, this, &SecondaryConfigView::syncGeometry);
0318 
0319     emit showSignal();
0320 }
0321 
0322 void SecondaryConfigView::focusOutEvent(QFocusEvent *ev)
0323 {
0324     Q_UNUSED(ev);
0325 
0326     const auto *focusWindow = qGuiApp->focusWindow();
0327 
0328     if ((focusWindow && (focusWindow->flags().testFlag(Qt::Popup)
0329                          || focusWindow->flags().testFlag(Qt::ToolTip)))
0330             || m_latteView->alternativesIsShown()) {
0331         return;
0332     }
0333 
0334     const auto parent = qobject_cast<PrimaryConfigView *>(m_parent);
0335 
0336     if (!m_latteView->containsMouse() && parent && !parent->sticker() && !parent->isActive()) {
0337         parent->hideConfigWindow();
0338     }
0339 }
0340 
0341 void SecondaryConfigView::setupWaylandIntegration()
0342 {
0343     if (m_shellSurface || !KWindowSystem::isPlatformWayland() || !m_latteView || !m_latteView->containment()) {
0344         // already setup
0345         return;
0346     }
0347 
0348     if (m_corona) {
0349         using namespace KWayland::Client;
0350         PlasmaShell *interface = m_corona->waylandCoronaInterface();
0351 
0352         if (!interface) {
0353             return;
0354         }
0355 
0356         Surface *s = Surface::fromWindow(this);
0357 
0358         if (!s) {
0359             return;
0360         }
0361 
0362         qDebug() << "wayland secondary settings surface was created...";
0363 
0364         m_shellSurface = interface->createSurface(s, this);
0365         m_shellSurface->setSkipTaskbar(true);
0366 #if KF5_VERSION_MINOR >= 47
0367         m_shellSurface->setSkipSwitcher(true);
0368 #endif
0369 
0370         syncGeometry();
0371     }
0372 }
0373 
0374 bool SecondaryConfigView::event(QEvent *e)
0375 {
0376     if (e->type() == QEvent::PlatformSurface) {
0377         if (auto pe = dynamic_cast<QPlatformSurfaceEvent *>(e)) {
0378             switch (pe->surfaceEventType()) {
0379             case QPlatformSurfaceEvent::SurfaceCreated:
0380 
0381                 if (m_shellSurface) {
0382                     break;
0383                 }
0384 
0385                 setupWaylandIntegration();
0386                 break;
0387 
0388             case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed:
0389                 if (m_shellSurface) {
0390                     delete m_shellSurface;
0391                     m_shellSurface = nullptr;
0392                     qDebug() << "WAYLAND secondary config window surface was deleted...";
0393                 }
0394 
0395                 break;
0396             }
0397         }
0398     }
0399 
0400     return QQuickView::event(e);
0401 }
0402 
0403 void SecondaryConfigView::hideConfigWindow()
0404 {
0405     if (m_shellSurface) {
0406         //!NOTE: Avoid crash in wayland environment with qt5.9
0407         close();
0408     } else {
0409         hide();
0410     }
0411 }
0412 
0413 void SecondaryConfigView::updateEffects()
0414 {
0415     //! Don't apply any effect before the wayland surface is created under wayland
0416     //! https://bugs.kde.org/show_bug.cgi?id=392890
0417     if (KWindowSystem::isPlatformWayland() && !m_shellSurface) {
0418         return;
0419     }
0420 
0421     if (!m_background) {
0422         m_background = new Plasma::FrameSvg(this);
0423     }
0424 
0425     if (m_background->imagePath() != "widgets/panel-background") {
0426         m_background->setImagePath(QStringLiteral("widgets/panel-background"));
0427     }
0428 
0429     m_background->setEnabledBorders(m_enabledBorders);
0430     m_background->resizeFrame(size());
0431 
0432     QRegion mask = m_background->mask();
0433 
0434     QRegion fixedMask = mask.isNull() ? QRegion(QRect(0,0,width(),height())) : mask;
0435 
0436     if (!fixedMask.isEmpty()) {
0437         setMask(fixedMask);
0438     } else {
0439         setMask(QRegion());
0440     }
0441 
0442     if (KWindowSystem::compositingActive()) {
0443         KWindowEffects::enableBlurBehind(winId(), true, fixedMask);
0444     } else {
0445         KWindowEffects::enableBlurBehind(winId(), false);
0446     }
0447 }
0448 
0449 //!BEGIN borders
0450 Plasma::FrameSvg::EnabledBorders SecondaryConfigView::enabledBorders() const
0451 {
0452     return m_enabledBorders;
0453 }
0454 
0455 void SecondaryConfigView::updateEnabledBorders()
0456 {
0457     if (!this->screen()) {
0458         return;
0459     }
0460 
0461     Plasma::FrameSvg::EnabledBorders borders = Plasma::FrameSvg::AllBorders;
0462 
0463     switch (m_latteView->location()) {
0464     case Plasma::Types::TopEdge:
0465         borders &= ~Plasma::FrameSvg::TopBorder;
0466         break;
0467 
0468     case Plasma::Types::LeftEdge:
0469         borders &= ~Plasma::FrameSvg::LeftBorder;
0470         break;
0471 
0472     case Plasma::Types::RightEdge:
0473         borders &= ~Plasma::FrameSvg::RightBorder;
0474         break;
0475 
0476     case Plasma::Types::BottomEdge:
0477         borders &=  ~Plasma::FrameSvg::BottomBorder;
0478         break;
0479 
0480     default:
0481         break;
0482     }
0483 
0484     if (m_enabledBorders != borders) {
0485         m_enabledBorders = borders;
0486 
0487         m_corona->dialogShadows()->addWindow(this, m_enabledBorders);
0488 
0489         emit enabledBordersChanged();
0490     }
0491 }
0492 
0493 //!END borders
0494 
0495 }
0496 }
0497