You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Crash: dwindle assert from dragEnd() when dragBegin() rejects a drag, via stale m_draggingTiled
#15789
Hyprland dies with SIGSEGV on an assert while dragging a window with a mouse bind (SUPER + mouse:272 → hl.dsp.window.drag()). It is intermittent — I have two crashes with this signature and no reliable trigger, so the report is based on reading the code rather than on a recipe.
changeMouseBindMode appears twice: it calls dragBegin(), and dragBegin() calls it straight back, which runs dragEnd() for a drag that never finished starting.
Analysis
m_draggingTiled is only ever cleared inside updateDragWindow(), and dragBegin() has three early returns that never reach it.
src/layout/supplementary/DragController.cpp:
102 — m_draggingTiled = false; (the only reset, inside updateDragWindow())
115 — m_draggingTiled = true; after changeFloatingMode() floats a tiled window
275–279 — dragEnd() reads it:
if (m_draggingTiled) {
if (draggingTarget->floating())
g_layoutManager->changeFloatingMode(draggingTarget);
draggingTarget->rememberFloatingSize(m_draggingWindowOriginalFloatSize);
}
288–295 — dragEnd()'s teardown resets m_wasDraggingWindow, m_dragMode, m_exclusiveDeviceGrab and m_forcedGrabbedCorner, but notm_draggingTiled.
So the flag survives a completed drag and stays true until the nextupdateDragWindow() clears it. Meanwhile dragBegin() bails out before that point in three places, each calling changeMouseBindMode(MBIND_INVALID) → dragEnd():
143–147 — window not mapped
149–153 — no workspace
97–99 (inside updateDragWindow(), above the reset at 102) — drag rejected on a fullscreen workspace
On any of those, dragEnd() runs with m_draggingTiled left over from the previous drag and calls changeFloatingMode() on a window this drag never floated. In dwindle that reaches movedTarget() → addTarget() with the node tree in a state it does not expect, and the assert fires.
The first two paths are the more likely ones in practice: they are exactly what a drag started on a window that is mid-unmap or has just changed workspace will hit.
Suggested fix
Make the flag per-drag rather than per-updateDragWindow(). Either of these closes it; doing both is cheap:
Initialise it with the rest of the drag state at the top of dragBegin() (alongside lines 130–134, before the early returns at 143 and 149):
m_draggingTiled = false;
Clear it in dragEnd()'s teardown for symmetry, next to the other resets at 292–295:
m_draggingTiled = false;
A stricter alternative would be to have dragBegin()'s failure paths do their own minimal cleanup instead of re-entering changeMouseBindMode(), so dragEnd() is never called for a drag that never began — that also removes the re-entrancy the backtrace shows, but it is a larger change.
Two smaller things noticed in the same file
dragEnd() line 222 does auto draggingTarget = m_target.lock(); and dereferences it at 226 (draggingTarget->window()) with no null check.
dragBegin() line 136 does the same with DRAGGINGTARGET and dereferences it at 143.
Both are reachable if m_target is empty or expired at that moment.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Version: 0.56.2, commit
efb50993780079460b0cbed1363e2166a2de1d9f, tagv0.56.2(Archhyprland 0.56.2-1)GPU: AMD Radeon 680M (Rembrandt), amdgpu
Layout: dwindle
What happens
Hyprland dies with SIGSEGV on an assert while dragging a window with a mouse bind (
SUPER + mouse:272→hl.dsp.window.drag()). It is intermittent — I have two crashes with this signature and no reliable trigger, so the report is based on reading the code rather than on a recipe.Backtrace (relevant frames, top first)
changeMouseBindModeappears twice: it callsdragBegin(), anddragBegin()calls it straight back, which runsdragEnd()for a drag that never finished starting.Analysis
m_draggingTiledis only ever cleared insideupdateDragWindow(), anddragBegin()has three early returns that never reach it.src/layout/supplementary/DragController.cpp:m_draggingTiled = false;(the only reset, insideupdateDragWindow())m_draggingTiled = true;afterchangeFloatingMode()floats a tiled windowdragEnd()reads it:dragEnd()'s teardown resetsm_wasDraggingWindow,m_dragMode,m_exclusiveDeviceGrabandm_forcedGrabbedCorner, but notm_draggingTiled.So the flag survives a completed drag and stays
trueuntil the nextupdateDragWindow()clears it. MeanwhiledragBegin()bails out before that point in three places, each callingchangeMouseBindMode(MBIND_INVALID)→dragEnd():updateDragWindow(), above the reset at 102) — drag rejected on a fullscreen workspaceOn any of those,
dragEnd()runs withm_draggingTiledleft over from the previous drag and callschangeFloatingMode()on a window this drag never floated. In dwindle that reachesmovedTarget()→addTarget()with the node tree in a state it does not expect, and the assert fires.The first two paths are the more likely ones in practice: they are exactly what a drag started on a window that is mid-unmap or has just changed workspace will hit.
Suggested fix
Make the flag per-drag rather than per-
updateDragWindow(). Either of these closes it; doing both is cheap:dragBegin()(alongside lines 130–134, before the early returns at 143 and 149):m_draggingTiled = false;dragEnd()'s teardown for symmetry, next to the other resets at 292–295:m_draggingTiled = false;A stricter alternative would be to have
dragBegin()'s failure paths do their own minimal cleanup instead of re-enteringchangeMouseBindMode(), sodragEnd()is never called for a drag that never began — that also removes the re-entrancy the backtrace shows, but it is a larger change.Two smaller things noticed in the same file
dragEnd()line 222 doesauto draggingTarget = m_target.lock();and dereferences it at 226 (draggingTarget->window()) with no null check.dragBegin()line 136 does the same withDRAGGINGTARGETand dereferences it at 143.Both are reachable if
m_targetis empty or expired at that moment.All reactions