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