Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/dbus/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
71 changes: 51 additions & 20 deletions src/dbus/applicationservice.cpp
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down
6 changes: 5 additions & 1 deletion src/dbus/applicationservice.h
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -18,6 +18,7 @@
#include <QFile>
#include <QMap>
#include <QObject>
#include <QSet>
#include <QSharedPointer>
#include <QString>
#include <QTextStream>
Expand Down Expand Up @@ -199,12 +200,15 @@ public Q_SLOTS:
DesktopFile m_desktopSource;
QSharedPointer<DesktopEntry> m_entry{nullptr};
QHash<QDBusObjectPath, QSharedPointer<InstanceService>> m_Instances;
QSet<QString> m_splashInstanceIds;
void updateAfterLaunch(bool isLaunch) noexcept;
static bool shouldBeShown(const std::unique_ptr<DesktopEntry> &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<ApplicationManager1Service *>(QObject::parent()); }
[[nodiscard]] const ApplicationManager1Service *parent() const
{
Expand Down
40 changes: 35 additions & 5 deletions src/dbus/prelaunchsplashhelper.cpp
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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,
Expand Down Expand Up @@ -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));
Expand All @@ -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",
Comment on lines +128 to +131
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m_splashObjects.insert(instanceId, splash) will overwrite any existing entry for the same instanceId without destroying/deleting the previous splash object, which leaks the wrapper and may leave the compositor-side splash undisposed. Handle the "already has splash for this instance" case by closing/replacing safely before inserting.

Copilot uses AI. Check for mistakes.
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)
Expand Down
32 changes: 24 additions & 8 deletions src/dbus/prelaunchsplashhelper.h
Original file line number Diff line number Diff line change
@@ -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 <QObject>
#include <QtWaylandClient/QWaylandClientExtension>
#include <QHash>
#include <QIcon>
#include <QLoggingCategory>
#include <QObject>
#include <QtWaylandClient/QWaylandClientExtension>
#include <memory>
#include <vector>

#include "qwayland-treeland-prelaunch-splash-v1.h"
#include "qwayland-treeland-prelaunch-splash-v2.h"

// Wayland C types
struct wl_buffer;
namespace QtWaylandClient {
Expand All @@ -25,23 +27,34 @@ 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<PrelaunchSplashHelper>
, public QtWayland::treeland_prelaunch_splash_manager_v1
, public QtWayland::treeland_prelaunch_splash_manager_v2
{
Q_OBJECT
public:
explicit 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.
*
Expand All @@ -56,6 +69,9 @@ class PrelaunchSplashHelper
void handleBufferRelease(wl_buffer *buffer);

std::vector<std::unique_ptr<QtWaylandClient::QWaylandShmBuffer>> m_iconBuffers; // keep alive until compositor releases

// Active splash objects keyed by instance_id
QHash<QString, QtWayland::treeland_prelaunch_splash_v2 *> m_splashObjects;
};

#endif // PRELAUNCHSPLASHHELPER_H