Skip to content

fix: resolve popup cursor display incorrect as arrow when hovering links#444

Merged
yixinshark merged 1 commit intolinuxdeepin:masterfrom
yixinshark:fix-cursorShape
Mar 20, 2026
Merged

fix: resolve popup cursor display incorrect as arrow when hovering links#444
yixinshark merged 1 commit intolinuxdeepin:masterfrom
yixinshark:fix-cursorShape

Conversation

@yixinshark
Copy link
Contributor

@yixinshark yixinshark commented Mar 20, 2026

By default, Qt's QWidgetWindow forwards unhandled events (like CursorChange) to its top-level QWidget. If the global EventFilter monitors all widgets indiscriminately, it will intercept the forwarded event on the root widget and wrongly capture its ArrowCursor fallback value, thereby overwriting the correct PointingHand cursor globally.

Instead of evaluating the widget's personal cursor state when a CursorChange happens, we now read the globally computed cursor shape straight from the target native windowHandle, ensuring Wayland popups consistently reflect the correct pointer shape.

修复悬浮超链接时气泡游标异常显示为箭头的问题

Qt 内部窗口事件回传机制会导致系统原生的 QWindow 将自身游标变化转发给底下的
最顶层 QWidget 容器。因为原事件监听器无差别捕获所有由于转发而受到的游标改变
信号,而顶层容器并未独立设过游标(呈现默认箭头),导致在捕获后向 Wayland 误传
了箭头重置。

通过将取值对象从转发目标(Widget小组件自身)改为实际的载体原生窗口(WindowHandle), 彻底绕过组件树转发污染,使得弹窗在 Wayland 环境下可以随时反馈准确的游标状态。

Log: resolve popup cursor display incorrect as arrow when hovering links

Summary by Sourcery

Bug Fixes:

  • Fix popup cursors incorrectly reverting to the default arrow when hovering hyperlinks by basing the propagated cursor shape on the window handle instead of the widget.

@sourcery-ai
Copy link

sourcery-ai bot commented Mar 20, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Adjusts the global event filter’s cursor handling so that popup cursors are derived from the native window handle’s effective cursor shape rather than the forwarding QWidget, preventing Wayland popups from incorrectly resetting to Arrow when hovering links.

Sequence diagram for updated cursor handling on popup hover

sequenceDiagram
    actor User
    participant WaylandCompositor
    participant QWidgetWindow
    participant RootQWidget as RootQWidget
    participant EventFilter
    participant QWindowHandle as QWindowHandle
    participant PluginPopup

    User->>WaylandCompositor: Move pointer over hyperlink in popup
    WaylandCompositor->>QWidgetWindow: Pointer move
    QWidgetWindow->>RootQWidget: CursorChange event (forwarded)
    RootQWidget->>EventFilter: eventFilter(widget, CursorChange)
    EventFilter->>RootQWidget: widget->window()
    EventFilter->>QWindowHandle: window()->windowHandle()
    EventFilter->>QWindowHandle: cursor().shape()
    QWindowHandle-->>EventFilter: effective cursorShape (PointingHandCursor)
    EventFilter->>PluginPopup: requestSetCursor(cursorShape)
    PluginPopup->>WaylandCompositor: Update popup cursor
    WaylandCompositor-->>User: Pointer shown as PointingHandCursor
Loading

Class diagram for updated EventFilter cursor handling

classDiagram
    class EventFilter {
        +bool eventFilter(QObject* watched, QEvent* event)
        -void handleCursorChange(QWidget* widget)
    }

    class QWidget {
        +QWidget* window()
        +QCursor cursor()
    }

    class QWindow {
        +QCursor cursor()
        +QWindow* windowHandle()
    }

    class PluginPopup {
        +static PluginPopup* getWithoutCreating(QWindow* windowHandle)
        +void requestSetCursor(int cursorShape)
    }

    EventFilter ..> QWidget : observes
    QWidget ..> QWindow : window
    QWindow <.. PluginPopup : lookup_by_windowHandle
    EventFilter ..> PluginPopup : emits_requestSetCursor
    EventFilter ..> QWindow : reads_cursor_via_windowHandle
    QWidget ..> QWindow : windowHandle (via window()->windowHandle())
Loading

File-Level Changes

Change Details Files
Use the native windowHandle cursor instead of the widget cursor when propagating cursor changes to plugin popups.
  • Retrieve the Qt::CursorShape from windowHandle->cursor().shape() rather than widget->cursor().shape() in the global event filter
  • Keep the rest of the cursor-change handling and signal emission logic unchanged so only the cursor source is switched
src/loader/widgetplugin.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

@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 1 issue, and left some high level feedback:

  • In EventFilter::handleCursorChange, windowHandle is dereferenced without a null check; consider guarding against widget->window()->windowHandle() returning nullptr to avoid a potential crash in cases where the widget has not yet been backed by a native window.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `EventFilter::handleCursorChange`, `windowHandle` is dereferenced without a null check; consider guarding against `widget->window()->windowHandle()` returning `nullptr` to avoid a potential crash in cases where the widget has not yet been backed by a native window.

## Individual Comments

### Comment 1
<location path="src/loader/widgetplugin.cpp" line_range="82" />
<code_context>
         auto windowHandle = widget->window()->windowHandle();
         if (auto pluginPopup = Plugin::PluginPopup::getWithoutCreating(windowHandle)) {
-            Qt::CursorShape cursorShape = widget->cursor().shape();
+            Qt::CursorShape cursorShape = windowHandle->cursor().shape();
             Q_EMIT pluginPopup->requestSetCursor(static_cast<int>(cursorShape));
         }
</code_context>
<issue_to_address>
**issue (bug_risk):** Potential null dereference when accessing windowHandle->cursor().

