From 50352e586282e99b649cf5c0263cccc59e31f73c Mon Sep 17 00:00:00 2001 From: Hagai Cohen Date: Mon, 1 Jun 2026 19:44:26 +0300 Subject: [PATCH] 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 --- hyprtester/src/tests/main/window.cpp | 21 +++++++++++++++++++++ src/config/shared/actions/ConfigActions.cpp | 17 ++++++++++++----- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/hyprtester/src/tests/main/window.cpp b/hyprtester/src/tests/main/window.cpp index 0a6b6faae..d5906ac7d 100644 --- a/hyprtester/src/tests/main/window.cpp +++ b/hyprtester/src/tests/main/window.cpp @@ -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"); diff --git a/src/config/shared/actions/ConfigActions.cpp b/src/config/shared/actions/ConfigActions.cpp index 7b007c36b..fe435d3e2 100644 --- a/src/config/shared/actions/ConfigActions.cpp +++ b/src/config/shared/actions/ConfigActions.cpp @@ -230,15 +230,22 @@ ActionResult Actions::pinWindow(eTogglableAction action, std::optionalm_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;