Skip to content

feat: support KWin split menus in QML title bars - #688

Open
18202781743 wants to merge 1 commit into
linuxdeepin:masterfrom
18202781743:kwin-split-menu
Open

feat: support KWin split menus in QML title bars#688
18202781743 wants to merge 1 commit into
linuxdeepin:masterfrom
18202781743:kwin-split-menu

Conversation

@18202781743

@18202781743 18202781743 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

为 QML TitleBar/WindowButtonGroup 增加 splitScreenEnabled,并在最大化按钮悬停或长按时请求 KWin 分屏菜单。普通点击保持最大化/还原行为;长按成功后释放不触发最大化;不支持平台接口时正常点击行为不受影响。

DQuickWindowAttached 直接封装平台函数调用,不经过 dtkgui。客户端处理按钮交互和功能禁用时的收起请求,窗口关闭/消失后的菜单清理由窗管负责。

关联平台接口:linuxdeepin/dde-qtplatform-plugins#345

验证:实现工作目录中 Qt5/Qt6 构建通过(最多 6 个核心);QML 无界面测试 8 项通过(包含初始化/清理,覆盖普通点击、长按、悬停离开、功能禁用及不支持的平台)。PR 基于最新 master,差异检查通过;真实 KWin 桌面显示效果尚未实测。

Summary by Sourcery

Enable QML title-bar maximize buttons to invoke and manage KWin split-screen menus while retaining existing window-state interactions.

New Features:

  • Add configurable split-screen menu support to QML title bars and window button groups through the maximize button.

Bug Fixes:

  • Preserve normal maximize and restore behavior when split-menu support is unavailable or disabled.
  • Prevent a successful long-press split-menu request from also triggering the maximize action on release.

Enhancements:

  • Support showing and dismissing the platform split-screen menu through hover and long-press interactions, including cleanup when buttons are hidden, disabled, or leave the hover state.
  • Expose direct platform split-menu operations from DQuickWindowAttached for QML consumers.

1. Add splitScreenEnabled property to TitleBar and WindowButtonGroup
QML components
2. Implement showSplitMenu and hideSplitMenu methods in
DQuickWindowAttached using platform functions
3. Add hover timer (700ms) and hold timer (300ms) to trigger split menu
on the maximize button
4. Track splitMenuWindowId in DQuickWindowAttachedPrivate to manage
menu lifecycle
5. Handle menu hiding on hover exit, release, cancel, visibility change,
and enabled change
6. Guard menu operations with button/window visibility and enabled
checks

Log: Added KWin split screen menu support triggered by hovering or long-
pressing the maximize button in QML title bars

Influence:
1. Test hovering over the maximize button for 700ms to verify the split
menu appears
2. Test long-pressing the maximize button for 300ms to verify the split
menu appears
3. Verify normal click still maximizes/restores the window when menu is
not triggered
4. Test that moving the mouse away hides the split menu
5. Verify splitScreenEnabled=false disables the split menu entirely
6. Test menu behavior when the window is not visible or the button is
disabled
7. Verify the menu positions correctly relative to the button's screen
position
8. Test hide delay behavior when releasing outside vs. inside the button

feat: 在 QML 标题栏中支持 KWin 分屏菜单

1. 在 TitleBar 和 WindowButtonGroup QML 组件中添加 splitScreenEnabled
属性
2. 通过平台函数在 DQuickWindowAttached 中实现 showSplitMenu 和
hideSplitMenu 方法
3. 添加悬停定时器(700ms)和长按定时器(300ms)以在最大化按钮上触发分屏
菜单
4. 在 DQuickWindowAttachedPrivate 中跟踪 splitMenuWindowId 以管理菜单生
命周期
5. 处理鼠标离开、释放、取消、可见性变化和启用状态变化时的菜单隐藏
6. 使用按钮/窗口可见性和启用状态检查保护菜单操作

Log: 新增 KWin 分屏菜单支持,通过悬停或长按 QML 标题栏中的最大化按钮触发

Influence:
1. 测试将鼠标悬停在最大化按钮上 700ms,验证分屏菜单是否出现
2. 测试长按最大化按钮 300ms,验证分屏菜单是否出现
3. 验证未触发菜单时正常点击仍能最大化/还原窗口
4. 测试鼠标移开后分屏菜单是否正确隐藏
5. 验证 splitScreenEnabled=false 时完全禁用分屏菜单
6. 测试窗口不可见或按钮禁用时的菜单行为
7. 验证菜单相对于按钮屏幕位置的正确定位
8. 测试在按钮外部释放与内部释放时的隐藏延迟行为
@deepin-ci-robot

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 18202781743

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 Sep 11, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds configurable KWin split-menu support to QML title bars by combining maximize-button hover/long-press interaction with direct platform-function calls, while retaining existing maximize/restore behavior on unsupported platforms and ordinary clicks.

