File indexing completed on 2024-04-28 16:54:59

0001 /*
0002     SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include <config-plasma.h>
0008 
0009 #include "../c_ptr.h"
0010 #include "debug.h"
0011 #include "panelconfigview.h"
0012 #include "panelshadows_p.h"
0013 #include "panelview.h"
0014 #include "screenpool.h"
0015 #include "shellcorona.h"
0016 
0017 #include <QAction>
0018 #include <QApplication>
0019 #include <QDebug>
0020 #include <QGuiApplication>
0021 #include <QQmlContext>
0022 #include <QQmlEngine>
0023 #include <QRegularExpression>
0024 #include <QScreen>
0025 
0026 #include <KX11Extras>
0027 #include <kactioncollection.h>
0028 #include <kwindoweffects.h>
0029 #include <kwindowsystem.h>
0030 
0031 #include <Plasma/Containment>
0032 #include <Plasma/Package>
0033 
0034 #include <KWayland/Client/plasmashell.h>
0035 #include <KWayland/Client/surface.h>
0036 
0037 #if HAVE_X11
0038 #include <NETWM>
0039 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0040 #include <private/qtx11extras_p.h>
0041 #include <qpa/qplatformwindow_p.h>
0042 #else
0043 #include <QX11Info>
0044 #include <QtPlatformHeaders/QXcbWindowFunctions>
0045 #endif
0046 #include <xcb/xcb.h>
0047 #endif
0048 #include <chrono>
0049 
0050 using namespace std::chrono_literals;
0051 static const int MINSIZE = 10;
0052 
0053 PanelView::PanelView(ShellCorona *corona, QScreen *targetScreen, QWindow *parent)
0054     : PlasmaQuick::ContainmentView(corona, parent)
0055     , m_offset(0)
0056     , m_maxLength(0)
0057     , m_minLength(0)
0058     , m_contentLength(0)
0059     , m_distance(0)
0060     , m_thickness(30)
0061     , m_minDrawingWidth(0)
0062     , m_minDrawingHeight(0)
0063     , m_initCompleted(false)
0064     , m_floating(false)
0065     , m_alignment(Qt::AlignLeft)
0066     , m_corona(corona)
0067     , m_visibilityMode(NormalPanel)
0068     , m_opacityMode(Adaptive)
0069     , m_backgroundHints(Plasma::Types::StandardBackground)
0070     , m_shellSurface(nullptr)
0071 {
0072     if (targetScreen) {
0073         setPosition(targetScreen->geometry().center());
0074         setScreenToFollow(targetScreen);
0075         setScreen(targetScreen);
0076     }
0077     setResizeMode(QuickViewSharedEngine::SizeRootObjectToView);
0078     setColor(QColor(Qt::transparent));
0079     setFlags(Qt::FramelessWindowHint | Qt::WindowDoesNotAcceptFocus);
0080     updateAdaptiveOpacityEnabled();
0081 
0082     connect(&m_theme, &Plasma::Theme::themeChanged, this, &PanelView::updateMask);
0083     connect(&m_theme, &Plasma::Theme::themeChanged, this, &PanelView::updateAdaptiveOpacityEnabled);
0084     connect(this, &PanelView::backgroundHintsChanged, this, &PanelView::updateMask);
0085     connect(this, &PanelView::backgroundHintsChanged, this, &PanelView::updateEnabledBorders);
0086     // TODO: add finished/componentComplete signal to QuickViewSharedEngine,
0087     // so we exactly know when rootobject is available
0088     connect(this, &QuickViewSharedEngine::statusChanged, this, &PanelView::handleQmlStatusChange);
0089 
0090     m_unhideTimer.setSingleShot(true);
0091     m_unhideTimer.setInterval(500ms);
0092     connect(&m_unhideTimer, &QTimer::timeout, this, &PanelView::restoreAutoHide);
0093 
0094     m_lastScreen = targetScreen;
0095     connect(this, &PanelView::locationChanged, this, &PanelView::restore);
0096     connect(this, &PanelView::containmentChanged, this, &PanelView::refreshContainment);
0097 
0098     // FEATURE 352476: cancel focus on the panel when clicking outside
0099     connect(this, &PanelView::activeFocusItemChanged, this, [this] {
0100         if (containment()->status() == Plasma::Types::AcceptingInputStatus && !activeFocusItem()) {
0101             // BUG 454729: avoid switching to PassiveStatus in keyboard navigation
0102             containment()->setStatus(Plasma::Types::ActiveStatus);
0103         }
0104     });
0105 
0106     if (!m_corona->kPackage().isValid()) {
0107         qCWarning(PLASMASHELL) << "Invalid home screen package";
0108     }
0109 
0110     m_strutsTimer.setSingleShot(true);
0111     connect(&m_strutsTimer, &QTimer::timeout, this, &PanelView::updateStruts);
0112 
0113     // Register enums
0114     qmlRegisterUncreatableMetaObject(PanelView::staticMetaObject, "org.kde.plasma.shell.panel", 0, 1, "Global", QStringLiteral("Error: only enums"));
0115 
0116     qmlRegisterAnonymousType<QScreen>("", 1);
0117     rootContext()->setContextProperty(QStringLiteral("panel"), this);
0118     setSource(m_corona->kPackage().fileUrl("views", QStringLiteral("Panel.qml")));
0119     updatePadding();
0120     updateFloating();
0121 }
0122 
0123 PanelView::~PanelView()
0124 {
0125     if (containment()) {
0126         m_corona->requestApplicationConfigSync();
0127     }
0128 }
0129 
0130 KConfigGroup PanelView::panelConfig(ShellCorona *corona, Plasma::Containment *containment, QScreen *screen)
0131 {
0132     if (!containment || !screen) {
0133         return KConfigGroup();
0134     }
0135     KConfigGroup views(corona->applicationConfig(), "PlasmaViews");
0136     views = KConfigGroup(&views, QStringLiteral("Panel %1").arg(containment->id()));
0137 
0138     if (containment->formFactor() == Plasma::Types::Vertical) {
0139         return KConfigGroup(&views, QStringLiteral("Vertical") + QString::number(screen->size().height()));
0140         // treat everything else as horizontal
0141     } else {
0142         return KConfigGroup(&views, QStringLiteral("Horizontal") + QString::number(screen->size().width()));
0143     }
0144 }
0145 
0146 KConfigGroup PanelView::panelConfigDefaults(ShellCorona *corona, Plasma::Containment *containment, QScreen *screen)
0147 {
0148     if (!containment || !screen) {
0149         return KConfigGroup();
0150     }
0151 
0152     KConfigGroup views(corona->applicationConfig(), "PlasmaViews");
0153     views = KConfigGroup(&views, QStringLiteral("Panel %1").arg(containment->id()));
0154 
0155     return KConfigGroup(&views, QStringLiteral("Defaults"));
0156 }
0157 
0158 int PanelView::readConfigValueWithFallBack(const QString &key, int defaultValue)
0159 {
0160     int value = config().readEntry(key, configDefaults().readEntry(key, defaultValue));
0161     return value;
0162 }
0163 
0164 KConfigGroup PanelView::config() const
0165 {
0166     return panelConfig(m_corona, containment(), m_screenToFollow);
0167 }
0168 
0169 KConfigGroup PanelView::configDefaults() const
0170 {
0171     return panelConfigDefaults(m_corona, containment(), m_screenToFollow);
0172 }
0173 
0174 Q_INVOKABLE QString PanelView::fileFromPackage(const QString &key, const QString &fileName)
0175 {
0176     return corona()->kPackage().filePath(key.toUtf8(), fileName);
0177 }
0178 
0179 void PanelView::maximize()
0180 {
0181     int length;
0182     if (containment()->formFactor() == Plasma::Types::Vertical) {
0183         length = m_screenToFollow->size().height();
0184     } else {
0185         length = m_screenToFollow->size().width();
0186     }
0187     setOffset(0);
0188     setMinimumLength(length);
0189     setMaximumLength(length);
0190 }
0191 
0192 Qt::Alignment PanelView::alignment() const
0193 {
0194     return m_alignment;
0195 }
0196 
0197 void PanelView::setAlignment(Qt::Alignment alignment)
0198 {
0199     if (m_alignment == alignment) {
0200         return;
0201     }
0202 
0203     m_alignment = alignment;
0204     // alignment is not resolution dependent, doesn't save to Defaults
0205     config().parent().writeEntry("alignment", (int)m_alignment);
0206     Q_EMIT alignmentChanged();
0207     positionPanel();
0208 }
0209 
0210 int PanelView::offset() const
0211 {
0212     return m_offset;
0213 }
0214 
0215 void PanelView::setOffset(int offset)
0216 {
0217     if (m_offset == offset) {
0218         return;
0219     }
0220 
0221     if (formFactor() == Plasma::Types::Vertical) {
0222         if (offset + m_maxLength > m_screenToFollow->size().height()) {
0223             setMaximumLength(-m_offset + m_screenToFollow->size().height());
0224         }
0225     } else {
0226         if (offset + m_maxLength > m_screenToFollow->size().width()) {
0227             setMaximumLength(-m_offset + m_screenToFollow->size().width());
0228         }
0229     }
0230 
0231     m_offset = offset;
0232     config().writeEntry("offset", m_offset);
0233     configDefaults().writeEntry("offset", m_offset);
0234     positionPanel();
0235     Q_EMIT offsetChanged();
0236     m_corona->requestApplicationConfigSync();
0237     Q_EMIT m_corona->availableScreenRegionChanged();
0238 }
0239 
0240 int PanelView::thickness() const
0241 {
0242     return m_thickness;
0243 }
0244 
0245 int PanelView::totalThickness() const
0246 {
0247     switch (containment()->location()) {
0248     case Plasma::Types::TopEdge:
0249         return thickness() + m_topFloatingPadding + m_bottomFloatingPadding;
0250     case Plasma::Types::LeftEdge:
0251         return thickness() + m_leftFloatingPadding + m_rightFloatingPadding;
0252     case Plasma::Types::RightEdge:
0253         return thickness() + m_rightFloatingPadding + m_leftFloatingPadding;
0254     case Plasma::Types::BottomEdge:
0255         return thickness() + m_bottomFloatingPadding + m_topFloatingPadding;
0256     default:
0257         return thickness();
0258     }
0259 }
0260 
0261 void PanelView::setThickness(int value)
0262 {
0263     if (value < minThickness()) {
0264         value = minThickness();
0265     }
0266     if (value == thickness()) {
0267         return;
0268     }
0269 
0270     m_thickness = value;
0271     Q_EMIT thicknessChanged();
0272 
0273     // For thickness, always use the default thickness value for horizontal/vertical panel
0274     configDefaults().writeEntry("thickness", value);
0275     m_corona->requestApplicationConfigSync();
0276     resizePanel();
0277 }
0278 
0279 int PanelView::length() const
0280 {
0281     return qMax(1, m_contentLength);
0282 }
0283 
0284 void PanelView::setLength(int value)
0285 {
0286     if (value == m_contentLength) {
0287         return;
0288     }
0289 
0290     m_contentLength = value;
0291 
0292     resizePanel();
0293 }
0294 
0295 int PanelView::maximumLength() const
0296 {
0297     return m_maxLength;
0298 }
0299 
0300 void PanelView::setMaximumLength(int length)
0301 {
0302     if (length == m_maxLength) {
0303         return;
0304     }
0305 
0306     if (m_minLength > length) {
0307         setMinimumLength(length);
0308     }
0309 
0310     config().writeEntry("maxLength", length);
0311     configDefaults().writeEntry("maxLength", length);
0312     m_maxLength = length;
0313     Q_EMIT maximumLengthChanged();
0314     m_corona->requestApplicationConfigSync();
0315 
0316     resizePanel();
0317 }
0318 
0319 int PanelView::minimumLength() const
0320 {
0321     return m_minLength;
0322 }
0323 
0324 void PanelView::setMinimumLength(int length)
0325 {
0326     if (length == m_minLength) {
0327         return;
0328     }
0329 
0330     if (m_maxLength < length) {
0331         setMaximumLength(length);
0332     }
0333 
0334     config().writeEntry("minLength", length);
0335     configDefaults().writeEntry("minLength", length);
0336     m_minLength = length;
0337     Q_EMIT minimumLengthChanged();
0338     m_corona->requestApplicationConfigSync();
0339 
0340     resizePanel();
0341 }
0342 
0343 int PanelView::distance() const
0344 {
0345     return m_distance;
0346 }
0347 
0348 void PanelView::setDistance(int dist)
0349 {
0350     if (m_distance == dist) {
0351         return;
0352     }
0353 
0354     m_distance = dist;
0355     Q_EMIT distanceChanged();
0356     positionPanel();
0357 }
0358 
0359 bool PanelView::floating() const
0360 {
0361     return m_floating;
0362 }
0363 
0364 void PanelView::setFloating(bool floating)
0365 {
0366     if (m_floating == floating) {
0367         return;
0368     }
0369 
0370     m_floating = floating;
0371     if (config().isValid() && config().parent().isValid()) {
0372         config().parent().writeEntry("floating", (int)floating);
0373         m_corona->requestApplicationConfigSync();
0374     }
0375     Q_EMIT floatingChanged();
0376 
0377     updateFloating();
0378     updateEnabledBorders();
0379     updateMask();
0380 }
0381 
0382 int PanelView::minThickness() const
0383 {
0384     if (formFactor() == Plasma::Types::Vertical) {
0385         return m_minDrawingWidth;
0386     } else if (formFactor() == Plasma::Types::Horizontal) {
0387         return m_minDrawingHeight;
0388     }
0389     return 0;
0390 }
0391 
0392 Plasma::Types::BackgroundHints PanelView::backgroundHints() const
0393 {
0394     return m_backgroundHints;
0395 }
0396 
0397 void PanelView::setBackgroundHints(Plasma::Types::BackgroundHints hint)
0398 {
0399     if (m_backgroundHints == hint) {
0400         return;
0401     }
0402 
0403     m_backgroundHints = hint;
0404 
0405     Q_EMIT backgroundHintsChanged();
0406 }
0407 
0408 Plasma::FrameSvg::EnabledBorders PanelView::enabledBorders() const
0409 {
0410     return m_enabledBorders;
0411 }
0412 
0413 void PanelView::setVisibilityMode(PanelView::VisibilityMode mode)
0414 {
0415     if (m_visibilityMode == mode) {
0416         return;
0417     }
0418 
0419     m_visibilityMode = mode;
0420 
0421     disconnect(containment(), &Plasma::Applet::activated, this, &PanelView::showTemporarily);
0422     if (edgeActivated()) {
0423         connect(containment(), &Plasma::Applet::activated, this, &PanelView::showTemporarily);
0424     }
0425 
0426     if (config().isValid() && config().parent().isValid()) {
0427         // panelVisibility is not resolution dependent, don't write to Defaults
0428         config().parent().writeEntry("panelVisibility", (int)mode);
0429         m_corona->requestApplicationConfigSync();
0430     }
0431 
0432     visibilityModeToWayland();
0433     updateStruts();
0434 
0435     Q_EMIT visibilityModeChanged();
0436 
0437     restoreAutoHide();
0438 }
0439 
0440 void PanelView::visibilityModeToWayland()
0441 {
0442     if (!m_shellSurface) {
0443         return;
0444     }
0445     KWayland::Client::PlasmaShellSurface::PanelBehavior behavior;
0446     switch (m_visibilityMode) {
0447     case NormalPanel:
0448         behavior = KWayland::Client::PlasmaShellSurface::PanelBehavior::AlwaysVisible;
0449         break;
0450     case AutoHide:
0451         behavior = KWayland::Client::PlasmaShellSurface::PanelBehavior::AutoHide;
0452         break;
0453     case LetWindowsCover:
0454         behavior = KWayland::Client::PlasmaShellSurface::PanelBehavior::WindowsCanCover;
0455         break;
0456     case WindowsGoBelow:
0457         behavior = KWayland::Client::PlasmaShellSurface::PanelBehavior::WindowsGoBelow;
0458         break;
0459     default:
0460         Q_UNREACHABLE();
0461         return;
0462     }
0463     m_shellSurface->setPanelBehavior(behavior);
0464 }
0465 
0466 PanelView::VisibilityMode PanelView::visibilityMode() const
0467 {
0468     return m_visibilityMode;
0469 }
0470 
0471 PanelView::OpacityMode PanelView::opacityMode() const
0472 {
0473     if (!m_theme.adaptiveTransparencyEnabled()) {
0474         return PanelView::Translucent;
0475     }
0476     return m_opacityMode;
0477 }
0478 
0479 bool PanelView::adaptiveOpacityEnabled()
0480 {
0481     return m_theme.adaptiveTransparencyEnabled();
0482 }
0483 
0484 void PanelView::setOpacityMode(PanelView::OpacityMode mode)
0485 {
0486     if (m_opacityMode != mode) {
0487         m_opacityMode = mode;
0488         if (config().isValid() && config().parent().isValid()) {
0489             config().parent().writeEntry("panelOpacity", (int)mode);
0490             m_corona->requestApplicationConfigSync();
0491         }
0492         Q_EMIT opacityModeChanged();
0493     }
0494 }
0495 
0496 void PanelView::updateAdaptiveOpacityEnabled()
0497 {
0498     Q_EMIT opacityModeChanged();
0499     Q_EMIT adaptiveOpacityEnabledChanged();
0500 }
0501 
0502 void PanelView::positionPanel()
0503 {
0504     if (!containment()) {
0505         return;
0506     }
0507 
0508     if (!m_initCompleted) {
0509         return;
0510     }
0511 
0512     KWindowEffects::SlideFromLocation slideLocation = KWindowEffects::NoEdge;
0513 
0514     switch (containment()->location()) {
0515     case Plasma::Types::TopEdge:
0516         containment()->setFormFactor(Plasma::Types::Horizontal);
0517         slideLocation = KWindowEffects::TopEdge;
0518         break;
0519 
0520     case Plasma::Types::LeftEdge:
0521         containment()->setFormFactor(Plasma::Types::Vertical);
0522         slideLocation = KWindowEffects::LeftEdge;
0523         break;
0524 
0525     case Plasma::Types::RightEdge:
0526         containment()->setFormFactor(Plasma::Types::Vertical);
0527         slideLocation = KWindowEffects::RightEdge;
0528         break;
0529 
0530     case Plasma::Types::BottomEdge:
0531     default:
0532         containment()->setFormFactor(Plasma::Types::Horizontal);
0533         slideLocation = KWindowEffects::BottomEdge;
0534         break;
0535     }
0536     const QPoint pos = geometryByDistance(m_distance).topLeft();
0537     setPosition(pos);
0538 
0539     if (m_shellSurface) {
0540         m_shellSurface->setPosition(pos);
0541     }
0542 
0543     KWindowEffects::slideWindow(this, slideLocation, -1);
0544 }
0545 
0546 QRect PanelView::geometryByDistance(int distance) const
0547 {
0548     if (!containment() || !m_screenToFollow) {
0549         return QRect();
0550     }
0551 
0552     QScreen *s = m_screenToFollow;
0553     const QRect screenGeometry = s->geometry();
0554     QRect r(QPoint(0, 0), formFactor() == Plasma::Types::Vertical ? QSize(totalThickness(), height()) : QSize(width(), totalThickness()));
0555 
0556     if (formFactor() == Plasma::Types::Horizontal) {
0557         switch (m_alignment) {
0558         case Qt::AlignCenter:
0559             r.moveCenter(screenGeometry.center() + QPoint(m_offset, 0));
0560             break;
0561         case Qt::AlignRight:
0562             r.moveRight(screenGeometry.right() - m_offset);
0563             break;
0564         case Qt::AlignLeft:
0565         default:
0566             r.moveLeft(screenGeometry.left() + m_offset);
0567         }
0568     } else {
0569         switch (m_alignment) {
0570         case Qt::AlignCenter:
0571             r.moveCenter(screenGeometry.center() + QPoint(0, m_offset));
0572             break;
0573         case Qt::AlignRight:
0574             r.moveBottom(screenGeometry.bottom() - m_offset);
0575             break;
0576         case Qt::AlignLeft:
0577         default:
0578             r.moveTop(screenGeometry.top() + m_offset);
0579         }
0580     }
0581 
0582     switch (containment()->location()) {
0583     case Plasma::Types::TopEdge:
0584         r.moveTop(screenGeometry.top() + distance);
0585         break;
0586 
0587     case Plasma::Types::LeftEdge:
0588         r.moveLeft(screenGeometry.left() + distance);
0589         break;
0590 
0591     case Plasma::Types::RightEdge:
0592         r.moveRight(screenGeometry.right() - distance);
0593         break;
0594 
0595     case Plasma::Types::BottomEdge:
0596     default:
0597         r.moveBottom(screenGeometry.bottom() - distance);
0598     }
0599     return r.intersected(screenGeometry);
0600 }
0601 
0602 void PanelView::resizePanel()
0603 {
0604     if (!m_initCompleted) {
0605         return;
0606     }
0607 
0608     // On Wayland when a screen is disconnected and the panel is migrating to a newscreen
0609     // it can happen a moment where the qscreen gets destroyed before it gets reassigned
0610     // to the new screen
0611     if (!m_screenToFollow) {
0612         return;
0613     }
0614 
0615     QSize targetSize;
0616     QSize targetMinSize;
0617     QSize targetMaxSize;
0618 
0619     if (formFactor() == Plasma::Types::Vertical) {
0620         const int minSize = qMax(MINSIZE, m_minLength);
0621         int maxSize = qMin(m_maxLength, m_screenToFollow->size().height() - m_offset);
0622         maxSize = qMax(minSize, maxSize);
0623         targetMinSize = QSize(totalThickness(), minSize);
0624         targetMaxSize = QSize(totalThickness(), maxSize);
0625         targetSize = QSize(totalThickness(), std::clamp(m_contentLength + m_topFloatingPadding + m_bottomFloatingPadding, minSize, maxSize));
0626     } else {
0627         const int minSize = qMax(MINSIZE, m_minLength);
0628         int maxSize = qMin(m_maxLength, m_screenToFollow->size().width() - m_offset);
0629         maxSize = qMax(minSize, maxSize);
0630         targetMinSize = QSize(minSize, totalThickness());
0631         targetMaxSize = QSize(maxSize, totalThickness());
0632         targetSize = QSize(std::clamp(m_contentLength + m_leftFloatingPadding + m_rightFloatingPadding, minSize, maxSize), totalThickness());
0633     }
0634     if (minimumSize() != targetMinSize) {
0635         setMinimumSize(targetMinSize);
0636     }
0637     if (maximumSize() != targetMaxSize) {
0638         setMaximumSize(targetMaxSize);
0639     }
0640     if (size() != targetSize) {
0641         resize(targetSize);
0642     }
0643 
0644     // position will be updated implicitly from resizeEvent
0645 }
0646 
0647 void PanelView::restore()
0648 {
0649     KConfigGroup panelConfig = config();
0650     if (!panelConfig.isValid()) {
0651         return;
0652     }
0653 
0654     // All the defaults are based on whatever are the current values
0655     // so won't be weirdly reset after screen resolution change
0656 
0657     // alignment is not resolution dependent
0658     // but if fails read it from the resolution dependent one as
0659     // the place for this config key is changed in Plasma 5.9
0660     // Do NOT use readConfigValueWithFallBack
0661     setAlignment((Qt::Alignment)panelConfig.parent().readEntry<int>("alignment", panelConfig.readEntry<int>("alignment", m_alignment)));
0662 
0663     // All the other values are read from screen independent values,
0664     // but fallback on the screen independent section, as is the only place
0665     // is safe to directly write during plasma startup, as there can be
0666     // resolution changes
0667     m_offset = readConfigValueWithFallBack("offset", m_offset);
0668     if (m_alignment != Qt::AlignCenter) {
0669         m_offset = qMax(0, m_offset);
0670     }
0671 
0672     setFloating((bool)config().parent().readEntry<int>("floating", configDefaults().parent().readEntry<int>("floating", false)));
0673     setThickness(configDefaults().readEntry("thickness", m_thickness));
0674 
0675     const QSize screenSize = m_screenToFollow->size();
0676     setMinimumSize(QSize(-1, -1));
0677     // FIXME: an invalid size doesn't work with QWindows
0678     setMaximumSize(screenSize);
0679 
0680     m_initCompleted = true;
0681     positionPanel();
0682 
0683     const int side = containment()->formFactor() == Plasma::Types::Vertical ? screenSize.height() : screenSize.width();
0684     const int maxSize = side - m_offset;
0685     m_maxLength = qBound<int>(MINSIZE, readConfigValueWithFallBack("maxLength", side), maxSize);
0686     m_minLength = qBound<int>(MINSIZE, readConfigValueWithFallBack("minLength", side), maxSize);
0687 
0688     // panelVisibility is not resolution dependent
0689     // but if fails read it from the resolution dependent one as
0690     // the place for this config key is changed in Plasma 5.9
0691     // Do NOT use readConfigValueWithFallBack
0692     setVisibilityMode((VisibilityMode)panelConfig.parent().readEntry<int>("panelVisibility", panelConfig.readEntry<int>("panelVisibility", (int)NormalPanel)));
0693     setOpacityMode((OpacityMode)config().parent().readEntry<int>("panelOpacity",
0694                                                                  configDefaults().parent().readEntry<int>("panelOpacity", PanelView::OpacityMode::Adaptive)));
0695     resizePanel();
0696 
0697     Q_EMIT maximumLengthChanged();
0698     Q_EMIT minimumLengthChanged();
0699     Q_EMIT offsetChanged();
0700     Q_EMIT alignmentChanged();
0701 }
0702 
0703 void PanelView::showConfigurationInterface(Plasma::Applet *applet)
0704 {
0705     if (!applet || !applet->containment()) {
0706         return;
0707     }
0708 
0709     Plasma::Containment *cont = qobject_cast<Plasma::Containment *>(applet);
0710 
0711     const bool isPanelConfig = (cont && cont == containment() && cont->isContainment());
0712 
0713     if (m_panelConfigView) {
0714         if (m_panelConfigView->applet() == applet) {
0715             if (isPanelConfig) { // toggles panel controller, whereas applet config is always brought to front
0716                 if (m_panelConfigView->isVisible()) {
0717                     m_panelConfigView->hide();
0718                 } else {
0719                     m_panelConfigView->show();
0720                 }
0721                 return;
0722             }
0723 
0724             m_panelConfigView->show();
0725             m_panelConfigView->requestActivate();
0726             return;
0727         }
0728 
0729         m_panelConfigView->hide();
0730         m_panelConfigView->deleteLater();
0731     }
0732 
0733     if (isPanelConfig) {
0734         m_panelConfigView = new PanelConfigView(cont, this);
0735     } else {
0736         m_panelConfigView = new PlasmaQuick::ConfigView(applet);
0737     }
0738 
0739     m_panelConfigView->init();
0740     m_panelConfigView->show();
0741     m_panelConfigView->requestActivate();
0742 
0743     if (isPanelConfig) {
0744         KWindowSystem::setState(m_panelConfigView->winId(), NET::SkipTaskbar | NET::SkipPager);
0745     }
0746 }
0747 
0748 void PanelView::restoreAutoHide()
0749 {
0750     bool autoHide = true;
0751     disconnect(m_transientWindowVisibleWatcher);
0752 
0753     if (!edgeActivated()) {
0754         autoHide = false;
0755     } else if (m_containsMouse) {
0756         autoHide = false;
0757     } else if (containment() && containment()->isUserConfiguring()) {
0758         autoHide = false;
0759     } else if (containment() && containment()->status() >= Plasma::Types::NeedsAttentionStatus && containment()->status() != Plasma::Types::HiddenStatus) {
0760         autoHide = false;
0761     } else {
0762         for (QWindow *window : qApp->topLevelWindows()) {
0763             if (window->transientParent() == this && window->isVisible()) {
0764                 m_transientWindowVisibleWatcher = connect(window, &QWindow::visibleChanged, this, &PanelView::restoreAutoHide);
0765                 autoHide = false;
0766                 break; // if multiple are open, we will re-evaluate this expression after the first closes
0767             }
0768         }
0769     }
0770 
0771     setAutoHideEnabled(autoHide);
0772 }
0773 
0774 void PanelView::setAutoHideEnabled(bool enabled)
0775 {
0776 #if HAVE_X11
0777     if (KWindowSystem::isPlatformX11()) {
0778         xcb_connection_t *c = QX11Info::connection();
0779 
0780         const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW");
0781         xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData());
0782 
0783         UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr));
0784 
0785         if (!atom) {
0786             return;
0787         }
0788 
0789         if (!enabled) {
0790             xcb_delete_property(c, winId(), atom->atom);
0791             return;
0792         }
0793 
0794         KWindowEffects::SlideFromLocation slideLocation = KWindowEffects::NoEdge;
0795         uint32_t value = 0;
0796 
0797         switch (location()) {
0798         case Plasma::Types::TopEdge:
0799             value = 0;
0800             slideLocation = KWindowEffects::TopEdge;
0801             break;
0802         case Plasma::Types::RightEdge:
0803             value = 1;
0804             slideLocation = KWindowEffects::RightEdge;
0805             break;
0806         case Plasma::Types::BottomEdge:
0807             value = 2;
0808             slideLocation = KWindowEffects::BottomEdge;
0809             break;
0810         case Plasma::Types::LeftEdge:
0811             value = 3;
0812             slideLocation = KWindowEffects::LeftEdge;
0813             break;
0814         case Plasma::Types::Floating:
0815         default:
0816             value = 4;
0817             break;
0818         }
0819 
0820         int hideType = 0;
0821         if (m_visibilityMode == LetWindowsCover) {
0822             hideType = 1;
0823         }
0824         value |= hideType << 8;
0825 
0826         xcb_change_property(c, XCB_PROP_MODE_REPLACE, winId(), atom->atom, XCB_ATOM_CARDINAL, 32, 1, &value);
0827         KWindowEffects::slideWindow(this, slideLocation, -1);
0828     }
0829 #endif
0830     if (m_shellSurface && (m_visibilityMode == PanelView::AutoHide || m_visibilityMode == PanelView::LetWindowsCover)) {
0831         if (enabled) {
0832             m_shellSurface->requestHideAutoHidingPanel();
0833         } else {
0834             if (m_visibilityMode == PanelView::AutoHide)
0835                 m_shellSurface->requestShowAutoHidingPanel();
0836         }
0837     }
0838 }
0839 
0840 void PanelView::resizeEvent(QResizeEvent *ev)
0841 {
0842     updateEnabledBorders();
0843     // don't setGeometry() to make really sure we aren't doing a resize loop
0844     if (m_screenToFollow) {
0845         const QPoint pos = geometryByDistance(m_distance).topLeft();
0846         setPosition(pos);
0847         if (m_shellSurface) {
0848             m_shellSurface->setPosition(pos);
0849         }
0850 
0851         m_strutsTimer.start(STRUTSTIMERDELAY);
0852         Q_EMIT m_corona->availableScreenRegionChanged();
0853     }
0854 
0855     PlasmaQuick::ContainmentView::resizeEvent(ev);
0856 }
0857 
0858 void PanelView::moveEvent(QMoveEvent *ev)
0859 {
0860     updateEnabledBorders();
0861     m_strutsTimer.start(STRUTSTIMERDELAY);
0862     PlasmaQuick::ContainmentView::moveEvent(ev);
0863     if (m_screenToFollow && !m_screenToFollow->geometry().contains(geometry())) {
0864         positionPanel();
0865     }
0866 }
0867 
0868 void PanelView::keyPressEvent(QKeyEvent *event)
0869 {
0870     // Press escape key to cancel focus on the panel
0871     if (event->key() == Qt::Key_Escape) {
0872         if (containment()->status() == Plasma::Types::AcceptingInputStatus) {
0873             containment()->setStatus(Plasma::Types::PassiveStatus);
0874         }
0875         // No return for Wayland
0876     }
0877 
0878     PlasmaQuick::ContainmentView::keyPressEvent(event);
0879     if (event->isAccepted()) {
0880         return;
0881     }
0882 
0883     // Catch arrows keyPress to have same behavior as tab/backtab
0884     if ((event->key() == Qt::Key_Right && qApp->layoutDirection() == Qt::LeftToRight)
0885         || (event->key() == Qt::Key_Left && qApp->layoutDirection() != Qt::LeftToRight) || event->key() == Qt::Key_Down) {
0886         event->accept();
0887         QKeyEvent tabEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier);
0888         qApp->sendEvent((QObject *)this, (QEvent *)&tabEvent);
0889         return;
0890     } else if ((event->key() == Qt::Key_Right && qApp->layoutDirection() != Qt::LeftToRight)
0891                || (event->key() == Qt::Key_Left && qApp->layoutDirection() == Qt::LeftToRight) || event->key() == Qt::Key_Up) {
0892         event->accept();
0893         QKeyEvent backtabEvent(QEvent::KeyPress, Qt::Key_Backtab, Qt::NoModifier);
0894         qApp->sendEvent((QObject *)this, (QEvent *)&backtabEvent);
0895         return;
0896     }
0897 }
0898 
0899 void PanelView::integrateScreen()
0900 {
0901     updateMask();
0902     KX11Extras::setOnAllDesktops(winId(), true);
0903     KWindowSystem::setType(winId(), NET::Dock);
0904 #if HAVE_X11
0905 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0906     QXcbWindowFunctions::setWmWindowType(this, QXcbWindowFunctions::Dock);
0907 #else
0908     // QXcbWindow isn't installed and thus inaccessible to us, but it does read this magic property...
0909     setProperty("_q_xcb_wm_window_type", QNativeInterface::Private::QXcbWindow::Dock);
0910 #endif
0911 #endif
0912     if (m_shellSurface) {
0913         m_shellSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Panel);
0914         m_shellSurface->setSkipTaskbar(true);
0915     }
0916     setVisibilityMode(m_visibilityMode);
0917 
0918     if (containment()) {
0919         containment()->reactToScreenChange();
0920     }
0921 }
0922 
0923 void PanelView::showEvent(QShowEvent *event)
0924 {
0925     PlasmaQuick::ContainmentView::showEvent(event);
0926 
0927     integrateScreen();
0928 }
0929 
0930 void PanelView::setScreenToFollow(QScreen *screen)
0931 {
0932     if (screen == m_screenToFollow) {
0933         return;
0934     }
0935 
0936     if (!screen) {
0937         return;
0938     }
0939 
0940     if (!m_screenToFollow.isNull()) {
0941         // disconnect from old screen
0942         disconnect(m_screenToFollow, &QScreen::virtualGeometryChanged, this, &PanelView::updateStruts);
0943         disconnect(m_screenToFollow, &QScreen::geometryChanged, this, &PanelView::restore);
0944     }
0945 
0946     connect(screen, &QScreen::virtualGeometryChanged, this, &PanelView::updateStruts, Qt::UniqueConnection);
0947     connect(screen, &QScreen::geometryChanged, this, &PanelView::restore, Qt::UniqueConnection);
0948 
0949     /*connect(screen, &QObject::destroyed, this, [this]() {
0950         if (PanelView::screen()) {
0951             m_screenToFollow = PanelView::screen();
0952             adaptToScreen();
0953         }
0954     });*/
0955 
0956     m_screenToFollow = screen;
0957 
0958     setScreen(screen);
0959     adaptToScreen();
0960 }
0961 
0962 QScreen *PanelView::screenToFollow() const
0963 {
0964     return m_screenToFollow;
0965 }
0966 
0967 void PanelView::adaptToScreen()
0968 {
0969     Q_EMIT screenToFollowChanged(m_screenToFollow);
0970     m_lastScreen = m_screenToFollow;
0971 
0972     if (!m_screenToFollow) {
0973         return;
0974     }
0975 
0976     integrateScreen();
0977     showTemporarily();
0978     restore();
0979 }
0980 
0981 bool PanelView::event(QEvent *e)
0982 {
0983     switch (e->type()) {
0984     case QEvent::Enter:
0985         m_containsMouse = true;
0986         if (edgeActivated()) {
0987             m_unhideTimer.stop();
0988         }
0989         break;
0990 
0991     case QEvent::Leave:
0992         m_containsMouse = false;
0993         if (edgeActivated()) {
0994             m_unhideTimer.start();
0995         }
0996         break;
0997 
0998         /*Fitt's law: if the containment has margins, and the mouse cursor clicked
0999          * on the mouse edge, forward the click in the containment boundaries
1000          */
1001 
1002     case QEvent::MouseMove:
1003     case QEvent::MouseButtonPress:
1004     case QEvent::MouseButtonRelease: {
1005         QMouseEvent *me = static_cast<QMouseEvent *>(e);
1006 
1007         // first, don't mess with position if the cursor is actually outside the view:
1008         // somebody is doing a click and drag that must not break when the cursor i outside
1009         if (geometry().contains(QCursor::pos(screenToFollow()))) {
1010             if (!containmentContainsPosition(me->windowPos()) && !m_fakeEventPending) {
1011                 QMouseEvent me2(me->type(),
1012                                 positionAdjustedForContainment(me->windowPos()),
1013                                 positionAdjustedForContainment(me->windowPos()),
1014                                 positionAdjustedForContainment(me->windowPos()) + position(),
1015                                 me->button(),
1016                                 me->buttons(),
1017                                 me->modifiers());
1018 
1019                 m_fakeEventPending = true;
1020                 QCoreApplication::sendEvent(this, &me2);
1021                 m_fakeEventPending = false;
1022                 return true;
1023             }
1024         } else {
1025             // default handling if current mouse position is outside the panel
1026             return ContainmentView::event(e);
1027         }
1028         break;
1029     }
1030 
1031     case QEvent::Wheel: {
1032         QWheelEvent *we = static_cast<QWheelEvent *>(e);
1033 
1034         if (!containmentContainsPosition(we->position()) && !m_fakeEventPending) {
1035             QWheelEvent we2(positionAdjustedForContainment(we->position()),
1036                             positionAdjustedForContainment(we->position()) + position(),
1037                             we->pixelDelta(),
1038                             we->angleDelta(),
1039                             we->buttons(),
1040                             we->modifiers(),
1041                             we->phase(),
1042                             we->inverted());
1043 
1044             m_fakeEventPending = true;
1045             QCoreApplication::sendEvent(this, &we2);
1046             m_fakeEventPending = false;
1047             return true;
1048         }
1049         break;
1050     }
1051 
1052     case QEvent::DragEnter: {
1053         QDragEnterEvent *de = static_cast<QDragEnterEvent *>(e);
1054         if (!containmentContainsPosition(de->pos()) && !m_fakeEventPending) {
1055             QDragEnterEvent de2(positionAdjustedForContainment(de->pos()).toPoint(),
1056                                 de->possibleActions(),
1057                                 de->mimeData(),
1058                                 de->mouseButtons(),
1059                                 de->keyboardModifiers());
1060 
1061             m_fakeEventPending = true;
1062             QCoreApplication::sendEvent(this, &de2);
1063             m_fakeEventPending = false;
1064             return true;
1065         }
1066         break;
1067     }
1068     // DragLeave just works
1069     case QEvent::DragLeave:
1070         break;
1071     case QEvent::DragMove: {
1072         QDragMoveEvent *de = static_cast<QDragMoveEvent *>(e);
1073         if (!containmentContainsPosition(de->pos()) && !m_fakeEventPending) {
1074             QDragMoveEvent de2(positionAdjustedForContainment(de->pos()).toPoint(),
1075                                de->possibleActions(),
1076                                de->mimeData(),
1077                                de->mouseButtons(),
1078                                de->keyboardModifiers());
1079 
1080             m_fakeEventPending = true;
1081             QCoreApplication::sendEvent(this, &de2);
1082             m_fakeEventPending = false;
1083             return true;
1084         }
1085         break;
1086     }
1087     case QEvent::Drop: {
1088         QDropEvent *de = static_cast<QDropEvent *>(e);
1089         if (!containmentContainsPosition(de->pos()) && !m_fakeEventPending) {
1090             QDropEvent de2(positionAdjustedForContainment(de->pos()).toPoint(),
1091                            de->possibleActions(),
1092                            de->mimeData(),
1093                            de->mouseButtons(),
1094                            de->keyboardModifiers());
1095 
1096             m_fakeEventPending = true;
1097             QCoreApplication::sendEvent(this, &de2);
1098             m_fakeEventPending = false;
1099             return true;
1100         }
1101         break;
1102     }
1103 
1104     case QEvent::Hide: {
1105         if (m_panelConfigView && m_panelConfigView->isVisible()) {
1106             m_panelConfigView->hide();
1107         }
1108         m_containsMouse = false;
1109         break;
1110     }
1111     case QEvent::PlatformSurface:
1112         switch (static_cast<QPlatformSurfaceEvent *>(e)->surfaceEventType()) {
1113         case QPlatformSurfaceEvent::SurfaceCreated:
1114             setupWaylandIntegration();
1115             PanelShadows::self()->addWindow(this, enabledBorders());
1116             updateEnabledBorders();
1117             break;
1118         case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed:
1119             delete m_shellSurface;
1120             m_shellSurface = nullptr;
1121             PanelShadows::self()->removeWindow(this);
1122             break;
1123         }
1124         break;
1125     default:
1126         break;
1127     }
1128 
1129     return ContainmentView::event(e);
1130 }
1131 
1132 bool PanelView::containmentContainsPosition(const QPointF &point) const
1133 {
1134     QQuickItem *containmentItem = containment()->property("_plasma_graphicObject").value<QQuickItem *>();
1135 
1136     if (!containmentItem) {
1137         return false;
1138     }
1139 
1140     return QRectF(containmentItem->mapToScene(QPoint(m_leftPadding, m_topPadding)),
1141                   QSizeF(containmentItem->width() - m_leftPadding - m_rightPadding, containmentItem->height() - m_topPadding - m_bottomPadding))
1142         .contains(point);
1143 }
1144 
1145 QPointF PanelView::positionAdjustedForContainment(const QPointF &point) const
1146 {
1147     QQuickItem *containmentItem = containment()->property("_plasma_graphicObject").value<QQuickItem *>();
1148 
1149     if (!containmentItem) {
1150         return point;
1151     }
1152 
1153     QRectF containmentRect(containmentItem->mapToScene(QPoint(0, 0)), QSizeF(containmentItem->width(), containmentItem->height()));
1154 
1155     // We are removing 1 to the e.g. containmentRect.right() - m_rightPadding because the last pixel would otherwise
1156     // the first one in the margin, and thus the mouse event would be discarded. Instead, the first pixel given by
1157     // containmentRect.left() + m_leftPadding the first one *not* in the margin, so it work.
1158     return QPointF(qBound(containmentRect.left() + m_leftPadding, point.x(), containmentRect.right() - m_rightPadding - 1),
1159                    qBound(containmentRect.top() + m_topPadding, point.y(), containmentRect.bottom() - m_bottomPadding - 1));
1160 }
1161 
1162 void PanelView::updateMask()
1163 {
1164     if (m_backgroundHints == Plasma::Types::NoBackground) {
1165         KWindowEffects::enableBlurBehind(this, false);
1166         KWindowEffects::enableBackgroundContrast(this, false);
1167         setMask(QRegion());
1168     } else {
1169         QRegion mask;
1170 
1171         QQuickItem *rootObject = this->rootObject();
1172         if (rootObject) {
1173             const QVariant maskProperty = rootObject->property("panelMask");
1174             if (static_cast<QMetaType::Type>(maskProperty.type()) == QMetaType::QRegion) {
1175                 mask = maskProperty.value<QRegion>();
1176                 mask.translate(rootObject->property("maskOffsetX").toInt(), rootObject->property("maskOffsetY").toInt());
1177             }
1178         }
1179         KWindowEffects::enableBlurBehind(this, m_theme.blurBehindEnabled(), mask);
1180         KWindowEffects::enableBackgroundContrast(this,
1181                                                  m_theme.backgroundContrastEnabled(),
1182                                                  m_theme.backgroundContrast(),
1183                                                  m_theme.backgroundIntensity(),
1184                                                  m_theme.backgroundSaturation(),
1185                                                  mask);
1186 
1187         if (KX11Extras::compositingActive()) {
1188             setMask(QRegion());
1189         } else {
1190             setMask(mask);
1191         }
1192     }
1193 }
1194 
1195 bool PanelView::canSetStrut() const
1196 {
1197 #if HAVE_X11
1198     if (!KWindowSystem::isPlatformX11()) {
1199         return true;
1200     }
1201     // read the wm name, need to do this every time which means a roundtrip unfortunately
1202     // but WM might have changed
1203     NETRootInfo rootInfo(QX11Info::connection(), NET::Supported | NET::SupportingWMCheck);
1204     if (qstricmp(rootInfo.wmName(), "KWin") == 0) {
1205         // KWin since 5.7 can handle this fine, so only exclude for other window managers
1206         return true;
1207     }
1208 
1209     const QRect thisScreen = screen()->geometry();
1210     const int numScreens = corona()->numScreens();
1211     if (numScreens < 2) {
1212         return true;
1213     }
1214 
1215     // Extended struts against a screen edge near to another screen are really harmful, so windows maximized under the panel is a lesser pain
1216     // TODO: force "windows can cover" in those cases?
1217     const auto screenIds = m_corona->screenIds();
1218     for (int id : screenIds) {
1219         if (id == containment()->screen()) {
1220             continue;
1221         }
1222 
1223         const QRect otherScreen = corona()->screenGeometry(id);
1224         if (!otherScreen.isValid()) {
1225             continue;
1226         }
1227 
1228         switch (location()) {
1229         case Plasma::Types::TopEdge:
1230             if (otherScreen.bottom() <= thisScreen.top()) {
1231                 return false;
1232             }
1233             break;
1234         case Plasma::Types::BottomEdge:
1235             if (otherScreen.top() >= thisScreen.bottom()) {
1236                 return false;
1237             }
1238             break;
1239         case Plasma::Types::RightEdge:
1240             if (otherScreen.left() >= thisScreen.right()) {
1241                 return false;
1242             }
1243             break;
1244         case Plasma::Types::LeftEdge:
1245             if (otherScreen.right() <= thisScreen.left()) {
1246                 return false;
1247             }
1248             break;
1249         default:
1250             return false;
1251         }
1252     }
1253     return true;
1254 #else
1255     return true;
1256 #endif
1257 }
1258 
1259 void PanelView::updateStruts()
1260 {
1261 #if HAVE_X11
1262     if (!containment() || containment()->isUserConfiguring() || !m_screenToFollow) {
1263         return;
1264     }
1265 
1266     NETExtendedStrut strut;
1267 
1268     if (m_visibilityMode == NormalPanel) {
1269         if (!canSetStrut()) {
1270             KX11Extras::setExtendedStrut(winId(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
1271             return;
1272         }
1273 
1274         // NOTE Device coordinates must be transformed to logical coordinates when setting struts.
1275         // as KX11Extras::setExtendedStrut(...) will multiply struct.* by DPR again
1276         // Panel sizes are already divided by DPR.
1277         const double devicePixelRatio = m_screenToFollow->devicePixelRatio();
1278         const QRect thisScreen{m_screenToFollow->geometry().topLeft() / devicePixelRatio, m_screenToFollow->geometry().size()};
1279         QRect wholeScreen;
1280         const auto screens = qGuiApp->screens();
1281         for (auto screen : screens) {
1282             const QRect geometry = screen->geometry();
1283             wholeScreen = wholeScreen.united(QRect(geometry.topLeft() / devicePixelRatio, geometry.size()));
1284         }
1285 
1286         // extended struts are to the combined screen geoms, not the single screen
1287         switch (location()) {
1288         case Plasma::Types::TopEdge: {
1289             const int topOffset = thisScreen.top();
1290             strut.top_width = totalThickness() + topOffset;
1291             strut.top_start = x() / devicePixelRatio;
1292             strut.top_end = x() / devicePixelRatio + width() - 1;
1293             //                 qDebug() << "setting top edge to" << strut.top_width << strut.top_start << strut.top_end;
1294             break;
1295         }
1296 
1297         case Plasma::Types::BottomEdge: {
1298             const int bottomOffset = wholeScreen.bottom() - thisScreen.bottom();
1299             strut.bottom_width = totalThickness() + bottomOffset;
1300             strut.bottom_start = x() / devicePixelRatio;
1301             strut.bottom_end = x() / devicePixelRatio + width() - 1;
1302             //                 qDebug() << "setting bottom edge to" << strut.bottom_width << strut.bottom_start << strut.bottom_end;
1303             break;
1304         }
1305 
1306         case Plasma::Types::RightEdge: {
1307             const int rightOffset = wholeScreen.right() - thisScreen.right();
1308             strut.right_width = totalThickness() + rightOffset;
1309             strut.right_start = y() / devicePixelRatio;
1310             strut.right_end = y() / devicePixelRatio + height() - 1;
1311             //                 qDebug() << "setting right edge to" << strut.right_width << strut.right_start << strut.right_end;
1312             break;
1313         }
1314 
1315         case Plasma::Types::LeftEdge: {
1316             const int leftOffset = thisScreen.x();
1317             strut.left_width = totalThickness() + leftOffset;
1318             strut.left_start = y() / devicePixelRatio;
1319             strut.left_end = y() / devicePixelRatio + height() - 1;
1320             //                 qDebug() << "setting left edge to" << strut.left_width << strut.left_start << strut.left_end;
1321             break;
1322         }
1323 
1324         default:
1325             // qDebug() << "where are we?";
1326             break;
1327         }
1328     }
1329 
1330     KX11Extras::setExtendedStrut(winId(),
1331                                  strut.left_width,
1332                                  strut.left_start,
1333                                  strut.left_end,
1334                                  strut.right_width,
1335                                  strut.right_start,
1336                                  strut.right_end,
1337                                  strut.top_width,
1338                                  strut.top_start,
1339                                  strut.top_end,
1340                                  strut.bottom_width,
1341                                  strut.bottom_start,
1342                                  strut.bottom_end);
1343 #endif
1344 }
1345 
1346 void PanelView::refreshContainment()
1347 {
1348     restore();
1349     connect(containment(), &Plasma::Containment::userConfiguringChanged, this, [this](bool configuring) {
1350         if (configuring) {
1351             showTemporarily();
1352         } else {
1353             m_unhideTimer.start();
1354             updateStruts();
1355         }
1356     });
1357 
1358     connect(containment(), &Plasma::Applet::statusChanged, this, &PanelView::refreshStatus);
1359     connect(containment(), &Plasma::Applet::appletDeleted, this, [this] {
1360         // containment()->destroyed() is true only when the user deleted it
1361         // so the config is to be thrown away, not during shutdown
1362         if (containment()->destroyed()) {
1363             KConfigGroup views(m_corona->applicationConfig(), "PlasmaViews");
1364             for (auto grp : views.groupList()) {
1365                 if (grp.contains(QRegularExpression(QStringLiteral("Panel %1$").arg(QString::number(containment()->id()))))) {
1366                     qDebug() << "Panel" << containment()->id() << "removed by user";
1367                     views.deleteGroup(grp);
1368                 }
1369                 views.sync();
1370             }
1371         }
1372     });
1373 }
1374 
1375 void PanelView::handleQmlStatusChange(QQmlComponent::Status status)
1376 {
1377     if (status != QQmlComponent::Ready) {
1378         return;
1379     }
1380 
1381     QQuickItem *rootObject = this->rootObject();
1382     if (rootObject) {
1383         disconnect(this, &QuickViewSharedEngine::statusChanged, this, &PanelView::handleQmlStatusChange);
1384 
1385         updatePadding();
1386         updateFloating();
1387         int paddingSignal = rootObject->metaObject()->indexOfSignal("bottomPaddingChanged()");
1388         if (paddingSignal >= 0) {
1389             connect(rootObject, SIGNAL(bottomPaddingChanged()), this, SLOT(updatePadding()));
1390             connect(rootObject, SIGNAL(topPaddingChanged()), this, SLOT(updatePadding()));
1391             connect(rootObject, SIGNAL(rightPaddingChanged()), this, SLOT(updatePadding()));
1392             connect(rootObject, SIGNAL(leftPaddingChanged()), this, SLOT(updatePadding()));
1393             connect(rootObject, SIGNAL(minPanelHeightChanged()), this, SLOT(updatePadding()));
1394             connect(rootObject, SIGNAL(minPanelWidthChanged()), this, SLOT(updatePadding()));
1395         }
1396         const int floatingSignal = rootObject->metaObject()->indexOfSignal("bottomFloatingPaddingChanged()");
1397         if (floatingSignal >= 0) {
1398             connect(rootObject, SIGNAL(bottomFloatingPaddingChanged()), this, SLOT(updateFloating()));
1399             connect(rootObject, SIGNAL(topFloatingPaddingChanged()), this, SLOT(updateFloating()));
1400             connect(rootObject, SIGNAL(rightFloatingPaddingChanged()), this, SLOT(updateFloating()));
1401             connect(rootObject, SIGNAL(leftFloatingPaddingChanged()), this, SLOT(updateFloating()));
1402             connect(rootObject, SIGNAL(hasShadowsChanged()), this, SLOT(updateShadows()));
1403             connect(rootObject, SIGNAL(maskOffsetXChanged()), this, SLOT(updateMask()));
1404             connect(rootObject, SIGNAL(maskOffsetYChanged()), this, SLOT(updateMask()));
1405         }
1406 
1407         const QVariant maskProperty = rootObject->property("panelMask");
1408         if (static_cast<QMetaType::Type>(maskProperty.type()) == QMetaType::QRegion) {
1409             connect(rootObject, SIGNAL(panelMaskChanged()), this, SLOT(updateMask()));
1410             updateMask();
1411         }
1412     }
1413 }
1414 
1415 void PanelView::refreshStatus(Plasma::Types::ItemStatus status)
1416 {
1417     if (status == Plasma::Types::NeedsAttentionStatus) {
1418         showTemporarily();
1419         setFlags(flags() | Qt::WindowDoesNotAcceptFocus);
1420         if (m_shellSurface) {
1421             m_shellSurface->setPanelTakesFocus(false);
1422         }
1423     } else if (status == Plasma::Types::AcceptingInputStatus) {
1424         setFlags(flags() & ~Qt::WindowDoesNotAcceptFocus);
1425 #ifdef HAVE_X11
1426         KX11Extras::forceActiveWindow(winId());
1427 #endif
1428         if (m_shellSurface) {
1429             m_shellSurface->setPanelTakesFocus(true);
1430         }
1431 
1432         m_corona->savePreviousWindow();
1433 
1434         const auto nextItem = rootObject()->nextItemInFocusChain();
1435         if (nextItem) {
1436             nextItem->forceActiveFocus(Qt::TabFocusReason);
1437         } else {
1438             containment()->setStatus(Plasma::Types::PassiveStatus);
1439         }
1440     } else {
1441         if (status == Plasma::Types::PassiveStatus) {
1442             m_corona->restorePreviousWindow();
1443         } else if (status == Plasma::Types::ActiveStatus) {
1444             m_corona->clearPreviousWindow();
1445         }
1446 
1447         restoreAutoHide();
1448         setFlags(flags() | Qt::WindowDoesNotAcceptFocus);
1449         if (m_shellSurface) {
1450             m_shellSurface->setPanelTakesFocus(false);
1451         }
1452     }
1453 }
1454 
1455 void PanelView::showTemporarily()
1456 {
1457     setAutoHideEnabled(false);
1458 
1459     QTimer *t = new QTimer(this);
1460     t->setSingleShot(true);
1461     t->setInterval(3s);
1462     connect(t, &QTimer::timeout, this, &PanelView::restoreAutoHide);
1463     connect(t, &QTimer::timeout, t, &QObject::deleteLater);
1464     t->start();
1465 }
1466 
1467 void PanelView::screenDestroyed(QObject *)
1468 {
1469     //     NOTE: this is overriding the screen destroyed slot, we need to do this because
1470     //     otherwise Qt goes mental and starts moving our panels. See:
1471     //     https://codereview.qt-project.org/#/c/88351/
1472     //     if(screen == this->m_screenToFollow) {
1473     //         DO NOTHING, panels are moved by ::readaptToScreen
1474     //     }
1475 }
1476 
1477 void PanelView::setupWaylandIntegration()
1478 {
1479     if (m_shellSurface) {
1480         // already setup
1481         return;
1482     }
1483     if (ShellCorona *c = qobject_cast<ShellCorona *>(corona())) {
1484         using namespace KWayland::Client;
1485         PlasmaShell *interface = c->waylandPlasmaShellInterface();
1486         if (!interface) {
1487             return;
1488         }
1489         Surface *s = Surface::fromWindow(this);
1490         if (!s) {
1491             return;
1492         }
1493         m_shellSurface = interface->createSurface(s, this);
1494     }
1495 }
1496 
1497 bool PanelView::edgeActivated() const
1498 {
1499     return m_visibilityMode == PanelView::AutoHide || m_visibilityMode == LetWindowsCover;
1500 }
1501 
1502 void PanelView::updateEnabledBorders()
1503 {
1504     Plasma::FrameSvg::EnabledBorders borders = Plasma::FrameSvg::AllBorders;
1505     if (m_backgroundHints == Plasma::Types::NoBackground) {
1506         borders = Plasma::FrameSvg::NoBorder;
1507     } else {
1508         switch (location()) {
1509         case Plasma::Types::TopEdge:
1510             borders &= ~Plasma::FrameSvg::TopBorder;
1511             break;
1512         case Plasma::Types::LeftEdge:
1513             borders &= ~Plasma::FrameSvg::LeftBorder;
1514             break;
1515         case Plasma::Types::RightEdge:
1516             borders &= ~Plasma::FrameSvg::RightBorder;
1517             break;
1518         case Plasma::Types::BottomEdge:
1519             borders &= ~Plasma::FrameSvg::BottomBorder;
1520             break;
1521         default:
1522             break;
1523         }
1524 
1525         if (m_screenToFollow) {
1526             if (x() <= m_screenToFollow->geometry().x()) {
1527                 borders &= ~Plasma::FrameSvg::LeftBorder;
1528             }
1529             if (x() + width() >= m_screenToFollow->geometry().x() + m_screenToFollow->geometry().width()) {
1530                 borders &= ~Plasma::FrameSvg::RightBorder;
1531             }
1532             if (y() <= m_screenToFollow->geometry().y()) {
1533                 borders &= ~Plasma::FrameSvg::TopBorder;
1534             }
1535             if (y() + height() >= m_screenToFollow->geometry().y() + m_screenToFollow->geometry().height()) {
1536                 borders &= ~Plasma::FrameSvg::BottomBorder;
1537             }
1538         }
1539     }
1540 
1541     if (m_enabledBorders != borders) {
1542         PanelShadows::self()->setEnabledBorders(this, borders);
1543         m_enabledBorders = borders;
1544         Q_EMIT enabledBordersChanged();
1545     }
1546 }
1547 
1548 void PanelView::updatePadding()
1549 {
1550     if (!rootObject())
1551         return;
1552     m_leftPadding = rootObject()->property("leftPadding").toInt();
1553     m_rightPadding = rootObject()->property("rightPadding").toInt();
1554     m_topPadding = rootObject()->property("topPadding").toInt();
1555     m_bottomPadding = rootObject()->property("bottomPadding").toInt();
1556     m_minDrawingHeight = rootObject()->property("minPanelHeight").toInt();
1557     m_minDrawingWidth = rootObject()->property("minPanelWidth").toInt();
1558     Q_EMIT minThicknessChanged();
1559     setThickness(m_thickness);
1560 }
1561 
1562 void PanelView::updateShadows()
1563 {
1564     if (!rootObject()) {
1565         return;
1566     }
1567     bool hasShadows = rootObject()->property("hasShadows").toBool();
1568     if (hasShadows) {
1569         PanelShadows::self()->addWindow(this, enabledBorders());
1570     } else {
1571         PanelShadows::self()->removeWindow(this);
1572     }
1573 }
1574 
1575 void PanelView::updateFloating()
1576 {
1577     if (!rootObject()) {
1578         return;
1579     }
1580     m_leftFloatingPadding = rootObject()->property("leftFloatingPadding").toInt();
1581     m_rightFloatingPadding = rootObject()->property("rightFloatingPadding").toInt();
1582     m_topFloatingPadding = rootObject()->property("topFloatingPadding").toInt();
1583     m_bottomFloatingPadding = rootObject()->property("bottomFloatingPadding").toInt();
1584 
1585     positionPanel();
1586     resizePanel();
1587     updateMask();
1588 }
1589 
1590 #include "moc_panelview.cpp"