config/actions: fix null derefs in pin dispatcher (#14914)

* hyprtester: add issue14134 regression test

D#14134 reports a pin dispatcher SEGV when a monitor hosting a
pinned floating window is disabled (e.g. lid close on a laptop).
After CMonitor::onDisconnect resets m_activeWorkspace to null,
the CMonitor SP stays alive in m_realMonitors, and the pinned-
window branch of moveWorkspaceToMonitor leaves the window's
m_monitor pointing at the disabled monitor.

The next pin dispatch does null deref inside Config::Actions::pinWindow.

* config/actions: fix null derefs in pin dispatcher

PR #14061 replaced moveToWorkspace(m_activeWorkspace) with
assignToSpace(m_activeWorkspace->m_space) and a separate
layoutTarget() call.

The two new derefs aren't covered by the existing PMONITOR check,
and either one being null crashes the compositor.

In practice, i use pyprland which uses pining function,
and since i am on laptop, sometimes my lid is closed and i have
monitor disabled, this causes null deref when computer goes idle.

    Layout::CWindowTarget::assignToSpace   <- segv
    Config::Actions::pinWindow
    pin
    CDispatcherTranslator::run
    CHyprCtl::getReply

In this fix i've added null checks to avoid crashing
This commit is contained in:
Hagai Cohen
2026-06-07 12:26:03 +02:00
committed by Vaxry
parent 93daf6e9c8
commit 50352e5862
2 changed files with 33 additions and 5 deletions
+21
View File
@@ -588,6 +588,27 @@ TEST_CASE(issue14038) {
// this should not crash hyprland. If we are alive, we good.
}
TEST_CASE(issue14134) {
OK(getFromSocket("/output create headless HEADLESS-4"));
OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-4' })"));
if (!spawnKitty("kitty_14134"))
FAIL_TEST("Could not spawn kitty");
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'toggle', window = 'class:kitty_14134' })"));
OK(getFromSocket("/dispatch hl.dsp.window.pin({ action = 'toggle', window = 'class:kitty_14134' })"));
OK(getFromSocket("/eval hl.monitor({ output = 'HEADLESS-4', disabled = true })"));
// will return error, should not be OK.
getFromSocket("/dispatch hl.dsp.window.pin({ action = 'toggle', window = 'class:kitty_14134' })");
OK(getFromSocket("/reload"));
Tests::killAllWindows();
ASSERT(Tests::windowCount(), 0);
OK(getFromSocket("/output remove HEADLESS-4"));
}
TEST_CASE(specialFloatRecenters) {
if (!spawnKitty("kitty_special_float_recenter"))
FAIL_TEST("Could not spawn kitty");
+12 -5
View File
@@ -230,15 +230,22 @@ ActionResult Actions::pinWindow(eTogglableAction action, std::optional<PHLWINDOW
if (wantPin == window->m_pinned)
return {};
window->m_pinned = wantPin;
window->updateFullscreenInputState();
*window->alpha(Desktop::View::WINDOW_ALPHA_FULLSCREEN) = window->isBlockedByFullscreen() ? 0.F : 1.F;
const auto PMONITOR = window->m_monitor.lock();
if (!PMONITOR)
return actionError("Window has no monitor", eActionErrorLevel::WARNING, eActionErrorCode::INVALID_STATE);
window->layoutTarget()->assignToSpace(PMONITOR->m_activeWorkspace->m_space);
if (!PMONITOR->m_activeWorkspace || !PMONITOR->m_activeWorkspace->m_space)
return actionError("Monitor has no active workspace", eActionErrorLevel::WARNING, eActionErrorCode::INVALID_STATE);
const auto LAYOUTTARGET = window->layoutTarget();
if (!LAYOUTTARGET)
return actionError("Window has no layout target", eActionErrorLevel::WARNING, eActionErrorCode::INVALID_STATE);
window->m_pinned = wantPin;
window->updateFullscreenInputState();
*window->alpha(Desktop::View::WINDOW_ALPHA_FULLSCREEN) = window->isBlockedByFullscreen() ? 0.F : 1.F;
LAYOUTTARGET->assignToSpace(PMONITOR->m_activeWorkspace->m_space);
window->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_PINNED);
const auto PWORKSPACE = window->m_workspace;