diff --git a/src/wayland/wlr_layershell.cpp b/src/wayland/wlr_layershell.cpp index 9b4f32f2..010d0f77 100644 --- a/src/wayland/wlr_layershell.cpp +++ b/src/wayland/wlr_layershell.cpp @@ -196,6 +196,7 @@ WaylandPanelInterface::WaylandPanelInterface(QObject* parent) QObject::connect(this->layer, &ProxyWindowBase::windowTransformChanged, this, &WaylandPanelInterface::windowTransformChanged); QObject::connect(this->layer, &ProxyWindowBase::colorChanged, this, &WaylandPanelInterface::colorChanged); QObject::connect(this->layer, &ProxyWindowBase::maskChanged, this, &WaylandPanelInterface::maskChanged); + QObject::connect(this->layer, &ProxyWindowBase::surfaceFormatChanged, this, &WaylandPanelInterface::surfaceFormatChanged); // panel specific QObject::connect(this->layer, &WlrLayershell::anchorsChanged, this, &WaylandPanelInterface::anchorsChanged); @@ -232,6 +233,7 @@ proxyPair(qint32, height, setHeight); proxyPair(QuickshellScreenInfo*, screen, setScreen); proxyPair(QColor, color, setColor); proxyPair(PendingRegion*, mask, setMask); +proxyPair(QsSurfaceFormat, surfaceFormat, setSurfaceFormat); // panel specific proxyPair(Anchors, anchors, setAnchors); diff --git a/src/wayland/wlr_layershell.hpp b/src/wayland/wlr_layershell.hpp index f6f6988a..32aeecdb 100644 --- a/src/wayland/wlr_layershell.hpp +++ b/src/wayland/wlr_layershell.hpp @@ -155,6 +155,9 @@ public: [[nodiscard]] PendingRegion* mask() const override; void setMask(PendingRegion* mask) override; + [[nodiscard]] QsSurfaceFormat surfaceFormat() const override; + void setSurfaceFormat(QsSurfaceFormat mask) override; + [[nodiscard]] QQmlListProperty data() override; // panel specific diff --git a/src/window/floatingwindow.cpp b/src/window/floatingwindow.cpp index a1d23f54..918b186d 100644 --- a/src/window/floatingwindow.cpp +++ b/src/window/floatingwindow.cpp @@ -36,6 +36,7 @@ FloatingWindowInterface::FloatingWindowInterface(QObject* parent) QObject::connect(this->window, &ProxyWindowBase::windowTransformChanged, this, &FloatingWindowInterface::windowTransformChanged); QObject::connect(this->window, &ProxyWindowBase::colorChanged, this, &FloatingWindowInterface::colorChanged); QObject::connect(this->window, &ProxyWindowBase::maskChanged, this, &FloatingWindowInterface::maskChanged); + QObject::connect(this->window, &ProxyWindowBase::surfaceFormatChanged, this, &FloatingWindowInterface::surfaceFormatChanged); // clang-format on } @@ -64,6 +65,7 @@ proxyPair(qint32, height, setHeight); proxyPair(QuickshellScreenInfo*, screen, setScreen); proxyPair(QColor, color, setColor); proxyPair(PendingRegion*, mask, setMask); +proxyPair(QsSurfaceFormat, surfaceFormat, setSurfaceFormat); #undef proxyPair // NOLINTEND diff --git a/src/window/floatingwindow.hpp b/src/window/floatingwindow.hpp index def1183a..36f933b4 100644 --- a/src/window/floatingwindow.hpp +++ b/src/window/floatingwindow.hpp @@ -4,6 +4,7 @@ #include #include "proxywindow.hpp" +#include "windowinterface.hpp" class ProxyFloatingWindow: public ProxyWindowBase { Q_OBJECT; @@ -50,6 +51,9 @@ public: [[nodiscard]] PendingRegion* mask() const override; void setMask(PendingRegion* mask) override; + [[nodiscard]] QsSurfaceFormat surfaceFormat() const override; + void setSurfaceFormat(QsSurfaceFormat mask) override; + [[nodiscard]] QQmlListProperty data() override; // NOLINTEND diff --git a/src/window/panelinterface.hpp b/src/window/panelinterface.hpp index 6f8d1ec1..b9664ff9 100644 --- a/src/window/panelinterface.hpp +++ b/src/window/panelinterface.hpp @@ -13,7 +13,8 @@ class Anchors { Q_PROPERTY(bool right MEMBER mRight); Q_PROPERTY(bool top MEMBER mTop); Q_PROPERTY(bool bottom MEMBER mBottom); - QML_VALUE_TYPE(anchors); + QML_VALUE_TYPE(panelAnchors); + QML_STRUCTURED_VALUE; public: [[nodiscard]] bool horizontalConstraint() const noexcept { return this->mLeft && this->mRight; } @@ -40,7 +41,8 @@ class Margins { Q_PROPERTY(qint32 right MEMBER mRight); Q_PROPERTY(qint32 top MEMBER mTop); Q_PROPERTY(qint32 bottom MEMBER mBottom); - QML_VALUE_TYPE(margins); + QML_VALUE_TYPE(panelMargins); + QML_STRUCTURED_VALUE; public: [[nodiscard]] bool operator==(const Margins& other) const noexcept { diff --git a/src/window/proxywindow.cpp b/src/window/proxywindow.cpp index 6af1dcc3..608883ae 100644 --- a/src/window/proxywindow.cpp +++ b/src/window/proxywindow.cpp @@ -6,10 +6,13 @@ #include #include #include +#include #include #include #include #include +#include +#include #include #include #include @@ -50,7 +53,7 @@ ProxyWindowBase::~ProxyWindowBase() { this->deleteWindow(true); } void ProxyWindowBase::onReload(QObject* oldInstance) { this->window = this->retrieveWindow(oldInstance); auto wasVisible = this->window != nullptr && this->window->isVisible(); - if (this->window == nullptr) this->window = this->createQQuickWindow(); + this->ensureQWindow(); // The qml engine will leave the WindowInterface as owner of everything // nested in an item, so we have to make sure the interface's children @@ -85,10 +88,55 @@ void ProxyWindowBase::postCompleteWindow() { this->setVisible(this->mVisible); } ProxiedWindow* ProxyWindowBase::createQQuickWindow() { return new ProxiedWindow(this); } -void ProxyWindowBase::createWindow() { - if (this->window != nullptr) return; - this->window = this->createQQuickWindow(); +void ProxyWindowBase::ensureQWindow() { + auto format = QSurfaceFormat::defaultFormat(); + { + // match QtQuick's default format, including env var controls + static const auto useDepth = qEnvironmentVariableIsEmpty("QSG_NO_DEPTH_BUFFER"); + static const auto useStencil = qEnvironmentVariableIsEmpty("QSG_NO_STENCIL_BUFFER"); + static const auto enableDebug = qEnvironmentVariableIsSet("QSG_OPENGL_DEBUG"); + static const auto disableVSync = qEnvironmentVariableIsSet("QSG_NO_VSYNC"); + + if (useDepth && format.depthBufferSize() == -1) format.setDepthBufferSize(24); + else if (!useDepth) format.setDepthBufferSize(0); + + if (useStencil && format.stencilBufferSize() == -1) format.setStencilBufferSize(8); + else if (!useStencil) format.setStencilBufferSize(0); + + auto opaque = this->qsSurfaceFormat.opaqueModified ? this->qsSurfaceFormat.opaque + : this->mColor.alpha() >= 255; + + if (opaque) format.setAlphaBufferSize(0); + else format.setAlphaBufferSize(8); + + if (enableDebug) format.setOption(QSurfaceFormat::DebugContext); + if (disableVSync) format.setSwapInterval(0); + + format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); + format.setRedBufferSize(8); + format.setGreenBufferSize(8); + format.setBlueBufferSize(8); + } + + this->mSurfaceFormat = format; + + auto useOldWindow = this->window != nullptr; + + if (useOldWindow) { + if (this->window->requestedFormat() != format) { + useOldWindow = false; + } + } + + if (useOldWindow) return; + delete this->window; + this->window = this->createQQuickWindow(); + this->window->setFormat(format); +} + +void ProxyWindowBase::createWindow() { + this->ensureQWindow(); this->connectWindow(); this->completeWindow(); emit this->windowConnected(); @@ -320,6 +368,8 @@ void ProxyWindowBase::setColor(QColor color) { ); this->window->setColor(premultiplied); + // setColor also modifies the alpha buffer size of the surface format + this->window->setFormat(this->mSurfaceFormat); } } @@ -343,6 +393,17 @@ void ProxyWindowBase::setMask(PendingRegion* mask) { emit this->maskChanged(); } +void ProxyWindowBase::setSurfaceFormat(QsSurfaceFormat format) { + if (format == this->qsSurfaceFormat) return; + if (this->window != nullptr) { + qmlWarning(this) << "Cannot set window surface format."; + return; + } + + this->qsSurfaceFormat = format; + emit this->surfaceFormatChanged(); +} + void ProxyWindowBase::onMaskChanged() { if (this->window != nullptr) this->updateMask(); } diff --git a/src/window/proxywindow.hpp b/src/window/proxywindow.hpp index 14c90339..2ed4bcd3 100644 --- a/src/window/proxywindow.hpp +++ b/src/window/proxywindow.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ class ProxiedWindow; /// [FloatingWindow]: ../floatingwindow class ProxyWindowBase: public Reloadable { Q_OBJECT; + // clang-format off /// The QtQuick window backing this window. /// /// > [!WARNING] Do not expect values set via this property to work correctly. @@ -46,7 +48,9 @@ class ProxyWindowBase: public Reloadable { Q_PROPERTY(PendingRegion* mask READ mask WRITE setMask NOTIFY maskChanged); Q_PROPERTY(QObject* windowTransform READ windowTransform NOTIFY windowTransformChanged); Q_PROPERTY(bool backingWindowVisible READ isVisibleDirect NOTIFY backerVisibilityChanged); + Q_PROPERTY(QsSurfaceFormat surfaceFormat READ surfaceFormat WRITE setSurfaceFormat NOTIFY surfaceFormatChanged); Q_PROPERTY(QQmlListProperty data READ data); + // clang-format on Q_CLASSINFO("DefaultProperty", "data"); public: @@ -59,6 +63,7 @@ public: void operator=(ProxyWindowBase&&) = delete; void onReload(QObject* oldInstance) override; + void ensureQWindow(); void createWindow(); void deleteWindow(bool keepItemOwnership = false); @@ -98,6 +103,9 @@ public: [[nodiscard]] PendingRegion* mask() const; virtual void setMask(PendingRegion* mask); + [[nodiscard]] QsSurfaceFormat surfaceFormat() const { return this->qsSurfaceFormat; } + void setSurfaceFormat(QsSurfaceFormat format); + [[nodiscard]] QObject* windowTransform() const { return nullptr; } // NOLINT [[nodiscard]] QQmlListProperty data(); @@ -115,6 +123,7 @@ signals: void screenChanged(); void colorChanged(); void maskChanged(); + void surfaceFormatChanged(); protected slots: virtual void onWidthChanged(); @@ -135,6 +144,8 @@ protected: QQuickItem* mContentItem = nullptr; bool reloadComplete = false; bool ranLints = false; + QsSurfaceFormat qsSurfaceFormat; + QSurfaceFormat mSurfaceFormat; private: void polishItems(); diff --git a/src/window/windowinterface.hpp b/src/window/windowinterface.hpp index c969a21d..8603de31 100644 --- a/src/window/windowinterface.hpp +++ b/src/window/windowinterface.hpp @@ -15,6 +15,26 @@ class ProxyWindowBase; class QsWindowAttached; +class QsSurfaceFormat { + Q_GADGET; + QML_VALUE_TYPE(surfaceFormat); + QML_STRUCTURED_VALUE; + Q_PROPERTY(bool opaque MEMBER opaque WRITE setOpaque); + +public: + bool opaque = false; + bool opaqueModified = false; + + void setOpaque(bool opaque) { + this->opaque = opaque; + this->opaqueModified = true; + } + + [[nodiscard]] bool operator==(const QsSurfaceFormat& other) const { + return other.opaqueModified == this->opaqueModified && other.opaque == this->opaque; + } +}; + ///! Base class of Quickshell windows /// Base class of Quickshell windows /// ### Attached properties @@ -46,6 +66,10 @@ class WindowInterface: public Reloadable { /// along with map[To|From]Item (which is not reactive). Q_PROPERTY(QObject* windowTransform READ windowTransform NOTIFY windowTransformChanged); /// The background color of the window. Defaults to white. + /// + /// > [!WARNING] If the window color is opaque before it is made visible, + /// > it will not be able to become transparent later unless @@surfaceFormat$.opaque + /// > is false. Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged); /// The clickthrough mask. Defaults to null. /// @@ -90,6 +114,16 @@ class WindowInterface: public Reloadable { /// } /// ``` Q_PROPERTY(PendingRegion* mask READ mask WRITE setMask NOTIFY maskChanged); + /// Set the surface format to request from the system. + /// + /// - `opaque` - If the requested surface should be opaque. Opaque windows allow + /// the operating system to avoid drawing things behind them, or blending the window + /// with those behind it, saving power and GPU load. If unset, this property defaults to + /// true if @@color is opaque, or false if not. *You should not need to modify this + /// property unless you create a surface that starts opaque and later becomes transparent.* + /// + /// > [!NOTE] The surface format cannot be changed after the window is created. + Q_PROPERTY(QsSurfaceFormat surfaceFormat READ surfaceFormat WRITE setSurfaceFormat NOTIFY surfaceFormatChanged); Q_PROPERTY(QQmlListProperty data READ data); // clang-format on Q_CLASSINFO("DefaultProperty", "data"); @@ -124,6 +158,9 @@ public: [[nodiscard]] virtual PendingRegion* mask() const = 0; virtual void setMask(PendingRegion* mask) = 0; + [[nodiscard]] virtual QsSurfaceFormat surfaceFormat() const = 0; + virtual void setSurfaceFormat(QsSurfaceFormat format) = 0; + [[nodiscard]] virtual QQmlListProperty data() = 0; static QsWindowAttached* qmlAttachedProperties(QObject* object); @@ -138,6 +175,7 @@ signals: void windowTransformChanged(); void colorChanged(); void maskChanged(); + void surfaceFormatChanged(); }; class QsWindowAttached: public QObject { diff --git a/src/x11/panel_window.cpp b/src/x11/panel_window.cpp index c133abd3..bda4aa54 100644 --- a/src/x11/panel_window.cpp +++ b/src/x11/panel_window.cpp @@ -482,6 +482,7 @@ XPanelInterface::XPanelInterface(QObject* parent) QObject::connect(this->panel, &ProxyWindowBase::windowTransformChanged, this, &XPanelInterface::windowTransformChanged); QObject::connect(this->panel, &ProxyWindowBase::colorChanged, this, &XPanelInterface::colorChanged); QObject::connect(this->panel, &ProxyWindowBase::maskChanged, this, &XPanelInterface::maskChanged); + QObject::connect(this->panel, &ProxyWindowBase::surfaceFormatChanged, this, &XPanelInterface::surfaceFormatChanged); // panel specific QObject::connect(this->panel, &XPanelWindow::anchorsChanged, this, &XPanelInterface::anchorsChanged); @@ -516,6 +517,7 @@ proxyPair(qint32, height, setHeight); proxyPair(QuickshellScreenInfo*, screen, setScreen); proxyPair(QColor, color, setColor); proxyPair(PendingRegion*, mask, setMask); +proxyPair(QsSurfaceFormat, surfaceFormat, setSurfaceFormat); // panel specific proxyPair(Anchors, anchors, setAnchors); diff --git a/src/x11/panel_window.hpp b/src/x11/panel_window.hpp index b37c9c50..12645589 100644 --- a/src/x11/panel_window.hpp +++ b/src/x11/panel_window.hpp @@ -136,6 +136,9 @@ public: [[nodiscard]] PendingRegion* mask() const override; void setMask(PendingRegion* mask) override; + [[nodiscard]] QsSurfaceFormat surfaceFormat() const override; + void setSurfaceFormat(QsSurfaceFormat mask) override; + [[nodiscard]] QQmlListProperty data() override; // panel specific