这是针对 Linux Wayland (wlcom) 平台模态问题的完整解决方案。
一、问题分析
在 Wayland 平台上,窗口管理机制比 X11 更为严格。 QQuickWidget 或 QML 窗口与 QWidget 对话框属于不同的窗口层级。如果 一个 QDialog / QWidget 没有正确设置 Wayland 层面的父窗口(Transient Parent),Wayland 合成器(Compositor)就无法识别它们之间的模态关系,导致模态失效。
二、解决方案
核心修复方案是在对话框显示( exec() )之前,强制创建其底层窗口句柄,并手动设置其 Transient Parent 为 QML 的主窗口。
关键步骤:
- 调用 dialog.winId() 确保对话框创建了原生窗口句柄。
- 调用 dialog.windowHandle()->setTransientParent(qml_parent) 建立模态关系。
三、什么是Transient
在 Wayland(以及 X11)的窗口协议中, “Transient”(瞬态) 这个词有特定的技术含义,不仅仅是字面上的“短暂”。
简单来说, Transient Parent 是窗口管理器(Compositor)用来识别“ 辅助窗口属于哪个主窗口 ”的唯一凭证。
以下是详细的技术分析:
1. 什么是 Transient(瞬态)窗口?
在窗口系统中,窗口分为两类:
- Toplevel(顶层窗口) :独立存在的窗口,比如你的主程序界面( main.qml 对应的窗口)。它们在任务栏有图标,可以独立最小化。
- Transient(瞬态窗口) : 依附于 某个顶层窗口存在的辅助窗口,比如对话框(Dialog)、提示框(Alert)、工具栏等。
“Transient Parent” 就是指这个辅助窗口所依附的那个“父亲”。
2. 为什么 Wayland 必须显式设置它?
Wayland 与 X11 的最大区别在于 安全性 和 隔离性 。
- X11 的“混乱” :在 X11 时代,任何程序都可以通过全局坐标得知其他窗口的位置,甚至拦截其他窗口的输入。窗口管理器通常比较“宽容”,只要你设了模态(Modal)标志,它可能就会尝试阻塞输入,哪怕它不知道你属于谁。
- Wayland 的“严格” :在 Wayland 中,Compositor(合成器,如 KWin, Mutter 或 wlcom)默认认为每个窗口都是独立的、沙箱化的。
- 如果你弹出一个对话框( WritingAddItemDialog ),但没有告诉 Compositor “我是 main.qml 的孩子”。
- Compositor 就会把它当成另一个独立的顶层窗口。
- 结果 :既然是两个独立的程序窗口,它们之间当然没有模态阻塞关系。用户依然可以点击后面的主窗口,模态失效。
只有当你调用 setTransientParent 时,底层才会发送协议(如 xdg_toplevel_set_parent )给 Compositor,正式“认亲”。Compositor 确认关系后,才会执行模态策略(变暗背景、拦截点击)。
3. Qt 中的两个 “Parent” 的区别
这是最容易混淆的地方:
QObject Parent / QWidget Parent(内存与对象树层面)
- 代码: new WritingAddItemDialog(parentWidget)
- 作用:负责 内存管理 (父对象析构时自动析构子对象)和 Qt 内部的事件传递 。
- 局限:对于 QQuickWidget 和 QWidget 混用的情况,或者跨越了 QML/C++ 边界,Qt 有时无法自动将这个对象树关系映射到操作系统的窗口关系上。
Window Transient Parent(操作系统窗口层面)
- 代码: windowHandle()->setTransientParent(qmlWindow)
- 作用:直接告诉操作系统(Wayland Compositor)窗口层级关系。
- 这就是我们修复的核心 :我们绕过了 Qt 的对象层级,直接在底层窗口句柄(QWindow)级别建立了父子关系。
在 Wayland 上, “模态”是一种窗口管理器提供的服务 ,而 Transient Parent 是申请这项服务的“入场券”。没有这张入场券,对话框就是个路人,无权阻塞主窗口。
四、Demo
1 | //适用于QML为父窗口,widgets子窗口需要显示模态的场景 |