Linux: only raise floating widgets during an active drag - #840
Conversation
On Linux/X11 the CDockManager constructor connects a handler to QApplication::focusWindowChanged that, on every focus-window change, calls raise() on the dock manager, on every floating widget, and on the newly focused window. The original intent (githubuser0xFFFF#722) was narrower: bring the application and its floating widgets to the foreground only while a floating dock widget is being dragged over another application. Reacting to *every* focus change makes this a self-sustaining loop. raise() restacks top-level windows, which on X11 can make the window manager move focus, which re-emits focusWindowChanged and re-enters the handler. Because all windows are raised on every pass, the z-order never reaches a stable state, so the windows keep restacking - visible as continuous flicker. It gets worse the more floating widgets exist, since each pass raises more windows and generates more focus transitions. The later QMainWindow/QDialog/CFloatingDockContainer type test only narrows which focus changes start the loop; once started it still runs unbounded (githubuser0xFFFF#785). Fix: gate the entire raise block on a floating widget actually being dragged. Outside a drag the handler now returns immediately and issues no raise() calls, so it can no longer feed itself - no restacking, no flicker. The foreground behaviour during a real drag is unchanged and ends naturally when the drag finishes. ADS already tracks this state internally (FloatingDockContainerPrivate::DraggingFloatingWidget); this exposes it through a small public accessor: bool CFloatingDockContainer::isDraggingActive() const; Fixes githubuser0xFFFF#785.
|
When running the SimpleExample on Ubuntu 24.04 with i3 on X11 I would get endless restacking when creating multiple floating windows: resulting in the windows figthing endlessly for focus. I think most window managers do some form of focus-stealing prevention, and refuse or defer a programmatic raise that isn't tied to recent user input. The window gets marked "demands attention" instead of actually being restacked/focused. That refusal naturally stops the loop: the app asks to raise everything, the WM quietly declines, nothing re-fires. i3 is strict and trusts the client's raise/focus requests instead of second-guessing them. This leads to the is a raise() -> focus-change -> raise() loop. As a bonus this fixes #785 for me. |
|
Thank you very much for your contribution. |
…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.
On Linux/X11 the CDockManager constructor connects a handler to QApplication::focusWindowChanged that, on every focus-window change, calls raise() on the dock manager, on every floating widget, and on the newly focused window. The original intent (#722) was narrower: bring the application and its floating widgets to the foreground only while a floating dock widget is being dragged over another application.
Reacting to every focus change makes this a self-sustaining loop. raise() restacks top-level windows, which on X11 can make the window manager move focus, which re-emits focusWindowChanged and re-enters the handler. Because all windows are raised on every pass, the z-order never reaches a stable state, so the windows keep restacking - visible as continuous flicker. It gets worse the more floating widgets exist, since each pass raises more windows and generates more focus transitions. The later QMainWindow/QDialog/CFloatingDockContainer type test only narrows which focus changes start the loop; once started it still runs unbounded (#785).
Fix: gate the entire raise block on a floating widget actually being dragged. Outside a drag the handler now returns immediately and issues no raise() calls, so it can no longer feed itself - no restacking, no flicker. The foreground behaviour during a real drag is unchanged and ends naturally when the drag finishes.
ADS already tracks this state internally
(FloatingDockContainerPrivate::DraggingFloatingWidget); this exposes it through a small public accessor:
bool CFloatingDockContainer::isDraggingActive() const;
Fixes #785.