diff --git a/docs/plugin/API_en.md b/docs/plugin/API_en.md index 90552fcee..9eb6fcb2e 100644 --- a/docs/plugin/API_en.md +++ b/docs/plugin/API_en.md @@ -257,6 +257,19 @@ class DS_SHARE DPluginMetaData : public QObject }; ``` +### Applet Enable Configuration + +`AppletConfigManager` in the `dde-shell` process reads `org.deepin.dde.shell / org.deepin.dde.shell / enable`, uses the applet's `Plugin.Id` as the DConfig subpath, and passes the enabled state to `DAppletLoader`. This configuration applies only to child applets that declare `Plugin.Parent`; top-level panels such as the Dock are always created by the startup flow and are not controlled by DConfig. When the value is `false`, the corresponding child applet instance is not created. Runtime configuration changes unload or reload that child applet immediately. `dde-shell-frame` does not read or store this configuration state. + +The `-d` command-line option is handled by `DPluginLoader`, which filters the corresponding applet, including a top-level panel, while scanning metadata. It has higher priority than DConfig; an applet disabled with `-d` cannot be re-enabled through DConfig during the lifetime of the process. + +For example, to disable the launchpad applet: + +```bash +dde-dconfig set -a org.deepin.dde.shell -r org.deepin.dde.shell \ + -s /org.deepin.ds.dock.launcherapplet -k enable -v false +``` + ### DAppletBridge API Bridge for communicating with applet from C++: diff --git a/docs/plugin/API_zh_CN.md b/docs/plugin/API_zh_CN.md index f4316d2a3..1b2d1a971 100644 --- a/docs/plugin/API_zh_CN.md +++ b/docs/plugin/API_zh_CN.md @@ -257,6 +257,19 @@ class DS_SHARE DPluginMetaData : public QObject }; ``` +### Applet 启用配置 + +`dde-shell` 进程中的 `AppletConfigManager` 读取 `org.deepin.dde.shell / org.deepin.dde.shell / enable`,并使用 Applet 的 `Plugin.Id` 作为 DConfig subpath,再将启用状态交给 `DAppletLoader`。该配置只作用于包含 `Plugin.Parent` 的子 Applet;Dock 等顶层 Panel 始终由启动流程创建,不受 DConfig 控制。值为 `false` 时不会创建对应的子 Applet 实例;运行时修改配置会实时卸载或重新加载对应子 Applet。`dde-shell-frame` 本身不读取或保存此配置状态。 + +命令行参数 `-d` 由 `DPluginLoader` 处理,会在扫描 metadata 时直接过滤对应 Applet,包括顶层 Panel。它的优先级高于 DConfig;在当前进程生命周期内,被 `-d` 禁用的 Applet 无法通过 DConfig 重新启用。 + +例如禁用启动器 Applet: + +```bash +dde-dconfig set -a org.deepin.dde.shell -r org.deepin.dde.shell \ + -s /org.deepin.ds.dock.launcherapplet -k enable -v false +``` + ### DAppletBridge API 从 C++ 与小部件通信的桥接: diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt index c201baa5b..8f6adb1c9 100644 --- a/shell/CMakeLists.txt +++ b/shell/CMakeLists.txt @@ -15,6 +15,8 @@ endif() add_executable(dde-shell main.cpp + appletconfigmanager.h + appletconfigmanager.cpp appletloader.h appletloader.cpp shell.h diff --git a/shell/appletconfigmanager.cpp b/shell/appletconfigmanager.cpp new file mode 100644 index 000000000..548d46c62 --- /dev/null +++ b/shell/appletconfigmanager.cpp @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "appletconfigmanager.h" + +#include "pluginloader.h" + +#include +#include +#include + +DS_BEGIN_NAMESPACE + +DCORE_USE_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(dsLoaderLog) + +static constexpr auto AppId{"org.deepin.dde.shell"}; +static constexpr auto ConfigName{"org.deepin.dde.shell"}; +static constexpr auto EnableKey{"enable"}; + +AppletConfigManager::AppletConfigManager(QObject *parent) + : QObject(parent) +{ +} + +QStringList AppletConfigManager::disabledPlugins() const +{ + return m_disabledPlugins; +} + +bool AppletConfigManager::isAppletEnabled(const QString &pluginId) const +{ + return !m_disabledPlugins.contains(pluginId); +} + +void AppletConfigManager::setAppletEnabled(const QString &pluginId, bool enabled) +{ + const bool currentlyEnabled = isAppletEnabled(pluginId); + if (currentlyEnabled == enabled) + return; + + if (enabled) + m_disabledPlugins.removeAll(pluginId); + else + m_disabledPlugins.append(pluginId); + Q_EMIT appletEnabledChanged(pluginId, enabled); +} + +void AppletConfigManager::ensureAppletConfigs() +{ + auto loader = DPluginLoader::instance(); + const auto plugins = loader->plugins(); + QSet pluginIds; + for (const auto &plugin : plugins) { + pluginIds.insert(plugin.pluginId()); + } + + for (const auto &pluginId : pluginIds) { + if (m_appletConfigs.contains(pluginId)) + continue; + + auto config = DConfig::create(QLatin1String(AppId), + QLatin1String(ConfigName), + QLatin1Char('/') + pluginId, + this); + m_appletConfigs.insert(pluginId, config); + if (!config || !config->isValid()) { + qCWarning(dsLoaderLog) << "Unable to create applet DConfig; applet remains enabled:" << pluginId; + setAppletEnabled(pluginId, true); + continue; + } + + setAppletEnabled(pluginId, config->value(QLatin1String(EnableKey), true).toBool()); + QObject::connect(config, &DConfig::valueChanged, this, [this, pluginId, config](const QString &key) { + if (key == QLatin1String(EnableKey)) + setAppletEnabled(pluginId, config->value(QLatin1String(EnableKey), true).toBool()); + }); + } +} + +DS_END_NAMESPACE diff --git a/shell/appletconfigmanager.h b/shell/appletconfigmanager.h new file mode 100644 index 000000000..4dbb0fd7d --- /dev/null +++ b/shell/appletconfigmanager.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "dsglobal.h" + +#include +#include +#include + +namespace Dtk::Core { +class DConfig; +} + +DS_BEGIN_NAMESPACE + +class AppletConfigManager : public QObject +{ + Q_OBJECT +public: + explicit AppletConfigManager(QObject *parent = nullptr); + + QStringList disabledPlugins() const; + bool isAppletEnabled(const QString &pluginId) const; + void ensureAppletConfigs(); + +Q_SIGNALS: + void appletEnabledChanged(const QString &pluginId, bool enabled); + +private: + void setAppletEnabled(const QString &pluginId, bool enabled); + + QMap m_appletConfigs; + QStringList m_disabledPlugins; +}; + +DS_END_NAMESPACE diff --git a/shell/appletloader.cpp b/shell/appletloader.cpp index 3564f91a0..1617a8001 100644 --- a/shell/appletloader.cpp +++ b/shell/appletloader.cpp @@ -1,8 +1,9 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2023 -2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later #include "appletloader.h" +#include "appletconfigmanager.h" #include "pluginloader.h" #include "applet.h" #include "containment.h" @@ -84,22 +85,30 @@ class DAppletLoaderPrivate : public DObjectPrivate bool init(DApplet *applet); void createChildren(DApplet *applet); + DApplet *findApplet(DApplet *applet, const QString &pluginId) const; + DApplet *parentApplet(const QString &pluginId) const; + void fail(const QString &pluginId, const QString &reason); void loadTranslation(const DPluginMetaData &pluginData); void removeTranslation(const QString &pluginId); + void removeTranslations(const QString &pluginId); QPointer m_applet = nullptr; + QPointer m_configManager = nullptr; + QString m_pluginId; QMap m_pluginTranslators; D_DECLARE_PUBLIC(DAppletLoader); }; -DAppletLoader::DAppletLoader(class DApplet *applet, QObject *parent) +DAppletLoader::DAppletLoader(DApplet *applet, AppletConfigManager *configManager, QObject *parent) : QObject(parent) , DObject(*new DAppletLoaderPrivate(this)) { D_D(DAppletLoader); d->m_applet = applet; + d->m_configManager = configManager; + Q_ASSERT(configManager); } DAppletLoader::~DAppletLoader() @@ -107,18 +116,54 @@ DAppletLoader::~DAppletLoader() } +void DAppletLoader::setPluginId(const QString &pluginId) +{ + D_D(DAppletLoader); + d->m_pluginId = pluginId; +} + void DAppletLoader::exec() { D_D(DAppletLoader); - d->loadTranslation(d->m_applet->pluginMetaData()); + auto applet = d->m_applet.data(); - if (!d->load(d->m_applet)) - return; + if (!d->m_pluginId.isEmpty()) { + applet = d->findApplet(applet, d->m_pluginId); + if (applet) { + return; + } + auto containment = qobject_cast(d->parentApplet(d->m_pluginId)); + if (!containment) { + d->fail(d->m_pluginId, QStringLiteral("Parent containment was not found.")); + return; + } + + applet = containment->createApplet(DAppletData(d->m_pluginId)); + if (!applet) { + d->fail(d->m_pluginId, QStringLiteral("Failed to create applet in its parent containment.")); + return; + } + } - d->createRootObject(d->m_applet); + d->loadTranslation(applet->pluginMetaData()); - if (!d->init(d->m_applet)) + if (!d->load(applet)) return; + + d->createRootObject(applet); + + if (!d->init(applet)) + return; +} + +void DAppletLoader::remove(const QString &pluginId) +{ + D_D(DAppletLoader); + auto applet = d->findApplet(d->m_applet, pluginId); + if (auto containment = applet ? qobject_cast(applet->parentApplet()) : nullptr) { + containment->removeApplet(applet); + d->removeTranslations(pluginId); + } } DApplet *DAppletLoader::applet() const @@ -145,11 +190,12 @@ void DAppletLoaderPrivate::doCreateRootObject(DApplet *applet) qCDebug(dsLoaderLog) << "Created rootObject for the applet:" << applet->pluginId(); } }); - qCDebug(dsLoaderLog) << "Begin to create rootObject the applet:" << applet->pluginId(); - if (!engine->create()) { - engine->deleteLater(); - } + QMetaObject::invokeMethod(engine, [engine]() { + if (!engine->create()) { + engine->deleteLater(); + } + }, Qt::QueuedConnection); } bool DAppletLoaderPrivate::doLoad(DApplet *applet) @@ -190,6 +236,10 @@ void DAppletLoaderPrivate::createChildren(DApplet *applet) const auto data = applet->appletData(); auto groups = groupList(applet, data); for (const auto &item : std::as_const(groups)) { + if (!m_configManager->isAppletEnabled(item.pluginId())) + continue; + if (findApplet(m_applet, item.pluginId())) + continue; auto child = containment->createApplet(item); if (!child) { @@ -199,6 +249,41 @@ void DAppletLoaderPrivate::createChildren(DApplet *applet) } } +DApplet *DAppletLoaderPrivate::findApplet(DApplet *applet, const QString &pluginId) const +{ + if (!applet) + return nullptr; + if (applet->pluginId() == pluginId) + return applet; + + auto containment = qobject_cast(applet); + if (!containment) + return nullptr; + + const auto children = containment->applets(); + for (const auto &child : children) { + if (auto result = findApplet(child, pluginId)) + return result; + } + return nullptr; +} + +DApplet *DAppletLoaderPrivate::parentApplet(const QString &pluginId) const +{ + const auto parentPlugin = DPluginLoader::instance()->parentPlugin(pluginId); + if (!parentPlugin.isValid()) + return m_applet; + + return findApplet(m_applet, parentPlugin.pluginId()); +} + +void DAppletLoaderPrivate::fail(const QString &pluginId, const QString &reason) +{ + D_Q(DAppletLoader); + qCWarning(dsLoaderLog) << reason << pluginId; + Q_EMIT q->failed(pluginId); +} + bool DAppletLoaderPrivate::load(DApplet *applet) { if (!doLoad(applet)) { @@ -248,6 +333,9 @@ void DAppletLoaderPrivate::loadTranslation(const DPluginMetaData &pluginData) const QString baseDir = pluginData.pluginDir(); const QString pluginId = pluginData.pluginId(); + if (!m_configManager->isAppletEnabled(pluginId)) + return; + auto translator = new QTranslator(qApp); const QString pluginTranslationDir(baseDir + "/translations/"); if (translator->load(QLocale::system(), pluginId, QLatin1String("_"), pluginTranslationDir)) { @@ -275,4 +363,12 @@ void DAppletLoaderPrivate::removeTranslation(const QString &pluginId) } } +void DAppletLoaderPrivate::removeTranslations(const QString &pluginId) +{ + const auto children = DPluginLoader::instance()->childrenPlugin(pluginId); + for (const auto &child : children) + removeTranslations(child.pluginId()); + removeTranslation(pluginId); +} + DS_END_NAMESPACE diff --git a/shell/appletloader.h b/shell/appletloader.h index 5f088ded4..04ed9dfe9 100644 --- a/shell/appletloader.h +++ b/shell/appletloader.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -12,16 +12,19 @@ DS_BEGIN_NAMESPACE class DApplet; +class AppletConfigManager; class DAppletLoaderPrivate; class DAppletLoader : public QObject, public DTK_CORE_NAMESPACE::DObject { Q_OBJECT D_DECLARE_PRIVATE(DAppletLoader) public: - explicit DAppletLoader(DApplet *applet, QObject *parent = nullptr); + explicit DAppletLoader(DApplet *applet, AppletConfigManager *configManager, QObject *parent = nullptr); virtual ~DAppletLoader() override; + void setPluginId(const QString &pluginId); void exec(); + void remove(const QString &pluginId); DApplet *applet() const; Q_SIGNALS: diff --git a/shell/main.cpp b/shell/main.cpp index eebd0069c..aa7335652 100644 --- a/shell/main.cpp +++ b/shell/main.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -19,6 +20,7 @@ #include "applet.h" #include "containment.h" #include "pluginloader.h" +#include "appletconfigmanager.h" #include "appletloader.h" #include "qmlengine.h" #include "shell.h" @@ -49,9 +51,11 @@ static void disableLogOutput() class AppletManager { public: - explicit AppletManager(const QStringList &pluginIds) + explicit AppletManager(const QStringList &pluginIds, AppletConfigManager *configManager) { qCDebug(dsLog) << "Preloading plugins:" << pluginIds; + Q_ASSERT(configManager); + auto rootApplet = qobject_cast(DPluginLoader::instance()->rootApplet()); Q_ASSERT(rootApplet); @@ -62,9 +66,8 @@ class AppletManager continue; } - auto loader = new DAppletLoader(applet); + auto loader = new DAppletLoader(applet, configManager); m_loaders << loader; - QObject::connect(loader, &DAppletLoader::failed, qApp, [this, loader, pluginIds](const QString &pluginId) { if (pluginIds.contains(pluginId)) { m_loaders.removeOne(loader); @@ -72,6 +75,24 @@ class AppletManager } }); } + + QObject::connect(configManager, + &AppletConfigManager::appletEnabledChanged, + qApp, + [this](const QString &pluginId, bool enabled) { + auto loader = loaderForPlugin(pluginId); + if (!loader) { + qCWarning(dsLog) << "Unable to find the loader for applet:" << pluginId; + return; + } + + if (enabled) { + loader->setPluginId(pluginId); + loader->exec(); + } else { + loader->remove(pluginId); + } + }); } void enableSceneview() { @@ -90,6 +111,26 @@ class AppletManager item->deleteLater(); } } + + DAppletLoader *loaderForPlugin(const QString &pluginId) const + { + QString rootPluginId = pluginId; + QSet visitedPluginIds; + while (!visitedPluginIds.contains(rootPluginId)) { + visitedPluginIds.insert(rootPluginId); + const auto parent = DPluginLoader::instance()->parentPlugin(rootPluginId); + if (!parent.isValid()) + break; + rootPluginId = parent.pluginId(); + } + + for (auto loader : m_loaders) { + if (loader->applet() && loader->applet()->pluginId() == rootPluginId) + return loader; + } + return nullptr; + } + QList m_loaders; }; @@ -200,11 +241,13 @@ int main(int argc, char *argv[]) } shell.dconfigsMigrate(); + AppletConfigManager appletConfigManager; + appletConfigManager.ensureAppletConfigs(); // TODO disable qml's cache avoid to parsing error for ExecutionEngine. shell.disableQmlCache(); shell.setFlickableWheelDeceleration(6000); - AppletManager manager(pluginIds); + AppletManager manager(pluginIds, &appletConfigManager); if (parser.isSet(sceneviewOption)) manager.enableSceneview(); diff --git a/shell/org.deepin.dde.shell.json b/shell/org.deepin.dde.shell.json index 55af2a67a..9c2b926ae 100644 --- a/shell/org.deepin.dde.shell.json +++ b/shell/org.deepin.dde.shell.json @@ -11,6 +11,17 @@ "description": "DConfig that has completed migration", "permissions": "readwrite", "visibility": "private" + }, + "enable": { + "value": true, + "serial": 0, + "flags": [], + "name": "Enable Applet", + "name[zh_CN]": "启用 Applet", + "description": "Enable or disable the dde-shell applet selected by the DConfig subpath.", + "description[zh_CN]": "启用或禁用由 DConfig subpath 指定的 dde-shell Applet。", + "permissions": "readwrite", + "visibility": "private" } } }