Skip to content
Merged
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
14 changes: 14 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ pkg_get_variable(WAYLAND_PROTOCOLS_DATADIR wayland-protocols pkgdatadir)
add_subdirectory(plugins)
add_subdirectory(src)

## systemd user units
# The target (and its instances, pulled in via dde-tray-loader.target.wants)
# is started by dde-shell@DDE.service through Wants=dde-tray-loader.target.
# The instance unit is a configure_file template so the loader path follows
# CMAKE_INSTALL_FULL_LIBEXECDIR instead of being hard-coded.
configure_file(systemd/dde-tray-loader@.service.in
"${CMAKE_CURRENT_BINARY_DIR}/dde-tray-loader@.service" @ONLY)
install(FILES
systemd/dde-tray-loader.target
"${CMAKE_CURRENT_BINARY_DIR}/dde-tray-loader@.service"
DESTINATION lib/systemd/user)
install(DIRECTORY systemd/dde-tray-loader.target.wants
DESTINATION lib/systemd/user)

configure_file(dde-dock.pc.in dde-dock.pc @ONLY)
install(FILES ${CMAKE_BINARY_DIR}/dde-dock.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
Expand Down
2 changes: 2 additions & 0 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ Depends: ${shlibs:Depends}, ${misc:Depends},
libkf6windowsystem6,
dde-daemon (>=6.1.26),
lastore-daemon (>=5.2.9),
Breaks:
dde-shell (<< 2.0.55),
Conflicts:
dde-dock,
dde-dock-onboard-plugin
Expand Down
3 changes: 3 additions & 0 deletions debian/dde-tray-loader.install
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
usr/libexec/trayplugin-loader
usr/lib/*/libdde-trayplugin-interface.so.*
usr/lib/systemd/user/dde-tray-loader.target
usr/lib/systemd/user/dde-tray-loader@.service
usr/lib/systemd/user/dde-tray-loader.target.wants/
usr/share/dde-dock/translations
usr/share/trayplugin-loader/translations
usr/share/dsg/configs/*/*.json
Expand Down
3 changes: 3 additions & 0 deletions src/loader/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ add_executable(trayplugin-loader
utils/setproctitle.cpp
pluginmanager.h
pluginmanager.cpp
trayplugingroups.h
trayplugingroups.cpp
loader.qrc
dqwaylandplatform.h
dqwaylandplatform.cpp
Expand Down Expand Up @@ -66,6 +68,7 @@ install(TARGETS trayplugin-loader DESTINATION ${CMAKE_INSTALL_LIBEXECDIR})
dtk_add_config_meta_files(APPID org.deepin.dde.dock FILES ./configs/org.deepin.dde.dock.plugin.common.json) # compat
dtk_add_config_meta_files(APPID org.deepin.dde.tray-loader FILES ./configs/org.deepin.dde.dock.plugin.common.json)
dtk_add_config_meta_files(APPID org.deepin.dde.tray-loader FILES "${PLUGIN_INTERACTION_CONFIG_META_FILE}")
dtk_add_config_meta_files(APPID org.deepin.dde.tray-loader FILES ./configs/org.deepin.dde.dock.plugin.groups.json)

# 安装 .qm 文件
install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/trayplugin-loader/translations)
64 changes: 64 additions & 0 deletions src/loader/configs/org.deepin.dde.dock.plugin.groups.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"magic": "dsg.config.meta",
"version": "1.0",
"contents": {
"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 tray plugins",
"name[zh_CN]": "自维护托盘插件",
"description": "Tray plugins maintained by their own lifecycle and not restarted on crash. Loaded together in one process group.",
"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": "Tray plugins maintained by sibling subprojects, isolated in their own process group.",
"description[zh_CN]": "由同级子项目维护的托盘插件,独立成组加载以便隔离。",
"permissions": "readwrite",
"visibility": "private"
},
"crashProneTrayPlugins": {
"value": [],
"serial": 0,
"flags": [],
"name": "crash-prone tray plugins",
"name[zh_CN]": "易于崩溃的托盘插件",
"description": "Tray plugins known to be unstable, isolated in their own process group so a crash does not affect other plugins.",
"description[zh_CN]": "已知不稳定的托盘插件,独立成组加载,崩溃时不影响其他插件。",
"permissions": "readwrite",
"visibility": "private"
}
}
}
82 changes: 65 additions & 17 deletions src/loader/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include "setproctitle.h"

Check warning on line 5 in src/loader/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "setproctitle.h" not found.
#include "pluginsiteminterface_v2.h"

Check warning on line 6 in src/loader/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "pluginsiteminterface_v2.h" not found.
#include "pluginmanager.h"
#include "trayplugingroups.h"

#include <DDBusSender>

Check warning on line 10 in src/loader/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

Check warning on line 11 in src/loader/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

#include <QCommandLineOption>

Check warning on line 13 in src/loader/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

Check warning on line 14 in src/loader/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

Check warning on line 15 in src/loader/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

#include <QStringLiteral>

Check warning on line 17 in src/loader/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

