From e80f8486a6e21fb169ffcb0fae6270b798fd03e6 Mon Sep 17 00:00:00 2001 From: luhongxu Date: Wed, 16 Sep 2026 15:39:36 +0800 Subject: [PATCH 1/3] feat: add ext-background-effect-v1 protocol support Add ext-background-effect-v1 (blur) protocol server-side support: - Vendored wlroots C implementation in waylib/src/server/wlroots_extra/ based on wlroots MR 5304 (wlr_surface_synced + wlr_addon pattern) - Qt wrapper WBackgroundEffectManagerV1 in waylib/src/server/protocols/ - Integration in Helper: register manager, hook blur state into SurfaceWrapper via setBlur() on surface commit - Delete the obsolete test_window_bg example and add test_window_blur PMS: TASK-395091 --- REUSE.toml | 8 + examples/CMakeLists.txt | 2 +- examples/test_window_bg/main.cpp | 105 -------- .../CMakeLists.txt | 7 +- examples/test_window_blur/main.cpp | 201 ++++++++++++++ src/seat/helper.cpp | 10 + src/seat/helper.h | 2 + src/surface/surfacewrapper.cpp | 13 + src/surface/surfacewrapper.h | 4 + waylib/src/server/CMakeLists.txt | 16 ++ waylib/src/server/kernel/wlr_all.h | 1 + waylib/src/server/kernel/wlr_fwd.h | 2 + .../protocols/WBackgroundEffectManagerV1 | 1 + .../protocols/wbackgroundeffectmanagerv1.cpp | 64 +++++ .../protocols/wbackgroundeffectmanagerv1.h | 33 +++ .../wlr_ext_background_effect_v1.c | 245 ++++++++++++++++++ .../wlr_ext_background_effect_v1.h | 49 ++++ 17 files changed, 654 insertions(+), 109 deletions(-) delete mode 100644 examples/test_window_bg/main.cpp rename examples/{test_window_bg => test_window_blur}/CMakeLists.txt (70%) create mode 100644 examples/test_window_blur/main.cpp create mode 100644 waylib/src/server/protocols/WBackgroundEffectManagerV1 create mode 100644 waylib/src/server/protocols/wbackgroundeffectmanagerv1.cpp create mode 100644 waylib/src/server/protocols/wbackgroundeffectmanagerv1.h create mode 100644 waylib/src/server/wlroots_extra/wlr_ext_background_effect_v1.c create mode 100644 waylib/src/server/wlroots_extra/wlr_ext_background_effect_v1.h diff --git a/REUSE.toml b/REUSE.toml index 4732de82e4..503d755fb5 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -211,6 +211,7 @@ path = [ "waylib/src/server/protocols/WSessionLockManager", "waylib/src/server/protocols/WSessionLockSurface", "waylib/src/server/protocols/WPointerConstraintsV1", + "waylib/src/server/protocols/WBackgroundEffectManagerV1", ] precedence = "aggregate" SPDX-FileCopyrightText = "UnionTech Software Technology Co., Ltd." @@ -232,3 +233,10 @@ path = ["waylib/src/server/protocols/private/text-input-unstable-v2.xml"] precedence = "aggregate" SPDX-FileCopyrightText = "None" SPDX-License-Identifier = "HPND" + +# Vendored wlroots extra files for waylib (MIT, upstream wlroots) +[[annotations]] +path = ["waylib/src/server/wlroots_extra/**"] +precedence = "aggregate" +SPDX-FileCopyrightText = "wlroots contributors" +SPDX-License-Identifier = "MIT" diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ce8ceb8148..e13e5fb153 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,6 +1,6 @@ set(MODULE_PATH ${PROJECT_SOURCE_DIR}/src/modules) -add_subdirectory(test_window_bg) +add_subdirectory(test_window_blur) add_subdirectory(test_wallpaper_color) add_subdirectory(test_show_desktop) add_subdirectory(test_xdgport_wallpaper) diff --git a/examples/test_window_bg/main.cpp b/examples/test_window_bg/main.cpp deleted file mode 100644 index 4a0d45c79f..0000000000 --- a/examples/test_window_bg/main.cpp +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (C) 2024-2026 UnionTech Software Technology Co., Ltd. -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "qwayland-treeland-personalization-manager-v1.h" - -#include - -#include -#include -#include - -class PersonalizationManager - : public QWaylandClientExtensionTemplate - , public QtWayland::treeland_personalization_manager_v1 -{ - Q_OBJECT -public: - explicit PersonalizationManager(); -}; - -PersonalizationManager::PersonalizationManager() - : QWaylandClientExtensionTemplate(1) -{ -} - -class PersonalizationWindow - : public QWaylandClientExtensionTemplate - , public QtWayland::treeland_personalization_window_context_v1 -{ - Q_OBJECT -public: - explicit PersonalizationWindow(struct ::treeland_personalization_window_context_v1 *object); -}; - -PersonalizationWindow::PersonalizationWindow( - struct ::treeland_personalization_window_context_v1 *object) - : QWaylandClientExtensionTemplate(1) - , QtWayland::treeland_personalization_window_context_v1(object) -{ -} - -int main(int argc, char *argv[]) -{ - QApplication app(argc, argv); - PersonalizationManager manager; - - QObject::connect(&manager, &PersonalizationManager::activeChanged, &manager, [&manager] { - qDebug() << "personalzation manager" << manager.isActive(); - - if (manager.isActive()) { - QWidget *widget = new QWidget; - widget->setAttribute(Qt::WA_TranslucentBackground); - // widget->setWindowFlags(Qt::FramelessWindowHint); // - // 可选,去除窗口边框 - widget->resize(640, 480); - - QPushButton *button = new QPushButton("Change background", widget); - button->setGeometry(0, 0, 100, 50); // 设置按钮的位置和大小 - - QPushButton *titlebar = new QPushButton("Change titlebar", widget); - titlebar->setGeometry(0, 55, 100, 50); // 设置按钮的位置和大小 - - widget->show(); - - QWindow *window = widget->windowHandle(); - - if (window && window->handle()) { - QtWaylandClient::QWaylandWindow *waylandWindow = - static_cast(window->handle()); - - struct wl_surface *surface = waylandWindow->wlSurface(); - if (surface) { - PersonalizationWindow *context = - new PersonalizationWindow(manager.get_window_context(surface)); - - QObject::connect(button, &QPushButton::clicked, [context]() { - static int state = PersonalizationWindow::blend_mode_transparent; - if (state > PersonalizationWindow::blend_mode_blur) { - state = 0; - } - context->set_blend_mode(state); - qDebug() << "===========background state: ==========" << state; - state++; - }); - - QObject::connect(titlebar, &QPushButton::clicked, [context]() { - static bool disableTitlebar = false; - if (disableTitlebar) { - context->set_titlebar( - TREELAND_PERSONALIZATION_WINDOW_CONTEXT_V1_ENABLE_MODE_DISABLE); - } else { - context->set_titlebar( - TREELAND_PERSONALIZATION_WINDOW_CONTEXT_V1_ENABLE_MODE_ENABLE); - } - disableTitlebar = !disableTitlebar; - }); - } - } - } - }); - - return app.exec(); -} - -#include "main.moc" diff --git a/examples/test_window_bg/CMakeLists.txt b/examples/test_window_blur/CMakeLists.txt similarity index 70% rename from examples/test_window_bg/CMakeLists.txt rename to examples/test_window_blur/CMakeLists.txt index 53e4120915..5047025d41 100644 --- a/examples/test_window_bg/CMakeLists.txt +++ b/examples/test_window_blur/CMakeLists.txt @@ -2,9 +2,10 @@ find_package(Qt6 REQUIRED COMPONENTS Gui WaylandClient Widgets) if(Qt6_VERSION VERSION_GREATER_EQUAL 6.10) find_package(Qt6 REQUIRED COMPONENTS GuiPrivate WaylandClientPrivate) endif() -find_package(TreelandProtocols REQUIRED) +find_package(PkgConfig REQUIRED) +pkg_get_variable(WAYLAND_PROTOCOLS_DATADIR wayland-protocols pkgdatadir) -set(BIN_NAME test-window-bg) +set(BIN_NAME test-window-blur) qt_add_executable(${BIN_NAME} main.cpp @@ -12,7 +13,7 @@ qt_add_executable(${BIN_NAME} qt_generate_wayland_protocol_client_sources(${BIN_NAME} FILES - ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-personalization-manager-v1.xml + ${WAYLAND_PROTOCOLS_DATADIR}/staging/ext-background-effect/ext-background-effect-v1.xml ) target_link_libraries(${BIN_NAME} diff --git a/examples/test_window_blur/main.cpp b/examples/test_window_blur/main.cpp new file mode 100644 index 0000000000..a5d1d9d902 --- /dev/null +++ b/examples/test_window_blur/main.cpp @@ -0,0 +1,201 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// This demo is modeled after quickshell's background_effect implementation: +// https://github.com/quickshell-mirror/quickshell/tree/master/src/wayland/background_effect +// (surface lifecycle handling, capability checks, and blur region management +// follow the same approach used there.) + +#include "qwayland-ext-background-effect-v1.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +// Modeled after quickshell's qs::wayland::background_effect implementation: +// https://github.com/quickshell-mirror/quickshell/tree/master/src/wayland/background_effect +class BackgroundEffectManager + : public QWaylandClientExtensionTemplate + , public QtWayland::ext_background_effect_manager_v1 +{ + Q_OBJECT +public: + explicit BackgroundEffectManager() + : QWaylandClientExtensionTemplate(1) + { + initialize(); + } + + bool blurAvailable() const { return isActive() && m_blurAvailable; } + +protected: + void ext_background_effect_manager_v1_capabilities(uint32_t flags) override + { + m_blurAvailable = flags & capability_blur; + } + +private: + bool m_blurAvailable = false; +}; + +class BlurWindow : public QWidget +{ + Q_OBJECT +public: + explicit BlurWindow() + : m_manager(new BackgroundEffectManager) + { + setWindowTitle("Window Blur Test"); + // The window itself must be translucent so that the background + // behind it can be seen and blurred. + setAttribute(Qt::WA_TranslucentBackground); + resize(640, 480); + + auto *layout = new QVBoxLayout(this); + + auto *statusLabel = new QLabel("Blur region: off", this); + layout->addWidget(statusLabel); + + auto *toggleButton = new QPushButton("Toggle blur region", this); + layout->addWidget(toggleButton); + connect(toggleButton, &QPushButton::clicked, this, [this, statusLabel] { + m_blurEnabled = !m_blurEnabled; + applyBlurRegion(); + statusLabel->setText(m_blurEnabled ? "Blur region: on" : "Blur region: off"); + }); + } + + ~BlurWindow() override + { + // m_effect is destroyed in eventFilter() on SurfaceAboutToBeDestroyed, + // which Qt delivers before the underlying wl_surface goes away. + } + +protected: + bool eventFilter(QObject *watched, QEvent *event) override + { + if (event->type() == QEvent::PlatformSurface) { + auto *surfaceEvent = static_cast(event); + switch (surfaceEvent->surfaceEventType()) { + case QPlatformSurfaceEvent::SurfaceCreated: + createEffectSurface(); + break; + case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed: + // Destroy the ext_background_effect_surface_v1 before the + // wl_surface, otherwise the compositor raises the + // surface_destroyed protocol error. + destroyEffectSurface(); + break; + } + } + return QWidget::eventFilter(watched, event); + } + + void showEvent(QShowEvent *event) override + { + QWidget::showEvent(event); + if (QWindow *window = windowHandle()) { + if (!window->parent()) { + window->installEventFilter(this); + } + // The platform surface may already be created at this point. + createEffectSurface(); + } + } + + void resizeEvent(QResizeEvent *event) override + { + QWidget::resizeEvent(event); + // The blur region is surface-local state; keep it in sync with the + // window size so it always covers the whole surface. + applyBlurRegion(); + } + +private: + QtWaylandClient::QWaylandWindow *waylandWindow() const + { + QWindow *window = windowHandle(); + if (!window || !window->handle()) + return nullptr; + return static_cast(window->handle()); + } + + void createEffectSurface() + { + if (m_effect) + return; + + auto *waylandWindow = this->waylandWindow(); + if (!waylandWindow || !waylandWindow->surface()) + return; + + if (!m_manager->blurAvailable()) { + qWarning() << "ext-background-effect-v1 blur is not supported by the compositor"; + return; + } + + m_effect = new QtWayland::ext_background_effect_surface_v1( + m_manager->get_background_effect(waylandWindow->surface())); + applyBlurRegion(); + } + + void destroyEffectSurface() + { + if (!m_effect) + return; + + if (m_effect->isInitialized()) + m_effect->destroy(); + delete m_effect; + m_effect = nullptr; + } + + void applyBlurRegion() + { + if (!m_effect || !m_effect->isInitialized()) + return; + + if (!m_blurEnabled) { + // NULL region removes the effect. + m_effect->set_blur_region(nullptr); + return; + } + + auto *waylandWindow = this->waylandWindow(); + auto *display = waylandWindow ? waylandWindow->display() : nullptr; + if (!display) + return; + + // Full-surface blur, in surface-local (logical) coordinates; the + // compositor clips the region to the surface size. + QRegion region(0, 0, width(), height()); + + struct ::wl_region *wlRegion = display->createRegion(region); + m_effect->set_blur_region(wlRegion); + // set_blur_region has copy semantics, safe to destroy immediately. + wl_region_destroy(wlRegion); + } + + BackgroundEffectManager *m_manager = nullptr; + QtWayland::ext_background_effect_surface_v1 *m_effect = nullptr; + bool m_blurEnabled = false; +}; + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + BlurWindow window; + window.show(); + + return app.exec(); +} + +#include "main.moc" diff --git a/src/seat/helper.cpp b/src/seat/helper.cpp index 4561020192..08f004fe74 100644 --- a/src/seat/helper.cpp +++ b/src/seat/helper.cpp @@ -1708,6 +1708,15 @@ void Helper::onSurfaceWrapperAdded(SurfaceWrapper *wrapper) wrapper->setCoverEnabled(true); } + // ext-background-effect-v1 blur integration + // XWayland surfaces cannot attach ext-background-effect objects; skip the + // per-commit sync for them. Also guard against surfaces not yet created + // (pre-launch splash). + if (!isXwayland && wrapper->surface()) { + wrapper->syncBackgroundEffectBlur(); + connect(wrapper->surface(), &WSurface::commit, wrapper, &SurfaceWrapper::syncBackgroundEffectBlur); + } + if (isXwayland) { auto xwaylandSurface = qobject_cast(wrapper->shellSurface()); auto updateDecorationTitleBar = [wrapper, xwaylandSurface, sessionManager = m_sessionManager]() { @@ -2307,6 +2316,7 @@ void Helper::init(Treeland::Treeland *treeland) m_server->attach(); m_server->attach(); + m_backgroundEffectManagerV1 = m_server->attach(); m_pointerConstraintsV1 = m_server->attach(); m_pointerConstraintsManager = new PointerConstraintsManager(m_pointerConstraintsV1, this); wlr_fractional_scale_manager_v1_create(m_server->handle(), WLR_FRACTIONAL_SCALE_V1_VERSION); diff --git a/src/seat/helper.h b/src/seat/helper.h index 8e860a2452..402c5da69b 100644 --- a/src/seat/helper.h +++ b/src/seat/helper.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -477,6 +478,7 @@ private Q_SLOTS: ShellHandler *m_shellHandler = nullptr; WXdgDecorationManager *m_xdgDecorationManager = nullptr; WXdgDialogManagerV1 *m_xdgDialogManagerV1 = nullptr; + WBackgroundEffectManagerV1 *m_backgroundEffectManagerV1 = nullptr; WXdgToplevelTagManagerV1 *m_xdgToplevelTagManagerV1 = nullptr; WForeignToplevel *m_foreignToplevel = nullptr; WExtForeignToplevelListV1 *m_extForeignToplevelListV1 = nullptr; diff --git a/src/surface/surfacewrapper.cpp b/src/surface/surfacewrapper.cpp index 1c176828dc..2cf941a0bf 100644 --- a/src/surface/surfacewrapper.cpp +++ b/src/surface/surfacewrapper.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -2597,6 +2598,18 @@ void SurfaceWrapper::setBlur(bool blur) Q_EMIT blurChanged(); } +void SurfaceWrapper::syncBackgroundEffectBlur() +{ + auto *wlrSurface = surface() ? surface()->handle() : nullptr; + if (!wlrSurface) { + return; + } + + const auto *state = wlr_ext_background_effect_v1_get_surface_state(wlrSurface); + const bool hasBlur = state && pixman_region32_not_empty(&state->blur_region); + setBlur(hasBlur); +} + bool SurfaceWrapper::coverEnabled() const { return m_coverContent; diff --git a/src/surface/surfacewrapper.h b/src/surface/surfacewrapper.h index ab30ddd61b..e30a4b0ad1 100644 --- a/src/surface/surfacewrapper.h +++ b/src/surface/surfacewrapper.h @@ -327,6 +327,10 @@ class SurfaceWrapper : public QQuickItem bool blur() const; void setBlur(bool blur); + // Sync the blur state from the ext-background-effect-v1 protocol surface + // state (a non-empty blur region means the surface should be blurred). + void syncBackgroundEffectBlur(); + bool coverEnabled() const; void setCoverEnabled(bool enabled); diff --git a/waylib/src/server/CMakeLists.txt b/waylib/src/server/CMakeLists.txt index e5d0b24d67..71ebb6992d 100644 --- a/waylib/src/server/CMakeLists.txt +++ b/waylib/src/server/CMakeLists.txt @@ -109,6 +109,13 @@ ws_generate( xdg-shell-protocol ) +ws_generate( + server + wayland-protocols + staging/ext-background-effect/ext-background-effect-v1.xml + ext-background-effect-v1-protocol +) + set(SOURCES wayliblogging.cpp @@ -168,6 +175,7 @@ set(SOURCES ${WAYLAND_PROTOCOLS_OUTPUTDIR}/xdg-output-unstable-v1-protocol.c ${WAYLAND_PROTOCOLS_OUTPUTDIR}/security-context-v1-protocol.c ${WAYLAND_PROTOCOLS_OUTPUTDIR}/xdg-shell-protocol.c + ${WAYLAND_PROTOCOLS_OUTPUTDIR}/ext-background-effect-v1-protocol.c utils/wtools.cpp utils/wthreadutils.cpp @@ -213,6 +221,9 @@ set(SOURCES protocols/wsessionlock.cpp protocols/wsessionlocksurface.cpp protocols/wremotesubsurfacemanagerv1.cpp + protocols/wbackgroundeffectmanagerv1.cpp + + wlroots_extra/wlr_ext_background_effect_v1.c ) set(HEADERS @@ -333,6 +344,9 @@ set(HEADERS protocols/WSessionLock protocols/wsessionlocksurface.h protocols/WSessionLockSurface + protocols/wbackgroundeffectmanagerv1.h + protocols/WBackgroundEffectManagerV1 + wlroots_extra/wlr_ext_background_effect_v1.h ) set(PRIVATE_HEADERS @@ -361,6 +375,7 @@ set(PRIVATE_HEADERS ${WAYLAND_PROTOCOLS_OUTPUTDIR}/text-input-unstable-v2-protocol.h ${WAYLAND_PROTOCOLS_OUTPUTDIR}/xdg-output-unstable-v1-protocol.h ${WAYLAND_PROTOCOLS_OUTPUTDIR}/xdg-shell-protocol.h + ${WAYLAND_PROTOCOLS_OUTPUTDIR}/ext-background-effect-v1-protocol.h protocols/private/winputmethodv2_p.h protocols/private/wtextinput_p.h @@ -463,6 +478,7 @@ target_include_directories(${TARGET} $ $ ${WAYLIB_WLROOTS_INCLUDE_DIRS} + $ $ PRIVATE $ diff --git a/waylib/src/server/kernel/wlr_all.h b/waylib/src/server/kernel/wlr_all.h index b9b559ef54..6a5efaf78b 100644 --- a/waylib/src/server/kernel/wlr_all.h +++ b/waylib/src/server/kernel/wlr_all.h @@ -78,6 +78,7 @@ extern "C" { #include #include #include +#include #include #include #include diff --git a/waylib/src/server/kernel/wlr_fwd.h b/waylib/src/server/kernel/wlr_fwd.h index 9ed5bf7408..66acacafda 100644 --- a/waylib/src/server/kernel/wlr_fwd.h +++ b/waylib/src/server/kernel/wlr_fwd.h @@ -44,6 +44,8 @@ struct wlr_drm_connector; struct wlr_drm_format; struct wlr_drm_format_set; struct wlr_export_dmabuf_manager_v1; +struct wlr_ext_background_effect_manager_v1; +struct wlr_ext_background_effect_surface_v1_state; struct wlr_ext_foreign_toplevel_handle_v1; struct wlr_ext_foreign_toplevel_handle_v1_state; struct wlr_ext_foreign_toplevel_list_v1; diff --git a/waylib/src/server/protocols/WBackgroundEffectManagerV1 b/waylib/src/server/protocols/WBackgroundEffectManagerV1 new file mode 100644 index 0000000000..7c56424efe --- /dev/null +++ b/waylib/src/server/protocols/WBackgroundEffectManagerV1 @@ -0,0 +1 @@ +#include "wbackgroundeffectmanagerv1.h" diff --git a/waylib/src/server/protocols/wbackgroundeffectmanagerv1.cpp b/waylib/src/server/protocols/wbackgroundeffectmanagerv1.cpp new file mode 100644 index 0000000000..6439321979 --- /dev/null +++ b/waylib/src/server/protocols/wbackgroundeffectmanagerv1.cpp @@ -0,0 +1,64 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "wbackgroundeffectmanagerv1.h" +#include "private/wglobal_p.h" + +#include + +WAYLIB_SERVER_BEGIN_NAMESPACE + +class Q_DECL_HIDDEN WBackgroundEffectManagerV1Private : public WObjectPrivate +{ +public: + WBackgroundEffectManagerV1Private(WBackgroundEffectManagerV1 *qq) + : WObjectPrivate(qq) + { + } + + inline wlr_ext_background_effect_manager_v1 *handle() const { + return reinterpret_cast(q_func()->m_handle); + } + + W_DECLARE_PUBLIC(WBackgroundEffectManagerV1) +}; + +WBackgroundEffectManagerV1::WBackgroundEffectManagerV1() + : WObject(*new WBackgroundEffectManagerV1Private(this)) +{ +} + +wlr_ext_background_effect_manager_v1 *WBackgroundEffectManagerV1::handle() const +{ + return reinterpret_cast(m_handle); +} + +QByteArrayView WBackgroundEffectManagerV1::interfaceName() const +{ + return "ext_background_effect_manager_v1"; +} + +void WBackgroundEffectManagerV1::create(WServer *server) +{ + W_D(WBackgroundEffectManagerV1); + if (!m_handle) { + m_handle = wlr_ext_background_effect_manager_v1_create( + server->handle(), InterfaceVersion, 1); + } +} + +void WBackgroundEffectManagerV1::destroy(WServer *) +{ + m_handle = nullptr; +} + +wl_global *WBackgroundEffectManagerV1::global() const +{ + W_D(const WBackgroundEffectManagerV1); + if (m_handle) + return d->handle()->global; + + return nullptr; +} + +WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/protocols/wbackgroundeffectmanagerv1.h b/waylib/src/server/protocols/wbackgroundeffectmanagerv1.h new file mode 100644 index 0000000000..a64088e36a --- /dev/null +++ b/waylib/src/server/protocols/wbackgroundeffectmanagerv1.h @@ -0,0 +1,33 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include +#include + +#include + +WAYLIB_SERVER_BEGIN_NAMESPACE + +class WBackgroundEffectManagerV1Private; +class WAYLIB_SERVER_EXPORT WBackgroundEffectManagerV1 : public QObject, public WObject, public WServerInterface +{ + Q_OBJECT + W_DECLARE_PRIVATE(WBackgroundEffectManagerV1) + +public: + explicit WBackgroundEffectManagerV1(); + + wlr_ext_background_effect_manager_v1 *handle() const; + + QByteArrayView interfaceName() const override; + static constexpr int InterfaceVersion = 1; + +protected: + void create(WServer *server) override; + void destroy(WServer *server) override; + wl_global *global() const override; +}; + +WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/wlroots_extra/wlr_ext_background_effect_v1.c b/waylib/src/server/wlroots_extra/wlr_ext_background_effect_v1.c new file mode 100644 index 0000000000..bb67c80a7e --- /dev/null +++ b/waylib/src/server/wlroots_extra/wlr_ext_background_effect_v1.c @@ -0,0 +1,245 @@ +#include +#include +#include "wlr_ext_background_effect_v1.h" +#include +#include +#include "ext-background-effect-v1-protocol.h" + +#define BACKGROUND_EFFECT_VERSION 1 + +struct wlr_ext_background_effect_surface_v1 { + struct wl_resource *resource; + struct wlr_surface *surface; + struct wlr_addon addon; + struct wlr_surface_synced synced; + struct wlr_ext_background_effect_surface_v1_state pending, current; +}; + +static const struct ext_background_effect_surface_v1_interface surface_impl; +static const struct ext_background_effect_manager_v1_interface manager_impl; + +static struct wlr_ext_background_effect_surface_v1 *surface_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_background_effect_surface_v1_interface, &surface_impl)); + + return wl_resource_get_user_data(resource); +} + +static void surface_destroy(struct wlr_ext_background_effect_surface_v1 *surface) { + if (surface == NULL) { + return; + } + + wlr_surface_synced_finish(&surface->synced); + wlr_addon_finish(&surface->addon); + wl_resource_set_user_data(surface->resource, NULL); + free(surface); +} + +static void surface_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_ext_background_effect_surface_v1 *surface = surface_from_resource(resource); + surface_destroy(surface); +} + +static void surface_handle_destroy(struct wl_client *client, struct wl_resource *resource) { + (void)client; + wl_resource_destroy(resource); +} + +static void surface_handle_set_blur_region(struct wl_client *client, struct wl_resource *resource, + struct wl_resource *region_resource) { + (void)client; + struct wlr_ext_background_effect_surface_v1 *surface = surface_from_resource(resource); + + if (surface == NULL) { + wl_resource_post_error(resource, + EXT_BACKGROUND_EFFECT_SURFACE_V1_ERROR_SURFACE_DESTROYED, + "The wl_surface object has been destroyed"); + return; + } + + if (region_resource != NULL) { + const pixman_region32_t *region = wlr_region_from_resource(region_resource); + pixman_region32_copy(&surface->pending.blur_region, region); + } else { + pixman_region32_clear(&surface->pending.blur_region); + } +} + +static const struct ext_background_effect_surface_v1_interface surface_impl = { + .destroy = surface_handle_destroy, + .set_blur_region = surface_handle_set_blur_region, +}; + +static void surface_synced_init_state(void *_state) { + struct wlr_ext_background_effect_surface_v1_state *state = _state; + pixman_region32_init(&state->blur_region); +} + +static void surface_synced_finish_state(void *_state) { + struct wlr_ext_background_effect_surface_v1_state *state = _state; + pixman_region32_fini(&state->blur_region); +} + +static void surface_synced_move_state(void *_dst, void *_src) { + struct wlr_ext_background_effect_surface_v1_state *dst = _dst; + struct wlr_ext_background_effect_surface_v1_state *src = _src; + + pixman_region32_copy(&dst->blur_region, &src->blur_region); +} + +static const struct wlr_surface_synced_impl surface_synced_impl = { + .state_size = sizeof(struct wlr_ext_background_effect_surface_v1_state), + .init_state = surface_synced_init_state, + .finish_state = surface_synced_finish_state, + .move_state = surface_synced_move_state, +}; + +static void surface_addon_destroy(struct wlr_addon *addon) { + struct wlr_ext_background_effect_surface_v1 *surface = wl_container_of(addon, surface, addon); + + surface_destroy(surface); +} + +static const struct wlr_addon_interface surface_addon_impl = { + .name = "ext_background_effect_surface_v1", + .destroy = surface_addon_destroy, +}; + +static struct wlr_ext_background_effect_surface_v1 *surface_from_wlr_surface( + struct wlr_surface *wlr_surface) { + struct wlr_addon *addon = wlr_addon_find(&wlr_surface->addons, NULL, &surface_addon_impl); + if (addon == NULL) { + return NULL; + } + + struct wlr_ext_background_effect_surface_v1 *surface = + wl_container_of(addon, surface, addon); + + return surface; +} + +static void manager_handle_destroy(struct wl_client *client, struct wl_resource *resource) { + (void)client; + wl_resource_destroy(resource); +} + +static void manager_handle_get_background_effect(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, struct wl_resource *surface_resource) { + struct wlr_surface *wlr_surface = wlr_surface_from_resource(surface_resource); + + if (surface_from_wlr_surface(wlr_surface) != NULL) { + wl_resource_post_error(manager_resource, + EXT_BACKGROUND_EFFECT_MANAGER_V1_ERROR_BACKGROUND_EFFECT_EXISTS, + "The wl_surface object already has a ext_background_effect_surface_v1 object"); + return; + } + + struct wlr_ext_background_effect_surface_v1 *surface = calloc(1, sizeof(*surface)); + if (surface == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + if (!wlr_surface_synced_init(&surface->synced, wlr_surface, &surface_synced_impl, + &surface->pending, &surface->current)) { + free(surface); + wl_resource_post_no_memory(manager_resource); + return; + } + + uint32_t version = wl_resource_get_version(manager_resource); + surface->resource = + wl_resource_create(client, &ext_background_effect_surface_v1_interface, version, id); + + if (surface->resource == NULL) { + wlr_surface_synced_finish(&surface->synced); + free(surface); + wl_resource_post_no_memory(manager_resource); + return; + } + + wl_resource_set_implementation(surface->resource, &surface_impl, surface, + surface_handle_resource_destroy); + + surface->surface = wlr_surface; + wlr_addon_init(&surface->addon, &wlr_surface->addons, NULL, &surface_addon_impl); +} + +static const struct ext_background_effect_manager_v1_interface manager_impl = { + .destroy = manager_handle_destroy, + .get_background_effect = manager_handle_get_background_effect, +}; + +static void manager_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void manager_bind(struct wl_client *wl_client, void *data, uint32_t version, uint32_t id) { + struct wlr_ext_background_effect_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(wl_client, + &ext_background_effect_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, + manager_handle_resource_destroy); + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); + + ext_background_effect_manager_v1_send_capabilities(resource, manager->capabilities); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + (void)data; + struct wlr_ext_background_effect_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wl_signal_emit_mutable(&manager->events.destroy, NULL); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + + wl_global_destroy(manager->global); + wl_list_remove(&manager->display_destroy.link); + free(manager); +} + +struct wlr_ext_background_effect_manager_v1 *wlr_ext_background_effect_manager_v1_create( + struct wl_display *display, uint32_t version, uint32_t capabilities) { + assert(version <= BACKGROUND_EFFECT_VERSION); + + struct wlr_ext_background_effect_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, &ext_background_effect_manager_v1_interface, version, + manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + manager->capabilities = capabilities; + + wl_signal_init(&manager->events.destroy); + wl_list_init(&manager->resources); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +const struct wlr_ext_background_effect_surface_v1_state * +wlr_ext_background_effect_v1_get_surface_state(struct wlr_surface *wlr_surface) { + struct wlr_ext_background_effect_surface_v1 *surface = + surface_from_wlr_surface(wlr_surface); + + if (surface == NULL) { + return NULL; + } + + return &surface->current; +} diff --git a/waylib/src/server/wlroots_extra/wlr_ext_background_effect_v1.h b/waylib/src/server/wlroots_extra/wlr_ext_background_effect_v1.h new file mode 100644 index 0000000000..560d275af8 --- /dev/null +++ b/waylib/src/server/wlroots_extra/wlr_ext_background_effect_v1.h @@ -0,0 +1,49 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_EXT_BACKGROUND_EFFECT_V1_H +#define WLR_TYPES_WLR_EXT_BACKGROUND_EFFECT_V1_H + +#include +#include + +struct wlr_surface; + +struct wlr_ext_background_effect_surface_v1_state { + pixman_region32_t blur_region; +}; + +struct wlr_ext_background_effect_manager_v1 { + struct wl_global *global; + uint32_t capabilities; // bitmask of enum ext_background_effect_manager_v1_capability + + struct { + struct wl_signal destroy; + } events; + + void *data; + + struct { + struct wl_list resources; // wl_resource_get_link() + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +struct wlr_ext_background_effect_manager_v1 *wlr_ext_background_effect_manager_v1_create( + struct wl_display *display, uint32_t version, uint32_t capabilities); + +/* + * Get the committed background effect state for a surface. + * + * Returns NULL if the client has not attached a background effect object to + * the surface. + */ +const struct wlr_ext_background_effect_surface_v1_state * +wlr_ext_background_effect_v1_get_surface_state(struct wlr_surface *surface); + +#endif From bd102e19d85525dc9a1c789a875ef5632a99b111 Mon Sep 17 00:00:00 2001 From: deepin-wm Date: Thu, 24 Sep 2026 12:52:21 +0800 Subject: [PATCH 2/3] feat(background-effect): wire blur region through to rendering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete the ext-background-effect-v1 data path from protocol state to the actual blur rendering: - waylib: add WBackgroundEffectManagerV1::surfaceBlurRegion(WSurface *) returning the committed blur region as a QRegion (pixman conversion via the existing WTools::fromPixmanRegion) - SurfaceWrapper: add QRegion blurRegion property (+ QML-friendly blurRegionRects); blur() is now true when either the personalization path or a non-empty protocol region requests it; drop syncBackgroundEffectBlur which reached into the wlroots C API and degraded the region to a bool - Helper: sync blur region from the waylib API on surface commit - QML: Blur effect supports the protocol region; the blurred layer is re-drawn clipped to each region rect, and rects touching a window edge inherit the window corner radius (region ∩ rounded shape) - tests: end-to-end protocol test (capabilities, double-buffered region set/keep/NULL/re-enable/destroy, background_effect_exists) and a server-side functional test - waylib: serve an inert xdg_output instead of asserting when get_xdg_output races an output that is not yet registered in the output layout (aborts the whole compositor otherwise) PMS: TASK-395091 --- src/core/qml/Effects/+vulkan/Blur.qml | 3 + src/core/qml/Effects/Blur.qml | 63 ++++- src/core/qml/SurfaceContent.qml | 1 + src/seat/helper.cpp | 9 +- src/surface/surfacewrapper.cpp | 33 ++- src/surface/surfacewrapper.h | 17 +- tests/CMakeLists.txt | 1 + tests/protocols/CMakeLists.txt | 1 + tests/protocols/INDEX.md | 2 + .../ext-background-effect-v1/CMakeLists.txt | 7 + .../ext-background-effect-v1/README.md | 39 ++++ .../ext-background-effect-v1.c | 216 ++++++++++++++++++ .../ext-background-effect-v1.h | 37 +++ .../ext-background-effect-v1/setup.cpp | 58 +++++ .../CMakeLists.txt | 19 ++ .../test_protocol_background-effect/main.cpp | 54 +++++ .../protocols/wbackgroundeffectmanagerv1.cpp | 17 ++ .../protocols/wbackgroundeffectmanagerv1.h | 9 + waylib/src/server/protocols/wxdgoutput.cpp | 9 +- 19 files changed, 578 insertions(+), 17 deletions(-) create mode 100644 tests/protocols/ext-background-effect-v1/CMakeLists.txt create mode 100644 tests/protocols/ext-background-effect-v1/README.md create mode 100644 tests/protocols/ext-background-effect-v1/ext-background-effect-v1.c create mode 100644 tests/protocols/ext-background-effect-v1/ext-background-effect-v1.h create mode 100644 tests/protocols/ext-background-effect-v1/setup.cpp create mode 100644 tests/test_protocol_background-effect/CMakeLists.txt create mode 100644 tests/test_protocol_background-effect/main.cpp diff --git a/src/core/qml/Effects/+vulkan/Blur.qml b/src/core/qml/Effects/+vulkan/Blur.qml index acd34851da..8986f8e698 100644 --- a/src/core/qml/Effects/+vulkan/Blur.qml +++ b/src/core/qml/Effects/+vulkan/Blur.qml @@ -10,6 +10,9 @@ Rectangle { color: Qt.rgba(0.0, 0.0, 0.0, 0.0) property bool radiusEnabled: radius > 0 + // ext-background-effect-v1 blur region rects. The software/vulkan stub has + // no real blur pass; accepted for interface parity with Effects/Blur.qml. + property var regionRects: [] property int blurMax: Helper.config.blurStrength property bool blurEnabled: blurMax > 0 && blurAmount > 0 property real blurAmount: Helper.config.blurAmount diff --git a/src/core/qml/Effects/Blur.qml b/src/core/qml/Effects/Blur.qml index 0b7f369563..a0422cc890 100644 --- a/src/core/qml/Effects/Blur.qml +++ b/src/core/qml/Effects/Blur.qml @@ -13,6 +13,11 @@ RenderBufferBlitter { property real radius: 0 property bool radiusEnabled: radius > 0 + // ext-background-effect-v1 blur region rects (surface-local), empty means + // no protocol region (blur covers the whole surface). + property var regionRects: [] + readonly property bool regionEnabled: regionRects.length > 0 + readonly property bool masked: radiusEnabled || regionEnabled property int blurMax: Helper.config.blurStrength property bool blurEnabled: blurMax > 0 && blurAmount > 0 property real blurAmount: Helper.config.blurAmount @@ -74,9 +79,9 @@ RenderBufferBlitter { MultiEffect { id: blur anchors.fill: parent - layer.enabled: blitter.radiusEnabled - smooth: blitter.radiusEnabled - opacity: blitter.radiusEnabled ? 0 : blitter.opacity + layer.enabled: blitter.masked + smooth: blitter.masked + opacity: blitter.masked ? 0 : blitter.opacity source: blitter.content autoPaddingEnabled: false blurEnabled: blitter.blurEnabled @@ -86,6 +91,56 @@ RenderBufferBlitter { saturation: 0.2 } + // Protocol blur region mask: re-draw the blurred layer clipped to + // each rect of the client-provided region. QRegion rects never + // overlap, so one Shape per rect paints each pixel exactly once. + Item { + id: regionMask + anchors.fill: parent + visible: blitter.regionEnabled + // When the rounded-corner mask below re-draws this item + // (region ∩ rounded window shape), hide the direct render to + // avoid double blending. + opacity: blitter.radiusEnabled ? 0 : 1 + layer.enabled: blitter.regionEnabled + smooth: true + + Repeater { + model: blitter.regionRects + + Shape { + id: regionShape + + required property rect modelData + + anchors.fill: parent + preferredRendererType: Shape.CurveRenderer + ShapePath { + strokeWidth: 0 + fillItem: blur + PathRectangle { + readonly property bool touchLeft: regionShape.modelData.x <= 0 + readonly property bool touchTop: regionShape.modelData.y <= 0 + readonly property bool touchRight: regionShape.modelData.x + regionShape.modelData.width >= blur.width + readonly property bool touchBottom: regionShape.modelData.y + regionShape.modelData.height >= blur.height + + x: regionShape.modelData.x + y: regionShape.modelData.y + width: regionShape.modelData.width + height: regionShape.modelData.height + // A rect touching a window edge inherits the + // window corner radius on that edge (region + // ∩ rounded window shape). + topLeftRadius: touchLeft && touchTop ? blitter.radius : 0 + topRightRadius: touchRight && touchTop ? blitter.radius : 0 + bottomLeftRadius: touchLeft && touchBottom ? blitter.radius : 0 + bottomRightRadius: touchRight && touchBottom ? blitter.radius : 0 + } + } + } + } + } + Loader { x: blur.x y: blur.y @@ -95,7 +150,7 @@ RenderBufferBlitter { preferredRendererType: Shape.CurveRenderer ShapePath { strokeWidth: 0 - fillItem: blur + fillItem: blitter.regionEnabled ? regionMask : blur PathRectangle { width: blur.width height: blur.height diff --git a/src/core/qml/SurfaceContent.qml b/src/core/qml/SurfaceContent.qml index 24325696fb..2cd312ecfb 100644 --- a/src/core/qml/SurfaceContent.qml +++ b/src/core/qml/SurfaceContent.qml @@ -24,6 +24,7 @@ Item { anchors.fill: parent radiusEnabled: cornerRadius > 0 radius: cornerRadius + regionRects: wrapper?.blurRegionRects ?? [] } } diff --git a/src/seat/helper.cpp b/src/seat/helper.cpp index 08f004fe74..7d33ed46c7 100644 --- a/src/seat/helper.cpp +++ b/src/seat/helper.cpp @@ -1713,8 +1713,13 @@ void Helper::onSurfaceWrapperAdded(SurfaceWrapper *wrapper) // per-commit sync for them. Also guard against surfaces not yet created // (pre-launch splash). if (!isXwayland && wrapper->surface()) { - wrapper->syncBackgroundEffectBlur(); - connect(wrapper->surface(), &WSurface::commit, wrapper, &SurfaceWrapper::syncBackgroundEffectBlur); + auto *manager = m_backgroundEffectManagerV1; + auto syncBlurRegion = [manager, wrapper] { + wrapper->setBlurRegion(manager ? manager->surfaceBlurRegion(wrapper->surface()) + : QRegion()); + }; + syncBlurRegion(); + connect(wrapper->surface(), &WSurface::commit, wrapper, syncBlurRegion); } if (isXwayland) { diff --git a/src/surface/surfacewrapper.cpp b/src/surface/surfacewrapper.cpp index 2cf941a0bf..3f1c6bd0a7 100644 --- a/src/surface/surfacewrapper.cpp +++ b/src/surface/surfacewrapper.cpp @@ -2584,7 +2584,9 @@ SurfaceWrapper *SurfaceWrapper::findModal() const bool SurfaceWrapper::blur() const { - return m_blur; + // Blur is enabled when either the personalization path or the + // ext-background-effect-v1 protocol region requests it. + return m_blur || !m_blurRegion.isEmpty(); } void SurfaceWrapper::setBlur(bool blur) @@ -2598,16 +2600,33 @@ void SurfaceWrapper::setBlur(bool blur) Q_EMIT blurChanged(); } -void SurfaceWrapper::syncBackgroundEffectBlur() +QRegion SurfaceWrapper::blurRegion() const { - auto *wlrSurface = surface() ? surface()->handle() : nullptr; - if (!wlrSurface) { + return m_blurRegion; +} + +void SurfaceWrapper::setBlurRegion(const QRegion ®ion) +{ + if (m_blurRegion == region) { return; } - const auto *state = wlr_ext_background_effect_v1_get_surface_state(wlrSurface); - const bool hasBlur = state && pixman_region32_not_empty(&state->blur_region); - setBlur(hasBlur); + const bool blurChangedBefore = blur(); + m_blurRegion = region; + + Q_EMIT blurRegionChanged(); + if (blurChangedBefore != blur()) { + Q_EMIT blurChanged(); + } +} + +QVariantList SurfaceWrapper::blurRegionRects() const +{ + QVariantList rects; + for (const QRect &r : m_blurRegion) { + rects.append(r); + } + return rects; } bool SurfaceWrapper::coverEnabled() const diff --git a/src/surface/surfacewrapper.h b/src/surface/surfacewrapper.h index e30a4b0ad1..893e6e1141 100644 --- a/src/surface/surfacewrapper.h +++ b/src/surface/surfacewrapper.h @@ -11,6 +11,8 @@ #include #include #include +#include +#include Q_MOC_INCLUDE() Q_MOC_INCLUDE() @@ -81,6 +83,9 @@ class SurfaceWrapper : public QQuickItem // through treeland_dde_shell_surface_v1.set_surface_position Q_PROPERTY(QPoint clientRequstPos READ clientRequstPos NOTIFY clientRequstPosChanged FINAL) Q_PROPERTY(bool blur READ blur NOTIFY blurChanged FINAL) + Q_PROPERTY(QRegion blurRegion READ blurRegion NOTIFY blurRegionChanged FINAL) + // QRegion is not a QML value type; expose the region rects for QML bindings. + Q_PROPERTY(QVariantList blurRegionRects READ blurRegionRects NOTIFY blurRegionChanged FINAL) Q_PROPERTY(bool isWindowAnimationRunning READ isWindowAnimationRunning NOTIFY windowAnimationRunningChanged FINAL) Q_PROPERTY(bool coverEnabled READ coverEnabled NOTIFY coverEnabledChanged FINAL) Q_PROPERTY(bool acceptKeyboardFocus READ acceptKeyboardFocus FINAL) @@ -325,11 +330,15 @@ class SurfaceWrapper : public QQuickItem void setClientRequstPos(QPoint pos); bool blur() const; + // Personalization-driven full-window blur (deprecated protocol path). void setBlur(bool blur); - // Sync the blur state from the ext-background-effect-v1 protocol surface - // state (a non-empty blur region means the surface should be blurred). - void syncBackgroundEffectBlur(); + // ext-background-effect-v1 protocol blur region, surface-local + // coordinates. A non-empty region enables blur; blur() is true when + // either this or the personalization path requests it. + QRegion blurRegion() const; + void setBlurRegion(const QRegion ®ion); + QVariantList blurRegionRects() const; bool coverEnabled() const; void setCoverEnabled(bool enabled); @@ -419,6 +428,7 @@ public Q_SLOTS: void autoPlaceYOffsetChanged(); void clientRequstPosChanged(); void blurChanged(); + void blurRegionChanged(); void windowAnimationRunningChanged(); void coverEnabledChanged(); void aboutToBeInvalidated(); @@ -584,6 +594,7 @@ public Q_SLOTS: uint m_hideByLockScreen : 1; uint m_confirmHideByLockScreen : 1; uint m_blur : 1; + QRegion m_blurRegion; uint m_isActivated : 1; uint m_attention : 1; uint m_isIMCandidatePanel : 1; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 845ebfda27..ee06bd2fae 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,6 +11,7 @@ add_subdirectory(test_protocol_shortcut) add_subdirectory(test_protocol_virtual-output) add_subdirectory(test_protocol_wallpaper-color) add_subdirectory(test_protocol_show-desktop) +add_subdirectory(test_protocol_background-effect) add_subdirectory(test_protocol_xwindow-control) add_subdirectory(test_protocol_prelaunch-splash) add_subdirectory(test_protocol_pointerconstraints) diff --git a/tests/protocols/CMakeLists.txt b/tests/protocols/CMakeLists.txt index 6641b559cc..edbb1eaf48 100644 --- a/tests/protocols/CMakeLists.txt +++ b/tests/protocols/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory(drm) add_subdirectory(input-method-unstable-v2) add_subdirectory(virtual-keyboard-unstable-v1) add_subdirectory(wlr-data-control-unstable-v1) +add_subdirectory(ext-background-effect-v1) add_subdirectory(wlr-export-dmabuf-unstable-v1) add_subdirectory(wlr-foreign-toplevel-management-unstable-v1) add_subdirectory(wlr-gamma-control-unstable-v1) diff --git a/tests/protocols/INDEX.md b/tests/protocols/INDEX.md index cbc337b4c8..977c0d9f82 100644 --- a/tests/protocols/INDEX.md +++ b/tests/protocols/INDEX.md @@ -68,6 +68,7 @@ xdg-shell 由共享 xdg-toplevel 夹具覆盖(多个 toplevel 测试复用) | 协议 | 覆盖等级 | 主要生产结果 | | --- | --- | --- | | [wayland-alpha-modifier-v1](wayland-alpha-modifier-v1/README.md) | E | 映射 toplevel + `set_multiplier`,回读真实 `wlr_alpha_modifier_surface_v1_state::multiplier` 与请求值一致 | +| [ext-background-effect-v1](ext-background-effect-v1/README.md) | E / P | bind 下发 blur 能力位;`set_blur_region` 双缓冲流转(set/保持/NULL/重启用/destroy)回读真实 `SurfaceWrapper::blurRegion()`;重复关联触发 `background_effect_exists` | | [wayland-cursor-shape-v1](wayland-cursor-shape-v1/README.md) | E | 真实 `wl_pointer` 上 `set_shape`,回读真实 `wlr_cursor_shape_manager_v1::events.request_set_shape` 捕获的 shape 值与请求一致 | | [wayland-ext-data-control-v1](wayland-ext-data-control-v1/README.md) | E | 创建 offer 并 `set_selection` 后回读真实 `wlr_seat::selection_source` 非空 | | [wayland-ext-foreign-toplevel-list-v1](wayland-ext-foreign-toplevel-list-v1/README.md) | E | 映射 toplevel 后回读真实 `wlr_ext_foreign_toplevel_list_v1::toplevels` 列表非空 | @@ -161,6 +162,7 @@ request stub 算作 request 覆盖;生成的 client-protocol 文件本身不 | 协议 | E 级回读的生产对象 | | --- | --- | | `alpha-modifier-v1` | `wlr_alpha_modifier_surface_v1_state::multiplier` | +| `ext-background-effect-v1` | `SurfaceWrapper::blurRegion()` 与客户端设置的 region 一致(含双缓冲保持/NULL 移除/destroy 清除) | | `cursor-shape-v1` | `wlr_cursor_shape_manager_v1::events.request_set_shape` 捕获的 shape | | `ext-data-control-v1` | `wlr_seat::selection_source` 非空 | | `ext-foreign-toplevel-list-v1` | `wlr_ext_foreign_toplevel_list_v1::toplevels` 列表非空 | diff --git a/tests/protocols/ext-background-effect-v1/CMakeLists.txt b/tests/protocols/ext-background-effect-v1/CMakeLists.txt new file mode 100644 index 0000000000..0f90e73d2e --- /dev/null +++ b/tests/protocols/ext-background-effect-v1/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +treeland_add_protocol_test( + NAME ext_background_effect_v1 + XML "${WAYLAND_PROTOCOLS_DATADIR}/staging/ext-background-effect/ext-background-effect-v1.xml" + SETUP "${CMAKE_CURRENT_SOURCE_DIR}/setup.cpp" + CLIENT "${CMAKE_CURRENT_SOURCE_DIR}/ext-background-effect-v1.c" +) diff --git a/tests/protocols/ext-background-effect-v1/README.md b/tests/protocols/ext-background-effect-v1/README.md new file mode 100644 index 0000000000..e5b2a7b058 --- /dev/null +++ b/tests/protocols/ext-background-effect-v1/README.md @@ -0,0 +1,39 @@ +# `ext-background-effect-v1` 测试规范 + +## 范围 + +- XML / interface:`ext_background_effect_manager_v1` / `ext_background_effect_surface_v1`(version 1,staging) +- 测试源码:`tests/protocols/ext-background-effect-v1/` +- 服务端实现:`waylib/src/server/wlroots_extra/wlr_ext_background_effect_v1.c`(vendored,来自 wlroots MR 5304) + + `waylib/src/server/protocols/wbackgroundeffectmanagerv1.{h,cpp}`(`WBackgroundEffectManagerV1::surfaceBlurRegion`) +- Fixture:headless output fixture + xdg-toplevel-client(128×128 solid buffer 映射) +- 覆盖等级:E + +## 必须观察到的结果 + +| 场景 | 客户端动作 | 必须观察到的结果 | 证据层级 | +| --- | --- | --- | --- | +| 绑定全局 | 绑定 `ext_background_effect_manager_v1`(v1) | 收到 `capabilities` 事件且仅含 `blur`(值 1) | P | +| 关联表面 | `get_background_effect(surface)` | 返回非 NULL 的 `ext_background_effect_surface_v1` | P | +| 设置区域 | `set_blur_region(10,10 40x40)` + `commit` | 回读真实 `SurfaceWrapper::blurRegion()` == (10,10 40x40) 且 `blur()` 为 true | E | +| 双缓冲保持 | 不再 `set_blur_region`,直接再 `commit` | 区域仍为 (10,10 40x40)(pending 状态不会在 commit 间被清空) | E | +| NULL 移除 | `set_blur_region(NULL)` + `commit` | 回读区域为空、`blur()` 为 false | E | +| 重新启用 | `set_blur_region(0,0 128x128)` + `commit` | 回读区域 == (0,0 128x128) | E | +| 销毁对象 | `ext_background_effect_surface_v1.destroy` + `commit` | 回读区域为空 | E | +| 重复关联错误 | 对同一 `wl_surface` 再次 `get_background_effect` | 连接收到 `background_effect_exists` 协议错误 | P | + +## 生产结果 + +测试绑定真实的 `ext_background_effect_manager_v1` 全局并校验 bind 时下发的能力位; +映射真实 xdg_toplevel 后附加背景效果对象,依次驱动 set/commit/NULL/重启用/destroy 的 +双缓冲状态流转。每次 commit 后通过 server bridge 回读生产 `SurfaceWrapper::blurRegion()` +(由 `Helper` 在 `WSurface::commit` 信号里经 +`WBackgroundEffectManagerV1::surfaceBlurRegion()` 同步),证明请求确实到达真实合成器 +surface 管线,且双缓冲状态在连续 commit 间保持(回归防护:vendored 实现曾因多余的 +`pixman_region32_clear` 在第二次 commit 后丢失模糊区域)。最后对同一 surface 重复 +`get_background_effect`,断言协议错误码为 `background_effect_exists`(致命错误,置于用例末尾)。 + +## 已知边界 / 下一项结果 + +未验证多矩形区域的逐矩形渲染遮罩与模糊像素效果(V 级);blur 能力位关闭时的客户端 +降级行为未覆盖。 diff --git a/tests/protocols/ext-background-effect-v1/ext-background-effect-v1.c b/tests/protocols/ext-background-effect-v1/ext-background-effect-v1.c new file mode 100644 index 0000000000..3a6f6f5c67 --- /dev/null +++ b/tests/protocols/ext-background-effect-v1/ext-background-effect-v1.c @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2026 UnionTech Software Technology Co., Ltd. + * SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + * + * Test the ext_background_effect_manager_v1 global attached by Treeland's + * Helper (WBackgroundEffectManagerV1). + * + * Coverage level E (end-to-end): the client binds the manager, checks the + * blur capability, maps a real xdg_toplevel, attaches a background effect + * object and drives the double-buffered blur region through + * set/commit/remove/re-enable/destroy cycles. After every commit the test + * reads back the production SurfaceWrapper blur region over the server + * bridge, proving the requests reach the real compositor surface pipeline + * and that the region survives repeated commits (double-buffered state is + * kept, not cleared, between commits). + */ + +#include "ext-background-effect-v1.h" +#include "client-connection.h" +#include "server-bridge-api.h" +#include "xdg-toplevel-client.h" +#include "ext-background-effect-v1-client-protocol.h" + +#include +#include + +static int read_server(struct background_effect_server_state *state) { + return TEST_READ_SERVER(background_effect_read_server_state, state, + "background-effect: failed to read server state"); +} + +/* Waits until a mapped SurfaceWrapper was captured, bounded by roundtrips. */ +static int wait_server_wrapper(struct client_connection *conn, + struct background_effect_server_state *state) { + for (int i = 0; i < 50; ++i) { + if (read_server(state)) + return 0; + if (state->valid) + return 1; + if (wl_display_roundtrip(conn->display) < 0) + return 0; + } + return state->valid; +} + +static int expect_region(struct background_effect_server_state *state, + int x, int y, int width, int height) { + return state->valid && state->has_region && state->blur_enabled + && state->x == x && state->y == y + && state->width == width && state->height == height; +} + +static int expect_no_region(struct background_effect_server_state *state) { + return state->valid && !state->has_region && !state->blur_enabled; +} + +static void set_blur_region(struct ext_background_effect_surface_v1 *effect, + struct wl_compositor *compositor, int x, int y, int width, int height) { + struct wl_region *region = wl_compositor_create_region(compositor); + wl_region_add(region, x, y, width, height); + ext_background_effect_surface_v1_set_blur_region(effect, region); + /* set_blur_region has copy semantics, safe to destroy immediately. */ + wl_region_destroy(region); +} + +static void handle_capabilities(void *data, + struct ext_background_effect_manager_v1 *manager, uint32_t flags) { + (void)manager; + *(uint32_t *)data = flags; +} + +/* The manager interface has no other events in version 1. */ +static const struct ext_background_effect_manager_v1_listener manager_listener = { + .capabilities = handle_capabilities, +}; + +int protocol_test_run(const char *socket_name) { + struct client_connection conn; + if (!client_connect(&conn, socket_name)) { + TEST_ERROR("background-effect: connect failed\n"); + return 1; + } + + int failed = 0; + uint32_t capabilities = 0; + struct background_effect_server_state srv; + memset(&srv, 0, sizeof(srv)); + + struct ext_background_effect_manager_v1 *manager = + client_bind(&conn, ext_background_effect_manager_v1_interface.name, + &ext_background_effect_manager_v1_interface, 1); + if (manager == NULL) { + TEST_ERROR("background-effect: failed to bind ext_background_effect_manager_v1\n"); + client_disconnect(&conn); + return 1; + } + + /* manager.bind: capabilities must arrive on bind and contain blur. */ + ext_background_effect_manager_v1_add_listener(manager, &manager_listener, &capabilities); + if (wl_display_roundtrip(conn.display) < 0) { + TEST_ERROR("background-effect: roundtrip after bind failed\n"); + client_disconnect(&conn); + return 1; + } + if (capabilities != EXT_BACKGROUND_EFFECT_MANAGER_V1_CAPABILITY_BLUR) { + TEST_ERROR("background-effect: expected capabilities %u, got %u\n", + EXT_BACKGROUND_EFFECT_MANAGER_V1_CAPABILITY_BLUR, capabilities); + failed = 1; + } + + struct xdg_toplevel_client tc; + if (!failed + && !xdg_toplevel_client_create_with_solid_buffer(&conn, &tc, 128, 128, 0xff00ff00u)) { + TEST_ERROR("background-effect: create toplevel failed\n"); + failed = 1; + } + + struct ext_background_effect_surface_v1 *effect = NULL; + if (!failed) { + effect = ext_background_effect_manager_v1_get_background_effect(manager, tc.surface); + if (effect == NULL) { + TEST_ERROR("background-effect: get_background_effect failed\n"); + failed = 1; + } + } + + /* set_blur_region + commit: the region must reach the production wrapper. */ + if (!failed) { + set_blur_region(effect, tc.compositor, 10, 10, 40, 40); + wl_surface_commit(tc.surface); + wl_display_roundtrip(conn.display); + if (!wait_server_wrapper(&conn, &srv) || !expect_region(&srv, 10, 10, 40, 40)) { + TEST_ERROR("background-effect: expected region 10,10 40x40, got valid=%d has_region=%d %d,%d %dx%d\n", + srv.valid, srv.has_region, srv.x, srv.y, srv.width, srv.height); + failed = 1; + } + } + + /* Double-buffered state: a commit without set_blur_region must keep the + * region (the pending region is not cleared by the move to current). */ + if (!failed) { + wl_surface_commit(tc.surface); + wl_display_roundtrip(conn.display); + if (read_server(&srv) || !expect_region(&srv, 10, 10, 40, 40)) { + TEST_ERROR("background-effect: region lost after plain commit (got %d,%d %dx%d)\n", + srv.x, srv.y, srv.width, srv.height); + failed = 1; + } + } + + /* NULL region removes the effect on the next commit. */ + if (!failed) { + ext_background_effect_surface_v1_set_blur_region(effect, NULL); + wl_surface_commit(tc.surface); + wl_display_roundtrip(conn.display); + if (read_server(&srv) || !expect_no_region(&srv)) { + TEST_ERROR("background-effect: region not removed by NULL region\n"); + failed = 1; + } + } + + /* Re-enable with a region covering the whole surface. */ + if (!failed) { + set_blur_region(effect, tc.compositor, 0, 0, 128, 128); + wl_surface_commit(tc.surface); + wl_display_roundtrip(conn.display); + if (read_server(&srv) || !expect_region(&srv, 0, 0, 128, 128)) { + TEST_ERROR("background-effect: region not re-enabled after NULL\n"); + failed = 1; + } + } + + /* Destroying the effect object removes the effect on the next commit. */ + if (!failed) { + ext_background_effect_surface_v1_destroy(effect); + effect = NULL; + wl_surface_commit(tc.surface); + wl_display_roundtrip(conn.display); + if (read_server(&srv) || !expect_no_region(&srv)) { + TEST_ERROR("background-effect: region not removed after effect destroy\n"); + failed = 1; + } + } + + /* A second get_background_effect on the same surface must raise the + * background_effect_exists protocol error (fatal, so run it last). */ + if (!failed) { + effect = ext_background_effect_manager_v1_get_background_effect(manager, tc.surface); + if (effect == NULL) { + TEST_ERROR("background-effect: re-attach after destroy failed\n"); + failed = 1; + } else { + ext_background_effect_manager_v1_get_background_effect(manager, tc.surface); + if (wl_display_roundtrip(conn.display) >= 0) { + TEST_ERROR("background-effect: expected a protocol error for duplicate effect object\n"); + failed = 1; + } else { + const struct wl_interface *error_interface = NULL; + uint32_t error_id = 0; + uint32_t code = wl_display_get_protocol_error(conn.display, + &error_interface, &error_id); + if (code != EXT_BACKGROUND_EFFECT_MANAGER_V1_ERROR_BACKGROUND_EFFECT_EXISTS) { + TEST_ERROR("background-effect: expected background_effect_exists error, got %u\n", code); + failed = 1; + } + } + } + } + + if (effect) + ext_background_effect_surface_v1_destroy(effect); + ext_background_effect_manager_v1_destroy(manager); + xdg_toplevel_client_destroy(&tc); + client_disconnect(&conn); + return failed; +} diff --git a/tests/protocols/ext-background-effect-v1/ext-background-effect-v1.h b/tests/protocols/ext-background-effect-v1/ext-background-effect-v1.h new file mode 100644 index 0000000000..6553c2a5c4 --- /dev/null +++ b/tests/protocols/ext-background-effect-v1/ext-background-effect-v1.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2026 UnionTech Software Technology Co., Ltd. + * SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + */ +#ifndef EXT_BACKGROUND_EFFECT_TEST_H +#define EXT_BACKGROUND_EFFECT_TEST_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Snapshot of the real mapped SurfaceWrapper's ext-background-effect-v1 blur + * state, read on the compositor (Qt) thread. The blur region is + * double-buffered state applied on wl_surface.commit. + */ +struct background_effect_server_state { + int valid; /* a mapped SurfaceWrapper was captured */ + int has_region; /* the surface has a non-empty committed blur region */ + int blur_enabled; /* SurfaceWrapper::blur() (region or personalization) */ + int x; + int y; + int width; + int height; /* bounding rect of the committed blur region */ +}; + +/* Defined in setup.cpp; runs on the compositor thread via + * invoke_on_server_thread() and fills *data with the blur region state. */ +void background_effect_read_server_state(void *data); + +#ifdef __cplusplus +} +#endif + +#endif /* EXT_BACKGROUND_EFFECT_TEST_H */ diff --git a/tests/protocols/ext-background-effect-v1/setup.cpp b/tests/protocols/ext-background-effect-v1/setup.cpp new file mode 100644 index 0000000000..8676e58c8d --- /dev/null +++ b/tests/protocols/ext-background-effect-v1/setup.cpp @@ -0,0 +1,58 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#include "ext-background-effect-v1.h" +#include "server-bridge.h" +#include "core/shellhandler.h" +#include "seat/helper.h" +#include "surface/surfacewrapper.h" + +#include + +namespace { +// The real mapped XdgToplevel SurfaceWrapper created by the client. +SurfaceWrapper *g_wrapper = nullptr; +} + +void protocol_test_setup(Helper *helper) +{ + add_headless_output(helper->backend(), false); + + QObject::connect(helper->shellHandler(), + &ShellHandler::surfaceWrapperAdded, + helper, + [](SurfaceWrapper *wrapper) { + if (wrapper->type() == SurfaceWrapper::Type::XdgToplevel) + g_wrapper = wrapper; + }); +} + +// E-level read: report the captured production SurfaceWrapper's committed +// ext-background-effect-v1 blur region, synced from the protocol surface +// state on every wl_surface.commit. +void background_effect_read_server_state(void *data) +{ + auto *state = static_cast(data); + state->valid = 0; + state->has_region = 0; + state->blur_enabled = 0; + state->x = 0; + state->y = 0; + state->width = 0; + state->height = 0; + + if (!g_wrapper) + return; + + state->valid = 1; + const QRegion region = g_wrapper->blurRegion(); + state->blur_enabled = g_wrapper->blur() ? 1 : 0; + if (region.isEmpty()) + return; + + state->has_region = 1; + const QRect rect = region.boundingRect(); + state->x = rect.x(); + state->y = rect.y(); + state->width = rect.width(); + state->height = rect.height(); +} diff --git a/tests/test_protocol_background-effect/CMakeLists.txt b/tests/test_protocol_background-effect/CMakeLists.txt new file mode 100644 index 0000000000..67b97c73d3 --- /dev/null +++ b/tests/test_protocol_background-effect/CMakeLists.txt @@ -0,0 +1,19 @@ +find_package(Qt6 REQUIRED COMPONENTS Test) + +add_executable(test_protocol_background-effect main.cpp) + +target_link_libraries(test_protocol_background-effect + PRIVATE + libtreeland + Qt::Test +) + +add_test(NAME test_protocol_background-effect COMMAND test_protocol_background-effect) + +set_property(TEST test_protocol_background-effect PROPERTY + ENVIRONMENT "QT_QPA_PLATFORM=offscreen" +) + +set_property(TEST test_protocol_background-effect PROPERTY + TIMEOUT 3 +) diff --git a/tests/test_protocol_background-effect/main.cpp b/tests/test_protocol_background-effect/main.cpp new file mode 100644 index 0000000000..f419ef8b8b --- /dev/null +++ b/tests/test_protocol_background-effect/main.cpp @@ -0,0 +1,54 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include +#include + +#include +#include + +class BackgroundEffectTest : public QObject +{ + Q_OBJECT + + WAYLIB_SERVER_NAMESPACE::WServer *m_server = nullptr; + WAYLIB_SERVER_NAMESPACE::WBackgroundEffectManagerV1 *m_protocol = nullptr; + +public: + BackgroundEffectTest(QObject *parent = nullptr) + : QObject(parent) + { + } + +private Q_SLOTS: + + void initTestCase() + { + m_server = new WAYLIB_SERVER_NAMESPACE::WServer(); + } + + void testAttach() + { + m_protocol = m_server->attach(); + QVERIFY(m_protocol != nullptr); + QVERIFY(m_server->interfaceList().contains(m_protocol)); + QCOMPARE(m_protocol->interfaceName(), QByteArray("ext_background_effect_manager_v1")); + } + + void testSurfaceBlurRegionWithoutSurface() + { + // Without a surface (or before any client attached an effect object) + // the committed blur region is empty. + QVERIFY(m_protocol != nullptr); + QVERIFY(m_protocol->surfaceBlurRegion(nullptr).isEmpty()); + } + + void cleanupTestCase() + { + m_server->deleteLater(); + m_server = nullptr; + } +}; + +QTEST_MAIN(BackgroundEffectTest) +#include "main.moc" diff --git a/waylib/src/server/protocols/wbackgroundeffectmanagerv1.cpp b/waylib/src/server/protocols/wbackgroundeffectmanagerv1.cpp index 6439321979..da0dd87fc9 100644 --- a/waylib/src/server/protocols/wbackgroundeffectmanagerv1.cpp +++ b/waylib/src/server/protocols/wbackgroundeffectmanagerv1.cpp @@ -3,6 +3,9 @@ #include "wbackgroundeffectmanagerv1.h" #include "private/wglobal_p.h" +#include "wtools.h" + +#include #include @@ -38,6 +41,20 @@ QByteArrayView WBackgroundEffectManagerV1::interfaceName() const return "ext_background_effect_manager_v1"; } +QRegion WBackgroundEffectManagerV1::surfaceBlurRegion(WSurface *surface) const +{ + auto *wlrSurface = surface ? surface->handle() : nullptr; + if (!wlrSurface) + return QRegion(); + + const auto *state = wlr_ext_background_effect_v1_get_surface_state(wlrSurface); + if (!state) + return QRegion(); + + return WTools::fromPixmanRegion( + const_cast(&state->blur_region)); +} + void WBackgroundEffectManagerV1::create(WServer *server) { W_D(WBackgroundEffectManagerV1); diff --git a/waylib/src/server/protocols/wbackgroundeffectmanagerv1.h b/waylib/src/server/protocols/wbackgroundeffectmanagerv1.h index a64088e36a..8976f445a9 100644 --- a/waylib/src/server/protocols/wbackgroundeffectmanagerv1.h +++ b/waylib/src/server/protocols/wbackgroundeffectmanagerv1.h @@ -6,10 +6,12 @@ #include #include +#include #include WAYLIB_SERVER_BEGIN_NAMESPACE +class WSurface; class WBackgroundEffectManagerV1Private; class WAYLIB_SERVER_EXPORT WBackgroundEffectManagerV1 : public QObject, public WObject, public WServerInterface { @@ -21,6 +23,13 @@ class WAYLIB_SERVER_EXPORT WBackgroundEffectManagerV1 : public QObject, public W wlr_ext_background_effect_manager_v1 *handle() const; + /* + * The committed blur region of the surface's ext-background-effect-v1 + * state, in surface-local coordinates. An empty region means the surface + * has no background effect (or no committed blur region). + */ + QRegion surfaceBlurRegion(WSurface *surface) const; + QByteArrayView interfaceName() const override; static constexpr int InterfaceVersion = 1; diff --git a/waylib/src/server/protocols/wxdgoutput.cpp b/waylib/src/server/protocols/wxdgoutput.cpp index b5bc19835a..328bb303c2 100644 --- a/waylib/src/server/protocols/wxdgoutput.cpp +++ b/waylib/src/server/protocols/wxdgoutput.cpp @@ -168,7 +168,14 @@ static void output_manager_handle_get_xdg_output(struct wl_client *client, struct wlr_output_layout_output *layout_output = wlr_output_layout_get(layout, output); - assert(layout_output); + // An output can exist before it is registered in the layout (e.g. a + // just-created virtual output racing a client's get_xdg_output). + // Aborting here kills the whole compositor; serve an inert xdg_output + // instead, mirroring the output == NULL handling above. + if (!layout_output) { + wl_list_init(wl_resource_get_link(xdg_output_resource)); + return; + } struct way_xdg_output_v1 *_xdg_output, *xdg_output = NULL; wl_list_for_each(_xdg_output, &manager->outputs, link) { From decc9bc003cf83f913a83c5e622fc4d9131c4c03 Mon Sep 17 00:00:00 2001 From: deepin-wm Date: Thu, 24 Sep 2026 13:19:03 +0800 Subject: [PATCH 3/3] fix: update SurfaceContent.qml copyright year range to include 2026 The SPDX Year Range Checker requires modified files to carry the current year in their copyright range. --- src/core/qml/SurfaceContent.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/qml/SurfaceContent.qml b/src/core/qml/SurfaceContent.qml index 2cd312ecfb..e6e8afd9d9 100644 --- a/src/core/qml/SurfaceContent.qml +++ b/src/core/qml/SurfaceContent.qml @@ -1,4 +1,4 @@ -// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// Copyright (C) 2024-2026 UnionTech Software Technology Co., Ltd. // SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only import QtQuick