Sequence diagram for QML split-menu interaction

sequenceDiagram
    participant User
    participant MaximizeButton
    participant DQuickWindowAttached
    participant Platform

    alt Hover for 700ms or hold for 300ms
        User->>MaximizeButton: hover or press
        MaximizeButton->>DQuickWindowAttached: showSplitMenu(maxOrWindedBtn)
        DQuickWindowAttached->>Platform: _d_supportSplitMenu(wid)
        alt Platform supports split menu
            DQuickWindowAttached->>Platform: _d_showSplitMenu(wid, rect)
            Platform-->>DQuickWindowAttached: menu shown
        else Unsupported or invalid window/button
            DQuickWindowAttached-->>MaximizeButton: false
        end
    else Ordinary click
        User->>MaximizeButton: click
        MaximizeButton->>MaximizeButton: maxOrWinded()
    end
    alt Menu requested
        User->>MaximizeButton: release or leave button
        MaximizeButton->>DQuickWindowAttached: hideSplitMenu(delay)
        DQuickWindowAttached->>Platform: _d_hideSplitMenu(wid, delay)
    end
Loading

File-Level Changes

Change Details Files
Expose split-screen configuration through the QML title-bar component hierarchy.
  • Add enabled property at title-bar and button-group levels.
  • Forward the property to the maximize button group.
qt6/src/qml/TitleBar.qml
qt6/src/qml/WindowButtonGroup.qml
Add hover and long-press interaction handling for the maximize button while preserving normal click behavior.
  • Request the split menu after hover or a 300 ms press.
  • Suppress maximize/restore after a successful long press.
  • Cancel or hide the menu when the pointer leaves, interaction ends, visibility changes, or the feature is disabled.
  • Keep ordinary clicks functional when split menus are disabled or unsupported.
qt6/src/qml/WindowButtonGroup.qml
Implement platform-function based split-menu display and lifecycle management for QML windows.
  • Validate the window and button, resolve platform callbacks, and pass the button's global logical rectangle to the platform.
  • Track the owning window ID and support immediate or delayed menu hiding.
  • Expose the operations through DQuickWindowAttached without routing through dtkgui.
src/dquickwindow.cpp
src/dquickwindow.h
src/private/dquickwindow_p.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

@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

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="qt6/src/qml/WindowButtonGroup.qml" line_range="131-136" />
<code_context>
+                control.maxOrWinded()
+            __longPressed = false
+        }
+        onVisibleChanged: {
+            if (!visible) {
+                hoverTimer.stop()
+                holdTimer.stop()
+                __menuRequested = false
+            }
+        }
+        onEnabledChanged: if (!enabled) hideSplitMenu(false)
</code_context>
<issue_to_address>
**issue (bug_risk):** When the maximize button becomes invisible while a split menu is open, this handler stops the timers and clears `__menuRequested` without calling `hideSplitMenu`, so the platform menu remains visible and the client loses the state needed to hide it.

**Triggers:** When the window enters fullscreen or another state that makes `maxOrWindedBtn.visible` false after the split menu has been shown.

