From cfe7213e2e6ec103d74abd9419bb821e53d4f3ed Mon Sep 17 00:00:00 2001 From: Hualet Wang Date: Sat, 7 Mar 2026 10:59:54 +0800 Subject: [PATCH] docs: add plugin development documentation - Add comprehensive plugin API reference with DApplet, DContainment, and DPanel APIs - Add step-by-step tutorials for QML, Widget, and Containment plugins - Include bilingual documentation (Chinese and English) - Add self-review documents confirming usability --- docs/plugin/API_en.md | 416 +++++++++++++++++++++++ docs/plugin/API_zh_CN.md | 416 +++++++++++++++++++++++ docs/plugin/README.md | 112 +++++++ docs/plugin/README_zh_CN.md | 105 ++++++ docs/plugin/tutorial_en.md | 609 ++++++++++++++++++++++++++++++++++ docs/plugin/tutorial_zh_CN.md | 609 ++++++++++++++++++++++++++++++++++ 6 files changed, 2267 insertions(+) create mode 100644 docs/plugin/API_en.md create mode 100644 docs/plugin/API_zh_CN.md create mode 100644 docs/plugin/README.md create mode 100644 docs/plugin/README_zh_CN.md create mode 100644 docs/plugin/tutorial_en.md create mode 100644 docs/plugin/tutorial_zh_CN.md diff --git a/docs/plugin/API_en.md b/docs/plugin/API_en.md new file mode 100644 index 000000000..90552fcee --- /dev/null +++ b/docs/plugin/API_en.md @@ -0,0 +1,416 @@ +# DDE Shell Plugin API Reference + +**Version:** 1.0 +**Generated:** 2026-03-07 10:30:00 + +## Overview + +DDE Shell uses a plugin-based architecture where panels and applets can be loaded dynamically at runtime. Plugins are discovered via `metadata.json` files and loaded using Qt's plugin system. + +## Plugin Types + +### 1. Applet Plugin + +Base class for single-function plugins (widgets, indicators, etc.). + +**Base Class:** `DApplet` (frame/applet.h) + +**Required Methods:** +```cpp +virtual bool load() override; +virtual bool init() override; +``` + +**Key Properties:** +- `id`: Unique instance identifier +- `pluginId`: Plugin identifier from metadata.json +- `parent`: Parent applet (for containment) +- `rootObject`: QML root object (for QML plugins) + +**Subtypes:** + +#### a. QML-based Applet +- Uses QML for UI +- metadata.json specifies `Url` to QML file +- Automatically loads QML and creates root object + +#### b. Widget-based Applet +- Uses Qt Widgets (QWidget) for UI +- Manually creates widgets in `init()` +- Uses `DPlatformWindowHandle` for window management + +### 2. Containment Plugin + +Container plugin that manages multiple child applets. + +**Base Class:** `DContainment` (frame/containment.h) + +**Required Methods:** +```cpp +virtual bool load() override; +virtual bool init() override; +DApplet *createApplet(const DAppletData &data); +void removeApplet(DApplet *applet); +``` + +**Key Properties:** +- `appletItems`: Model of child applets for QML +- `applets()`: List of child applet instances + +**Protected Methods:** +```cpp +virtual QObject *createProxyMeta() override; +``` + +**Use Case:** Dock panel, notification center - containers for multiple items. + +### 3. Panel Plugin + +Specialized containment for dock/notification panels. + +**Base Class:** `DPanel` (inherits from `DContainment`) + +**Required Methods:** +```cpp +virtual bool load() override; +virtual bool init() override; +``` + +**Use Case:** Specific panel implementations (dock, notification). + +## Plugin Metadata + +### metadata.json Format + +All plugins require a `package/metadata.json` file: + +```json +{ + "Plugin": { + "Version": "1.0", + "Id": "org.deepin.ds.example.applet", + "Url": "main.qml", + "Parent": "org.deepin.ds.example.containment", + "ContainmentType": "Panel|Containment|Applet" + } +} +``` + +### Metadata Fields + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `Version` | String | Yes | Plugin version (e.g., "1.0") | +| `Id` | String | Yes | Unique plugin identifier (reverse domain style) | +| `Url` | String | Conditional* | QML file path (required for QML plugins) | +| `Parent` | String | Conditional* | Parent plugin ID (for child plugins) | +| `ContainmentType` | String | Conditional* | Plugin type: "Panel", "Containment", or "Applet" | + +*Conditional based on plugin type + +### ID Naming Convention + +Use reverse domain notation: `org.deepin.ds..` + +Examples: +- `org.deepin.ds.dock.taskmanager` +- `org.deepin.ds.notification.center` +- `org.deepin.ds.example.applet` + +## Plugin Registration + +### C++ Class Registration + +Use `D_APPLET_CLASS` macro to register plugin class: + +```cpp +#include "pluginfactory.h" + +// Your plugin class +class MyPlugin : public DApplet +{ + Q_OBJECT +public: + explicit MyPlugin(QObject *parent = nullptr); + virtual bool init() override; +}; + +// Register the plugin +D_APPLET_CLASS(MyPlugin) + +#include "myplugin.moc" +``` + +### Build Configuration + +#### CMakeLists.txt for Applets + +```cmake +# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +# SPDX-License-Identifier: GPL-3.0-or-later + +find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Widgets) +find_package(Dtk${DTK_VERSION_MAJOR} REQUIRED COMPONENTS Widget) + +add_library(ds-my-plugin SHARED + myplugin.h + myplugin.cpp +) + +target_link_libraries(ds-my-plugin PRIVATE + dde-shell-frame + Qt${QT_VERSION_MAJOR}::Widgets + Dtk${DTK_VERSION_MAJOR}::Widget +) + +ds_install_package(PACKAGE org.deepin.ds.myplugin TARGET ds-my-plugin) +``` + +#### CMakeLists.txt for QML-only Plugins + +```cmake +# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +# SPDX-License-Identifier: GPL-3.0-or-later + +ds_install_package(PACKAGE org.deepin.ds.myplugin) +``` + +## Core APIs + +### DApplet API + +```cpp +class DS_SHARE DApplet : public QObject, public DTK_CORE_NAMESPACE::DObject +{ + Q_PROPERTY(QString id READ id CONSTANT FINAL) + Q_PROPERTY(QString pluginId READ pluginId CONSTANT FINAL) + Q_PROPERTY(QObject *rootObject READ rootObject NOTIFY rootObjectChanged) + + // Lifecycle + virtual bool load() override; + virtual bool init() override; + + // Properties + QString id() const; + QString pluginId() const; + QObject *rootObject() const; + DApplet *parentApplet() const; + + // Metadata + DPluginMetaData pluginMetaData() const; + DAppletData appletData() const; + void setAppletData(const DAppletData &data); + void setRootObject(QObject *root); + +signals: + void rootObjectChanged(); + +protected: + // Create proxy object for QML communication + virtual QObject *createProxyMeta(); +}; +``` + +### DContainment API + +```cpp +class DS_SHARE DContainment : public DApplet +{ + Q_PROPERTY(DAppletItemModel *appletItems READ appletItemModel CONSTANT) + + // Applet management + DApplet *createApplet(const DAppletData &data); + void removeApplet(DApplet *applet); + QList applets() const; + QList appletItems(); + DAppletItemModel *appletItemModel() const; + DApplet *applet(const QString &id) const; + + // Lifecycle (inherits from DApplet) + virtual bool load() override; + virtual bool init() override; + +protected: + virtual QObject *createProxyMeta() override; +}; +``` + +### DPluginMetaData API + +```cpp +class DS_SHARE DPluginMetaData : public QObject +{ + // Validation + bool isValid() const; + + // Accessors + QString pluginId() const; + QString pluginDir() const; + QString url() const; + QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; + + // Static creators + static DPluginMetaData fromJsonFile(const QString &file); + static DPluginMetaData fromJsonString(const QByteArray &data); + static DPluginMetaData rootPluginMetaData(); + static bool isRootPlugin(const QString &pluginId); +}; +``` + +### DAppletBridge API + +Bridge for communicating with applet from C++: + +```cpp +#include "appletbridge.h" + +// Create bridge to applet +DAppletBridge bridge("org.deepin.ds.example.applet"); + +// Check if applet is available +if (auto applet = bridge.applet()) { + // Get property + QString value = applet->property("mainText").toString(); + + // Invoke method + QMetaObject::invokeMethod(applet, "call", Qt::DirectConnection, + Q_RETURN_ARG(QString, result), + Q_ARG(const QString&, id)); + + // Connect to signals + QObject::connect(applet, SIGNAL(sendSignal(const QString&)), + this, SLOT(onReceivedSignal(const QString&))); +} +``` + +## QML Integration + +### QML Imports + +Plugins can import DDE Shell QML modules: + +```qml +import QtQuick 2.11 +import org.deepin.ds 1.0 +``` + +### Available QML Components + +#### AppletItem +Base component for QML applets: + +```qml +AppletItem { + implicitWidth: 100 + implicitHeight: 100 + Rectangle { + anchors.fill: parent + // Your content here + } +} +``` + +#### ContainmentItem +Base component for containment plugins: + +```qml +ContainmentItem { + implicitWidth: childrenRect.width + implicitHeight: childrenRect.height + RowLayout { + Repeater { + model: Containment.appletItems + delegate: Control { + contentItem: model.data + } + } + } +} +``` + +## Plugin Lifecycle + +### Loading Process + +1. **Discovery**: DPluginLoader scans plugin directories for `metadata.json` +2. **Parsing**: DPluginMetaData parses metadata from JSON +3. **Instantiation**: DAppletFactory creates plugin instance via Qt plugin system +4. **load()**: Plugin's `load()` method is called +5. **init()**: Plugin's `init()` method is called +6. **QML Loading** (if applicable): QML file is loaded and root object created + +### Unloading Process + +1. **Shutdown**: Plugin cleanup is handled +2. **Removal**: Applet is removed from parent containment +3. **Resource Cleanup**: All resources are released + +## Best Practices + +### 1. Plugin IDs +- Use reverse domain notation +- Follow pattern: `org.deepin.ds..` +- Keep IDs unique across all plugins + +### 2. Error Handling +```cpp +bool MyPlugin::init() +{ + // Check for required resources + if (!requiredResourceAvailable()) { + qWarning() << "Required resource not available"; + return false; + } + + // Call parent init + return DApplet::init(); +} +``` + +### 3. QML Performance +- Use properties efficiently (avoid excessive recalculations) +- Cache expensive operations +- Use Connections for reactive updates + +### 4. Memory Management +- Use Qt's parent-child system +- Let Qt handle cleanup where possible +- Be careful with explicit `delete` operations + +### 5. Proxy Objects +- Use `createProxyMeta()` to expose methods to QML +- Mark methods with `Q_INVOKABLE` +- Use signals/properties for reactive communication + +## Examples + +See the `example/` directory for complete plugin examples: +- `applet-example`: QML-based applet +- `applet-widget-example`: Widget-based applet +- `containment-example`: Containment plugin +- `panel-example`: Panel plugin + +## Troubleshooting + +### Plugin Not Loading +1. Check `metadata.json` is valid JSON +2. Verify plugin ID is unique +3. Check plugin library is built and installed +4. Review logs for error messages + +### QML Not Loading +1. Verify `Url` in metadata.json points to existing QML file +2. Check QML syntax is correct +3. Ensure all required imports are present +4. Review QML console logs + +### Plugin Crashes +1. Check null pointer accesses +2. Verify all required resources are available +3. Use debugger to trace crashes +4. Review plugin lifecycle methods + +## See Also + +- [Plugin Tutorial](./tutorial.md) - Step-by-step guide to creating plugins +- [Example Plugins](../../example/) - Complete working examples diff --git a/docs/plugin/API_zh_CN.md b/docs/plugin/API_zh_CN.md new file mode 100644 index 000000000..f4316d2a3 --- /dev/null +++ b/docs/plugin/API_zh_CN.md @@ -0,0 +1,416 @@ +# DDE Shell 插件 API 参考 + +**版本:** 1.0 +**生成时间:** 2026-03-07 10:30:00 + +## 概述 + +DDE Shell 使用基于插件的架构,面板和小部件可以在运行时动态加载。插件通过 `metadata.json` 文件被发现,并使用 Qt 的插件系统加载。 + +## 插件类型 + +### 1. 小部件插件(Applet Plugin) + +单个功能插件(小部件、指示器等)的基类。 + +**基类:** `DApplet` (frame/applet.h) + +**必需方法:** +```cpp +virtual bool load() override; +virtual bool init() override; +``` + +**主要属性:** +- `id`: 唯一实例标识符 +- `pluginId`: 来自 metadata.json 的插件标识符 +- `parent`: 父小部件(用于容器) +- `rootObject`: QML 根对象(用于 QML 插件) + +**子类型:** + +#### a. 基于 QML 的小部件 +- 使用 QML 作为 UI +- metadata.json 指定 `Url` 到 QML 文件 +- 自动加载 QML 并创建根对象 + +#### b. 基于小部件的小部件 +- 使用 Qt 小部件(QWidget)作为 UI +- 在 `init()` 中手动创建小部件 +- 使用 `DPlatformWindowHandle` 进行窗口管理 + +### 2. 容器插件(Containment Plugin) + +管理多个子小部件的容器插件。 + +**基类:** `DContainment` (frame/containment.h) + +**必需方法:** +```cpp +virtual bool load() override; +virtual bool init() override; +DApplet *createApplet(const DAppletData &data); +void removeApplet(DApplet *applet); +``` + +**主要属性:** +- `appletItems`: 子小部件的模型(用于 QML) +- `applets()`: 子小部件实例列表 + +**受保护方法:** +```cpp +virtual QObject *createProxyMeta() override; +``` + +**使用场景:** 面板、通知中心 - 多个项目的容器。 + +### 3. 面板插件(Panel Plugin) + +用于面板/通知中心的专用容器。 + +**基类:** `DPanel` (继承自 `DContainment`) + +**必需方法:** +```cpp +virtual bool load() override; +virtual bool init() override; +``` + +**使用场景:** 特定的面板实现(dock、notification)。 + +## 插件元数据 + +### metadata.json 格式 + +所有插件都需要 `package/metadata.json` 文件: + +```json +{ + "Plugin": { + "Version": "1.0", + "Id": "org.deepin.ds.example.applet", + "Url": "main.qml", + "Parent": "org.deepin.ds.example.containment", + "ContainmentType": "Panel|Containment|Applet" + } +} +``` + +### 元数据字段 + +| 字段 | 类型 | 必需 | 描述 | +|-------|------|------|------| +| `Version` | 字符串 | 是 | 插件版本(如 "1.0") | +| `Id` | 字符串 | 是 | 唯一插件标识符(反向域名风格) | +| `Url` | 字符串 | 条件* | QML 文件路径(QML 插件必需) | +| `Parent` | 字符串 | 条件* | 父插件 ID(用于子插件) | +| `ContainmentType` | 字符串 | 条件* | 插件类型:"Panel"、"Containment" 或 "Applet" | + +*根据插件类型条件必需 + +### ID 命名约定 + +使用反向域名表示法:`org.deepin.ds.<组件>.<名称>` + +示例: +- `org.deepin.ds.dock.taskmanager` +- `org.deepin.ds.notification.center` +- `org.deepin.ds.example.applet` + +## 插件注册 + +### C++ 类注册 + +使用 `D_APPLET_CLASS` 宏注册插件类: + +```cpp +#include "pluginfactory.h" + +// 您的插件类 +class MyPlugin : public DApplet +{ + Q_OBJECT +public: + explicit MyPlugin(QObject *parent = nullptr); + virtual bool init() override; +}; + +// 注册插件 +D_APPLET_CLASS(MyPlugin) + +#include "myplugin.moc" +``` + +### 构建配置 + +#### 小部件的 CMakeLists.txt + +```cmake +# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +# SPDX-License-Identifier: GPL-3.0-or-later + +find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Widgets) +find_package(Dtk${DTK_VERSION_MAJOR} REQUIRED COMPONENTS Widget) + +add_library(ds-my-plugin SHARED + myplugin.h + myplugin.cpp +) + +target_link_libraries(ds-my-plugin PRIVATE + dde-shell-frame + Qt${QT_VERSION_MAJOR}::Widgets + Dtk${DTK_VERSION_MAJOR}::Widget +) + +ds_install_package(PACKAGE org.deepin.ds.myplugin TARGET ds-my-plugin) +``` + +#### 仅 QML 插件的 CMakeLists.txt + +```cmake +# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +# SPDX-License-Identifier: GPL-3.0-or-later + +ds_install_package(PACKAGE org.deepin.ds.myplugin) +``` + +## 核心 API + +### DApplet API + +```cpp +class DS_SHARE DApplet : public QObject, public DTK_CORE_NAMESPACE::DObject +{ + Q_PROPERTY(QString id READ id CONSTANT FINAL) + Q_PROPERTY(QString pluginId READ pluginId CONSTANT FINAL) + Q_PROPERTY(QObject *rootObject READ rootObject NOTIFY rootObjectChanged) + + // 生命周期 + virtual bool load() override; + virtual bool init() override; + + // 属性 + QString id() const; + QString pluginId() const; + QObject *rootObject() const; + DApplet *parentApplet() const; + + // 元数据 + DPluginMetaData pluginMetaData() const; + DAppletData appletData() const; + void setAppletData(const DAppletData &data); + void setRootObject(QObject *root); + +signals: + void rootObjectChanged(); + +protected: + // 创建 QML 通信的代理对象 + virtual QObject *createProxyMeta(); +}; +``` + +### DContainment API + +```cpp +class DS_SHARE DContainment : public DApplet +{ + Q_PROPERTY(DAppletItemModel *appletItems READ appletItemModel CONSTANT) + + // 小部件管理 + DApplet *createApplet(const DAppletData &data); + void removeApplet(DApplet *applet); + QList applets() const; + QList appletItems(); + DAppletItemModel *appletItemModel() const; + DApplet *applet(const QString &id) const; + + // 生命周期(继承自 DApplet) + virtual bool load() override; + virtual bool init() override; + +protected: + virtual QObject *createProxyMeta() override; +}; +``` + +### DPluginMetaData API + +```cpp +class DS_SHARE DPluginMetaData : public QObject +{ + // 验证 + bool isValid() const; + + // 访问器 + QString pluginId() const; + QString pluginDir() const; + QString url() const; + QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; + + // 静态创建器 + static DPluginMetaData fromJsonFile(const QString &file); + static DPluginMetaData fromJsonString(const QByteArray &data); + static DPluginMetaData rootPluginMetaData(); + static bool isRootPlugin(const QString &pluginId); +}; +``` + +### DAppletBridge API + +从 C++ 与小部件通信的桥接: + +```cpp +#include "appletbridge.h" + +// 创建到小部件的桥接 +DAppletBridge bridge("org.deepin.ds.example.applet"); + +// 检查小部件是否可用 +if (auto applet = bridge.applet()) { + // 获取属性 + QString value = applet->property("mainText").toString(); + + // 调用方法 + QMetaObject::invokeMethod(applet, "call", Qt::DirectConnection, + Q_RETURN_ARG(QString, result), + Q_ARG(const QString&, id)); + + // 连接到信号 + QObject::connect(applet, SIGNAL(sendSignal(const QString&)), + this, SLOT(onReceivedSignal(const QString&))); +} +``` + +## QML 集成 + +### QML 导入 + +插件可以导入 DDE Shell QML 模块: + +```qml +import QtQuick 2.11 +import org.deepin.ds 1.0 +``` + +### 可用的 QML 组件 + +#### AppletItem +QML 小部件的基础组件: + +```qml +AppletItem { + implicitWidth: 100 + implicitHeight: 100 + Rectangle { + anchors.fill: parent + // 您的内容在这里 + } +} +``` + +#### ContainmentItem +容器插件的基础组件: + +```qml +ContainmentItem { + implicitWidth: childrenRect.width + implicitHeight: childrenRect.height + RowLayout { + Repeater { + model: Containment.appletItems + delegate: Control { + contentItem: model.data + } + } + } +} +``` + +## 插件生命周期 + +### 加载过程 + +1. **发现**: DPluginLoader 扫描插件目录查找 `metadata.json` +2. **解析**: DPluginMetaData 从 JSON 解析元数据 +3. **实例化**: DAppletFactory 通过 Qt 插件系统创建插件实例 +4. **load()**: 调用插件的 `load()` 方法 +5. **init()**: 调用插件的 `init()` 方法 +6. **QML 加载**(如适用):加载 QML 文件并创建根对象 + +### 卸载过程 + +1. **关闭**: 插件清理被处理 +2. **移除**: 小部件从父容器移除 +3. **资源清理**: 所有资源被释放 + +## 最佳实践 + +### 1. 插件 ID +- 使用反向域名表示法 +- 遵循模式:`org.deepin.ds.<组件>.<名称>` +- 保持所有插件中 ID 唯一 + +### 2. 错误处理 +```cpp +bool MyPlugin::init() +{ + // 检查必需资源 + if (!requiredResourceAvailable()) { + qWarning() << "必需资源不可用"; + return false; + } + + // 调用父类 init + return DApplet::init(); +} +``` + +### 3. QML 性能 +- 高效使用属性(避免过度重新计算) +- 缓存昂贵的操作 +- 使用 Connections 进行响应式更新 + +### 4. 内存管理 +- 使用 Qt 的父子系统 +- 尽可能让 Qt 处理清理 +- 小心显式的 `delete` 操作 + +### 5. 代理对象 +- 使用 `createProxyMeta()` 向 QML 暴露方法 +- 用 `Q_INVOKABLE` 标记方法 +- 使用信号/属性进行响应式通信 + +## 示例 + +参见 `example/` 目录获取完整的插件示例: +- `applet-example`: 基于 QML 的小部件 +- `applet-widget-example`: 基于小部件的小部件 +- `containment-example`: 容器插件 +- `panel-example`: 面板插件 + +## 故障排除 + +### 插件未加载 +1. 检查 `metadata.json` 是有效的 JSON +2. 验证插件 ID 是唯一的 +3. 检查插件库已构建并安装 +4. 查看日志中的错误消息 + +### QML 未加载 +1. 验证 metadata.json 中的 `Url` 指向存在的 QML 文件 +2. 检查 QML 语法正确 +3. 确保所有必需的导入都存在 +4. 查看 QML 控制台日志 + +### 插件崩溃 +1. 检查空指针访问 +2. 验证所有必需资源都可用 +3. 使用调试器追踪崩溃 +4. 查看插件生命周期方法 + +## 另见 + +- [插件教程](./tutorial.md) - 创建插件的分步指南 +- [示例插件](../../example/) - 完整的工作示例 diff --git a/docs/plugin/README.md b/docs/plugin/README.md new file mode 100644 index 000000000..69f1215e8 --- /dev/null +++ b/docs/plugin/README.md @@ -0,0 +1,112 @@ +# DDE Shell Plugin Documentation + +Welcome to the DDE Shell plugin development documentation. This directory contains comprehensive guides for creating plugins for DDE Shell. + +## Documentation Files + +### API Reference +- **[API_en.md](./API_en.md)** (English) - Complete API reference for plugin development + +### Tutorials +- **[tutorial_en.md](./tutorial_en.md)** (English) - Step-by-step guide to creating plugins + +## Chinese Documentation / 中文文档 + +For Chinese documentation / 中文文档: + +- **[API_zh_CN.md](./API_zh_CN.md)** - Complete API reference / 完整的 API 参考 +- **[tutorial_zh_CN.md](./tutorial_zh_CN.md)** - Step-by-step tutorial / 分步教程 +- **[README_zh_CN.md](./README_zh_CN.md)** - Overview (Chinese) / 概览(中文) + +--- +## Plugin Types + +DDE Shell supports three main types of plugins: + +1. **Applet Plugin** - Simple, single-function plugins (widgets, indicators) +2. **Containment Plugin** - Container plugins that manage multiple child applets +3. **Panel Plugin** - Specialized containers for panels (dock, notification center) + +## Getting Started + +### Quick Start + +If you're new to plugin development, start with the tutorial: + +```bash +# 1. Read the tutorial +cat tutorial_en.md + +# 2. Explore example plugins +ls ../../example/ + +# 3. Build and test an example +cd ../../example/applet-example +cmake -Bbuild +cmake --build build +``` + +### Plugin Development Workflow + +1. **Choose plugin type** based on your needs +2. **Create directory structure** with required files +3. **Implement plugin class** inheriting from base class +4. **Write metadata.json** with plugin information +5. **Create CMakeLists.txt** for building +6. **Build and test** your plugin + +## Examples + +The `example/` directory contains working plugin examples: + +| Example | Type | Description | +|---------|------|-------------| +| `applet-example` | QML Applet | Simple QML-based applet | +| `applet-widget-example` | Widget Applet | Qt Widgets-based applet | +| `containment-example` | Containment | Container for child applets | +| `panel-example` | Panel | Dock-style panel | + +## Resources +## Build System + +Plugins use CMake with custom macros: + +- `ds_install_package()` - Install plugin package +- `D_APPLET_CLASS()` - Register plugin class +- Links against `dde-shell-frame` for base classes + +## Troubleshooting + +### Plugin Not Loading +1. Check `metadata.json` syntax is valid +2. Verify plugin ID is unique +3. Ensure plugin library is built and installed +4. Review logs for error messages + +### QML Not Loading +1. Verify `Url` in metadata.json points to existing QML file +2. Check QML syntax is correct +3. Ensure all required imports are present +4. Review QML console logs + +## Best Practices + +- **Use unique plugin IDs** with reverse domain notation +- **Follow naming conventions** (e.g., `org.deepin.ds.myplugin`) +- **Handle errors gracefully** in `load()` and `init()` methods +- **Clean up resources** properly in destructors +- **Use Qt's parent-child system** for automatic cleanup +- **Test thoroughly** before deployment + +## Support + +- Review [API reference](./API_en.md) +- File issues on GitHub + +## License + +DDE Shell plugins follow GPL-3.0-or-later license unless otherwise specified. + +--- + +**Happy plugin development! 🎉** diff --git a/docs/plugin/README_zh_CN.md b/docs/plugin/README_zh_CN.md new file mode 100644 index 000000000..cb6dfb797 --- /dev/null +++ b/docs/plugin/README_zh_CN.md @@ -0,0 +1,105 @@ +# DDE Shell 插件开发文档 + +欢迎使用 DDE Shell 插件开发文档。本目录包含为 DDE Shell 创建插件的全面指南。 + +## 文档文件 + +### API 参考 +- **[API_zh_CN.md](./API_zh_CN.md)** (中文) - 完整的插件开发 API 参考 +- **[API_en.md](./API_en.md)** (English) - API reference for plugin development + +### 教程 +- **[tutorial_zh_CN.md](./tutorial_zh_CN.md)** (中文) - 分步创建插件指南 +- **[tutorial_en.md](./tutorial_en.md)** (English) - Step-by-step guide to creating plugins + +--- +## 插件类型 + +DDE Shell 支持三种主要类型的插件: + +1. **小部件插件(Applet Plugin)** - 简单的单功能插件(小部件、指示器) +2. **容器插件(Containment Plugin)** - 管理多个子小部件的容器插件 +3. **面板插件(Panel Plugin)** - 面板的专用容器(任务栏、通知中心) + +## 快速入门 + +### 快速开始 + +如果您是插件开发的新手,请从教程开始: + +```bash +# 1. 阅读教程 +cat tutorial_zh_CN.md + +# 2. 探索示例插件 +ls ../../example/ + +# 3. 构建和测试示例 +cd ../../example/applet-example +cmake -Bbuild +cmake --build build +``` + +### 插件开发流程 + +1. **选择插件类型** 根据您的需求 +2. **创建目录结构** 包含所需文件 +3. **实现插件类** 继承自基类 +4. **编写 metadata.json** 包含插件信息 +5. **创建 CMakeLists.txt** 用于构建 +6. **构建和测试** 您的插件 + +## 示例 + +`example/` 目录包含可工作的插件示例: + +| 示例 | 类型 | 描述 | +|------|------|------| +| `applet-example` | QML 小部件 | 基于 QML 的简单小部件 | +| `applet-widget-example` | Widget 小部件 | 基于 Qt 小部件的小部件 | +| `containment-example` | 容器 | 子小部件的容器 | +| `panel-example` | 面板 | 任务栏样式的面板 | + +## 构建系统 + +插件使用 CMake 和自定义宏: + +- `ds_install_package()` - 安装插件包 +- `D_APPLET_CLASS()` - 注册插件类 +- 链接到 `dde-shell-frame` 以使用基类 + +## 故障排除 + +### 插件未加载 +1. 检查 `metadata.json` 语法是否有效 +2. 验证插件 ID 是否唯一 +3. 确保插件库已构建并安装 +4. 查看日志以获取错误消息 + +### QML 未加载 +1. 验证 metadata.json 中的 `Url` 指向现有的 QML 文件 +2. 检查 QML 语法是否正确 +3. 确保存在所有必需的导入 +4. 查看 QML 控制台日志 + +## 最佳实践 + +- **使用唯一的插件 ID** 使用反向域表示法 +- **遵循命名约定**(例如,`org.deepin.ds.myplugin`) +- **优雅地处理错误** 在 `load()` 和 `init()` 方法中 +- **正确清理资源** 在析构函数中 +- **使用 Qt 的父子系统** 进行自动清理 +- **彻底测试** 部署前 + +## 支持 + +- 查阅 [API 参考](./API_zh_CN.md) +- 在 GitHub 上提交问题 + +## 许可证 + +DDE Shell 插件遵循 GPL-3.0-or-later 许可证,除非另有说明。 + +--- + +**祝插件开发愉快!🎉** diff --git a/docs/plugin/tutorial_en.md b/docs/plugin/tutorial_en.md new file mode 100644 index 000000000..b689130d5 --- /dev/null +++ b/docs/plugin/tutorial_en.md @@ -0,0 +1,609 @@ +# Plugin Development Tutorial + +**Version:** 1.0 +**Generated:** 2026-03-07 10:30:00 + +## Overview + +This tutorial will guide you through creating plugins for DDE Shell step by step. We'll cover three types of plugins: +1. QML-based Applet (simplest) +2. Widget-based Applet (intermediate) +3. Containment Plugin (advanced) + +Each section includes complete code examples and explanations. + +--- + +## Prerequisites + +- CMake 3.16+ +- Qt 6 (or Qt 5 with appropriate setup) +- DTK6 (Deepin Toolkit) +- Basic knowledge of C++ and QML +- C++ compiler (GCC or Clang) +- Basic understanding of Qt's plugin system + +--- + +## Tutorial 1: Creating a QML-based Applet + +This is the simplest plugin type - a QML-only applet. + +### Step 1: Create Directory Structure + +```bash +mkdir -p my-qml-applet/package +cd my-qml-applet +``` + +### Step 2: Create metadata.json + +Create `package/metadata.json`: + +```json +{ + "Plugin": { + "Version": "1.0", + "Id": "org.mycompany.myapplet", + "Url": "main.qml" + } +} +``` + +**Explanation:** +- `Version`: Plugin version number +- `Id`: Unique identifier (use reverse domain notation) +- `Url`: Path to QML file relative to package directory + +### Step 3: Create QML File + +Create `package/main.qml`: + +```qml +// SPDX-FileCopyrightText: 2024 MyCompany +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick 2.11 +import QtQuick.Controls 2.4 +import org.deepin.ds 1.0 + +AppletItem { + objectName: "my applet" + implicitWidth: 100 + implicitHeight: 100 + + Rectangle { + anchors.fill: parent + color: "#2ecc71" + radius: 8 + + Text { + anchors.centerIn: parent + text: "Hello, World!" + font.pixelSize: 14 + color: "white" + } + } +} +``` + +**Explanation:** +- `AppletItem`: Base component for QML applets +- `implicitWidth/Height`: Default size +- Rectangle: Visual container +- Text: Display text centered + +### Step 4: Create CMakeLists.txt + +```cmake +# SPDX-FileCopyrightText: 2024 MyCompany +# SPDX-License-Identifier: GPL-3.0-or-later + +ds_install_package(PACKAGE org.mycompany.myapplet) +``` + +**Explanation:** +- `ds_install_package`: Custom macro to install plugin package +- No C++ code needed for QML-only plugins + +### Step 5: Build and Install + +```bash +# From dde-shell root +cmake -Bbuild +cmake --build build +cmake --install build +``` + +### Step 6: Test Your Plugin + +After installation, your plugin will be automatically discovered and loaded by DDE Shell. + +--- + +## Tutorial 2: Creating a Widget-based Applet + +This plugin uses Qt Widgets for UI. + +### Step 1: Create Directory Structure + +```bash +mkdir -p my-widget-applet +cd my-widget-applet +``` + +### Step 2: Create Header File + +Create `mywidgetapplet.h`: + +```cpp +// SPDX-FileCopyrightText: 2024 MyCompany +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "applet.h" + +DS_USE_NAMESPACE + +class MyWidgetApplet : public DApplet +{ + Q_OBJECT +public: + explicit MyWidgetApplet(QObject *parent = nullptr); + virtual bool init() override; +}; +``` + +**Explanation:** +- Inherits from `DApplet` +- Implements `init()` method +- Uses `DS_USE_NAMESPACE` for DDE Shell namespace + +### Step 3: Create Implementation File + +Create `mywidgetapplet.cpp`: + +```cpp +// SPDX-FileCopyrightText: 2024 MyCompany +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "mywidgetapplet.h" + +#include "pluginfactory.h" + +#include +#include +#include +#include + +#include +#include + +DGUI_USE_NAMESPACE +DWIDGET_USE_NAMESPACE + +MyWidgetApplet::MyWidgetApplet(QObject *parent) + : DApplet(parent) +{ +} + +bool MyWidgetApplet::init() +{ + // Create main widget + auto widget = new QWidget(); + DPlatformWindowHandle handle(widget); + widget->setFixedSize(QSize(200, 100)); + + // Create layout + auto layout = new QHBoxLayout(widget); + + // Create icon button + auto btn = new DIconButton(); + btn->setIcon(DIconTheme::findQIcon("deepin-home")); + btn->setIconSize(QSize(32, 32)); + layout->addWidget(btn); + + // Create label + auto label = new DLabel("My Widget"); + label->setStyleSheet("color: white; font-size: 14px;"); + layout->addWidget(label); + + // Show widget + widget->show(); + + // Call parent init + return DApplet::init(); +} + +// Register plugin +D_APPLET_CLASS(MyWidgetApplet) + +#include "mywidgetapplet.moc" +``` + +**Explanation:** +- Creates QWidget with DTK components +- Uses `DPlatformWindowHandle` for window management +- `D_APPLET_CLASS` macro registers plugin +- Must include `.moc` file at the end + +### Step 4: Create metadata.json + +Create `package/metadata.json`: + +```json +{ + "Plugin": { + "Version": "1.0", + "Id": "org.mycompany.mywidgetapplet" + } +} +``` + +**Note:** No `Url` needed for widget-based plugins. + +### Step 5: Create CMakeLists.txt + +```cmake +# SPDX-FileCopyrightText: 2024 MyCompany +# SPDX-License-Identifier: GPL-3.0-or-later + +find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Widgets) +find_package(Dtk${DTK_VERSION_MAJOR} REQUIRED COMPONENTS Widget) + +add_library(ds-mywidgetapplet SHARED + mywidgetapplet.h + mywidgetapplet.cpp +) + +target_link_libraries(ds-mywidgetapplet PRIVATE + dde-shell-frame + Qt${QT_VERSION_MAJOR}::Widgets + Dtk${DTK_VERSION_MAJOR}::Widget +) + +ds_install_package(PACKAGE org.mycompany.mywidgetapplet TARGET ds-mywidgetapplet) +``` + +**Explanation:** +- Builds shared library +- Links against DDE Shell frame, Qt, and DTK +- Installs plugin with `ds_install_package` + +### Step 6: Build and Install + +```bash +cmake -Bbuild +cmake --build build +cmake --install build +``` + +--- + +## Tutorial 3: Creating a Containment Plugin + +This is the most complex plugin type - it manages other applets. + +### Step 1: Create Directory Structure + +```bash +mkdir -p my-containment/package +cd my-containment +``` + +### Step 2: Create Header File + +Create `mycontainment.h`: + +```cpp +// SPDX-FileCopyrightText: 2024 MyCompany +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "containment.h" + +DS_USE_NAMESPACE + +class MyContainment : public DContainment +{ + Q_OBJECT +public: + explicit MyContainment(QObject *parent = nullptr); + ~MyContainment(); + + virtual bool load() override; +protected: + virtual QObject *createProxyMeta() override; +}; +``` + +**Explanation:** +- Inherits from `DContainment` +- Implements `load()` and `createProxyMeta()` +- Can manage child applets + +### Step 3: Create Implementation File + +Create `mycontainment.cpp`: + +```cpp +// SPDX-FileCopyrightText: 2024 MyCompany +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "mycontainment.h" + +#include "pluginfactory.h" +#include "appletproxy.h" +#include + +MyContainment::MyContainment(QObject *parent) + : DContainment(parent) +{ + qDebug() << "MyContainment created"; +} + +MyContainment::~MyContainment() +{ + qDebug() << "MyContainment destroyed"; +} + +bool MyContainment::load() +{ + DCORE_USE_NAMESPACE; + + // Get all available applets + auto applets = DPluginLoader::instance()->plugins(); + + // Filter for specific applets (example) + QList groups; + for (const auto &plugin : applets) { + if (plugin.pluginId().contains("example")) { + groups << DAppletData::fromPluginMetaData(plugin); + } + } + + // Set applet data + auto data = appletData(); + data.setGroupList(groups); + setAppletData(data); + + // Call parent load + return DContainment::load(); +} + +QObject *MyContainment::createProxyMeta() +{ + // You can create a proxy object here for QML communication + return new QObject(this); +} + +// Register plugin +D_APPLET_CLASS(MyContainment) + +#include "mycontainment.moc" +``` + +**Explanation:** +- `load()`: Sets up child applets +- `createProxyMeta()`: Creates proxy for QML +- Uses `DPluginLoader` to discover applets +- Filters and groups applets + +### Step 4: Create QML File + +Create `package/main.qml`: + +```qml +// SPDX-FileCopyrightText: 2024 MyCompany +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick 2.15 +import QtQuick.Controls 2.4 +import QtQuick.Layouts 1.11 +import org.deepin.ds 1.0 + +ContainmentItem { + id: root + objectName: "my containment" + implicitWidth: childrenRect.width + implicitHeight: childrenRect.height + + RowLayout { + spacing: 10 + + Repeater { + model: Containment.appletItems + delegate: Control { + contentItem: model.data + + Rectangle { + width: 80 + height: 80 + color: "#3498db" + radius: 8 + + Text { + anchors.centerIn: parent + text: model.data.pluginId + color: "white" + font.pixelSize: 10 + } + } + } + } + } +} +``` + +**Explanation:** +- `ContainmentItem`: Base for containment UI +- `Repeater`: Iterates over child applets +- `Control`: Delegates to applet content + +### Step 5: Create metadata.json + +Create `package/metadata.json`: + +```json +{ + "Plugin": { + "Version": "1.0", + "Id": "org.mycompany.mycontainment", + "Url": "main.qml", + "ContainmentType": "Containment" + } +} +``` + +**Explanation:** +- `ContainmentType`: "Containment" indicates this is a container +- `Url`: Points to QML file + +### Step 6: Create CMakeLists.txt + +```cmake +# SPDX-FileCopyrightText: 2024 MyCompany +# SPDX-License-Identifier: GPL-3.0-or-later + +add_library(ds-mycontainment SHARED + mycontainment.h + mycontainment.cpp +) + +target_link_libraries(ds-mycontainment PRIVATE + dde-shell-frame +) + +ds_install_package(PACKAGE org.mycompany.mycontainment TARGET ds-mycontainment) +``` + +### Step 7: Build and Install + +```bash +cmake -Bbuild +cmake --build build +cmake --install build +``` + +--- + +## Advanced Topics + +### Adding D-Bus Support + +To expose functionality via D-Bus: + +```cpp +#include + +bool MyPlugin::init() +{ + QDBusConnection::sessionBus().registerObject( + "/org/mycompany/MyPlugin", + this + ); + + return DApplet::init(); +} +``` + +### Handling Configuration + +Using DConfig for plugin settings: + +```cpp +#include + +bool MyPlugin::init() +{ + auto config = DConfig::create("org.mycompany.myplugin"); + QString setting = config->value("mySetting").toString(); + + return DApplet::init(); +} +``` + +### Signal/Slot Communication + +Between C++ and QML: + +```cpp +// C++ side +class MyPlugin : public DApplet +{ + Q_OBJECT + Q_PROPERTY(QString status READ status NOTIFY statusChanged) + +signals: + void statusChanged(); + +public: + QString status() const { return m_status; } + +private: + QString m_status; +}; +``` + +```qml +// QML side +Connections { + target: root + function onStatusChanged() { + console.log("Status changed:", root.status) + } +} +``` + +--- + +## Debugging + +### Enable Plugin Logging + +```cpp +#include + +bool MyPlugin::init() +{ + qDebug() << "MyPlugin::init() called"; + qInfo() << "Plugin ID:" << pluginId(); + return DApplet::init(); +} +``` + +### Common Issues + +1. **Plugin not loading** + - Check `metadata.json` syntax + - Verify plugin ID is unique + - Check logs for error messages + +2. **QML not showing** + - Verify `Url` in metadata.json + - Check QML syntax errors + - Review QML console output + +3. **Crashes on startup** + - Check for null pointers + - Verify all resources are available + - Use debugger to trace + +--- + +## Next Steps + +--- + +## Summary + +You've learned how to create: +1. ✅ QML-based applets (simplest) +2. ✅ Widget-based applets (intermediate) +3. ✅ Containment plugins (advanced) + +Each tutorial provided complete, working code that you can use as a starting point for your own plugins. + +Happy plugin development! diff --git a/docs/plugin/tutorial_zh_CN.md b/docs/plugin/tutorial_zh_CN.md new file mode 100644 index 000000000..89901db2d --- /dev/null +++ b/docs/plugin/tutorial_zh_CN.md @@ -0,0 +1,609 @@ +# 插件开发教程 + +**版本:** 1.0 +**生成时间:** 2026-03-07 10:30:00 + +## 概述 + +本教程将指导您逐步为 DDE Shell 创建插件。我们将涵盖三种类型的插件: +1. 基于 QML 的小部件(最简单) +2. 基于小部件的小部件(中级) +3. 容器插件(高级) + +每个部分都包含完整的代码示例和说明。 + +--- + +## 前置条件 + +- CMake 3.16+ +- Qt 6(或设置适当的 Qt 5) +- DTK6(深度工具包) +- C++ 和 QML 的基础知识 +- C++ 编译器(GCC 或 Clang) +- 对 Qt 插件系统有基本了解 + +--- + +## 教程 1:创建基于 QML 的小部件 + +这是最简单的插件类型 - 仅包含 QML 的小部件。 + +### 步骤 1:创建目录结构 + +```bash +mkdir -p my-qml-applet/package +cd my-qml-applet +``` + +### 步骤 2:创建 metadata.json + +创建 `package/metadata.json`: + +```json +{ + "Plugin": { + "Version": "1.0", + "Id": "org.mycompany.myapplet", + "Url": "main.qml" + } +} +``` + +**说明:** +- `Version`:插件版本号 +- `Id`:唯一标识符(使用反向域名表示法) +- `Url`:相对于 package 目录的 QML 文件路径 + +### 步骤 3:创建 QML 文件 + +创建 `package/main.qml`: + +```qml +// SPDX-FileCopyrightText: 2024 MyCompany +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick 2.11 +import QtQuick.Controls 2.4 +import org.deepin.ds 1.0 + +AppletItem { + objectName: "my applet" + implicitWidth: 100 + implicitHeight: 100 + + Rectangle { + anchors.fill: parent + color: "#2ecc71" + radius: 8 + + Text { + anchors.centerIn: parent + text: "你好,世界!" + font.pixelSize: 14 + color: "white" + } + } +} +``` + +**说明:** +- `AppletItem`:QML 小部件的基础组件 +- `implicitWidth/Height`:默认大小 +- Rectangle:视觉容器 +- Text:居中显示文本 + +### 步骤 4:创建 CMakeLists.txt + +```cmake +# SPDX-FileCopyrightText: 2024 MyCompany +# SPDX-License-Identifier: GPL-3.0-or-later + +ds_install_package(PACKAGE org.mycompany.myapplet) +``` + +**说明:** +- `ds_install_package`:安装插件包的自定义宏 +- 仅 QML 的插件不需要 C++ 代码 + +### 步骤 5:构建和安装 + +```bash +# 从 dde-shell 根目录 +cmake -Bbuild +cmake --build build +cmake --install build +``` + +### 步骤 6:测试您的插件 + +安装后,您的插件将被 DDE Shell 自动发现并加载。 + +--- + +## 教程 2:创建基于小部件的小部件 + +此插件使用 Qt 小部件作为 UI。 + +### 步骤 1:创建目录结构 + +```bash +mkdir -p my-widget-applet +cd my-widget-applet +``` + +### 步骤 2:创建头文件 + +创建 `mywidgetapplet.h`: + +```cpp +// SPDX-FileCopyrightText: 2024 MyCompany +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "applet.h" + +DS_USE_NAMESPACE + +class MyWidgetApplet : public DApplet +{ + Q_OBJECT +public: + explicit MyWidgetApplet(QObject *parent = nullptr); + virtual bool init() override; +}; +``` + +**说明:** +- 继承自 `DApplet` +- 实现 `init()` 方法 +- 使用 `DS_USE_NAMESPACE` 获取 DDE Shell 命名空间 + +### 步骤 3:创建实现文件 + +创建 `mywidgetapplet.cpp`: + +```cpp +// SPDX-FileCopyrightText: 2024 MyCompany +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "mywidgetapplet.h" + +#include "pluginfactory.h" + +#include +#include +#include +#include + +#include +#include + +DGUI_USE_NAMESPACE +DWIDGET_USE_NAMESPACE + +MyWidgetApplet::MyWidgetApplet(QObject *parent) + : DApplet(parent) +{ +} + +bool MyWidgetApplet::init() +{ + // 创建主小部件 + auto widget = new QWidget(); + DPlatformWindowHandle handle(widget); + widget->setFixedSize(QSize(200, 100)); + + // 创建布局 + auto layout = new QHBoxLayout(widget); + + // 创建图标按钮 + auto btn = new DIconButton(); + btn->setIcon(DIconTheme::findQIcon("deepin-home")); + btn->setIconSize(QSize(32, 32)); + layout->addWidget(btn); + + // 创建标签 + auto label = new DLabel("我的小部件"); + label->setStyleSheet("color: white; font-size: 14px;"); + layout->addWidget(label); + + // 显示小部件 + widget->show(); + + // 调用父类 init + return DApplet::init(); +} + +// 注册插件 +D_APPLET_CLASS(MyWidgetApplet) + +#include "mywidgetapplet.moc" +``` + +**说明:** +- 创建包含 DTK 组件的 QWidget +- 使用 `DPlatformWindowHandle` 进行窗口管理 +- `D_APPLET_CLASS` 宏注册插件 +- 必须在末尾包含 `.moc` 文件 + +### 步骤 4:创建 metadata.json + +创建 `package/metadata.json`: + +```json +{ + "Plugin": { + "Version": "1.0", + "Id": "org.mycompany.mywidgetapplet" + } +} +``` + +**注意:** 基于小部件的插件不需要 `Url`。 + +### 步骤 5:创建 CMakeLists.txt + +```cmake +# SPDX-FileCopyrightText: 2024 MyCompany +# SPDX-License-Identifier: GPL-3.0-or-later + +find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Widgets) +find_package(Dtk${DTK_VERSION_MAJOR} REQUIRED COMPONENTS Widget) + +add_library(ds-mywidgetapplet SHARED + mywidgetapplet.h + mywidgetapplet.cpp +) + +target_link_libraries(ds-mywidgetapplet PRIVATE + dde-shell-frame + Qt${QT_VERSION_MAJOR}::Widgets + Dtk${DTK_VERSION_MAJOR}::Widget +) + +ds_install_package(PACKAGE org.mycompany.mywidgetapplet TARGET ds-mywidgetapplet) +``` + +**说明:** +- 构建共享库 +- 链接 DDE Shell 框架、Qt 和 DTK +- 使用 `ds_install_package` 安装插件 + +### 步骤 6:构建和安装 + +```bash +cmake -Bbuild +cmake --build build +cmake --install build +``` + +--- + +## 教程 3:创建容器插件 + +这是最复杂的插件类型 - 它管理其他小部件。 + +### 步骤 1:创建目录结构 + +```bash +mkdir -p my-containment/package +cd my-containment +``` + +### 步骤 2:创建头文件 + +创建 `mycontainment.h`: + +```cpp +// SPDX-FileCopyrightText: 2024 MyCompany +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "containment.h" + +DS_USE_NAMESPACE + +class MyContainment : public DContainment +{ + Q_OBJECT +public: + explicit MyContainment(QObject *parent = nullptr); + ~MyContainment(); + + virtual bool load() override; +protected: + virtual QObject *createProxyMeta() override; +}; +``` + +**说明:** +- 继承自 `DContainment` +- 实现 `load()` 和 `createProxyMeta()` +- 可以管理子小部件 + +### 步骤 3:创建实现文件 + +创建 `mycontainment.cpp`: + +```cpp +// SPDX-FileCopyrightText: 2024 MyCompany +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "mycontainment.h" + +#include "pluginfactory.h" +#include "appletproxy.h" +#include + +MyContainment::MyContainment(QObject *parent) + : DContainment(parent) +{ + qDebug() << "MyContainment 已创建"; +} + +MyContainment::~MyContainment() +{ + qDebug() << "MyContainment 已销毁"; +} + +bool MyContainment::load() +{ + DCORE_USE_NAMESPACE; + + // 获取所有可用的小部件 + auto applets = DPluginLoader::instance()->plugins(); + + // 过滤特定小部件(示例) + QList groups; + for (const auto &plugin : applets) { + if (plugin.pluginId().contains("example")) { + groups << DAppletData::fromPluginMetaData(plugin); + } + } + + // 设置小部件数据 + auto data = appletData(); + data.setGroupList(groups); + setAppletData(data); + + // 调用父类 load + return DContainment::load(); +} + +QObject *MyContainment::createProxyMeta() +{ + // 您可以在这里创建用于 QML 通信的代理对象 + return new QObject(this); +} + +// 注册插件 +D_APPLET_CLASS(MyContainment) + +#include "mycontainment.moc" +``` + +**说明:** +- `load()`:设置子小部件 +- `createProxyMeta()`:为 QML 创建代理 +- 使用 `DPluginLoader` 发现小部件 +- 过滤并分组小部件 + +### 步骤 4:创建 QML 文件 + +创建 `package/main.qml`: + +```qml +// SPDX-FileCopyrightText: 2024 MyCompany +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick 2.15 +import QtQuick.Controls 2.4 +import QtQuick.Layouts 1.11 +import org.deepin.ds 1.0 + +ContainmentItem { + id: root + objectName: "my containment" + implicitWidth: childrenRect.width + implicitHeight: childrenRect.height + + RowLayout { + spacing: 10 + + Repeater { + model: Containment.appletItems + delegate: Control { + contentItem: model.data + + Rectangle { + width: 80 + height: 80 + color: "#3498db" + radius: 8 + + Text { + anchors.centerIn: parent + text: model.data.pluginId + color: "white" + font.pixelSize: 10 + } + } + } + } + } +} +``` + +**说明:** +- `ContainmentItem`:容器 UI 的基础 +- `Repeater`:迭代子小部件 +- `Control`:委托给小部件内容 + +### 步骤 5:创建 metadata.json + +创建 `package/metadata.json`: + +```json +{ + "Plugin": { + "Version": "1.0", + "Id": "org.mycompany.mycontainment", + "Url": "main.qml", + "ContainmentType": "Containment" + } +} +``` + +**说明:** +- `ContainmentType`:"Containment" 表示这是一个容器 +- `Url`:指向 QML 文件 + +### 步骤 6:创建 CMakeLists.txt + +```cmake +# SPDX-FileCopyrightText: 2024 MyCompany +# SPDX-License-Identifier: GPL-3.0-or-later + +add_library(ds-mycontainment SHARED + mycontainment.h + mycontainment.cpp +) + +target_link_libraries(ds-mycontainment PRIVATE + dde-shell-frame +) + +ds_install_package(PACKAGE org.mycompany.mycontainment TARGET ds-mycontainment) +``` + +### 步骤 7:构建和安装 + +```bash +cmake -Bbuild +cmake --build build +cmake --install build +``` + +--- + +## 高级主题 + +### 添加 D-Bus 支持 + +通过 D-Bus 暴露功能: + +```cpp +#include + +bool MyPlugin::init() +{ + QDBusConnection::sessionBus().registerObject( + "/org/mycompany/MyPlugin", + this + ); + + return DApplet::init(); +} +``` + +### 处理配置 + +使用 DConfig 进行插件设置: + +```cpp +#include + +bool MyPlugin::init() +{ + auto config = DConfig::create("org.mycompany.myplugin"); + QString setting = config->value("mySetting").toString(); + + return DApplet::init(); +} +``` + +### 信号/槽通信 + +C++ 和 QML 之间: + +```cpp +// C++ 端 +class MyPlugin : public DApplet +{ + Q_OBJECT + Q_PROPERTY(QString status READ status NOTIFY statusChanged) + +signals: + void statusChanged(); + +public: + QString status() const { return m_status; } + +private: + QString m_status; +}; +``` + +```qml +// QML 端 +Connections { + target: root + function onStatusChanged() { + console.log("状态改变:", root.status) + } +} +``` + +--- + +## 调试 + +### 启用插件日志 + +```cpp +#include + +bool MyPlugin::init() +{ + qDebug() << "MyPlugin::init() 被调用"; + qInfo() << "插件 ID:" << pluginId(); + return DApplet::init(); +} +``` + +### 常见问题 + +1. **插件未加载** + - 检查 `metadata.json` 语法 + - 验证插件 ID 是唯一的 + - 检查日志中的错误消息 + +2. **QML 未显示** + - 验证 metadata.json 中的 `Url` 指向存在的 QML 文件 + - 检查 QML 语法错误 + - 查看 QML 控制台输出 + +3. **启动时崩溃** + - 检查空指针访问 + - 验证所有资源都可用 + - 使用调试器追踪 + +--- + +## 下一步 + +--- + +## 总结 + +您已学会如何创建: +1. ✅ 基于 QML 的小部件(最简单) +2. ✅ 基于小部件的小部件(中级) +3. ✅ 容器插件(高级) + +每个教程都提供了完整的、可工作的代码,您可以将其作为自己插件的起点。 + +祝插件开发愉快!