Skip to content

feat: add card surface plugin interface for QML-based plugins - #474

Open
wjyrich wants to merge 1 commit into
linuxdeepin:masterfrom
wjyrich:CardPlugin
Open

feat: add card surface plugin interface for QML-based plugins#474
wjyrich wants to merge 1 commit into
linuxdeepin:masterfrom
wjyrich:CardPlugin

Conversation

@wjyrich

@wjyrich wjyrich commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Introduce PluginsItemInterfaceV3 extending V2 with virtual functions for card item key, QML source, icon source and preferred size. Implement CardPluginItem class and integrate into loader to support creating card QQuickView surfaces. Brightness plugin updated to V3 with a simple QML card showing icon, title and slider.

Log: Added card surface support for dock plugins with QML implementation

Influence:

  1. Verify brightness plugin detects card surfaces and shows QML card on dock
  2. Test card surface showing/hiding when support changes
  3. Verify card surface dark/light theme switching
  4. Test card surface resize when dock container changes size
  5. Verify fallback compatibility with plugins not implementing V3

feat: 添加基于 QML 的卡片表面插件接口

引入 PluginsItemInterfaceV3 扩展 V2,增加卡片项键、QML 源、图标源和首 选大小的虚函数。实现 CardPluginItem 类并集成到加载器中,支持创建卡片
QQuickView 表面。亮度插件更新至 V3,使用简单的 QML 卡片显示图标、标题和
滑块。

Log: 新增卡片表面支持,插件可使用 QML 实现

Influence:

  1. 验证亮度插件检测到卡片表面并在任务栏上显示 QML 卡片
  2. 测试支持状态变化时卡片表面的显示/隐藏
  3. 验证卡片表面深色/浅色主题切换
  4. 测试任务栏容器大小变化时卡片表面自适应调整
  5. 验证未实现 V3 的插件回退兼容性

Summary by Sourcery

Introduce a V3 plugins item interface and loader support for QML-based card surfaces, and adopt it in the brightness dock plugin.

New Features:

  • Add PluginsItemInterfaceV3 with card item key, QML source, icon source and preferred size for card surfaces.
  • Introduce CardPluginItem to host plugin-provided QML cards in a QQuickView and expose them as dock card surfaces.
  • Extend the brightness dock plugin to provide a QML card surface with icon, title and slider using the V3 interface.
  • Add a new Card plugin type for embedded dock plugins.

Enhancements:

  • Update WidgetPlugin to create, show, resize and destroy card surfaces alongside existing quick widgets with theme propagation.
  • Adjust plugin manager to prefer loading PluginsItemInterfaceV3 plugins while remaining compatible with V2 and legacy interfaces.

Build:

  • Update trayplugin-loader build to depend on Qt Quick and Qml and compile the new cardpluginitem sources.

PMS: TASK-392671

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: wjyrich

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

sourcery-ai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a QML-based card surface plugin interface (PluginsItemInterfaceV3) and a CardPluginItem loader path, wires it into the existing plugin manager/loader to create and manage Wayland-backed QQuickView card surfaces, and updates the brightness dock plugin to implement V3 with a QML card, while preserving backward compatibility for V2/legacy plugins.

Sequence diagram for creating a QML card surface on itemAdded

sequenceDiagram
    participant PluginManager
    participant BrightnessPlugin
    participant WidgetPlugin
    participant CardPluginItem
    participant EmbedPlugin

    PluginManager->>BrightnessPlugin: loadPlugin(pluginFilePath)
    BrightnessPlugin->>PluginManager: instance implements PluginsItemInterfaceV3
    PluginManager->>WidgetPlugin: new WidgetPlugin(pluginsItemInterface)

    BrightnessPlugin->>WidgetPlugin: itemAdded(this, cardItemKey())
    WidgetPlugin->>WidgetPlugin: createCardItemIfNeeded(itemInter, itemKey)
    WidgetPlugin->>CardPluginItem: new CardPluginItem(cardInterface, itemKey, this)
    CardPluginItem->>CardPluginItem: init()
    CardPluginItem->>CardPluginItem: QQuickView setSource(cardQmlSource())

    WidgetPlugin->>EmbedPlugin: Plugin::EmbedPlugin::get(CardPluginItem.window())
    EmbedPlugin->>EmbedPlugin: setPluginType(Plugin::EmbedPlugin::Card)
    EmbedPlugin->>WidgetPlugin: dockColorThemeChanged(uint32_t)
    WidgetPlugin->>CardPluginItem: setDockColorTheme(int)
    EmbedPlugin->>CardPluginItem: eventGeometry(QRect)
    CardPluginItem->>CardPluginItem: resize(QSize)

    WidgetPlugin->>CardPluginItem: show()
