Skip to content

Commit 35405ee

Browse files
committed
feat(dock): add app launch event reporting for taskbar icons
1. Implement LaunchDurationReporter to report event 1000610003 when taskbar icons appear 2. Report app metadata: name, launch type, version, unique ID, timestamp, and package type 3. Query Application Manager via DBus for instance information 4. Detect package type using ll-cli for linglong apps and dpkg-query for deb packages 5. Implement cache with 30-minute TTL for linglong and deb packages 6. Async D-Bus query execution for better performance Log: Report app launch events when taskbar icons appear for analytics Influence: 1. Verify event 1000610003 triggers when new taskbar icons appear 2. Verify correct version reporting for linglong and deb packages feat(dock): 添加任务栏图标出现时的应用启动事件上报 1. 实现 LaunchDurationReporter 在任务栏图标出现时上报 1000610003 事件 2. 上报应用元数据:名称、启动类型、版本、唯一 ID、时间戳和包类型 3. 通过 DBus 查询应用管理器获取实例信息 4. 使用 ll-cli 检测玲珑应用,使用 dpkg-query 检测 deb 包 5. 实现 30 分钟 TTL 的玲珑和 deb 包缓存 6. D-Bus 查询异步执行,提升性能 Log: 任务栏图标出现时上报应用启动事件用于分析 Influence: 1. 验证新图标出现时触发 1000610003 事件 2. 验证玲珑和 deb 包版本正确上报 PMS: TASK-389405
1 parent 263f2ec commit 35405ee

5 files changed

Lines changed: 305 additions & 1 deletion

File tree

