Skip to content

Conversation

@caixr23
Copy link
Contributor

@caixr23 caixr23 commented Jan 6, 2026

This commit transitions the control center from file-based QML plugin loading to Qt6's resource system for better performance and deployment. Key changes include:

  1. Replaced manual file copying with qt_add_qml_module for proper QML module management
  2. Added plugin-system and plugin-device as proper subdirectories
  3. Updated plugin loading logic to support both resource-based and file- based plugins
  4. Modified DCI icon theme search paths to use resource paths
  5. Fixed local file paths in QML by converting them to proper file:// URLs
  6. Enhanced plugin discovery with multiple path resolution strategies

The migration improves plugin loading performance, simplifies deployment, and provides better integration with Qt6's QML module system. It also maintains backward compatibility with existing file- based plugins.

Log: Improved QML plugin loading performance and deployment

Influence:

  1. Test control center startup and plugin loading
  2. Verify all plugin modules load correctly (system, device, accounts, etc.)
  3. Check icon display in various modules
  4. Test plugin navigation and functionality
  5. Verify resource paths work in different deployment scenarios
  6. Test backward compatibility with existing plugin configurations

feat: 迁移 QML 插件到 Qt6 资源系统

本次提交将控制中心从基于文件的 QML 插件加载迁移到 Qt6 资源系统,以获得更
好的性能和部署体验。主要变更包括:

  1. 使用 qt_add_qml_module 替代手动文件复制,实现更好的 QML 模块管理
  2. 添加 plugin-system 和 plugin-device 作为正式子目录
  3. 更新插件加载逻辑以支持基于资源和基于文件的插件
  4. 修改 DCI 图标主题搜索路径以使用资源路径
  5. 通过转换为 file:// URL 修复 QML 中的本地文件路径问题
  6. 增强插件发现功能,支持多种路径解析策略

此次迁移提高了插件加载性能,简化了部署流程,并提供了与 Qt6 QML 模块系统
的更好集成。同时保持了对现有基于文件的插件的向后兼容性。

Log: 提升 QML 插件加载性能和部署体验

Influence:

  1. 测试控制中心启动和插件加载
  2. 验证所有插件模块正确加载(系统、设备、账户等)
  3. 检查各模块中的图标显示
  4. 测试插件导航和功能
  5. 验证资源路径在不同部署场景下的工作
  6. 测试与现有插件配置的向后兼容性

Summary by Sourcery

Migrate the control center and its plugins from filesystem-based QML loading to Qt 6’s resource-based QML module system while preserving compatibility with existing plugins.

New Features:

  • Support resource-based QML plugins via Qt 6 qml modules alongside existing file-based plugins.
  • Add dedicated system and device plugin subdirectories to be built and installed as first-class plugins.

Enhancements:

  • Introduce flexible QML path resolution for plugins, including qrc-based modules, optional qml subdirectories, and main entry variants.
  • Adjust plugin loading to determine QML locations via metadata and update DCI icon theme search paths to include resource paths.
  • Update QML and C++ models to normalize local file paths into file:// URLs for use as image and icon sources.
  • Load the main DccWindow QML from a registered Qt QML module instead of a raw file path.
  • Include plugin QML resources and image assets in qt_add_qml_module definitions for both core and plugin modules, eliminating manual QML directory copying in the build and install steps.

Build:

  • Replace custom QML file copying with qt_add_qml_module for control center and plugin QML packaging, including resource registration and installation targets.
  • Update CMake plugin macros and frame plugin build configuration to collect QML and resource files into Qt 6 QML modules.
  • Register plugin-system and plugin-device as build subdirectories using the shared plugin install macro.

Tests:

  • Require regression testing of control center startup, plugin discovery and loading (system, device, accounts, etc.), icon rendering, QML resource resolution, and backward compatibility with file-based plugins.

@sourcery-ai
Copy link

sourcery-ai bot commented Jan 6, 2026

Reviewer's Guide

Migrates QML plugin handling and control center QML loading from filesystem-based directories to Qt 6’s qrc-based QML module system, updating plugin discovery/loading logic, icon theme search paths, URL handling for local resources, and CMake build/install rules, while adding system/device plugins as proper subdirectories.

Sequence diagram for updated QML plugin loading (QRC and file-based)

