From f6c629cf9ac88f9515979aeece676959f0d606aa Mon Sep 17 00:00:00 2001 From: luhongxu Date: Tue, 15 Sep 2026 21:49:20 +0800 Subject: [PATCH 1/2] feat: migrate set_xwindow_position_relative to treeland-xwindow-control-unstable-v1 protocol - Add new xwindow-control module with XWindowControlInterfaceV1 implementing the treeland_xwindow_control_v1 global interface - Register XWindowControlInterfaceV1 in Helper::initShell() - Remove deprecated set_xwindow_position_relative override from DDEShellManagerInterfaceV1Private (now handled by the new module) - Update example to use the new treeland_xwindow_control_v1 client interface - Add protocol integration test and Qt unit test for the new interface --- .../test_set_xwindow_position/CMakeLists.txt | 2 +- examples/test_set_xwindow_position/main.cpp | 24 +-- src/modules/CMakeLists.txt | 1 + .../dde-shell/ddeshellmanagerinterfacev1.cpp | 18 +- src/modules/xwindow-control/CMakeLists.txt | 20 +++ .../xwindowcontrolinterfacev1.cpp | 84 +++++++++ .../xwindowcontrolinterfacev1.h | 34 ++++ src/seat/helper.cpp | 1 + src/seat/helper.h | 2 + tests/CMakeLists.txt | 1 + tests/protocols/CMakeLists.txt | 1 + .../CMakeLists.txt | 7 + .../setup.cpp | 13 ++ .../treeland-xwindow-control-unstable-v1.c | 168 ++++++++++++++++++ .../treeland-xwindow-control-unstable-v1.h | 51 ++++++ .../CMakeLists.txt | 19 ++ tests/test_protocol_xwindow-control/main.cpp | 49 +++++ 17 files changed, 465 insertions(+), 30 deletions(-) create mode 100644 src/modules/xwindow-control/CMakeLists.txt create mode 100644 src/modules/xwindow-control/xwindowcontrolinterfacev1.cpp create mode 100644 src/modules/xwindow-control/xwindowcontrolinterfacev1.h create mode 100644 tests/protocols/treeland-xwindow-control-unstable-v1/CMakeLists.txt create mode 100644 tests/protocols/treeland-xwindow-control-unstable-v1/setup.cpp create mode 100644 tests/protocols/treeland-xwindow-control-unstable-v1/treeland-xwindow-control-unstable-v1.c create mode 100644 tests/protocols/treeland-xwindow-control-unstable-v1/treeland-xwindow-control-unstable-v1.h create mode 100644 tests/test_protocol_xwindow-control/CMakeLists.txt create mode 100644 tests/test_protocol_xwindow-control/main.cpp diff --git a/examples/test_set_xwindow_position/CMakeLists.txt b/examples/test_set_xwindow_position/CMakeLists.txt index ae443e83e2..69af348c64 100644 --- a/examples/test_set_xwindow_position/CMakeLists.txt +++ b/examples/test_set_xwindow_position/CMakeLists.txt @@ -10,7 +10,7 @@ qt_add_executable(test-set-xwindow-position qt_generate_wayland_protocol_client_sources(test-set-xwindow-position FILES - ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-dde-shell-v1.xml + ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-xwindow-control-unstable-v1.xml NO_INCLUDE_CORE_ONLY ) diff --git a/examples/test_set_xwindow_position/main.cpp b/examples/test_set_xwindow_position/main.cpp index 641209d916..a81fbcf088 100644 --- a/examples/test_set_xwindow_position/main.cpp +++ b/examples/test_set_xwindow_position/main.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2025 UnionTech Software Technology Co., Ltd. +// Copyright (C) 2025-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 @@ -10,20 +10,20 @@ #include -#include +#include #include #include -class DDEShellManagerV1 - : public QWaylandClientExtensionTemplate - , public QtWayland::treeland_dde_shell_manager_v1 +class XWindowControlV1 + : public QWaylandClientExtensionTemplate + , public QtWayland::treeland_xwindow_control_v1 { Q_OBJECT public: - DDEShellManagerV1() - : QWaylandClientExtensionTemplate( - treeland_dde_shell_manager_v1_interface.version) + XWindowControlV1() + : QWaylandClientExtensionTemplate( + treeland_xwindow_control_v1_interface.version) { } }; @@ -71,7 +71,7 @@ int main(int argc, char *argv[]) return 1; } - DDEShellManagerV1 manager; + XWindowControlV1 control; struct wl_callback_listener callback_listener = { .done = []([[maybe_unused]] void *data, wl_callback *callback, @@ -85,8 +85,8 @@ int main(int argc, char *argv[]) QTimer timer; QObject::connect(&timer, &QTimer::timeout, [&] { - if (!manager.isActive()) { - qCritical() << "DDEShellManagerV1 is not active!"; + if (!control.isActive()) { + qCritical() << "XWindowControlV1 is not active!"; return; } struct wl_surface *surface = static_cast( @@ -97,7 +97,7 @@ int main(int argc, char *argv[]) wl_fixed_t dx = wl_fixed_from_int(640); wl_fixed_t dy = wl_fixed_from_int(0); - wl_callback *callback = manager.set_xwindow_position_relative(wid, surface, dx, dy); + wl_callback *callback = control.set_xwindow_position_relative(wid, surface, dx, dy); wl_callback_add_listener(callback, &callback_listener, nullptr); qWarning() << "Setting xwindow position relative, wait for result..."; }); diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt index 3b9dba87bf..bca2b712c1 100644 --- a/src/modules/CMakeLists.txt +++ b/src/modules/CMakeLists.txt @@ -30,6 +30,7 @@ add_subdirectory(personalization) add_subdirectory(shortcut) add_subdirectory(wallpaper-color) add_subdirectory(show-desktop) +add_subdirectory(xwindow-control) add_subdirectory(virtual-output) add_subdirectory(dde-shell) add_subdirectory(capture) diff --git a/src/modules/dde-shell/ddeshellmanagerinterfacev1.cpp b/src/modules/dde-shell/ddeshellmanagerinterfacev1.cpp index 847c80d5bf..ef3fb3a864 100644 --- a/src/modules/dde-shell/ddeshellmanagerinterfacev1.cpp +++ b/src/modules/dde-shell/ddeshellmanagerinterfacev1.cpp @@ -5,7 +5,7 @@ #include "qwayland-server-treeland-dde-shell-v1.h" -#include "helper.h" +#include #include @@ -34,8 +34,6 @@ class DDEShellManagerInterfaceV1Private : public QtWaylandServer::treeland_dde_s void get_treeland_multitaskview(Resource *resource, uint32_t id) override; void get_treeland_window_picker(Resource *resource, uint32_t id) override; void get_treeland_lockscreen(Resource *resource, uint32_t id) override; - void set_xwindow_position_relative(Resource *resource, uint32_t callback, uint32_t wid, - struct ::wl_resource *anchor, wl_fixed_t dx, wl_fixed_t dy) override; }; void DDEShellManagerInterfaceV1Private::get_treeland_lockscreen(Resource *resource, @@ -59,20 +57,6 @@ void DDEShellManagerInterfaceV1Private::get_treeland_lockscreen(Resource *resour Q_EMIT q->lockScreenCreated(lockScreen); } -void DDEShellManagerInterfaceV1Private::set_xwindow_position_relative(Resource *resource, - uint32_t callback, - uint32_t wid, - struct ::wl_resource *anchor, - wl_fixed_t dx, - wl_fixed_t dy) -{ - WSurface *wsurface = WSurface::fromHandle(wlr_surface_from_resource(anchor)); - uint32_t ok = (wsurface && Helper::instance()->setXWindowPositionRelative(wid, wsurface, dx, dy)) ? 0 : 1; - wl_resource *cb = wl_resource_create(resource->client(), &wl_callback_interface, 1, callback); - wl_callback_send_done(cb, ok); - wl_resource_destroy(cb); -} - DDEShellManagerInterfaceV1Private::DDEShellManagerInterfaceV1Private(DDEShellManagerInterfaceV1 *_q) : QtWaylandServer::treeland_dde_shell_manager_v1() , q(_q) diff --git a/src/modules/xwindow-control/CMakeLists.txt b/src/modules/xwindow-control/CMakeLists.txt new file mode 100644 index 0000000000..85704de450 --- /dev/null +++ b/src/modules/xwindow-control/CMakeLists.txt @@ -0,0 +1,20 @@ +find_package(TreelandProtocols REQUIRED) + +waylib_generate_qtwayland_server_protocol(libtreeland + PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-xwindow-control-unstable-v1.xml + BASENAME treeland-xwindow-control-unstable-v1 +) + +impl_treeland( + NAME + module_xwindow_control + SOURCE + ${CMAKE_SOURCE_DIR}/src/modules/xwindow-control/xwindowcontrolinterfacev1.h + ${CMAKE_SOURCE_DIR}/src/modules/xwindow-control/xwindowcontrolinterfacev1.cpp + LINK + Wlroots::wlroots + Waylib::WaylibServer + Qt6::Core + Qt6::Gui + Qt6::Quick +) diff --git a/src/modules/xwindow-control/xwindowcontrolinterfacev1.cpp b/src/modules/xwindow-control/xwindowcontrolinterfacev1.cpp new file mode 100644 index 0000000000..62f5617285 --- /dev/null +++ b/src/modules/xwindow-control/xwindowcontrolinterfacev1.cpp @@ -0,0 +1,84 @@ +// 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 "xwindowcontrolinterfacev1.h" +#include "qwayland-server-treeland-xwindow-control-unstable-v1.h" + +#include "seat/helper.h" + +#include +#include + +#include + +class XWindowControlInterfaceV1Private : public QtWaylandServer::treeland_xwindow_control_v1 +{ +public: + XWindowControlInterfaceV1Private(XWindowControlInterfaceV1 *_q); + wl_global *global() const; + + XWindowControlInterfaceV1 *q; + +protected: + void destroy(Resource *resource) override; + void set_xwindow_position_relative(Resource *resource, uint32_t callback, uint32_t wid, + struct ::wl_resource *anchor, wl_fixed_t dx, wl_fixed_t dy) override; +}; + +XWindowControlInterfaceV1Private::XWindowControlInterfaceV1Private(XWindowControlInterfaceV1 *_q) + : QtWaylandServer::treeland_xwindow_control_v1() + , q(_q) +{ +} + +wl_global *XWindowControlInterfaceV1Private::global() const +{ + return m_global; +} + +void XWindowControlInterfaceV1Private::destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +void XWindowControlInterfaceV1Private::set_xwindow_position_relative(Resource *resource, + uint32_t callback, + uint32_t wid, + struct ::wl_resource *anchor, + wl_fixed_t dx, + wl_fixed_t dy) +{ + WSurface *wsurface = WSurface::fromHandle(wlr_surface_from_resource(anchor)); + uint32_t ok = (wsurface && Helper::instance()->setXWindowPositionRelative(wid, wsurface, dx, dy)) ? 0 : 1; + wl_resource *cb = wl_resource_create(resource->client(), &wl_callback_interface, 1, callback); + wl_callback_send_done(cb, ok); + wl_resource_destroy(cb); +} + +XWindowControlInterfaceV1::XWindowControlInterfaceV1(QObject *parent) + : QObject(parent) + , d(new XWindowControlInterfaceV1Private(this)) +{ +} + +XWindowControlInterfaceV1::~XWindowControlInterfaceV1() = default; + +void XWindowControlInterfaceV1::create(WServer *server) +{ + d->init(server->handle(), InterfaceVersion); +} + +void XWindowControlInterfaceV1::destroy([[maybe_unused]] WServer *server) +{ + d->globalRemove(); +} + +wl_global *XWindowControlInterfaceV1::global() const +{ + return d->global(); +} + +QByteArrayView XWindowControlInterfaceV1::interfaceName() const +{ + return d->interfaceName(); +} diff --git a/src/modules/xwindow-control/xwindowcontrolinterfacev1.h b/src/modules/xwindow-control/xwindowcontrolinterfacev1.h new file mode 100644 index 0000000000..b8703c6da9 --- /dev/null +++ b/src/modules/xwindow-control/xwindowcontrolinterfacev1.h @@ -0,0 +1,34 @@ +// 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 + +WAYLIB_SERVER_USE_NAMESPACE + +class XWindowControlInterfaceV1Private; +class XWindowControlInterfaceV1 + : public QObject + , public WAYLIB_SERVER_NAMESPACE::WServerInterface +{ + Q_OBJECT + +public: + explicit XWindowControlInterfaceV1(QObject *parent = nullptr); + ~XWindowControlInterfaceV1() override; + + 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; + +private: + std::unique_ptr d; +}; diff --git a/src/seat/helper.cpp b/src/seat/helper.cpp index 4b40dea43e..a0aa23483d 100644 --- a/src/seat/helper.cpp +++ b/src/seat/helper.cpp @@ -1931,6 +1931,7 @@ void Helper::init(Treeland::Treeland *treeland) &SessionManager::syncActiveSessionXWaylandPrimaryOutput); m_wallpaperColorV1 = m_server->attach(); m_showDesktopInterfaceV1 = m_server->attach(); + m_xWindowControlInterfaceV1 = m_server->attach(); m_virtualOutputInterfaceV1 = m_server->attach(); auto captureManagerV1 = m_server->attach(); diff --git a/src/seat/helper.h b/src/seat/helper.h index 8c4cf104a5..d3d1ad8b14 100644 --- a/src/seat/helper.h +++ b/src/seat/helper.h @@ -11,6 +11,7 @@ #include "modules/wallpaper/wallpapermanagerinterfacev1.h" #include "modules/wallpaper/wallpapernotifierinterfacev1.h" #include "modules/show-desktop/showdesktopinterfacev1.h" +#include "modules/xwindow-control/xwindowcontrolinterfacev1.h" #include "utils/fpsdisplaymanager.h" #include @@ -471,6 +472,7 @@ private Q_SLOTS: WOutputManagerV1 *m_outputManager = nullptr; WXdgOutputManager *m_xwaylandOutputManager = nullptr; ShowDesktopInterfaceV1 *m_showDesktopInterfaceV1 = nullptr; + XWindowControlInterfaceV1 *m_xWindowControlInterfaceV1 = nullptr; ShowDesktopInterfaceV1::State m_showDesktop = ShowDesktopInterfaceV1::State::Normal; DDEShellManagerInterfaceV1 *m_ddeShellV1 = nullptr; VirtualOutputManagerInterfaceV1 *m_virtualOutputInterfaceV1 = nullptr; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bb4fb08e76..3e746e6baa 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,6 +10,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_xwindow-control) add_subdirectory(test_protocol_prelaunch-splash) add_subdirectory(test_protocol_pointerconstraints) add_subdirectory(test_effect_glass) diff --git a/tests/protocols/CMakeLists.txt b/tests/protocols/CMakeLists.txt index 2bf91fc0b3..e8e54ee68b 100644 --- a/tests/protocols/CMakeLists.txt +++ b/tests/protocols/CMakeLists.txt @@ -59,6 +59,7 @@ add_subdirectory(treeland-wallpaper-manager-unstable-v1) add_subdirectory(treeland-wallpaper-shell-unstable-v1) add_subdirectory(treeland-wallpaper-desktop-v1) add_subdirectory(treeland-show-desktop-v1) +add_subdirectory(treeland-xwindow-control-unstable-v1) add_subdirectory(treeland-show-desktop-desktop-v1) add_subdirectory(treeland-wine-window-state-unstable-v1) add_subdirectory(treeland-wine-window-management-unstable-v1) diff --git a/tests/protocols/treeland-xwindow-control-unstable-v1/CMakeLists.txt b/tests/protocols/treeland-xwindow-control-unstable-v1/CMakeLists.txt new file mode 100644 index 0000000000..a071a74698 --- /dev/null +++ b/tests/protocols/treeland-xwindow-control-unstable-v1/CMakeLists.txt @@ -0,0 +1,7 @@ + +treeland_add_protocol_test( + NAME treeland_xwindow_control_v1 + XML "${TREELAND_PROTOCOLS_DATA_DIR}/treeland-xwindow-control-unstable-v1.xml" + SETUP "${CMAKE_CURRENT_SOURCE_DIR}/setup.cpp" + CLIENT "${CMAKE_CURRENT_SOURCE_DIR}/treeland-xwindow-control-unstable-v1.c" +) diff --git a/tests/protocols/treeland-xwindow-control-unstable-v1/setup.cpp b/tests/protocols/treeland-xwindow-control-unstable-v1/setup.cpp new file mode 100644 index 0000000000..491745f560 --- /dev/null +++ b/tests/protocols/treeland-xwindow-control-unstable-v1/setup.cpp @@ -0,0 +1,13 @@ +// 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 "modules/xwindow-control/xwindowcontrolinterfacev1.h" +#include "server-bridge.h" + +#include + +WAYLIB_SERVER_USE_NAMESPACE + +void protocol_test_setup(Helper *helper) +{ + find_server_interface(helper); +} diff --git a/tests/protocols/treeland-xwindow-control-unstable-v1/treeland-xwindow-control-unstable-v1.c b/tests/protocols/treeland-xwindow-control-unstable-v1/treeland-xwindow-control-unstable-v1.c new file mode 100644 index 0000000000..2623bf84fe --- /dev/null +++ b/tests/protocols/treeland-xwindow-control-unstable-v1/treeland-xwindow-control-unstable-v1.c @@ -0,0 +1,168 @@ +// 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 "treeland-xwindow-control-unstable-v1.h" +#include "server-bridge-api.h" +#include "treeland-xwindow-control-unstable-v1-client-protocol.h" + +#include +#include +#include +#include +#include + +struct test_case { + const char *name; + int (*run)(struct test_ctx *ctx); +}; + +void test_init(struct test_ctx *ctx) +{ + memset(ctx, 0, sizeof(*ctx)); + ctx->result_cap = 32; + ctx->results = calloc(ctx->result_cap, sizeof(*ctx->results)); +} + +void test_destroy(struct test_ctx *ctx) +{ + free(ctx->results); + memset(ctx, 0, sizeof(*ctx)); +} + +int test_add(struct test_ctx *ctx, const char *name) +{ + if (ctx->result_count == ctx->result_cap) { + ctx->result_cap *= 2; + ctx->results = realloc(ctx->results, (size_t)ctx->result_cap * sizeof(*ctx->results)); + } + const int index = ctx->result_count++; + ctx->results[index] = (struct test_result) { .name = name }; + return index; +} + +void test_fail(struct test_ctx *ctx, int index, const char *format, ...) +{ + va_list arguments; + va_start(arguments, format); + vsnprintf(ctx->results[index].message, TEST_MSG_MAX, format, arguments); + va_end(arguments); + ctx->results[index].failed = 1; +} + +void test_pass(struct test_ctx *ctx, int index) +{ + ctx->results[index].failed = 0; +} + +int test_print_results(struct test_ctx *ctx) +{ + int failed = 0; + printf("\n=== results ===\n"); + for (int i = 0; i < ctx->result_count; ++i) { + printf(" [%s] %s", ctx->results[i].failed ? "FAIL" : "PASS", ctx->results[i].name); + if (ctx->results[i].failed) { + printf(" -- %s", ctx->results[i].message); + ++failed; + } + printf("\n"); + } + printf("%d/%d passed\n", ctx->result_count - failed, ctx->result_count); + return failed == 0; +} + +static int connect_client(struct test_ctx *ctx, const char *socket_name) +{ + if (!client_connect(&ctx->connection, socket_name)) + return 0; + ctx->display = ctx->connection.display; + ctx->compositor = client_bind(&ctx->connection, "wl_compositor", &wl_compositor_interface, 1); + ctx->manager = client_bind(&ctx->connection, "treeland_xwindow_control_v1", + &treeland_xwindow_control_v1_interface, 1); + return ctx->manager != NULL; +} + +static int bind_manager(struct test_ctx *ctx) +{ + return ctx->manager != NULL; +} + +static void callback_done(void *data, struct wl_callback *callback, uint32_t result) +{ + (void)callback; + struct test_ctx *ctx = data; + ctx->callback_received = 1; + ctx->callback_result = result; +} + +static const struct wl_callback_listener callback_listener = { + .done = callback_done, +}; + +static int set_xwindow_position_relative(struct test_ctx *ctx) +{ + if (!ctx->compositor || !ctx->manager) + return 0; + struct wl_surface *surface = wl_compositor_create_surface(ctx->compositor); + if (!surface) + return 0; + wl_fixed_t dx = wl_fixed_from_int(0); + wl_fixed_t dy = wl_fixed_from_int(0); + struct wl_callback *cb = + treeland_xwindow_control_v1_set_xwindow_position_relative(ctx->manager, 0, surface, dx, dy); + if (!cb) + return 0; + wl_callback_add_listener(cb, &callback_listener, ctx); + wl_surface_destroy(surface); + return 1; +} + +static int callback_failure_received(struct test_ctx *ctx) +{ + return ctx->callback_received && ctx->callback_result == 1; +} + +static int destroy_manager(struct test_ctx *ctx) +{ + if (!ctx->manager) + return 0; + treeland_xwindow_control_v1_destroy(ctx->manager); + ctx->manager = NULL; + return 1; +} + +static const struct test_case cases[] = { + { "manager.bind", bind_manager }, + { "request.set_xwindow_position_relative", set_xwindow_position_relative }, + { "callback.done(failure)", callback_failure_received }, + { "manager.destroy", destroy_manager }, +}; + +void test_cleanup(struct test_ctx *ctx) +{ + if (ctx->manager) treeland_xwindow_control_v1_destroy(ctx->manager); + client_disconnect(&ctx->connection); +} + +int protocol_test_run(const char *socket_name) +{ + struct test_ctx ctx; + test_init(&ctx); + if (!connect_client(&ctx, socket_name)) { + fprintf(stderr, "failed to connect to or bind treeland_xwindow_control_v1\n"); + test_cleanup(&ctx); + test_destroy(&ctx); + return 1; + } + + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + const int result = test_add(&ctx, cases[i].name); + if (!cases[i].run(&ctx)) + test_fail(&ctx, result, "assertion failed"); + if (wl_display_roundtrip(ctx.display) < 0) + test_fail(&ctx, result, "Wayland connection failed"); + } + + test_cleanup(&ctx); + const int success = test_print_results(&ctx); + test_destroy(&ctx); + return success ? 0 : 1; +} diff --git a/tests/protocols/treeland-xwindow-control-unstable-v1/treeland-xwindow-control-unstable-v1.h b/tests/protocols/treeland-xwindow-control-unstable-v1/treeland-xwindow-control-unstable-v1.h new file mode 100644 index 0000000000..ee5315dfa8 --- /dev/null +++ b/tests/protocols/treeland-xwindow-control-unstable-v1/treeland-xwindow-control-unstable-v1.h @@ -0,0 +1,51 @@ +// 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 XWINDOW_CONTROL_TEST_H +#define XWINDOW_CONTROL_TEST_H + +#ifdef __cplusplus +extern "C" { +#endif + +int protocol_test_run(const char *socket_name); + +#include "client-connection.h" + +struct treeland_xwindow_control_v1; + +#define TEST_MSG_MAX 256 + +struct test_result { + const char *name; + int failed; + char message[TEST_MSG_MAX]; +}; + +struct test_ctx { + struct client_connection connection; + struct wl_display *display; + + struct wl_compositor *compositor; + struct treeland_xwindow_control_v1 *manager; + + int callback_received; + uint32_t callback_result; + + struct test_result *results; + int result_count; + int result_cap; +}; + +void test_init(struct test_ctx *ctx); +void test_destroy(struct test_ctx *ctx); +int test_add(struct test_ctx *ctx, const char *name); +void test_fail(struct test_ctx *ctx, int idx, const char *fmt, ...); +void test_pass(struct test_ctx *ctx, int idx); + +int test_print_results(struct test_ctx *ctx); +void test_cleanup(struct test_ctx *ctx); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/tests/test_protocol_xwindow-control/CMakeLists.txt b/tests/test_protocol_xwindow-control/CMakeLists.txt new file mode 100644 index 0000000000..3edf46a916 --- /dev/null +++ b/tests/test_protocol_xwindow-control/CMakeLists.txt @@ -0,0 +1,19 @@ +find_package(Qt6 REQUIRED COMPONENTS Test) + +add_executable(test_protocol_xwindow-control main.cpp) + +target_link_libraries(test_protocol_xwindow-control + PRIVATE + libtreeland + Qt::Test +) + +add_test(NAME test_protocol_xwindow-control COMMAND test_protocol_xwindow-control) + +set_property(TEST test_protocol_xwindow-control PROPERTY + ENVIRONMENT "QT_QPA_PLATFORM=offscreen" +) + +set_property(TEST test_protocol_xwindow-control PROPERTY + TIMEOUT 3 +) diff --git a/tests/test_protocol_xwindow-control/main.cpp b/tests/test_protocol_xwindow-control/main.cpp new file mode 100644 index 0000000000..a862f9bd1b --- /dev/null +++ b/tests/test_protocol_xwindow-control/main.cpp @@ -0,0 +1,49 @@ +// 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 "modules/xwindow-control/xwindowcontrolinterfacev1.h" + +#include + +#include +#include + +class XWindowControlTest : public QObject +{ + Q_OBJECT + + WAYLIB_SERVER_NAMESPACE::WServer *m_server = nullptr; + +public: + XWindowControlTest(QObject *parent = nullptr) + : QObject(parent) + { + } + +private Q_SLOTS: + + void initTestCase() + { + m_server = new WAYLIB_SERVER_NAMESPACE::WServer(); + } + + void testCreate() + { + QVERIFY(m_server->attach(m_server) != nullptr); + } + + void verifyXWindowControl() + { + auto protocol = m_server->findChild(); + QVERIFY(protocol != nullptr); + } + + void cleanupTestCase() + { + m_server->deleteLater(); + m_server = nullptr; + } +}; + +QTEST_MAIN(XWindowControlTest) +#include "main.moc" From bda300178a78d773185d09fcf92ce74e59474e0b Mon Sep 17 00:00:00 2001 From: rewine Date: Wed, 16 Sep 2026 11:05:29 +0800 Subject: [PATCH 2/2] fix(xwindow-control): guard null callback and invalid anchor in v1 request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Check wl_resource_create result and post no-memory error instead of crashing; guard null anchor resource; document the protocol test contract and record the XWayland success path as a known boundary. 检查 wl_resource_create 结果并在分配失败时上报 no-memory 错误而非 崩溃;守卫空 anchor 资源;补充协议测试契约文档,并将 XWayland 成功 路径记为已知边界。 Log: 修复 xwindow-control 回调分配失败崩溃并补测试文档 Influence: 客户端传入无效 anchor 或回调资源分配失败时合成器不再崩溃, 统一返回失败结果;协议测试新增契约文档与索引条目。 --- examples/test_set_xwindow_position/main.cpp | 4 ++ .../xwindowcontrolinterfacev1.cpp | 10 ++++- tests/protocols/INDEX.md | 3 +- .../README.md | 40 +++++++++++++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 tests/protocols/treeland-xwindow-control-unstable-v1/README.md diff --git a/examples/test_set_xwindow_position/main.cpp b/examples/test_set_xwindow_position/main.cpp index a81fbcf088..a7eea92a96 100644 --- a/examples/test_set_xwindow_position/main.cpp +++ b/examples/test_set_xwindow_position/main.cpp @@ -98,6 +98,10 @@ int main(int argc, char *argv[]) wl_fixed_t dy = wl_fixed_from_int(0); wl_callback *callback = control.set_xwindow_position_relative(wid, surface, dx, dy); + if (!callback) { + qCritical() << "Failed to send set_xwindow_position_relative request!"; + return; + } wl_callback_add_listener(callback, &callback_listener, nullptr); qWarning() << "Setting xwindow position relative, wait for result..."; }); diff --git a/src/modules/xwindow-control/xwindowcontrolinterfacev1.cpp b/src/modules/xwindow-control/xwindowcontrolinterfacev1.cpp index 62f5617285..14227636b4 100644 --- a/src/modules/xwindow-control/xwindowcontrolinterfacev1.cpp +++ b/src/modules/xwindow-control/xwindowcontrolinterfacev1.cpp @@ -11,6 +11,8 @@ #include +#include + class XWindowControlInterfaceV1Private : public QtWaylandServer::treeland_xwindow_control_v1 { public: @@ -48,9 +50,15 @@ void XWindowControlInterfaceV1Private::set_xwindow_position_relative(Resource *r wl_fixed_t dx, wl_fixed_t dy) { - WSurface *wsurface = WSurface::fromHandle(wlr_surface_from_resource(anchor)); + WSurface *wsurface = nullptr; + if (anchor && strcmp(wl_resource_get_class(anchor), "wl_surface") == 0) + wsurface = WSurface::fromHandle(wlr_surface_from_resource(anchor)); uint32_t ok = (wsurface && Helper::instance()->setXWindowPositionRelative(wid, wsurface, dx, dy)) ? 0 : 1; wl_resource *cb = wl_resource_create(resource->client(), &wl_callback_interface, 1, callback); + if (!cb) { + wl_client_post_no_memory(resource->client()); + return; + } wl_callback_send_done(cb, ok); wl_resource_destroy(cb); } diff --git a/tests/protocols/INDEX.md b/tests/protocols/INDEX.md index 67e49d9d27..3c791f65dd 100644 --- a/tests/protocols/INDEX.md +++ b/tests/protocols/INDEX.md @@ -42,6 +42,7 @@ Wayland 线上请求与事件;本文档规定发出请求后,测试必须观 | [wallpaper-shell-unstable-v1](treeland-wallpaper-shell-unstable-v1/README.md) | I / P | wallpaper shell 与 notifier 生命周期 | | [wallpaper desktop 联合路径](treeland-wallpaper-desktop-v1/README.md) | P / E | manager 配置、shell surface 与真实 output 的关联 | | [show-desktop-v1](treeland-show-desktop-v1/README.md) | E / P | show-desktop 隐藏并恢复真实窗口 | +| [treeland-xwindow-control-unstable-v1](treeland-xwindow-control-unstable-v1/README.md) | P | `set_xwindow_position_relative` 失败回调;成功路径需 Xwayland,当前环境未覆盖 | | [wine-window-management-unstable-v1](treeland-wine-window-management-unstable-v1/README.md) | P / E | 真实 wrapper 的位置与置顶层同步 | | [wine-window-state-unstable-v1](treeland-wine-window-state-unstable-v1/README.md) | P / E | 真实 wrapper 的最小化、attention 与可见性同步 | | [drm (`wl_drm`)](drm/README.md) | P / V(GPU 条件) | wlroots native global、GBM DMA-BUF 与真实纹理 readback | @@ -78,7 +79,7 @@ request stub 算作 request 覆盖;生成的 client-protocol 文件本身不 | --- | --- | --- | --- | | app-id-resolver-v1 | 4 / 4 | `identify_request` 的 id、真实 pidfd;`respond` 转换同一 splash wrapper 并写入 app-id | resolver 断开 fallback、sandbox 后续策略、XWayland | | capture-unstable-v1 | 8 / 11 | `source_ready/failed`、`buffer/buffer_done/ready/failed`;64×64 红色像素读回 | session 的 `start/frame_done`,及 `frame/object/ready/cancel` 持久流;OUTPUT/REGION、cursor、mask | -| dde-shell-v1 | 26 / 27 | checker/active/picker 事件;真实 wrapper DDE 元数据、lockscreen、picker PID | `set_xwindow_position_relative`;multitask 只证明 `toggle` 请求/信号,未证明真实 UI 状态;`shutdown/switch_user` 外部会话流程 | +| dde-shell-v1 | 26 / 27 | checker/active/picker 事件;真实 wrapper DDE 元数据、lockscreen、picker PID | multitask 只证明 `toggle` 请求/信号,未证明真实 UI 状态;`shutdown/switch_user` 外部会话流程(`set_xwindow_position_relative` 已迁移至 `treeland-xwindow-control-unstable-v1`) | | ddm-v1 | 0 / 7 | 无未请求 VT event;生产连接生命周期 | 所有会话/渲染控制 request 与 `switch_to_vt/acquire_vt` 的实际系统流程 | | foreign-toplevel-manager-v2 | 16 / 16 | `toplevel/identifier/closed`;真实最小化、最大化、全屏、焦点与 icon rectangle;最小化与布局状态正交(含取消布局后不卡在最小化) | `pid/title/app_id/output_enter/output_leave/state/done/parent` payload;指定 `wl_output` 的 fullscreen hint、preview 像素;`Tiling` 组合 | | input-manager-unstable-v1 | 1 / 22 | 默认测试仅证明空设备 manager 可绑定;uinput target 断言 Keyboard capability 热插拔 | settings/apply、真实 mouse/touchpad 配置生效、无设备 failed;uinput E 层需显式启用并实际执行 | diff --git a/tests/protocols/treeland-xwindow-control-unstable-v1/README.md b/tests/protocols/treeland-xwindow-control-unstable-v1/README.md new file mode 100644 index 0000000000..9fc61d70b4 --- /dev/null +++ b/tests/protocols/treeland-xwindow-control-unstable-v1/README.md @@ -0,0 +1,40 @@ +# `treeland-xwindow-control-unstable-v1` 测试规范 + +## 范围 + +- XML / interface:`treeland_xwindow_control_v1`(v1) +- 测试源码:`tests/protocols/treeland-xwindow-control-unstable-v1/` +- Fixture:`protocol_test_setup` 注册生产 `XWindowControlInterfaceV1` 全局并创建 + headless output(仅协议/资源级,无需 mapped 窗口) +- 覆盖等级:**P** + +## 必须观察到的结果 + +| 场景 | 客户端动作 | 必须观察到的结果 | 证据层级 | +| --- | --- | --- | --- | +| 初始化 | bind `treeland_xwindow_control_v1` | manager 非空 | P | +| 请求(失败) | `set_xwindow_position_relative`,anchor 为无对应 `SurfaceWrapper` 的裸 `wl_surface`、wid=0 | `wl_callback.done(1)` | P | +| 资源销毁 | `destroy` | 资源销毁,无协议错误 | P | + +## 生产结果 + +`set_xwindow_position_relative` 在服务端解析 anchor `wl_surface` 为 `WSurface`,再交给 +`Helper::setXWindowPositionRelative(wid, wsurface, dx, dy)`。失败路径测试证明:当 anchor +`wl_surface` 没有 `SurfaceWrapper`(裸 surface)且 wid 为 0 时,`Helper` 返回 false, +服务端把结果 `1` 通过新建的 `wl_callback` 回传并销毁该 callback。 + +`XWindowControlInterfaceV1Private` 在 `wl_resource_create` 分配 callback 失败时会调用 +`wl_client_post_no_memory` 并中止该请求,避免对 null callback 解引用导致合成器崩溃;该 +OOM 分支不在测试中强制触发(无法稳定注入 Wayland 内存分配失败)。 + +## 已知边界 / 下一项结果 + +- **成功路径(callback.done(0))未覆盖**:`Helper::setXWindowPositionRelative` 要求 anchor + 拥有真实 `SurfaceWrapper`,且 wid 匹配一个已映射的 `Type::XWayland` `SurfaceWrapper`。 + 生成一个真实的 XWayland 窗口需要可用的 Xwayland 实例;treeland 协议测试在 + `WLR_BACKENDS=headless`、`WLR_RENDERER=pixman` 下运行,Xwayland 启动时 glamor 需要 + GBM Wayland 接口,无 GPU 环境下无法启动,因此无法在当前 CI 中产出 XWayland + `SurfaceWrapper`。成功路径(wid 匹配、目标位置 == anchor.topLeft + (dx,dy))作为下一项 + 待验证结果,前提是具备可运行的 Xwayland 测试环境(GPU 或软件 glamor)。 +- 无效 anchor(客户端传入 null 或非 `wl_surface` 对象)已被服务端守卫为失败结果 + (`callback.done(1)`),但未单列为测试用例;可在具备成功路径环境后补充错误注入用例。