From 0b790139d689910a0fa38b560322aa0c599cb78a Mon Sep 17 00:00:00 2001 From: Ivy233 Date: Fri, 22 May 2026 18:49:02 +0800 Subject: [PATCH] feat(dock): add app launch event reporting for taskbar icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- panels/dock/taskmanager/CMakeLists.txt | 2 + .../taskmanager/launchdurationreporter.cpp | 241 ++++++++++++++++++ .../dock/taskmanager/launchdurationreporter.h | 44 ++++ panels/dock/taskmanager/taskmanager.cpp | 11 +- panels/dock/taskmanager/taskmanager.h | 2 + 5 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 panels/dock/taskmanager/launchdurationreporter.cpp create mode 100644 panels/dock/taskmanager/launchdurationreporter.h diff --git a/panels/dock/taskmanager/CMakeLists.txt b/panels/dock/taskmanager/CMakeLists.txt index 3854b7e27..84dfcc76f 100644 --- a/panels/dock/taskmanager/CMakeLists.txt +++ b/panels/dock/taskmanager/CMakeLists.txt @@ -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 diff --git a/panels/dock/taskmanager/launchdurationreporter.cpp b/panels/dock/taskmanager/launchdurationreporter.cpp new file mode 100644 index 000000000..56c8406c5 --- /dev/null +++ b/panels/dock/taskmanager/launchdurationreporter.cpp @@ -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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 queryInstances(const QString &desktopId) +{ + QList 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 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>(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(launchTypeReply.arguments().constFirst()).variant().toString(); + } + if (info.launchType.isEmpty()) { + info.launchType = QStringLiteral("unknown"); + } + + result.append(info); + } + return result; +} + +QHash loadAllLinglongVersions() +{ + QHash 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 +} + +} diff --git a/panels/dock/taskmanager/launchdurationreporter.h b/panels/dock/taskmanager/launchdurationreporter.h new file mode 100644 index 000000000..665517fd1 --- /dev/null +++ b/panels/dock/taskmanager/launchdurationreporter.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +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 m_linglongCache; // desktopId -> version + QHash m_debCache; // desktopId -> cache entry + qint64 m_linglongCacheTime = 0; // Timestamp in seconds, 0 = never loaded + QMutex m_cacheMutex; + QThreadPool m_workerPool; +}; + +} diff --git a/panels/dock/taskmanager/taskmanager.cpp b/panels/dock/taskmanager/taskmanager.cpp index 0c64335f5..7b4356e84 100644 --- a/panels/dock/taskmanager/taskmanager.cpp +++ b/panels/dock/taskmanager/taskmanager.cpp @@ -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" @@ -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() @@ -323,7 +326,9 @@ void TaskManager::requestWindowsView(const QModelIndexList &indexes) const void TaskManager::handleWindowAdded(QPointer 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; @@ -362,6 +367,10 @@ void TaskManager::handleWindowAdded(QPointer 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) diff --git a/panels/dock/taskmanager/taskmanager.h b/panels/dock/taskmanager/taskmanager.h index 517bd0da3..e7b4eb7a4 100644 --- a/panels/dock/taskmanager/taskmanager.h +++ b/panels/dock/taskmanager/taskmanager.h @@ -17,6 +17,7 @@ namespace dock { class AppItem; class AbstractWindowMonitor; +class LaunchDurationReporter; class TaskManager : public DS_NAMESPACE::DContainment, public AbstractTaskManagerInterface { Q_OBJECT @@ -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; };