Skip to content

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

Merged
deepin-bot[bot] merged 1 commit into
linuxdeepin:masterfrom
wjyrich:CardPlugin
Sep 10, 2026
Merged

deepin-bot[bot] merged 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 QML card-surface support for dock plugins while adding a responsive media card and preserving compatibility with existing plugins.

New Features:

  • Add a version 3 plugin interface and loader support for QML-based card surfaces, including ordering, tooltips, context menus, resizing, and theme integration.
  • Add a media-player card surface with artwork, track information, and playback controls backed by MPRIS state.
  • Add fashion-mode messaging so plugins can adapt their layouts and card surfaces to dock mode and size.

Bug Fixes:

  • Improve media-player metadata, artwork, capability, identity, and state handling across player changes and incomplete MPRIS implementations.
  • Preserve compatibility with existing V1/V2 plugins and prevent stale embedded-surface mappings during card recreation.

Enhancements:

  • Adjust the date-time plugin layout for fashion mode, compact dock sizes, and consistent single-line or two-line rendering.

Build:

  • Add Qt Quick and QML dependencies required by the media plugin.

PMS: TASK-392671

@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

@wjyrich

wjyrich commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

/test github-pr-review-ci

@deepin-bot

deepin-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown

TAG Bot

New tag: 2.0.39
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #495

1. Introduce PluginsItemInterfaceV3 interface with cardItemKey(),
cardWindow(), cardOrder(), cardContextMenu(), cardTipsWidget() and
invokedCardMenuItem() methods to enable plugins to expose card surfaces
in the dock
2. Add Attribute_HasCard plugin flag and implement CardPluginItem in
the loader that manages the card surface lifecycle, including show/
hide, resize, tooltip display with hover delay, context menu and XDG
activation token support
3. Implement CardPluginItem event filter for mouse interaction including
right-click context menu, enter/leave for tooltip timer, and proper
surface binding with Plugin::EmbedPlugin
4. Add MSG_DOCK_FASHION_MODE message so taskbar informs plugins when
fashion mode changes, and MediaPlugin enables card area only in fashion
mode
5. Add card sorting message MSG_CARD_ORDER sent after plugin surface
creation, ensuring media card positioning is adjustable through plugin
configuration
6. Handle plugin map lifecycle in EmbedPlugin with proper removal that
prevents stale plugin bindings when windows are reused
7. Fix EmbedPlugin visibleChanged connection binding to plugin instance
rather than window to prevent unbinding when reused for same window

Log: Added dock card surface plugin feature. Media plugin now displays
album art and music controls in a new card area next to the tray when in
fashion mode, and datetime plugin adjusts from two-line to single-line
layout when dock is compact.

Influence:
1. Test plugin loading for various MPRIS players (pause, previous, next
buttons on card)
2. Verify media card tooltip displays song information correctly
3. Test context menu in card surfaces with XDG activation
4. Switch dock between fashion and efficient modes, verify cards show/
hide correctly
5. Verify card surfaces don't appear in efficient mode
6. Test compact dock sizes to verify datetime single-line layout
transition
7. Test fashion mode message propagation to plugins
8. Verify card order configuration affects card positioning

feat: 增加插件卡片区域支持

1. 引入 PluginsItemInterfaceV3 接口,新增 cardItemKey()、
cardWindow()、cardOrder()、cardContextMenu()、cardTipsWidget() 及
invokedCardMenuItem() 方法,使插件可以展示卡片区域
2. 增加 Attribute_HasCard 插件属性,装载器实现 CardPluginItem 管理卡
片的生命周期,包括显示/隐藏、尺寸调整、带悬停延迟的气泡提示和带 XDG
activation token 的右键菜单
3. CardPluginItem 通过事件过滤器处理鼠标交互,右键弹出上下文菜单,进出触
发气泡定时器,并正确绑定 Plugin::EmbedPlugin 插件表面
4. 新增 MSG_DOCK_FASHION_MODE 消息,任务栏在模式变化时通知插件,媒体插件
只在时尚模式下启用卡片区
5. 新增 MSG_CARD_ORDER 排序消息,在插件表面创建后发送,卡片顺序可通过插
件配置调整
6. 修复 EmbedPlugin 映射生命周期,窗口复用时防止残留的插件绑定
7. 修复 EmbedPlugin 的 visibleChanged 信号连接绑定到插件实例而非窗口,避
免窗口复用时解除后续新建插件的绑定