sequenceDiagram
    participant App as ControlCenter
    participant PM as PluginManager
    participant TP as QThreadPool
    participant Task as LoadPluginTask
    participant QFile
    participant QQmlFile
    participant QQmlComponent

    App->>PM: loadModules(root, async, blacklist)
    loop For each plugin directory
        PM->>PM: create PluginData(name, path)
        PM->>PM: loadMetaData(plugin)
        alt plugin has lib<name>_qml.so
            PM->>TP: start(new LoadPluginTask(plugin, this, true))
            TP-->>Task: run()
            Task->>Task: doLoadQrc()
            Task->>QFile: exists(soPath)
            alt soPath exists
                Task->>PM: updatePluginStatus(MetaDataLoad)
                Task->>Task: load shared object via QLibrary
            else soPath missing
                Task->>PM: updatePluginStatus(MetaDataErr | MetaDataEnd)
            end
            Task->>Task: build qmlPaths list via pluginQmlPath()
            loop probe candidate QML URLs
                Task->>QQmlFile: isLocalFile(path)
                alt local file
                    Task->>QFile: QFileInfo(urlToLocalFileOrQrc(path)).exists()
                else qrc or nonlocal
                    Task->>QFile: QFileInfo(path).exists()
                end
                alt exists
                    Task->>Task: m_data->qmlPathType = matchedType
                    Task->>Task: break
                end
            end
            Task->>PM: updatePluginStatus(MetaDataEnd)
        else no lib<name>_qml.so
            PM->>PM: updatePluginStatus(MetaDataEnd)
        end
    end

    loop For each PluginData in m_plugins
        PM->>PM: loadPlugin(plugin)
        PM->>PM: loadModule(plugin)
        PM->>PM: qmlPath = pluginQmlPath(plugin, plugin->qmlPathType, PT_MODULE)
        PM->>PM: paths = DIconTheme::dciThemeSearchPaths()
        PM->>PM: paths.append(pluginQmlPath(plugin, plugin->qmlPathType, PT_DIR))
        PM->>PM: DIconTheme::setDciThemeSearchPaths(paths)
        alt qmlPathType is not QMLPATH_IsQrc and !QFile::exists(qmlPath)
            PM->>PM: updatePluginStatus(ModuleErr | ModuleEnd)
        else
            PM->>PM: updatePluginStatus(ModuleLoad)
            PM->>QQmlComponent: new QQmlComponent(engine)
            PM->>QQmlComponent: setProperty(PluginData, plugin)
            PM->>QQmlComponent: loadUrl(qmlPath, Asynchronous)
        end

        PM->>PM: loadMain(plugin)
        PM->>PM: updatePluginStatus(MainObjLoad)
        PM->>PM: mainPath = pluginQmlPath(plugin, plugin->qmlPathType, PT_MAIN)
        alt mainPath not empty
            PM->>QQmlComponent: new QQmlComponent(engine)
            PM->>QQmlComponent: setProperty(PluginData, plugin)
            PM->>QQmlComponent: loadUrl(mainPath, Asynchronous)
        end
    end
Loading

Class diagram for updated QML plugin loading structures

classDiagram
    class PluginManager {
        +PluginManager(DccManager *parent)
        +void loadMetaData(PluginData *plugin)
        +void loadModule(PluginData *plugin)
        +void loadMain(PluginData *plugin)
        +void loadModules(DccObject *root, bool async, const QStringList &blacklist)
        +void loadPlugin(PluginData *plugin)
        +bool isDeleting() const
        +QThreadPool* threadPool()
        +Q_SIGNAL void updatePluginStatus(PluginData *plugin, int status, const QString &msg)
    }

    class LoadPluginTask {
        -PluginManager *m_pManager
        -PluginData *m_data
        -bool m_isQrc
        +LoadPluginTask(PluginData *data, PluginManager *pManager, bool isQrc=false)
        +void run() override
        -void doLoadSo()
        -void doLoadQrc()
    }

    class PluginData {
        +QString name
        +QString path
        +uint qmlPathType
        +DccObject *module
        +DccObject *mainObj
        +DccObject *soObj
        +QThread *thread
        +PluginData(const QString &_name, const QString &_path)
        +~PluginData()
    }

    class PluginQmlPathType {
        <<enumeration>>
        QMLPATH_IsQrc = 0x01
        QMLPATH_IsQmlDir = 0x02
        QMLPATH_IsCapitalize = 0x04
        QML_Local_Lower = 0
        QML_Local_Capitalize
        QML_QRC_Capitalize
        QML_QRC_Lower
        QML_QRC_QML_Capitalize
        QML_QRC_QML_Lower
    }

    class PathType {
        <<enumeration>>
        PT_MODULE
        PT_MAIN
        PT_DIR
        PT_QTC_DIR
    }

    class PluginStatus {
        <<enumeration>>
        PluginBegin = 0x10000000
        PluginEnd = 0x20000000
        MetaDataLoad = 0x02000000
        MetaDataEnd = 0x04000000
        MetaDataErr = 0x08000000
        ModuleLoad
        ModuleErr
        MainObjLoad
        DataBegin
        DataEnd
        MainObjEnd
        PluginEndMask
    }

    PluginManager "1" --> "*" PluginData : manages
    PluginManager "1" --> "*" LoadPluginTask : creates
    LoadPluginTask "1" --> "1" PluginData : loads
    LoadPluginTask "1" --> "1" PluginManager : reports_status

    PluginData --> PluginQmlPathType : uses_qmlPathType
    PluginManager --> PluginQmlPathType : uses
    PluginManager --> PathType : uses
    LoadPluginTask --> PluginStatus : emits

    class GlobalFunctions {
        +QString pluginQmlPath(PluginData *plugin, uint pathType, PathType type)
    }

    GlobalFunctions --> PluginData
    GlobalFunctions --> PluginQmlPathType
    GlobalFunctions --> PathType
Loading

File-Level Changes

Change Details Files
Introduce flexible QML plugin path resolution and separate code paths for qrc-based vs filesystem-based plugins in PluginManager.
  • Add PluginQmlPathType and PathType enums plus pluginQmlPath() helper to construct module, main, and directory URLs for both qrc and local plugins with different naming/layout variants.
  • Extend PluginData with qmlPathType and initialize it to local lower-case paths by default.
  • Refactor LoadPluginTask to distinguish doLoadSo() for existing file-based loading and doLoadQrc() for resource-based plugins, including loading companion lib*_qml.so and probing multiple candidate QML paths with QQmlFile/QFileInfo to determine qmlPathType.
  • Adjust PluginManager::loadMetaData to detect lib*_qml.so and schedule a qrc-loading task, and update loadModule/loadMain to use pluginQmlPath(), set DIconTheme search paths per-plugin, and simplify main QML lookup.
  • Remove global addition of plugin filesystem paths to DIconTheme search paths in loadModules and rely on per-plugin setup instead.
src/dde-control-center/pluginmanager.cpp
Switch control center and plugin builds to Qt 6’s qt_add_qml_module-based resource system and install QML as resources instead of copying directories.
  • Redefine dcc_build_plugin macro to take QML_FILES and RESOURCE_FILES, auto-discover QML/JS and resource files if not provided, compute relative paths, and call qt_add_qml_module with a flat resource prefix and per-plugin output directory, then install the QML module library into the plugin install dir.
  • Update frame plugin CMakeLists to collect QML and resource files, pass them to qt_add_qml_module with RESOURCE_PREFIX "/" and RESOURCES, and keep existing PLUGIN_TARGET/SOURCES wiring.
  • Remove the top-level control center qml copy target and install step, leaving translation handling but no longer installing raw qml/ directories.
  • Add src/plugin-system and src/plugin-device as subdirectories and minimal CMakeLists that invoke dcc_install_plugin for system and device plugins.
misc/DdeControlCenterPluginMacros.cmake
src/dde-control-center/frame/plugin/CMakeLists.txt
src/dde-control-center/CMakeLists.txt
CMakeLists.txt
src/plugin-device/CMakeLists.txt
src/plugin-system/CMakeLists.txt
Update QML engine and icon theme initialization to use resource-based modules and paths.
  • Change main.cpp to load the main window via engine->loadFromModule("org.deepin.dcc", "DccWindow") instead of loading DccWindow.qml from a filesystem path.
  • Adjust DccManager::init to append the qrc-based ":/org/deepin/dcc" path to DIconTheme’s dciThemeSearchPaths instead of DefaultModuleDirectory.
src/dde-control-center/main.cpp
src/dde-control-center/dccmanager.cpp
Normalize various model-provided icon/image paths into proper file:// URLs for QML consumers.
  • In ThemeVieweModel::data, wrap absolute pic paths from getPicList() with QUrl::fromLocalFile when they start with "/".
  • In AppInfoListModel::data, convert absolute icon paths to file URLs before returning IconRole values.
  • In SyncInfoListModel::data, convert absolute displayIcon paths to file URLs.
  • In AppsModel::data (privacy plugin), convert absolute icon paths to file URLs for IconNameRole.
  • In DeepinIDUserInfo.qml, prefix dccData.model.avatar with "file://" when used as an Image source.
src/plugin-personalization/operation/personalizationinterface.cpp
src/plugin-deepinid/operation/appinfolistmodel.cpp
src/plugin-deepinid/operation/syncinfolistmodel.cpp
src/plugin-privacy/operation/privacysecuritymodel.cpp
src/plugin-deepinid/qml/DeepinIDUserInfo.qml
Minor QML utility and style adjustments for the new module layout.
  • Move HomePage.qml into src/dde-control-center/frame/plugin and fix a font reference from DTK.fontManager.t10 to D.DTK.fontManager.t10.
  • Reorder the .pragma library directive in DccUtils.js (keeping it but placing it at the top).
src/dde-control-center/frame/plugin/DccUtils.js
src/dde-control-center/frame/plugin/HomePage.qml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • In DeepinIDUserInfo.qml the avatar source is now always prefixed with file://; consider following the pattern used elsewhere (checking for an absolute path and using QUrl::fromLocalFile or equivalent) to avoid double-prefixing when the model already provides a URL.
  • In PluginManager::loadModule, DIconTheme::dciThemeSearchPaths is appended to on every plugin load but never restored, which can lead to unbounded growth and duplicates; consider computing per-plugin paths without mutating global state repeatedly or restoring the original list after use.
  • In LoadPluginTask::doLoadQrc, a missing lib*_qml.so emits MetaDataErr | MetaDataEnd but the function still proceeds to QML path probing and finally emits MetaDataEnd again; it may be clearer either to return early on error or to adjust the status flags to avoid conflicting signals for the same plugin.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `DeepinIDUserInfo.qml` the avatar source is now always prefixed with `file://`; consider following the pattern used elsewhere (checking for an absolute path and using `QUrl::fromLocalFile` or equivalent) to avoid double-prefixing when the model already provides a URL.
- In `PluginManager::loadModule`, `DIconTheme::dciThemeSearchPaths` is appended to on every plugin load but never restored, which can lead to unbounded growth and duplicates; consider computing per-plugin paths without mutating global state repeatedly or restoring the original list after use.
- In `LoadPluginTask::doLoadQrc`, a missing `lib*_qml.so` emits `MetaDataErr | MetaDataEnd` but the function still proceeds to QML path probing and finally emits `MetaDataEnd` again; it may be clearer either to return early on error or to adjust the status flags to avoid conflicting signals for the same plugin.

## Individual Comments

### Comment 1
<location> `src/dde-control-center/pluginmanager.cpp:117-126` </location>
<code_context>
+        }
+        path += QString(":/org/deepin/dcc/") + plugin->name;
+
+    } else {
+        path = plugin->path;
+    }
</code_context>

<issue_to_address>
**issue (bug_risk):** Missing early return after SO-missing error causes double MetaDataEnd emission and inconsistent state.

When `soPath` does not exist in `doLoadQrc()`, you emit `MetaDataErr | MetaDataEnd` but still run the QML path detection loop and emit `MetaDataEnd` again. This double emission can make consumers see a normal completion after an error, complicating state handling. Please either return immediately after emitting the error or avoid emitting the final `MetaDataEnd` in this branch to keep the status sequence consistent.
</issue_to_address>

### Comment 2
<location> `src/dde-control-center/pluginmanager.cpp:280` </location>
<code_context>
+    };
+
+    for (const auto &path : qmlPaths) {
+        if ((QQmlFile::isLocalFile(path.first) && QFileInfo(QQmlFile::urlToLocalFileOrQrc(path.first)).exists()) || QFileInfo(path.first).exists()) {
+            m_data->qmlPathType = path.second;
+            break;
</code_context>

<issue_to_address>
**issue (bug_risk):** QML file existence check does not work for qrc URLs and likely never marks qmlPathType as QML-based.

In `doLoadQrc()`, this condition never succeeds for `qrc:/...` URLs: `QQmlFile::isLocalFile("qrc:/...")` is false, and `QFileInfo("qrc:/...").exists()` is also false because it expects a local path. That leaves `qmlPathType` at the default local type even when the QML actually lives in resources, sending `pluginQmlPath` and `loadModule/loadMain` down the wrong paths. Use `QQmlFile::urlToLocalFileOrQrc(path.first)` (or `QQmlFile::exists`) for all URLs and run the existence check on the resolved path so qrc resources are correctly detected.
</issue_to_address>

### Comment 3
<location> `src/dde-control-center/pluginmanager.cpp:108` </location>
<code_context>
+    PT_QTC_DIR,    // qrc:/org/deepin/dcc 形式
+};
+
+static QString pluginQmlPath(PluginData *plugin, uint pathType, PathType type)
+{
+    QString path;
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring the new QML path logic by separating path-building from existence checks and centralizing the search strategy into small helpers instead of inlined bitmask logic.

You can keep all current behavior but significantly reduce complexity and coupling by (1) splitting path construction from existence checks and (2) avoiding raw bitmask use at call sites.

### 1. Split `pluginQmlPath` responsibilities

Right now `pluginQmlPath` both builds paths and does existence checking for `PT_MAIN`. That makes it hard to reason about and forces extra flags. You can keep the same external behavior by separating **pattern construction** from **existence resolution**:

```cpp
// Keep using PluginQmlPathType + PathType, but make this builder "dumb".
static QString buildQmlPath(const PluginData *plugin, uint pathType, PathType type)
{
    QString path;
    if (pathType & PluginQmlPathType::QMLPATH_IsQrc) {
        if (type != PT_DIR && type != PT_MAIN) {
            path = QStringLiteral("qrc");
        }
        path += QStringLiteral(":/org/deepin/dcc/") + plugin->name;
    } else {
        path = plugin->path;
    }

    if (pathType & PluginQmlPathType::QMLPATH_IsQmlDir) {
        path += QStringLiteral("/qml");
    }
    if (type & PT_DIR) {
        return path;
    }

    const QString baseName = (pathType & PluginQmlPathType::QMLPATH_IsCapitalize)
        ? plugin->name.left(1).toUpper() + plugin->name.mid(1)
        : plugin->name;

    if (type == PT_MAIN) {
        // just return first candidate, don't check here
        return path + "/" + baseName + "Main.qml";
    }
    return path + "/" + baseName + ".qml";
}

// small helper to try the main.qml variants and do existence checks
static QString resolveMainQml(const PluginData *plugin, uint pathType)
{
    const QString base = buildQmlPath(plugin, pathType, PT_DIR);
    QStringList candidates{
        base + "/" + plugin->name.left(1).toUpper() + plugin->name.mid(1) + "Main.qml",
        base + "/main.qml",
    };

    for (const auto &qmlPath : candidates) {
        const QString url = (pathType & PluginQmlPathType::QMLPATH_IsQrc)
            ? QStringLiteral("qrc") + qmlPath
            : qmlPath;
        if ((QQmlFile::isLocalFile(url) &&
             QFileInfo(QQmlFile::urlToLocalFileOrQrc(url)).exists()) ||
            QFileInfo(url).exists()) {
            return url;
        }
    }
    return {};
}
```

Then `loadMain` becomes simpler and no longer depends on `PT_MAIN` doing extra work:

```cpp
void PluginManager::loadMain(PluginData *plugin)
{
    if (isDeleting()) {
        return;
    }
    Q_EMIT updatePluginStatus(plugin, MainObjLoad, "load Main");

    const QString qmlPath = resolveMainQml(plugin, plugin->qmlPathType);
    if (qmlPath.isEmpty()) {
        Q_EMIT updatePluginStatus(plugin, MainObjErr | MainObjEnd, "Main.qml not exists");
        return;
    }

    auto *component = new QQmlComponent(m_manager->engine(), m_manager->engine());
    component->setProperty("PluginData", QVariant::fromValue(plugin));
    connect(component, &QQmlComponent::statusChanged, this, &PluginManager::mainLoading);
    component->loadUrl(qmlPath, QQmlComponent::Asynchronous);
}
```

This keeps all existing search behavior but makes it clear where patterns are built vs where files are verified.

### 2. Reduce repetition in `doLoadQrc`

You can keep `PluginQmlPathType` as-is but express the search strategy in a compact, data-driven loop so it’s easier to see and maintain:

```cpp
void LoadPluginTask::doLoadQrc()
{
    // ... existing .so loading ...

    static const uint candidates[] = {
        PluginQmlPathType::QML_QRC_QML_Lower,
        PluginQmlPathType::QML_QRC_QML_Capitalize,
        PluginQmlPathType::QML_QRC_Lower,
        PluginQmlPathType::QML_QRC_Capitalize,
        PluginQmlPathType::QML_Local_Lower,
        PluginQmlPathType::QML_Local_Capitalize,
    };

    for (uint type : candidates) {
        const QString modulePath = buildQmlPath(m_data, type, PT_MODULE);
        const QString mainPath = resolveMainQml(m_data, type);

        const QString chosen = !mainPath.isEmpty() ? mainPath : modulePath;
        if (chosen.isEmpty()) {
            continue;
        }

        if ((QQmlFile::isLocalFile(chosen) &&
             QFileInfo(QQmlFile::urlToLocalFileOrQrc(chosen)).exists()) ||
            QFileInfo(chosen).exists()) {
            m_data->qmlPathType = type;
            break;
        }
    }

    Q_EMIT m_pManager->updatePluginStatus(
        m_data, MetaDataEnd,
        ": load qml qrc finished. elasped time :" + QString::number(timer.elapsed()));
}
```

Benefits:

- No 12-line hardcoded list; the pattern `{module,main} × types` is explicit.
- All path construction logic lives in `buildQmlPath`/`resolveMainQml`, so if you change layout rules you only touch those helpers.
- `qmlPathType` remains the “descriptor” of the chosen layout, but the resolution logic is localized and easier to follow.

These two small refactors keep your new QRC support and icon-path integration fully intact, while cutting down on branching, redundant calls, and bitmask-related cognitive load.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the DDE Control Center from file-based QML plugin loading to Qt6's resource system for improved performance and deployment. The changes introduce qt_add_qml_module for proper QML module management, add support for resource-based plugin paths, and convert local file paths to file:// URLs for QML compatibility.

Key Changes

  • Replaced manual QML file copying with qt_add_qml_module in the build system, enabling proper Qt6 QML module integration
  • Enhanced plugin loading logic to support both resource-based (qrc://) and file-based plugins with multiple path resolution strategies
  • Added file:// URL prefixes for local file paths in C++ models to ensure proper QML image source handling

Reviewed changes

Copilot reviewed 16 out of 27 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/plugin-system/qml/system.qml New system plugin entry QML file with basic metadata
src/plugin-system/qml/metadata.json Plugin metadata descriptor for system plugin
src/plugin-system/qml/commoninfo.dci DCI icon resource for system plugin
src/plugin-system/CMakeLists.txt Build configuration for system plugin using dcc_install_plugin macro
src/plugin-device/qml/device.qml New device plugin entry QML file
src/plugin-device/qml/metadata.json Plugin metadata descriptor for device plugin
src/plugin-device/qml/hardware.dci DCI icon resource for device plugin
src/plugin-device/CMakeLists.txt Build configuration for device plugin
src/plugin-privacy/operation/privacysecuritymodel.cpp Converts local file icon paths to file:// URLs for QML
src/plugin-personalization/operation/personalizationinterface.cpp Converts theme picture paths to file:// URLs
src/plugin-deepinid/qml/DeepinIDUserInfo.qml Adds file:// prefix to avatar image source
src/plugin-deepinid/operation/syncinfolistmodel.cpp Converts displayIcon paths to file:// URLs
src/plugin-deepinid/operation/appinfolistmodel.cpp Converts app icon paths to file:// URLs
src/dde-control-center/pluginmanager.cpp Major refactor: adds resource path resolution, QRC plugin loading, and DCI theme search path updates
src/dde-control-center/main.cpp Changes main window loading from file path to QML module import
src/dde-control-center/frame/plugin/sidebar.dci New sidebar icon resource file
src/dde-control-center/frame/plugin/reddot.dci New notification badge icon resource
src/dde-control-center/frame/plugin/SecondPage.qml New second-level navigation page with sidebar and content areas
src/dde-control-center/frame/plugin/HomePage.qml Updated font reference to use D.DTK namespace
src/dde-control-center/frame/plugin/DccWindow.qml New main window QML component with navigation and search
src/dde-control-center/frame/plugin/DccUtils.js Moved .pragma library directive to proper location at file start
src/dde-control-center/frame/plugin/Crumb.qml New breadcrumb navigation component
src/dde-control-center/frame/plugin/CMakeLists.txt Updated to include resource files in qt_add_qml_module
src/dde-control-center/dccmanager.cpp Updates DCI icon theme search paths to use resource prefix
src/dde-control-center/CMakeLists.txt Removes manual QML file copying in favor of qt_add_qml_module
misc/DdeControlCenterPluginMacros.cmake Refactored dcc_build_plugin to use qt_add_qml_module with resource support
CMakeLists.txt Adds plugin-system and plugin-device subdirectories to build

PT_MODULE, // qrc:/org/deepin/dcc/name.qml 形式
PT_MAIN, // qrc:/org/deepin/dcc/nameMain.qml 形式
PT_DIR = 0x80, // :/org/deepin/dcc 形式
PT_QTC_DIR, // qrc:/org/deepin/dcc 形式
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PathType enum defines PT_QTC_DIR but it is never used in the code. This could indicate either dead code or a missing implementation. Consider removing it if it's not needed, or implementing the corresponding logic if it was intended to be used.

Suggested change
PT_QTC_DIR, // qrc:/org/deepin/dcc 形式

Copilot uses AI. Check for mistakes.
Comment on lines 260 to 285
} else {
Q_EMIT m_pManager->updatePluginStatus(m_data, MetaDataErr | MetaDataEnd, "File does not exist:" + soPath);
}
// 尝试多种路径查找 QML 文件
QList<QPair<QString, uint>> qmlPaths{
{ pluginQmlPath(m_data, PluginQmlPathType::QML_QRC_QML_Lower, PathType::PT_MODULE), PluginQmlPathType::QML_QRC_QML_Lower }, //
{ pluginQmlPath(m_data, PluginQmlPathType::QML_QRC_QML_Capitalize, PathType::PT_MODULE), PluginQmlPathType::QML_QRC_QML_Capitalize }, //
{ pluginQmlPath(m_data, PluginQmlPathType::QML_QRC_Lower, PathType::PT_MODULE), PluginQmlPathType::QML_QRC_Lower }, //
{ pluginQmlPath(m_data, PluginQmlPathType::QML_QRC_Capitalize, PathType::PT_MODULE), PluginQmlPathType::QML_QRC_Capitalize }, //
{ pluginQmlPath(m_data, PluginQmlPathType::QML_Local_Lower, PathType::PT_MODULE), PluginQmlPathType::QML_Local_Lower }, //
{ pluginQmlPath(m_data, PluginQmlPathType::QML_Local_Capitalize, PathType::PT_MODULE), PluginQmlPathType::QML_Local_Capitalize }, //
{ pluginQmlPath(m_data, PluginQmlPathType::QML_QRC_QML_Lower, PathType::PT_MAIN), PluginQmlPathType::QML_QRC_QML_Lower }, //
{ pluginQmlPath(m_data, PluginQmlPathType::QML_QRC_QML_Capitalize, PathType::PT_MAIN), PluginQmlPathType::QML_QRC_QML_Capitalize }, //
{ pluginQmlPath(m_data, PluginQmlPathType::QML_QRC_Lower, PathType::PT_MAIN), PluginQmlPathType::QML_QRC_Lower }, //
{ pluginQmlPath(m_data, PluginQmlPathType::QML_QRC_Capitalize, PathType::PT_MAIN), PluginQmlPathType::QML_QRC_Capitalize }, //
{ pluginQmlPath(m_data, PluginQmlPathType::QML_Local_Lower, PathType::PT_MAIN), PluginQmlPathType::QML_Local_Lower }, //
{ pluginQmlPath(m_data, PluginQmlPathType::QML_Local_Capitalize, PathType::PT_MAIN), PluginQmlPathType::QML_Local_Capitalize }, //
};

for (const auto &path : qmlPaths) {
if ((QQmlFile::isLocalFile(path.first) && QFileInfo(QQmlFile::urlToLocalFileOrQrc(path.first)).exists()) || QFileInfo(path.first).exists()) {
m_data->qmlPathType = path.second;
break;
}
}
Q_EMIT m_pManager->updatePluginStatus(m_data, MetaDataEnd, ": load qml qrc finished. elasped time :" + QString::number(timer.elapsed()));
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the doLoadQrc function, if the .so file does not exist, an error is emitted but the function continues to search for QML paths and eventually emits MetaDataEnd with success status. This could be confusing as it reports both an error and success. Consider returning early after emitting the error, or clarifying the intended behavior when the .so file is optional.

Copilot uses AI. Check for mistakes.
@@ -176,6 +239,52 @@ void LoadPluginTask::doRun()
Q_EMIT m_pManager->updatePluginStatus(m_data, DataEnd, ": load plugin finished. elasped time :" + QString::number(timer.elapsed()));
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The word "elasped" is misspelled. It should be "elapsed".

Suggested change
Q_EMIT m_pManager->updatePluginStatus(m_data, DataEnd, ": load plugin finished. elasped time :" + QString::number(timer.elapsed()));
Q_EMIT m_pManager->updatePluginStatus(m_data, DataEnd, ": load plugin finished. elapsed time :" + QString::number(timer.elapsed()));

Copilot uses AI. Check for mistakes.
break;
}
}
Q_EMIT m_pManager->updatePluginStatus(m_data, MetaDataEnd, ": load qml qrc finished. elasped time :" + QString::number(timer.elapsed()));
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The word "elasped" is misspelled. It should be "elapsed".

