From 49f499486158a0f808933601dd2cb35c1b3cc41e Mon Sep 17 00:00:00 2001 From: zhaoyingzhen Date: Mon, 21 Sep 2026 16:33:20 +0800 Subject: [PATCH] feat: move tray plugin loading to systemd-managed tray loader services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Register org.deepin.dde.Dock1 only after the QML DockCompositor is ready so Type=dbus reflects compositor readiness. Replace the in-process LoadTrayPlugins supervisor with dde-tray-loader systemd services. Query LockFront asynchronously at startup and on service owner changes without D-Bus auto-activation, and follow Visible signals. Discard stale replies and rely on service events to recover from errors without polling. Use the desktop notification policy until a state arrives, then preserve the latest state when queries fail or the service exits. dock 仅在 QML DockCompositor 就绪后才注册 org.deepin.dde.Dock1,使 Type=dbus 单元能反映合成器就绪状态;移除进程内的 LoadTrayPlugins 管理器, 改用 dde-tray-loader 的 systemd 服务。 启动时和服务 owner 变化后异步查询 LockFront,并监听 Visible 信号; 查询禁止 D-Bus 自动激活,以保留会话启动顺序。丢弃过期回复,查询失败后 等待服务事件恢复,不再轮询。首次获取状态前使用桌面通知策略,后续查询 失败或服务退出时保留最近一次状态。 Log: move tray plugin loading to systemd-managed tray loader services Pms: TASK-395865 --- debian/control | 3 +- panels/dock/CMakeLists.txt | 2 - .../dock/dconfig/org.deepin.ds.dock.tray.json | 32 --- panels/dock/dockpanel.cpp | 16 +- panels/dock/dockpanel.h | 2 - panels/dock/loadtrayplugins.cpp | 199 ------------------ panels/dock/loadtrayplugins.h | 47 ----- .../server/notificationmanager.cpp | 71 +++++-- .../notification/server/notificationmanager.h | 3 + 9 files changed, 74 insertions(+), 301 deletions(-) delete mode 100644 panels/dock/loadtrayplugins.cpp delete mode 100644 panels/dock/loadtrayplugins.h diff --git a/debian/control b/debian/control index 2b8473747..2bf9d1520 100644 --- a/debian/control +++ b/debian/control @@ -71,7 +71,8 @@ Description: DDE Shell dock library Package: dde-shell Architecture: any Depends: - dde-tray-loader (>= 1.99.8), + dde-session (>= 2.0.33), + dde-tray-loader (>= 2.0.41), libdde-shell (= ${binary:Version}), libdde-shell-dock (= ${binary:Version}), libdtk6declarative, diff --git a/panels/dock/CMakeLists.txt b/panels/dock/CMakeLists.txt index e1711e171..80f756180 100644 --- a/panels/dock/CMakeLists.txt +++ b/panels/dock/CMakeLists.txt @@ -26,8 +26,6 @@ file( dockhelper.cpp waylanddockhelper.h waylanddockhelper.cpp - loadtrayplugins.h - loadtrayplugins.cpp ) # Old dbus interface compatible diff --git a/panels/dock/dconfig/org.deepin.ds.dock.tray.json b/panels/dock/dconfig/org.deepin.ds.dock.tray.json index 975d840e6..df9f9fd8b 100644 --- a/panels/dock/dconfig/org.deepin.ds.dock.tray.json +++ b/panels/dock/dconfig/org.deepin.ds.dock.tray.json @@ -88,38 +88,6 @@ "description[zh_CN]": "托盘折叠区域当前的状态", "permissions": "readwrite", "visibility": "private" - }, - "selfMaintenanceTrayPlugins": { - "value": ["libapplication-tray.so", "libbrightness.so", "libdatetime.so", "libdnd-mode.so", "libeye-comfort-mode.so", "libmedia.so", "libnotification.so", "libonboard.so", "libshutdown.so", "libairplane-mode.so", "libbluetooth.so", "libdock-network-plugin.so", "libdock-wirelesscasting-plugin.so", "libkeyboard-layout.so", "libpower.so", "libsound.so"], - "serial": 0, - "flags": [], - "name": "self maintenance plugins", - "name[zh_CN]": "自维护托盘插件", - "description": "self maintenance plugins", - "description[zh_CN]": "自维护托盘插件", - "permissions": "readonly", - "visibility": "private" - }, - "subprojectTrayPlugins": { - "value": ["libdock-clipboard-plugin.so", "libddegrandsearch_dockplugin.so", "libdeepin-screen-recorder-plugin.so", "libdeepin-system-monitor-plugin.so", "libshot-start-plugin.so", "libshot-start-record-plugin.so", "libdde-disk-mount-plugin.so"], - "serial": 0, - "flags": [], - "name": "subproject tray plugins", - "name[zh_CN]": "子项目托盘插件", - "description": "subproject tray plugins", - "description[zh_CN]": "子项目托盘插件", - "permissions": "readwrite", - "visibility": "private" - }, - "crashProneTrayPlugins": { - "value": [], - "serial": 0, - "flags": [], - "name": "crash-prone tray plugins", - "name[zh_CN]": "易于崩溃的托盘插件", - "description": "crash-prone tray plugins", - "description[zh_CN]": "易于崩溃的托盘插件", - "permissions": "readwrite" } } } diff --git a/panels/dock/dockpanel.cpp b/panels/dock/dockpanel.cpp index ea6d50481..0fb95980a 100644 --- a/panels/dock/dockpanel.cpp +++ b/panels/dock/dockpanel.cpp @@ -15,7 +15,6 @@ #include "dockdbusproxy.h" #include "dockfrontadaptor.h" #include "dockdaemonadaptor.h" -#include "loadtrayplugins.h" #include #include @@ -41,15 +40,21 @@ DockPanel::DockPanel(QObject *parent) , m_theme(ColorTheme::Dark) , m_hideState(Show) , m_dockScreen(nullptr) - , m_loadTrayPlugins(new LoadTrayPlugins(this)) , m_compositorReady(false) , m_launcherShown(false) , m_contextDragging(false) , m_isResizing(false) { + // The org.deepin.dde.Dock1 service name is used as the readiness flag of the + // Type=dbus systemd unit (dde-shell@DDE.service). It must only be acquired + // after the QML DockCompositor is created, so that tray plugin loader + // services ordered After=dde-shell@DDE.service can rely on it being safe + // to connect to the dockplugin Wayland socket. connect(this, &DockPanel::compositorReadyChanged, this, [this] { if (!m_compositorReady) return; - m_loadTrayPlugins->loadDockPlugins(); + if (!QDBusConnection::sessionBus().registerService("org.deepin.dde.Dock1")) { + qCWarning(dockLog) << "Failed to register org.deepin.dde.Dock1 after compositor ready"; + } }); } @@ -83,7 +88,10 @@ bool DockPanel::init() DockDBusProxy* proxy = new DockDBusProxy(this); DockFrontAdaptor* dockFrontAdaptor = new DockFrontAdaptor(proxy); Q_UNUSED(dockFrontAdaptor) - QDBusConnection::sessionBus().registerService("org.deepin.dde.Dock1"); + // NOTE: the object is exported here, but the org.deepin.dde.Dock1 service + // name is only acquired once the compositor is ready, see the constructor. + // It is the BusName of the Type=dbus unit dde-shell@DDE.service and thus + // defines when systemd considers the dock started. QDBusConnection::sessionBus().registerObject("/org/deepin/dde/Dock1", "org.deepin.dde.Dock1", proxy); DockDaemonAdaptor* dockDaemonAdaptor = new DockDaemonAdaptor(proxy); diff --git a/panels/dock/dockpanel.h b/panels/dock/dockpanel.h index 53ba48539..a7551639e 100644 --- a/panels/dock/dockpanel.h +++ b/panels/dock/dockpanel.h @@ -13,7 +13,6 @@ namespace dock { class DockHelper; -class LoadTrayPlugins; class DockPanel : public DS_NAMESPACE::DPanel, public QDBusContext { @@ -167,7 +166,6 @@ private Q_SLOTS: HideState m_hideState; DockHelper* m_helper; QPointer m_dockScreen; - LoadTrayPlugins *m_loadTrayPlugins; bool m_compositorReady; bool m_launcherShown; bool m_contextDragging; diff --git a/panels/dock/loadtrayplugins.cpp b/panels/dock/loadtrayplugins.cpp deleted file mode 100644 index 6672bcb0d..000000000 --- a/panels/dock/loadtrayplugins.cpp +++ /dev/null @@ -1,199 +0,0 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "loadtrayplugins.h" -#include "environments.h" - -#include - -#include - -#include -#include -#include - -namespace dock { - -LoadTrayPlugins::LoadTrayPlugins(QObject *parent) - : QObject(parent) -{ - -} - -LoadTrayPlugins::~LoadTrayPlugins() -{ - for (auto &pInfo : m_processes) { - if (pInfo.process) { - pInfo.process->kill(); - //pInfo.process->waitForFinished(); - pInfo.process->deleteLater(); - } - } -} - -void LoadTrayPlugins::loadDockPlugins() -{ - QString validExePath = loaderPath(); - if (validExePath.isEmpty()) { - qWarning() << "No valid loader executable path found."; - return; - } - - auto pluginGroupMap = groupPlugins(allPluginPaths()); - for (auto it = pluginGroupMap.begin(); it != pluginGroupMap.end(); ++it) { - if (it.value().isEmpty()) continue; - qDebug() << "Load plugin:" << it.value() << " group:" << it.key(); - startProcess(validExePath, it.value(), it.key()); - } -} - -void LoadTrayPlugins::handleProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - auto *process = qobject_cast(sender()); - if (!process) return; - - if (exitCode == SIGKILL || exitCode == SIGTERM || exitStatus != QProcess::CrashExit) return; - - for (auto it = m_processes.begin(); it != m_processes.end(); ++it) { - if (it->process == process) { - if (it->retryCount < m_maxRetries) { - it->retryCount++; - qWarning() << "Plugin exit:" << it->pluginPath << " code:" << exitCode << " exitStatus:" << exitStatus; - QTimer::singleShot(1000, process, [ this, process ] { - setProcessEnv(process); - process->start(); - }); - } else { - qWarning() << "Maximum retries reached for plugin:" << it->pluginPath; - process->deleteLater(); - m_processes.erase(it); - } - break; - } - } -} - -void LoadTrayPlugins::startProcess(const QString &loaderPath, const QString &pluginPath, const QString &groupName) -{ - auto *process = new QProcess(this); - setProcessEnv(process); - - connect(process, QOverload::of(&QProcess::finished), - this, &LoadTrayPlugins::handleProcessFinished); - - ProcessInfo pInfo = { process, pluginPath, 0 }; - m_processes.append(pInfo); - - process->setProgram(loaderPath); - process->setArguments({"-p", pluginPath, "-g", groupName, "-platform", "wayland"}); - process->start(); -} - -void LoadTrayPlugins::setProcessEnv(QProcess *process) -{ - if (!process) return; - - QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - // TODO: use protocols to determine the environment instead of environment variables - env.remove("DDE_CURRENT_COMPOSITOR"); - - process->setProcessEnvironment(env); -} - -QString LoadTrayPlugins::loaderPath() const -{ - QStringList execPaths; - execPaths << qEnvironmentVariable("TRAY_LOADER_EXECUTE_PATH") - << QString("%1/trayplugin-loader").arg(CMAKE_INSTALL_FULL_LIBEXECDIR); - - QString validExePath; - for (const QString &execPath : execPaths) { - if (QFile::exists(execPath)) { - validExePath = execPath; - break; - } - } - - return validExePath; -} - -QStringList LoadTrayPlugins::allPluginPaths() const -{ - QStringList dirs; - const auto pluginsPath = qEnvironmentVariable("TRAY_DEBUG_PLUGIN_PATH"); - if (!pluginsPath.isEmpty()) - dirs << pluginsPath.split(QDir::listSeparator()); - - if (dirs.isEmpty()) - dirs << pluginDirs; - - QStringList pluginPaths; - for (auto &pluginDir : dirs) { - QDir dir(pluginDir); - if (!dir.exists()) { - qWarning() << "The plugin directory does not exist:" << pluginDir; - continue; - } - - auto pluginFileInfos = dir.entryInfoList({"*.so"}, QDir::Files); - for (auto &pluginInfo : pluginFileInfos) { - pluginPaths.append(pluginInfo.absoluteFilePath()); - } - } - - return pluginPaths; -} - -QMap LoadTrayPlugins::groupPlugins(const QStringList &pluginPaths) const -{ - const QString selfMaintenancePluginsKey = "selfMaintenanceTrayPlugins"; - const QString subprojectPluginsKey = "subprojectTrayPlugins"; - const QString crashPronePluginsKey = "crashProneTrayPlugins"; - const QString otherPluginsKey = "otherTrayPlugins"; - - auto dConfig = Dtk::Core::DConfig::create("org.deepin.dde.shell", "org.deepin.ds.dock.tray", QString()); - QStringList selfMaintenanceTrayPlugins = dConfig->value(selfMaintenancePluginsKey).toStringList(); - QStringList subprojectTrayPlugins = dConfig->value(subprojectPluginsKey).toStringList(); - QStringList crashProneTrayPlugins = dConfig->value(crashPronePluginsKey).toStringList(); - dConfig->deleteLater(); - - QStringList selfMaintenancePluginPaths; - QStringList subprojectPluginPaths; - QStringList crashPronePluginPaths; - QStringList otherPluginPaths; - - for (auto &filePath : pluginPaths) { - QString pluginName = filePath.section("/", -1); - if (crashProneTrayPlugins.contains(pluginName)) { - crashPronePluginPaths.append(filePath); - } else if (selfMaintenanceTrayPlugins.contains(pluginName)) { - selfMaintenancePluginPaths.append(filePath); - } else if (subprojectTrayPlugins.contains(pluginName)) { - subprojectPluginPaths.append(filePath); - } else { - otherPluginPaths.append(filePath); - } - } - - QMap pluginGroup; - - if (!selfMaintenancePluginPaths.isEmpty()) { - pluginGroup.insert(selfMaintenancePluginsKey, selfMaintenancePluginPaths.join(";")); - } - - if (!subprojectPluginPaths.isEmpty()) { - pluginGroup.insert(subprojectPluginsKey, subprojectPluginPaths.join(";")); - } - - if (!crashPronePluginPaths.isEmpty()) { - pluginGroup.insert(crashPronePluginsKey, crashPronePluginPaths.join(";")); - } - - if (!otherPluginPaths.isEmpty()) { - pluginGroup.insert(otherPluginsKey, otherPluginPaths.join(";")); - } - - return pluginGroup; -} -} diff --git a/panels/dock/loadtrayplugins.h b/panels/dock/loadtrayplugins.h deleted file mode 100644 index 04f4fe8b6..000000000 --- a/panels/dock/loadtrayplugins.h +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -namespace dock { -const QStringList pluginDirs = { - "/usr/lib/dde-dock/plugins/", - "/usr/lib/dde-dock/plugins/quick-trays/", - "/usr/lib/dde-dock/plugins/system-trays/" -}; - -class LoadTrayPlugins : public QObject -{ - Q_OBJECT -public: - explicit LoadTrayPlugins(QObject *parent = nullptr); - ~LoadTrayPlugins() override; - - void loadDockPlugins(); - -private slots: - void handleProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); - -private: - QString loaderPath() const; - QStringList allPluginPaths() const; - QMap groupPlugins(const QStringList &pluginPaths) const; - - void startProcess(const QString &loaderPath, const QString &pluginPath, const QString &groupName); - void setProcessEnv(QProcess *process); - -private: - struct ProcessInfo { - QProcess *process = nullptr; - QString pluginPath; - int retryCount = 0; - }; - - QList m_processes; - const int m_maxRetries = 5; -}; - -} diff --git a/panels/notification/server/notificationmanager.cpp b/panels/notification/server/notificationmanager.cpp index 74c564197..e0e8da9cb 100644 --- a/panels/notification/server/notificationmanager.cpp +++ b/panels/notification/server/notificationmanager.cpp @@ -15,7 +15,10 @@ #include #include -#include +#include +#include +#include +#include #include #include #include @@ -45,6 +48,8 @@ static const QString DDENotifyDBusServer = "org.deepin.dde.Notification1"; static const QString DDENotifyDBusPath = "/org/deepin/dde/Notification1"; static const QString SessionDBusService = "org.deepin.dde.SessionManager1"; static const QString SessionDaemonDBusPath = "/org/deepin/dde/SessionManager1"; +static const QString LockFrontDBusService = "org.deepin.dde.LockFront1"; +static const QString LockFrontDBusPath = "/org/deepin/dde/LockFront1"; NotificationManager::NotificationManager(QObject *parent) : QObject(parent) @@ -689,22 +694,55 @@ bool NotificationManager::invokeShellAction(const QString &data) void NotificationManager::initScreenLockedState() { - const QString interfaceAndServiceName = "org.deepin.dde.LockFront1"; - const QString path = "/org/deepin/dde/LockFront1"; + auto connection = QDBusConnection::sessionBus(); + connection.connect(LockFrontDBusService, LockFrontDBusPath, LockFrontDBusService, + "Visible", this, SLOT(onScreenLockedChanged(bool))); - QDBusInterface interface(interfaceAndServiceName, path, - "org.freedesktop.DBus.Properties", QDBusConnection::sessionBus()); + auto *serviceWatcher = new QDBusServiceWatcher(LockFrontDBusService, connection, + QDBusServiceWatcher::WatchForOwnerChange, this); + connect(serviceWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, + [this](const QString &, const QString &, const QString &newOwner) { + // Discard any reply from the previous owner before starting a new query. + if (m_screenLockedQueryWatcher) { + m_screenLockedQueryWatcher->deleteLater(); + m_screenLockedQueryWatcher = nullptr; + } + if (!newOwner.isEmpty()) + queryScreenLockedState(); + }); - QDBusReply reply = interface.call("Get", "org.deepin.dde.LockFront1", "Visible"); - if (reply.isValid()) { - m_screenLocked = reply.value().variant().toBool(); - } else { - m_screenLocked = false; - qWarning(notifyLog) << "Failed to get the lock visible property:" << reply.error().message(); - } + queryScreenLockedState(); +} - QDBusConnection::sessionBus().connect(interfaceAndServiceName, path, interfaceAndServiceName, - "Visible", this, SLOT(onScreenLockedChanged(bool))); +void NotificationManager::queryScreenLockedState() +{ + if (m_screenLockedQueryWatcher) + return; + + auto message = QDBusMessage::createMethodCall(LockFrontDBusService, LockFrontDBusPath, + "org.freedesktop.DBus.Properties", "Get"); + // dde-session starts LockFront after the dock; querying must not activate it early. + message.setAutoStartService(false); + message << LockFrontDBusService << QStringLiteral("Visible"); + m_screenLockedQueryWatcher = new QDBusPendingCallWatcher( + QDBusConnection::sessionBus().asyncCall(message), this); + connect(m_screenLockedQueryWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) { + watcher->deleteLater(); + if (watcher != m_screenLockedQueryWatcher) + return; + m_screenLockedQueryWatcher = nullptr; + const QDBusPendingReply reply = *watcher; + if (reply.isError()) { + // An absent service is expected at startup. Wait for its registration or Visible. + const auto error = reply.error(); + if (error.type() != QDBusError::ServiceUnknown + && error.name() != QLatin1String("org.freedesktop.DBus.Error.NameHasNoOwner")) { + qWarning(notifyLog) << "Failed to get the lock visible property:" << error.message(); + } + return; + } + onScreenLockedChanged(reply.value().variant().toBool()); + }); } void NotificationManager::onHandingPendingEntities() @@ -772,6 +810,11 @@ void NotificationManager::removePendingEntity(const NotifyEntity &entity) void NotificationManager::onScreenLockedChanged(bool screenLocked) { + // A Visible signal supersedes any pending query reply. + if (m_screenLockedQueryWatcher) { + m_screenLockedQueryWatcher->deleteLater(); + m_screenLockedQueryWatcher = nullptr; + } m_screenLocked = screenLocked; } diff --git a/panels/notification/server/notificationmanager.h b/panels/notification/server/notificationmanager.h index f2756669d..103183cd1 100644 --- a/panels/notification/server/notificationmanager.h +++ b/panels/notification/server/notificationmanager.h @@ -7,6 +7,7 @@ #include #include +class QDBusPendingCallWatcher; class QTimer; namespace notification { @@ -84,6 +85,7 @@ public Q_SLOTS: bool isExtendedAction(qint64 id, const QString &actionId) const; bool invokeShellAction(const QString &data); void initScreenLockedState(); + void queryScreenLockedState(); private slots: void onHandingPendingEntities(); @@ -93,6 +95,7 @@ private slots: private: uint m_replacesCount = 0; bool m_screenLocked = false; + QDBusPendingCallWatcher *m_screenLockedQueryWatcher = nullptr; DataAccessor *m_persistence = nullptr; NotificationSetting *m_setting = nullptr;