From cbfe71ea9f79fbb33fedae20d6aa77717dd7215f Mon Sep 17 00:00:00 2001 From: rewine Date: Tue, 10 Feb 2026 15:50:11 +0800 Subject: [PATCH 1/2] feat: add prelaunch splash v2 protocol support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Upgrade from treeland-prelaunch-splash-v1 to v2 protocol 2. Add instanceId parameter to support multiple instances of same app 3. Implement splash close notification when client crashes or closes early 4. Add SplashResource class to manage individual splash instances 5. Handle splash destruction requests from clients Log: Added support for treeland_prelaunch_splash_manager_v2 protocol Influence: 1. Test prelaunch splash creation with appId and instanceId parameters 2. Verify splash closes properly when client requests destruction 3. Test multiple instances of same app with different instanceIds 4. Verify cleanup when client crashes or closes unexpectedly 5. Test backward compatibility with existing splash functionality feat: 添加 prelaunch splash v2 协议支持 1. 从 treeland-prelaunch-splash-v1 升级到 v2 协议 2. 添加 instanceId 参数以支持同一应用的多个实例 3. 实现客户端崩溃或提前关闭时的闪屏关闭通知 4. 新增 SplashResource 类管理单个闪屏实例 5. 处理客户端发起的闪屏销毁请求 Log: 新增 treeland_prelaunch_splash_manager_v2 协议支持 Influence: 1. 测试使用 appId 和 instanceId 参数创建预启动闪屏 2. 验证客户端请求销毁时闪屏正确关闭 3. 测试同一应用多个实例使用不同 instanceId 的情况 4. 验证客户端崩溃或意外关闭时的清理机制 5. 测试与现有闪屏功能的向后兼容性 --- src/core/shellhandler.cpp | 27 +++++++ src/core/shellhandler.h | 6 +- src/modules/prelaunch-splash/CMakeLists.txt | 8 +- .../prelaunch-splash/prelaunchsplash.cpp | 79 ++++++++++++++++--- .../prelaunch-splash/prelaunchsplash.h | 5 +- src/seat/helper.cpp | 13 ++- 6 files changed, 117 insertions(+), 21 deletions(-) diff --git a/src/core/shellhandler.cpp b/src/core/shellhandler.cpp index 23921d6ae..b6ee39487 100644 --- a/src/core/shellhandler.cpp +++ b/src/core/shellhandler.cpp @@ -100,8 +100,11 @@ void ShellHandler::updateWrapperContainer(SurfaceWrapper *wrapper, WSurface *par // Prelaunch splash request: create a SurfaceWrapper that is not yet bound to a shellSurface void ShellHandler::handlePrelaunchSplashRequested(const QString &appId, + const QString &instanceId, QW_NAMESPACE::qw_buffer *iconBuffer) { + Q_UNUSED(instanceId); // TODO: will be provided by AM DBus in future + if (!Helper::instance()->globalConfig()->enablePrelaunchSplash()) return; if (!m_appIdResolverManager) @@ -140,6 +143,7 @@ void ShellHandler::handlePrelaunchSplashRequested(const QString &appId, std::bind(&ShellHandler::createPrelaunchSplash, this, appId, + instanceId, iconBuffer, std::placeholders::_1, std::placeholders::_2, @@ -150,12 +154,15 @@ void ShellHandler::handlePrelaunchSplashRequested(const QString &appId, } void ShellHandler::createPrelaunchSplash(const QString &appId, + const QString &instanceId, QW_NAMESPACE::qw_buffer *iconBuffer, const QSize &lastSize, const QString &darkPalette, const QString &lightPalette, qlonglong splashThemeType) { + Q_UNUSED(instanceId); // TODO: will be provided by AM DBus in future + if (!m_pendingPrelaunchAppIds.contains(appId)) { if (iconBuffer) { iconBuffer->unlock(); @@ -210,6 +217,26 @@ void ShellHandler::createPrelaunchSplash(const QString &appId, } } +void ShellHandler::handlePrelaunchSplashClosed(const QString &appId, const QString &instanceId) +{ + Q_UNUSED(instanceId); // TODO: will be provided by AM DBus in future + + // Remove pending prelaunch request if it hasn't created a wrapper yet + m_pendingPrelaunchAppIds.remove(appId); + + // Find and destroy any existing prelaunch wrapper with the matching appId + for (int i = 0; i < m_prelaunchWrappers.size(); ++i) { + auto *wrapper = m_prelaunchWrappers[i]; + if (wrapper->appId() == appId) { + qCDebug(treelandShell) + << "Client requested close_splash, destroy wrapper appId=" << appId; + m_prelaunchWrappers.removeAt(i); + m_rootSurfaceContainer->destroyForSurface(wrapper); + return; + } + } +} + Workspace *ShellHandler::workspace() const { return m_workspace; diff --git a/src/core/shellhandler.h b/src/core/shellhandler.h index bfc88c2fd..4129cbb0c 100644 --- a/src/core/shellhandler.h +++ b/src/core/shellhandler.h @@ -108,8 +108,12 @@ private Q_SLOTS: const QByteArray &value); // Prelaunch splash related: creates a prelaunch SurfaceWrapper when // PrelaunchSplash::splashRequested - void handlePrelaunchSplashRequested(const QString &appId, QW_NAMESPACE::qw_buffer *iconBuffer); + void handlePrelaunchSplashRequested(const QString &appId, + const QString &instanceId, + QW_NAMESPACE::qw_buffer *iconBuffer); + void handlePrelaunchSplashClosed(const QString &appId, const QString &instanceId); void createPrelaunchSplash(const QString &appId, + const QString &instanceId, QW_NAMESPACE::qw_buffer *iconBuffer, const QSize &lastSize, const QString &darkPalette, diff --git a/src/modules/prelaunch-splash/CMakeLists.txt b/src/modules/prelaunch-splash/CMakeLists.txt index 47ae39bf0..763ec7c1c 100644 --- a/src/modules/prelaunch-splash/CMakeLists.txt +++ b/src/modules/prelaunch-splash/CMakeLists.txt @@ -1,8 +1,8 @@ -find_package(TreelandProtocols REQUIRED) +find_package(TreelandProtocols 0.5.5 REQUIRED) local_qtwayland_server_protocol_treeland(libtreeland - PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-prelaunch-splash-v1.xml - BASENAME treeland-prelaunch-splash-v1 + PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-prelaunch-splash-v2.xml + BASENAME treeland-prelaunch-splash-v2 ) impl_treeland( @@ -11,7 +11,7 @@ impl_treeland( SOURCE prelaunchsplash.h prelaunchsplash.cpp - ${CMAKE_BINARY_DIR}/src/modules/prelaunch-splash/wayland-treeland-prelaunch-splash-v1-server-protocol.c + ${CMAKE_BINARY_DIR}/src/modules/prelaunch-splash/wayland-treeland-prelaunch-splash-v2-server-protocol.c INCLUDE $ LINK diff --git a/src/modules/prelaunch-splash/prelaunchsplash.cpp b/src/modules/prelaunch-splash/prelaunchsplash.cpp index 8240116e5..174ac76e3 100644 --- a/src/modules/prelaunch-splash/prelaunchsplash.cpp +++ b/src/modules/prelaunch-splash/prelaunchsplash.cpp @@ -1,7 +1,7 @@ // Copyright (C) 2025 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 "prelaunchsplash.h" -#include "qwayland-server-treeland-prelaunch-splash-v1.h" +#include "qwayland-server-treeland-prelaunch-splash-v2.h" #include @@ -19,9 +19,47 @@ QW_USE_NAMESPACE Q_LOGGING_CATEGORY(prelaunchSplash, "treeland.prelaunchsplash", QtInfoMsg) -#define TREELAND_PRELAUNCH_SPLASH_MANAGER_V1_VERSION 1 +#define TREELAND_PRELAUNCH_SPLASH_MANAGER_V2_VERSION 1 -class PrelaunchSplashPrivate : public QtWaylandServer::treeland_prelaunch_splash_manager_v1 +class SplashResource : public QtWaylandServer::treeland_prelaunch_splash_v2 +{ +public: + SplashResource(PrelaunchSplash *owner, + struct ::wl_resource *resource, + const QString &appId, + const QString &instanceId) + : QtWaylandServer::treeland_prelaunch_splash_v2(resource) + , m_owner(owner) + , m_appId(appId) + , m_instanceId(instanceId) + { + } + + QString appId() const { return m_appId; } + QString instanceId() const { return m_instanceId; } + +protected: + void treeland_prelaunch_splash_v2_destroy(Resource *resource) override + { + qCInfo(prelaunchSplash) + << "Client destroy splash appId=" << m_appId << " instanceId=" << m_instanceId; + wl_resource_destroy(resource->handle); + } + + void treeland_prelaunch_splash_v2_destroy_resource(Resource *) override + { + // Covers both explicit destroy() and client crash/disconnect + Q_EMIT m_owner->splashCloseRequested(m_appId, m_instanceId); + delete this; + } + +private: + PrelaunchSplash *m_owner; + QString m_appId; + QString m_instanceId; +}; + +class PrelaunchSplashPrivate : public QtWaylandServer::treeland_prelaunch_splash_manager_v2 { public: explicit PrelaunchSplashPrivate(PrelaunchSplash *q) @@ -35,22 +73,38 @@ class PrelaunchSplashPrivate : public QtWaylandServer::treeland_prelaunch_splash } protected: - void treeland_prelaunch_splash_manager_v1_destroy(Resource *resource) override + void treeland_prelaunch_splash_manager_v2_destroy(Resource *resource) override { wl_resource_destroy(resource->handle); } - void treeland_prelaunch_splash_manager_v1_create_splash( + void treeland_prelaunch_splash_manager_v2_create_splash( Resource *resource, + uint32_t id, const QString &app_id, + const QString &instance_id, const QString &sandboxEngineName, struct ::wl_resource *icon_buffer) override { - Q_UNUSED(resource); qCInfo(prelaunchSplash) - << "create_splash request sandbox=" << sandboxEngineName << " app_id=" << app_id; + << "create_splash request sandbox=" << sandboxEngineName + << " app_id=" << app_id << " instance_id=" << instance_id; + + auto *splashResource = + wl_resource_create(resource->client(), + &treeland_prelaunch_splash_v2_interface, + wl_resource_get_version(resource->handle), + id); + if (!splashResource) { + wl_resource_post_no_memory(resource->handle); + return; + } + + // SplashResource self-destructs via destroy_resource callback + new SplashResource(q, splashResource, app_id, instance_id); + auto qb = icon_buffer ? QW_NAMESPACE::qw_buffer::try_from_resource(icon_buffer) : nullptr; - Q_EMIT q->splashRequested(app_id, qb); + Q_EMIT q->splashRequested(app_id, instance_id, qb); } private: @@ -69,9 +123,8 @@ void PrelaunchSplash::create(WAYLIB_SERVER_NAMESPACE::WServer *server) { if (d->isGlobal()) return; - // server->handle() returns qw_display* which can be implicitly converted to wl_display* - d->init(*server->handle(), TREELAND_PRELAUNCH_SPLASH_MANAGER_V1_VERSION); - qCDebug(prelaunchSplash) << "PrelaunchSplash global created"; + d->init(*server->handle(), TREELAND_PRELAUNCH_SPLASH_MANAGER_V2_VERSION); + qCDebug(prelaunchSplash) << "PrelaunchSplash v2 global created"; } void PrelaunchSplash::destroy(WAYLIB_SERVER_NAMESPACE::WServer *server) @@ -80,7 +133,7 @@ void PrelaunchSplash::destroy(WAYLIB_SERVER_NAMESPACE::WServer *server) if (!d->isGlobal()) return; d->globalRemove(); - qCDebug(prelaunchSplash) << "PrelaunchSplash global removed"; + qCDebug(prelaunchSplash) << "PrelaunchSplash v2 global removed"; } wl_global *PrelaunchSplash::global() const @@ -90,7 +143,7 @@ wl_global *PrelaunchSplash::global() const QByteArrayView PrelaunchSplash::interfaceName() const { - return QtWaylandServer::treeland_prelaunch_splash_manager_v1::interfaceName(); + return QtWaylandServer::treeland_prelaunch_splash_manager_v2::interfaceName(); } // End of file diff --git a/src/modules/prelaunch-splash/prelaunchsplash.h b/src/modules/prelaunch-splash/prelaunchsplash.h index 9003916eb..3aeb6bc89 100644 --- a/src/modules/prelaunch-splash/prelaunchsplash.h +++ b/src/modules/prelaunch-splash/prelaunchsplash.h @@ -37,7 +37,10 @@ class PrelaunchSplash ~PrelaunchSplash() override; Q_SIGNALS: - void splashRequested(const QString &appId, QW_NAMESPACE::qw_buffer *iconBuffer); + void splashRequested(const QString &appId, + const QString &instanceId, + QW_NAMESPACE::qw_buffer *iconBuffer); + void splashCloseRequested(const QString &appId, const QString &instanceId); protected: // WServerInterface void create(WAYLIB_SERVER_NAMESPACE::WServer *server) override; diff --git a/src/seat/helper.cpp b/src/seat/helper.cpp index 17e7652f4..3d4ccb4aa 100644 --- a/src/seat/helper.cpp +++ b/src/seat/helper.cpp @@ -1229,9 +1229,18 @@ void Helper::init(Treeland::Treeland *treeland) connect(m_prelaunchSplash, &PrelaunchSplash::splashRequested, m_shellHandler, - [this](const QString &appId, QW_NAMESPACE::qw_buffer *iconBuffer) { + [this](const QString &appId, + const QString &instanceId, + QW_NAMESPACE::qw_buffer *iconBuffer) { if (m_shellHandler) - m_shellHandler->handlePrelaunchSplashRequested(appId, iconBuffer); + m_shellHandler->handlePrelaunchSplashRequested(appId, instanceId, iconBuffer); + }); + connect(m_prelaunchSplash, + &PrelaunchSplash::splashCloseRequested, + m_shellHandler, + [this](const QString &appId, const QString &instanceId) { + if (m_shellHandler) + m_shellHandler->handlePrelaunchSplashClosed(appId, instanceId); }); connect(m_ddeShellV1, &DDEShellManagerInterfaceV1::toggleMultitaskview, this, [this] { if (m_multitaskView) { From 1d8b6b9a43ec832ee6d0dffdf01705c25af30349 Mon Sep 17 00:00:00 2001 From: rewine Date: Tue, 10 Feb 2026 17:14:53 +0800 Subject: [PATCH 2/2] feat: add prelaunch splash protocol test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added new test suite for prelaunch splash protocol functionality 1. Created new test directory test_protocol_prelaunch-splash 2. Implemented Qt test framework with CMake configuration 3. Added test cases for protocol creation and signal verification 4. Set up offscreen testing environment with 3-second timeout 5. Tests verify PrelaunchSplash protocol attachment to server 6. Validates splashRequested and splashCloseRequested signals This test ensures the prelaunch splash protocol works correctly with the Wayland server integration and provides automated validation for the splash screen functionality. Influence: 1. Run the new test executable to verify prelaunch splash protocol 2. Check test results for protocol attachment and signal functionality 3. Verify no regressions in existing protocol tests 4. Confirm test timeout behavior with 3-second limit 5. Validate offscreen rendering environment setup feat: 添加预启动闪屏协议测试 新增预启动闪屏协议功能的测试套件 1. 创建新的测试目录 test_protocol_prelaunch-splash 2. 使用 CMake 配置实现 Qt 测试框架 3. 添加协议创建和信号验证的测试用例 4. 设置离屏测试环境,超时时间为3秒 5. 测试验证 PrelaunchSplash 协议正确附加到服务器 6. 验证 splashRequested 和 splashCloseRequested 信号 该测试确保预启动闪屏协议与 Wayland 服务器集成正常工作,并为闪屏功能提供 自动化验证。 Influence: 1. 运行新的测试可执行文件验证预启动闪屏协议 2. 检查测试结果,确认协议附加和信号功能正常 3. 验证现有协议测试无回归问题 4. 确认3秒超时限制的测试行为 5. 验证离屏渲染环境设置正确 --- tests/CMakeLists.txt | 1 + .../CMakeLists.txt | 19 ++++++ tests/test_protocol_prelaunch-splash/main.cpp | 65 +++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 tests/test_protocol_prelaunch-splash/CMakeLists.txt create mode 100644 tests/test_protocol_prelaunch-splash/main.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b2a571676..5f127b0db 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,3 +7,4 @@ add_subdirectory(test_protocol_shortcut) add_subdirectory(test_protocol_virtual-output) add_subdirectory(test_protocol_wallpaper-color) add_subdirectory(test_protocol_window-management) +add_subdirectory(test_protocol_prelaunch-splash) diff --git a/tests/test_protocol_prelaunch-splash/CMakeLists.txt b/tests/test_protocol_prelaunch-splash/CMakeLists.txt new file mode 100644 index 000000000..ce4ab5d12 --- /dev/null +++ b/tests/test_protocol_prelaunch-splash/CMakeLists.txt @@ -0,0 +1,19 @@ +find_package(Qt6 REQUIRED COMPONENTS Test) + +add_executable(test_protocol_prelaunch-splash main.cpp) + +target_link_libraries(test_protocol_prelaunch-splash + PRIVATE + libtreeland + Qt::Test +) + +add_test(NAME test_protocol_prelaunch-splash COMMAND test_protocol_prelaunch-splash) + +set_property(TEST test_protocol_prelaunch-splash PROPERTY + ENVIRONMENT "QT_QPA_PLATFORM=offscreen" +) + +set_property(TEST test_protocol_prelaunch-splash PROPERTY + TIMEOUT 3 +) diff --git a/tests/test_protocol_prelaunch-splash/main.cpp b/tests/test_protocol_prelaunch-splash/main.cpp new file mode 100644 index 000000000..df5804a39 --- /dev/null +++ b/tests/test_protocol_prelaunch-splash/main.cpp @@ -0,0 +1,65 @@ +// Copyright (C) 2025 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/prelaunch-splash/prelaunchsplash.h" + +#include + +#include +#include +#include + +class PrelaunchSplashTest : public QObject +{ + Q_OBJECT + + WAYLIB_SERVER_NAMESPACE::WServer *m_server = nullptr; + PrelaunchSplash *m_protocol = nullptr; + +public: + PrelaunchSplashTest(QObject *parent = nullptr) + : QObject(parent) + { + } + +private Q_SLOTS: + + void initTestCase() + { + m_server = new WAYLIB_SERVER_NAMESPACE::WServer(); + } + + void testCreate() + { + m_protocol = m_server->attach(); + QVERIFY(m_protocol != nullptr); + } + + void verifyPrelaunchSplash() + { + QVERIFY(m_protocol != nullptr); + } + + void testSignals() + { + QVERIFY(m_protocol != nullptr); + + QSignalSpy splashRequestedSpy(m_protocol, + &PrelaunchSplash::splashRequested); + QVERIFY(splashRequestedSpy.isValid()); + + QSignalSpy splashCloseSpy(m_protocol, + &PrelaunchSplash::splashCloseRequested); + QVERIFY(splashCloseSpy.isValid()); + } + + void cleanupTestCase() + { + m_server->deleteLater(); + m_server = nullptr; + m_protocol = nullptr; + } +}; + +QTEST_MAIN(PrelaunchSplashTest) +#include "main.moc"