[Technical exchange] deepin-skills 实战教程
Tofloor
poster avatar
sshnuke
deepin
7 hours ago
Author

deepin-skills 实战教程:让 AI 帮你写 deepin 原生应用

本文面向想在 deepin/UOS 上开发原生应用和桌面插件的开发者,手把手演示如何安装 deepin-skills 技能库,并通过自然语言驱动 AI 完成四个典型开发任务。所有示例均基于 deepin 25 / UOS V25 环境。

它解决什么问题

deepin 生态有一套自研的 DTK 框架和 DDE 桌面插件体系,文档分散在多个仓库和 Wiki 中,新手往往要在十几个仓库之间来回跳转才能拼出一个可运行的插件。deepin-skills 把这些经过长期验证的框架知识、插件接口和工程实践,组织成了 AI 可以按需读取的 Skill 文档。安装之后,你只需要用自然语言描述需求,AI 就会自动加载对应的技术资料,帮你生成符合 deepin 规范的代码。

项目地址:https://github.com/linuxdeepin/deepin-skills

你需要准备什么

  • deepin 25 或 UOS V25 系统
  • 一个支持读取 SKILL.md 的 AI 编程 Agent(如 UOS AI 内置的"小U同学",或 Claude Code、Cursor 等支持 Anthropic Skill 规范的工具)
  • 基本的终端操作能力,不需要预先掌握 DTK 或 QML 知识

安装技能库

打开终端,执行一行命令即可:

bash <(curl -fsSL https://raw.githubusercontent.com/linuxdeepin/deepin-skills/master/scripts/install.sh)

脚本会把四个技能安装到 ~/.agents/skills/ 目录下。如果之前装过旧版本,加 --force 参数强制覆盖:

bash <(curl -fsSL https://raw.githubusercontent.com/linuxdeepin/deepin-skills/master/scripts/install.sh) --force

安装完成后,检查一下目录结构:

ls ~/.agents/skills/

你会看到四个目录:

dde-control-center-development
dde-shell-development
dde-tray-development
dtk-development

每个目录就是一个独立的技能,包含 SKILL.md(触发描述与路由)、references/(按需加载的技术资料)和 evals/(验证测试用例)。

如果你用的是 UOS AI,还需要配置 bash MCP 让 AI 拥有执行命令的权限。打开 UOS AI → 设置 → MCP 服务,添加以下配置:

{
  "mcpServers": {
    "bash": {
      "command": "npx",
      "args": ["bash-mcp"]
    }
  }
}

四个技能分别做什么

在动手之前,先了解每个技能的适用场景,这样你才知道该向 AI 描述什么样的任务。

技能 用途 典型场景
dtk-development 开发基于 DTK 的桌面应用 做一个带设置窗口的应用,处理主题切换、图标加载、D-Bus 通信
dde-shell-development 扩展 DDE Shell 面板能力 给 Dock 加一个自定义插件,在顶栏显示系统信息
dde-control-center-development 开发控制中心设置模块 在控制中心新增一个"我的设备"设置页面
dde-tray-development 开发任务栏托盘插件 做一个网络状态托盘图标,点击弹出快捷面板

下面用四个实例分别演示。

实例一:用 DTK 创建一个设置窗口

这是最基础的场景——用 DTK 框架创建一个原生桌面应用,包含一个符合 deepin 设计规范的设置窗口。

向 AI 描述任务

打开你的 AI 编程 Agent,在项目目录中输入:

帮我用 DTK 创建一个 deepin 桌面应用,包含一个设置窗口,里面有"通知"和"外观"两个页面。通知页面有一个开关控件控制是否启用通知,外观页面有一个下拉框选择主题模式(浅色/深色/跟随系统)。用 DConfig 保存配置。

AI 会做什么

AI 识别到这涉及 DTK 应用开发,自动加载 dtk-development 技能。它会依次读取以下参考文档:

  1. references/app-dev-with-dtk.md —— 了解如何创建 DTK 应用和配置 CMake
  2. references/declarative/dialogs.md —— 了解 QML 对话框控件
  3. references/config/dconfig-cpp.md —— 了解 DConfig 的 C++ 接口
  4. references/theme/theme-switch.md —— 了解主题切换机制

然后生成项目代码。以下是一个典型的项目结构和核心代码:

项目结构

my-settings-app/
├── CMakeLists.txt
├── src/
│   ├── main.cpp
│   ├── settingswindow.cpp
│   └── settingswindow.h
└── configs/
    └── my-settings-app.json

CMakeLists.txt

cmake_minimum_required(VERSION 3.16)

project(my-settings-app VERSION 1.0.0)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

find_package(Qt6 REQUIRED COMPONENTS Widgets)
find_package(Dtk6Widget REQUIRED)
find_package(Dtk6Core REQUIRED)

add_executable(my-settings-app
    src/main.cpp
    src/settingswindow.cpp
)

target_link_libraries(my-settings-app
    Qt6::Widgets
    Dtk6::Widget
    Dtk6::Core
)

# 安装 DConfig 配置文件
install(FILES configs/my-settings-app.json
    DESTINATION /share/dsg/configs/
)

DConfig 配置文件

DConfig 是 deepin 的统一配置方案,类似 GSettings。先定义配置 schema:

{
    "magic": "dsg.config",
    "version": "1.0",
    "contents": {
        "enable_notifications": {
            "value": true,
            "serial": 0,
            "name": "启用通知",
            "description": "控制是否接收应用通知"
        },
        "theme_mode": {
            "value": "auto",
            "name": "主题模式",
            "description": "auto/light/dark"
        }
    }
}

核心代码

// settingswindow.h
#pragma once

#include 
#include 
#include 
#include 
#include 
#include 
#include 

DWIDGET_USE_NAMESPACE

class SettingsWindow : public DMainWindow {
    Q_OBJECT

public:
    explicit SettingsWindow(QWidget *parent = nullptr);

private slots:
    void onNotificationToggled(bool enabled);
    void onThemeChanged(int index);

private:
    DConfig *m_config;
    DSwitchButton *m_notifySwitch;
    DComboBox *m_themeCombo;

    void setupUI();
    void loadSettings();
};
// settingswindow.cpp
#include "settingswindow.h"
#include 
#include 
#include 
#include 
#include 

SettingsWindow::SettingsWindow(QWidget *parent)
    : DMainWindow(parent)
    , m_config(new DConfig("my-settings-app", this))
{
    setupUI();
    loadSettings();
}

void SettingsWindow::setupUI() {
    auto *centralWidget = new DWidget(this);
    auto *mainLayout = new QVBoxLayout(centralWidget);

    // 通知设置组
    auto *notifyGroup = new DGroupBox("通知", centralWidget);
    auto *notifyLayout = new QVBoxLayout(notifyGroup);
    auto *notifyRow = new QHBoxLayout();
    notifyRow->addWidget(new QLabel("启用通知"));
    m_notifySwitch = new DSwitchButton();
    notifyRow->addStretch();
    notifyRow->addWidget(m_notifySwitch);
    notifyLayout->addLayout(notifyRow);

    // 外观设置组
    auto *appearanceGroup = new DGroupBox("外观", centralWidget);
    auto *appearanceLayout = new QVBoxLayout(appearanceGroup);
    auto *themeRow = new QHBoxLayout();
    themeRow->addWidget(new QLabel("主题模式"));
    m_themeCombo = new DComboBox();
    m_themeCombo->addItems({"跟随系统", "浅色", "深色"});
    themeRow->addStretch();
    themeRow->addWidget(m_themeCombo);
    appearanceLayout->addLayout(themeRow);

    mainLayout->addWidget(notifyGroup);
    mainLayout->addWidget(appearanceGroup);
    mainLayout->addStretch();

    setCentralWidget(centralWidget);
    resize(500, 400);
    setTitle("设置");

    connect(m_notifySwitch, &DSwitchButton::checkedChanged,
            this, &SettingsWindow::onNotificationToggled);
    connect(m_themeCombo, &DComboBox::currentIndexChanged,
            this, &SettingsWindow::onThemeChanged);
}

void SettingsWindow::loadSettings() {
    // 从 DConfig 读取保存的配置
    bool notifyEnabled = m_config->value("enable_notifications", true).toBool();
    m_notifySwitch->setChecked(notifyEnabled);

    QString theme = m_config->value("theme_mode", "auto").toString();
    int index = (theme == "light") ? 1 : (theme == "dark") ? 2 : 0;
    m_themeCombo->setCurrentIndex(index);
}

void SettingsWindow::onNotificationToggled(bool enabled) {
    m_config->setValue("enable_notifications", enabled);
}

void SettingsWindow::onThemeChanged(int index) {
    QString theme = (index == 1) ? "light" : (index == 2) ? "dark" : "auto";
    m_config->setValue("theme_mode", theme);
}
// main.cpp
#include 
#include "settingswindow.h"

int main(int argc, char *argv[]) {
    DApplication app(argc, argv);
    app.setApplicationName("my-settings-app");
    app.setApplicationVersion("1.0.0");

    SettingsWindow window;
    window.show();

    return app.exec();
}

构建和运行

mkdir build && cd build
cmake ..
make -j$(nproc)
./my-settings-app

运行后你会看到一个 deepin 风格的设置窗口,开关和下拉框的值会通过 DConfig 持久化保存,下次打开自动恢复。

实例二:给 Dock 栏添加一个自定义插件

这个场景演示如何用 dde-shell-development 技能扩展 DDE 桌面的 Dock 栏,添加一个显示当前时间的插件。

向 AI 描述任务

帮我开发一个 DDE Shell 的 Dock 插件,显示当前时间,点击后弹出一个面板显示日期和星期。用纯 QML 实现。

AI 会读取的参考文档

  1. references/design.md —— 理解三层插件模型(Applet → Containment → Panel)
  2. references/plugin-development.md —— 学习如何创建纯 QML Applet
  3. references/api/qml-api.md —— 查看 QML API(AppletItem、Panel Popup)

插件结构

这是插件的身份证,dde-shell 启动时通过它发现和加载插件:

{
    "id": "dock-clock",
    "displayName": "时钟",
    "description": "在 Dock 栏显示当前时间",
    "version": "1.0.0",
    "pluginType": "applet",
    "Parent": "dock",
    "content": {
        "type": "qml",
        "main": "qml/main.qml"
    }
}

关键字段说明:

  • pluginType 设为 applet,表示这是一个最小功能单元
  • Parent 设为 dock",告诉框架这个插件挂在 Dock 面板下
  • content.main 指向 QML 入口文件

main.qml

import QtQuick
import org.deepin.ds 1.0

AppletItem {
    id: clockApplet

    // 插件在 Dock 中的尺寸
    implicitWidth: 80
    implicitHeight: 40

    // 时间更新定时器
    Timer {
        interval: 1000
        running: true
        repeat: true
        onTriggered: timeLabel.text = Qt.formatDateTime(new Date(), "HH:mm")
    }

    // 显示时间的文本
    Text {
        id: timeLabel
        anchors.centerIn: parent
        text: Qt.formatDateTime(new Date(), "HH:mm")
        font.pixelSize: 16
        color: palette.windowText
    }

    // 点击弹出面板
    Panel.popup {
        id: clockPopup
        width: 220
        height: 120

        Column {
            anchors.centerIn: parent
            spacing: 8

            Text {
                anchors.horizontalCenter: parent.horizontalCenter
                text: Qt.formatDateTime(new Date(), "yyyy-MM-dd")
                font.pixelSize: 24
                color: palette.windowText
            }

            Text {
                anchors.horizontalCenter: parent.horizontalCenter
                text: {
                    var days = ["星期日", "星期一", "星期二", "星期三",
                               "星期四", "星期五", "星期六"];
                    return days[new Date().getDay()];
                }
                font.pixelSize: 16
                color: palette.windowText
            }
        }
    }

    MouseArea {
        anchors.fill: parent
        onClicked: clockPopup.visible = !clockPopup.visible
    }
}

安装和测试

# 复制到系统插件目录
sudo cp -r dock-clock /usr/share/dde-shell/dock-clock

# 重启 dde-shell 使插件生效
systemctl --user restart dde-shell

重启后 Dock 栏右侧会出现一个时钟图标,显示当前时间,点击弹出日期和星期信息。

实例三:在控制中心添加一个设置模块

dde-control-center-development 技能用于扩展 DDE 控制中心。控制中心采用插件化架构,框架只管导航和布局,具体功能全靠插件实现。

向 AI 描述任务

帮我在 DDE 控制中心新增一个"网络诊断"模块,包含一个按钮,点击后执行 ping 测试,并在下方显示结果。

项目结构

{
    "id": "network-diag",
    "displayName": "网络诊断",
    "description": "网络连通性测试工具",
    "version": "1.0.0",
    "pluginType": "control-center",
    "Parent": "network",
    "content": {
        "type": "cpp",
        "library": "libnetworkdiag.so"
    }
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.16)

project(networkdiag VERSION 1.0.0)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)

find_package(Qt6 REQUIRED COMPONENTS Widgets)
find_package(Dtk6Widget REQUIRED)

# 控制中心框架头文件
find_path(DCC_INCLUDE_DIRS dccobject.h
    PATHS /usr/include/dde-control-center
)

add_library(networkdiag SHARED
    src/networkdiagplugin.cpp
    src/networkdiagpage.cpp
)

target_include_directories(networkdiag PRIVATE
    ${DCC_INCLUDE_DIRS}
    src
)

target_link_libraries(networkdiag
    Qt6::Widgets
    Dtk6::Widget
)

install(TARGETS networkdiag
    LIBRARY DESTINATION /usr/lib/dde-control-center/modules
)
install(DIRECTORY package/
    DESTINATION /usr/share/dde-control-center/modules/network-diag
)

插件入口

控制中心插件需要继承 DccObject 并注册到模块树中:

// networkdiagplugin.h
#pragma once
#include 

class NetworkDiagPlugin : public DccObject {
    Q_OBJECT
public:
    explicit NetworkDiagPlugin(QObject *parent = nullptr);
};
// networkdiagplugin.cpp
#include "networkdiagplugin.h"
#include "networkdiagpage.h"

NetworkDiagPlugin::NetworkDiagPlugin(QObject *parent)
    : DccObject(parent) {
    // 设置模块在控制中心导航树中的显示名称和图标
    setDisplayName("网络诊断");
    setDescription("测试网络连通性");
    setIcon("network-diagnostic");
}

// 当用户点击进入此模块时,框架调用此方法创建页面
QWidget *NetworkDiagPlugin::page() {
    return new NetworkDiagPage();
}

诊断页面

// networkdiagpage.h
#pragma once
#include 
#include 
#include 

DWIDGET_USE_NAMESPACE

class NetworkDiagPage : public DWidget {
    Q_OBJECT
public:
    explicit NetworkDiagPage(QWidget *parent = nullptr);

private slots:
    void onStartPing();

private:
    DPushButton *m_startBtn;
    DPlainTextEdit *m_resultView;
};
// networkdiagpage.cpp
#include "networkdiagpage.h"
#include 
#include 

NetworkDiagPage::NetworkDiagPage(QWidget *parent)
    : DWidget(parent) {
    auto *layout = new QVBoxLayout(this);

    m_startBtn = new DPushButton("开始诊断");
    m_resultView = new DPlainTextEdit();
    m_resultView->setReadOnly(true);
    m_resultView->setPlaceholderText("点击上方按钮开始网络诊断...");

    layout->addWidget(m_startBtn);
    layout->addWidget(m_resultView);

    connect(m_startBtn, &DPushButton::clicked,
            this, &NetworkDiagPage::onStartPing);
}

void NetworkDiagPage::onStartPing() {
    m_resultView->clear();
    m_resultView->appendPlainText("正在测试网络连通性...\n");

    auto *process = new QProcess(this);
    connect(process, &QProcess::readyReadStandardOutput, [=]() {
        m_resultView->appendPlainText(
            process->readAllStandardOutput());
    });
    connect(process, QOverload::of(&QProcess::finished), [=](int code) {
        m_resultView->appendPlainText(
            QString("\n诊断完成,退出码: %1").arg(code));
        process->deleteLater();
    });

    process->start("ping", {"-c", "4", "www.baidu.com"});
}

安装

mkdir build && cd build
cmake ..
make -j$(nproc)
sudo make install

# 重启控制中心
killall dde-control-center
dde-control-center &

打开控制中心,在网络分组下会看到"网络诊断"模块,点击进入后可以通过按钮执行 ping 测试。

实例四:开发一个任务栏托盘插件

dde-tray-development 技能覆盖任务栏托盘区域的插件开发。托盘插件需要继承 PluginsItemInterfaceV2 接口。

向 AI 描述任务

帮我做一个任务栏托盘插件,显示 CPU 使用率,右键菜单提供"打开系统监视器"选项,点击图标在快捷面板中显示详细信息。

AI 会读取的参考文档

  1. references/tray-plugin-spec.md —— 托盘插件接口规范(最大的一份文档,15KB)
  2. references/quick-panel-guide.md —— 快捷面板开发
  3. references/context-menu.md —— 右键菜单实现
  4. references/message-protocol.md —— 消息协议

核心代码

// cpu-monitor-plugin.h
#pragma once
#include 
// cpu-monitor-plugin.cpp
#include "cpu-monitor-plugin.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 

CpuMonitorPlugin::CpuMonitorPlugin(QObject *parent)
    : QObject(parent) {
    m_iconLabel = new QLabel();
    m_iconLabel->setFixedSize(24, 24);
    m_iconLabel->setAlignment(Qt::AlignCenter);

    m_tipLabel = new QLabel();
    m_tipLabel->setMargin(8);

    m_proc = new QProcess(this);
    m_timer = new QTimer(this);

    connect(m_timer, &QTimer::timeout, this, &CpuMonitorPlugin::updateCpuUsage);
    m_timer->start(2000);
    updateCpuUsage();
}

QWidget *CpuMonitorPlugin::itemWidget(const QString &itemKey) {
    return m_iconLabel;
}

QWidget *CpuMonitorPlugin::itemTipsWidget(const QString &itemKey) {
    return m_tipLabel;
}

QList CpuMonitorPlugin::itemContextMenu(const QString &itemKey) {
    QList actions;
    auto *openMonitor = new QAction("打开系统监视器");
    connect(openMonitor, &QAction::triggered, []() {
        QDesktopServices::openUrl(QUrl("deepin-system-monitor://"));
    });
    actions << openMonitor;
    return actions;
}

int CpuMonitorPlugin::itemFlags(const QString &itemKey) const {
    // 支持右键菜单 + 快捷面板详情页
    return PluginFlag::Type_Common
         | PluginFlag::Attribute_CanSetting
         | PluginFlag::Attribute_CanQuickShow;
}

void CpuMonitorPlugin::updateCpuUsage() {
    // 读取 /proc/stat 计算 CPU 使用率
    m_proc->start("bash", {"-c",
        "grep 'cpu ' /proc/stat | awk '{usage=($2+$4)*100/($2+$4+$5)} END {printf \"%.0f\", usage}'"
    });
    m_proc->waitForFinished();
    int usage = m_proc->readAllStandardOutput().trimmed().toInt();

    // 根据使用率选择颜色显示
    QString color = (usage > 80) ? "#ff4444" : (usage > 50) ? "#ffaa00" : "#00aa44";
    m_iconLabel->setText(QString("%2%")
                            .arg(color).arg(usage));
    m_iconLabel->setTextFormat(Qt::RichText);

    // 更新快捷面板详情
    m_tipLabel->setText(QString("CPU 使用率: %1%\n系统状态: %2")
                            .arg(usage)
                            .arg(usage > 80 ? "高负载" : usage > 50 ? "中等" : "正常"));
}
{ "id": "cpu-monitor", "name": "CPU 监视器", "version": "1.0.0", "description": "在任务栏显示 CPU 使用率", "interface": "PluginsItemInterfaceV2" }

安装


任务栏托盘区域会出现一个 CPU 使用率数字,颜色根据负载变化,右键可打开系统监视器,鼠标悬停显示详细信息。

创建你自己的技能

deepin-skills 的贡献规范定义了标准的技能目录结构。如果你想把自己团队的内部开发规范也封装成技能,按照以下结构创建:

skills/my-custom-skill/
├── SKILL.md
├── agents/
│   └── openai.yaml
├── references/
│   └── my-reference.md
└── evals/
    ├── README.md
    └── my-test.md

SKILL.md 模板

---
name: my-custom-skill
description: "我的自定义技能描述,告诉 AI 这个技能在什么场景下触发"
---

# 我的自定义技能

## 文档路由

| 场景 | 参考文档 |
|------|----------|
| 了解架构 | references/architecture.md |
| API 参考 | references/api.md |

## 高频场景

- 场景 A → references/a.md
- 场景 B → references/b.md

核心原则是:SKILL.md 只放路由信息和触发条件,详细技术内容放 references/,验证场景放 evals/。这样 AI 只在需要时才加载具体文档,不会浪费上下文窗口。

openai.yaml 模板

skills:
  - name: my-custom-skill
    description: "技能描述"
    system_prompt: |
      你是一个 my-custom-skill 专家,当用户提到 XXX 时触发此技能。

提交贡献

fork 仓库后,把你的技能放在 skills/ 下,同步更新 README.md 中的技能列表,然后提交 Pull Request。贡献前请阅读仓库的 CONTRIBUTING.md 了解完整规范。

常见问题排查

AI 没有触发技能

检查 SKILL.md 中的 description 字段是否准确描述了触发场景。如果描述太宽泛,AI 可能无法正确匹配。尝试在对话中明确提及技能名称,例如"使用 dtk-development 技能帮我..."。

技能安装后不生效

确认技能目录路径正确:

ls ~/.agents/skills/dtk-development/SKILL.md

如果路径没问题,重启你的 AI 编程 Agent 让它重新扫描技能目录。

UOS AI 执行技能时报错

大部分技能需要 bash 执行权限。确认 MCP 配置中已添加 bash-mcp(见上文安装部分),然后在对话中点击"重新生成"。

插件安装后不显示

DDE Shell 插件需要检查 Parent 字段是否指向了正确的面板(如 docktop-bar)。控制中心插件需要检查 pluginType 是否为 control-center。重启对应服务后检查日志:

# Dock 日志
journalctl --user -u dde-shell -f

# 控制中心日志
QT_LOGGING_RULES="*.debug=true" dde-control-center 2>&1 | grep network

四个技能的评估用例分布

deepin-skills 为每个技能配套了验证测试用例(Evals),确保 AI 使用技能时能正确理解和应用技术知识。以下是各技能的用例数量和分类:

技能 用例总数 主要分类
dtk-development 77 widgets(29)、declarative(12)、utilities(6)、theme(7)、config(4)、architecture(4)、custom-controls(6)、debugging(5)、platform(3)、project-setup(1)
dde-control-center-development 21 plugin-development(5)、cpp-api(4)、qml-api(4)、debugging(5)、architecture(3)
dde-shell-development 19 plugin-development(6)、api(4)、layershell(5)、qml-api(4)
dde-tray-development 13 tray-plugin-spec(4)、quick-panel(3)、context-menu(3)、message-protocol(3)

dtk-development 的 widgets 分类独占 29 个用例,覆盖了从对话框、按钮到列表视图、树视图等各类控件的验证场景,这也反映了 DTK 控件库的丰富程度。

写在最后

deepin-skills 的价值不在于它替代了文档,而在于它把分散在十几个仓库和 Wiki 中的知识重新组织成了 AI 可以按需检索的结构。四个技能、130 个评估用例、40 多份参考文档,构成了一个完整的 deepin 原生开发知识图谱。

如果你在 deepin 上有重复性的开发需求,不妨试着把它封装成一个 Skill——这比写一份内部 Wiki 有效得多,因为 AI 会自动帮你的团队成员加载和使用它。

Reply Favorite View the author
All Replies
avatar
kookboy
deepin
7 hours ago
#1

好东西~like

Reply View the author
avatar
芭芭雅嘎
deepin
6 hours ago
#2

这得加精agree

Reply View the author
avatar
tsgg
deepin
5 hours ago
#3

👍 很好,很实用。

Reply View the author