Loading

File-Level Changes

Change Details Files
Introduce PluginsItemInterfaceV3 and CardPluginItem to support QML-driven card surfaces for dock plugins.
  • Define PluginsItemInterfaceV3 extending V2 with virtual hooks for card item key, QML source, icon source and preferred size, plus a V3 Qt plugin IID.
  • Implement CardPluginItem that wraps a QQuickView, exposes theme-related properties to QML, and manages view lifecycle, sizing and QML context setup.
  • Add BrightnessCard.qml and cardQmlSource helper returning the card QML URL for the brightness plugin.
interfaces/pluginsiteminterface_v3.h
src/loader/cardpluginitem.h
src/loader/cardpluginitem.cpp
plugins/dde-dock/brightness/resources/qml/BrightnessCard.qml
plugins/dde-dock/brightness/brightness-constants.h
Integrate card surface handling into the loader and plugin manager, including lifecycle and theme/geometry updates.
  • Extend CMake and target_link_libraries to pull in Qt Quick/Qml and compile cardpluginitem sources.
  • Update PluginManager to prefer V3 interfaces, falling back to V2 and then the base interface for legacy plugins.
  • Enhance WidgetPlugin to detect V3 plugins, create/manage CardPluginItem per itemKey, hook EmbedPlugin signals for color theme and geometry, and track card items in a QHash.
  • Ensure card card items are cleaned up on WidgetPlugin destruction and on item removal, and add a Card type to EmbedPlugin::PluginType.
src/loader/CMakeLists.txt
src/loader/pluginmanager.cpp
src/loader/widgetplugin.h
src/loader/widgetplugin.cpp
src/tray-wayland-integration/plugin.h
Upgrade the brightness dock plugin to the V3 interface and expose a QML card surface with icon, title and slider.
  • Change BrightnessPlugin to implement PluginsItemInterfaceV3 and use the V3 Qt plugin metadata IID.
  • Add implementations for cardItemKey, cardQmlSource, cardIconSource and cardPreferredSize to describe the brightness card surface.
  • Modify brightness plugin init and support-change handling to register/unregister both the classic item and the card item with the proxy when brightness support toggles.
plugins/dde-dock/brightness/brightnessplugin.h
plugins/dde-dock/brightness/brightnessplugin.cpp

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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • CardPluginItem instances are constructed with WidgetPlugin as parent and also deleted via qDeleteAll(m_cardItems) in the destructor, which can lead to double deletion; either drop the parent relationship or remove qDeleteAll and rely on QObject ownership.
  • In createCardItemIfNeeded(), when cardItem->init() or window() fails you still return true, which prevents the normal widget path from being created; consider returning false on failure so the plugin can gracefully fall back to the non-card implementation.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- CardPluginItem instances are constructed with WidgetPlugin as parent and also deleted via qDeleteAll(m_cardItems) in the destructor, which can lead to double deletion; either drop the parent relationship or remove qDeleteAll and rely on QObject ownership.
- In createCardItemIfNeeded(), when cardItem->init() or window() fails you still return true, which prevents the normal widget path from being created; consider returning false on failure so the plugin can gracefully fall back to the non-card implementation.

## Individual Comments

### Comment 1
<location path="src/loader/widgetplugin.cpp" line_range="382" />
<code_context>
     return Plugin::EmbedPlugin::get(widget->windowHandle());
 }

