From b6436196be9183538c2ddc6184e8bbb0a239d3c2 Mon Sep 17 00:00:00 2001 From: Kilian Mio <86004375+Mikilio@users.noreply.github.com> Date: Thu, 20 Aug 2026 19:07:36 +0200 Subject: [PATCH] fix(display): enforce at least one real monitor in profile application (#3106) ensureEnabledOutput previously checked if ANY output was enabled, including virtual fallback outputs. A profile with eDP-1 disabled and FALLBACK enabled was treated as valid, causing the only real monitor to be turned off during hotplug transitions. Now ensureEnabledOutput only considers outputs with make+model (real monitors) when checking for enabled outputs. When all real outputs are disabled, it enables the first real one. When no real outputs exist in the profile at all, applyConfigEntry writes an empty config and lets the compositor handle fallback via its built-in default rules. Fixes the case where unplugging an external monitor leaves the laptop display disabled because a stale FALLBACK auto-profile was applied. --- .../DisplayConfig/DisplayConfigState.qml | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml b/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml index 34ce7a8c4..ba4e763cc 100644 --- a/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml +++ b/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml @@ -530,11 +530,20 @@ Singleton { const outputKeys = Object.keys(configEntry.outputs || {}); if (outputKeys.length === 0) return false; - const hasEnabled = outputKeys.some(k => !configEntry.outputs[k].disabled); - if (hasEnabled) + const hasEnabledReal = outputKeys.some(k => { + const o = configEntry.outputs[k]; + return !o.disabled && o.make && o.model; + }); + if (hasEnabledReal) return false; - delete configEntry.outputs[outputKeys[0]].disabled; - return true; + for (const k of outputKeys) { + const o = configEntry.outputs[k]; + if (o.make && o.model) { + delete o.disabled; + return true; + } + } + return false; } function applyConfigEntry(configEntry, configId, profileName, isManual) { @@ -548,6 +557,20 @@ Singleton { return; } ensureEnabledOutput(configEntry); + const outputKeys = Object.keys(configEntry.outputs || {}); + const hasRealOutput = outputKeys.some(k => { + const o = configEntry.outputs[k]; + return o.make && o.model; + }); + if (!hasRealOutput) { + backendWriteOutputsConfig({}, null, success => { + if (success) { + SettingsData.setActiveDisplayProfile(CompositorService.compositor, configId); + WlrOutputService.requestState(); + } + }); + return; + } // Capture the entry being applied so disabled-output settings fields can read // scale/position/transform back even when wlr reports no logical viewport. root.lastAppliedEntry = JSON.parse(JSON.stringify(configEntry));