Skip to content
Open
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: 2 additions & 0 deletions panels/dock/taskmanager/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ add_library(dock-taskmanager SHARED ${DBUS_INTERFACES}
dockgroupmodel.h
hoverpreviewproxymodel.cpp
hoverpreviewproxymodel.h
launchdurationreporter.cpp
launchdurationreporter.h
taskmanager.cpp
taskmanager.h
treelandwindow.cpp
Expand Down
241 changes: 241 additions & 0 deletions panels/dock/taskmanager/launchdurationreporter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include "globals.h"
#include "launchdurationreporter.h"

#ifdef HAVE_DDE_API_EVENTLOGGER
#include <dde-api/eventlogger.hpp>
#endif

#include <QDBusConnection>

Check warning on line 12 in panels/dock/taskmanager/launchdurationreporter.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDBusConnection> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDBusInterface>

Check warning on line 13 in panels/dock/taskmanager/launchdurationreporter.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDBusInterface> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDBusObjectPath>

Check warning on line 14 in panels/dock/taskmanager/launchdurationreporter.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDBusObjectPath> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDBusReply>

Check warning on line 15 in panels/dock/taskmanager/launchdurationreporter.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDBusReply> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDateTime>

Check warning on line 16 in panels/dock/taskmanager/launchdurationreporter.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDateTime> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QJsonDocument>

Check warning on line 17 in panels/dock/taskmanager/launchdurationreporter.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QJsonDocument> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QJsonObject>

Check warning on line 18 in panels/dock/taskmanager/launchdurationreporter.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QJsonObject> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QJsonArray>

Check warning on line 19 in panels/dock/taskmanager/launchdurationreporter.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QJsonArray> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QLoggingCategory>

Check warning on line 20 in panels/dock/taskmanager/launchdurationreporter.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QLoggingCategory> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QFile>

Check warning on line 21 in panels/dock/taskmanager/launchdurationreporter.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QFile> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QProcess>
#include <QThread>
#include <QtConcurrent>

Q_LOGGING_CATEGORY(launchDurationReporter, "org.deepin.dde.shell.dock.launchDurationReporter")

namespace {

using dock::escapeToObjectPath;

constexpr auto kAmService = "org.desktopspec.ApplicationManager1";
constexpr auto kApplicationIface = "org.desktopspec.ApplicationManager1.Application";
constexpr auto kInstanceIface = "org.desktopspec.ApplicationManager1.Instance";
constexpr int kLinglongCacheTTLSeconds = 1800; // 30 minutes
constexpr int kDebCacheTTLSeconds = 1800; // 30 minutes

struct InstanceInfo {
QString instanceId;
QString launchType;
};

QList<InstanceInfo> queryInstances(const QString &desktopId)
{
QList<InstanceInfo> result;
auto appPath = QStringLiteral("/org/desktopspec/ApplicationManager1/%1").arg(escapeToObjectPath(desktopId));

QDBusInterface appIface(QString::fromUtf8(kAmService),
appPath,
QStringLiteral("org.freedesktop.DBus.Properties"),
QDBusConnection::sessionBus());
appIface.setTimeout(1000); // 1 second timeout
QDBusReply<QVariant> reply = appIface.call(QStringLiteral("Get"),
QString::fromUtf8(kApplicationIface),
QStringLiteral("Instances"));
if (!reply.isValid()) {
qCDebug(launchDurationReporter) << "[DockIconTiming] queryInstances failed for" << desktopId << ":" << reply.error().message();
return result;
}

const auto paths = qdbus_cast<QList<QDBusObjectPath>>(reply.value());
for (const auto &path : paths) {
QDBusInterface instIface(QString::fromUtf8(kAmService),
path.path(),
QStringLiteral("org.freedesktop.DBus.Properties"),
QDBusConnection::sessionBus());
instIface.setTimeout(1000); // 1 second timeout

InstanceInfo info;
info.instanceId = path.path().section(QLatin1Char('/'), -1);

auto launchTypeReply = instIface.call(QStringLiteral("Get"),
QString::fromUtf8(kInstanceIface),
QStringLiteral("LaunchType"));
if (launchTypeReply.type() == QDBusMessage::ReplyMessage) {
info.launchType = qdbus_cast<QDBusVariant>(launchTypeReply.arguments().constFirst()).variant().toString();
}
if (info.launchType.isEmpty()) {
info.launchType = QStringLiteral("unknown");
}

result.append(info);
}
return result;
}

QHash<QString, QString> loadAllLinglongVersions()
{
QHash<QString, QString> result;

QProcess proc;
proc.start(QStringLiteral("ll-cli"), {QStringLiteral("list"), QStringLiteral("--type"), QStringLiteral("app")});
if (!proc.waitForFinished(3000)) {
qCWarning(launchDurationReporter) << "ll-cli list timeout";
return result;
}

if (proc.exitCode() != 0) {
qCWarning(launchDurationReporter) << "ll-cli list failed, exitCode:" << proc.exitCode();
return result;
}

QString output = QString::fromUtf8(proc.readAllStandardOutput());
QStringList lines = output.split(QLatin1Char('\n'), Qt::SkipEmptyParts);

// Skip header line
for (int i = 1; i < lines.size(); ++i) {
QStringList columns = lines[i].simplified().split(QLatin1Char(' '));
if (columns.size() >= 3) {
QString name = columns[1]; // 名称 column
QString version = columns[2]; // 版本 column
if (!name.isEmpty()) {
result.insert(name, version);
}
}
}

return result;
}

}

namespace dock {

LaunchDurationReporter::LaunchDurationReporter(QObject *parent)
: QObject(parent)
{
m_workerPool.setMaxThreadCount(1);
}

LaunchDurationReporter::~LaunchDurationReporter()
{
m_workerPool.waitForDone();
}

void LaunchDurationReporter::reportWindowAppeared(const QString &desktopId)
{
if (desktopId.isEmpty()) {
return;
}

auto future = QtConcurrent::run(&m_workerPool, [this, desktopId]() {
// Execute D-Bus query sequentially in worker thread
auto instances = queryInstances(desktopId);

QString uniqueId;
QString launchType = QStringLiteral("unknown");
if (!instances.isEmpty()) {
const auto &latest = instances.constLast();
uniqueId = latest.instanceId;
launchType = latest.launchType;
}

if (uniqueId.isEmpty()) {
return;
}

// Query package version and type
QString version;
QString pakType;

// Check cache with proper TTL management
{
QMutexLocker locker(&m_cacheMutex);
qint64 currentTime = QDateTime::currentSecsSinceEpoch();

// Refresh linglong cache if expired
if ((currentTime - m_linglongCacheTime) > kLinglongCacheTTLSeconds) {
m_linglongCache = loadAllLinglongVersions();
m_linglongCacheTime = currentTime;
}

// Check linglong cache first
if (m_linglongCache.contains(desktopId)) {
version = m_linglongCache.value(desktopId);
pakType = QStringLiteral("linglong");
}
// Check deb cache with per-entry TTL
else if (m_debCache.contains(desktopId)) {
const auto &entry = m_debCache.value(desktopId);
if ((currentTime - entry.timestamp) <= kDebCacheTTLSeconds) {
version = entry.version;
pakType = entry.pakType;
}
}
}

// If not found or expired, query dpkg
if (pakType.isEmpty()) {
QProcess proc;
proc.start(QStringLiteral("dpkg-query"), {QStringLiteral("-W"), QStringLiteral("-f=${Version}"), desktopId});
proc.waitForFinished(1000);
if (proc.exitCode() == 0) {
version = QString::fromUtf8(proc.readAllStandardOutput()).trimmed();
pakType = QStringLiteral("deb");
} else {
qCDebug(launchDurationReporter) << "dpkg-query failed for" << desktopId << "exitCode:" << proc.exitCode();
pakType = QStringLiteral("unknown");
}

// Cache the result in deb cache
QMutexLocker locker(&m_cacheMutex);
m_debCache.insert(desktopId, {version, pakType, QDateTime::currentSecsSinceEpoch()});
}

QMetaObject::invokeMethod(this, [this, desktopId, uniqueId, launchType, version, pakType]() {
doReport(desktopId, uniqueId, launchType, version, pakType);
}, Qt::QueuedConnection);
});
Q_UNUSED(future)
}

void LaunchDurationReporter::doReport(const QString &desktopId,
const QString &uniqueId,
const QString &launchType,
const QString &version,
const QString &pakType)
{
#ifdef HAVE_DDE_API_EVENTLOGGER
DDE_EventLogger::EventLogger::instance().writeEventLog({
1000610003,
desktopId,
QJsonObject{
{"app_name", desktopId},
{"launch_type", launchType},
{"app_version", version},
{"unique_id", uniqueId},
{"time", QDateTime::currentMSecsSinceEpoch()},
{"app_package_type", pakType},
},
});
#else
Q_UNUSED(desktopId)
Q_UNUSED(uniqueId)
Q_UNUSED(launchType)
Q_UNUSED(version)
Q_UNUSED(pakType)
#endif
}

}
44 changes: 44 additions & 0 deletions panels/dock/taskmanager/launchdurationreporter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <QHash>
#include <QMutex>
#include <QObject>
#include <QString>
#include <QThreadPool>

namespace dock {

struct DebCacheEntry {
QString version;
QString pakType;
qint64 timestamp; // in seconds
};

class LaunchDurationReporter : public QObject
{
Q_OBJECT
public:
explicit LaunchDurationReporter(QObject *parent = nullptr);
~LaunchDurationReporter() override;

void reportWindowAppeared(const QString &desktopId);

private:
void doReport(const QString &desktopId,
const QString &uniqueId,
const QString &launchType,
const QString &version,
const QString &pakType);

QHash<QString, QString> m_linglongCache; // desktopId -> version
QHash<QString, DebCacheEntry> m_debCache; // desktopId -> cache entry
qint64 m_linglongCacheTime = 0; // Timestamp in seconds, 0 = never loaded
QMutex m_cacheMutex;
QThreadPool m_workerPool;
};

}
11 changes: 10 additions & 1 deletion panels/dock/taskmanager/taskmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "globals.h"
#include "hoverpreviewproxymodel.h"
#include "itemmodel.h"
#include "launchdurationreporter.h"
#include "pluginfactory.h"
#include "taskmanager.h"
#include "taskmanageradaptor.h"
Expand Down Expand Up @@ -153,6 +154,8 @@ TaskManager::TaskManager(QObject *parent)
connect(Settings, &TaskManagerSettings::allowedForceQuitChanged, this, &TaskManager::allowedForceQuitChanged);
connect(Settings, &TaskManagerSettings::showAttentionAnimationChanged, this, &TaskManager::showAttentionAnimationChanged);
connect(Settings, &TaskManagerSettings::windowSplitChanged, this, &TaskManager::windowSplitChanged);

m_launchDurationReporter = new LaunchDurationReporter(this);
}

bool TaskManager::load()
Expand Down Expand Up @@ -323,7 +326,9 @@ void TaskManager::requestWindowsView(const QModelIndexList &indexes) const

void TaskManager::handleWindowAdded(QPointer<AbstractWindow> window)
{
if (!window || window->shouldSkip() || window->getAppItem() != nullptr) return;
if (!window || window->shouldSkip() || window->getAppItem() != nullptr) {
return;
}

// TODO: remove below code and use use model replaced.
QModelIndexList res;
Expand Down Expand Up @@ -362,6 +367,10 @@ void TaskManager::handleWindowAdded(QPointer<AbstractWindow> window)
appitem->setDesktopFileParser(desktopfile);

ItemModel::instance()->addItem(appitem);

if (m_launchDurationReporter && !desktopId.isEmpty()) {
m_launchDurationReporter->reportWindowAppeared(desktopId);
}
}

void TaskManager::dropFilesOnItem(const QString& itemId, const QStringList& urls)
Expand Down
2 changes: 2 additions & 0 deletions panels/dock/taskmanager/taskmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
namespace dock {
class AppItem;
class AbstractWindowMonitor;
class LaunchDurationReporter;
class TaskManager : public DS_NAMESPACE::DContainment, public AbstractTaskManagerInterface
{
Q_OBJECT
Expand Down Expand Up @@ -125,6 +126,7 @@ private Q_SLOTS:
DockGlobalElementModel *m_dockGlobalElementModel = nullptr;
DockItemModel *m_itemModel = nullptr;
HoverPreviewProxyModel *m_hoverPreviewModel = nullptr;
LaunchDurationReporter *m_launchDurationReporter = nullptr;
int queryTrashCount() const;
};

Expand Down
Loading