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