From 54d79d9d16f070f4567650e8916ab7bbc7675e63 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 19 Jul 2026 19:41:55 -0700 Subject: [PATCH] Fix screen flash when waking from sleep into the unlock screen The lock service arms a 5s blank timer whenever the screen locks, and input at the lock screen re-arms it. Closing the lid sprays pointer noise over the lock surface, so the timer was routinely armed right before suspend, froze mid-countdown, and fired moments after resume -- blanking the freshly woken unlock screen under the user. Guard the timer with a wall-clock check: if far more time elapsed than the interval, the countdown slept through a suspend, so take a fresh run-up instead of blanking. This also blanks the lock screen 5s after an untouched resume. Two accomplices made the flash worse and hid the real bug: - The clamshell watcher's 2s poll fired an unconditional global DPMS enable whenever no external monitor was active, relighting any blank within 2 seconds (lock-screen blanking never stuck on undocked laptops) and racing the resume modeset. Recovery now only wakes displays when it actually re-enables one. - Every keystroke at the lock screen dispatched a redundant DPMS enable via omarchy-system-wake, forcing extra modesets in the fragile just-resumed DRM state. Brightness "on" now skips the dispatch when every active display is already lit. Co-Authored-By: Claude Fable 5 --- bin/omarchy-brightness-display | 4 ++++ bin/omarchy-hyprland-monitor-internal | 13 +++++++------ shell/plugins/lock/Service.qml | 23 +++++++++++++++++++---- test/shell.d/monitor-recovery-test.sh | 2 ++ 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/bin/omarchy-brightness-display b/bin/omarchy-brightness-display index b3c3b74d..241ee091 100755 --- a/bin/omarchy-brightness-display +++ b/bin/omarchy-brightness-display @@ -32,6 +32,10 @@ if [[ $step == "off" ]]; then hyprctl dispatch 'hl.dsp.dpms({ action = "disable" })' >/dev/null 2>&1 exit 0 elif [[ $step == "on" ]]; then + # Skip the dispatch when every active display is already lit: a redundant + # DPMS enable right after system resume forces another modeset, which blanks + # the panel for a beat (visible flash at the unlock screen). + hyprctl monitors -j 2>/dev/null | jq -e '[.[] | select(.disabled == false)] | length > 0 and all(.dpmsStatus)' >/dev/null 2>&1 && exit 0 hyprctl dispatch 'hl.dsp.dpms({ action = "enable" })' >/dev/null 2>&1 exit 0 fi diff --git a/bin/omarchy-hyprland-monitor-internal b/bin/omarchy-hyprland-monitor-internal index 37c50e87..9916f95c 100755 --- a/bin/omarchy-hyprland-monitor-internal +++ b/bin/omarchy-hyprland-monitor-internal @@ -42,13 +42,14 @@ off() { } recover() { - if ! omarchy-hyprland-monitor-external-active; then - if omarchy-hyprland-toggle-enabled $TOGGLE; then - omarchy-hyprland-toggle $TOGGLE off - fi + # Runs from the clamshell watcher every few seconds, so it must be a no-op + # unless it actually re-enables a display: an unconditional wake here undoes + # lock-screen blanking and races the resume modeset into a visible flash. + omarchy-hyprland-monitor-external-active && return 0 + omarchy-hyprland-toggle-enabled $TOGGLE || return 0 - wake - fi + omarchy-hyprland-toggle $TOGGLE off + wake } toggle() { diff --git a/shell/plugins/lock/Service.qml b/shell/plugins/lock/Service.qml index b414f287..ca6bf6bd 100644 --- a/shell/plugins/lock/Service.qml +++ b/shell/plugins/lock/Service.qml @@ -108,7 +108,7 @@ Item { resetAuthenticationState() lockRequested = true - idleBlankTimer.restart() + armBlankTimer() logEvent("lock-requested") queueSessionLock() @@ -134,9 +134,14 @@ Item { runWake() } + function armBlankTimer() { + idleBlankTimer.armedAt = Date.now() + idleBlankTimer.restart() + } + function runWake() { if (!wakeProcess.running) wakeProcess.running = true - if (lockRequested) idleBlankTimer.restart() + if (lockRequested) armBlankTimer() } function runBlank() { @@ -370,7 +375,17 @@ Item { id: idleBlankTimer interval: 5000 repeat: false - onTriggered: if (root.lockRequested && !root.authenticating) root.runBlank() + property double armedAt: 0 + onTriggered: { + // A countdown frozen by suspend fires right after resume, which would + // blank the freshly woken unlock screen under the user. Wall-clock time + // exposes the gap: take a fresh run-up instead of blanking. + if (Date.now() - armedAt > interval + 2000) { + root.armBlankTimer() + return + } + if (root.lockRequested && !root.authenticating) root.runBlank() + } } Timer { @@ -395,7 +410,7 @@ Item { onAuthenticatingChanged: { if (!lockRequested) return if (authenticating) idleBlankTimer.stop() - else idleBlankTimer.restart() + else armBlankTimer() } FileView { diff --git a/test/shell.d/monitor-recovery-test.sh b/test/shell.d/monitor-recovery-test.sh index 063ef677..c0d002b0 100755 --- a/test/shell.d/monitor-recovery-test.sh +++ b/test/shell.d/monitor-recovery-test.sh @@ -58,7 +58,9 @@ grep -F "hyprctl dispatch 'hl.dsp.dpms({ action = \"enable\" })' >/dev/null 2>&1 grep -F 'hyprctl monitors all -j' "$monitor_internal" >/dev/null grep -F 'omarchy-hyprland-monitor-external-active' "$monitor_internal" >/dev/null grep -F 'wake' "$monitor_internal" >/dev/null +grep -F 'omarchy-hyprland-toggle-enabled $TOGGLE || return 0' "$monitor_internal" >/dev/null pass "internal monitor helper can re-enable disabled laptop displays" +pass "internal monitor recovery only wakes displays when it re-enables one" grep -F 'omarchy-hyprland-monitor-external-active' "$monitor_mirror" >/dev/null pass "internal mirror helper recovers when no active external display remains"