Skip to content

fix: filter synthesized click after wheel/gesture to prevent trackpad flicker - #180

Open
Resurgamz wants to merge 1 commit into
linuxdeepin:develop/eaglefrom
Resurgamz:develop/eagle
Open

fix: filter synthesized click after wheel/gesture to prevent trackpad flicker#180
Resurgamz wants to merge 1 commit into
linuxdeepin:develop/eaglefrom
Resurgamz:develop/eagle

Conversation

@Resurgamz

@Resurgamz Resurgamz commented Aug 13, 2026

Copy link
Copy Markdown

根因分析

PMS #279597 看图触摸板双指缩放闪烁:LibImageGraphicsView::mousePressEventimagegraphicsview.cpp:1063)对任意按下无条件 emit clicked()(L1074),不区分 MouseEventSynthesizedByQt 合成事件、不对滚轮/手势去抖。触摸板双指缩放后手指离开,因 event()(L1157-1208)处理 TouchBegin/Update/End 后未 return true/未 accept,Qt 默认将残余触摸合成为鼠标 press,命中 mousePressEvent 无条件 emit clicked(),触发 viewpanel.cpp:451-454 的 overlay(扩展面板/信息面板)反复显隐 → 图片闪烁。同文件 mouseReleaseEvent:1029/1049 既有 source 过滤 + 200ms 去抖,press 侧缺失,两者不对称。开发者自留注释 viewpanel.cpp:277「二指放大会触发信号,导致窗口隐藏,这里下面存在问题」直接印证。

关于"还原默认大小":经根因复核确认,setScaleValue(v)相对缩放m_scal *= scaleFactor; scale(...)),传入 1.0 为视觉 no-op 且不调 resetTransform(),故 resizeEvent → setScaleValue(1.0) 不构成"还原默认大小"机制;clicked/sigClicked/doubleClicked 信号链亦不触达 fitWindow/fitImage/autoFit/resetTransform。用户所述"还原默认大小"为闪烁(overlay/工具栏反复显隐)在已放大图片上的下游视觉观感,过滤合成点击后应随之消失(待硬件复测确认)。

关键证据:

  1. mousePressEvent:1074 无条件 emit clicked()(与 mouseReleaseEvent:1029/1049 的 source 过滤 + 200ms 去抖 + 位移阈值不对称)
  2. event():1207 未接纳触摸事件致 Qt 合成鼠标事件
  3. 症状不对称性自洽:合成点击对放大/缩小对称触发,仅放大时 overlay 显隐在已放大图片上肉眼可见

修复方案

press/release 两侧对"滚轮/缩放手势结束后 300ms 内的合成点击"去抖过滤,与既有 mouseReleaseEvent 过滤模式同构:

  1. 新增成员 m_lastWheelOrGestureTime,在 wheelEvent / pinchTriggered GestureFinished 中记录时间戳
  2. mousePressEvent 中若 e->source() == Qt::MouseEventSynthesizedByQt 且 300ms 内 → e->accept(); return;,过滤合成点击
  3. mouseReleaseEvent 合成分支的 sigClicked() 发射增加同样的时间窗判断(防御纵深)

仅过滤"滚轮/缩放手势结束后短时间窗内的合成点击",真实鼠标点击(source 非 synthesized)、真实单指触摸翻页(位移>200 且非紧随手势)不受影响。

改动安全评估

低风险:局部 early-return + 时间戳记录,无函数签名变更、无公开 API 变更。仅影响合成事件路径。blame 确认 emit clicked() 来自 first commit(非历史 bug 修复),无回归风险。

附带改动

同步将两个修改文件的 SPDX-FileCopyrightText 年份范围结束年份更新为 2026(2020 - 2023/20222020 - 2026),以满足仓库 Check-SPDX-Copyright CI 校验(文件在 2026 年被修改,年份范围需反映最新修改时间)。仅版权头注释,不涉及任何代码逻辑。

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

Sorry @Resurgamz, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Resurgamz

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 Aug 13, 2026

