Skip to content

fix: resolve touch screen scrolling and clicking issues in bluetooth …#437

Merged
yixinshark merged 1 commit intolinuxdeepin:masterfrom
yixinshark:fix-bluetoothTouchScreen
Mar 10, 2026
Merged

fix: resolve touch screen scrolling and clicking issues in bluetooth …#437
yixinshark merged 1 commit intolinuxdeepin:masterfrom
yixinshark:fix-bluetoothTouchScreen

Conversation

@yixinshark
Copy link
Contributor

@yixinshark yixinshark commented Mar 10, 2026

…applet

  • Issue background and cause: Touch screen operations such as scrolling and clicking did not work correctly with the native QScroller.

  • Specific changes: Replaced QScroller with a custom TouchScrollFilter for the bluetooth applet UI scroll area.

  • Technical details: Added TouchScrollFilter intercepting QEvent::TouchUpdate to offset the scroll area manually based on global coordinates differences, and on un-dragged QEvent::TouchEnd, simulating a click using QMouseEvent via core app dispatcher.

  • 问题背景和原因:原生的 QScroller 导致蓝牙插件面板的触屏滚动和无拖拽点击操作无法正常工作。

  • 具体修改内容:移除了原有蓝牙插件面板滚动区域的 QScroller 实现,替换为系统性的 TouchScrollFilter 组件。

  • 修改的技术细节:通过事件过滤器分析触控变化,基于全屏坐标增量手动控制滚动条数值实现触屏滚动。对于无明显拖拽的触摸抬起事件,通过查询子节点来直接模拟分发鼠标点击事件,从而保证点击的精准响应。

Log: fix: resolve touch screen scrolling and clicking issues in bluetooth applet
Pms: BUG-352505

Summary by Sourcery

Replace the bluetooth applet scroll handling with a custom touch event filter to fix touch scrolling and tapping behavior in the scroll area.

Bug Fixes:

  • Fix touch screen scrolling and tap/click recognition in the bluetooth applet scroll area.

Enhancements:

  • Introduce a reusable TouchScrollFilter utility for touch-driven scrolling and click simulation in generic QAbstractScrollArea-based views.

@sourcery-ai
Copy link

sourcery-ai bot commented Mar 10, 2026

Reviewer's Guide

Replaces the Bluetooth applet’s QScroller-based touch handling with a reusable TouchScrollFilter that manually scrolls on touch-drag and synthesizes mouse clicks on tap, improving touch scrolling and tap behavior in the scroll area.

Sequence diagram for touch scroll and tap handling via TouchScrollFilter

sequenceDiagram
    actor User
    participant TouchScreen
    participant Viewport as ScrollAreaViewport
    participant Filter as TouchScrollFilter
    participant Area as QAbstractScrollArea
    participant VBar as VerticalScrollBar
    participant HBar as HorizontalScrollBar
    participant Target as TargetWidget

    User->>TouchScreen: Finger down
    TouchScreen->>Viewport: QEvent TouchBegin
    Viewport->>Filter: eventFilter(TouchBegin)
    activate Filter
    Filter->>Filter: store m_lastPos
    deactivate Filter

    loop drag to scroll
        User->>TouchScreen: Move finger
        TouchScreen->>Viewport: QEvent TouchUpdate
        Viewport->>Filter: eventFilter(TouchUpdate)
        activate Filter
        Filter->>Filter: compute deltaX, deltaY
        alt exceeds startDragDistance
            Filter->>Filter: set m_isDrag = true
        end
        alt m_isDrag true
            opt vertical scroll
                Filter->>VBar: setValue(value - deltaY)
            end
            opt horizontal scroll
                Filter->>HBar: setValue(value - deltaX)
            end
            Filter->>Filter: update m_lastPos
        end
        deactivate Filter
    end

    User->>TouchScreen: Finger up
    TouchScreen->>Viewport: QEvent TouchEnd
    Viewport->>Filter: eventFilter(TouchEnd)
    activate Filter
    alt m_isDrag true
        Filter->>Filter: end gesture, no click
    else m_isDrag false
        Filter->>Viewport: childAt(touchPos)
        Viewport-->>Filter: TargetWidget or viewport
        Filter->>Target: synthesize QMouseEvent Press
        Filter->>Target: synthesize QMouseEvent Release
    end
    deactivate Filter
Loading

Class diagram for TouchScrollFilter integration with BluetoothApplet scroll area

