fix(idle): #127 — input wakes the screen compositor-side; revert the clamshell skip
All checks were successful
Check / eval (push) Successful in 3m33s

The idle brick was never DPMS. Hyprland ships misc:key_press_enables_dpms
and misc:mouse_move_enables_dpms OFF, so hypridle's on-resume was the ONLY
caller of `dpms on` in the session — an idle daemon with the display held
hostage behind it. hypridle then deadlocks on an unfixed upstream bug
(hyprwm/hypridle#171, "Disconnected from pollfd id 1", open since 2025-09,
absent from 0.1.7 and main) and hangs WITHOUT exiting, so Restart=always
never fires and systemd still reports it active (running). Blank + deadlock
= a black seat no keypress can escape. That was the brick: two mundane bugs,
not one exotic DRM state. 19 disconnects in 11 days on the dev box, incl.
the incident day; the compositor is healthy through all of them.

Both options on, so the wake lives in the compositor where no daemon can
lose it — a dead hypridle now costs auto-lock, not the machine. The #127
clamshell DPMS-off skip (060bf52) is therefore reverted: idle blanks again
in every dock/lid state, and the 27" panels stop being held lit all night
under a static lock screen. #135 (an option to opt back in) is retired
unbuilt — there is nothing left to opt into.

TEST B's first run was invalid and nearly bought the wrong answer: it ran
`hyprctl dispatch dpms off` by hand, which no wake path watches, so "input
did not wake it" was guaranteed on any hardware. The undocked control
returning the SAME result — where the theory demanded a difference — is what
exposed it. Driven through hypridle instead, input wakes it in all three
configurations, including clamshell-docked on the sole live output.

Filed from the diagnosis: #146 (hypridle dies silently, systemd calls it
healthy — the surviving bug) and #147 (a re-login leaves session units dead;
graphical-session.target never re-enters, which is why Waybar already runs
from exec-once).

V3 on the incident hardware (AMD dev box, clamshell): hypridle deliberately
stopped, `dpms off` -> keypress woke it in 6s; the identical test before the
options stayed black the full 45s. V1: nix flake check, checks.option-docs,
checks.clamshell-logind, checks.docking-ux, checks.display-profiles; read the
emitted hyprland.conf/hypridle.conf rather than the source.

V3 pending: the fix is proven via runtime `hyprctl keyword`, not yet from our
own Nix — HARDWARE-QUEUE re-checks it after nomarchy-home + relogin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 18:48:22 +01:00
parent c32b51897e
commit a2151f424b
7 changed files with 187 additions and 238 deletions

View File

@@ -51,34 +51,6 @@ let
inherit pkgs;
displayTransition = miniTransition;
}).displayWakeTool;
# #127 mitigation (2026-07-15): blanking the *only* live output with the
# lid shut (eDP disabled) left a black seat that sometimes would not
# recover (and even VT switch looked dead). Skip DPMS-off when any laptop
# internal is present but not enabled — i.e. CLAMSHELL, which is narrower
# than "docked" and is deliberately what the incident was (#136, Bernardo:
# laptop closed). Docked with the lid OPEN keeps every output enabled and
# still blanks; that case is untested, not exempted. Lock still runs at 5 min.
# Revisit: intentional DPMS-on-dock once wake is trustworthy — see
# BACKLOG #127 and LATER "DPMS when docked".
dpmsOff = pkgs.writeShellScript "nomarchy-dpms-off" ''
set -euo pipefail
jq=${pkgs.jq}/bin/jq
internals=$(hyprctl monitors all -j 2>/dev/null \
| $jq -r '.[] | select(.name | test("^(eDP|LVDS|DSI)")) | .name' \
2>/dev/null || true)
if [ -n "$internals" ]; then
for m in $internals; do
if ! hyprctl monitors -j 2>/dev/null \
| $jq -e --arg m "$m" 'any(.[]; .name == $m)' >/dev/null 2>&1; then
${pkgs.util-linux}/bin/logger -t nomarchy-idle -- \
"dpms-off skipped: clamshell (internal $m present but off)"
exit 0
fi
done
fi
hyprctl dispatch dpms off
'';
in
{
config = lib.mkIf cfg.idle.enable {
@@ -182,8 +154,14 @@ in
{ timeout = 300; on-timeout = "loginctl lock-session"; }
{
timeout = 600;
# Docked: skip blanking sole output (#127). Undocked: DPMS off.
on-timeout = "${dpmsOff}";
# Blanks in every dock/lid state — the #127 clamshell skip is
# gone: it was a cure for a cause that does not exist (proven on
# hardware 2026-07-16, see ROADMAP). The wake no longer depends
# on this daemon surviving — misc:{key_press,mouse_move}_enables_dpms
# in hyprland.nix means input wakes the screen compositor-side.
on-timeout = "hyprctl dispatch dpms off";
# Same rescue path as after_sleep (#127): re-enables a disabled
# internal, which plain `dpms on` cannot do.
on-resume = "${lib.getExe displayWake}";
}
# Suspend only on battery, and sooner than the old fixed 30 min