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