Switching from `widget->cursor()` (safe for a non-null widget) to `windowHandle->cursor()` introduces a crash risk if `widget->window()->windowHandle()` returns `nullptr` (e.g., before the native window exists). Please add a null check before using `windowHandle->cursor().shape()`, and either fall back to `widget->cursor()` or return early when no handle is available.
</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.

18202781743
18202781743 previously approved these changes Mar 20, 2026
By default, Qt's QWidgetWindow forwards unhandled events (like CursorChange)
to its top-level QWidget. If the global EventFilter monitors all widgets indiscriminately,
it will intercept the forwarded event on the root widget and wrongly capture its ArrowCursor
fallback value, thereby overwriting the correct PointingHand cursor globally.

Instead of evaluating the widget's personal cursor state when a CursorChange happens,
we now read the globally computed cursor shape straight from the target native windowHandle,
ensuring Wayland popups consistently reflect the correct pointer shape.

修复悬浮超链接时气泡游标异常显示为箭头的问题

Qt 内部窗口事件回传机制会导致系统原生的 QWindow 将自身游标变化转发给底下的
最顶层 QWidget 容器。因为原事件监听器无差别捕获所有由于转发而受到的游标改变
信号,而顶层容器并未独立设过游标(呈现默认箭头),导致在捕获后向 Wayland 误传
了箭头重置。

通过将取值对象从转发目标(Widget小组件自身)改为实际的载体原生窗口(WindowHandle),
彻底绕过组件树转发污染,使得弹窗在 Wayland 环境下可以随时反馈准确的游标状态。

Log: resolve popup cursor display incorrect as arrow when hovering links
@deepin-ci-robot
Copy link

deepin pr auto review

这段代码的 diff 主要是修改了版权年份和一行获取光标形状的逻辑。下面我将从语法逻辑、代码质量、代码性能和代码安全四个方面进行审查:

1. 语法逻辑

  • 版权声明修改:将 2023 修改为 2023 - 2026,符合版权声明的常规做法,表示版权有效期或维护周期的延长,逻辑正确。
  • 光标获取逻辑
    • 原代码widget->cursor().shape()
    • 新代码windowHandle->cursor().shape()
    • 分析:这是一个逻辑上的变更。原代码从 widget 实例获取光标形状,而新代码从 windowHandle 获取。
    • 潜在问题:Qt 的 QWidget::cursor() 返回的是该部件上设置的鼠标光标形状。如果部件没有显式设置光标,它会继承父部件的光标。而 QWindow::cursor() 返回的是窗口级别的光标。
    • 逻辑一致性:上下文中代码获取了 windowHandle 并用于 PluginPopup 的判断,这表明后续的操作可能是针对整个窗口级别的。如果 requestSetCursor 的意图是设置整个窗口的光标,那么从 windowHandle 获取形状是合理的。但如果 EventFilter 是挂载在特定子部件上,且需要响应子部件特定的光标变化(例如文本输入框的 I 型光标),那么原代码(获取 widget 的光标)可能更准确。
    • 建议:请确认 requestSetCursor 的具体实现。如果它是设置窗口光标,则修改是正确的;如果它是试图同步特定控件的光标状态,则原代码可能更合适。

2. 代码质量

  • 可读性:代码使用了 auto 关键字,类型推断清晰,符合现代 C++ 风格。
  • 变量命名cursorShapewindowHandle 命名清晰,能够准确表达其用途。
  • 注释:该段代码缺少注释解释为什么要从 windowHandle 获取光标而不是 widget。由于这是一个逻辑变更,建议添加注释说明原因,例如:"使用窗口级别的光标形状以保持与 requestSetCursor 作用域一致"。

3. 代码性能

  • 性能影响windowHandle->cursor()widget->cursor() 的调用开销都非常小,几乎可以忽略不计。这次修改对性能没有显著影响。

4. 代码安全

  • 空指针检查
    • 代码中已经检查了 windowHandle 的有效性(通过 getWithoutCreating 返回值判断)。
    • widget->window() 通常会返回有效的顶层窗口,但理论上 window() 可能返回 nullptr(如果 widget 尚未被插入到窗口树中)。不过,考虑到这是在事件过滤器中,通常 widget 已经构建完成,风险较低。
    • 建议:为了代码的健壮性,可以考虑增加对 windowHandle 的显式非空检查,尽管 getWithoutCreating 内部可能已经处理了。
  • 类型转换static_cast<int>(cursorShape) 将枚举类型转换为 int。这在 Qt 的信号槽机制中传递枚举时是常见的做法,通常是安全的,前提是接收端能正确处理该整数值。

总结与改进建议

这次修改主要是逻辑上的调整,语法没有问题。主要关注点在于光标获取对象的变更。

改进建议:

  1. 添加注释:建议在修改处添加注释,解释为什么从 windowHandle 获取光标,以便后续维护者理解。

    // 获取窗口级别的光标形状,确保与 requestSetCursor 的作用域一致
    Qt::CursorShape cursorShape = windowHandle->cursor().shape();
  2. 确认业务逻辑:请务必确认业务需求。如果 EventFilter 是为了处理特定子控件(如按钮、输入框)的光标事件,原代码 widget->cursor() 可能更符合预期。如果是为了统一窗口光标,新代码是正确的。

  3. 空指针保护(可选):虽然当前逻辑下风险较低,但可以增加防御性编程:

    if (windowHandle && widget) {
        // ... existing logic
    }
  4. 版权年份:确保 2026 年是预期的结束年份,或者如果项目是持续维护的,通常使用 2023 - present 或仅保留起始年份 2023 也是常见的做法(取决于公司政策)。这里使用具体年份可能是为了配合特定的发布周期。

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

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

@yixinshark yixinshark merged commit 892f8bc into linuxdeepin:master Mar 20, 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