From 29d007890923cb6d45a92c79b792574e9da9973f Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Wed, 14 Apr 2021 17:52:31 +0200 Subject: [PATCH] Ensure we can set per-window properties before the intial commit In the current implementation we cannot use a LayerShellQt before the shell surface is created. At the moment a shell surface is created, the constructor is run and then QtWayland commits the current state. This means the compositor configures the window before a client has any chance to set anchors or margins. This works whilst we're just being a simple fullscreen window, but won't scale for plasmashell in the future. This patch makes LayerShellQt::Window always creatable, and we can set and cache properties before the platform window is created, just like one can on QWindow and XDGShell properties. This also makes it less potentially crashy as ::get always returns a valid result, and sets up the public API to be QML-able as an attached property in future. Co-authored on Aleix's patch for the unit test --- src/interfaces/shell.cpp | 2 +- src/interfaces/shell.h | 2 + src/interfaces/window.cpp | 109 +++++++++++++++++++++++++++++------ src/interfaces/window.h | 27 +++++++-- src/qwaylandlayersurface.cpp | 39 ++++++++++--- tests/main.cpp | 99 +++++++++++++++++++++++++------ 6 files changed, 229 insertions(+), 49 deletions(-) diff --git a/src/interfaces/shell.cpp b/src/interfaces/shell.cpp index 6eb490c..89b42d7 100644 --- a/src/interfaces/shell.cpp +++ b/src/interfaces/shell.cpp @@ -6,8 +6,8 @@ #include "shell.h" #include -#include #include +#include using namespace LayerShellQt; diff --git a/src/interfaces/shell.h b/src/interfaces/shell.h index cccae17..c68017a 100644 --- a/src/interfaces/shell.h +++ b/src/interfaces/shell.h @@ -8,6 +8,8 @@ #define LAYERSHELLQTSHELL_H #include "layershellqt_export.h" +#include "window.h" +#include namespace LayerShellQt { diff --git a/src/interfaces/window.cpp b/src/interfaces/window.cpp index a1eaa99..4f3c5f2 100644 --- a/src/interfaces/window.cpp +++ b/src/interfaces/window.cpp @@ -15,58 +15,133 @@ using namespace LayerShellQt; class LayerShellQt::WindowPrivate { public: - WindowPrivate(QWaylandLayerSurface *surface) - : surface(surface) + WindowPrivate(QWindow *window) + : parentWindow(window) { } - QWaylandLayerSurface *const surface; + QWindow *parentWindow; + QString scope = QStringLiteral("qt"); + Window::Anchors anchor = {Window::AnchorTop | Window::AnchorBottom | Window::AnchorLeft | Window::AnchorRight}; + int32_t exclusionZone = 0; + bool keyboardInteractivity = false; + Window::Layer layer = Window::LayerTop; + QMargins margins; + QWaylandLayerSurface* getSurface() const; }; -Window::~Window() = default; +static QMap s_map; -void Window::setAnchor(Anchor anchor) +Window::~Window() { - d->surface->setAnchor(anchor); + s_map.remove(d->parentWindow); +} + +void Window::setAnchor(Anchors anchor) +{ + d->anchor = anchor; + if (auto surface = d->getSurface()) { + surface->setAnchor(anchor); + } +} + +Window::Anchors Window::anchor() const +{ + return d->anchor; } void Window::setExclusiveZone(int32_t zone) { - d->surface->setExclusiveZone(zone); + d->exclusionZone = zone; + if (auto surface= d->getSurface()) { + surface->setExclusiveZone(zone); + } +} + +int32_t Window::exclusionZone() const +{ + return d->exclusionZone; } void Window::setMargins(const QMargins &margins) { - d->surface->setMargins(margins); + d->margins = margins; + if (auto surface= d->getSurface()) { + surface->setMargins(margins); + } +} + +QMargins Window::margins() const +{ + return d->margins; } void Window::setKeyboardInteractivity(bool enabled) { - d->surface->setKeyboardInteractivity(enabled); + d->keyboardInteractivity = enabled; + if (auto surface= d->getSurface()) { + surface->setKeyboardInteractivity(enabled); + } +} + +bool Window::keyboardInteractivity() const +{ + return d->keyboardInteractivity; } void Window::setLayer(Layer layer) { - d->surface->setLayer(layer); + d->layer = layer; + if (auto surface= d->getSurface()) { + surface->setLayer(layer); + } +} + +void Window::setScope(const QString &scope) +{ + d->scope = scope; + //this is static and must be set before the platform window is created +} + +QString Window::scope() const +{ + return d->scope; +} + +Window::Layer Window::layer() const +{ + return d->layer; } Window::Window(WindowPrivate *d) - : d(d) + : QObject(d->parentWindow) + , d(d) { + s_map.insert(d->parentWindow, this); } -Window *Window::get(QWindow *window) +QWaylandLayerSurface *WindowPrivate::getSurface() const { - auto ww = dynamic_cast(window->handle()); + if (!parentWindow) { + return nullptr; + } + auto ww = dynamic_cast(parentWindow->handle()); if (!ww) { - qCDebug(LAYERSHELLQT) << "window not a wayland window" << window; + qCDebug(LAYERSHELLQT) << "window not a wayland window" << parentWindow; return nullptr; } QWaylandLayerSurface *s = qobject_cast(ww->shellSurface()); if (!s) { - qCDebug(LAYERSHELLQT) << "window not using wlr-layer-shell" << window << ww->shellSurface(); + qCDebug(LAYERSHELLQT) << "window not using wlr-layer-shell" << parentWindow << ww->shellSurface(); return nullptr; } - - return new Window(new WindowPrivate(s)); + return s; +} + +Window *Window::get(QWindow *window) +{ + if (s_map.contains(window)) { + return s_map[window]; + } + return new Window(new WindowPrivate(window)); } diff --git a/src/interfaces/window.h b/src/interfaces/window.h index 64fbbcb..c820d99 100644 --- a/src/interfaces/window.h +++ b/src/interfaces/window.h @@ -30,6 +30,7 @@ public: AnchorRight = 8, // the right edge of the anchor rectangle }; Q_ENUM(Anchor); + Q_DECLARE_FLAGS(Anchors, Anchor) /** * This enum type is used to specify the layer where a surface can be put in. @@ -42,12 +43,28 @@ public: }; Q_ENUM(Layer) - void setAnchor(Anchor anchor); - void setExclusiveZone(int32_t zone); - void setMargins(const QMargins &margins); - void setKeyboardInteractivity(bool enabled); - void setLayer(Layer layer); + void setAnchor(Anchors anchor); + Anchors anchor() const; + void setExclusiveZone(int32_t zone); + int32_t exclusionZone() const; + + void setMargins(const QMargins &margins); + QMargins margins() const; + + void setKeyboardInteractivity(bool enabled); + bool keyboardInteractivity() const; + + void setLayer(Layer layer); + Layer layer() const; + + void setScope(const QString &scope); + QString scope() const; + + /** + * Gets the LayerShell Window for a given Qt Window + * Ownership is not transferred + */ static Window *get(QWindow *window); private: diff --git a/src/qwaylandlayersurface.cpp b/src/qwaylandlayersurface.cpp index d1bcd3c..3a840e2 100644 --- a/src/qwaylandlayersurface.cpp +++ b/src/qwaylandlayersurface.cpp @@ -5,6 +5,7 @@ * SPDX-License-Identifier: LGPL-3.0-or-later */ +#include "interfaces/shell.h" #include "qwaylandlayershell_p.h" #include "qwaylandlayersurface_p.h" @@ -16,14 +17,38 @@ namespace LayerShellQt { QWaylandLayerSurface::QWaylandLayerSurface(QWaylandLayerShell *shell, QtWaylandClient::QWaylandWindow *window) : QtWaylandClient::QWaylandShellSurface(window) - , QtWayland::zwlr_layer_surface_v1( - // TODO: Specify namespace - shell->get_layer_surface(window->waylandSurface()->object(), - window->waylandScreen()->output(), - QtWayland::zwlr_layer_shell_v1::layer_top, - QStringLiteral("qt"))) + , QtWayland::zwlr_layer_surface_v1() { - set_anchor(anchor_top | anchor_bottom | anchor_left | anchor_right); + Window::Layer layer =Window::LayerTop; + QString scope =QStringLiteral( "qt"); + LayerShellQt::Window *interface = Window::get(window->window()); + Window::Anchors anchors = {Window::AnchorTop | Window::AnchorBottom | Window::AnchorLeft | Window::AnchorRight}; + + if (interface) { + anchors = interface->anchor(); + layer = interface->layer(); + scope = interface->scope(); + } + + init(shell->get_layer_surface(window->waylandSurface()->object(), window->waylandScreen()->output(), layer, scope)); + set_anchor(anchors); + + if (interface) { + setMargins(interface->margins()); + setKeyboardInteractivity(interface->keyboardInteractivity()); + setExclusiveZone(interface->exclusionZone()); + } + + QSize size = window->surfaceSize(); + if (anchors & Window::AnchorLeft && anchors & Window::AnchorRight) { + size.setWidth(0); + } + if (anchors & Window::AnchorTop && anchors & Window::AnchorBottom) { + size.setHeight(0); + } + if (size.isValid() && size != QSize(0,0)) { + set_size(size.width(), size.height()); + } } QWaylandLayerSurface::~QWaylandLayerSurface() diff --git a/tests/main.cpp b/tests/main.cpp index c3d4d3d..9c82ba7 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -4,35 +4,96 @@ * SPDX-License-Identifier: LGPL-3.0-or-later */ +#include + #include -#include +#include +#include +#include +#include + +#include + #include #include +using namespace LayerShellQt; + +QStringList enumsToStringList(QMetaEnum metaEnum) +{ + QStringList ret; + ret.reserve(metaEnum.keyCount()); + for (int i = 0; i < metaEnum.keyCount(); ++i) { + ret.append(metaEnum.key(i)); + } + return ret; +} + +template +T stringToEnum(QMetaEnum metaEnum, const QString &str) +{ + T ret = {}; + const auto splitted = str.split(QLatin1Char('|')); + for (const auto &value : splitted) { + ret |= T(metaEnum.keyToValue(qPrintable(value))); + } + return ret; +} + +class BasicWindow : public QRasterWindow +{ + void paintEvent(QPaintEvent *) { + QPainter p(this); + p.fillRect(QRect(0,0,width(), height()), Qt::red); + } +}; + int main(int argc, char **argv) { - LayerShellQt::Shell::useLayerShell(); + Shell::useLayerShell(); QGuiApplication app(argc, argv); - QQmlApplicationEngine engine; - engine.loadData( - "import QtQuick.Controls 2.10\n" - "import QtQuick 2.10\n" - "\n" - "ApplicationWindow {" - " width: 100; height: 100\n" - " visible: true\n" - " Rectangle { color: 'red'; anchors.fill: parent }" - "}" - , - QStringLiteral("bananaland:/potato.qml")); + const auto layerMetaEnum = QMetaEnum::fromType(); + const auto anchorMetaEnum = QMetaEnum::fromType(); - QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [](QObject *object) { - auto layerWindow = LayerShellQt::Window::get(qobject_cast(object)); - Q_ASSERT(layerWindow); - layerWindow->setMargins({50, 50, 50, 50}); - }); + QCommandLineParser parser; + QCommandLineOption marginsOption(QStringLiteral("margins"), QStringLiteral("Window margins"), QStringLiteral("pixels"), QStringLiteral("0")); + QCommandLineOption scopeOption(QStringLiteral("scope"), QStringLiteral("Window scope"), QStringLiteral("namespace"), QStringLiteral("normal")); + QCommandLineOption anchorsOption(QStringLiteral("anchors"), + QStringLiteral("Either ") + enumsToStringList(anchorMetaEnum).join(QLatin1String("|")), + QStringLiteral("anchors"), + QStringLiteral("AnchorTop|AnchorBottom|AnchorLeft|AnchorRight")); + QCommandLineOption layerOption(QStringLiteral("layer"), + QStringLiteral("One of ") + enumsToStringList(layerMetaEnum).join(QLatin1String("|")), + QStringLiteral("layer"), + QStringLiteral("LayerTop")); + parser.addOptions({marginsOption, scopeOption, anchorsOption, layerOption}); + parser.addHelpOption(); + parser.process(app); + + BasicWindow window; + + LayerShellQt::Window* layerShell = LayerShellQt::Window::get(&window); + if (parser.isSet(marginsOption)) { + int margins = parser.value(marginsOption).toInt(); + layerShell->setMargins({margins, margins, margins, margins}); + } + + if (parser.isSet(scopeOption)) { + layerShell->setScope(parser.value(scopeOption)); + } + if (parser.isSet(layerOption)) { + layerShell->setLayer(Window::Layer(layerMetaEnum.keyToValue(qPrintable(parser.value(layerOption))))); + } + if (parser.isSet(anchorsOption)) { + layerShell->setAnchor(stringToEnum(anchorMetaEnum, parser.value(anchorsOption))); + } + window.show(); + + + // just so you don't block yourself out whilst testing + QTimer::singleShot(5000, &app, &QGuiApplication::quit); return app.exec(); }