Skip to content

fix: disable menu scroll indicator in application tray plugin#451

Merged
18202781743 merged 1 commit into
linuxdeepin:masterfrom
18202781743:master
Apr 23, 2026
Merged

fix: disable menu scroll indicator in application tray plugin#451
18202781743 merged 1 commit into
linuxdeepin:masterfrom
18202781743:master

Conversation

@18202781743
Copy link
Copy Markdown
Contributor

@18202781743 18202781743 commented Apr 22, 2026

  1. Removed scroll indicator functionality in QMenu submenus to prevent
    display issues
  2. Added private Qt headers to access QMenuPrivate and disable scrolling
  3. Adjusted window positioning logic for popup sub-windows

Log: Disabled menu scroll indicator in application tray to fix display
issues

Influence:

  1. Test application tray menu display without scroll indicators
  2. Verify sub-menu opening behavior and positioning
  3. Test popup window positioning for wayland integration
  4. Verify menu size and layout correctness
  5. Test on different screen resolutions and scaling factors

fix: 禁止托盘菜单滚动指示器功能

  1. 移除 QMenu 子菜单的滚动指示器功能,解决显示问题
  2. 添加 Qt 私有头文件以访问 QMenuPrivate 并禁用滚动
  3. 调整弹出子窗口的定位逻辑

Log: 禁用托盘菜单滚动指示器以修复显示问题

Influence:

  1. 测试托盘菜单显示,确认无滚动指示器
  2. 验证子菜单打开行为和定位
  3. 测试 wayland 集成下的弹出窗口定位
  4. 验证菜单尺寸和布局正确性
  5. 在不同屏幕分辨率和缩放比例下测试

PMS: BUG-333243

Summary by Sourcery

Disable scroll indicators in application tray menus and adjust popup positioning for Wayland tray integration.

Bug Fixes:

  • Remove QMenu submenu scrolling to prevent initial display issues with scroll indicators in the application tray menu.
  • Correct popup sub-window vertical positioning in the Wayland tray integration to align with the parent window.

Build:

  • Link the application tray plugin against Qt CorePrivate and WidgetsPrivate to support use of private Qt menu internals.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 22, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Disables QMenu scroll indicators for application tray submenus using Qt private APIs and adjusts Wayland plugin popup positioning, including linking against Qt private libraries needed for the new behavior.

Sequence diagram for menuUpdated handling and scroll indicator disabling

sequenceDiagram
    participant DBusMenu
    participant DBusMenuImporter
    participant QMenu
    participant QObjectPrivate
    participant QMenuPrivate
    participant QMenuScroller

    DBusMenuImporter->>DBusMenu: menuUpdated(menu : QMenu*)
    activate DBusMenu
    DBusMenu->>QMenu: sizeHint()
    QMenu-->>DBusMenu: size : QSize
    DBusMenu->>QMenu: setFixedSize(size)

    DBusMenu->>QObjectPrivate: get(menu)
    QObjectPrivate-->>DBusMenu: menuPrivate : QMenuPrivate*
    DBusMenu->>QMenuPrivate: access scroll
    QMenuPrivate->>QMenuScroller: set scrollFlags = ScrollNone
    deactivate DBusMenu
Loading

Sequence diagram for Wayland sub-window popup positioning

sequenceDiagram
    actor User
    participant TrayApplication
    participant PluginManagerIntegration
    participant ParentWindow as QWindow_parent
    participant SubWindow as QWindow_sub
    participant PluginPopup

    User->>TrayApplication: open sub-menu or popup
    TrayApplication->>PluginManagerIntegration: tryCreatePopupForSubWindow(SubWindow)
    activate PluginManagerIntegration

    PluginManagerIntegration->>ParentWindow: x(), width()
    ParentWindow-->>PluginManagerIntegration: parentX, parentWidth
    PluginManagerIntegration->>ParentWindow: y()
    ParentWindow-->>PluginManagerIntegration: parentY
    PluginManagerIntegration->>SubWindow: y()
    SubWindow-->>PluginManagerIntegration: subY

    PluginManagerIntegration->>PluginPopup: setX(parentX + parentWidth)
    PluginManagerIntegration->>PluginPopup: setY(parentY + subY)
    PluginManagerIntegration->>SubWindow: setY(parentY + subY)

    PluginManagerIntegration-->>TrayApplication: true
    deactivate PluginManagerIntegration