panels/dock/taskmanager/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ add_library(dock-taskmanager SHARED ${DBUS_INTERFACES}
7676
dockgroupmodel.h
7777
hoverpreviewproxymodel.cpp
7878
hoverpreviewproxymodel.h
79+
launchdurationreporter.cpp
80+
launchdurationreporter.h
7981
taskmanager.cpp
8082
taskmanager.h
8183
treelandwindow.cpp
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
2+
//
3+
// SPDX-License-Identifier: GPL-3.0-or-later
4+
5+
#include "globals.h"
6+
#include "launchdurationreporter.h"
7+
8+
#ifdef HAVE_DDE_API_EVENTLOGGER
9+
#include <dde-api/eventlogger.hpp>
10+
#endif
11+
12+
#include <QDBusConnection>
13+
#include <QDBusInterface>
14+
#include <QDBusObjectPath>
15+
#include <QDBusReply>
16+
#include <QDateTime>
17+
#include <QJsonDocument>
18+
#include <QJsonObject>
19+
#include <QJsonArray>
20+
#include <QLoggingCategory>
21+
#include <QFile>
22+
#include <QProcess>
23+
#include <QThread>
24+
#include <QtConcurrent>
25+
26+
Q_LOGGING_CATEGORY(launchDurationReporter, "org.deepin.dde.shell.dock.launchDurationReporter")
27+
28+
namespace {
29+
30+
using dock::escapeToObjectPath;
31+
32+
constexpr auto kAmService = "org.desktopspec.ApplicationManager1";
33+
constexpr auto kApplicationIface = "org.desktopspec.ApplicationManager1.Application";
34+
constexpr auto kInstanceIface = "org.desktopspec.ApplicationManager1.Instance";
35+
constexpr int kLinglongCacheTTLSeconds = 1800; // 30 minutes
36+
constexpr int kDebCacheTTLSeconds = 1800; // 30 minutes
37+
38+
struct InstanceInfo {
39+
QString instanceId;
40+
QString launchType;
41+
};
42+
43+
QList<InstanceInfo> queryInstances(const QString &desktopId)
44+
{
45+
QList<InstanceInfo> result;
46+
auto appPath = QStringLiteral("/org/desktopspec/ApplicationManager1/%1").arg(escapeToObjectPath(desktopId));
47+
48+
QDBusInterface appIface(QString::fromUtf8(kAmService),
49+
appPath,
50+
QStringLiteral("org.freedesktop.DBus.Properties"),
51+
QDBusConnection::sessionBus());
52+
appIface.setTimeout(5000); // 5 second timeout
53+
QDBusReply<QVariant> reply = appIface.call(QStringLiteral("Get"),
54+
QString::fromUtf8(kApplicationIface),
55+
QStringLiteral("Instances"));
56+
if (!reply.isValid()) {
57+
qCDebug(launchDurationReporter) << "[DockIconTiming] queryInstances failed for" << desktopId << ":" << reply.error().message();
58+
return result;
59+
}
60+
61+
const auto paths = qdbus_cast<QList<QDBusObjectPath>>(reply.value());
62+
for (const auto &path : paths) {
63+
QDBusInterface instIface(QString::fromUtf8(kAmService),
64+
path.path(),
65+
QStringLiteral("org.freedesktop.DBus.Properties"),
66+
QDBusConnection::sessionBus());
67+
instIface.setTimeout(5000); // 5 second timeout
68+
69+
InstanceInfo info;
70+
info.instanceId = path.path().section(QLatin1Char('/'), -1);
71+
72+
auto launchTypeReply = instIface.call(QStringLiteral("Get"),
73+
QString::fromUtf8(kInstanceIface),
74+
QStringLiteral("LaunchType"));
75+
if (launchTypeReply.type() == QDBusMessage::ReplyMessage) {
76+
info.launchType = qdbus_cast<QDBusVariant>(launchTypeReply.arguments().constFirst()).variant().toString();
77+
}
78+
if (info.launchType.isEmpty()) {
79+
info.launchType = QStringLiteral("unknown");
80+
}
81+
82+
result.append(info);
83+
}
84+
return result;
85+
}
86+
87+
QHash<QString, QString> loadAllLinglongVersions()
88+
{
89+
QHash<QString, QString> result;
90+
91+
QProcess proc;
92+
proc.start(QStringLiteral("ll-cli"), {QStringLiteral("list"), QStringLiteral("--type"), QStringLiteral("app")});
93+
if (!proc.waitForFinished(3000)) {
94+
qCWarning(launchDurationReporter) << "ll-cli list timeout";
95+
return result;
96+
}
97+
98+
if (proc.exitCode() != 0) {
99+
qCWarning(launchDurationReporter) << "ll-cli list failed, exitCode:" << proc.exitCode();
100+
return result;
101+
}
102+
103+
QString output = QString::fromUtf8(proc.readAllStandardOutput());
104+
QStringList lines = output.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
105+
106+
// Skip header line
107+
for (int i = 1; i < lines.size(); ++i) {
108+
QStringList columns = lines[i].simplified().split(QLatin1Char(' '));
109+
if (columns.size() >= 3) {
110+
QString name = columns[1]; // 名称 column
111+
QString version = columns[2]; // 版本 column
112+
if (!name.isEmpty()) {
113+
result.insert(name, version);
114+
}
115+
}
116+
}
117+
118+
return result;
119+
}
120+
121+
}
122+
123+
namespace dock {
124+
125+
LaunchDurationReporter::LaunchDurationReporter(QObject *parent)
126+
: QObject(parent)
127+
{
128+
m_workerPool.setMaxThreadCount(1);
129+
}
130+
131+
LaunchDurationReporter::~LaunchDurationReporter()
132+
{
133+
m_workerPool.waitForDone();
134+
}
135+
136+
void LaunchDurationReporter::reportWindowAppeared(const QString &desktopId)
137+
{
138+
if (desktopId.isEmpty()) {
139+
return;
140+
}
141+
142+
auto future = QtConcurrent::run(&m_workerPool, [this, desktopId]() {
143+
// Start async D-Bus query in global thread pool
144+
auto dbusQueryFuture = QtConcurrent::run([this, desktopId]() {
145+
return queryInstances(desktopId);
146+
});
147+
148+
// Query package version and type in parallel
149+
QString version;
150+
QString pakType;
151+
152+
// Check cache with proper TTL management
153+
{
154+
QMutexLocker locker(&m_cacheMutex);
155+
qint64 currentTime = QDateTime::currentSecsSinceEpoch();
156+
157+
// Refresh linglong cache if expired
158+
if ((currentTime - m_linglongCacheTime) > kLinglongCacheTTLSeconds) {
159+
m_linglongCache = loadAllLinglongVersions();
160+
m_linglongCacheTime = currentTime;
161+
}
162+
163+
// Check linglong cache first
164+
if (m_linglongCache.contains(desktopId)) {
165+
version = m_linglongCache.value(desktopId);
166+
pakType = QStringLiteral("linglong");
167+
}
168+
// Check deb cache with per-entry TTL
169+
else if (m_debCache.contains(desktopId)) {
170+
const auto &entry = m_debCache.value(desktopId);
171+
if ((currentTime - entry.timestamp) <= kDebCacheTTLSeconds) {
172+
version = entry.version;
173+
pakType = entry.pakType;
174+
}
175+
}
176+
}
177+
178+
// If not found or expired, query dpkg
179+
if (pakType.isEmpty()) {
180+
QProcess proc;
181+
proc.start(QStringLiteral("dpkg-query"), {QStringLiteral("-W"), QStringLiteral("-f=${Version}"), desktopId});
182+
proc.waitForFinished(1000);
183+
if (proc.exitCode() == 0) {
184+
version = QString::fromUtf8(proc.readAllStandardOutput()).trimmed();
185+
pakType = QStringLiteral("deb");
186+
} else {
187+
qCDebug(launchDurationReporter) << "dpkg-query failed for" << desktopId << "exitCode:" << proc.exitCode();
188+
pakType = QStringLiteral("unknown");
189+
}
190+
191+
// Cache the result in deb cache
192+
QMutexLocker locker(&m_cacheMutex);
193+
m_debCache.insert(desktopId, {version, pakType, QDateTime::currentSecsSinceEpoch()});
194+
}
195+
196+
// Wait for D-Bus query result
197+
dbusQueryFuture.waitForFinished();
198+
auto instances = dbusQueryFuture.result();
199+
200+
QString uniqueId;
201+
QString launchType = QStringLiteral("unknown");
202+
if (!instances.isEmpty()) {
203+
const auto &latest = instances.constLast();
204+
uniqueId = latest.instanceId;
205+
launchType = latest.launchType;
206+
}
207+
208+
if (uniqueId.isEmpty()) {
209+
return;
210+
}
211+
212+
QMetaObject::invokeMethod(this, [this, desktopId, uniqueId, launchType, version, pakType]() {
213+
doReport(desktopId, uniqueId, launchType, version, pakType);
214+
}, Qt::QueuedConnection);
215+
});
216+
Q_UNUSED(future)
217+
}
218+
219+
void LaunchDurationReporter::doReport(const QString &desktopId,
220+
const QString &uniqueId,
221+
const QString &launchType,
222+
const QString &version,
223+
const QString &pakType)
224+
{
225+
#ifdef HAVE_DDE_API_EVENTLOGGER
226+
DDE_EventLogger::EventLogger::instance().writeEventLog({
227+
1000610003,
228+
desktopId,
229+
QJsonObject{
230+
{"app_name", desktopId},
231+
{"launch_type", launchType},
232+
{"app_version", version},
233+
{"unique_id", uniqueId},
234+
{"time", QDateTime::currentMSecsSinceEpoch()},
235+
{"app_package_type", pakType},
236+
},
237+
});
238+
#else
239+
Q_UNUSED(desktopId)
240+
Q_UNUSED(uniqueId)
241+
Q_UNUSED(launchType)
242+
Q_UNUSED(version)
243+
Q_UNUSED(pakType)
244+
#endif
245+
}
246+
247+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
2+
//
3+
// SPDX-License-Identifier: GPL-3.0-or-later
4+
5+
#pragma once
6+
7+
#include <QHash>
8+
#include <QMutex>
9+
#include <QObject>
10+
#include <QString>
11+
#include <QThreadPool>
12+
13+
namespace dock {
14+
15+
struct DebCacheEntry {
16+
QString version;
17+
QString pakType;
18+
qint64 timestamp; // in seconds
19+
};
20+
21+
class LaunchDurationReporter : public QObject
22+
{
23+
Q_OBJECT
24+
public:
25+
explicit LaunchDurationReporter(QObject *parent = nullptr);
26+
~LaunchDurationReporter() override;
27+
28+
void reportWindowAppeared(const QString &desktopId);
29+
30+
private:
31+
void doReport(const QString &desktopId,
32+
const QString &uniqueId,
33+
const QString &launchType,
34+
const QString &version,
35+
const QString &pakType);
36+
37+
QHash<QString, QString> m_linglongCache; // desktopId -> version
38+
QHash<QString, DebCacheEntry> m_debCache; // desktopId -> cache entry
39+
qint64 m_linglongCacheTime = 0; // Timestamp in seconds, 0 = never loaded
40+
QMutex m_cacheMutex;
41+
QThreadPool m_workerPool;
42+
};
43+
44+
}

panels/dock/taskmanager/taskmanager.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "globals.h"
1616
#include "hoverpreviewproxymodel.h"
1717
#include "itemmodel.h"
18+
#include "launchdurationreporter.h"
1819
#include "pluginfactory.h"
1920
#include "taskmanager.h"
2021
#include "taskmanageradaptor.h"
@@ -153,6 +154,8 @@ TaskManager::TaskManager(QObject *parent)
153154
connect(Settings, &TaskManagerSettings::allowedForceQuitChanged, this, &TaskManager::allowedForceQuitChanged);
154155
connect(Settings, &TaskManagerSettings::showAttentionAnimationChanged, this, &TaskManager::showAttentionAnimationChanged);
155156
connect(Settings, &TaskManagerSettings::windowSplitChanged, this, &TaskManager::windowSplitChanged);
157+
158+
m_launchDurationReporter = new LaunchDurationReporter(this);
156159
}
157160

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

324327
void TaskManager::handleWindowAdded(QPointer<AbstractWindow> window)
325328
{
326-
if (!window || window->shouldSkip() || window->getAppItem() != nullptr) return;
329+
if (!window || window->shouldSkip() || window->getAppItem() != nullptr) {
330+
return;
331+
}
327332

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

364369
ItemModel::instance()->addItem(appitem);
370+
371+
if (m_launchDurationReporter && !desktopId.isEmpty()) {
372+
m_launchDurationReporter->reportWindowAppeared(desktopId);
373+
}
365374
}
366375

367376
void TaskManager::dropFilesOnItem(const QString& itemId, const QStringList& urls)

panels/dock/taskmanager/taskmanager.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
namespace dock {
1818
class AppItem;
1919
class AbstractWindowMonitor;
20+
class LaunchDurationReporter;
2021
class TaskManager : public DS_NAMESPACE::DContainment, public AbstractTaskManagerInterface
2122
{
2223
Q_OBJECT
@@ -125,6 +126,7 @@ private Q_SLOTS:
125126
DockGlobalElementModel *m_dockGlobalElementModel = nullptr;
126127
DockItemModel *m_itemModel = nullptr;
127128
HoverPreviewProxyModel *m_hoverPreviewModel = nullptr;
129+
LaunchDurationReporter *m_launchDurationReporter = nullptr;
128130
int queryTrashCount() const;
129131
};
130132

0 commit comments

Comments
 (0)