Copy link
Copy Markdown
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Debounces and filters synthesized mouse clicks generated shortly after wheel or pinch gestures in LibImageGraphicsView by tracking the last wheel/gesture timestamp and applying it symmetrically to mouse press/release handling, preventing unintended view mode toggles on trackpad zoom.

Sequence diagram for debounced synthesized click after wheel/pinch gestures

sequenceDiagram
    actor User
    participant QtEventSystem
    participant LibImageGraphicsView

    User->>QtEventSystem: trackpad pinch / wheel
    QtEventSystem->>LibImageGraphicsView: wheelEvent(event)
    LibImageGraphicsView->>LibImageGraphicsView: m_lastWheelOrGestureTime = currentMSecsSinceEpoch()

    User->>QtEventSystem: end gesture
    QtEventSystem->>LibImageGraphicsView: pinchTriggered(gesture)
    LibImageGraphicsView->>LibImageGraphicsView: m_lastWheelOrGestureTime = currentMSecsSinceEpoch() (GestureFinished)

    QtEventSystem->>LibImageGraphicsView: mousePressEvent(e)
    alt synthesized mouse event within 300ms
        LibImageGraphicsView->>LibImageGraphicsView: [e->source() == MouseEventSynthesizedByQt && now - m_lastWheelOrGestureTime < 300]
        LibImageGraphicsView-->>QtEventSystem: e->accept(), return
    else real or delayed click
        LibImageGraphicsView->>LibImageGraphicsView: normal press handling
    end

    QtEventSystem->>LibImageGraphicsView: mouseReleaseEvent(e)
    alt click within 200ms and small movement and not near gesture
        LibImageGraphicsView->>LibImageGraphicsView: [now - m_clickTime < 200 && abs(xpos) < 50 && now - m_lastWheelOrGestureTime >= 300]
        LibImageGraphicsView->>QtEventSystem: emit sigClicked()
    else ignore as gesture artifact
        LibImageGraphicsView->>LibImageGraphicsView: no sigClicked()
    end
Loading

File-Level Changes

Change Details Files
Introduce a shared timestamp for the last wheel or pinch gesture and use it to ignore synthesized clicks that occur shortly after zoom interactions.
  • Add member m_lastWheelOrGestureTime to LibImageGraphicsView to store the timestamp of the last wheel or pinch gesture.
  • Update wheelEvent to record the current time in m_lastWheelOrGestureTime whenever a wheel event occurs.
  • Update pinchTriggered to record the current time in m_lastWheelOrGestureTime when a pinch gesture reaches GestureFinished state.
libimageviewer/viewpanel/scen/imagegraphicsview.h
libimageviewer/viewpanel/scen/imagegraphicsview.cpp
Extend mouse press/release click detection logic to debounce synthesized clicks within 300ms of the last wheel or pinch gesture.
  • Add an early-return in mousePressEvent that accepts and discards MouseEventSynthesizedByQt events occurring less than 300ms after m_lastWheelOrGestureTime.
  • Tighten the existing click-detection condition in mouseReleaseEvent by requiring that at least 300ms have passed since m_lastWheelOrGestureTime before emitting sigClicked().
libimageviewer/viewpanel/scen/imagegraphicsview.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

… flicker

1. 新增成员 m_lastWheelOrGestureTime 记录最近一次滚轮/缩放手势时间戳;
2. 在 wheelEvent 和 pinchTriggered GestureFinished 中记录时间戳;
3. mousePressEvent 中过滤手势结束后 300ms 内的合成点击,避免误触发查看模式切换;
4. mouseReleaseEvent 合成分支的 sigClicked 发射增加同样的时间窗判断;

=====================================

1. added member m_lastWheelOrGestureTime to record last wheel/gesture timestamp;
2. recorded timestamp in wheelEvent and pinchTriggered GestureFinished;
3. filtered synthesized click within 300ms after gesture in mousePressEvent to avoid spurious view mode toggle;
4. added same time window check for sigClicked emission in mouseReleaseEvent synthesized branch;

Log: 修复触摸板双指缩放后手指离开合成点击导致图片闪烁还原默认大小的问题,过滤手势结束后的合成点击