Loading

Class diagram for DBusMenu scroll disabling and popup positioning

classDiagram
    class DBusMenuImporter {
        <<QtClass>>
        +signal menuUpdated(menu : QMenu*)
    }

    class DBusMenu {
        +DBusMenu(parent : QObject*)
        +createMenu(parent : QWidget*) QMenu*
    }

    class QMenu {
        +sizeHint() QSize
        +setFixedSize(size : QSize) void
    }

    class QObjectPrivate {
        +static get(object : QObject*) QObjectPrivate*
    }

    class QMenuPrivate {
        +scroll : QMenuScroller*
    }

    class QMenuScroller {
        <<enum-like>>
        +scrollFlags : int
        +ScrollNone : int
    }

    class PluginManagerIntegration {
        +tryCreatePopupForSubWindow(window : QWindow*) bool
    }

    class QWindow {
        +x() int
        +y() int
        +setX(x : int) void
        +setY(y : int) void
        +width() int
    }

    class PluginPopup {
        +setX(x : int) void
        +setY(y : int) void
    }

    DBusMenuImporter <|-- DBusMenu
    DBusMenu ..> QMenu : uses
    DBusMenu ..> QObjectPrivate : uses
    QObjectPrivate <|.. QMenuPrivate
    QMenuPrivate o-- QMenuScroller
    PluginManagerIntegration ..> QWindow : positions
    PluginManagerIntegration ..> PluginPopup : positions
Loading

File-Level Changes

Change Details Files
Disable QMenu submenu scroll indicators for tray menus using Qt private internals.
  • Connect DBusMenuImporter::menuUpdated to set QMenu fixed size from sizeHint without using a queued connection
  • Access QMenuPrivate via QObjectPrivate::get to modify the internal scroller state
  • Set QMenuPrivate::QMenuScroller::scrollFlags to ScrollNone to turn off scrolling behavior when menus are created/updated
plugins/application-tray/sniprotocolhandler.cpp
Adjust Wayland tray popup positioning for sub-windows.
  • Extend plugin popup window positioning to also update the child QWindow's y coordinate relative to the parent window position
  • Keep existing logic that positions the plugin popup to the right of the parent window
src/tray-wayland-integration/pluginmanagerintegration.cpp
Link application tray plugin against Qt private libraries required for QMenuPrivate access and update copyright years.
  • Add Qt CorePrivate and WidgetsPrivate to the tray plugin target_link_libraries so private headers used at compile time are backed by the correct libraries
  • Update SPDX copyright header years from 2023 to 2023 - 2026
plugins/application-tray/CMakeLists.txt
src/tray-wayland-integration/pluginmanagerintegration.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

Copy link
Copy Markdown

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

  • Using Qt private headers and QMenuPrivate ties this plugin to specific Qt internals; consider isolating this logic behind a small helper with clear comments about the Qt version assumptions and expected failure mode if the private API changes.
  • The change to the menuUpdated connection removed Qt::QueuedConnection, which may alter the timing and reentrancy of menu size/scroll updates; double-check whether a queued connection is still needed to avoid layout or signal recursion issues.
  • In tryCreatePopupForSubWindow, both pluginPopup and window now have their Y position adjusted using parentPos.y() + window->y(); verify that updating the child window’s Y is actually required and doesn’t cause double application of the offset in some environments.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Using Qt private headers and `QMenuPrivate` ties this plugin to specific Qt internals; consider isolating this logic behind a small helper with clear comments about the Qt version assumptions and expected failure mode if the private API changes.
- The change to the `menuUpdated` connection removed `Qt::QueuedConnection`, which may alter the timing and reentrancy of menu size/scroll updates; double-check whether a queued connection is still needed to avoid layout or signal recursion issues.
- In `tryCreatePopupForSubWindow`, both `pluginPopup` and `window` now have their Y position adjusted using `parentPos.y() + window->y()`; verify that updating the child window’s Y is actually required and doesn’t cause double application of the offset in some environments.

## Individual Comments

### Comment 1
<location path="plugins/application-tray/sniprotocolhandler.cpp" line_range="38-39" />
<code_context>
             menu->setFixedSize(menu->sizeHint());
-        }, Qt::QueuedConnection);
+            // TODO 删除 QMenu 的滚动功能,修复首次子菜单显示滚动指示器问题
+            if (auto dp = static_cast<QMenuPrivate*>(QObjectPrivate::get(menu))) {
+                dp->scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone;
+            }
+        });
</code_context>
<issue_to_address>
**issue (bug_risk):** Potential null dereference when accessing `dp->scroll`.

`dp->scroll` may still be null even when `QObjectPrivate::get(menu)` succeeds, depending on how the menu is constructed/used. Please either guard this access (e.g. `if (dp->scroll)`) or confirm from Qt sources that `scroll` is guaranteed non-null here before dereferencing it.
</issue_to_address>

### Comment 2
<location path="plugins/application-tray/sniprotocolhandler.cpp" line_range="41" />
<code_context>
+            if (auto dp = static_cast<QMenuPrivate*>(QObjectPrivate::get(menu))) {
+                dp->scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone;
+            }
+        });
     }
     virtual QMenu *createMenu(QWidget *parent) override
</code_context>
<issue_to_address>
**question (bug_risk):** Changing from `Qt::QueuedConnection` to default connection might introduce reentrancy or threading issues.

If `menuUpdated` can be emitted from a non-GUI thread or in reentrancy-sensitive paths, this change may alter when and where `setFixedSize` and the new private API calls run. Consider keeping the queued connection unless there’s a concrete need for synchronous execution, and verify the signal’s threading and reentrancy constraints before changing it.
</issue_to_address>

### Comment 3
<location path="src/tray-wayland-integration/pluginmanagerintegration.cpp" line_range="141" />
<code_context>
         // TODO move to parentWindow's right position.
         pluginPopup->setX(parentPos.x() + parentWindow->width());
         pluginPopup->setY(parentPos.y() + window->y());
+        window->setY(parentPos.y() + window->y());
         return true;
     }
</code_context>
<issue_to_address>
**issue (bug_risk):** Adjusting the subwindow’s Y coordinate like this may double-apply the parent offset or introduce unintended side effects.

For child windows, `window->y()` is usually parent-relative while `parentPos` is global. Adding `parentPos.y()` to `window->y()` and then writing it back to `window` likely applies the parent offset twice. Also, having `tryCreatePopupForSubWindow` mutate the window’s geometry may be unexpected to callers. Please double-check the coordinate spaces and whether only `pluginPopup` should be adjusted here, potentially using `mapToGlobal`/`mapFromGlobal` to avoid double offsets.
</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 plugins/application-tray/sniprotocolhandler.cpp Outdated
Comment thread plugins/application-tray/sniprotocolhandler.cpp Outdated
Comment thread src/tray-wayland-integration/pluginmanagerintegration.cpp Outdated
1. Removed scroll indicator functionality in QMenu submenus to prevent
display issues
2. Added private Qt headers to access QMenuPrivate and disable scrolling
3. Adjusted window positioning logic for popup sub-windows

Log: Disabled menu scroll indicator in application tray to fix display
issues

Influence:
1. Test application tray menu display without scroll indicators
2. Verify sub-menu opening behavior and positioning
3. Test popup window positioning for wayland integration
4. Verify menu size and layout correctness
5. Test on different screen resolutions and scaling factors

fix: 禁止托盘菜单滚动指示器功能

1. 移除 QMenu 子菜单的滚动指示器功能,解决显示问题
2. 添加 Qt 私有头文件以访问 QMenuPrivate 并禁用滚动
3. 调整弹出子窗口的定位逻辑

Log: 禁用托盘菜单滚动指示器以修复显示问题

Influence:
1. 测试托盘菜单显示,确认无滚动指示器
2. 验证子菜单打开行为和定位
3. 测试 wayland 集成下的弹出窗口定位
4. 验证菜单尺寸和布局正确性
5. 在不同屏幕分辨率和缩放比例下测试

PMS: BUG-333243
@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

这段代码的修改主要是为了解决 QMenu 中子菜单显示时出现滚动指示器的问题。以下是对该代码的审查意见,涵盖语法逻辑、代码质量、代码性能和代码安全四个方面:

