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.

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

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这种

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/qml/下(默认路径)可用loadFromModule

Copy link
Contributor

Choose a reason for hiding this comment

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

它只是在特定的目录下去找吧,只是因为我们没有设置查找路径,所以动态库用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的数据,而且返回值我看没地方使用,

Copy link
Contributor Author

Choose a reason for hiding this comment

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

检查并缓存文件路径

Copy link
Contributor

Choose a reason for hiding this comment

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

函数名字只是个检查,而且返回值没用到,

} 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
Contributor Author

Choose a reason for hiding this comment

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

这里是给dci设置查找路径

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
{
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不是一个东西把

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.

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

}
};

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.

函数名字只是个检查,而且返回值没用到,

@caixr23 caixr23 force-pushed the masterbak branch 2 times, most recently from ec13008 to 957a0f3 Compare January 14, 2026 08:22
@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

@caixr23 caixr23 force-pushed the masterbak branch 2 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

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 代码审查报告

总体概述

本次变更主要涉及将控制中心项目从传统的QML文件系统迁移到Qt 6的Qt Quick编译器(QQmlEngine)和资源系统(qrc),以及将QML文件集成到CMake构建系统中。此外,还包括一些插件系统的重构和文件路径处理改进。

1. 语法逻辑审查

1.1 CMakeLists.txt 变更

# src/dde-control-center/CMakeLists.txt
add_library(${DCCFrame_Name} SHARED
    "${DCC_PROJECT_ROOT_DIR}/include/dccfactory.h"
)
  • 问题:只包含一个头文件创建共享库是不完整的
  • 建议:应该包含所有必要的源文件,或者如果这是一个仅头文件的库,应该使用INTERFACE库类型

1.2 QML资源系统变更

# misc/DdeControlCenterPluginMacros.cmake
qt_add_qml_module(${_config_NAME}_qml
    URI "org.deepin.dcc.${_config_NAME}"
    VERSION 1.0
    QML_FILES ${QML_PATHS}
    RESOURCES ${RESOURCE_PATHS}
    NO_PLUGIN
    OUTPUT_DIRECTORY "org/deepin/dcc/${_config_NAME}"
)
  • 问题:使用NO_PLUGIN选项意味着不会生成插件注册代码,但后续代码中使用了QML_ELEMENT宏
  • 建议:确保QML_ELEMENT宏正确注册,或者移除NO_PLUGIN选项以生成插件注册代码

1.3 QML文件路径处理

// src/dde-control-center/pluginmanager.cpp
static bool checkQmlPath(PluginData *plugin)
{
    const QStringList paths{
        ":/qt/qml/org/deepin/dcc/" + plugin->name,
        plugin->path,
    };
    // ...
}
  • 问题:硬编码的资源路径前缀":/qt/qml/org/deepin/dcc/"可能与实际资源系统结构不匹配
  • 建议:考虑使用Qt提供的资源系统API来获取正确的资源路径

2. 代码质量审查

2.1 QML元素命名

// include/dccquickdbusinterface.h
QML_NAMED_ELEMENT(DccDBusInterface)
  • 问题:类名是DccQuickDBusInterface,但QML元素名是DccDBusInterface,可能导致混淆
  • 建议:保持命名一致性,或者添加注释说明命名差异的原因

2.2 错误处理

// src/dde-control-center/pluginmanager.cpp
void LoadPluginTask::doLoadQrc()
{
    const QString soPath = m_data->path + "/lib" + m_data->name + "_qml.so";
    QElapsedTimer timer;
    timer.start();
    if (QFile::exists(soPath)) {
        // ...
        QLibrary loader(soPath);
        Q_EMIT m_pManager->updatePluginStatus(m_data, MetaDataLoad, QString());
        if (!loader.load()) {
            Q_EMIT m_pManager->updatePluginStatus(m_data, MetaDataErr | MetaDataEnd, "Load the plugin failed." + loader.errorString());
            return;
        }
        // ...
    } else {
        Q_EMIT m_pManager->updatePluginStatus(m_data, MetaDataErr | MetaDataEnd, "File does not exist:" + soPath);
    }
    // ...
}
  • 问题:错误处理虽然存在,但缺少详细的错误日志记录
  • 建议:添加qCDebug或qCCritical日志记录,便于调试

2.3 代码重复

// src/dde-control-center/pluginmanager.cpp
if (plugin->qmlModule.endsWith(".qml")) {
    const QString qmlPath = plugin->qmlDir + "/" + plugin->qmlModule;
    Q_EMIT updatePluginStatus(plugin, ModuleLoad, ": load module " + qmlPath);
    component->loadUrl(qmlPath, QQmlComponent::Asynchronous);
} else {
    Q_EMIT updatePluginStatus(plugin, ModuleLoad, ": load module " + plugin->qmlModule);
    component->loadFromModule("org.deepin.dcc." + plugin->name, plugin->qmlModule, QQmlComponent::Asynchronous);
}
  • 问题:类似的逻辑在loadMain函数中重复出现
  • 建议:提取为公共函数,减少代码重复

