feat: grab-and-move — move windows by modifier + drag from anywhere (incl. drag-drop tree fix)#1407
Open
KhangHLe wants to merge 2 commits into
Open
feat: grab-and-move — move windows by modifier + drag from anywhere (incl. drag-drop tree fix)#1407KhangHLe wants to merge 2 commits into
KhangHLe wants to merge 2 commits into
Conversation
drop_as_tiling_window captured target_parent and nearest_container BEFORE calling update_window_state(-> Tiling), which mutates the tree: it re-inserts the window at its remembered tiling position and can flatten redundant split containers (move_container_within_tree's own doc warns this 'can cause the target parent to become detached'). The subsequent wrap/move against those stale references then fails with 'No common ancestor between containers' - and when the wrap succeeded against a detached parent first, the nearest window got reparented into an orphaned subtree, making platform_sync error 'Window has no workspace' on every event thereafter. - Convert to tiling FIRST; resolve containers_at_point/target_parent/ nearest_container against the post-mutation tree, excluding the moved window itself (it may now be a tiling child at the target). - The empty-workspace branch no longer needs its own late conversion. - Clear active_drag even when the drop errors (on whichever container instance now represents the window) so a failed drop can't leave every subsequent event re-entering drag handling. Reachable from native title-bar drags too; surfaced immediately by grab-and-move since it makes arbitrary drop points easy to produce.
Holding a modifier key (alt or win) and left-dragging anywhere on a
managed window moves it, exactly like a title-bar drag — the feature
that makes title-bar-less windows (WezTerm, yazi popups) mouse-movable
at all. Opt-in via config:
general:
grab_and_move:
enabled: true
modifier: 'alt'
Architecture (WH_MOUSE_LL hook on the dispatcher thread, modeled on
keyboard_hook):
- The hook swallows the modifier+click (the app never sees it) and
runs a manual drag session: a dedicated mover thread applies the
newest cursor position with synchronous SetWindowPos, coalescing so
a slow window (Firefox) gets one current move when ready instead of
a rubber-banding backlog of stale ones. The hook thread itself does
no syscalls on moves — one atomic load when idle.
- Delegating to the native modal move loop was tried first (posting
WM_NCLBUTTONDOWN/HTCAPTION, then WM_SYSCOMMAND/SC_MOVE|2, with and
without swallowing the click) and is ignored by custom-frame apps
(Firefox, WezTerm) — exactly the windows this feature targets.
- Only managed windows are draggable: the WM pushes its window set to
the hook on every WM event (diff-checked), so 'ignore' rules (games)
pass clicks through untouched.
- Drag lifecycle events feed WindowManager::process_window_drag, which
runs the same interactive move start/end handling as a native
title-bar drag — active_drag and tiling reflow behave identically.
- Modifier state via GetKeyState (the mechanism keybinding matching
already uses on this thread); GetAsyncKeyState read modifiers as
released from the hook context.
- macOS: inert no-op stub (WH_MOUSE_LL is Windows-only).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Grab-and-move: holding a modifier key (
altorwin) and left-dragging from anywhere on a managed window moves it — with full title-bar-drag semantics (tiling windows reflow on drop, floating windows reposition,ignored windows are untouched). This is the only mouse-move path for title-bar-less windows (terminals like WezTerm, custom-frame apps), and matches the feature PowerToys recently shipped as "Grab And Move" — but WM-aware.Also includes a standalone fix for drag-drop tree corruption that is reachable from native title-bar drags today (first commit — independently cherry-pickable).
Config
Implementation notes
WH_MOUSE_LLhook on the dispatcher thread, modeled on the existingkeyboard_hook.rs. The modifier+click is swallowed (the app never sees it) and a drag session starts; a dedicated mover thread applies the newest cursor position with synchronousSetWindowPos, coalescing so a slow window (e.g. Firefox) receives one current move whenever it's ready instead of a rubber-banding backlog. The hook itself does no syscalls on mouse-moves — one atomic load when idle.WM_NCLBUTTONDOWN/HTCAPTION, thenWM_SYSCOMMAND/SC_MOVE|2, with and without swallowing the click — and custom-frame apps (Firefox, WezTerm) ignore all of it. Those are exactly the windows this feature exists for.active_drag, tiling reflow) via a smallprocess_window_dragmethod.GetKeyState(the same mechanism keybinding matching uses on this thread) —GetAsyncKeyStatereads modifiers as released from the hook context.WH_MOUSE_LLis Windows-only).The bundled fix (first commit)
drop_as_tiling_windowresolvedtarget_parent/nearest_containerbeforeupdate_window_state(→ Tiling)mutates the tree (re-inserting the window and flattening redundant split containers —move_container_within_tree's own doc warns the flatten "can cause the target parent to become detached"). Dropping a tiling window where no re-tile target exists could then fail with "No common ancestor between containers" and reparent a real window into an orphaned subtree, after which everyplatform_syncerrors "Window has no workspace" forever. The fix resolves drop targets after the conversion, excludes the moved window from its own target search, and clearsactive_drageven when the drop errors.Testing
Built and daily-driven on Windows 11: tiling drops on all four quadrants (split + insertion), drops with no re-tile target (the previous error case), floating drags, ignored windows passing clicks through, plain clicks unaffected, WM pause/config-reload handling, drag smoothness on slow-frame apps (Firefox) via the coalescing mover.
This lives in and ships from KhangHLe/glazewm (with prebuilt releases) for anyone who wants it before it lands here. Happy to split the fix into its own PR, adjust config naming, or rework anything reviewers prefer.
— Ada