1. 语法逻辑

  • 私有 API 使用正确性:代码使用了 QObjectPrivate::get(menu) 获取 QMenu 的私有类指针 QMenuPrivate,并访问了其成员 scrollscrollFlags。从 Qt 源码的角度看,这种访问方式是正确的,能够达到修改内部行为的目的。
  • 类型转换static_cast<QMenuPrivate*> 在这里类型转换是安全的,前提是 menu 确实是一个 QMenu 对象,而 menuUpdated 信号传递的参数确实保证了这一点。
  • 逻辑有效性:将 scrollFlags 设置为 ScrollNone 在逻辑上确实能够禁用菜单的滚动功能,符合注释中的预期。

2. 代码质量

  • TODO 注释:代码中包含 // TODO 删除 QMenu 的滚动功能...。这表明这是一个临时的解决方案或者为了修复特定 Bug 而引入的变通方案。长期来看,依赖私有 API 并不是最佳实践,建议后续寻找官方 API 支持或者重构相关逻辑。
  • 代码可读性:代码逻辑清晰,但直接操作私有对象(Private Class)对于不熟悉 Qt 内部机制的开发者来说可能难以理解。建议保留详细的注释,解释为什么要修改这些私有标志,以及可能带来的副作用(例如菜单过长时无法滚动)。
  • 头文件包含:引入了 <private/qobject_p.h><private/qmenu_p.h>。这是使用 Qt 私有 API 的必要条件,但同时也增加了对 Qt 内部头文件的依赖。

3. 代码性能

  • 性能影响:这段代码位于 menuUpdated 信号的槽函数中,每次菜单更新时执行。虽然涉及指针转换和简单的赋值操作,开销极小,对性能影响可以忽略不计。
  • Qt::QueuedConnection:使用了 Qt::QueuedConnection,这是好的做法,确保了 UI 更新操作在事件循环中进行,避免了潜在的递归调用或重绘问题。

4. 代码安全与维护性 (重点)

  • 私有 API 依赖风险 (高危)
    • 版本兼容性:这是最严重的问题。Qt${QT_VERSION_MAJOR}::CorePrivateQt${QT_VERSION_MAJOR}::WidgetsPrivate 是 Qt 的私有模块。Qt 官方明确声明,私有 API 在不同版本之间(甚至是小版本更新或补丁更新)可能会发生变化,且不保证二进制兼容性。
    • 后果:如果项目升级 Qt 版本(例如从 Qt 5.15 升级到 Qt 6.0,或者 5.15.x 的某个补丁版本),QMenuPrivate 的结构可能会改变,或者 scroll 成员变量被移除/重命名。这将导致编译错误,或者在运行时因为内存布局错位导致程序崩溃。
    • CMakeLists.txt 修改:在 target_link_libraries 中添加 Private 库是正确的做法,但这进一步锁定了项目与特定 Qt 版本的绑定,增加了迁移成本。

改进建议

  1. 寻找替代方案

    • 检查是否可以通过设置 QMenu 的样式(QSS)来隐藏滚动指示器,或者通过 QAction 的数量控制来避免滚动。
    • 尝试通过事件过滤器(eventFilter)拦截绘制事件(QEvent::Paint)来阻止滚动条的绘制,虽然这比较 Hack,但比直接访问内存布局更安全一些。
  2. 加强版本检查

    • 如果必须使用私有 API,建议在代码中增加 QT_VERSION 检查。
    • 例如:#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) ... #endif
    • 确保只在已知该私有 API 结构稳定的特定 Qt 版本范围内编译这段代码。
  3. 运行时安全检查

    • 虽然使用了 static_cast,但在访问 dp->scroll 之前,最好确认 dp 指针有效,且 scroll 成员存在(虽然 C++ 编译期很难检查成员是否存在,但可以通过宏定义控制不同版本的逻辑)。
  4. 文档化

    • 在项目文档中明确记录该插件对 Qt 私有 API 的依赖,并注明该代码适用的 Qt 具体版本号。

总结

这段代码在功能上是有效的,能够解决当前的问题。但是,由于它严重依赖 Qt 的私有 API,存在极高的维护风险版本兼容性风险

建议:仅在确认当前 Qt 版本长期稳定不变,且无法通过公共 API 实现需求时使用此方案。同时,务必添加版本宏保护,并密切关注 Qt 版本升级时的代码适配工作。

@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

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

@18202781743 18202781743 merged commit d131a76 into linuxdeepin:master Apr 23, 2026
11 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.

3 participants