PMS: BUG-279597
Bug: https://pms.uniontech.com/bug-view-279597.html
@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

★ 总体评分:95分

■ 【总体评价】

代码正确实现了触摸板手势合成点击的过滤逻辑,但存在魔法数字和轻微代码重复
逻辑严密且无安全风险,因硬编码常量和重复调用扣5分

■ 【详细分析】

  • 1.语法逻辑(完全正确)✓
    代码在LibImageGraphicsView::mousePressEvent中前置拦截合成点击,在LibImageGraphicsView::mouseReleaseEvent中增加时间差判定,并在LibImageGraphicsView::wheelEvent和LibImageGraphicsView::pinchTriggered中正确更新时间戳。成员变量m_lastWheelOrGestureTime初始化为0,确保了在首次触发时currentMSecsSinceEpoch()减去0必然大于300,不会产生误拦截。

  • 2.代码质量(一般)✕
    代码中存在多处硬编码的魔法数字,如300ms、200ms和50px,降低了代码的可维护性。此外,在LibImageGraphicsView::mouseReleaseEvent的条件判断中,QDateTime::currentMSecsSinceEpoch()被连续调用了两次,存在轻微的代码冗余。
    潜在问题:不同场景下可能需要调整手势过滤的阈值,硬编码导致修改不便;短时间内多次获取系统时间虽然开销极小但不够优雅。
    建议:将300、200、50等阈值提取为类的静态常量或宏定义;在mouseReleaseEvent开头缓存当前时间戳变量供后续逻辑复用。

  • 3.代码性能(无性能问题)✓
    虽然在事件处理函数中调用了QDateTime::currentMSecsSinceEpoch()系统调用,但GUI鼠标和滚轮事件的触发频率受限于操作系统的轮询率,这点微小的系统调用开销完全可以忽略不计,不会引起任何性能瓶颈。

  • 4.代码安全(存在0个安全漏洞)✓
    漏洞对比统计:新增漏洞 0 个,减少漏洞 0 个,持平 0 个
    本次修改仅涉及GUI事件的过滤与时间戳比对,不涉及内存操作、外部输入解析、命令执行或权限变更,无任何安全风险。

  • 建议:保持现有的安全编码实践。

■ 【改进建议代码示例】

// imagegraphicsview.h
private:
    // 单击判定阈值(毫秒)
    static constexpr qint64 CLICK_THRESHOLD_MS = 200;
    // 移动距离判定阈值(像素)
    static constexpr int MOVE_THRESHOLD_PX = 50;
    // 手势合成点击过滤阈值(毫秒)
    static constexpr qint64 GESTURE_FILTER_THRESHOLD_MS = 300;

    qint64 m_clickTime{0};
    //最近一次滚轮/缩放手势的时间戳,用于过滤手势结束后合成的点击
    qint64 m_lastWheelOrGestureTime{0};
// imagegraphicsview.cpp
void LibImageGraphicsView::mouseReleaseEvent(QMouseEvent *e)
{
    // ... 前置逻辑 ...
    
    const qint64 currentTime = QDateTime::currentMSecsSinceEpoch();
    if ((currentTime - m_clickTime) < CLICK_THRESHOLD_MS && abs(xpos) < MOVE_THRESHOLD_PX &&
            (currentTime - m_lastWheelOrGestureTime) >= GESTURE_FILTER_THRESHOLD_MS) {
        m_clickTime = currentTime;
        emit sigClicked();
    }
    // ...
}

void LibImageGraphicsView::mousePressEvent(QMouseEvent *e)
{
    const qint64 currentTime = QDateTime::currentMSecsSinceEpoch();
    // 过滤触摸板/触摸屏缩放手势结束后短时间内的合成点击,避免误触发查看模式切换
    if (e->source() == Qt::MouseEventSynthesizedByQt &&
            (currentTime - m_lastWheelOrGestureTime) < GESTURE_FILTER_THRESHOLD_MS) {
        e->accept();
        return;
    }
    // ...
}

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.

2 participants