classDiagram
    class QObject
    class QAbstractScrollArea
    class QScrollArea
    class QScrollBar
    QScrollArea --|> QAbstractScrollArea

    class BluetoothApplet {
        - QScrollArea* m_scrollArea
        + void initUi()
    }

    class TouchScrollFilter {
        + TouchScrollFilter(area: QAbstractScrollArea*)
        + ~TouchScrollFilter()
        + bool eventFilter(watched: QObject*, event: QEvent*)
        - QAbstractScrollArea* m_area
        - QPointF m_lastPos
        - bool m_isDrag
    }

    TouchScrollFilter --|> QObject
    BluetoothApplet --> QScrollArea : owns
    BluetoothApplet --> TouchScrollFilter : creates
    TouchScrollFilter --> QAbstractScrollArea : scroll_target
    TouchScrollFilter --> QScrollBar : updates
Loading

File-Level Changes

Change Details Files
Replace QScroller configuration in the Bluetooth applet scroll area with a TouchScrollFilter instance to handle touch interactions.
  • Remove QScroller creation and overshoot configuration for the applet scroll area.
  • Instantiate TouchScrollFilter with the applet’s QAbstractScrollArea to enable custom touch handling.
  • Update includes to bring in the TouchScrollFilter header.
plugins/dde-dock/bluetooth/componments/bluetoothapplet.cpp
Introduce TouchScrollFilter utility that converts touch gestures into manual scroll bar updates and synthetic mouse clicks on tap.
  • Implement TouchScrollFilter as a QObject-based event filter attached to the scroll area viewport, enabling WA_AcceptTouchEvents.
  • On TouchUpdate, track global touch movement, detect drag using QApplication::startDragDistance, and adjust vertical/horizontal scroll bar values accordingly.
  • On non-drag TouchEnd, locate the child widget under the touch point and send synthesized MouseButtonPress/MouseButtonRelease events to simulate a click.
  • Consume touch events handled by the filter to prevent default processing.
plugins/dde-dock/common/touchscrollfilter.cpp
plugins/dde-dock/common/touchscrollfilter.h
Update copyright headers to the current year.
  • Adjust SPDX-FileCopyrightText year range from 2016–2022 to 2016–2026 in the Bluetooth applet source.
  • Add SPDX headers for the new TouchScrollFilter source and header files.
plugins/dde-dock/bluetooth/componments/bluetoothapplet.cpp
plugins/dde-dock/common/touchscrollfilter.cpp
plugins/dde-dock/common/touchscrollfilter.h

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 left some high level feedback:

  • In TouchScrollFilter::eventFilter, the final return QObject::eventFilter(watched, event); after return true; is unreachable and should be removed to avoid confusion.
  • For robustness, consider explicitly handling QEvent::TouchCancel similarly to TouchEnd (at least resetting m_isDrag/state) instead of letting it fall through to the default case while still accept()-ing the event.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `TouchScrollFilter::eventFilter`, the final `return QObject::eventFilter(watched, event);` after `return true;` is unreachable and should be removed to avoid confusion.
- For robustness, consider explicitly handling `QEvent::TouchCancel` similarly to `TouchEnd` (at least resetting `m_isDrag`/state) instead of letting it fall through to the default case while still `accept()`-ing the event.

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.

…applet

- Issue background and cause: Touch screen operations such as scrolling and clicking did not work correctly with the native `QScroller`.
- Specific changes: Replaced `QScroller` with a custom `TouchScrollFilter` for the bluetooth applet UI scroll area.
- Technical details: Added `TouchScrollFilter` intercepting `QEvent::TouchUpdate` to offset the scroll area manually based on global coordinates differences, and on un-dragged `QEvent::TouchEnd`, simulating a click using QMouseEvent via core app dispatcher.

- 问题背景和原因:原生的 `QScroller` 导致蓝牙插件面板的触屏滚动和无拖拽点击操作无法正常工作。
- 具体修改内容:移除了原有蓝牙插件面板滚动区域的 `QScroller` 实现,替换为系统性的 `TouchScrollFilter` 组件。
- 修改的技术细节:通过事件过滤器分析触控变化,基于全屏坐标增量手动控制滚动条数值实现触屏滚动。对于无明显拖拽的触摸抬起事件,通过查询子节点来直接模拟分发鼠标点击事件,从而保证点击的精准响应。

Log: fix: resolve touch screen scrolling and clicking issues in bluetooth applet
Pms: BUG-352505
@yixinshark yixinshark force-pushed the fix-bluetoothTouchScreen branch from 085efd1 to 1c7210d Compare March 10, 2026 09:17
@deepin-ci-robot
Copy link

deepin pr auto review

