From 32cb146d655fab21ff79ff868ad55ba4f0d12e79 Mon Sep 17 00:00:00 2001 From: Filippo Gentile Date: Tue, 9 Apr 2024 17:53:42 +0200 Subject: [PATCH] Wayland Layer Shell (#2048) * LXQtPanel: use LayerShell on Wayland layer_shell protocol allows to ask for placement on Wayland * LXQtPanel: fix position not applied immediatly on Wayland * LXQtPanel: partially fix alignment on Wayland TODO TODO: after changing length to pixels and back to percent alignment is ignored and always kept to right * LXQtPanel: fix auto-hide on Wayland * LXQtPanel: set LayerShellQt KeyboardInteractivityOnDemand Set layer shell keyboard interactivity on-demand * LXQtPanel: ensure QWindow is created and null-check layer shell window * LXQtPanel: silence warning on variable initialization * LXQtPanel: fix again missing variable initialization --- CMakeLists.txt | 2 + panel/CMakeLists.txt | 1 + panel/lxqtpanel.cpp | 243 ++++++++++++++++++++++++++++++++++--------- panel/lxqtpanel.h | 6 ++ 4 files changed, 205 insertions(+), 47 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 25350186d..3cd60ede7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,8 @@ find_package(lxqt ${LXQT_MINIMUM_VERSION} REQUIRED) find_package(lxqt-globalkeys-ui ${LXQT_GLOBALKEYS_MINIMUM_VERSION} REQUIRED) find_package(lxqt-menu-data ${LXQT_MINIMUM_VERSION} REQUIRED) +find_package(LayerShellQt REQUIRED) + # Patch Version set(LXQT_PANEL_PATCH_VERSION 0) set(LXQT_PANEL_VERSION ${LXQT_MAJOR_VERSION}.${LXQT_MINOR_VERSION}.${LXQT_PANEL_PATCH_VERSION}) diff --git a/panel/CMakeLists.txt b/panel/CMakeLists.txt index 916fced2c..556d2212f 100644 --- a/panel/CMakeLists.txt +++ b/panel/CMakeLists.txt @@ -103,6 +103,7 @@ target_link_libraries(${PROJECT} ${LIBRARIES} ${QTX_LIBRARIES} KF6::WindowSystem + LayerShellQt::Interface ${STATIC_PLUGINS} ) diff --git a/panel/lxqtpanel.cpp b/panel/lxqtpanel.cpp index 86ba83141..a1fb68f35 100644 --- a/panel/lxqtpanel.cpp +++ b/panel/lxqtpanel.cpp @@ -53,6 +53,8 @@ #include #include +#include + // Turn on this to show the time required to load each plugin during startup // #define DEBUG_PLUGIN_LOADTIME #ifdef DEBUG_PLUGIN_LOADTIME @@ -141,6 +143,7 @@ LXQtPanel::LXQtPanel(const QString &configGroup, LXQt::Settings *settings, QWidg mAnimationTime(0), mReserveSpace(true), mAnimation(nullptr), + mLayerWindow(nullptr), mLockPanel(false) { //You can find information about the flags and widget attributes in your @@ -230,6 +233,37 @@ LXQtPanel::LXQtPanel(const QString &configGroup, LXQt::Settings *settings, QWidg loadPlugins(); + if(qGuiApp->nativeInterface()) + { + // Create backing QWindow for LayerShellQt integration + create(); + + if(!windowHandle()) + { + qWarning() << "LXQtPanel: could not create QWindow for LayerShellQt integration."; + } + else + { + // Init Layer Shell (Must be done before showing widget) + mLayerWindow = LayerShellQt::Window::get(windowHandle()); + mLayerWindow->setLayer(LayerShellQt::Window::LayerTop); + + mLayerWindow->setScope(QStringLiteral("dock")); + + LayerShellQt::Window::Anchors anchors; + anchors.setFlag(LayerShellQt::Window::AnchorLeft); + anchors.setFlag(LayerShellQt::Window::AnchorBottom); + anchors.setFlag(LayerShellQt::Window::AnchorRight); + mLayerWindow->setAnchors(anchors); + + mLayerWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityOnDemand); + mLayerWindow->setCloseOnDismissed(false); + + mLayerWindow->setExclusiveEdge(LayerShellQt::Window::AnchorBottom); + mLayerWindow->setExclusiveZone(height()); + } + } + // NOTE: Some (X11) WMs may need the geometry to be set before QWidget::show(). setPanelGeometry(); @@ -479,6 +513,8 @@ void LXQtPanel::setPanelGeometry(bool animate) const QRect currentScreen = screens.at(mActualScreenNum)->geometry(); QRect rect; + LayerShellQt::Window::Anchors anchors; + LayerShellQt::Window::Anchor edge = LayerShellQt::Window::AnchorBottom; if (isHorizontal()) { @@ -500,6 +536,7 @@ void LXQtPanel::setPanelGeometry(bool animate) switch (mAlignment) { case LXQtPanel::AlignmentLeft: + anchors.setFlag(LayerShellQt::Window::AnchorLeft); rect.moveLeft(currentScreen.left()); break; @@ -508,13 +545,24 @@ void LXQtPanel::setPanelGeometry(bool animate) break; case LXQtPanel::AlignmentRight: + anchors.setFlag(LayerShellQt::Window::AnchorRight); rect.moveRight(currentScreen.right()); break; } + if(lengthInPercents() && mLength == 100) + { + //Fill all available width + anchors.setFlag(LayerShellQt::Window::AnchorLeft); + anchors.setFlag(LayerShellQt::Window::AnchorRight); + } + // Vert ....................... if (mPosition == ILXQtPanel::PositionTop) { + anchors.setFlag(LayerShellQt::Window::AnchorTop); + edge = LayerShellQt::Window::AnchorTop; + if (mHidden) rect.moveBottom(currentScreen.top() + PANEL_HIDE_SIZE - 1); else @@ -522,6 +570,9 @@ void LXQtPanel::setPanelGeometry(bool animate) } else { + anchors.setFlag(LayerShellQt::Window::AnchorBottom); + edge = LayerShellQt::Window::AnchorBottom; + if (mHidden) rect.moveTop(currentScreen.bottom() - PANEL_HIDE_SIZE + 1); else @@ -548,6 +599,7 @@ void LXQtPanel::setPanelGeometry(bool animate) switch (mAlignment) { case LXQtPanel::AlignmentLeft: + anchors.setFlag(LayerShellQt::Window::AnchorTop); rect.moveTop(currentScreen.top()); break; @@ -556,13 +608,24 @@ void LXQtPanel::setPanelGeometry(bool animate) break; case LXQtPanel::AlignmentRight: + anchors.setFlag(LayerShellQt::Window::AnchorBottom); rect.moveBottom(currentScreen.bottom()); break; } + if(lengthInPercents() && mLength == 100) + { + //Fill all available width + anchors.setFlag(LayerShellQt::Window::AnchorTop); + anchors.setFlag(LayerShellQt::Window::AnchorBottom); + } + // Horiz ...................... if (mPosition == ILXQtPanel::PositionLeft) { + anchors.setFlag(LayerShellQt::Window::AnchorLeft); + edge = LayerShellQt::Window::AnchorLeft; + if (mHidden) rect.moveRight(currentScreen.left() + PANEL_HIDE_SIZE - 1); else @@ -570,12 +633,25 @@ void LXQtPanel::setPanelGeometry(bool animate) } else { + anchors.setFlag(LayerShellQt::Window::AnchorRight); + edge = LayerShellQt::Window::AnchorRight; + if (mHidden) rect.moveLeft(currentScreen.right() - PANEL_HIDE_SIZE + 1); else rect.moveRight(currentScreen.right()); } } + + if(mLayerWindow) + { + mLayerWindow->setAnchors(anchors); + mLayerWindow->setExclusiveEdge(edge); + + // Make LayerShell apply changes immediatly + windowHandle()->requestUpdate(); + } + if (!mHidden || !mGeometry.isValid()) mGeometry = rect; if (rect != geometry()) { @@ -603,6 +679,37 @@ void LXQtPanel::setPanelGeometry(bool animate) setGeometry(rect); } } + + if(mLayerWindow) + { + // Emulate auto-hide on Wayland + // NOTE: we cannot move window out of screen so we make it smaller + + // NOTE: a cleaner approach would be to use screen edge protocol + // but it's specific to KWin + + if(mHidden && LXQtPanelWidget->isVisible()) + { + // Make it blank + LXQtPanelWidget->hide(); + + // And make it small + if(isHorizontal()) + resize(rect.width(), PANEL_HIDE_SIZE); + else + resize(PANEL_HIDE_SIZE, rect.height()); + } + else if(!mHidden && !LXQtPanelWidget->isVisible()) + { + // Restore contents + LXQtPanelWidget->show(); + + // And make it big again + resize(rect.size()); + } + + updateWmStrut(); + } } void LXQtPanel::setMargins() @@ -664,62 +771,104 @@ void LXQtPanel::updateWmStrut() if(wid == 0 || !isVisible()) return; - if (mReserveSpace && QApplication::primaryScreen()) + if(qGuiApp->nativeInterface()) { - const QRect wholeScreen = QApplication::primaryScreen()->virtualGeometry(); - const QRect rect = geometry(); - // NOTE: https://standards.freedesktop.org/wm-spec/wm-spec-latest.html - // Quote from the EWMH spec: " Note that the strut is relative to the screen edge, and not the edge of the xinerama monitor." - // So, we use the geometry of the whole screen to calculate the strut rather than using the geometry of individual monitors. - // Though the spec only mention Xinerama and did not mention XRandR, the rule should still be applied. - // At least openbox is implemented like this. - switch (mPosition) + if (mReserveSpace && QApplication::primaryScreen()) { - case LXQtPanel::PositionTop: - KX11Extras::setExtendedStrut(wid, - /* Left */ 0, 0, 0, - /* Right */ 0, 0, 0, - /* Top */ rect.top() + getReserveDimension(), rect.left(), rect.right(), - /* Bottom */ 0, 0, 0 - ); - break; + const QRect wholeScreen = QApplication::primaryScreen()->virtualGeometry(); + const QRect rect = geometry(); + // NOTE: https://standards.freedesktop.org/wm-spec/wm-spec-latest.html + // Quote from the EWMH spec: " Note that the strut is relative to the screen edge, and not the edge of the xinerama monitor." + // So, we use the geometry of the whole screen to calculate the strut rather than using the geometry of individual monitors. + // Though the spec only mention Xinerama and did not mention XRandR, the rule should still be applied. + // At least openbox is implemented like this. + switch (mPosition) + { + case LXQtPanel::PositionTop: + KX11Extras::setExtendedStrut(wid, + /* Left */ 0, 0, 0, + /* Right */ 0, 0, 0, + /* Top */ rect.top() + getReserveDimension(), rect.left(), rect.right(), + /* Bottom */ 0, 0, 0 + ); + break; - case LXQtPanel::PositionBottom: - KX11Extras::setExtendedStrut(wid, - /* Left */ 0, 0, 0, - /* Right */ 0, 0, 0, - /* Top */ 0, 0, 0, - /* Bottom */ wholeScreen.bottom() - rect.bottom() + getReserveDimension(), rect.left(), rect.right() - ); - break; + case LXQtPanel::PositionBottom: + KX11Extras::setExtendedStrut(wid, + /* Left */ 0, 0, 0, + /* Right */ 0, 0, 0, + /* Top */ 0, 0, 0, + /* Bottom */ wholeScreen.bottom() - rect.bottom() + getReserveDimension(), rect.left(), rect.right() + ); + break; - case LXQtPanel::PositionLeft: - KX11Extras::setExtendedStrut(wid, - /* Left */ rect.left() + getReserveDimension(), rect.top(), rect.bottom(), - /* Right */ 0, 0, 0, - /* Top */ 0, 0, 0, - /* Bottom */ 0, 0, 0 - ); + case LXQtPanel::PositionLeft: + KX11Extras::setExtendedStrut(wid, + /* Left */ rect.left() + getReserveDimension(), rect.top(), rect.bottom(), + /* Right */ 0, 0, 0, + /* Top */ 0, 0, 0, + /* Bottom */ 0, 0, 0 + ); - break; + break; - case LXQtPanel::PositionRight: + case LXQtPanel::PositionRight: + KX11Extras::setExtendedStrut(wid, + /* Left */ 0, 0, 0, + /* Right */ wholeScreen.right() - rect.right() + getReserveDimension(), rect.top(), rect.bottom(), + /* Top */ 0, 0, 0, + /* Bottom */ 0, 0, 0 + ); + break; + } + } else + { KX11Extras::setExtendedStrut(wid, - /* Left */ 0, 0, 0, - /* Right */ wholeScreen.right() - rect.right() + getReserveDimension(), rect.top(), rect.bottom(), - /* Top */ 0, 0, 0, - /* Bottom */ 0, 0, 0 - ); - break; + /* Left */ 0, 0, 0, + /* Right */ 0, 0, 0, + /* Top */ 0, 0, 0, + /* Bottom */ 0, 0, 0 + ); + } } - } else + else if(mLayerWindow && qGuiApp->nativeInterface()) { - KX11Extras::setExtendedStrut(wid, - /* Left */ 0, 0, 0, - /* Right */ 0, 0, 0, - /* Top */ 0, 0, 0, - /* Bottom */ 0, 0, 0 - ); + //TODO: duplicated code, also set in setPanelGeometry() + + if (mReserveSpace) + { + LayerShellQt::Window::Anchor edge = LayerShellQt::Window::AnchorBottom; + + switch (mPosition) + { + case LXQtPanel::PositionTop: + edge = LayerShellQt::Window::AnchorTop; + break; + + case LXQtPanel::PositionBottom: + edge = LayerShellQt::Window::AnchorBottom; + break; + + case LXQtPanel::PositionLeft: + edge = LayerShellQt::Window::AnchorLeft; + break; + + case LXQtPanel::PositionRight: + edge = LayerShellQt::Window::AnchorRight; + break; + } + + mLayerWindow->setExclusiveEdge(edge); + mLayerWindow->setExclusiveZone(getReserveDimension()); + } + else + { + mLayerWindow->setExclusiveEdge(LayerShellQt::Window::AnchorNone); + mLayerWindow->setExclusiveZone(0); + } + + // Make LayerShellQt apply changes immediatly + windowHandle()->requestUpdate(); } } diff --git a/panel/lxqtpanel.h b/panel/lxqtpanel.h index 7d847dfa6..c1930c1bf 100644 --- a/panel/lxqtpanel.h +++ b/panel/lxqtpanel.h @@ -42,6 +42,10 @@ class QMenu; class Plugin; class QAbstractItemModel; +namespace LayerShellQt { +class Window; +} + namespace LXQt { class Settings; class PluginInfo; @@ -688,6 +692,8 @@ private slots: */ QPropertyAnimation *mAnimation; + LayerShellQt::Window *mLayerWindow; + /** * @brief Flag for providing the configuration options in panel's context menu */