+bool WidgetPlugin::createCardItemIfNeeded(PluginsItemInterface *itemInter, const QString &itemKey)
+{
+    auto cardInterface = dynamic_cast<PluginsItemInterfaceV3 *>(itemInter);
</code_context>
<issue_to_address>
**issue (complexity):** Consider separating card-item classification from lifecycle management and making the card helpers void and explicitly branched in itemAdded/itemRemoved to clarify control flow and responsibilities.

The added card path does increase complexity, mainly through the overloaded `createCardItemIfNeeded` and its hidden control‑flow contract. You can simplify the logic and make `itemAdded`/`itemRemoved` easier to reason about by:

1. **Separate classification from lifecycle**  
   Extract a small helper that decides “is this a card item?” and use it to branch explicitly in `itemAdded`/`itemRemoved`. Then make the lifecycle helper `void` so its return value no longer encodes behavior.

   ```cpp
   // New helper
   bool WidgetPlugin::isCardItem(PluginsItemInterface *itemInter, const QString &itemKey) const
   {
       auto cardInterface = dynamic_cast<PluginsItemInterfaceV3 *>(itemInter);
       return cardInterface && cardInterface->cardItemKey() == itemKey;
   }

   // Adjusted itemAdded
   void WidgetPlugin::itemAdded(PluginsItemInterface * const itemInter, const QString &itemKey)
   {
       qDebug() << "itemAdded:" << itemKey;

       if (isCardItem(itemInter, itemKey)) {
           ensureCardItem(itemInter, itemKey);
           return;
       }

       auto flag = getPluginFlags();
       if (flag & Dock::Type_Quick) {
           // existing quick path...
       }
       // existing normal widget path...
   }
   ```

2. **Make card lifecycle explicit and non‑boolean**  
   Rename `createCardItemIfNeeded` to something like `ensureCardItem` and change the signature to `void`. Keep the semantics identical: normal widget path should never run for card items.

   ```cpp
   // Refactored from createCardItemIfNeeded
   void WidgetPlugin::ensureCardItem(PluginsItemInterface *itemInter, const QString &itemKey)
   {
       auto cardInterface = static_cast<PluginsItemInterfaceV3 *>(itemInter);

       if (auto existing = m_cardItems.value(itemKey)) {
           existing->show();
           return;
       }

       auto cardItem = new CardPluginItem(cardInterface, itemKey, this);
       if (!cardItem->init() || !cardItem->window()) {
           cardItem->deleteLater();
           qWarning() << "create card plugin surface failed" << itemInter->pluginName() << itemKey;
           return; // still block normal widget path
       }

       auto plugin = Plugin::EmbedPlugin::get(cardItem->window());
       plugin->setPluginFlags(getPluginFlags());
       plugin->setPluginId(itemInter->pluginName());
       plugin->setDisplayName(itemInter->pluginDisplayName());
       plugin->setItemKey(itemKey);
       plugin->setPluginType(Plugin::EmbedPlugin::Card);
       plugin->setPluginSizePolicy(itemInter->pluginSizePolicy());

       connect(plugin, &Plugin::EmbedPlugin::dockColorThemeChanged,
               this, &WidgetPlugin::onDockColorThemeChanged, Qt::UniqueConnection);
       connect(plugin, &Plugin::EmbedPlugin::dockColorThemeChanged,
               cardItem, [cardItem](uint32_t colorTheme) {
                   cardItem->setDockColorTheme(static_cast<int>(colorTheme));
               });
       connect(plugin, &Plugin::EmbedPlugin::eventGeometry,
               cardItem, [cardItem](const QRect &geometry) {
                   cardItem->resize(geometry.size());
               });

       m_cardItems.insert(itemKey, cardItem);
       cardItem->show();
   }
   ```

3. **Mirror the explicit branching in `itemRemoved`**  
   Keep the current behavior, but make the “card vs normal widget” decision obvious:

   ```cpp
   void WidgetPlugin::itemRemoved(PluginsItemInterface * const itemInter, const QString &itemKey)
   {
       Q_UNUSED(itemInter);

       if (auto cardItem = m_cardItems.take(itemKey)) {
           cardItem->hide();
           cardItem->deleteLater();
           return;
       }

       auto widget = m_pluginsItemInterface->itemWidget(itemKey);
       if (widget && widget->window() && widget->window()->windowHandle()) {
           widget->window()->windowHandle()->hide();
       }

       auto quickPanel = m_pluginsItemInterface->itemWidget(Dock::QUICK_ITEM_KEY);
       if (quickPanel && quickPanel->window() && quickPanel->window()->windowHandle()) {
           quickPanel->window()->windowHandle()->hide();
       }
   }
   ```

These changes keep all current functionality (including “card creation failure blocks normal widget path”) but make the control flow and responsibilities clearer: classification (`isCardItem`), lifecycle (`ensureCardItem`), and normal widget handling are separated and easier to follow.
</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 thread src/loader/widgetplugin.cpp Outdated
return Plugin::EmbedPlugin::get(widget->windowHandle());
}