#include <DGuiApplicationHelper>
#include <DStandardPaths>
Expand Down Expand Up @@ -72,6 +74,21 @@

int main(int argc, char *argv[], char *envp[])
{
// ExecCondition checks membership without initializing the GUI or connecting
// to the dock compositor. Exit 1 skips an empty group; 255 reports an error.
if (argc > 1 && qstrcmp(argv[1], "--check-group") == 0) {
QCoreApplication app(argc, argv);
if (argc != 3 || !loader::isValidGroup(QString::fromLocal8Bit(argv[2]))) {
qCritical() << "Expected --check-group <valid group name>.";
return 255;
}
bool configOk = false;
const auto paths = loader::pluginPathsForGroup(QString::fromLocal8Bit(argv[2]), &configOk);
if (!configOk)
return 255;
return paths.isEmpty() ? 1 : 0;
}

#ifndef QT_DEBUG
// 设置信号处理函数
struct sigaction sa;
Expand Down Expand Up @@ -151,18 +168,50 @@
"Group name for the specified plugin path(s).",
"group name"
);
QCommandLineOption pluginGroupLoadOption(
"group",
"Load all plugins belonging to <group name> "
"(selfMaintenanceTrayPlugins, subprojectTrayPlugins, "
"crashProneTrayPlugins, otherTrayPlugins).",
"group name"
);

parser.addOption(pluginPathsOption);
parser.addOption(pluginGroupNameOption);
parser.addOption(pluginGroupLoadOption);
parser.process(app);

if (!parser.isSet(pluginPathsOption)) {
qCritical() << "Error: -p is required.";
parser.showHelp(0);
if (parser.isSet(pluginPathsOption) == parser.isSet(pluginGroupLoadOption)) {
qCritical() << "Error: exactly one of -p or --group is required.";
parser.showHelp(1);
}

auto paths = parser.value(pluginPathsOption);
auto pluginPaths = paths.split(';', Qt::SkipEmptyParts);
QStringList pluginPaths;
QString pluginGroupName;
if (parser.isSet(pluginGroupLoadOption)) {
pluginGroupName = parser.value(pluginGroupLoadOption);
if (!loader::isValidGroup(pluginGroupName)) {
qCritical() << "Error: unknown group" << pluginGroupName
<< ". Valid groups:" << loader::SelfMaintenanceGroup
<< loader::SubprojectGroup << loader::CrashProneGroup
<< loader::OtherGroup;
return 1;
}
bool configOk = false;
pluginPaths = loader::pluginPathsForGroup(pluginGroupName, &configOk);
if (!configOk) {
// Do not mask a config load failure as a successful empty group.
qCritical() << "Failed to load group config for" << pluginGroupName;
return 2;
}
if (pluginPaths.isEmpty()) {
// Also handle a group becoming empty after ExecCondition ran.
qWarning() << "No plugins in group" << pluginGroupName << ", exiting.";
return 0;
}
} else {
pluginPaths = parser.value(pluginPathsOption).split(';', Qt::SkipEmptyParts);
}

#ifdef QT_DEBUG
const QDir shellDir(QString("%1/../../plugins/").arg(QCoreApplication::applicationDirPath()));
Expand All @@ -183,18 +232,17 @@
return -1;
}

QString pluginGroupName;
if (parser.isSet(pluginPathsOption)) {
pluginGroupName = parser.value(pluginGroupNameOption);
}

if (pluginGroupName.isEmpty()) {
pluginGroupName = pluginManager.loadedPlugins()[0]->pluginName();
}

app.setApplicationName(pluginGroupName);
app.setApplicationDisplayName(pluginGroupName);
setproctitle((QStringLiteral("tray plugin: ") + pluginGroupName).toStdString().c_str());
// Display name precedence: explicit -g override, else the --group name
// (empty in -p mode), else fall back to the first loaded plugin's name.
QString displayGroupName = parser.value(pluginGroupNameOption);
if (displayGroupName.isEmpty())
displayGroupName = pluginGroupName;
if (displayGroupName.isEmpty())
displayGroupName = pluginManager.loadedPlugins()[0]->pluginName();

app.setApplicationName(displayGroupName);
app.setApplicationDisplayName(displayGroupName);
setproctitle((QStringLiteral("tray plugin: ") + displayGroupName).toStdString().c_str());
qunsetenv("QT_SCALE_FACTOR");
for (auto iter = oldEnvs.begin(); iter != oldEnvs.end(); iter++) {
if (iter.value().isEmpty()) {
Expand Down
106 changes: 106 additions & 0 deletions src/loader/trayplugingroups.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include "trayplugingroups.h"

#include <DConfig>

Check warning on line 7 in src/loader/trayplugingroups.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

#include <QDir>

Check warning on line 9 in src/loader/trayplugingroups.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDir> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QLoggingCategory>
#include <QScopedPointer>
Comment thread
yixinshark marked this conversation as resolved.

Q_LOGGING_CATEGORY(loaderLog, "org.deepin.dde.tray-loader")

namespace loader {

namespace {

const QStringList &pluginDirs()
{
// Keep in sync with the directories previously scanned by dde-shell
// (panels/dock/loadtrayplugins.h).
static const QStringList dirs = {
"/usr/lib/dde-dock/plugins/",
"/usr/lib/dde-dock/plugins/quick-trays/",
"/usr/lib/dde-dock/plugins/system-trays/"
};
Comment thread
yixinshark marked this conversation as resolved.
return dirs;
}

QStringList allPluginPaths()
{
QStringList dirs;
const auto debugPaths = qEnvironmentVariable("TRAY_DEBUG_PLUGIN_PATH");
if (!debugPaths.isEmpty())
dirs << debugPaths.split(QDir::listSeparator());

if (dirs.isEmpty())
dirs << pluginDirs();

QStringList pluginPaths;
for (const auto &pluginDir : std::as_const(dirs)) {
QDir dir(pluginDir);
if (!dir.exists()) {
qCWarning(loaderLog) << "The plugin directory does not exist:" << pluginDir;
continue;
}

const auto pluginFileInfos = dir.entryInfoList({"*.so"}, QDir::Files);
for (const auto &pluginInfo : pluginFileInfos) {
pluginPaths.append(pluginInfo.absoluteFilePath());
}
}

return pluginPaths;
}

} // namespace

bool isValidGroup(const QString &groupName)
{
return groupName == SelfMaintenanceGroup
|| groupName == SubprojectGroup
|| groupName == CrashProneGroup
|| groupName == OtherGroup;
}

QStringList pluginPathsForGroup(const QString &groupName, bool *ok)
{
// The grouping schema is owned by dde-tray-loader itself (appid
// org.deepin.dde.tray-loader, config org.deepin.dde.dock.plugin.groups).
// It supersedes the grouping keys that previously lived in dde-shell's
// org.deepin.ds.dock.tray schema.
QScopedPointer<Dtk::Core::DConfig> dConfig(Dtk::Core::DConfig::create(
"org.deepin.dde.tray-loader", "org.deepin.dde.dock.plugin.groups", QString()));
if (!dConfig || !dConfig->isValid()) {
qCWarning(loaderLog) << "Failed to load group config for" << groupName;
if (ok) *ok = false;
return {};
}
if (ok) *ok = true;

const QStringList crashPronePlugins = dConfig->value(CrashProneGroup).toStringList();
const QStringList selfMaintenancePlugins = dConfig->value(SelfMaintenanceGroup).toStringList();
const QStringList subprojectPlugins = dConfig->value(SubprojectGroup).toStringList();

QStringList groupPluginPaths;
for (const auto &filePath : allPluginPaths()) {
const QString pluginName = filePath.section("/", -1);
// Each plugin belongs to exactly one group, with crash isolation taking priority.
const char *assignedGroup = OtherGroup;
if (crashPronePlugins.contains(pluginName))
assignedGroup = CrashProneGroup;
else if (selfMaintenancePlugins.contains(pluginName))
assignedGroup = SelfMaintenanceGroup;
else if (subprojectPlugins.contains(pluginName))
assignedGroup = SubprojectGroup;

if (groupName == assignedGroup)
groupPluginPaths.append(filePath);
}

return groupPluginPaths;
}

}
33 changes: 33 additions & 0 deletions src/loader/trayplugingroups.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <QStringList>

namespace loader {

// Well-known plugin group names. They match the keys of the
// org.deepin.dde.dock.plugin.groups DConfig schema (owned by dde-tray-loader),
// which assigns each plugin .so to the process group it should be loaded in.
inline constexpr auto SelfMaintenanceGroup = "selfMaintenanceTrayPlugins";
inline constexpr auto SubprojectGroup = "subprojectTrayPlugins";
inline constexpr auto CrashProneGroup = "crashProneTrayPlugins";
inline constexpr auto OtherGroup = "otherTrayPlugins";

// Whether <groupName> is one of the well-known group names. Used to reject
// typos in manual invocations early instead of silently loading nothing.
bool isValidGroup(const QString &groupName);

// Scan the standard dde-dock plugin directories and return the absolute
// paths of all plugins that belong to the given group according to the
// DConfig grouping configuration. Plugins not listed in any configured
// group fall into OtherGroup.
//
// When <ok> is non-null, it is set to false if the grouping config could
// not be loaded (so callers can distinguish a genuine empty group from a
// config failure), and to true otherwise.
QStringList pluginPathsForGroup(const QString &groupName, bool *ok = nullptr);

}
10 changes: 10 additions & 0 deletions systemd/dde-tray-loader.target
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[Unit]
Description=DDE Tray Plugin Loader target
# Tray plugin processes render into the dock's internal dockplugin Wayland
# compositor exported by dde-shell. Their lifetime is therefore bound to the
# dock shell: when the shell stops or crashes, the loader instances are
# stopped as well and restarted together with the shell.
BindsTo=dde-shell@DDE.service
After=dde-shell@DDE.service
# Stop with the session core without delaying its startup for dock readiness.
PartOf=dde-session-core.target
Loading
Loading