3. 代码性能审查

3.1 文件系统操作

// src/dde-control-center/pluginmanager.cpp
static bool checkQmlPath(PluginData *plugin)
{
    // ...
    for (const auto &path : paths) {
        if (QFile::exists(path)) {
            QDir dir(path);
            if (dir.exists("main.qml")) {
                plugin->qmlMain = "main.qml";
            }
            for (const auto &name : names) {
                if (dir.exists(name + "Main.qml")) {
                    plugin->qmlMain = name + "Main.qml";
                }
                if (dir.exists(name + ".qml")) {
                    plugin->qmlModule = name + ".qml";
                }
                if (!plugin->qmlModule.isEmpty() || !plugin->qmlMain.isEmpty()) {
                    break;
                }
            }
            // ...
        }
    }
    // ...
}
  • 问题:多次调用QFile::exists和QDir::exists可能导致性能问题,特别是在插件较多时
  • 建议:考虑缓存文件系统查询结果,或者减少不必要的文件系统操作

3.2 资源加载

// src/dde-control-center/pluginmanager.cpp
void LoadPluginTask::doLoadQrc()
{
    // ...
    QLibrary loader(soPath);
    Q_EMIT m_pManager->updatePluginStatus(m_data, MetaDataLoad, QString());
    if (!loader.load()) {
        Q_EMIT m_pManager->updatePluginStatus(m_data, MetaDataErr | MetaDataEnd, "Load the plugin failed." + loader.errorString());
        return;
    }
    // ...
}
  • 问题:同步加载插件可能会阻塞UI线程
  • 建议:考虑使用异步加载机制,或者确保此操作在后台线程执行

4. 代码安全审查

4.1 路径注入风险

// src/dde-control-center/pluginmanager.cpp
static bool checkQmlPath(PluginData *plugin)
{
    const QStringList paths{
        ":/qt/qml/org/deepin/dcc/" + plugin->name,
        plugin->path,
    };
    // ...
}
  • 问题:直接使用plugin->path可能导致路径注入攻击
  • 建议:验证和规范化plugin->path,确保它在预期的目录范围内

4.2 动态库加载

// src/dde-control-center/pluginmanager.cpp
void LoadPluginTask::doLoadQrc()
{
    const QString soPath = m_data->path + "/lib" + m_data->name + "_qml.so";
    // ...
    QLibrary loader(soPath);
    // ...
    if (!loader.load()) {
        Q_EMIT m_pManager->updatePluginStatus(m_data, MetaDataErr | MetaDataEnd, "Load the plugin failed." + loader.errorString());
        return;
    }
    // ...
}
  • 问题:动态加载库可能存在安全风险,特别是路径来自不可信来源
  • 建议:验证库的完整性和来源,考虑使用数字签名验证

4.3 QML文件路径处理

// src/plugin-deepinid/operation/appinfolistmodel.cpp
QVariant AppInfoListModel::data(const QModelIndex &index, int role) const
{
    // ...
    case IconRole:
        return appItem->icon.startsWith("/") ? QUrl::fromLocalFile(appItem->icon).toString() : appItem->icon;
    // ...
}
  • 问题:虽然处理了本地文件路径,但没有验证文件是否存在或是否可访问
  • 建议:添加文件存在性检查和访问权限验证

5. 其他建议

5.1 资源管理

// src/dde-control-center/pluginmanager.cpp
void PluginManager::loadModule(PluginData *plugin)
{
    // ...
    QStringList paths = Dtk::Gui::DIconTheme::dciThemeSearchPaths();
    const QString rcPath = plugin->qmlDir.startsWith("qrc:") ? plugin->qmlDir.mid(3) : plugin->qmlDir;
    paths.append(rcPath);
    Dtk::Gui::DIconTheme::setDciThemeSearchPaths(paths);
    // ...
}
  • 建议:考虑使用RAII模式管理资源路径,确保在函数结束时恢复原始路径

5.2 插件系统设计

// src/dde-control-center/pluginmanager.cpp
struct PluginData
{
    QString name;
    QString path;
    QString qmlDir;
    QString qmlModule;
    QString qmlMain;
    // ...
};
  • 建议:考虑使用智能指针管理插件数据,避免内存泄漏

总结

本次变更实现了从传统QML文件系统到Qt 6资源系统的迁移,这是一个重要的架构改进。主要优点包括:

  1. 更好的模块化和封装
  2. 改进的资源管理
  3. 更高效的QML加载机制

需要改进的地方:

  1. 增强错误处理和日志记录
  2. 减少代码重复
  3. 改进文件系统操作的性能
  4. 加强安全性验证,特别是路径处理和动态库加载

总体而言,这是一个积极的变更方向,但需要进一步完善细节处理和安全性考虑。

@18202781743 18202781743 requested review from BLumia and mhduiy January 16, 2026 07:35
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