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) { 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"