这份代码主要实现了一个自定义的触摸滚动过滤器 TouchScrollFilter,用于替代 QScroller 来处理 QAbstractScrollArea(如 QScrollArea)的触摸滚动事件,并添加了点击检测功能。

以下是对代码的详细审查和改进建议:

1. 语法与逻辑审查

  • 版权年份更新: bluetoothapplet.cpp 和新增文件的版权年份更新为 2026,这看起来像是一个笔误(通常是未来年份)。建议确认是否应为当前年份(如 2023 或 2024)。
  • 头文件包含: touchscrollfilter.cpp 中包含了 <QScrollBar><QApplication>,这是必要的。
  • 空指针检查: 在 eventFilter 的开头检查了 m_area 是否为空,这是很好的防御性编程习惯。
  • 事件类型过滤: 逻辑正确地只处理 TouchBegin, TouchUpdate, TouchEnd, TouchCancel,其他事件交由父类处理。
  • 触摸点处理: 使用了 te->points().first()。对于单指操作这是没问题的,但如果有多指触控,代码默认只响应第一个触摸点。如果需求是支持多指缩放或特定多指手势,这里需要修改。如果是简单的单指滚动,当前逻辑是可以接受的。

2. 代码质量

  • 命名规范: 变量命名(如 m_area, m_isDrag)遵循了 Qt 的命名规范,清晰易读。
  • 魔法数字: 代码中使用了 QApplication::startDragDistance(),这很好,避免了硬编码阈值。
  • 注释: 代码缺少必要的注释,特别是 TouchScrollFilter 的核心逻辑(如为什么要在 TouchEnd 中模拟鼠标点击)。建议添加注释说明其工作原理。

3. 代码性能

  • 事件过滤开销: eventFilter 会在每个事件到达 viewport 时被调用。代码在最开始就过滤掉了非触摸事件,这减少了不必要的处理,性能是可以接受的。
  • 频繁的 setValue: 在 TouchUpdate 中,每次手指移动都会调用 setValue。这会触发滚动条的 valueChanged 信号,可能导致 UI 重绘。对于高频的触摸事件,这可能会造成一定的性能压力。
    • 改进建议: 可以考虑引入一个简单的节流机制,或者确保接收滚动条信号的槽函数处理非常高效。

4. 代码安全

  • 内存管理: TouchScrollFilter 在构造函数中 new TouchScrollFilter(m_scrollArea)。由于 TouchScrollFilter 继承自 QObject 且将父对象设为了 m_scrollArea,Qt 的对象树机制会在 m_scrollArea 销毁时自动销毁过滤器,没有内存泄漏风险
  • 空指针解引用风险:
    • te->points().isEmpty() 检查了点列表是否为空,防止了 first() 崩溃。
    • TouchUpdate 中访问 m_area->verticalScrollBar()horizontalScrollBar() 前没有显式检查指针是否为空(虽然 QAbstractScrollArea 通常会保证返回有效的指针,但为了绝对安全,建议加上检查或断言)。
  • 事件发送的安全性:
    • TouchEnd 中,代码手动构造并发送了 QMouseEventQCoreApplication::sendEvent 是同步调用,如果目标 target 的事件处理函数中有耗时操作或删除了某些对象,可能会导致问题。不过对于简单的点击处理,通常风险可控。
    • 逻辑漏洞: 在 TouchEnd 中,childAt 找到的子控件 target 可能是一个不可见的控件(例如被遮挡的),或者是一个不接受鼠标事件的控件。直接向其发送鼠标事件可能不符合预期。

5. 具体改进建议

修改 touchscrollfilter.h

// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // 修改年份
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#ifndef TOUCHSCROLLFILTER_H
#define TOUCHSCROLLFILTER_H

#include <QObject>
#include <QPointF>
#include <QAbstractScrollArea>

class TouchScrollFilter : public QObject
{
    Q_OBJECT // 如果未来需要信号槽,建议加上 Q_OBJECT

public:
    explicit TouchScrollFilter(QAbstractScrollArea *area);
    ~TouchScrollFilter() override = default;

protected:
    bool eventFilter(QObject *watched, QEvent *event) override;

private:
    QAbstractScrollArea *m_area;
    QPointF m_lastPos;
    bool m_isDrag;
};

#endif // TOUCHSCROLLFILTER_H

修改 touchscrollfilter.cpp

// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // 修改年份
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "touchscrollfilter.h"

#include <QEvent>
#include <QTouchEvent>
#include <QMouseEvent>
#include <QScrollBar>
#include <QApplication>
#include <QDebug> // 用于调试

TouchScrollFilter::TouchScrollFilter(QAbstractScrollArea *area)
    : QObject(area)
    , m_area(area)
    , m_isDrag(false)
{
    if (m_area) {
        // 确保 viewport 和 area 都接受触摸事件
        m_area->viewport()->setAttribute(Qt::WA_AcceptTouchEvents, true);
        m_area->setAttribute(Qt::WA_AcceptTouchEvents, true);
        m_area->viewport()->installEventFilter(this);
    }
}

bool TouchScrollFilter::eventFilter(QObject *watched, QEvent *event)
{
    // 安全检查
    if (!m_area) {
        return QObject::eventFilter(watched, event);
    }

    const QEvent::Type type = event->type();
    
    // 只处理触摸相关事件
    if (type != QEvent::TouchBegin && type != QEvent::TouchUpdate && 
        type != QEvent::TouchEnd && type != QEvent::TouchCancel) {
        return QObject::eventFilter(watched, event);
    }

    QTouchEvent *te = static_cast<QTouchEvent *>(event);
    
    // 如果没有触摸点,直接忽略
    if (te->points().isEmpty()) {
        event->accept();
        return true;
    }

    // 获取第一个触摸点(主要处理单指操作)
    QEventPoint pt = te->points().first();

    switch (type) {
    case QEvent::TouchBegin: {
        m_lastPos = pt.globalPosition();
        m_isDrag = false;
        // 必须返回 false 以允许后续事件(如点击)被处理,或者在这里 accept 并接管
        // 为了接管手势,通常这里应该 accept
        event->accept();
        return true;
    }
    case QEvent::TouchUpdate: {
        qreal deltaY = pt.globalPosition().y() - m_lastPos.y();
        qreal deltaX = pt.globalPosition().x() - m_lastPos.x();

        // 判断是否达到拖动阈值
        if (!m_isDrag && (qAbs(deltaY) >= QApplication::startDragDistance() || 
                          qAbs(deltaX) >= QApplication::startDragDistance())) {
            m_isDrag = true;
        }

        if (m_isDrag) {
            // 增加空指针检查,尽管 Qt 通常保证不为空
            if (auto *vBar = m_area->verticalScrollBar()) {
                vBar->setValue(vBar->value() - static_cast<int>(deltaY));
            }
            if (auto *hBar = m_area->horizontalScrollBar()) {
                hBar->setValue(hBar->value() - static_cast<int>(deltaX));
            }
            m_lastPos = pt.globalPosition();
        }
        event->accept();
        return true;
    }
    case QEvent::TouchEnd: {
        // 如果没有发生拖动,则模拟鼠标点击
        if (!m_isDrag) {
            QWidget *target = m_area->viewport();
            QPoint widgetPos = pt.position().toPoint();
            
            // 查找点击位置下方的控件
            if (QWidget *child = target->childAt(widgetPos)) {
                // 检查控件是否可见且启用
                if (child->isVisible() && child->isEnabled() && !child->testAttribute(Qt::WA_TransparentForMouseEvents)) {
                    target = child;
                    widgetPos = target->mapFrom(m_area->viewport(), widgetPos);
                }
            }

            // 模拟鼠标按下和释放事件
            QMouseEvent press(QEvent::MouseButtonPress, widgetPos, pt.globalPosition(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
            QCoreApplication::sendEvent(target, &press);
            QMouseEvent release(QEvent::MouseButtonRelease, widgetPos, pt.globalPosition(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
            QCoreApplication::sendEvent(target, &release);
        }
        event->accept();
        return true;
    }
    case QEvent::TouchCancel:
        // 触摸取消(如来电中断),重置状态
        m_isDrag = false;
        event->accept();
        return true;
    default:
        break;
    }

    return QObject::eventFilter(watched, event);
}

6. 总结

代码整体逻辑清晰,实现了基本的触摸滚动和点击转换功能。

  1. 安全性: 建议在 TouchUpdate 中增加对滚动条指针的显式检查,并在 TouchEnd 中增加对目标控件可见性和可交互性的检查。
  2. 逻辑: 在 TouchBegin 中建议显式 accept 事件并返回 true,以确保完全接管该触摸序列,避免与系统默认手势冲突。
  3. 细节: 修正版权年份;添加必要的代码注释;在 TouchUpdate 中进行 int 转换以消除潜在的编译警告。
  4. 边界情况: 处理了 TouchCancel 事件,这是一个好的实践,可以防止状态卡死。

这些修改将使代码更加健壮和安全。

@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 50a5097 into linuxdeepin:master Mar 10, 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