**Suggested fix:** Call `hideSplitMenu(false)` (or an appropriate delayed hide) before clearing the request state when the button becomes invisible, and handle window visibility changes explicitly if they do not change the button's `visible` property.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment on lines +131 to +136
onVisibleChanged: {
if (!visible) {
hoverTimer.stop()
holdTimer.stop()
__menuRequested = false
}

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 (bug_risk): When the maximize button becomes invisible while a split menu is open, this handler stops the timers and clears __menuRequested without calling hideSplitMenu, so the platform menu remains visible and the client loses the state needed to hide it.

Triggers: When the window enters fullscreen or another state that makes maxOrWindedBtn.visible false after the split menu has been shown.

Suggested fix: Call hideSplitMenu(false) (or an appropriate delayed hide) before clearing the request state when the button becomes invisible, and handle window visibility changes explicitly if they do not change the button's visible property.

@deepin-ci-robot

Copy link
Copy Markdown
Contributor

deepin pr auto review

AI 代码审查报告

项目: linuxdeepin/dtkdeclarative
PR: #688 - feat: support KWin split menus in QML title bars
分支: kwin-split-menu → master
作者: 18202781743
提交: e826bfd
审查时间: 2026-09-11


总体评价

总分: 93/100 | 等级: 优秀
代码实现了 KWin 分屏菜单功能,通过悬停(700ms)和长按(300ms)触发分屏菜单,在 QML 标题栏的最大化按钮上集成。整体代码结构清晰,防御性编程较好,未发现安全漏洞。存在少量边界条件和状态管理方面的轻微问题,建议改进。


漏洞统计

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

类型 数量
严重(Critical) 0
高危(High) 0
中危(Medium) 0
低危(Low) 0
总计 0

四维度评分

维度 评分 状态 评价
语法逻辑 22/25 语法正确,逻辑清晰
代码质量 21/25 代码结构清晰,注释基本完整
代码性能 20/20 性能良好,资源使用合理
代码安全 30/30 存在0个安全漏洞

维度1: 语法逻辑 (22/25 ✓)

语法正确,逻辑清晰

代码目的: 在 QML 标题栏中支持 KWin 分屏菜单,通过悬停或长按最大化按钮触发。

分析:

C++ 层 showSplitMenu()hideSplitMenu() 实现逻辑正确,对窗口、按钮的可见性和启用状态进行了充分校验。QML 层通过 hoverTimer(700ms)和 holdTimer(300ms)实现了悬停触发和长按触发两种交互方式,事件处理(hover/press/release/cancel/click)逻辑合理,__longPressed 标志位正确区分了长按和短按行为。

发现的问题:

  1. WindowButtonGroup.qml 第131-137行 onVisibleChanged 处理器在按钮变为不可见时仅停止定时器和重置 __menuRequested,但未调用 hideSplitMenu() 隐藏已显示的平台菜单。与 onEnabledChanged(第138行调用 hideSplitMenu(false))处理不一致,可能导致平台菜单在按钮不可见时仍然显示。

    • 文件: qt6/src/qml/WindowButtonGroup.qml,函数: onVisibleChanged
    • 级别: 轻微问题 (-3分)
  2. dquickwindow.cpp 第1037行 showSplitMenu() 在显示新菜单前未检查 splitMenuWindowId 是否已设置(即是否有前一个菜单未关闭)。虽然 QML 层通过 __menuRequested 标志进行了防护,但 C++ 公共方法本身缺乏重入保护。

    • 文件: src/dquickwindow.cpp,函数: DQuickWindowAttached::showSplitMenu
    • 级别: 轻微问题 (已包含在上方扣分中)
  3. dquickwindow.cpp 第1041-1052行 hideSplitMenu(bool delay)delay=true 时不清理 splitMenuWindowId,可能导致后续调用对已关闭的菜单重复调用平台 hide 函数。splitMenuWindowId 在关联窗口销毁/隐藏时也未重置,可能持有过期的 WId。

    • 文件: src/dquickwindow.cpp,函数: DQuickWindowAttached::hideSplitMenu
    • 级别: 轻微问题 (已包含在上方扣分中)

建议修复:

// dquickwindow.cpp - showSplitMenu() 中添加重入保护
bool DQuickWindowAttached::showSplitMenu(QQuickItem *button)
{
    D_D(DQuickWindowAttached);
    // 如果已有菜单显示,先隐藏
    if (d->splitMenuWindowId)
        hideSplitMenu(false);
    // ... 原有逻辑
}

// dquickwindow.cpp - hideSplitMenu() 统一清理 splitMenuWindowId
void DQuickWindowAttached::hideSplitMenu(bool delay)
{
    D_D(DQuickWindowAttached);
    if (!d->splitMenuWindowId)
        return;
    const auto hide = reinterpret_cast<void (*)(WId, bool)>(
            QGuiApplication::platformFunction("_d_hideSplitMenu"));
    if (hide)
        hide(d->splitMenuWindowId, delay);
    d->splitMenuWindowId = 0;  // 始终清理,避免过期状态
}
// WindowButtonGroup.qml - onVisibleChanged 添加 hideSplitMenu 调用
onVisibleChanged: {
    if (!visible) {
        hoverTimer.stop()
        holdTimer.stop()
        hideSplitMenu(false)  // 隐藏平台菜单
        __menuRequested = false
    }
}

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

代码结构清晰,注释基本完整

分析:

代码结构合理,QML 层将分屏菜单逻辑封装在最大化按钮内部,C++ 层通过平台函数指针调用实现平台集成。命名规范一致(__longPressed__menuRequestedsplitMenuWindowId),与现有代码风格保持统一。无重复代码,无残留调试代码。

发现的问题:

  1. dquickwindow.cpp 第1015行新增的 showSplitMenu()hideSplitMenu() 函数缺少 Qt 风格的文档注释(/*! ~chinese \brief ... */),而文件中其他函数(如 popupSystemWindowMenusetEnabled 等)均有完整的文档注释。

    • 文件: src/dquickwindow.cpp,函数: showSplitMenu/hideSplitMenu
    • 级别: 关键函数无注释 (-2分)
  2. dquickwindow.cppshowSplitMenu() 第1028行获取 hide 函数指针时未进行类型转换(仅作为 void* 用于空指针检查),而 hideSplitMenu() 第1046-1047行则转换为 void (*)(WId, bool)。两处对同一平台函数的处理方式不一致,虽然功能正确但容易引起混淆。

    • 文件: src/dquickwindow.cpp,函数: showSplitMenu/hideSplitMenu
    • 级别: 结构可维护性 (-2分)

建议修复:

// 为新函数添加 Qt 风格文档注释
/*!
 * \~chinese \brief DQuickWindowAttached::showSplitMenu 显示分屏菜单
 * \~chinese 通过平台函数在指定按钮位置显示 KWin 分屏菜单
 * \~chinese \param button 触发分屏菜单的 QQuickItem 按钮
 * \~chinese \return 成功显示返回 true,否则返回 false
 */

// 统一函数指针类型定义,避免重复和不一致
using SplitMenuSupportFunc = bool (*)(WId);
using SplitMenuShowFunc = void (*)(WId, const QRect &);
using SplitMenuHideFunc = void (*)(WId, bool);

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

性能良好,资源使用合理

分析:

代码性能表现良好:

  • showSplitMenu()/hideSplitMenu() 仅在用户交互时调用,非高频路径
  • 两个 Timer 组件(700ms 和 300ms)在不需要时正确停止,避免无效定时器触发
  • splitMenuWindowId 为简单整型成员,无内存开销
  • QGuiApplication::platformFunction() 调用虽然涉及字符串查找,但在用户交互场景下性能影响可忽略
  • QRect 计算使用 mapRectToScenetoAlignedRect,为标准 Qt 操作,效率合理

无性能问题。


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

存在0个安全漏洞

安全分析:

  1. 平台函数调用安全: QGuiApplication::platformFunction() 使用硬编码字符串字面量("_d_supportSplitMenu""_d_showSplitMenu""_d_hideSplitMenu"),无用户输入参与函数名构造,不存在注入风险。所有函数指针在使用前均进行空指针检查。

  2. 指针安全: showSplitMenu()windowbutton 进行了完整的空指针和有效性校验(第1020-1022行),包括检查按钮所属窗口是否一致(button->window() != w)、按钮可见性和启用状态。

  3. reinterpret_cast 安全: 函数指针的 reinterpret_cast 转换是 Qt 平台集成的标准模式,源数据来自可信的平台插件注册,非外部输入。

  4. 无敏感信息: 代码中无密码、密钥、Token 等敏感信息。

  5. 无注入风险: 无 SQL 操作、无命令执行、无路径操作、无反序列化。

安全合规,无安全风险。


改进建议总结

  1. onVisibleChanged 中应调用 hideSplitMenu(false) 以保持与 onEnabledChanged 的一致性
  2. C++ showSplitMenu() 应在显示新菜单前隐藏已有菜单(重入保护)
  3. hideSplitMenu() 应始终清理 splitMenuWindowId,避免过期状态
  4. 为新增 C++ 函数添加 Qt 风格文档注释
  5. 统一平台函数指针的类型处理方式,考虑使用 using 别名

审查结论

本次提交实现了 KWin 分屏菜单功能,代码质量整体优秀。功能实现与 commit message 描述一致,包括:

  • 在 TitleBar 和 WindowButtonGroup 中添加 splitScreenEnabled 属性 ✓
  • 通过平台函数实现 showSplitMenu/hideSplitMenu 方法 ✓
  • 添加悬停定时器(700ms)和长按定时器(300ms)✓
  • DQuickWindowAttachedPrivate 中跟踪 splitMenuWindowId
  • 处理多种交互场景下的菜单隐藏 ✓
  • 使用按钮/窗口可见性和启用状态检查保护菜单操作 ✓

无安全漏洞,建议修复少量边界条件和状态管理问题。

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