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.

@caixr23 caixr23 requested a review from 18202781743 January 6, 2026 07:38
@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 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

@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.

Comment on lines 117 to 126
} else {
path = plugin->path;
}
if (pathType & PluginQmlPathType::QMLPATH_IsQmlDir) {
path += "/qml";
}
if (type & PT_DIR) {
return path;
}
QString name = (pathType & PluginQmlPathType::QMLPATH_IsCapitalize) ? plugin->name.left(1).toUpper() + plugin->name.mid(1) : plugin->name;
Copy link

Choose a reason for hiding this comment

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

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.

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.
m_data->soObj->moveToThread(m_pManager->thread());
m_data->soObj->setParent(m_pManager->parent());
}
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 2 times, most recently from d631e7c to 00db5e4 Compare January 6, 2026 08:51
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

我来对这段代码进行审查,主要从以下几个方面分析:

  1. 代码结构优化

  2. 资源管理改进

  3. QML模块化重构

  4. 性能优化

  5. 安全性考虑

  6. 代码结构优化:

  • 将插件从 qml 目录移动到各自的 src/plugin-* 目录下,这是很好的模块化改进
  • 新增了 plugin-system 和 plugin-device 两个新的插件目录,使插件结构更清晰
  • 将 QML 文件和相关资源文件统一管理,提高了代码组织性
  1. 资源管理改进:
  • 使用 Qt6 的 qt_add_qml_module 来管理 QML 资源,这是更好的资源管理方式
  • 将 QML 文件和资源文件(.dci, .svg, .png, .jpg)分开管理
  • 使用 QUrl::fromLocalFile 来处理本地文件路径,确保文件访问的安全性
  1. QML模块化重构:
  • 使用 Qt6 的 QML 模块系统,通过 URI "org.deepin.dcc.${_config_NAME}" 来组织模块
  • 改进了 QML 文件的加载机制,使用 loadFromModule 替代直接文件路径加载
  • 添加了资源前缀设置,使资源访问更加规范
  1. 性能优化:
  • 优化了插件加载流程,区分了 QRC 和 SO 两种加载方式
  • 改进了图标搜索路径的设置,避免了重复设置
  • 使用异步加载 QML 组件,提高启动性能
  1. 安全性考虑:
  • 对文件路径进行了规范化处理,使用 QUrl::fromLocalFile 确保文件访问安全
  • 添加了文件存在性检查,避免访问不存在的文件
  • 改进了资源文件的访问控制,使用统一的资源前缀

建议改进:

  1. 在 DccUtils.js 中,建议将 .pragma library 放在文件开头,这是更好的编码规范
  2. 可以考虑为插件加载添加更多的错误处理和日志记录
  3. 建议为资源文件添加版本控制机制
  4. 可以考虑添加插件依赖管理,确保插件加载顺序正确
  5. 建议为 QML 模块添加更多的文档说明,方便其他开发者使用

这些改进使得代码更加模块化、可维护,同时也提高了性能和安全性。整体来说,这是一个很好的重构,采用了 Qt6 的新特性,使代码更加现代化和规范化。

@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

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

void LoadPluginTask::doLoadQrc()
Copy link
Contributor

Choose a reason for hiding this comment

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

看能否不自己去实现这种加载资源的方式,使用qt提供的loadFromModule这种

}
}
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,
嵌套有点儿深,

}
};

static bool checkQmlPath(PluginData *plugin)
Copy link
Contributor

Choose a reason for hiding this comment

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

这个不仅仅是check吧,它更主要的功能是设置PluginData的数据,而且返回值我看没地方使用,

} else {

QStringList paths = Dtk::Gui::DIconTheme::dciThemeSearchPaths();
const QString rcPath = plugin->qmlDir.startsWith("qrc:") ? plugin->qmlDir.mid(3) : plugin->qmlDir;
Copy link
Contributor

Choose a reason for hiding this comment

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

我看qmlDir这个变量,在checkQmlPath时已经转换了,这里怎么又剪去了?

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

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.

3 participants