Log: 新增卡片区插件特性。媒体插件在时尚模式下可于托盤区域旁新卡片区展示
唱片封面和音乐控制,同时日期时间插件在 dock 尺寸紧凑时从两行显示切换为
一行。

Influence:
1. 测试各种 MPRIS 播放器的媒体插件加载(卡片上暂停/上一首/下一首按键)
2. 验证媒体卡片的气泡提示是否显示歌曲信息
3. 测试卡片上的右键菜单配合 XDG activation 是否可以正常激活
4. 在时尚模式和高效模式间切换时验证卡片正确显示/隐藏
5. 确保高效模式下不会出现卡片表面
6. 测试紧凑 dock 尺寸切换时日期时间控件的单行布局过渡
7. 测试时尚模式消息能否正确传递给插件
8. 验证卡片顺序配置是否影响卡片位置

PMS: TASK-392671
@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

AI 代码审查报告

PR: feat: add card surface plugin interface for QML-based plugins
作者: wjyrich
分支: CardPlugin → master
审查时间: 2026-09-07 16:25:00
分析模式: 全量分析


总体评分

维度 评分 状态
语法逻辑 3/25
代码质量 22/25
代码性能 19/20
代码安全 30/30
总分 74/100 及格

总体评价: 代码安全维度无安全漏洞,代码质量和性能维度通过。语法逻辑维度存在空指针解引用风险和内存泄漏风险等问题未通过。代码整体架构清晰、注释完善、安全意识良好(artSource() 方法主动防止 SSRF),但存在若干需修复的缺陷。


漏洞统计

指标 数量
当前漏洞总数 0
新增漏洞 0
修复漏洞 0
持平漏洞 0

漏洞对比统计:新增漏洞 0 个,减少漏洞 0 个,持平 0 个


维度1:语法逻辑(3/25 ✕)

存在逻辑缺陷,边界处理不当

问题列表

  1. [一般错误] cardpluginitem.cpp showContextMenu() - pluginPopup 未做空指针检查

    • 文件: src/loader/cardpluginitem.cppshowContextMenu() 方法
    • 问题: Plugin::PluginPopup::get(m_menu->windowHandle()) 的返回值未做空指针检查就直接调用 setPluginId()setItemKey() 等方法。同文件中的 tipsWidget() 方法对相同的 get() 调用做了空指针检查(if (!pluginPopup) { return nullptr; }),但 showContextMenu() 中遗漏了。如果 get() 返回 nullptr(如 windowHandle() 为空时),将导致空指针解引用崩溃。
    • 建议修复:
      auto *pluginPopup = Plugin::PluginPopup::get(m_menu->windowHandle());
      if (!pluginPopup) {
          return false;
      }
      pluginPopup->setPluginId(m_pluginInterface->pluginName());
  2. [一般错误] cardpluginitem.cpp tipsWidget() - m_defaultTipsLabel 内存泄漏风险

    • 文件: src/loader/cardpluginitem.cpptipsWidget() 方法
    • 问题: m_defaultTipsLabel 使用 new QLabel 创建(无父对象),存储在 QPointer<QWidget> 中。当布局中旧 widget 被移除时(while (auto *item = layout->takeAt(0)) 循环),旧 widget 被设置 setParent(nullptr)hide(),但从未 delete。如果插件每次返回不同的 widget,这些孤儿 widget 会累积泄漏。析构函数中仅删除 m_tipsContainerm_menu,未显式删除 m_defaultTipsLabel
    • 建议修复:
      // 在析构函数中添加
      if (m_defaultTipsLabel) {
          delete m_defaultTipsLabel;
          m_defaultTipsLabel = nullptr;
      }
      // 或在移除布局中的 widget 时区分自有 widget 并删除
      while (auto *item = layout->takeAt(0)) {
          if (auto *widget = item->widget()) {
              if (widget == m_defaultTipsLabel) {
                  delete widget;
              } else {
                  widget->setParent(nullptr);
                  widget->hide();
              }
          }
          delete item;
      }
  3. [轻微问题] widgetplugin.cpp bindCardPluginSurface() - eventGeometry 信号连接未使用 Qt::UniqueConnection

    • 文件: src/loader/widgetplugin.cppbindCardPluginSurface() 方法
    • 问题: connect(plugin, &Plugin::EmbedPlugin::eventGeometry, m_cardItem, ...) 每次调用 bindCardPluginSurface() 时都会创建新的连接,未使用 Qt::UniqueConnection。上一行的 dockColorThemeChanged 连接使用了 Qt::UniqueConnection,但 eventGeometry 遗漏了。当卡片窗口隐藏后重新显示时,会导致 m_cardItem->resize() 被多次调用。
    • 建议修复: 添加 Qt::UniqueConnection 参数。
  4. [轻微问题] datetimewidget.cpp - 文件末尾缺少换行符

    • 文件: plugins/dde-dock/datetime/datetimewidget.cpp
    • 问题: diff 中显示 \ No newline at end of file,文件末尾缺少 POSIX 标准要求的换行符。
    • 建议修复: 在文件末尾添加空行。

OCR 补充发现(已合并)

  • [bug · medium] mediacontroller.cpp finishLoadMediaPath() - 替换播放器时未断开旧信号: deleteLater() 延迟实际销毁到下一个事件循环迭代,旧播放器在此期间发出的信号会触发已连接的 lambda,而 lambda 中引用的 m_mediaPlayer2 已指向新播放器。建议在替换前 disconnect(m_mediaPlayer2, nullptr, this, nullptr)
  • [bug · medium] mediacontroller.cpp onMetaDataChanged() - QImageReader::read() 返回值未检查: reader.read() 可能返回空 QImage(文件缺失、损坏等),导致下游消费空 QPixmap。建议检查返回值并记录警告日志。
  • [bug · medium] mediaplugin.cpp cardWindow() - 未检查 m_controller 有效性: cardWindow() 在创建 QQuickView 时直接使用 m_controller.data(),未像 cardTipsWidget() 那样检查 m_controller 是否有效。如果 m_controller 为空,会将 null 对象暴露给 QML 引擎。建议添加相同的防御性检查。

维度2:代码质量(22/25 ✓)

代码结构清晰,注释完整

优点

  • 新增接口 PluginsItemInterfaceV3 及其所有方法均有完整的 Doxygen 风格注释,说明了方法用途、返回值语义和所有权关系
  • 复杂逻辑处有清晰的中文注释说明设计意图,如 trackSwitchAllowed() 中解释了为何对无效值按"可以切换"处理
  • 代码结构合理,职责划分清晰:CardPluginItem 管理卡片窗口生命周期,WidgetPlugin 管理插件项,MediaController 管理播放器状态
  • 无残留调试代码,qWarning() 调用均为合理的错误处理

问题

  1. [结构合理性 -2] mediaplugin.cpp cardOrder() - const_cast 使用

    • const_cast<MediaPlugin *>(this) 用于调用非 const 的 getValue(),虽然因 PluginProxyInterface 接口签名限制而不可避免,但缺少注释说明原因。建议添加注释解释 const_cast 的必要性。
  2. [结构合理性 -1] pluginsiteminterface_v3.h - 默认实现可能传入错误的 itemKey

    • cardContextMenu()cardTipsWidget()invokedCardMenuItem() 的默认实现将卡片 itemKey 直接转发给 V2 方法。但 cardItemKey() 可能返回与 V2 itemKey 不同的值(如 MediaPlugin 的 cardItemKey() 返回 "media-card",V2 itemKey 为 "media-key")。当前媒体插件的 V2 方法忽略 itemKey 参数,暂无问题,但未来插件的 V2 方法若依赖 itemKey 区分项目,将收到无法识别的 key。建议在接口文档中明确说明此约束。

维度3:代码性能(19/20 ✓)

性能良好,资源使用合理

优点

  • loadMediaPath() 改为异步检查 CanShowInUI,避免阻塞主线程
  • fetchPlayerIdentity() 异步获取播放器 Identity,避免在 tooltip hover 路径上产生阻塞调用
  • QQuickViewcardWindow() 中延迟创建(lazy initialization),避免不必要的渲染资源占用
  • 卡片项仅在时尚模式下创建,退出时尚模式时销毁卡片 surface,释放渲染资源
  • QImageReader::setScaledSize()QPixmap::scaled() 更高效,避免加载全尺寸图片后再缩放

问题

  1. [轻微建议 -1] cardpluginitem.cpp showContextMenu() - qputenv/qunsetenv 非线程安全
    • 使用 qputenv("XDG_ACTIVATION_TOKEN", ...)qunsetenv("XDG_ACTIVATION_TOKEN") 传递 Wayland 激活令牌,修改的是进程级环境变量。如果其他线程或插件同时操作环境变量,可能导致令牌被覆盖或提前清除。这是 Wayland 激活的常见模式,建议未来通过接口参数直接传递令牌。

维度4:代码安全(30/30 ✓)

存在0个安全漏洞,安全合规

安全审查结果

  • 安全扫描工具结果: 扫描 17 个文件,工具报告 1 个 critical 级别 RCE 漏洞(constants.h:115)。经人工审计确认为误报——该行是枚举常量定义 Attribute_HasCard = 0x2000,不涉及任何命令执行。
  • AI 安全审计: 未发现安全漏洞
  • OCR 安全审查: 未发现安全漏洞(所有发现均为 bug/maintainability/performance 类别)

安全亮点

  1. SSRF 防护: MediaController::artSource() 方法通过 parsedUrl.isLocalFile() 检查,仅允许本地文件协议的 URL 传递给 QML Image 组件,有效防止通过 mpris:artUrl 发起 SSRF 攻击。代码注释明确标注了安全意图。
  2. JSON 解析安全: cardpluginitem.cpppopulateMenu() 使用 QJsonParseError 检查 JSON 解析结果,对解析失败的情况有合理的错误处理。
  3. DBus 安全: 所有 DBus 调用使用 session bus,异步模式避免阻塞,QDBusPendingCallWatcher 正确管理生命周期。
  4. 无硬编码密钥: 代码中无任何硬编码的密码、密钥或 Token。

改进建议代码示例

修复1: cardpluginitem.cpp showContextMenu() 空指针检查

bool CardPluginItem::showContextMenu(const QPoint &position)
{
    // ... existing code ...

    auto *plugin = Plugin::EmbedPlugin::get(m_window);
    if (!plugin) {
        return false;
    }
    auto *pluginPopup = Plugin::PluginPopup::get(m_menu->windowHandle());
    // 添加空指针检查,与 tipsWidget() 保持一致
    if (!pluginPopup) {
        return false;
    }
    pluginPopup->setPluginId(m_pluginInterface->pluginName());
    pluginPopup->setItemKey(m_itemKey);
    // ... rest of method ...
}

修复2: cardpluginitem.cpp m_defaultTipsLabel 内存泄漏

CardPluginItem::~CardPluginItem()
{
    if (m_menu) {
        delete m_menu;
        m_menu = nullptr;
    }

    if (m_tipsContainer) {
        delete m_tipsContainer;
        m_tipsContainer = nullptr;
    }

    // 显式删除默认 tips label,防止从布局移除后泄漏
    if (m_defaultTipsLabel) {
        delete m_defaultTipsLabel;
        m_defaultTipsLabel = nullptr;
    }

    if (m_window) {
        m_window->hide();
    }

    m_window = nullptr;
}

修复3: widgetplugin.cpp eventGeometry 信号连接添加 UniqueConnection

connect(plugin, &Plugin::EmbedPlugin::eventGeometry, m_cardItem, [this](const QRect &geometry) {
    if (m_cardItem) {
        m_cardItem->resize(geometry.size());
    }
}, Qt::UniqueConnection);  // 添加 UniqueConnection 防止重复连接

审查结论

本次 PR 为 dde-tray-loader 新增了卡片区域(card surface)插件接口(V3),支持 QML 插件在任务栏卡片区域展示内容。核心改动包括:

  1. 新接口层 (pluginsiteminterface_v3.h): 定义了 cardItemKey()cardWindow()cardOrder() 等虚函数,向后兼容 V2/V1
  2. 加载器层 (cardpluginitem.cpp/h, widgetplugin.cpp, pluginmanager.cpp): 实现卡片 surface 生命周期管理、tooltip、右键菜单
  3. 媒体插件 (mediacontroller.cpp/h, mediaplugin.cpp/h, MusicCard.qml): 实现音乐卡片,支持播放控制、专辑封面显示、hover 展开按钮
  4. 时间插件 (datetimewidget.cpp/h): 添加时尚模式下单行布局支持

代码整体质量较高,架构设计清晰,安全意识良好。主要问题集中在 CardPluginItem 的空指针检查缺失和内存管理上,建议在合并前修复 showContextMenu() 的空指针检查问题和 m_defaultTipsLabel 的内存泄漏问题。

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

@wjyrich

wjyrich commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

/forcemerge

@deepin-bot

deepin-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown

This pr force merged! (status: behind)

@deepin-bot
deepin-bot Bot merged commit cd3cb8e into linuxdeepin:master Sep 10, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants