Leave a locked session's display to the lock screen - #9152
Open
Pillumz wants to merge 1 commit into
Open
Conversation
The idle service runs omarchy-system-wake whenever its idle monitor reports activity during a cycle it started. While the session is locked that is the wrong reflex: the lock screen already lights the panel for input that reaches it and blanks it again five seconds later, and the idle monitor resuming is not evidence of input there. Hyprland's ext-idle-notify sends "resumed" the moment an idle inhibitor appears (IdleNotify.cpp, update() -> reset() while inhibited), so a browser starting media on a background workspace, or any client taking a wake lock, wakes a blanked lock screen with nobody at the machine - and nothing blanks it again until someone touches it. On an OLED that is the lock screen burning in overnight. Gate the wake on the lock service's state, read in-process the way the bar indicators read theirs, and log the skipped wake so `omarchy debug idle` shows what happened. The cycle itself still ends; only the display is left alone. Reading the lock in-process keeps the answer synchronous: the shell-out on the screensaver line reports the same locked property, but only after a round trip, and reads as unlocked whenever the call fails. Reproduced on Hyprland 0.56.2 / Omarchy 4.0.1 by locking, waiting for the monitor to idle, then playing a silent mpv clip into the locked session: the panel lit within a second and stayed lit after mpv exited, with zero evdev input in between. The new fixture test drives the real Service.qml against the compositor with a stand-in lock service registered after the idle service, the way shell.qml may load them, and a shadowed omarchy-system-wake: locked, no wake process is spawned; unlocked, the command runs as before.
Pillumz
force-pushed
the
fix/idle-no-wake-while-locked
branch
from
August 30, 2026 11:36
0ddddb3 to
194e57c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What happens
Lock the session and let the lock screen blank the display. Later the panel comes back on at the lock screen with nobody at the machine, and stays on until someone touches the keyboard or mouse - on an OLED, that is the lock screen burning in for hours.
omarchy debug idleshows a singleidle-monitor: active→idle-cycle-cancel: activity→process-start: wake omarchy-system-wakewith no input anywhere around it.Why
The idle service treats its
IdleMonitorresuming as user activity and runsomarchy-system-wake, which turns DPMS back on. But the monitor isrespectInhibitors: true, and Hyprland's ext-idle-notify implementation sendsresumedto an idled notification the moment an idle inhibitor appears (src/protocols/IdleNotify.cpp:update()while inhibited callsreset(), which sendsresumed). So any client taking an idle inhibitor - a browser starting media on a background workspace, a wake lock, an XWayland app suspending the screensaver - counts as activity with nobody there.Unlocked that is harmless. Locked it is the whole bug: the lock screen only re-arms its blank timer for pointer, click or key events delivered to its own surface, and none happened, so the display stays lit indefinitely.
Reproduced on Hyprland 0.56.2 / Omarchy 4.0.1 with nobody at the desk: lock, wait for
idle-monitor: idle, then start a silentmpvclip into the locked session from SSH. Hyprland logsNew idle inhibitor registered, the idle service logsactiveand runs the wake in the same second, the panel lights, and it is still lit after mpv exits. An evdev witness on every input device saw nothing throughout. The same run before the monitor idles does nothing, as the Hyprland code predicts.Fix
While the session is locked, the display belongs to the lock screen, so
cancelIdleCycle()no longer runsomarchy-system-wake; it logswake-skipped: session-lockedinstead and still ends the cycle. The decision lives inIdleModel.wakeAfterIdle()so it is unit-testable, andsessionLockedis added toomarchy-shell idle statussinceomarchy debug idleprints that and not the lock's status.The lock state comes from the lock service in-process,
shell.firstPartyServiceFor("omarchy.lock"), the same call the Stay Awake, DND, night-light and media widgets use for their services. Theomarchy-shell lock isLockedshell-out on the screensaver line answers from the samelockedproperty, but only after a subprocess round trip, and it reads as unlocked whenever the IPC call fails; a synchronous decision needs the direct read. The idle service never blanks a display itself - the only DPMS-off in the tree is the lock's own blank timer - so skipping its wake cannot strand a display that nothing else can restore: Hyprland'skey_press_enables_dpms/mouse_move_enables_dpmslight the panel on keys, motion and gestures, the lock surface's own click and key handlers run the lock's wake, andfinishUnlockruns it unconditionally (fingerprint unlock at a blanked panel included). Clamshell reconciliation, whichomarchy-system-wakealso does, has the lid switch bind,omarchy-hyprland-monitor-watchand the sleep lock as independent owners.Why this layer: making the lock screen re-blank on a resume it did not ask for would still flash the panel; refusing inside
omarchy-system-wakewhile locked would break the lock's own wake, which runs the same command; the Hyprland behaviour is protocol-legal and not ours to change; andrespectInhibitors: falsewould bring the screensaver back over video playback.One deliberate consequence: Hyprland does not enable DPMS for scroll (
axis) or bare button events, only for keys, motion and gesture starts, so a scroll-wheel nudge at a blanked lock screen used to light the panel only through this accidental idle wake (and then never blanked again). After this change it does nothing; a click or any pointer motion still wakes. If a scroll wake is wanted, it belongs inLockViewas a wheel handler that emitswakeRequested, which I am happy to follow up with.#7131 adds a byte-identical
lockServiceline with its own comment and alockInFlightproperty; #7132 reads the same service throughfirstPartyServiceForinside a function. Whichever lands second conflicts in the property block and instatusJson, and the two names should collapse into one.Tests
test/shell.d/idle-test.sh: unit cases forwakeAfterIdle, plus source assertions that keep the wiring honest on machines where the compositor fixture has to skip.test/shell.d/idle-lock-wake-test.sh(new, compositor-backed like the lock fixtures): loads the realService.qmlinto a throwaway quickshell with a stand-in shell whose services register by reassignment, asshell.qmldoes. The lock service is registered after the idle service and the fixture checkssessionLockedfollows, so a futureserviceForthat caches would fail here rather than silently disable the fix. Then it driveshandleActiveSignal()twice: locked, no wake process is spawned and the skip is logged; unlocked, the wake process spawns and a PATH-shadowedomarchy-system-wakeactually runs (marker file). The fake config uses day-long timeouts and the service is destroyed once the wake exits, so an orphaned fixture can never lock the developer's session; the test skips if the login shell would not resolve the shadow (a machine dev-linked to another checkout); the launch is undertimeout 15. Against the unpatched service the fixture fails on all eight assertions../test/shell: the same six pre-existing environmental failures with and without this change (packaging checkout, Claude usage collector, network guard, bar icon geometry); everything else passes.