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"