bool WidgetPlugin::createCardItemIfNeeded(PluginsItemInterface *itemInter, const QString &itemKey)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (complexity): Consider separating card-item classification from lifecycle management and making the card helpers void and explicitly branched in itemAdded/itemRemoved to clarify control flow and responsibilities.

The added card path does increase complexity, mainly through the overloaded createCardItemIfNeeded and its hidden control‑flow contract. You can simplify the logic and make itemAdded/itemRemoved easier to reason about by:

  1. Separate classification from lifecycle
    Extract a small helper that decides “is this a card item?” and use it to branch explicitly in itemAdded/itemRemoved. Then make the lifecycle helper void so its return value no longer encodes behavior.

    // New helper
    bool WidgetPlugin::isCardItem(PluginsItemInterface *itemInter, const QString &itemKey) const
    {
        auto cardInterface = dynamic_cast<PluginsItemInterfaceV3 *>(itemInter);
        return cardInterface && cardInterface->cardItemKey() == itemKey;
    }
    
    // Adjusted itemAdded
    void WidgetPlugin::itemAdded(PluginsItemInterface * const itemInter, const QString &itemKey)
    {
        qDebug() << "itemAdded:" << itemKey;
    
        if (isCardItem(itemInter, itemKey)) {
            ensureCardItem(itemInter, itemKey);
            return;
        }
    
        auto flag = getPluginFlags();
        if (flag & Dock::Type_Quick) {
            // existing quick path...
        }
        // existing normal widget path...
    }
  2. Make card lifecycle explicit and non‑boolean
    Rename createCardItemIfNeeded to something like ensureCardItem and change the signature to void. Keep the semantics identical: normal widget path should never run for card items.

    // Refactored from createCardItemIfNeeded
    void WidgetPlugin::ensureCardItem(PluginsItemInterface *itemInter, const QString &itemKey)
    {
        auto cardInterface = static_cast<PluginsItemInterfaceV3 *>(itemInter);
    
        if (auto existing = m_cardItems.value(itemKey)) {
            existing->show();
            return;
        }
    
        auto cardItem = new CardPluginItem(cardInterface, itemKey, this);
        if (!cardItem->init() || !cardItem->window()) {
            cardItem->deleteLater();
            qWarning() << "create card plugin surface failed" << itemInter->pluginName() << itemKey;
            return; // still block normal widget path
        }
    
        auto plugin = Plugin::EmbedPlugin::get(cardItem->window());
        plugin->setPluginFlags(getPluginFlags());
        plugin->setPluginId(itemInter->pluginName());
        plugin->setDisplayName(itemInter->pluginDisplayName());
        plugin->setItemKey(itemKey);
        plugin->setPluginType(Plugin::EmbedPlugin::Card);
        plugin->setPluginSizePolicy(itemInter->pluginSizePolicy());
    
        connect(plugin, &Plugin::EmbedPlugin::dockColorThemeChanged,
                this, &WidgetPlugin::onDockColorThemeChanged, Qt::UniqueConnection);
        connect(plugin, &Plugin::EmbedPlugin::dockColorThemeChanged,
                cardItem, [cardItem](uint32_t colorTheme) {
                    cardItem->setDockColorTheme(static_cast<int>(colorTheme));
                });
        connect(plugin, &Plugin::EmbedPlugin::eventGeometry,
                cardItem, [cardItem](const QRect &geometry) {
                    cardItem->resize(geometry.size());
                });
    
        m_cardItems.insert(itemKey, cardItem);
        cardItem->show();
    }
  3. Mirror the explicit branching in itemRemoved
    Keep the current behavior, but make the “card vs normal widget” decision obvious:

    void WidgetPlugin::itemRemoved(PluginsItemInterface * const itemInter, const QString &itemKey)
    {
        Q_UNUSED(itemInter);
    
        if (auto cardItem = m_cardItems.take(itemKey)) {
            cardItem->hide();
            cardItem->deleteLater();
            return;
        }
    
        auto widget = m_pluginsItemInterface->itemWidget(itemKey);
        if (widget && widget->window() && widget->window()->windowHandle()) {
            widget->window()->windowHandle()->hide();
        }
    
        auto quickPanel = m_pluginsItemInterface->itemWidget(Dock::QUICK_ITEM_KEY);
        if (quickPanel && quickPanel->window() && quickPanel->window()->windowHandle()) {
            quickPanel->window()->windowHandle()->hide();
        }
    }