Suggested change
Q_EMIT m_pManager->updatePluginStatus(m_data, MetaDataEnd, ": load qml qrc finished. elasped time :" + QString::number(timer.elapsed()));
Q_EMIT m_pManager->updatePluginStatus(m_data, MetaDataEnd, ": load qml qrc finished. elapsed time :" + QString::number(timer.elapsed()));

Copilot uses AI. Check for mistakes.
if (pathType & PluginQmlPathType::QMLPATH_IsQmlDir) {
path += "/qml";
}
if (type & PT_DIR) {
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition if (type != PT_DIR && type != PT_MAIN) on line 112 checks exact equality for PT_MAIN but uses bitwise check if (type & PT_DIR) on line 123. This inconsistency could lead to bugs if PT_DIR is combined with other flags. Consider using consistent comparison logic: either always use exact equality or always use bitwise checks for both conditions.

Suggested change
if (type & PT_DIR) {
if (type == PT_DIR || type == PT_QTC_DIR) {

Copilot uses AI. Check for mistakes.
@caixr23 caixr23 force-pushed the masterbak branch 3 times, most recently from 00db5e4 to 141e0ec Compare January 6, 2026 08:59
@deepin-bot
Copy link

deepin-bot bot commented Jan 6, 2026

TAG Bot

New tag: 6.1.66
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #2927

}
}
if (!plugin->qmlModule.isEmpty() || !plugin->qmlMain.isEmpty()) {
plugin->qmlDir = path.startsWith(":") ? "qrc" + path : path;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这种可以使用qtquick提供的接口,qmlfile去转这种类型,
另外,看我们能不能减少这种规则的fallback,
嵌套有点儿深,

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 27 changed files in this pull request and generated 1 comment.

Comment on lines +1 to 3
.pragma library
// SPDX-FileCopyrightText: 2024 - 2027 UnionTech Software Technology Co., Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pragma directive should come after the SPDX license headers, not before them. While functionally it may work in either position, placing license headers first is the standard convention for consistency and legal clarity. The pragma library directive should be moved to line 4 after the license header.

Suggested change
.pragma library
// SPDX-FileCopyrightText: 2024 - 2027 UnionTech Software Technology Co., Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 - 2027 UnionTech Software Technology Co., Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later
.pragma library

Copilot uses AI. Check for mistakes.
@deepin-bot
Copy link

deepin-bot bot commented Jan 7, 2026

TAG Bot

New tag: 6.1.67
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #2928

@caixr23 caixr23 force-pushed the masterbak branch 5 times, most recently from 0775c4d to 1a3f5a3 Compare January 14, 2026 03:11
@@ -10,7 +10,7 @@ namespace dccV25 {
class DccQuickRepeater : public QQuickRepeater
{
Q_OBJECT
QML_ELEMENT
QML_NAMED_ELEMENT(Repeater)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个跟qt quick里的相同,是特意这样弄的么?这样之后会不会容易引起误会,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

为了替换qt里的Repeater

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个和DCCRepeater不是一个东西把

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不是同一个

set(oneValueArgs NAME TARGET QML_ROOT_DIR)
set(qml_root_dir ${CMAKE_CURRENT_SOURCE_DIR}/qml)
set(multiValueArgs QML_FILES RESOURCE_FILES)
set(QML_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/qml)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QML_ROOT_DIR 这个不暴露出去吧,我们只处理默认的,不然下面跟QML_FILES一起组合使用很容易出问题,
如果不设置QML_FILES,我们就默认去处理,否则就让调用方去处理ALIAS,


target_include_directories(${DCCQmlPlugin_Name} PRIVATE
include
org/deepin/dcc
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这种路径给个绝对路径更直观点吧,

@caixr23 caixr23 force-pushed the masterbak branch 4 times, most recently from 620f473 to 2d1f365 Compare January 15, 2026 05:06
@deepin-bot
Copy link

deepin-bot bot commented Jan 15, 2026

TAG Bot

New tag: 6.1.68
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #2940

@caixr23 caixr23 force-pushed the masterbak branch 2 times, most recently from 6597037 to 8aca5db Compare January 16, 2026 02:38
@deepin-bot
Copy link

deepin-bot bot commented Jan 23, 2026

TAG Bot

New tag: 6.1.69
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #2954

@caixr23 caixr23 force-pushed the masterbak branch 6 times, most recently from 2dd5003 to b9db479 Compare January 28, 2026 03:39
if (m_engine)
return;

QQmlEngine::setObjectOwnership(dccV25::DccApp::instance(), QQmlEngine::CppOwnership);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个可以直接在c++中用qtquick方式注册,

switch (plugin->type) {
case T_V1_1: {
QFile qmldir(plugin->path + "/qmldir");
if (qmldir.open(QFile::ReadOnly)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

真要搞这种去判断么?

@@ -1,20 +1,84 @@
include(CMakeParseArguments)

function(dcc_to_capitalized_string input_string output_variable)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这种我们要导出去么?如果只是控制中心内部使用的话,尽量不对外,

@deepin-bot
Copy link

deepin-bot bot commented Jan 28, 2026

TAG Bot

New tag: 6.1.70
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #2964

@caixr23 caixr23 force-pushed the masterbak branch 3 times, most recently from 46c00e0 to 1118732 Compare January 28, 2026 09:05
This commit transitions the control center from file-based QML plugin
loading to Qt6's resource system for better performance and deployment.
Key changes include:
1. Replaced manual file copying with qt_add_qml_module for proper QML
module management
2. Added plugin-system and plugin-device as proper subdirectories
3. Updated plugin loading logic to support both resource-based and file-
based plugins
4. Modified DCI icon theme search paths to use resource paths
5. Fixed local file paths in QML by converting them to proper file://
URLs
6. Enhanced plugin discovery with multiple path resolution strategies

The migration improves plugin loading performance, simplifies
deployment, and provides better integration with Qt6's QML module
system. It also maintains backward compatibility with existing file-
based plugins.

Log: Improved QML plugin loading performance and deployment

Influence:
1. Test control center startup and plugin loading
2. Verify all plugin modules load correctly (system, device, accounts,
etc.)
3. Check icon display in various modules
4. Test plugin navigation and functionality
5. Verify resource paths work in different deployment scenarios
6. Test backward compatibility with existing plugin configurations

feat: 迁移 QML 插件到 Qt6 资源系统

本次提交将控制中心从基于文件的 QML 插件加载迁移到 Qt6 资源系统,以获得更
好的性能和部署体验。主要变更包括:
1. 使用 qt_add_qml_module 替代手动文件复制,实现更好的 QML 模块管理
2. 添加 plugin-system 和 plugin-device 作为正式子目录
3. 更新插件加载逻辑以支持基于资源和基于文件的插件
4. 修改 DCI 图标主题搜索路径以使用资源路径
5. 通过转换为 file:// URL 修复 QML 中的本地文件路径问题
6. 增强插件发现功能,支持多种路径解析策略

此次迁移提高了插件加载性能,简化了部署流程,并提供了与 Qt6 QML 模块系统
的更好集成。同时保持了对现有基于文件的插件的向后兼容性。

Log: 提升 QML 插件加载性能和部署体验

Influence:
1. 测试控制中心启动和插件加载
2. 验证所有插件模块正确加载(系统、设备、账户等)
3. 检查各模块中的图标显示
4. 测试插件导航和功能
5. 验证资源路径在不同部署场景下的工作
6. 测试与现有插件配置的向后兼容性
@deepin-ci-robot
Copy link

deepin pr auto review

Git Diff 代码审查报告

总体概述

本次变更主要涉及dde-control-center项目的重构,包括:

  1. 插件系统从v1.0升级到v1.1版本
  2. QML模块组织结构重新调整
  3. 文件命名规范统一(首字母大写)
  4. 资源文件加载方式改进

详细审查意见

1. 语法和逻辑问题

1.1 CMakeLists.txt中的版本管理

set(DCC_PLUGINS_VERSION 1.1)
set(DCC_TRANSLATION_INSTALL_DIR "${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/translations/v${DCC_PLUGINS_VERSION}")

问题:插件版本硬编码为1.1,缺乏灵活性。
建议

  • 考虑从配置文件或命令行参数读取版本号
  • 添加版本兼容性检查逻辑
  • 建议使用语义化版本控制(如1.1.0)

1.2 插件加载逻辑中的类型判断

enum PluginType {
    T_Unknown = 0,
    T_V1_0 = 0x01000000,
    T_V1_1 = 0x02000000,
    T_V_MASK = 0xFF000000,
    // ...
};

问题:使用位掩码表示版本类型,但实际代码中并未充分利用位运算特性。
建议

  • 如果不需要位运算特性,建议使用枚举类替代位掩码
  • 添加版本转换和比较的辅助函数

2. 代码质量问题

2.1 文件命名检查逻辑

if(${F_NAME} STREQUAL "main.qml")
    dcc_to_capitalized_string(${_config_NAME} UPPERNAME)
    message(FATAL_ERROR "Error: The filename '${F_NAME}' is deprecated. Please rename it to ${UPPERNAME}Main.qml. path:${FILE_PATH}")
endif()

问题

  • 错误提示信息中文件路径可能过长,影响可读性
  • 缺少自动修复建议
    建议
  • 提供更清晰的错误提示格式
  • 考虑添加自动重命名选项(需用户确认)

2.2 资源文件处理

file(GLOB_RECURSE _config_RESOURCE_FILES ${QML_ROOT_DIR}/*.dci ${QML_ROOT_DIR}/*.svg ${QML_ROOT_DIR}/*.png ${QML_ROOT_DIR}/*.jpg ${QML_ROOT_DIR}/*.webp)

问题

  • 使用GLOB_RECURSE可能包含不需要的文件
  • 缺少对文件大小和数量的限制
    建议
  • 添加文件大小检查,避免包含过大资源
  • 考虑添加资源文件白名单/黑名单机制

3. 性能问题

3.1 插件加载性能

void PluginManager::loadModules(DccObject *root, bool async, const QStringList &dirs, QQmlEngine *engine)
{
    // ...
    for (const auto &plugin : m_plugins) {
        loadPlugin(plugin);
    }
}

问题

  • 插件加载顺序可能影响启动性能
  • 缺少加载优先级控制
    建议
  • 实现插件依赖关系管理
  • 添加插件预加载和延迟加载机制
  • 考虑使用插件加载缓存

3.2 图标资源加载

QString pic = m_themeModel->getPicList().value(m_keys.at(row));
return pic.startsWith("/") ? QUrl::fromLocalFile(pic).toString() : pic;

问题

  • 每次访问都进行字符串检查和转换
  • 缺少资源缓存
    建议
  • 实现图标资源缓存机制
  • 考虑使用资源预加载

4. 安全问题

4.1 插件路径处理

if (filepath.contains("_v1.1")) {
    plugin->type = T_V1_1;
} else if (filepath.contains("_v1.0")) {
    plugin->type = T_V1_0;
}

问题

  • 使用简单的字符串包含判断可能被绕过
  • 缺少路径规范化处理
    建议
  • 使用正则表达式进行严格匹配
  • 添加路径合法性检查
  • 考虑使用QDir规范路径

4.2 插件名称验证

if(NOT "${_config_NAME}" MATCHES "^[A-Za-z0-9]+$")
    message(FATAL_ERROR "Error: The name '${_config_NAME}' contains invalid characters. Only alphanumeric characters are allowed.")
endif()

问题

  • 名称验证规则过于简单
  • 缺少长度限制
    建议
  • 添加名称长度限制
  • 考虑添加保留名称检查
  • 实现名称规范化处理

5. 兼容性问题

5.1 版本兼容性处理

default: { // 尝试枚举版本
    plugin->type = T_V1_1;
    if (updatePluginType(plugin)) {
        return true;
    }
    plugin->type = T_V1_0;
    if (updatePluginType(plugin)) {
        return true;
    }
    plugin->type = T_Unknown;
    return false;
}

问题

  • 版本检测逻辑可能导致不确定行为
  • 缺少明确的版本兼容性策略
    建议
  • 实现明确的版本兼容性矩阵
  • 添加版本降级警告
  • 考虑实现版本迁移工具

6. 其他建议

  1. 文档改进

    • 添加插件开发文档
    • 补充版本升级指南
    • 完善API文档
  2. 测试覆盖

    • 添加单元测试覆盖插件加载逻辑
    • 实现集成测试验证版本兼容性
    • 添加性能基准测试
  3. 错误处理

    • 统一错误处理机制
    • 添加详细的错误日志
    • 实现错误恢复策略
  4. 国际化

    • 检查所有新增字符串是否支持翻译
    • 验证多语言环境下的功能

总结

本次重构整体方向正确,但需要关注以下关键点:

  1. 版本管理机制需要更加灵活和健壮
  2. 插件加载性能需要进一步优化
  3. 安全性检查需要加强
  4. 错误处理和日志记录需要完善

建议在合并前进行充分的测试,特别是:

  • 不同版本插件的混合加载测试
  • 资源文件加载性能测试
  • 异常情况下的系统稳定性测试

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 18202781743, caixr23

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@caixr23
Copy link
Contributor Author

caixr23 commented Jan 28, 2026

/forcemerge

@deepin-bot
Copy link

deepin-bot bot commented Jan 28, 2026

This pr force merged! (status: blocked)

@deepin-bot deepin-bot bot merged commit 913a23b into linuxdeepin:master Jan 28, 2026
16 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants