Repository navigation
Conversation
|
Skipping CI for Draft Pull Request. |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: deepin-wm The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
Reviewer's GuideAPIAdd the shared topmost-window hit-test API and implement it using the render window’s paint order, including the cursor-position helper used by window chrome.
Uses of the APIUse the shared hit test to skip activation when a press delivered to a window is covered by another window.
UIRoute title-bar gestures through one press owner and gate activation, moving, menus, maximize, and window-button actions on the press belonging to this window.
Require the window under the cursor to be topmost before starting a resize from its decoration border.
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
add90b2 to
a1c0405
Compare
1. A press on the active window could raise and focus a window stacked below it, which showed up with server-side-decoration title bars. 2. Cause: the press landed on a part of the window on top that owns no client surface (a compositor-drawn title bar, the window border, a transparent margin), so it was delivered to the window below, and both the client press path and the compositor chrome activated whatever surface received it. 3. Fix: SurfaceWrapper::isTopmostWindowAt() tells whether a window painted above this one covers the position. The paint order comes from waylib's WOutputRenderWindow::paintOrderItemList(), so z-order, workspaces and show-on-all-workspace containers are honoured by construction; child windows (dialogs, X11 transients) cover like any other window. afterHandleEvent() and the resize border skip the activation when the answer is no. 4. Snap masks are not such a window: the edge-tiling overlay dde-shell draws is full-screen but is expected to keep an empty input region, so counting it would make every window look covered. Layer shells and popups keep their own stacking and are filtered by type as before. Log: 按下当前激活窗口(含标题栏、边框、透明边距)不再把下方窗口抬起,也不再因此抢走焦点。 Influence: 1. 两个带 SSD 标题栏的窗口部分重叠,点击上层窗口的标题栏与内容区,确认下层窗口不被抬起、也不被激活。 2. 点击下层窗口露出来的部分,确认它仍被激活并提升到最上层。 3. overlay / X11 above 窗口盖住刚激活过的窗口,按下后者可见部分,确认仍正常激活提升。 4. X11 below(_NET_WM_STATE_BELOW)窗口被激活过之后,点它上面的窗口,确认正常聚焦。 5. 显示在所有工作区的窗口、以及 CSD 对话框压住另一个窗口标题栏时,重复第 1 条。 6. 边缘平铺触发吸附蒙层后,确认窗口仍能被点击激活(蒙层不参与覆盖判定)。 7. 点击桌面、面板、dock,以及缩放边框、移动窗口,确认行为不变。 Multica Issue: WM-936
1. A press can reach this window's title bar while the cursor is over the window on top: when that window owns no input at the point (a transparent margin or the window border) the press falls through, and the title bar below then activates its own window, which restacks it above the window that was pressed. 2. Cause: the title bar acted on every press it received, without asking whether it is the window under the cursor. 3. Fix: one MouseArea owns the title bar presses (click activates, double click toggles maximize, dragging past the drag threshold moves the window, right click and touch long press open the window menu), and the ownership test runs once per press. A TapHandler/DragHandler variant gated its drag on the cursor position at drag time and was reverted after the pointer wedged on real hardware; that cause is still unknown and is not addressed here. Log: 上层窗口没有输入的位置落到下层标题栏时,标题栏不再激活、移动本窗口或弹出窗口菜单。 Influence: 1. 上层窗口点击/拖动它的 SSD 标题栏,下层窗口不被抬起、不被激活。 2. 上层窗口是没有 Decoration 的状态(最大化/全屏/平铺)或 CSD 窗口时,按它透明的边角, 落到下层标题栏的位置,确认下层不被激活、不被抬起。 3. 标题栏单击激活、双击最大化/还原、拖动移动、右键菜单、触屏长按、三个窗口按钮均不受影响。 4. 拖动标题栏移动窗口时,过程中与松手后都不出现窗口菜单。 Multica Issue: WM-936
a1c0405 to
9ecd5f2
Compare
There was a problem hiding this comment.
Hey - I've found 4 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src/core/qml/TitleBar.qml" line_range="146" />
<code_context>
onClicked: {
+ // A covered title bar can be reached through an area of the
+ // window above that owns no input; its buttons stay inert then.
+ if (!surface.isTopmostWindowUnderCursor())
+ return
surface.minimize()
</code_context>
<issue_to_address>
**Covered buttons still activate**
When a press starts on a covered part of a lower window’s title-bar button and ends on an exposed part of that same button, `onClicked` checks only the release-time position and does not record ownership on press, so the lower window’s button action runs even though the press began on the window above; the lower window is minimized, maximized, or closed.
Record whether the button belongs to the topmost window when pressed, and run its action only if it did.
Also at `src/core/qml/TitleBar.qml:165-166`, `src/core/qml/TitleBar.qml:191`.
</issue_to_address>
### Comment 2
<location path="src/core/qml/TitleBar.qml" line_range="146" />
<code_context>
onClicked: {
+ // A covered title bar can be reached through an area of the
+ // window above that owns no input; its buttons stay inert then.
+ if (!surface.isTopmostWindowUnderCursor())
+ return
surface.minimize()
</code_context>
<issue_to_address>
**Valid actions use the wrong position**
When a touchscreen press reaches a title-bar button or resize border while the seat cursor remains elsewhere, or a secondary-seat pointer is at a different position from the primary-seat pointer, the `TitleBar.qml` handlers call `isTopmostWindowUnderCursor()`, which checks the primary seat’s cursor instead of the touch position or the invoking seat’s pointer position, so valid actions are suppressed or actions on covered windows are allowed.
Check topmost-window status using the event position and the seat that received the event.
Also at `src/core/qml/TitleBar.qml:165`, `src/core/qml/TitleBar.qml:191`, `src/core/qml/Decoration.qml:62`, `src/surface/surfacewrapper.cpp:995-1003`.
</issue_to_address>
### Comment 3
<location path="src/surface/surfacewrapper.cpp" line_range="987" />
<code_context>
+ continue;
+ if (window->type() != Type::XdgToplevel && window->type() != Type::XWayland)
+ continue;
+ if (window->mapRectToScene(QRectF(QPointF(0, 0), window->size())).contains(scenePos))
+ return false;
+ }
</code_context>
<issue_to_address>
**Shadow presses still raise lower windows**
When the press lands in a visible shadow band outside the upper window’s bounds, `isTopmostWindowAt()` checks only the upper wrapper’s `size()`, so a press in its visible XdgShadow band falls through the coverage test and the lower window is activated and raised.
Include the upper window’s visible shadow extents in the coverage test, or hit-test its rendered shadow shape.
</issue_to_address>
### Comment 4
<location path="src/core/qml/TitleBar.qml" line_range="99-100" />
<code_context>
+ onPressAndHold: function (mouse) {
+ // Touch screen only, and never mid-drag: this timer is not stopped by
+ // movement, while a dragged window keeps the cursor over the same spot.
+ if (pressBelongsToWindow && pressWasTouch && !movingWindow)
+ surface.windowMenuRequested(Qt.point(mouse.x, mouse.y))
+ }
}
</code_context>
<issue_to_address>
**Touch long press activates window**
When a touch long press opens the menu on an inactive window and the finger is released, `onPressAndHold` opens the window menu, but releasing the same left-button press still reaches `onClicked`, which activates the surface. Long-pressing an inactive window therefore also focuses and raises it when the menu is invoked.
Track that the press-and-hold handler ran and suppress the corresponding click action on release.
Also at `src/core/qml/TitleBar.qml:89`.
</issue_to_address>| onClicked: { | ||
| // A covered title bar can be reached through an area of the | ||
| // window above that owns no input; its buttons stay inert then. | ||
| if (!surface.isTopmostWindowUnderCursor()) |
There was a problem hiding this comment.
🟡 Medium · Covered buttons still activate
When a press starts on a covered part of a lower window’s title-bar button and ends on an exposed part of that same button, onClicked checks only the release-time position and does not record ownership on press, so the lower window’s button action runs even though the press began on the window above; the lower window is minimized, maximized, or closed.
Record whether the button belongs to the topmost window when pressed, and run its action only if it did.
Also at src/core/qml/TitleBar.qml:165-166, src/core/qml/TitleBar.qml:191.
Prompt for AI agents
In `src/core/qml/TitleBar.qml` at line 146:
**Covered buttons still activate**
When a press starts on a covered part of a lower window’s title-bar button and ends on an exposed part of that same button, `onClicked` checks only the release-time position and does not record ownership on press, so the lower window’s button action runs even though the press began on the window above; the lower window is minimized, maximized, or closed.
Record whether the button belongs to the topmost window when pressed, and run its action only if it did.
Also at `src/core/qml/TitleBar.qml:165-166`, `src/core/qml/TitleBar.qml:191`.| onClicked: { | ||
| // A covered title bar can be reached through an area of the | ||
| // window above that owns no input; its buttons stay inert then. | ||
| if (!surface.isTopmostWindowUnderCursor()) |
There was a problem hiding this comment.
🟡 Medium · Valid actions use the wrong position
When a touchscreen press reaches a title-bar button or resize border while the seat cursor remains elsewhere, or a secondary-seat pointer is at a different position from the primary-seat pointer, the TitleBar.qml handlers call isTopmostWindowUnderCursor(), which checks the primary seat’s cursor instead of the touch position or the invoking seat’s pointer position, so valid actions are suppressed or actions on covered windows are allowed.
Check topmost-window status using the event position and the seat that received the event.
Also at src/core/qml/TitleBar.qml:165, src/core/qml/TitleBar.qml:191, src/core/qml/Decoration.qml:62, src/surface/surfacewrapper.cpp:995-1003.
Prompt for AI agents
In `src/core/qml/TitleBar.qml` at line 146:
**Valid actions use the wrong position**
When a touchscreen press reaches a title-bar button or resize border while the seat cursor remains elsewhere, or a secondary-seat pointer is at a different position from the primary-seat pointer, the `TitleBar.qml` handlers call `isTopmostWindowUnderCursor()`, which checks the primary seat’s cursor instead of the touch position or the invoking seat’s pointer position, so valid actions are suppressed or actions on covered windows are allowed.
Check topmost-window status using the event position and the seat that received the event.
Also at `src/core/qml/TitleBar.qml:165`, `src/core/qml/TitleBar.qml:191`, `src/core/qml/Decoration.qml:62`, `src/surface/surfacewrapper.cpp:995-1003`.| continue; | ||
| if (window->type() != Type::XdgToplevel && window->type() != Type::XWayland) | ||
| continue; | ||
| if (window->mapRectToScene(QRectF(QPointF(0, 0), window->size())).contains(scenePos)) |
There was a problem hiding this comment.
🟡 Medium · Shadow presses still raise lower windows
When the press lands in a visible shadow band outside the upper window’s bounds, isTopmostWindowAt() checks only the upper wrapper’s size(), so a press in its visible XdgShadow band falls through the coverage test and the lower window is activated and raised.
Include the upper window’s visible shadow extents in the coverage test, or hit-test its rendered shadow shape.
Prompt for AI agents
In `src/surface/surfacewrapper.cpp` at line 987:
**Shadow presses still raise lower windows**
When the press lands in a visible shadow band outside the upper window’s bounds, `isTopmostWindowAt()` checks only the upper wrapper’s `size()`, so a press in its visible XdgShadow band falls through the coverage test and the lower window is activated and raised.
Include the upper window’s visible shadow extents in the coverage test, or hit-test its rendered shadow shape.| if (pressBelongsToWindow && pressWasTouch && !movingWindow) | ||
| surface.windowMenuRequested(Qt.point(mouse.x, mouse.y)) |
There was a problem hiding this comment.
🟡 Medium · Touch long press activates window
When a touch long press opens the menu on an inactive window and the finger is released, onPressAndHold opens the window menu, but releasing the same left-button press still reaches onClicked, which activates the surface. Long-pressing an inactive window therefore also focuses and raises it when the menu is invoked.
Track that the press-and-hold handler ran and suppress the corresponding click action on release.
Also at src/core/qml/TitleBar.qml:89.
Prompt for AI agents
In `src/core/qml/TitleBar.qml` at lines 99-100:
**Touch long press activates window**
When a touch long press opens the menu on an inactive window and the finger is released, `onPressAndHold` opens the window menu, but releasing the same left-button press still reaches `onClicked`, which activates the surface. Long-pressing an inactive window therefore also focuses and raises it when the menu is invoked.
Track that the press-and-hold handler ran and suppress the corresponding click action on release.
Also at `src/core/qml/TitleBar.qml:89`.|
被 #1612 修复 |
解决的问题
在已激活窗口内按下鼠标时,位于它 Z 轴下方、带 SSD(服务端装饰)标题栏的窗口会被错误地抬到最上层。用户复现的现象是:按上层窗口的内容区(不只是标题栏)也会触发。
问题来源:WM-936
根因
按下的归属判定缺失,表现为两条通道:
CompositorInputFilter::afterHandleEvent()激活,落到下层标题栏/边框就由对应的 QML 处理器激活,激活即stackToLast(),于是下层被抬到上面。activateSurface()/moveRequested()/ 弹菜单,没有问过「我是光标下的那个窗口吗」。改了什么
src/core/qml/TitleBar.qmlMouseArea(单击激活、双击最大化/还原、超过拖动阈值才移动、右键与触屏长按弹窗口菜单),归属判定每次按下只算一次;三个窗口按钮也先过覆盖判定src/input/compositorinputfilter.cppafterHandleEvent()里,若按下的位置被按下窗口之上的窗口覆盖,则不激活(层级与激活态都不动),按下照旧送给实际收到它的客户端src/surface/surfacewrapper.{h,cpp}isTopmostWindowAt()/isTopmostWindowUnderCursor():用 waylib 的WOutputRenderWindow::paintOrderItemList()取整棵 scene 的绘制顺序(自下而上),再从最上层往下找第一个覆盖该位置的窗口。z序、工作区、「显示在所有工作区」的容器由绘制顺序本身保证,不自己维护排序src/core/qml/Decoration.qml为什么这么改
childItems()插入序:treeland 的视觉层级由z决定(窗口角色、XWayland above/below、XdgPopup),插入序只在同z内生效。只比插入序会双向出错——漏判 overlay/X11 above 覆盖者(原 bug 仍复现),误判被stackToLast()排到末尾的z=-1窗口(点它上面的窗口不聚焦)。所以直接用 waylib 已有的绘制顺序,而不是自己再实现一套比较(src/modules/snap-target/snaphandler.cpp取「光标下最上层」也是这么做的)。afterHandleEvent()修不全:穿透的按下会先被下层窗口的标题栏/边框的 QML 处理器接住直接激活,根本走不到afterHandleEvent()。setSurfaceRole()给它同样的z),所以也算覆盖者;popup / input popup 有自己的堆叠和输入 grab,仍被 type 判断排除,行为与改前一致。提交(2 个)
按「判定/入口」与「标题栏交互」分组,后者依赖前者,两个提交各自可编译。
bc2f08476fix(surface): do not activate a window that a window above coversisTopmostWindowAt()/isTopmostWindowUnderCursor()判定,以及afterHandleEvent()、Decoration.qml缩放边框两个入口9ecd5f264fix(surface): give a title bar press only to the window under itMouseArea+ 按下时的归属判定(含按钮、触屏手势与「拖动不弹菜单」)验证情况
qmlcachegen编译通过;标题栏手势用QWindowSystemInterface按WSeat同款 source 做离屏事件回放(真实鼠标按住/拖动/单击、触摸合成长按);确定性 lint 0 findings。真机反馈目前只覆盖一项——拖动标题栏会弹出窗口菜单,已修复(提交 2 里的设备判定)。MouseEvent.source自 Qt 6.6 起弃用,比较改成显式等值(万一某次 Qt 构建里该属性消失,退化方向是「触屏长按不弹菜单」这种一眼可见的问题,而不是原 bug 静默回来);注释里写明「真实指针事件以NotSynthesized送达 QML」这个来自WSeat(waylib/src/server/kernel/wseat.cpp)的前提。Log与Influence,请按 Influence 逐条跑:_NET_WM_STATE_ABOVE)窗口盖住一个刚激活过的窗口 → 按下后者可见部分仍正常激活提升。_NET_WM_STATE_BELOW)窗口被激活过之后,点它上面的普通窗口 → 正常聚焦。已知边界(未在本 PR 处理)
QRectF(0, 0, other->size()),不含 XdgShadow 环带:方向是「漏判」,误差在 shadowBlurRadius 量级,看起来像阴影压住的位置仍可能抬起下层。isTopmostWindowAt()会遍历整棵 scene,只允许用在按下/单击路径,不要放进 hover/motion 这类每帧路径。相关
Summary by Sourcery
Ensure window presses only activate, move, resize, or operate on the visually topmost window at the pointer location.
Bug Fixes:
Enhancements: