fix(idle): skip DPMS-off when docked (#127 mitigation)
All checks were successful
Check / eval (push) Successful in 5m24s

Blanking the only live output in dock mode led to a brick where wake and
even VT switch failed. Skip hypridle's 10m dpms off when any laptop
internal (eDP/LVDS/DSI) is present but not enabled; lock at 5m remains.
Undocked still blanks. Document LATER revisit for intentional
DPMS-when-docked once wake is proven.

V1: nix flake check --no-build. V3: docked idle stays lit under lock.
This commit is contained in:
2026-07-15 12:51:34 +01:00
parent e9ab0d8d64
commit 060bf524af
6 changed files with 81 additions and 57 deletions

View File

@@ -20,24 +20,10 @@ let
exit 1
'';
# Same transition binary hyprland.nix puts on PATH — re-derived here so
# hypridle can pin absolute store paths (thin PATH in the user unit).
# Must stay in lockstep with modules/home/hyprland.nix's transition.
# We only need undock/enable + dpms; call via PATH-installed name is
# fragile, so wake embeds the transition from a minimal import chain:
# dump+wake live in display-tools.nix; transition is the public name
# on PATH after HM activation — wake script uses absolute path from
# a local reimplementation of just the enable/dpms rescue when the
# full transition package isn't injectable without circular imports.
#
# Practical approach: invoke `nomarchy-display-wake` from PATH after
# activation; for the unit Exec lines use a wrapper that prefers the
# store path of a wake script built with transition = null fallback
# (hyprctl keyword + reload + dpms) matching undock's public contract.
# Absolute store paths for hypridle (thin PATH). Wake reuses the shared
# helper; a mini transition avoids a circular import on hyprland.nix.
displayWake =
let
# Minimal undock-equivalent for idle's store path (no circular dep on
# hyprland.nix). Prefer the real transition when on PATH at runtime.
miniTransition = pkgs.writeShellScriptBin "nomarchy-display-transition" ''
set -u
case "''${1:-}" in
@@ -65,6 +51,31 @@ let
inherit pkgs;
displayTransition = miniTransition;
}).displayWakeTool;
# #127 mitigation (2026-07-15): blanking the *only* live output while
# docked (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. 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: docked (internal $m not enabled)"
exit 0
fi
done
fi
hyprctl dispatch dpms off
'';
in
{
config = lib.mkIf cfg.idle.enable {
@@ -168,8 +179,8 @@ in
{ timeout = 300; on-timeout = "loginctl lock-session"; }
{
timeout = 600;
on-timeout = "hyprctl dispatch dpms off";
# Same rescue path as after_sleep (#127).
# Docked: skip blanking sole output (#127). Undocked: DPMS off.
on-timeout = "${dpmsOff}";
on-resume = "${lib.getExe displayWake}";
}
# Suspend only on battery, and sooner than the old fixed 30 min