These changes keep all current functionality (including “card creation failure blocks normal widget path”) but make the control flow and responsibilities clearer: classification (isCardItem), lifecycle (ensureCardItem), and normal widget handling are separated and easier to follow.

@18202781743
18202781743 self-requested a review July 6, 2026 06:56
Comment thread src/loader/widgetplugin.cpp Outdated
void WidgetPlugin::itemAdded(PluginsItemInterface * const itemInter, const QString &itemKey)
{
qDebug() << "itemAdded:" << itemKey;
if (createCardItemIfNeeded(itemInter, itemKey)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

可以按照flag来处理card,

Comment thread interfaces/pluginsiteminterface_v3.h Outdated
* The loader creates a QQuickView for this URL and exposes it to the dock
* compositor as a Wayland surface.
*/
virtual QUrl cardQmlSource() const

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

这里返回一个qwindow是不是更好,让应用控制,这样也能支持qml和qwidget,

@deepin-bot

deepin-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

TAG Bot

New tag: 2.0.36
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #476

@wjyrich
wjyrich force-pushed the CardPlugin branch 4 times, most recently from 74167e4 to 59457ba Compare July 14, 2026 03:15
Comment thread src/loader/widgetplugin.cpp Outdated
qDebug() << "itemAdded:" << itemKey;

auto flag = getPluginFlags();
if ((flag & Dock::Attribute_HasCard) && createCardItemIfNeeded(itemInter, itemKey)) {

@18202781743 18202781743 Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

不需要为false就return吧,逻辑是不是还可以往下走,只处理card需要的逻辑,card跟tray和quick一样,只是其中的一个item类型,按照之前quick这样的处理方式处理card,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

m_proxyInter->itemAdded(this, MEDIA_KEY);
m_proxyInter->itemAdded(this, cardItemKey());     在上面会调用两次  根据不同的 flag来走。

Comment thread src/loader/widgetplugin.h Outdated
private:
PluginsItemInterface* m_pluginsItemInterface;
QScopedPointer<PluginItem> m_pluginItem;
QHash<QString, CardPluginItem *> m_cardItems;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

这个不需要是个QHash吧,它是不是应该只有一个,

@deepin-bot

deepin-bot Bot commented Jul 23, 2026

Copy link
Copy Markdown

TAG Bot

New tag: 2.0.37
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #482

@deepin-bot

deepin-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

TAG Bot

New tag: 2.0.38
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #484

@wjyrich
wjyrich force-pushed the CardPlugin branch 2 times, most recently from 69f7be0 to 44c850d Compare August 6, 2026 02:49
@wjyrich

wjyrich commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

/test github-pr-review-ci

UI

Add a new plugin interface version (V3) supporting card surface
rendering via native QWindow, enabling richer plugin UI like media
player controls. Implement the media plugin card feature with QML-
based music player card, previous song support, and improved metadata
handling.

- Add PluginsItemInterfaceV3 with cardItemKey() and cardWindow() for
native window rendering
- Add Attribute_HasCard plugin flag to identify card-capable plugins
- Implement CardPluginItem in loader to manage card windows as
EmbedPlugin
- Add media card QML UI with playback controls, artwork display, and
metadata
- Add previous track support and playback toggle to MediaController
- Improve MPRIS metadata handling (artist list, title, artwork path)
- Fix plugin unload issues with QPointer guard in EmbedPlugin
- Update media plugin to use V3 interface and create card surface
- Add play-previous SVG icon and update CMakeLists for QtQuick
dependencies

Log: Added card surface support for dock plugins and music player card
UI

Influence:
1. Test media plugin card appears when music player is active
2. Verify card artwork display with various mpris:artUrl sources
3. Test playback controls (play/pause, next, previous) via card
4. Verify card auto-hides when plugin/player is removed
5. Test plugin loading with V2 backwards compatibility
6. Test card window resize and positioning in dock
7. Verify metadata display (title, artist) update in real-time
8. Test hover behavior and control animations in card UI
9. Test clean plugin unload and resource cleanup
10. Verify card works with different system themes (light/dark)

feat: 为任务栏插件添加卡片表面支持及音乐播放器卡片UI

新增插件接口版本V3,支持通过原生QWindow渲染卡片表面,实现更丰富的插件
UI(如媒体播放控制)。实现媒体插件的卡片功能,包含基于QML的音乐播放器卡
片、上一曲支持和改进的元数据处理。

- 新增PluginsItemInterfaceV3接口,提供cardItemKey()和cardWindow()实现原
生窗口渲染
- 新增Attribute_HasCard插件标志,用于标识支持卡片的插件
- 在加载器中实现CardPluginItem,管理作为EmbedPlugin的卡片窗口
- 添加媒体卡片QML UI,包含播放控制、封面显示和元数据展示
- 在MediaController中添加上一曲支持和播放切换功能
- 改进MPRIS元数据处理(艺术家列表、标题、封面路径)
- 使用QPointer守卫修复EmbedPlugin中的插件卸载问题
- 更新媒体插件使用V3接口并创建卡片表面
- 添加上一曲SVG图标并更新CMakeLists添加QtQuick依赖

Log: 新增任务栏插件卡片表面功能和音乐播放器卡片UI

Influence:
1. 测试音乐播放器活动时媒体插件卡片正确显示
2. 验证卡片封面图在不同mpris:artUrl源下的显示效果
3. 测试通过卡片进行播放控制(播放/暂停、下一曲、上一曲)
4. 验证插件/播放器移除时卡片自动隐藏
5. 测试V2版本插件的向后兼容加载
6. 测试卡片窗口在任务栏中的尺寸调整和定位
7. 验证元数据(标题、艺术家)实时更新
8. 测试卡片UI的悬停行为和控件动画
9. 测试插件的干净卸载和资源清理
10. 验证卡片在不同系统主题(浅色/深色)下的显示效果

PMS: TASK-392671
@wjyrich

wjyrich commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

/test github-pr-review-ci

@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

用户-dock 任务栏插件系统 V3 接口及卡片 Surface 机制的 Diff 补丁代码审查。
  1. 理解上下文与需求

    • 角色:UOS 操作系统代码审核助手。
    • 任务:对提供的 Diff 补丁进行深度审核。
    • 输入:Diff 补丁,以及已知信息(当前/历史扫描结果为空 ${sast_check_result}${last_sast_check_result})。
    • 约束:严格的输出格式,禁止特定词汇,强制符号,安全优先评分,绑定评价词等。
  2. 分析 Diff 补丁

    • 新增接口PluginsItemInterfaceV3,继承 V2,增加 cardItemKey()cardWindow()。正常的设计演进。
    • 新增标志Attribute_HasCard = 0x2000
    • 核心逻辑 WidgetPlugin::createCardItemIfNeeded
      • dynamic_cast 获取 V3 接口。
      • 检查 m_cardItem 是否存在,存在则 show。
      • 创建 CardPluginItem,调用 init()
      • 获取 EmbedPlugin,配置属性,连接信号。
      • 潜在问题m_cardItem 被赋值,但 m_cardItemQPointer<CardPluginItem>CardPluginItem 的父对象是 this (WidgetPlugin)。deleteLater 不会立刻析构,但如果在 deleteLater 后立刻被访问(如快速重入),QPointer 能防住。不过如果 cardItem->init() 失败,调用了 deleteLater(),然后返回。此时 m_cardItem 并没有被赋值,所以安全。如果 init 成功,但 EmbedPlugin::get 失败,调用了 deleteLater(),也安全。
      • 逻辑问题:在 itemRemoved 中,只是 m_cardItem->hide(),没有清理 m_cardItem 本身。如果插件被卸载/移除,m_cardItem 依然保留,下次 itemAdded 时直接 show()。这可能是有意为之(重用),但如果插件被卸载,m_cardItem 内部的 m_pluginInterface 指针会变成悬空指针!CardPluginItem 构造时保存了 PluginsItemInterfaceV3 *m_pluginInterface,但使用的是原始指针,没有使用 QPointer。如果插件被卸载,m_pluginInterface 悬空。
    • CardPluginItem
      • m_pluginInterface 是原始指针。在 init() 中调用 m_pluginInterface->cardWindow() 等。如果插件被卸载,此指针悬空。
    • MediaPlugin
      • cardWindow() 中,m_cardViewmutable QPointer<QQuickView>。如果为空,new QQuickView
      • 问题:在 const 成员函数中修改 mutable 成员,虽然语法合法,但存在线程安全问题或重入问题。如果在多线程或信号槽重入中调用 cardWindow(),可能创建多个 QQuickView。不过 QPointer 赋值是原子的,但 new QQuickView 和设置属性不是原子的。由于 Qt 插件主要在主线程运行,重入风险较低,但仍属于不良设计。
      • 析构函数 ~MediaPlugin()delete m_cardView;m_cardViewQPointer,直接 delete 是可以的,但通常用 deleteLater() 更安全。不过如果在主线程析构,直接 delete 没问题。
    • MediaController
      • artSource():增加了安全校验,仅允许本地文件协议。这是修复 SSRF 的好做法。
      • loadMediaPath 改为异步获取 CanShowInUI
      • removeMediaPath 中清理状态。
      • metadataText 处理 QStringListQVariantList。逻辑正确。
    • EmbedPlugin::get
      • 使用 QPointer<EmbedPlugin> pluginGuard(plugin) 修复了 visibleChanged 回调中的悬空指针问题。这是一个很好的修复。
  3. 安全漏洞分析

    • 已知信息current_tool_sast_checklast_sast_check_report 均为空(${sast_check_result})。
    • AI 独立分析
      • MediaController::artSource() 已经做了 isLocalFile() 的校验,看起来防止了 SSRF。
      • 但是,在 MusicCard.qml 中,artworkImage.source 绑定到 mediaController.artSource。如果 artSource() 返回了合法的本地文件路径,比如 /tmp/evil.so(伪装成图片),QML 的 Image 组件会尝试加载它。虽然 Image 组件通常只解析图片格式,不会执行代码,但这属于“本地文件读取”。不过 artSource() 已经限制了 file:// 协议,所以远程 SSRF 被阻断了。
      • 潜在 SSRF 绕过/本地文件泄露MediaController::artSource() 中,parsedUrl.isLocalFile() 为 true 时,直接返回 url(即原始的 mpris:artUrl 字符串)。如果恶意 MPRIS 客户端发送的 artUrlfile:///etc/passwd,那么 artSource() 会返回 file:///etc/passwd,然后 QML 的 Image 组件会尝试加载 /etc/passwd 作为图片。虽然加载会失败(不是有效图片),但这构成了本地任意文件读取尝试(路径遍历/信息暴露)。攻击面:恶意本地应用通过 D-Bus 注册 MPRIS 服务,提供恶意的 artUrl。危害:虽然不能直接看到文件内容(因为 Image 组件渲染失败),但可以通过侧信道(如加载时间、错误日志)探测文件是否存在,或者触发系统对特定路径的访问(如挂载点触发)。更严重的是,如果未来 QML Image 组件或其依赖的图像库存在解析漏洞,可能导致代码执行。作为一个安全审计,应该限制图片路径到特定目录(如 /tmp, /home, ~/.cache),或者至少对路径进行规范化检查,防止读取敏感系统文件。
      • 悬空指针/Use-After-FreeCardPluginItem 中的 PluginsItemInterfaceV3 *m_pluginInterface 是原始指针。如果插件被卸载(PluginManager 析构或卸载插件),WidgetPlugin 可能依然持有 m_cardItem,而 m_cardItem 持有悬空的 m_pluginInterface。当 createCardItemIfNeeded 被再次调用或窗口事件触发时,可能访问悬空指针。不过 WidgetPlugin 的生命周期通常和插件绑定,itemRemoved 只是 hide,没有销毁 m_cardItem。如果插件被卸载,WidgetPlugin 也会被销毁,从而销毁 m_cardItem。所以悬空指针风险可能不高,但设计上不够健壮。
      • 重点看 artSource() 的本地文件泄露:这是一个中危漏洞。虽然 QML Image 加载非图片文件通常失败,但它确实发起了对任意本地文件的 open() 系统调用。这违反了最小权限原则,且可能被用于探测文件存在性(如果错误被记录或通过其他方式可观测)。更规范的写法是校验文件后缀(如 .png, .jpg, .svg)或者限制路径前缀。
      • 等等,artSource() 返回的是 url 字符串,包含 file:// 前缀。QML Image 接受这个 URL。如果 URL 是 file:///etc/shadow,QML 会尝试读取 /etc/shadow。这确实是一个本地文件读取漏洞(虽然输出受限,但输入/读取动作发生了)。
      • 再看 onMetaDataChanged 中的 QImageReader reader(artPath); info.pixmap = QPixmap::fromImage(reader.read());,这里也直接使用了 artPath,没有做任何路径限制!这比 QML 更直接,如果在 C++ 侧读取了敏感文件并尝试解码,可能会触发某些底层库的漏洞,或者至少浪费资源。这也是一个漏洞点。
      • 漏洞总结
        1. 本地任意文件读取/探测(中危):在 MediaController::artSource()MediaController::onMetaDataChanged() 中,仅校验了协议是否为 file://,未对文件路径进行白名单或黑名单限制。恶意 MPRIS 客户端可提供如 file:///etc/passwd 的 URL,导致进程尝试读取系统敏感文件。
  4. 语法逻辑分析

    • dynamic_cast 使用正确。
    • QPointer 使用正确。
    • metadataText 处理了 QStringListQVariantList,逻辑正确。
    • loadMediaPath 异步化,逻辑正确,错误时默认显示,兼容旧版。
    • removeMediaPath 清理状态,逻辑正确。
    • 逻辑瑕疵MediaPlugin::cardWindow()const 函数,但内部使用了 mutable 修饰的 m_cardView 并可能 new 对象。虽然语法正确,但在 const 函数中产生副作用(修改对象状态)是反模式,且在重入情况下可能导致创建多个 View(尽管 QPointer 赋值会覆盖,但前一个可能泄漏或未被正确管理)。不过因为 m_cardViewQPointer,如果 new 出来赋值给 m_cardView,然后重入又 new 一个,旧的 m_cardView 会被覆盖,导致内存泄漏(因为 QPointer 只是观察者,不管理生命周期,而这里没有 delete 旧的)。这是一个内存泄漏的逻辑错误!
      • 代码:
        QWindow *MediaPlugin::cardWindow() const
        {
            if (m_cardView) {
                return m_cardView;
            }
            auto view = new QQuickView; // 如果这里被重入(比如信号槽递归),会 new 多次
            // ... 设置 view ...
            m_cardView = view; // 覆盖旧的,旧的泄漏
            return m_cardView;
        }
      • 虽然在单线程 GUI 中重入概率低,但这是一个明确的逻辑缺陷(非线程安全+潜在内存泄漏)。判定为“存在错误”。
  5. 代码质量分析

    • 命名规范:符合 Qt/UOS 风格。
    • 注释:关键接口有注释(英文),部分逻辑有注释。
    • 代码重复:metadataText 提取为辅助函数,减少了重复。
    • mutable 的使用:如上所述,在 const 函数中修改状态,属于“

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