From ade987855f16fe6ff3d10b26c8b7b1053ca39a8a Mon Sep 17 00:00:00 2001 From: rewine Date: Tue, 10 Feb 2026 15:54:50 +0800 Subject: [PATCH] feat: support treeland prelaunch splash manager v2 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. Generate instance UUID early for splash association 3. Track splash objects per instance ID for proper lifecycle management 4. Add closeSplashForInstance and closeAllSplashes methods 5. Implement proper cleanup of splash objects in destructor Log: Added support for treeland prelaunch splash manager v2 with instance-based splash management Influence: 1. Test application launch with prelaunch splash display 2. Verify splash closes when instance is removed normally 3. Test multiple instances of same application with separate splashes 4. Verify splash cleanup during application manager shutdown 5. Test client crash scenarios to ensure orphaned splashes are cleaned up 6. Validate Wayland protocol compatibility with updated v2 interface feat: 支持 treeland 预启动闪屏管理器 v2 1. 从 treeland-prelaunch-splash-v1 升级到 v2 协议 2. 提前生成实例 UUID 用于闪屏关联 3. 按实例 ID 跟踪闪屏对象以实现正确的生命周期管理 4. 添加 closeSplashForInstance 和 closeAllSplashes 方法 5. 在析构函数中实现闪屏对象的正确清理 Log: 新增支持 treeland 预启动闪屏管理器 v2,支持基于实例的闪屏管理 Influence: 1. 测试应用程序启动时的预启动闪屏显示 2. 验证实例正常移除时闪屏正确关闭 3. 测试同一应用程序多个实例的独立闪屏管理 4. 验证应用程序管理器关闭时的闪屏清理 5. 测试客户端崩溃场景确保孤儿闪屏被清理 6. 验证与更新 v2 接口的 Wayland 协议兼容性 --- src/dbus/CMakeLists.txt | 2 +- src/dbus/applicationservice.cpp | 71 +++++++++++++++++++++--------- src/dbus/applicationservice.h | 6 ++- src/dbus/prelaunchsplashhelper.cpp | 40 ++++++++++++++--- src/dbus/prelaunchsplashhelper.h | 32 ++++++++++---- 5 files changed, 116 insertions(+), 35 deletions(-) diff --git a/src/dbus/CMakeLists.txt b/src/dbus/CMakeLists.txt index 9b63cf92..1abaf60a 100644 --- a/src/dbus/CMakeLists.txt +++ b/src/dbus/CMakeLists.txt @@ -21,7 +21,7 @@ endif() qt_generate_wayland_protocol_client_sources(dde_am_dbus FILES - ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-prelaunch-splash-v1.xml + ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-prelaunch-splash-v2.xml ) target_sources(dde_am_dbus PRIVATE diff --git a/src/dbus/applicationservice.cpp b/src/dbus/applicationservice.cpp index 5d22a022..1705ef06 100644 --- a/src/dbus/applicationservice.cpp +++ b/src/dbus/applicationservice.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2023-2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -323,27 +323,10 @@ ApplicationService::Launch(const QString &action, const QStringList &fields, con } } - // Notify the compositor to show a splash screen if in a Wayland session. // Suppress splash for system autostart launches or singleton apps with existing instances. const bool isAutostartLaunch = optionsMap.contains("_autostart") && optionsMap.value("_autostart").toBool(); const bool isSingleton = findEntryValue(DesktopFileEntryKey, "X-Deepin-Singleton", EntryValueType::Boolean).toBool(); const bool singletonWithInstance = isSingleton && !m_Instances.isEmpty(); - if (isAutostartLaunch) { - qCInfo(amPrelaunchSplash) << "Skip prelaunch splash (autostart)" << id(); - } else if (singletonWithInstance) { - qCInfo(amPrelaunchSplash) << "Skip prelaunch splash (singleton with existing instance)" << id(); - } else if (auto *am = parent()) { - if (auto *helper = am->splashHelper()) { - const auto iconVar = findEntryValue(DesktopFileEntryKey, "Icon", EntryValueType::IconString); - const QString iconName = iconVar.isNull() ? QString{} : iconVar.toString(); - qCInfo(amPrelaunchSplash) << "Show prelaunch splash request" << id() << "icon" << iconName; - helper->show(id(), iconName); - } else { - qCInfo(amPrelaunchSplash) << "Skip prelaunch splash (no helper instance)" << id(); - } - } else { - qCWarning(amPrelaunchSplash) << "Skip prelaunch splash (no parent ApplicationManager1Service)" << id(); - } // Those are internal properties, user shouldn't pass them to Application Manager optionsMap.remove("_autostart"); @@ -376,11 +359,32 @@ ApplicationService::Launch(const QString &action, const QStringList &fields, con cmds.push_back("-e"); // run all original execution commands in deepin-terminal } + // Generate instance UUID early so splash and job lambda share the same id. + auto instanceRandomUUID = QUuid::createUuid().toString(QUuid::Id128); + + // Notify the compositor to show a splash screen (after validation passes). + if (isAutostartLaunch) { + qCInfo(amPrelaunchSplash) << "Skip prelaunch splash (autostart)" << id(); + } else if (singletonWithInstance) { + qCInfo(amPrelaunchSplash) << "Skip prelaunch splash (singleton with existing instance)" << id(); + } else if (auto *am = parent()) { + if (auto *helper = am->splashHelper()) { + const auto iconVar = findEntryValue(DesktopFileEntryKey, "Icon", EntryValueType::IconString); + const QString iconName = iconVar.isNull() ? QString{} : iconVar.toString(); + qCInfo(amPrelaunchSplash) << "Show prelaunch splash request" << id() << "instance" << instanceRandomUUID << "icon" << iconName; + helper->show(id(), instanceRandomUUID, iconName); + m_splashInstanceIds.insert(instanceRandomUUID); + } else { + qCInfo(amPrelaunchSplash) << "Skip prelaunch splash (no helper instance)" << id(); + } + } else { + qCWarning(amPrelaunchSplash) << "Skip prelaunch splash (no parent ApplicationManager1Service)" << id(); + } + auto &jobManager = parent()->jobManager(); return jobManager.addJob( m_applicationPath.path(), - [this, task, cmds = std::move(cmds)](const QVariant &value) -> QVariant { // do not change it to mutable lambda - auto instanceRandomUUID = QUuid::createUuid().toString(QUuid::Id128); + [this, task, instanceRandomUUID, cmds = std::move(cmds)](const QVariant &value) -> QVariant { // do not change it to mutable lambda auto objectPath = m_applicationPath.path() + "/" + instanceRandomUUID; auto newCommands = cmds; @@ -986,6 +990,7 @@ bool ApplicationService::addOneInstance(const QString &instanceId, void ApplicationService::removeOneInstance(const QDBusObjectPath &instance) noexcept { if (auto it = m_Instances.constFind(instance); it != m_Instances.cend()) { + closeSplashForInstance(it.value()->instanceId()); emit InterfacesRemoved(instance, getChildInterfacesFromObject(it->data())); unregisterObjectFromDBus(instance.path()); m_Instances.remove(instance); @@ -1008,6 +1013,32 @@ void ApplicationService::detachAllInstance() noexcept } m_Instances.clear(); + closeAllSplashes(); +} + +void ApplicationService::closeSplashForInstance(const QString &instanceId) noexcept +{ + if (!m_splashInstanceIds.remove(instanceId)) { + return; + } + + if (auto *am = parent()) { + if (auto *helper = am->splashHelper()) { + helper->closeSplash(instanceId); + } else { + qCInfo(amPrelaunchSplash) << "Skip closeSplash (no helper instance)" << id(); + } + } else { + qCWarning(amPrelaunchSplash) << "Skip closeSplash (no parent ApplicationManager1Service)" << id(); + } +} + +void ApplicationService::closeAllSplashes() noexcept +{ + const auto ids = m_splashInstanceIds; + for (const auto &instanceId : ids) { + closeSplashForInstance(instanceId); + } } QDBusObjectPath ApplicationService::findInstance(const QString &instanceId) const diff --git a/src/dbus/applicationservice.h b/src/dbus/applicationservice.h index 208679b1..526e6cbc 100644 --- a/src/dbus/applicationservice.h +++ b/src/dbus/applicationservice.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2023-2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -199,12 +200,15 @@ public Q_SLOTS: DesktopFile m_desktopSource; QSharedPointer m_entry{nullptr}; QHash> m_Instances; + QSet m_splashInstanceIds; void updateAfterLaunch(bool isLaunch) noexcept; static bool shouldBeShown(const std::unique_ptr &entry) noexcept; [[nodiscard]] bool autostartCheck(const QString &filePath) const noexcept; void setAutostartSource(AutostartSource &&source) noexcept; void appendExtraEnvironments(QVariantMap &runtimeOptions) const noexcept; void processCompatibility(const QString &action, QVariantMap &options, QString &execStr); + void closeSplashForInstance(const QString &instanceId) noexcept; + void closeAllSplashes() noexcept; [[nodiscard]] ApplicationManager1Service *parent() { return dynamic_cast(QObject::parent()); } [[nodiscard]] const ApplicationManager1Service *parent() const { diff --git a/src/dbus/prelaunchsplashhelper.cpp b/src/dbus/prelaunchsplashhelper.cpp index 629c7b76..0f94ba01 100644 --- a/src/dbus/prelaunchsplashhelper.cpp +++ b/src/dbus/prelaunchsplashhelper.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025-2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later @@ -22,7 +22,13 @@ PrelaunchSplashHelper::PrelaunchSplashHelper() { } -PrelaunchSplashHelper::~PrelaunchSplashHelper() = default; +PrelaunchSplashHelper::~PrelaunchSplashHelper() +{ + for (auto *splash : std::as_const(m_splashObjects)) { + splash->destroy(); + } + qDeleteAll(m_splashObjects); +} static const struct wl_buffer_listener kBufferListener = { PrelaunchSplashHelper::bufferRelease, @@ -93,7 +99,7 @@ wl_buffer *PrelaunchSplashHelper::buildIconBuffer(const QIcon &icon) return createBufferWithPainter(iconSize, dpr, icon); } -void PrelaunchSplashHelper::show(const QString &appId, const QString &iconName) +void PrelaunchSplashHelper::show(const QString &appId, const QString &instanceId, const QString &iconName) { if (!isActive()) { qCWarning(amPrelaunchSplash, "Skip prelaunch splash (extension inactive): %s", qPrintable(appId)); @@ -110,10 +116,34 @@ void PrelaunchSplashHelper::show(const QString &appId, const QString &iconName) } } + // If this instance already has a splash, skip creating a new one. + if (m_splashObjects.contains(instanceId)) { + qCInfo(amPrelaunchSplash, "Instance %s already has an active splash, skipping", qPrintable(instanceId)); + return; + } + // Keep previously sent buffers alive; compositor releases them asynchronously. wl_buffer *buffer = buildIconBuffer(icon); - create_splash(appId, QStringLiteral("dde-application-manager"), buffer); - qCInfo(amPrelaunchSplash, "Sent create_splash for %s %s", qPrintable(appId), buffer ? "with icon buffer" : "without icon buffer"); + + auto *splash = new QtWayland::treeland_prelaunch_splash_v2(); + splash->init(create_splash(appId, instanceId, QStringLiteral("dde-application-manager"), buffer)); + m_splashObjects.insert(instanceId, splash); + qCInfo(amPrelaunchSplash, "Sent create_splash for %s instance %s %s", + qPrintable(appId), qPrintable(instanceId), + buffer ? "with icon buffer" : "without icon buffer"); +} + +void PrelaunchSplashHelper::closeSplash(const QString &instanceId) +{ + auto *splash = m_splashObjects.take(instanceId); + if (!splash) { + qCInfo(amPrelaunchSplash, "No active splash for instance %s", qPrintable(instanceId)); + return; + } + + splash->destroy(); + delete splash; + qCInfo(amPrelaunchSplash, "Destroyed splash for instance %s", qPrintable(instanceId)); } /*static*/ void PrelaunchSplashHelper::bufferRelease(void *data, wl_buffer *buffer) diff --git a/src/dbus/prelaunchsplashhelper.h b/src/dbus/prelaunchsplashhelper.h index 3f227900..a105ed81 100644 --- a/src/dbus/prelaunchsplashhelper.h +++ b/src/dbus/prelaunchsplashhelper.h @@ -1,18 +1,20 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025-2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later #ifndef PRELAUNCHSPLASHHELPER_H #define PRELAUNCHSPLASHHELPER_H -#include -#include +#include #include #include +#include +#include #include #include -#include "qwayland-treeland-prelaunch-splash-v1.h" +#include "qwayland-treeland-prelaunch-splash-v2.h" + // Wayland C types struct wl_buffer; namespace QtWaylandClient { @@ -25,12 +27,17 @@ Q_DECLARE_LOGGING_CATEGORY(amPrelaunchSplash) * @brief Helper for sending prelaunch splash requests over Wayland. * * Creates wl_buffers from application icons using shared memory buffers and - * sends them to the compositor via treeland_prelaunch_splash_manager_v1. + * sends them to the compositor via treeland_prelaunch_splash_manager_v2. + * + * Each create_splash call returns a treeland_prelaunch_splash_v2 object. + * Destroying the object dismisses the corresponding splash. Objects are + * tracked per instance_id so non-singleton apps can have multiple splashes. + * * Not thread-safe; use from the Qt GUI thread. */ class PrelaunchSplashHelper : public QWaylandClientExtensionTemplate - , public QtWayland::treeland_prelaunch_splash_manager_v1 + , public QtWayland::treeland_prelaunch_splash_manager_v2 { Q_OBJECT public: @@ -38,10 +45,16 @@ class PrelaunchSplashHelper ~PrelaunchSplashHelper(); /** - * Show a prelaunch splash for the given app id and icon name. + * Show a prelaunch splash for the given app/instance and icon name. * iconName may be a theme name or absolute path; empty means no icon. */ - void show(const QString &appId, const QString &iconName); + void show(const QString &appId, const QString &instanceId, const QString &iconName); + + /** + * Close the prelaunch splash for the given instance. + * Destroys the splash object returned by create_splash. + */ + void closeSplash(const QString &instanceId); /** * @brief Wayland wl_buffer_listener callback for buffer release. * @@ -56,6 +69,9 @@ class PrelaunchSplashHelper void handleBufferRelease(wl_buffer *buffer); std::vector> m_iconBuffers; // keep alive until compositor releases + + // Active splash objects keyed by instance_id + QHash m_splashObjects; }; #endif // PRELAUNCHSPLASHHELPER_H