Add Wayland docking support - #844
Conversation
|
Thank you very much for your comprehensive documentation. Could you please tell me, in which way your merge request is related to this one: #837 Do I need to merge both or is your merge request a more comlpete replacement? Thank you. |
|
This PR is related to #837, but they are different. This PR allows undocked windows to be separate from the original window. You can then move widgets between windows to reparent them. It allows Qt ADS to work correctly without crashes or corruption while allowing undocked window resizing. It does somewhat degrade the Qt-ADS experience, but you can accomplish all widget positioning you may want. This PR is also written to only affect Wayland code and deliberately avoid touching code for other platforms. PR 837 improves the drag & drop widget experience within the original window on Wayland. It does not fix the multiple window experience. Here's a video attempting to show the differences: After playing with this some more, I am working on seeing if we can keep the existing widget drag behavior within the window and only activate this 844 behavior when we need to create a new window. |
… window PR githubuser0xFFFF#844 always converts a dock drag into a native compositor platform drag at the drag threshold on Wayland, because the mouse-tracked drag preview relied on the (unreliable) global cursor position. That replaces the familiar in-window rearrange UX with a new top-level window for every drag. This restores the normal behavior while the cursor stays inside the source top-level window, and only starts the native platform drag once the cursor leaves it - as one continuous gesture. The same applies to rearranging widgets inside an already-floating window. How it works: - The in-window phase reuses CFloatingDragPreview. On Wayland the preview is rendered as a child of the source window (a Qt::Tool top-level cannot be positioned in screen coordinates there) and is driven by the reliable, event-supplied global position from the grabbing tab/title-bar instead of QCursor::pos(). Drop-target hit-testing/overlays are confined to the source container (new CFloatingDragPreview::setSourceContainer()). - When the cursor leaves the source window, the preview is torn down (cancelDraggingSilently()) and a real CFloatingDockContainer is created and handed to the existing CFloatingDockContainer::startPlatformDrag(), while the press's implicit pointer grab is still held. Implemented for both the tab (CDockWidgetTab) and title-bar (CDockAreaTitleBar) drag paths. startPlatformDrag(): do not wait for the new window to be exposed before QDrag::exec(). Per xdg_toplevel_drag_v1 the attach offset that places the window under the cursor is only honoured while the toplevel is still UNMAPPED; show() creates the xdg_toplevel role but the surface only maps once the event loop is pumped, so waiting for exposure made the compositor ignore the offset and place the new window at its default position (screen center). This also removes the previous 250ms exposure wait. Impact on non-Wayland platforms (X11/Windows/macOS): none. Every behavioral change is gated on internal::isWayland(): - The child-widget preview, event-coordinate tracking, source-container confinement, boundary-cross conversion, and the preview raise() are all isWayland()-gated; otherwise the preview stays a Qt::Tool top-level and the existing QCursor::pos()-driven moveFloating() path is used unchanged. - The drag-decision sites in CDockWidgetTab::startFloating and DockAreaTitleBarPrivate::makeAreaFloating drop the old isWayland-only "PlatformDrag" branch; the remaining expression is identical to the previous value on non-Wayland (where PlatformDrag was always false). - The CFloatingDragPreview shared-code refactor (moveFloating(QPoint) overload, explicit-position overlay calls, finishDragging position source) is behavior-preserving off Wayland: the no-arg overlay methods already delegate with QCursor::pos(), and the event-position stash used by finishDragging is only set on Wayland, so the live QCursor::pos() is still used elsewhere. - startPlatformDrag() is only ever invoked from isWayland()-gated paths. See doc/wayland_hybrid_drag_test_plan.md for the manual verification procedure and the automated-coverage gap (these paths need a live compositor + QDrag). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
I just pushed an additional commit that effectively combines 837 into 844. The result is much nicer, and Wayland feels like a first-class platform for Qt ADS! The PR is implemented in a way that only affects Wayland. Other platforms should be functionally unchanged. Here is a demo: http://download.joulescope.com/other/qt_ads_wayland_02.mp4 I am happy to squash and force commit if you want a single commit to review and merge. Let me know! Detailed docs on this second commit: SummaryThis PR adds Wayland support to the docking system. On Wayland a client cannot reposition top-level windows and Latest commit: keep the in-window drag preview, go native only when leaving the windowThe original approach converted every dock drag into a native platform drag (a new top-level window) the moment the drag threshold was crossed, which replaced the familiar in-window rearrange UX. The latest commit makes the behavior hybrid: while the cursor stays inside the source top-level window, the drag uses the normal in-window On Wayland the in-window preview is rendered as a child of the source window and driven by the reliable event-supplied position from the grabbing widget (not One important protocol detail: Impact on other platformsNone. Every behavioral change is gated on Relationship to #837#844 and #837 solve the same Wayland problem in overlapping code, and with the latest commit #844 now fully absorbs #837. They remain co-mergeable (a merge produces two resolvable, same-intent conflicts in #837's real footprint is three files (~48 insertions / 38 deletions):
In short: the only lines in #837 not already present in #844 are two |
|
Thanks for your hard working on wayland support on linux wayland. This solution is almost fantastic and I think I can close my PR, tested on my KDE wayland. I find a minor bug: If you drag a docking window out of the main container and make it finally a floating window, sometime you cannot drag another docking window onto the recent newly created floating window. Another workon maybe a wayland limitation: You can not actually control the position of floating window when it transforms from docking window just now. It's wired when you drag a window out but it has a large distance with you cursor. |
|
Hi @Wing-summer - It is working really well for me on Ubuntu 26.04 LTS. A few things:
|
|
|
…window drag Qt Advanced Docking System could not dock on Wayland: a client cannot position top-level windows and the global cursor position is unreliable, so the mouse-tracked floating-widget drag never worked. This adds Wayland docking by combining two mechanisms, selected by whether the drag stays inside its source window. Native cross-window drag (xdg_toplevel_drag_v1): - CFloatingDockContainer::startPlatformDrag() runs a compositor-driven drag via QDrag with the Qt main-window-drag MIME types, so a floating window is moved by the compositor and can be dropped onto another window. - Drop targets handle the drag in CDockContainerWidget dragEnter/Move/Leave/drop events, with a recorded drop-candidate fallback for compositors that do not deliver a drop event over the dragged window. - The drop overlays (CDockOverlay) and the in-window drag preview are rendered as child widgets of the relevant top-level window, because a Qt::Tool top-level cannot be positioned in screen coordinates on Wayland. - Floating containers use a native window with no parent on Wayland, and the DockManager stays-on-top emulation that would recreate window surfaces is skipped. In-window preview, native only when leaving the window: - While the cursor stays inside the source top-level window, a drag uses the familiar in-window CFloatingDragPreview plus drop overlays (no new window), driven by the reliable event-supplied position from the grabbing tab or title bar and confined to the source container. - When the cursor leaves the window, the preview is torn down and a real CFloatingDockContainer is created and handed to startPlatformDrag() while the press's implicit pointer grab is still held - a single continuous gesture. This covers both the tab (CDockWidgetTab) and title-bar (CDockAreaTitleBar) drag paths and applies to rearranging widgets inside floating windows too. - The attach offset that places the new window under the cursor is only honored while the toplevel is unmapped, so startPlatformDrag() runs QDrag::exec() without waiting for exposure, and the caller passes the surface-local grab offset explicitly - shifted by the window frame top and left margins - instead of deriving it from the unmapped window's (meaningless) geometry, so the grabbed content point stays under the cursor on both axes. Style sheets and overlay lifetime: - A Wayland floating container has no parent widget, so it does not inherit the dock manager's effective style sheet through the widget hierarchy. The style sheets along the dock manager parent chain are applied explicitly when the window is created, and CDockManager re-applies them on QEvent::StyleChange so a floating window keeps matching the docked content when the style sheet changes at runtime (an application-wide qApp style sheet is still applied by Qt automatically). - The drop overlays are reparented into the top-level window they are shown over and reparented back to the dock manager's window when hidden, so a dock-manager-owned overlay is never left as a child of a transient floating window that gets destroyed. Saved layouts restore the docked arrangement, floating-window sizes and their maximized/normal state, but not floating-window or main-window positions: Wayland does not let a client position its own top-level windows, so the compositor decides where restored windows appear. Non-Wayland platforms (X11, Windows, macOS) are unaffected: every behavioral change is gated on internal::isWayland(), and the shared drag-preview code keeps using QCursor::pos() and the existing window positioning off Wayland. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
90ee526 to
cdca4b2
Compare
|
I just did a force push, which further improves this PR.
Overall, this PR makes Wayland very well supported by Qt ADS! |
|
@mliberty1 Thank your very much for your fantastic work. This is a really massive improvement for Linux users. @Wing-summer Thank you very much for reviewing the merge request, |
|
@githubuser0xFFFF - Thanks for all your work on this great project, being so responsive, and merging this PR so quickly! I think a bunch of ADS users have struggled using xcb (X11 emulation). My customers will be happy that the Joulescope UI will soon have proper Wayland support! |
|
@mliberty1 Thank you again for your outstanding contribution. Today I released ADS 5.0.0 with all the contributed new features. I highlighted your contribution in the New and Noteworthy section and added a showcase section for your Joulescope application. |
|
Excellent, and thank you for your work on this project, @githubuser0xFFFF ! |
…ory) Brings in 14 upstream commits incl. dark mode support (githubuser0xFFFF#842), Wayland drag-and-drop with in-window preview (githubuser0xFFFF#844), auto-hide title bar fix (githubuser0xFFFF#843), Linux floating-raise fix (githubuser0xFFFF#840), LP360 double-removal fix (githubuser0xFFFF#838), and lazy CDockWidgetTab creation for Python factories (githubuser0xFFFF#848). Conflict resolutions: - DockManager.cpp: keep both new includes (QTimer ours, QStyleHints upstream) - DockOverlay.cpp: keep fork's AutoHideFeatureEnabled guard structure and half-panel edge-band fall-throughs; adopt upstream's GlobalPos parameter (new dropAreaUnderCursor(QPoint) overload) instead of QCursor::pos() - FloatingDockContainer.cpp: line-ending false conflict (fork stores LF, upstream CRLF) re-merged content-wise; kept macOS escape-key polling, took upstream's Wayland ctor (isWayland() null-parent) + comment - FloatingDragPreview.cpp: kept fork's dropOverlaysEnabled() early-out; adopted upstream's SourceContainer/Wayland container-picking loop Validated: full build clean; QuadrantHitTestTest + OverlayGateTest pass.
|
there is a reason why isWayland() function is based on instead of something like ? anyway thanks for you great work |

Thanks for your great work on Qt-ADS! I have been using it in the Joulescope UI for many years, and it has been excellent. I have a number of customers affected by my UI issue #316, poor Wayland support. I tried to get someone on Upwork to fix this, but failed. Today, I worked with Claude Code / Fable to get this done.
This PR is a self-contained fix for Wayland. It's hopefully relatively easy to accept since most changes are gated to only affect Wayland. It does touch a bunch of files, though. The addition of
GlobalPosparameter to a few methods should be a flow-through no-change on other platforms.The overall experience is functional, but definitely not as slick as Windows and macOS. However, functional is an improvement and good enough to address my customers' concerns about slow performance using xcb.
Curated AI-generated summary follows:
Summary
This PR adds support for docking (undocking, moving, and re-docking floating widgets) on Wayland. It implements the approach outlined in #714 and follows the same mechanism that Qt itself uses for
QDockWidget/QToolBaron Wayland.Wayland forbids the techniques the docking system relies on elsewhere: a client cannot move its own top level windows in screen coordinates, cannot query the global cursor position, and cannot position one top level over another. Instead of the mouse tracked drag preview, undocking now performs a compositor driven drag:
QWidgettitle bar cannot).QDragcarrying the MIME types thatQWaylandDataDevicetranslates into anxdg_toplevel_drag_v1request, so the real floating window follows the cursor (no translucent preview).CDockContainerWidgetaccepts drops and drives the drop overlays from the drag-and-drop event positions instead ofQCursor::pos(), which is stale during a drag on Wayland.Platform impact
Every change is guarded by a new
ads::internal::isWayland()helper, so X11, Windows and macOS are unaffected. Where a function is shared (e.g. the drop overlay setup, now factored intoCDockContainerWidget::showDropOverlays()), the behavior is preserved on the other platforms. I verified the X11 mouse-drag path still works unchanged.This change is self-contained and does not depend on any other PR. On the non-Wayland paths the existing top-level
Qt::Tooloverlays are kept exactly as before; only on Wayland are the overlays child widgets.Requirements and behavioral notes (also updated in README)
xdg_toplevel_drag_v1support; on older Qt the code is harmless but the window will not follow the cursor, so X11/XWayland is still recommended there.Testing
Tested on GNOME / Mutter 50.1 with Qt 6.11 (PySide6): undock, move via the decoration, drop on an indicator to re-dock, drop outside to keep floating, re-dock a floating window via its title bar, floating-to-floating drop, and auto-hide undock. The X11/xcb path was regression tested and is unchanged.
Other compositors are not yet covered; on KDE/KWin a benign Qt warning (
This plugin supports grabbing the mouse only for popup windows) is emitted by Qt's own drag-and-drop internals and does not block docking.References