From 060bf524affc2773cbcdb9ca8a13e7c078d762d7 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Wed, 15 Jul 2026 12:51:34 +0100 Subject: [PATCH] fix(idle): skip DPMS-off when docked (#127 mitigation) 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. --- README.md | 2 +- agent/BACKLOG.md | 31 +++++++++++++++++++-------- agent/HARDWARE-QUEUE.md | 43 ++++++++++++------------------------- agent/JOURNAL.md | 9 ++++++++ docs/ROADMAP.md | 6 ++++++ modules/home/idle.nix | 47 +++++++++++++++++++++++++---------------- 6 files changed, 81 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index acea1a3..9364e5d 100644 --- a/README.md +++ b/README.md @@ -258,7 +258,7 @@ two tables below are split along exactly that line. | `nomarchy.batteryNotify.enable` | `true` | Low-battery toasts at the bar's thresholds — 25% low, 10% critical (stays up until dismissed); silent no-op on machines without a battery | | `nomarchy.dockAudio.enable` | `true` | On a fresh external-monitor plug, PipeWire/WirePlumber are reprobed after hardware settles and the highest-priority available HDMI/DP/USB sink becomes default, with a toast and journal result. A later manual speaker choice sticks until the next physical plug; unplug falls back to built-in. Manual route: System › Audio | | `nomarchy.firstBootWelcome.enable` | `true` | One dismissible “you're set” toast on the first session (SUPER+M / SUPER+T / SUPER+? + network pointer); marker is `settings.firstBootShown` in the flake checkout | -| `nomarchy.idle.enable` | `true` | hyprlock + hypridle (idle lock 5 min, display off 10, suspend 15 min — battery-only; uses `nomarchy-suspend` so suspend-then-hibernate applies when enabled) | +| `nomarchy.idle.enable` | `true` | hyprlock + hypridle (lock 5 min; display off 10 min **except when docked** — laptop internal present but disabled skips DPMS, #127; suspend 15 min battery-only via `nomarchy-suspend`) | | `nomarchy.idle.fingerprint` | `false` | Unlock the lock screen with a fingerprint as well as the password, and say so on the input field. Set it alongside `nomarchy.hardware.fingerprint.pam` in system.nix: hyprlock is configured from Home Manager, which cannot read the NixOS option — and hyprlock does **not** take a fingerprint through PAM at all (its PAM stack runs only on submit), so it uses its own fprintd backend that this switch turns on | | `nomarchy.yazi.enable` | `true` | yazi TUI file manager, themed + curated plugins | | `nomarchy.osd.enable` | `true` | swayosd on-screen display for volume/brightness/mute | diff --git a/agent/BACKLOG.md b/agent/BACKLOG.md index a46fb8c..6217147 100644 --- a/agent/BACKLOG.md +++ b/agent/BACKLOG.md @@ -55,16 +55,21 @@ or (b) undock → `nomarchy-display-transition undock` re-enabling the panel. Both failed. `after_sleep_cmd` only does `hyprctl dispatch dpms on` and never re-enables a disabled internal. -**Progress 2026-07-15 (no-SSH path + recovery hardening):** undock/enable -always end with `dpms on`; hypridle `on-resume` / `after_sleep` run -`nomarchy-display-wake` (dpms + zero-output undock rescue + auto-dump); -`nomarchy-display-dump` + SUPER+SHIFT+D + TTY (`Ctrl+Alt+F3`) so capture -does not need SSH. V3 still needs a real docked idle repro. +**Progress 2026-07-15:** +- Recovery tooling: undock ends with `dpms on`; `nomarchy-display-wake` / + `nomarchy-display-dump` (SUPER+SHIFT+D; TTY if it works). +- **Mitigation (shipped):** do **not** DPMS-off when docked — if any + laptop internal (eDP/LVDS/DSI) exists in `monitors all` but is not + enabled, the 10 min listener skips blanking. Lock at 5 min still runs; + undocked laptops still DPMS-off as before. Bernardo: Ctrl+Alt+F3 did + not yield a usable TTY on the original brick, so prevention > dump path. +- **Revisit later (LATER):** intentional DPMS-when-docked once wake is + proven trustworthy (monitor power-save without a brick). -**Pass:** docked on AC, idle past lock + DPMS; external KB/mouse restores -the external; if the external is unplugged while black, eDP comes back -without reboot; journal shows display-watch undock `result=ok` when the -cable leaves. Capture steps in HARDWARE-QUEUE (TTY dump preferred). +**Pass (current mitigation):** docked on AC, idle past 10+ min → external +stays lit under hyprlock (journal: `dpms-off skipped: docked`); undocked +still blanks at 10 min and wakes on input. Full original pass (wake after +DPMS-while-docked) deferred to the LATER revisit. ## NEXT @@ -131,6 +136,14 @@ offline ISO's behaviour untouched (`checks.*` for the offline path stay green). ## LATER +- **DPMS when docked (revisit #127):** today we **skip** display blanking + while any laptop internal is present-but-disabled (dock/clamshell), so + idle only locks. That avoids blanking the sole external and the brick + where wake/TTY failed (2026-07-15). Revisit when we want monitor + power-save on dock again: re-enable DPMS-off for that mode only if + `nomarchy-display-wake` (or better) reliably restores the external and + undock-while-black restores eDP on the AMD dock setup — without + depending on SSH. Until then, leave the skip in `modules/home/idle.nix`. - **Wallpapers artifact split** (ROADMAP § Faster switches — decided, deferred): pinned `Nomarchy-wallpapers` input so a state write stops re-copying 86 MB. Follow-on: pre-built theme variants if switches are diff --git a/agent/HARDWARE-QUEUE.md b/agent/HARDWARE-QUEUE.md index d1ed0b3..e222897 100644 --- a/agent/HARDWARE-QUEUE.md +++ b/agent/HARDWARE-QUEUE.md @@ -49,35 +49,20 @@ Everything else below stays open; order is convenience, not a gate. restores **only** the radios that were on before. If Wi-Fi was already off, it must stay off after disengage. Glyph must **not** appear when airplane is off. -- [ ] **#127 docked idle blackout (AMD dev box, 2026-07-15)** — - **Incident (FAILED, unsolicited):** laptop docked, idle a few minutes, - screens went black ("sleep"). External keyboard/mouse did not restore - any display; unplugging the external did not re-enable the laptop - panel; Caps Lock LED still toggled (machine alive). Forced recovery - was needed; a long Grok session died with the brick. - **Important distinction:** on AC, hypridle is *not* supposed to - suspend — only lock (5m) + `dpms off` (10m). Confirm next time with - dump file or journal for `Suspending|PM: suspend` vs hypridle lines - so we know DPMS-stuck vs real S3. - **No SSH required** (post-#127 tooling): Caps Lock alive ⇒ machine is - up. Prefer **local TTY** over SSH: - 1. `Ctrl+Alt+F3` → log in on the text console. - 2. `nomarchy-display-wake` — try recovery (dpms on + undock rescue). - 3. `nomarchy-display-dump` — writes `~/nomarchy-display-dump-*.txt` - (monitors, journals, DRM status). Open that file after the seat - is usable; no second machine needed. - 4. Optional while unlocked: `SUPER+SHIFT+D` runs the same dump. - Auto: undock enable-fail and zero-output wake also dump under `~/`. - **Repro (after `nomarchy-home` + relogin so hypridle/watcher pick up):** - 1. Dock, confirm dock mode (eDP disabled). - 2. Idle past 10 min **or** force: `loginctl lock-session` then - `hyprctl dispatch dpms off`. - 3. External KB/mouse — external should wake (`nomarchy-display-wake` - path). If not: TTY → dump → note. - 4. Unplug external while black — eDP should return (undock + `dpms on`). - **Pass:** input wakes external while docked; undock while black restores - eDP without reboot; dump files only appear on real failures. - Related: docking recovery round 8 (awake undock) and BACKLOG #127. +- [ ] **#127 docked idle — mitigation check (AMD dev box)** — + **Context:** original brick = docked idle → black, no input wake, undock + no eDP, Caps Lock alive; Ctrl+Alt+F3 gave no usable TTY. **Shipped + mitigation:** skip DPMS-off while docked (internal present-but-disabled); + lock at 5m still. Full DPMS-when-docked deferred (BACKLOG LATER). + **After `nomarchy-home` + relogin:** + 1. Dock (eDP disabled in `hyprctl monitors all -j` / not in `monitors`). + 2. Idle past 10+ min (or wait for hypridle). + 3. **Pass mitigation:** external still shows hyprlock (not black power-save); + `journalctl --user -t nomarchy-idle --since '-20 min'` has + `dpms-off skipped: docked`. + 4. Undocked control: eDP only → at 10 min screen *does* blank; input wakes. + Optional: `nomarchy-display-dump` / SUPER+SHIFT+D for other display faults. + Related: docking round 8; LATER “DPMS when docked”. - [ ] **#96 battery-limit row on threshold-less firmware (Acer M5-481T)** — on the Acer (live or installed session with the commit carrying this entry): open System (`SUPER+CTRL+I`). **Pass:** a "Battery limit" row diff --git a/agent/JOURNAL.md b/agent/JOURNAL.md index cd559b2..40384b4 100644 --- a/agent/JOURNAL.md +++ b/agent/JOURNAL.md @@ -19,6 +19,15 @@ Template: --- +## 2026-07-15 — #127 skip DPMS-off when docked (mitigation) +- **Task:** Bernardo: no usable TTY on brick; prefer prevent over dump. +- **Did:** hypridle 10m listener skips `dpms off` when an internal + (eDP/LVDS/DSI) is present-but-disabled; lock still at 5m. Documented + LATER “DPMS when docked” revisit. Dump/wake tools remain. +- **Verified:** V0/V1 flake check. V3: HARDWARE-QUEUE mitigation check. +- **Pending:** human docked idle past 10m → external lit under lock. +- **Next suggestion:** V3 docked idle, or other queue. + ## 2026-07-15 — #127 no-SSH dump + docked idle wake harden - **Task:** Tackle docked idle blackout without needing SSH mid-brick. - **Did:** `nomarchy-display-dump` / `nomarchy-display-wake`; undock always diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index f163013..6ff2049 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -441,6 +441,12 @@ Design/decision records and a running log of shipped work (items marked `nomarchy-suspend` (hypridle + Power menu). LUKS: unlock session before encrypted hibernate phase so resume is LUKS-only (not hyprlock twice). Menu row self-gates on CanHibernate. V3: HARDWARE-QUEUE bag-carry test. +- **Docked idle DPMS (#127 mitigation):** after a docked-idle brick (alive + machine, black external, undock no eDP, no usable Ctrl+Alt+F3), hypridle + **skips DPMS-off** when any laptop internal is present but not enabled + (dock/clamshell). Lock at 5 min unchanged; undocked still blanks at 10 min. + Intentional DPMS-when-docked is LATER (BACKLOG) once wake is proven. + Tooling kept: `nomarchy-display-wake` / `nomarchy-display-dump`. - ✓ **Generation prune (#128):** weekly `nomarchy-gen-prune` (system + HM): delete only gens older than **14 days** that are also beyond the **3 most recent past** gens (current always kept). Stock `nix.gc` no longer uses diff --git a/modules/home/idle.nix b/modules/home/idle.nix index 87eb4d1..0038d59 100644 --- a/modules/home/idle.nix +++ b/modules/home/idle.nix @@ -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