diff --git a/.clang-format b/.clang-format index eb81a3b..610ee65 100644 --- a/.clang-format +++ b/.clang-format @@ -31,12 +31,12 @@ SpaceBeforeInheritanceColon: false SpaceBeforeParens: ControlStatementsExceptControlMacros SortIncludes: CaseSensitive PointerAlignment: Left -PackConstructorInitializers: NextLine +PackConstructorInitializers: CurrentLine LineEnding: LF InsertBraces: false -BreakConstructorInitializers: AfterColon +BreakConstructorInitializers: BeforeComma BreakBeforeBraces: Custom -BreakInheritanceList: AfterColon +BreakInheritanceList: BeforeComma AllowAllParametersOfDeclarationOnNextLine: false AllowAllArgumentsOnNextLine: false AlwaysBreakTemplateDeclarations: Yes diff --git a/CMakeLists.txt b/CMakeLists.txt index 29b4413..4902a83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,6 @@ set(QT_MIN_VERSION "6.6.0") set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -option(LAYERSHELL "Enable wayland layershell support" ON) - option(WAYLAND "Enable wayland support" ON) add_compile_options(-Wall -Wextra) diff --git a/Justfile b/Justfile index c4ffa6e..687f266 100644 --- a/Justfile +++ b/Justfile @@ -30,4 +30,4 @@ clean: rm -rf {{builddir}} run *ARGS='': build - {{builddir}}/quickshell {{ARGS}} + {{builddir}}/src/core/quickshell {{ARGS}} diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 54b9fe2..d19c796 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -16,13 +16,4 @@ qt_add_executable(quickshell qt_add_qml_module(quickshell URI QuickShell) -target_link_libraries(quickshell PRIVATE ${QT_DEPS} quickshell-wayland) - -if (LAYERSHELL) - find_package(LayerShellQt REQUIRED) - - target_link_libraries(quickshell PRIVATE LayerShellQtInterface) - target_compile_definitions(quickshell PRIVATE CONF_LAYERSHELL) - - target_sources(quickshell PRIVATE layershell.cpp) -endif() +target_link_libraries(quickshell PRIVATE ${QT_DEPS} quickshell-waylandplugin) diff --git a/src/core/layershell.cpp b/src/core/layershell.cpp deleted file mode 100644 index 0503906..0000000 --- a/src/core/layershell.cpp +++ /dev/null @@ -1,300 +0,0 @@ -#include "layershell.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "proxywindow.hpp" -#include "shellwindow.hpp" - -WaylandShellWindow::WaylandShellWindow(QObject* parent): - ProxyShellWindow(parent), mWayland(new WaylandShellWindowExtensions(this)) {} - -void WaylandShellWindow::setupWindow() { - this->shellWindow = LayerShellQt::Window::get(this->window); - - this->ProxyShellWindow::setupWindow(); - - // clang-format off - QObject::connect(this->shellWindow, &LayerShellQt::Window::anchorsChanged, this, &ProxyShellWindow::anchorsChanged); - QObject::connect(this->shellWindow, &LayerShellQt::Window::marginsChanged, this, &ProxyShellWindow::marginsChanged); - - QObject::connect( - this->shellWindow, &LayerShellQt::Window::layerChanged, - this->mWayland, &WaylandShellWindowExtensions::layerChanged - ); - QObject::connect( - this->shellWindow, &LayerShellQt::Window::keyboardInteractivityChanged, - this->mWayland, &WaylandShellWindowExtensions::keyboardFocusChanged - ); - - QObject::connect(this->window, &QWindow::widthChanged, this, &WaylandShellWindow::updateExclusionZone); - QObject::connect(this->window, &QWindow::heightChanged, this, &WaylandShellWindow::updateExclusionZone); - QObject::connect(this, &ProxyShellWindow::anchorsChanged, this, &WaylandShellWindow::updateExclusionZone); - QObject::connect(this, &ProxyShellWindow::marginsChanged, this, &WaylandShellWindow::updateExclusionZone); - // clang-format on - - this->setAnchors(this->mAnchors); - this->setMargins(this->mMargins); - this->setExclusionMode(this->mExclusionMode); // also sets exclusion zone - this->mWayland->setLayer(this->mLayer); - this->shellWindow->setScope(this->mScope); - this->mWayland->setKeyboardFocus(this->mKeyboardFocus); - - this->connected = true; -} - -QQuickWindow* WaylandShellWindow::disownWindow() { - QObject::disconnect(this->shellWindow, nullptr, this, nullptr); - return this->ProxyWindowBase::disownWindow(); -} - -void WaylandShellWindow::setWidth(qint32 width) { - this->mWidth = width; - - // only update the actual size if not blocked by anchors - auto anchors = this->anchors(); - if (!anchors.mLeft || !anchors.mRight) this->ProxyShellWindow::setWidth(width); -} - -void WaylandShellWindow::setHeight(qint32 height) { - this->mHeight = height; - - // only update the actual size if not blocked by anchors - auto anchors = this->anchors(); - if (!anchors.mTop || !anchors.mBottom) this->ProxyShellWindow::setHeight(height); -} - -void WaylandShellWindow::setAnchors(Anchors anchors) { - if (this->window == nullptr) { - this->mAnchors = anchors; - return; - } - - auto lsAnchors = LayerShellQt::Window::Anchors(); - if (anchors.mLeft) lsAnchors |= LayerShellQt::Window::AnchorLeft; - if (anchors.mRight) lsAnchors |= LayerShellQt::Window::AnchorRight; - if (anchors.mTop) lsAnchors |= LayerShellQt::Window::AnchorTop; - if (anchors.mBottom) lsAnchors |= LayerShellQt::Window::AnchorBottom; - - if (!anchors.mLeft || !anchors.mRight) this->ProxyWindowBase::setWidth(this->mWidth); - if (!anchors.mTop || !anchors.mBottom) this->ProxyWindowBase::setHeight(this->mHeight); - - this->shellWindow->setAnchors(lsAnchors); -} - -Anchors WaylandShellWindow::anchors() const { - if (this->window == nullptr) return this->mAnchors; - - auto lsAnchors = this->shellWindow->anchors(); - - Anchors anchors; - anchors.mLeft = lsAnchors.testFlag(LayerShellQt::Window::AnchorLeft); - anchors.mRight = lsAnchors.testFlag(LayerShellQt::Window::AnchorRight); - anchors.mTop = lsAnchors.testFlag(LayerShellQt::Window::AnchorTop); - anchors.mBottom = lsAnchors.testFlag(LayerShellQt::Window::AnchorBottom); - - return anchors; -} - -void WaylandShellWindow::setExclusiveZone(qint32 zone) { - if (zone < 0) zone = 0; - if (this->connected && zone == this->mExclusionZone) return; - this->mExclusionZone = zone; - - if (this->window != nullptr && this->exclusionMode() == ExclusionMode::Normal) { - this->shellWindow->setExclusiveZone(zone); - emit this->exclusionZoneChanged(); - } -} - -qint32 WaylandShellWindow::exclusiveZone() const { - if (this->window == nullptr) return this->mExclusionZone; - else return this->shellWindow->exclusionZone(); -} - -ExclusionMode::Enum WaylandShellWindow::exclusionMode() const { return this->mExclusionMode; } - -void WaylandShellWindow::setExclusionMode(ExclusionMode::Enum exclusionMode) { - if (this->connected && exclusionMode == this->mExclusionMode) return; - this->mExclusionMode = exclusionMode; - - if (this->window != nullptr) { - if (exclusionMode == ExclusionMode::Normal) { - this->shellWindow->setExclusiveZone(this->mExclusionZone); - emit this->exclusionZoneChanged(); - } else if (exclusionMode == ExclusionMode::Ignore) { - this->shellWindow->setExclusiveZone(-1); - emit this->exclusionZoneChanged(); - } else { - this->updateExclusionZone(); - } - } -} - -void WaylandShellWindow::setMargins(Margins margins) { - if (this->window == nullptr) this->mMargins = margins; - else { - auto lsMargins = QMargins(margins.mLeft, margins.mTop, margins.mRight, margins.mBottom); - this->shellWindow->setMargins(lsMargins); - } -} - -Margins WaylandShellWindow::margins() const { - if (this->window == nullptr) return this->mMargins; - auto lsMargins = this->shellWindow->margins(); - - auto margins = Margins(); - margins.mLeft = lsMargins.left(); - margins.mRight = lsMargins.right(); - margins.mTop = lsMargins.top(); - margins.mBottom = lsMargins.bottom(); - - return margins; -} - -void WaylandShellWindowExtensions::setLayer(Layer::Enum layer) { - if (this->window->window == nullptr) { - this->window->mLayer = layer; - return; - } - - auto lsLayer = LayerShellQt::Window::LayerBackground; - - // clang-format off - switch (layer) { - case Layer::Background: lsLayer = LayerShellQt::Window::LayerBackground; break; - case Layer::Bottom: lsLayer = LayerShellQt::Window::LayerBottom; break; - case Layer::Top: lsLayer = LayerShellQt::Window::LayerTop; break; - case Layer::Overlay: lsLayer = LayerShellQt::Window::LayerOverlay; break; - } - // clang-format on - - this->window->shellWindow->setLayer(lsLayer); -} - -Layer::Enum WaylandShellWindowExtensions::layer() const { - if (this->window->window == nullptr) return this->window->mLayer; - - auto layer = Layer::Top; - auto lsLayer = this->window->shellWindow->layer(); - - // clang-format off - switch (lsLayer) { - case LayerShellQt::Window::LayerBackground: layer = Layer::Background; break; - case LayerShellQt::Window::LayerBottom: layer = Layer::Bottom; break; - case LayerShellQt::Window::LayerTop: layer = Layer::Top; break; - case LayerShellQt::Window::LayerOverlay: layer = Layer::Overlay; break; - } - // clang-format on - - return layer; -} - -void WaylandShellWindowExtensions::setScope(const QString& scope) { - if (this->window->window == nullptr) this->window->mScope = scope; - else this->window->shellWindow->setScope(scope); -} - -QString WaylandShellWindowExtensions::scope() const { - if (this->window->window == nullptr) return this->window->mScope; - else return this->window->shellWindow->scope(); -} - -void WaylandShellWindowExtensions::setKeyboardFocus(KeyboardFocus::Enum focus) { - if (this->window->window == nullptr) { - this->window->mKeyboardFocus = focus; - return; - } - - auto lsFocus = LayerShellQt::Window::KeyboardInteractivityNone; - - // clang-format off - switch (focus) { - case KeyboardFocus::None: lsFocus = LayerShellQt::Window::KeyboardInteractivityNone; break; - case KeyboardFocus::Exclusive: lsFocus = LayerShellQt::Window::KeyboardInteractivityExclusive; break; - case KeyboardFocus::OnDemand: lsFocus = LayerShellQt::Window::KeyboardInteractivityOnDemand; break; - } - // clang-format on - - this->window->shellWindow->setKeyboardInteractivity(lsFocus); -} - -KeyboardFocus::Enum WaylandShellWindowExtensions::keyboardFocus() const { - if (this->window->window == nullptr) return this->window->mKeyboardFocus; - - auto focus = KeyboardFocus::None; - auto lsFocus = this->window->shellWindow->keyboardInteractivity(); - - // clang-format off - switch (lsFocus) { - case LayerShellQt::Window::KeyboardInteractivityNone: focus = KeyboardFocus::None; break; - case LayerShellQt::Window::KeyboardInteractivityExclusive: focus = KeyboardFocus::Exclusive; break; - case LayerShellQt::Window::KeyboardInteractivityOnDemand: focus = KeyboardFocus::OnDemand; break; - } - // clang-format on - - return focus; -} - -void WaylandShellWindowExtensions::setScreenConfiguration(ScreenConfiguration::Enum configuration) { - if (this->window->window == nullptr) { - this->window->mScreenConfiguration = configuration; - return; - } - - auto lsConfiguration = LayerShellQt::Window::ScreenFromQWindow; - - // clang-format off - switch (configuration) { - case ScreenConfiguration::Window: lsConfiguration = LayerShellQt::Window::ScreenFromQWindow; break; - case ScreenConfiguration::Compositor: lsConfiguration = LayerShellQt::Window::ScreenFromCompositor; break; - } - // clang-format on - - this->window->shellWindow->setScreenConfiguration(lsConfiguration); -} - -ScreenConfiguration::Enum WaylandShellWindowExtensions::screenConfiguration() const { - if (this->window->window == nullptr) return this->window->mScreenConfiguration; - - auto configuration = ScreenConfiguration::Window; - auto lsConfiguration = this->window->shellWindow->screenConfiguration(); - - // clang-format off - switch (lsConfiguration) { - case LayerShellQt::Window::ScreenFromQWindow: configuration = ScreenConfiguration::Window; break; - case LayerShellQt::Window::ScreenFromCompositor: configuration = ScreenConfiguration::Compositor; break; - } - // clang-format on - - return configuration; -} - -void WaylandShellWindow::updateExclusionZone() { - if (this->window != nullptr && this->exclusionMode() == ExclusionMode::Auto) { - auto anchors = this->anchors(); - - auto zone = -1; - - if (anchors.mTop && anchors.mBottom) { - if (anchors.mLeft) zone = this->width() + this->margins().mLeft; - else if (anchors.mRight) zone = this->width() + this->margins().mRight; - } else if (anchors.mLeft && anchors.mRight) { - if (anchors.mTop) zone = this->height() + this->margins().mTop; - else if (anchors.mBottom) zone = this->height() + this->margins().mBottom; - } - - if (zone != -1) { - this->shellWindow->setExclusiveZone(zone); - emit this->exclusionZoneChanged(); - } - } -} diff --git a/src/core/main.cpp b/src/core/main.cpp index edeeb86..0fa915b 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -10,10 +10,6 @@ #include "rootwrapper.hpp" -#ifdef CONF_LAYERSHELL -#include -#endif - int main(int argc, char** argv) { const auto app = QGuiApplication(argc, argv); QGuiApplication::setApplicationName("quickshell"); @@ -42,10 +38,6 @@ int main(int argc, char** argv) { return -1; } -#if CONF_LAYERSHELL - LayerShellQt::Shell::useLayerShell(); -#endif - // Base window transparency appears to be additive. // Use a fully transparent window with a colored rect. QQuickWindow::setDefaultAlphaBuffer(true); diff --git a/src/core/proxywindow.hpp b/src/core/proxywindow.hpp index e28311e..1bbe3a0 100644 --- a/src/core/proxywindow.hpp +++ b/src/core/proxywindow.hpp @@ -154,10 +154,10 @@ signals: void colorChanged(); void maskChanged(); -private slots: +protected slots: + virtual void onWidthChanged(); + virtual void onHeightChanged(); void onMaskChanged(); - void onWidthChanged(); - void onHeightChanged(); void onScreenDestroyed(); protected: diff --git a/src/core/qmlscreen.cpp b/src/core/qmlscreen.cpp index b72d4c4..29a4629 100644 --- a/src/core/qmlscreen.cpp +++ b/src/core/qmlscreen.cpp @@ -7,8 +7,9 @@ #include #include -QuickShellScreenInfo::QuickShellScreenInfo(QObject* parent, QScreen* screen): - QObject(parent), screen(screen) { +QuickShellScreenInfo::QuickShellScreenInfo(QObject* parent, QScreen* screen) + : QObject(parent) + , screen(screen) { if (this->screen != nullptr) { // clang-format off diff --git a/src/core/reload.hpp b/src/core/reload.hpp index 8533309..ff15fad 100644 --- a/src/core/reload.hpp +++ b/src/core/reload.hpp @@ -13,7 +13,9 @@ /// /// [ProxyWindowBase]: ../proxywindowbase /// [PersistentProperties]: ../persistentproperties -class Reloadable: public QObject, public QQmlParserStatus { +class Reloadable + : public QObject + , public QQmlParserStatus { Q_OBJECT; Q_INTERFACES(QQmlParserStatus); /// An additional identifier that can be used to try to match a reloadable object to its diff --git a/src/core/rootwrapper.cpp b/src/core/rootwrapper.cpp index 17dd549..a5050b4 100644 --- a/src/core/rootwrapper.cpp +++ b/src/core/rootwrapper.cpp @@ -12,8 +12,10 @@ #include "shell.hpp" #include "watcher.hpp" -RootWrapper::RootWrapper(QString rootPath): - QObject(nullptr), rootPath(std::move(rootPath)), engine(this) { +RootWrapper::RootWrapper(QString rootPath) + : QObject(nullptr) + , rootPath(std::move(rootPath)) + , engine(this) { this->reloadGraph(true); if (this->root == nullptr) { diff --git a/src/core/shellwindow.hpp b/src/core/shellwindow.hpp index 25a123a..e041a50 100644 --- a/src/core/shellwindow.hpp +++ b/src/core/shellwindow.hpp @@ -20,6 +20,18 @@ class Anchors { Q_PROPERTY(bool bottom MEMBER mBottom); public: + [[nodiscard]] bool horizontalConstraint() const noexcept { return this->mLeft && this->mRight; } + [[nodiscard]] bool verticalConstraint() const noexcept { return this->mTop && this->mBottom; } + + [[nodiscard]] bool operator==(const Anchors& other) const noexcept { + // clang-format off + return this->mLeft == other.mLeft + && this->mRight == other.mRight + && this->mTop == other.mTop + && this->mBottom == other.mBottom; + // clang-format on + } + bool mLeft = false; bool mRight = false; bool mTop = false; @@ -34,6 +46,15 @@ class Margins { Q_PROPERTY(qint32 bottom MEMBER mBottom); public: + [[nodiscard]] bool operator==(const Margins& other) const noexcept { + // clang-format off + return this->mLeft == other.mLeft + && this->mRight == other.mRight + && this->mTop == other.mTop + && this->mBottom == other.mBottom; + // clang-format on + } + qint32 mLeft = 0; qint32 mRight = 0; qint32 mTop = 0; diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index 89a34f1..477f135 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -1,4 +1,9 @@ -qt_add_library(quickshell-wayland STATIC) +qt_add_library(quickshell-wayland STATIC + shell_integration.cpp + layer_surface.cpp + layershell.cpp + waylandshellwindow.cpp +) qt_add_qml_module(quickshell-wayland URI QuickShell.Wayland) @@ -49,4 +54,6 @@ function (wl_proto name path) target_sources(quickshell-wayland PRIVATE ${PROTO_BUILD_PATH}/qwayland-${name}.cpp) endfunction() +wl_proto("wlr-layer-shell-unstable-v1" "${PROTO_SRC_PATH}/wlr-layer-shell-unstable-v1.xml") + target_include_directories(quickshell-wayland PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/wl-proto) diff --git a/src/wayland/layer_surface.cpp b/src/wayland/layer_surface.cpp new file mode 100644 index 0000000..116fc81 --- /dev/null +++ b/src/wayland/layer_surface.cpp @@ -0,0 +1,165 @@ +#include "layer_surface.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../core/shellwindow.hpp" +#include "layershell.hpp" +#include "shell_integration.hpp" + +// clang-format off +[[nodiscard]] QtWayland::zwlr_layer_shell_v1::layer toWaylandLayer(const Layer::Enum& layer) noexcept; +[[nodiscard]] QtWayland::zwlr_layer_surface_v1::anchor toWaylandAnchors(const Anchors& anchors) noexcept; +[[nodiscard]] QtWayland::zwlr_layer_surface_v1::keyboard_interactivity toWaylandKeyboardFocus(const KeyboardFocus::Enum& focus) noexcept; +[[nodiscard]] QSize constrainedSize(const Anchors& anchors, const QSize& size) noexcept; +// clang-format on + +// clang-format off +QSWaylandLayerSurface::QSWaylandLayerSurface( + QSWaylandLayerShellIntegration* shell, + QtWaylandClient::QWaylandWindow* window +): QtWaylandClient::QWaylandShellSurface(window) { + // clang-format on + + auto* qwindow = window->window(); + this->ext = LayershellWindowExtension::get(qwindow); + + if (this->ext == nullptr) { + throw "QSWaylandLayerSurface created with null LayershellWindowExtension"; + } + + wl_output* output = nullptr; // NOLINT (import) + if (this->ext->useWindowScreen) { + auto* waylandScreen = + dynamic_cast(qwindow->screen()->handle()); + + if (waylandScreen != nullptr) { + output = waylandScreen->output(); + } else { + qWarning() << "Layershell screen is set but does not corrospond to a real screen. Letting " + "the compositor pick."; + } + } + + this->init(shell->get_layer_surface( + window->waylandSurface()->object(), + output, + toWaylandLayer(this->ext->mLayer), + this->ext->mNamespace + )); + + this->updateAnchors(); + this->updateLayer(); + this->updateMargins(); + this->updateExclusiveZone(); + this->updateKeyboardFocus(); + + // new updates will be sent from the extension + this->ext->surface = this; + + auto size = constrainedSize(this->ext->mAnchors, qwindow->size()); + this->set_size(size.width(), size.height()); +} + +QSWaylandLayerSurface::~QSWaylandLayerSurface() { this->destroy(); } + +void QSWaylandLayerSurface::zwlr_layer_surface_v1_configure( + quint32 serial, + quint32 width, + quint32 height +) { + this->ack_configure(serial); + + this->size = QSize(static_cast(width), static_cast(height)); + if (!this->configured) { + this->configured = true; + this->window()->resizeFromApplyConfigure(this->size); + this->window()->handleExpose(QRect(QPoint(), this->size)); + } else { + this->window()->applyConfigureWhenPossible(); + } +} + +void QSWaylandLayerSurface::zwlr_layer_surface_v1_closed() { this->window()->window()->close(); } + +bool QSWaylandLayerSurface::isExposed() const { return this->configured; } + +void QSWaylandLayerSurface::applyConfigure() { + this->window()->resizeFromApplyConfigure(this->size); +} + +void QSWaylandLayerSurface::setWindowGeometry(const QRect& geometry) { + auto size = constrainedSize(this->ext->mAnchors, geometry.size()); + this->set_size(size.width(), size.height()); +} + +QWindow* QSWaylandLayerSurface::qwindow() { return this->window()->window(); } + +void QSWaylandLayerSurface::updateLayer() { this->set_layer(toWaylandLayer(this->ext->mLayer)); } + +void QSWaylandLayerSurface::updateAnchors() { + this->set_anchor(toWaylandAnchors(this->ext->mAnchors)); + this->setWindowGeometry(this->window()->windowContentGeometry()); +} + +void QSWaylandLayerSurface::updateMargins() { + auto& margins = this->ext->mMargins; + this->set_margin(margins.mTop, margins.mRight, margins.mBottom, margins.mLeft); +} + +void QSWaylandLayerSurface::updateExclusiveZone() { + this->set_exclusive_zone(this->ext->mExclusiveZone); +} + +void QSWaylandLayerSurface::updateKeyboardFocus() { + this->set_keyboard_interactivity(toWaylandKeyboardFocus(this->ext->mKeyboardFocus)); +} + +QtWayland::zwlr_layer_shell_v1::layer toWaylandLayer(const Layer::Enum& layer) noexcept { + switch (layer) { + case Layer::Background: return QtWayland::zwlr_layer_shell_v1::layer_background; + case Layer::Bottom: return QtWayland::zwlr_layer_shell_v1::layer_bottom; + case Layer::Top: return QtWayland::zwlr_layer_shell_v1::layer_top; + case Layer::Overlay: return QtWayland::zwlr_layer_shell_v1::layer_overlay; + } + + return QtWayland::zwlr_layer_shell_v1::layer_top; +} + +QtWayland::zwlr_layer_surface_v1::anchor toWaylandAnchors(const Anchors& anchors) noexcept { + quint32 wl = 0; + if (anchors.mLeft) wl |= QtWayland::zwlr_layer_surface_v1::anchor_left; + if (anchors.mRight) wl |= QtWayland::zwlr_layer_surface_v1::anchor_right; + if (anchors.mTop) wl |= QtWayland::zwlr_layer_surface_v1::anchor_top; + if (anchors.mBottom) wl |= QtWayland::zwlr_layer_surface_v1::anchor_bottom; + return static_cast(wl); +} + +QtWayland::zwlr_layer_surface_v1::keyboard_interactivity +toWaylandKeyboardFocus(const KeyboardFocus::Enum& focus) noexcept { + switch (focus) { + case KeyboardFocus::None: return QtWayland::zwlr_layer_surface_v1::keyboard_interactivity_none; + case KeyboardFocus::Exclusive: + return QtWayland::zwlr_layer_surface_v1::keyboard_interactivity_exclusive; + case KeyboardFocus::OnDemand: + return QtWayland::zwlr_layer_surface_v1::keyboard_interactivity_on_demand; + } + + return QtWayland::zwlr_layer_surface_v1::keyboard_interactivity_none; +} + +QSize constrainedSize(const Anchors& anchors, const QSize& size) noexcept { + return QSize( + anchors.horizontalConstraint() ? 0 : size.width(), + anchors.verticalConstraint() ? 0 : size.height() + ); +} diff --git a/src/wayland/layer_surface.hpp b/src/wayland/layer_surface.hpp new file mode 100644 index 0000000..3f50513 --- /dev/null +++ b/src/wayland/layer_surface.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "layershell.hpp" +#include "shell_integration.hpp" + +class Q_WAYLANDCLIENT_EXPORT QSWaylandLayerSurface + : public QtWaylandClient::QWaylandShellSurface + , public QtWayland::zwlr_layer_surface_v1 { +public: + QSWaylandLayerSurface( + QSWaylandLayerShellIntegration* shell, + QtWaylandClient::QWaylandWindow* window + ); + + ~QSWaylandLayerSurface() override; + QSWaylandLayerSurface(QSWaylandLayerSurface&&) = delete; + QSWaylandLayerSurface(const QSWaylandLayerSurface&) = delete; + void operator=(QSWaylandLayerSurface&&) = delete; + void operator=(const QSWaylandLayerSurface&) = delete; + + [[nodiscard]] bool isExposed() const override; + void applyConfigure() override; + void setWindowGeometry(const QRect& geometry) override; + +private: + void zwlr_layer_surface_v1_configure(quint32 serial, quint32 width, quint32 height) override; + void zwlr_layer_surface_v1_closed() override; + + QWindow* qwindow(); + void updateLayer(); + void updateAnchors(); + void updateMargins(); + void updateExclusiveZone(); + void updateKeyboardFocus(); + + LayershellWindowExtension* ext; + QSize size; + bool configured = false; + + friend class LayershellWindowExtension; +}; diff --git a/src/wayland/layershell.cpp b/src/wayland/layershell.cpp new file mode 100644 index 0000000..8a12f27 --- /dev/null +++ b/src/wayland/layershell.cpp @@ -0,0 +1,145 @@ +#include "layershell.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../core/shellwindow.hpp" +#include "layer_surface.hpp" +#include "shell_integration.hpp" + +LayershellWindowExtension* LayershellWindowExtension::get(QWindow* window) { + auto v = window->property("layershell_ext"); + + if (v.canConvert()) { + return v.value(); + } else { + return nullptr; + } +} + +bool LayershellWindowExtension::attach(QWindow* window) { + if (this->surface != nullptr) + throw "Cannot change the attached window of a LayershellWindowExtension"; + + auto* current = LayershellWindowExtension::get(window); + + bool hasSurface = false; + + if (current != nullptr) { + if (current->mNamespace != this->mNamespace) return false; + + if (current->surface != nullptr) { + if (current->surface->qwindow()->screen() != window->screen()) return false; + this->surface = current->surface; + + // update window with current settings, leveraging old extension's cached values + current->setAnchors(this->mAnchors); + current->setMargins(this->mMargins); + current->setExclusiveZone(this->mExclusiveZone); + current->setLayer(this->mLayer); + current->setKeyboardFocus(this->mKeyboardFocus); + + this->surface->ext = this; + current->surface = nullptr; + current->deleteLater(); + + hasSurface = true; + } + } + + if (!hasSurface) { + window->create(); + + auto* waylandWindow = dynamic_cast(window->handle()); + if (waylandWindow == nullptr) { + qWarning() << window << "is not a wayland window. Cannot create layershell surface."; + return false; + } + + static QSWaylandLayerShellIntegration* layershellIntegration = nullptr; // NOLINT + if (layershellIntegration == nullptr) { + layershellIntegration = new QSWaylandLayerShellIntegration(); + if (!layershellIntegration->initialize(waylandWindow->display())) { + delete layershellIntegration; + layershellIntegration = nullptr; + qWarning() << "Failed to initialize layershell integration"; + } + } + + waylandWindow->setShellIntegration(layershellIntegration); + } + + this->setParent(window); + window->setProperty("layershell_ext", QVariant::fromValue(this)); + return true; +} + +void LayershellWindowExtension::setAnchors(Anchors anchors) { + if (anchors != this->mAnchors) { + this->mAnchors = anchors; + if (this->surface != nullptr) this->surface->updateAnchors(); + emit this->anchorsChanged(); + } +} + +Anchors LayershellWindowExtension::anchors() const { return this->mAnchors; } + +void LayershellWindowExtension::setMargins(Margins margins) { + if (margins != this->mMargins) { + this->mMargins = margins; + if (this->surface != nullptr) this->surface->updateMargins(); + emit this->marginsChanged(); + } +} + +Margins LayershellWindowExtension::margins() const { return this->mMargins; } + +void LayershellWindowExtension::setExclusiveZone(qint32 exclusiveZone) { + if (exclusiveZone != this->mExclusiveZone) { + this->mExclusiveZone = exclusiveZone; + if (this->surface != nullptr) this->surface->updateExclusiveZone(); + emit this->exclusiveZoneChanged(); + } +} + +qint32 LayershellWindowExtension::exclusiveZone() const { return this->mExclusiveZone; } + +void LayershellWindowExtension::setLayer(Layer::Enum layer) { + if (layer != this->mLayer) { + this->mLayer = layer; + if (this->surface != nullptr) this->surface->updateLayer(); + emit this->layerChanged(); + } +} + +Layer::Enum LayershellWindowExtension::layer() const { return this->mLayer; } + +void LayershellWindowExtension::setKeyboardFocus(KeyboardFocus::Enum focus) { + if (focus != this->mKeyboardFocus) { + this->mKeyboardFocus = focus; + if (this->surface != nullptr) this->surface->updateKeyboardFocus(); + emit this->keyboardFocusChanged(); + } +} + +KeyboardFocus::Enum LayershellWindowExtension::keyboardFocus() const { + return this->mKeyboardFocus; +} + +void LayershellWindowExtension::setUseWindowScreen(bool value) { + this->useWindowScreen = value; // has no effect post configure +} + +void LayershellWindowExtension::setNamespace(QString ns) { + if (!this->isConfigured()) this->mNamespace = std::move(ns); +} + +QString LayershellWindowExtension::ns() const { return this->mNamespace; } + +bool LayershellWindowExtension::isConfigured() const { return this->surface != nullptr; } diff --git a/src/wayland/layershell.hpp b/src/wayland/layershell.hpp new file mode 100644 index 0000000..56e5dc4 --- /dev/null +++ b/src/wayland/layershell.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "../core/shellwindow.hpp" + +namespace Layer { // NOLINT +Q_NAMESPACE; +QML_ELEMENT; + +enum Enum { + Background = 0, + Bottom = 1, + Top = 2, + Overlay = 3, +}; +Q_ENUM_NS(Enum); + +} // namespace Layer + +/// Type of keyboard focus that will be accepted by a [ShellWindow] +/// +/// [ShellWindow]: ../shellwindow +namespace KeyboardFocus { // NOLINT +Q_NAMESPACE; +QML_ELEMENT; + +enum Enum { + /// No keyboard input will be accepted. + None = 0, + /// Exclusive access to the keyboard, locking out all other windows. + Exclusive = 1, + /// Access to the keyboard as determined by the operating system. + /// + /// > [!WARNING] On some systems, `OnDemand` may cause the shell window to + /// > retain focus over another window unexpectedly. + /// > You should try `None` if you experience issues. + OnDemand = 2, +}; +Q_ENUM_NS(Enum); + +} // namespace KeyboardFocus + +namespace ScreenConfiguration { // NOLINT +Q_NAMESPACE; +QML_ELEMENT; + +enum Enum { + Window = 0, + Compositor = 1, +}; +Q_ENUM_NS(Enum); + +} // namespace ScreenConfiguration + +class QSWaylandLayerSurface; + +class LayershellWindowExtension: public QObject { + Q_OBJECT; + +public: + LayershellWindowExtension(QObject* parent = nullptr): QObject(parent) {} + ~LayershellWindowExtension() override = default; + LayershellWindowExtension(LayershellWindowExtension&&) = delete; + LayershellWindowExtension(const LayershellWindowExtension&) = delete; + void operator=(LayershellWindowExtension&&) = delete; + void operator=(const LayershellWindowExtension&) = delete; + + // returns the layershell extension if attached, otherwise nullptr + static LayershellWindowExtension* get(QWindow* window); + + // Attach this layershell extension to the given window. + // The extension is reparented to the window and replaces any existing extensions. + // Returns false if the window cannot be used. + bool attach(QWindow* window); + + void setAnchors(Anchors anchors); + [[nodiscard]] Anchors anchors() const; + + void setMargins(Margins margins); + [[nodiscard]] Margins margins() const; + + void setExclusiveZone(qint32 exclusiveZone); + [[nodiscard]] qint32 exclusiveZone() const; + + void setLayer(Layer::Enum layer); + [[nodiscard]] Layer::Enum layer() const; + + void setKeyboardFocus(KeyboardFocus::Enum focus); + [[nodiscard]] KeyboardFocus::Enum keyboardFocus() const; + + // no effect if configured + void setUseWindowScreen(bool value); + void setNamespace(QString ns); + [[nodiscard]] QString ns() const; + [[nodiscard]] bool isConfigured() const; + +signals: + void anchorsChanged(); + void marginsChanged(); + void exclusiveZoneChanged(); + void layerChanged(); + void keyboardFocusChanged(); + +private: + // if configured the screen cannot be changed + QSWaylandLayerSurface* surface = nullptr; + + bool useWindowScreen = false; + Anchors mAnchors; + Margins mMargins; + qint32 mExclusiveZone = 0; + Layer::Enum mLayer = Layer::Top; + QString mNamespace = "quickshell"; + KeyboardFocus::Enum mKeyboardFocus = KeyboardFocus::None; + + friend class QSWaylandLayerSurface; +}; diff --git a/src/wayland/shell_integration.cpp b/src/wayland/shell_integration.cpp new file mode 100644 index 0000000..134d14d --- /dev/null +++ b/src/wayland/shell_integration.cpp @@ -0,0 +1,22 @@ +#include "shell_integration.hpp" + +#include +#include +#include + +#include "layer_surface.hpp" +#include "wayland-wlr-layer-shell-unstable-v1-client-protocol.h" + +QSWaylandLayerShellIntegration::QSWaylandLayerShellIntegration() + : QtWaylandClient::QWaylandShellIntegrationTemplate(4) {} + +QSWaylandLayerShellIntegration::~QSWaylandLayerShellIntegration() { + if (this->object() != nullptr) { + zwlr_layer_shell_v1_destroy(this->object()); + } +} + +QtWaylandClient::QWaylandShellSurface* +QSWaylandLayerShellIntegration::createShellSurface(QtWaylandClient::QWaylandWindow* window) { + return new QSWaylandLayerSurface(this, window); +} diff --git a/src/wayland/shell_integration.hpp b/src/wayland/shell_integration.hpp new file mode 100644 index 0000000..1630ec0 --- /dev/null +++ b/src/wayland/shell_integration.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include +#include + +class Q_WAYLANDCLIENT_EXPORT QSWaylandLayerShellIntegration + : public QtWaylandClient::QWaylandShellIntegrationTemplate + , public QtWayland::zwlr_layer_shell_v1 { +public: + QSWaylandLayerShellIntegration(); + ~QSWaylandLayerShellIntegration() override; + QSWaylandLayerShellIntegration(QSWaylandLayerShellIntegration&&) = delete; + QSWaylandLayerShellIntegration(const QSWaylandLayerShellIntegration&) = delete; + void operator=(QSWaylandLayerShellIntegration&&) = delete; + void operator=(const QSWaylandLayerShellIntegration&) = delete; + + QtWaylandClient::QWaylandShellSurface* createShellSurface(QtWaylandClient::QWaylandWindow* window + ) override; +}; diff --git a/src/wayland/waylandshellwindow.cpp b/src/wayland/waylandshellwindow.cpp new file mode 100644 index 0000000..9307153 --- /dev/null +++ b/src/wayland/waylandshellwindow.cpp @@ -0,0 +1,174 @@ +#include "waylandshellwindow.hpp" + +#include +#include +#include +#include +#include + +#include "../core/proxywindow.hpp" +#include "../core/shellwindow.hpp" +#include "layershell.hpp" + +WaylandShellWindow::WaylandShellWindow(QObject* parent) + : ProxyShellWindow(parent) + , mWayland(new WaylandShellWindowExtensions(this)) + , windowExtension(new LayershellWindowExtension(this)) {} + +void WaylandShellWindow::setupWindow() { + this->ProxyShellWindow::setupWindow(); + + // clang-format off + QObject::connect(this->windowExtension, &LayershellWindowExtension::anchorsChanged, this, &ProxyShellWindow::anchorsChanged); + QObject::connect(this->windowExtension, &LayershellWindowExtension::marginsChanged, this, &ProxyShellWindow::marginsChanged); + QObject::connect(this->windowExtension, &LayershellWindowExtension::exclusiveZoneChanged, this, &ProxyShellWindow::exclusionZoneChanged); + + QObject::connect( + this->windowExtension, &LayershellWindowExtension::layerChanged, + this->mWayland, &WaylandShellWindowExtensions::layerChanged + ); + QObject::connect( + this->windowExtension, &LayershellWindowExtension::keyboardFocusChanged, + this->mWayland, &WaylandShellWindowExtensions::keyboardFocusChanged + ); + + QObject::connect(this->window, &QWindow::widthChanged, this, &WaylandShellWindow::updateExclusionZone); + QObject::connect(this->window, &QWindow::heightChanged, this, &WaylandShellWindow::updateExclusionZone); + QObject::connect(this, &ProxyShellWindow::anchorsChanged, this, &WaylandShellWindow::updateExclusionZone); + QObject::connect(this, &ProxyShellWindow::marginsChanged, this, &WaylandShellWindow::updateExclusionZone); + // clang-format on + + this->setMargins(this->mMargins); + this->setExclusionMode(this->mExclusionMode); // also sets exclusion zone + + if (!this->windowExtension->attach(this->window)) { + // todo: discard window + } + + this->connected = true; +} + +QQuickWindow* WaylandShellWindow::disownWindow() { return this->ProxyWindowBase::disownWindow(); } + +void WaylandShellWindow::setWidth(qint32 width) { + this->mWidth = width; + + // only update the actual size if not blocked by anchors + if (!this->windowExtension->anchors().horizontalConstraint()) { + this->ProxyShellWindow::setWidth(width); + } +} + +void WaylandShellWindow::onWidthChanged() { + this->ProxyShellWindow::onWidthChanged(); + + // change the remembered width only when not horizontally constrained. + if (this->window != nullptr && !this->windowExtension->anchors().horizontalConstraint()) { + this->mWidth = this->window->width(); + } +} + +void WaylandShellWindow::setHeight(qint32 height) { + this->mHeight = height; + + // only update the actual size if not blocked by anchors + if (!this->windowExtension->anchors().verticalConstraint()) { + this->ProxyShellWindow::setHeight(height); + } +} + +void WaylandShellWindow::onHeightChanged() { + this->ProxyShellWindow::onHeightChanged(); + + // change the remembered height only when not vertically constrained. + if (this->window != nullptr && !this->windowExtension->anchors().verticalConstraint()) { + this->mHeight = this->window->height(); + } +} + +void WaylandShellWindow::setAnchors(Anchors anchors) { + this->windowExtension->setAnchors(anchors); + this->setHeight(this->mHeight); + this->setWidth(this->mWidth); +} + +Anchors WaylandShellWindow::anchors() const { return this->windowExtension->anchors(); } + +void WaylandShellWindow::setExclusiveZone(qint32 zone) { + this->windowExtension->setExclusiveZone(zone); +} + +qint32 WaylandShellWindow::exclusiveZone() const { return this->windowExtension->exclusiveZone(); } + +ExclusionMode::Enum WaylandShellWindow::exclusionMode() const { return this->mExclusionMode; } + +void WaylandShellWindow::setExclusionMode(ExclusionMode::Enum exclusionMode) { + if (this->connected && exclusionMode == this->mExclusionMode) return; + this->mExclusionMode = exclusionMode; + + if (this->window != nullptr) { + if (exclusionMode == ExclusionMode::Normal) { + this->windowExtension->setExclusiveZone(this->mExclusionZone); + } else if (exclusionMode == ExclusionMode::Ignore) { + this->windowExtension->setExclusiveZone(-1); + } else { + this->updateExclusionZone(); + } + } +} + +void WaylandShellWindow::setMargins(Margins margins) { this->windowExtension->setMargins(margins); } + +Margins WaylandShellWindow::margins() const { return this->windowExtension->margins(); } + +void WaylandShellWindowExtensions::setLayer(Layer::Enum layer) { + this->window->windowExtension->setLayer(layer); +} + +Layer::Enum WaylandShellWindowExtensions::layer() const { + return this->window->windowExtension->layer(); +} + +void WaylandShellWindowExtensions::setNamespace(const QString& ns) { + if (this->window->windowExtension->isConfigured()) { + auto* context = QQmlEngine::contextForObject(this); + if (context != nullptr) { + context->engine()->throwError(QString("Cannot change shell window namespace post-configure.") + ); + } + + return; + } + + this->window->windowExtension->setNamespace(ns); +} + +QString WaylandShellWindowExtensions::ns() const { return this->window->windowExtension->ns(); } + +void WaylandShellWindowExtensions::setKeyboardFocus(KeyboardFocus::Enum focus) { + this->window->windowExtension->setKeyboardFocus(focus); +} + +KeyboardFocus::Enum WaylandShellWindowExtensions::keyboardFocus() const { + return this->window->windowExtension->keyboardFocus(); +} + +void WaylandShellWindow::updateExclusionZone() { + if (this->window != nullptr && this->exclusionMode() == ExclusionMode::Auto) { + auto anchors = this->anchors(); + + auto zone = -1; + + if (anchors.mTop && anchors.mBottom) { + if (anchors.mLeft) zone = this->width() + this->margins().mLeft; + else if (anchors.mRight) zone = this->width() + this->margins().mRight; + } else if (anchors.mLeft && anchors.mRight) { + if (anchors.mTop) zone = this->height() + this->margins().mTop; + else if (anchors.mBottom) zone = this->height() + this->margins().mBottom; + } + + if (zone != -1) { + this->windowExtension->setExclusiveZone(zone); + } + } +} diff --git a/src/core/layershell.hpp b/src/wayland/waylandshellwindow.hpp similarity index 55% rename from src/core/layershell.hpp rename to src/wayland/waylandshellwindow.hpp index 6f0c0d7..878b4ca 100644 --- a/src/core/layershell.hpp +++ b/src/wayland/waylandshellwindow.hpp @@ -1,61 +1,11 @@ #pragma once -#include #include -#include -#include +#include #include -#include "shellwindow.hpp" - -namespace Layer { // NOLINT -Q_NAMESPACE; -QML_ELEMENT; - -enum Enum { - Background = 0, - Bottom = 1, - Top = 2, - Overlay = 3, -}; -Q_ENUM_NS(Enum); - -} // namespace Layer - -/// Type of keyboard focus that will be accepted by a [ShellWindow] -/// -/// [ShellWindow]: ../shellwindow -namespace KeyboardFocus { // NOLINT -Q_NAMESPACE; -QML_ELEMENT; - -enum Enum { - /// No keyboard input will be accepted. - None = 0, - /// Exclusive access to the keyboard, locking out all other windows. - Exclusive = 1, - /// Access to the keyboard as determined by the operating system. - /// - /// > [!WARNING] On some systems, `OnDemand` may cause the shell window to - /// > retain focus over another window unexpectedly. - /// > You should try `None` if you experience issues. - OnDemand = 2, -}; -Q_ENUM_NS(Enum); - -} // namespace KeyboardFocus - -namespace ScreenConfiguration { // NOLINT -Q_NAMESPACE; -QML_ELEMENT; - -enum Enum { - Window = 0, - Compositor = 1, -}; -Q_ENUM_NS(Enum); - -} // namespace ScreenConfiguration +#include "../core/shellwindow.hpp" +#include "layershell.hpp" class WaylandShellWindowExtensions; @@ -89,16 +39,13 @@ public: protected slots: void updateExclusionZone(); + void onWidthChanged() override; + void onHeightChanged() override; private: WaylandShellWindowExtensions* mWayland = nullptr; - LayerShellQt::Window* shellWindow = nullptr; - Layer::Enum mLayer = Layer::Top; - QString mScope; - KeyboardFocus::Enum mKeyboardFocus = KeyboardFocus::None; - ScreenConfiguration::Enum mScreenConfiguration = ScreenConfiguration::Window; - + LayershellWindowExtension* windowExtension; bool connected = false; friend class WaylandShellWindowExtensions; @@ -106,26 +53,27 @@ private: class WaylandShellWindowExtensions: public QObject { Q_OBJECT; + // clang-format off /// The shell layer the window sits in. Defaults to `Layer.Top`. Q_PROPERTY(Layer::Enum layer READ layer WRITE setLayer NOTIFY layerChanged); - Q_PROPERTY(QString scope READ scope WRITE setScope); + /// Similar to the class property of windows. Can be used to identify the window to external tools. + Q_PROPERTY(QString namespace READ ns WRITE setNamespace); /// The degree of keyboard focus taken. Defaults to `KeyboardFocus.None`. - Q_PROPERTY(KeyboardFocus::Enum keyboardFocus READ keyboardFocus WRITE setKeyboardFocus NOTIFY - keyboardFocusChanged); - Q_PROPERTY(ScreenConfiguration::Enum screenConfiguration READ screenConfiguration WRITE - setScreenConfiguration); + Q_PROPERTY(KeyboardFocus::Enum keyboardFocus READ keyboardFocus WRITE setKeyboardFocus NOTIFY keyboardFocusChanged); + // clang-format on QML_ELEMENT; QML_UNCREATABLE("WaylandShellWindowExtensions cannot be created"); public: - explicit WaylandShellWindowExtensions(WaylandShellWindow* window): - QObject(window), window(window) {} + explicit WaylandShellWindowExtensions(WaylandShellWindow* window) + : QObject(window) + , window(window) {} void setLayer(Layer::Enum layer); [[nodiscard]] Layer::Enum layer() const; - void setScope(const QString& scope); - [[nodiscard]] QString scope() const; + void setNamespace(const QString& ns); + [[nodiscard]] QString ns() const; void setKeyboardFocus(KeyboardFocus::Enum focus); [[nodiscard]] KeyboardFocus::Enum keyboardFocus() const; diff --git a/src/wayland/wl-proto/wlr-layer-shell-unstable-v1.xml b/src/wayland/wl-proto/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..283f391 --- /dev/null +++ b/src/wayland/wl-proto/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,407 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + After creating a layer_surface object and setting it up, the client + must perform an initial commit without any buffer attached. + The compositor will reply with a layer_surface.configure event. + The client must acknowledge it and is then allowed to attach a buffer + to map the surface. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + + This request indicates that the client will not use the layer_shell + object any more. Objects that have been created through this instance + are not affected. + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Attaching a null buffer to a layer surface unmaps it. + + Unmapping a layer_surface means that the surface cannot be shown by the + compositor until it is explicitly mapped again. The layer_surface + returns to the state it had right after layer_shell.get_layer_surface. + The client can re-map the surface by performing a commit without any + buffer attached, waiting for a configure event and handling it as usual. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive exclusive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Types of keyboard interaction possible for layer shell surfaces. The + rationale for this is twofold: (1) some applications are not interested + in keyboard events and not allowing them to be focused can improve the + desktop experience; (2) some applications will want to take exclusive + keyboard focus. + + + + + This value indicates that this surface is not interested in keyboard + events and the compositor should never assign it the keyboard focus. + + This is the default value, set for newly created layer shell surfaces. + + This is useful for e.g. desktop widgets that display information or + only have interaction with non-keyboard input devices. + + + + + Request exclusive keyboard focus if this surface is above the shell surface layer. + + For the top and overlay layers, the seat will always give + exclusive keyboard focus to the top-most layer which has keyboard + interactivity set to exclusive. If this layer contains multiple + surfaces with keyboard interactivity set to exclusive, the compositor + determines the one receiving keyboard events in an implementation- + defined manner. In this case, no guarantee is made when this surface + will receive keyboard focus (if ever). + + For the bottom and background layers, the compositor is allowed to use + normal focus semantics. + + This setting is mainly intended for applications that need to ensure + they receive all keyboard events, such as a lock screen or a password + prompt. + + + + + This requests the compositor to allow this surface to be focused and + unfocused by the user in an implementation-defined manner. The user + should be able to unfocus this surface even regardless of the layer + it is on. + + Typically, the compositor will want to use its normal mechanism to + manage keyboard focus between layer shell surfaces with this setting + and regular toplevels on the desktop layer (e.g. click to focus). + Nevertheless, it is possible for a compositor to require a special + interaction to focus or unfocus layer shell surfaces (e.g. requiring + a click even if focus follows the mouse normally, or providing a + keybinding to switch focus between layers). + + This setting is mainly intended for desktop shell components (e.g. + panels) that allow keyboard interaction. Using this option can allow + implementing a desktop shell that can be fully usable without the + mouse. + + + + + + + Set how keyboard events are delivered to this surface. By default, + layer shell surfaces do not receive keyboard events; this request can + be used to change this. + + This setting is inherited by child surfaces set by the get_popup + request. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Keyboard interactivity is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + + + + + + + Requests an edge for the exclusive zone to apply. The exclusive + edge will be automatically deduced from anchor points when possible, + but when the surface is anchored to a corner, it will be necessary + to set it explicitly to disambiguate, as it is not possible to deduce + which one of the two corner edges should be used. + + The edge must be one the surface is anchored to, otherwise the + invalid_exclusive_edge protocol error will be raised. + + + + +