Skip to content

Commit 0b79013

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 0b79013

5 files changed

Lines changed: 299 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: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
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(1000); // 1 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(1000); // 1 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+
// Execute D-Bus query sequentially in worker thread
144+
auto instances = queryInstances(desktopId);
145+
146+
QString uniqueId;
147+
QString launchType = QStringLiteral("unknown");
148+
if (!instances.isEmpty()) {
149+
const auto &latest = instances.constLast();
150+
uniqueId = latest.instanceId;
151+
launchType = latest.launchType;
152+
}
153+
154+
if (uniqueId.isEmpty()) {
155+
return;
156+
}
157+
158+
// Query package version and type
159+
QString version;
160+
QString pakType;
161+
162+
// Check cache with proper TTL management
163+
{
164+
QMutexLocker locker(&m_cacheMutex);
165+
qint64 currentTime = QDateTime::currentSecsSinceEpoch();
166+
167+
// Refresh linglong cache if expired
168+
if ((currentTime - m_linglongCacheTime) > kLinglongCacheTTLSeconds) {
169+
m_linglongCache = loadAllLinglongVersions();
170+
m_linglongCacheTime = currentTime;
171+
}
172+
173+
// Check linglong cache first
174+
if (m_linglongCache.contains(desktopId)) {
175+
version = m_linglongCache.value(desktopId);
176+
pakType = QStringLiteral("linglong");
177+
}
178+
// Check deb cache with per-entry TTL
179+
else if (m_debCache.contains(desktopId)) {
180+
const auto &entry = m_debCache.value(desktopId);
181+
if ((currentTime - entry.timestamp) <= kDebCacheTTLSeconds) {
182+
version = entry.version;
183+
pakType = entry.pakType;
184+
}
185+
}
186+
}
187+
188+
// If not found or expired, query dpkg
189+
if (pakType.isEmpty()) {
190+
QProcess proc;
191+
proc.start(QStringLiteral("dpkg-query"), {QStringLiteral("-W"), QStringLiteral("-f=${Version}"), desktopId});
192+
proc.waitForFinished(1000);
193+
if (proc.exitCode() == 0) {
194+
version = QString::fromUtf8(proc.readAllStandardOutput()).trimmed();
195+
pakType = QStringLiteral("deb");
196+
} else {
197+
qCDebug(launchDurationReporter) << "dpkg-query failed for" << desktopId << "exitCode:" << proc.exitCode();
198+
pakType = QStringLiteral("unknown");
199+
}
200+
201+
// Cache the result in deb cache
202+
QMutexLocker locker(&m_cacheMutex);
203+
m_debCache.insert(desktopId, {version, pakType, QDateTime::currentSecsSinceEpoch()});
204+
}
205+
206+
QMetaObject::invokeMethod(this, [this, desktopId, uniqueId, launchType, version, pakType]() {
207+
doReport(desktopId, uniqueId, launchType, version, pakType);
208+
}, Qt::QueuedConnection);
209+
});
210+
Q_UNUSED(future)
211+
}
212+
213+
void LaunchDurationReporter::doReport(const QString &desktopId,
214+
const QString &uniqueId,
215+
const QString &launchType,
216+
const QString &version,
217+
const QString &pakType)
218+
{
219+
#ifdef HAVE_DDE_API_EVENTLOGGER
220+
DDE_EventLogger::EventLogger::instance().writeEventLog({
221+
1000610003,
222+
desktopId,
223+
QJsonObject{
224+
{"app_name", desktopId},
225+
{"launch_type", launchType},
226+
{"app_version", version},
227+
{"unique_id", uniqueId},
228+
{"time", QDateTime::currentMSecsSinceEpoch()},
229+
{"app_package_type", pakType},
230+
},
231+
});
232+
#else
233+
Q_UNUSED(desktopId)
234+
Q_UNUSED(uniqueId)
235+
Q_UNUSED(launchType)
236+
Q_UNUSED(version)
237+
Q_UNUSED(pakType)
238+
#endif
239+
}
240+
241+
}
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)