File indexing completed on 2024-05-05 09:56:38

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