fix(idle): skip DPMS-off when docked (#127 mitigation)
All checks were successful
Check / eval (push) Successful in 5m24s
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:
@@ -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.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.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.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.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.yazi.enable` | `true` | yazi TUI file manager, themed + curated plugins |
|
||||||
| `nomarchy.osd.enable` | `true` | swayosd on-screen display for volume/brightness/mute |
|
| `nomarchy.osd.enable` | `true` | swayosd on-screen display for volume/brightness/mute |
|
||||||
|
|||||||
@@ -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`
|
panel. Both failed. `after_sleep_cmd` only does `hyprctl dispatch dpms on`
|
||||||
and never re-enables a disabled internal.
|
and never re-enables a disabled internal.
|
||||||
|
|
||||||
**Progress 2026-07-15 (no-SSH path + recovery hardening):** undock/enable
|
**Progress 2026-07-15:**
|
||||||
always end with `dpms on`; hypridle `on-resume` / `after_sleep` run
|
- Recovery tooling: undock ends with `dpms on`; `nomarchy-display-wake` /
|
||||||
`nomarchy-display-wake` (dpms + zero-output undock rescue + auto-dump);
|
`nomarchy-display-dump` (SUPER+SHIFT+D; TTY if it works).
|
||||||
`nomarchy-display-dump` + SUPER+SHIFT+D + TTY (`Ctrl+Alt+F3`) so capture
|
- **Mitigation (shipped):** do **not** DPMS-off when docked — if any
|
||||||
does not need SSH. V3 still needs a real docked idle repro.
|
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
|
**Pass (current mitigation):** docked on AC, idle past 10+ min → external
|
||||||
the external; if the external is unplugged while black, eDP comes back
|
stays lit under hyprlock (journal: `dpms-off skipped: docked`); undocked
|
||||||
without reboot; journal shows display-watch undock `result=ok` when the
|
still blanks at 10 min and wakes on input. Full original pass (wake after
|
||||||
cable leaves. Capture steps in HARDWARE-QUEUE (TTY dump preferred).
|
DPMS-while-docked) deferred to the LATER revisit.
|
||||||
|
|
||||||
## NEXT
|
## NEXT
|
||||||
|
|
||||||
@@ -131,6 +136,14 @@ offline ISO's behaviour untouched (`checks.*` for the offline path stay green).
|
|||||||
|
|
||||||
## LATER
|
## 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,
|
- **Wallpapers artifact split** (ROADMAP § Faster switches — decided,
|
||||||
deferred): pinned `Nomarchy-wallpapers` input so a state write stops
|
deferred): pinned `Nomarchy-wallpapers` input so a state write stops
|
||||||
re-copying 86 MB. Follow-on: pre-built theme variants if switches are
|
re-copying 86 MB. Follow-on: pre-built theme variants if switches are
|
||||||
|
|||||||
@@ -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
|
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
|
off, it must stay off after disengage. Glyph must **not** appear when
|
||||||
airplane is off.
|
airplane is off.
|
||||||
- [ ] **#127 docked idle blackout (AMD dev box, 2026-07-15)** —
|
- [ ] **#127 docked idle — mitigation check (AMD dev box)** —
|
||||||
**Incident (FAILED, unsolicited):** laptop docked, idle a few minutes,
|
**Context:** original brick = docked idle → black, no input wake, undock
|
||||||
screens went black ("sleep"). External keyboard/mouse did not restore
|
no eDP, Caps Lock alive; Ctrl+Alt+F3 gave no usable TTY. **Shipped
|
||||||
any display; unplugging the external did not re-enable the laptop
|
mitigation:** skip DPMS-off while docked (internal present-but-disabled);
|
||||||
panel; Caps Lock LED still toggled (machine alive). Forced recovery
|
lock at 5m still. Full DPMS-when-docked deferred (BACKLOG LATER).
|
||||||
was needed; a long Grok session died with the brick.
|
**After `nomarchy-home` + relogin:**
|
||||||
**Important distinction:** on AC, hypridle is *not* supposed to
|
1. Dock (eDP disabled in `hyprctl monitors all -j` / not in `monitors`).
|
||||||
suspend — only lock (5m) + `dpms off` (10m). Confirm next time with
|
2. Idle past 10+ min (or wait for hypridle).
|
||||||
dump file or journal for `Suspending|PM: suspend` vs hypridle lines
|
3. **Pass mitigation:** external still shows hyprlock (not black power-save);
|
||||||
so we know DPMS-stuck vs real S3.
|
`journalctl --user -t nomarchy-idle --since '-20 min'` has
|
||||||
**No SSH required** (post-#127 tooling): Caps Lock alive ⇒ machine is
|
`dpms-off skipped: docked`.
|
||||||
up. Prefer **local TTY** over SSH:
|
4. Undocked control: eDP only → at 10 min screen *does* blank; input wakes.
|
||||||
1. `Ctrl+Alt+F3` → log in on the text console.
|
Optional: `nomarchy-display-dump` / SUPER+SHIFT+D for other display faults.
|
||||||
2. `nomarchy-display-wake` — try recovery (dpms on + undock rescue).
|
Related: docking round 8; LATER “DPMS when docked”.
|
||||||
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.
|
|
||||||
- [ ] **#96 battery-limit row on threshold-less firmware (Acer M5-481T)** —
|
- [ ] **#96 battery-limit row on threshold-less firmware (Acer M5-481T)** —
|
||||||
on the Acer (live or installed session with the commit carrying this
|
on the Acer (live or installed session with the commit carrying this
|
||||||
entry): open System (`SUPER+CTRL+I`). **Pass:** a "Battery limit" row
|
entry): open System (`SUPER+CTRL+I`). **Pass:** a "Battery limit" row
|
||||||
|
|||||||
@@ -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
|
## 2026-07-15 — #127 no-SSH dump + docked idle wake harden
|
||||||
- **Task:** Tackle docked idle blackout without needing SSH mid-brick.
|
- **Task:** Tackle docked idle blackout without needing SSH mid-brick.
|
||||||
- **Did:** `nomarchy-display-dump` / `nomarchy-display-wake`; undock always
|
- **Did:** `nomarchy-display-dump` / `nomarchy-display-wake`; undock always
|
||||||
|
|||||||
@@ -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
|
`nomarchy-suspend` (hypridle + Power menu). LUKS: unlock session before
|
||||||
encrypted hibernate phase so resume is LUKS-only (not hyprlock twice).
|
encrypted hibernate phase so resume is LUKS-only (not hyprlock twice).
|
||||||
Menu row self-gates on CanHibernate. V3: HARDWARE-QUEUE bag-carry test.
|
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):
|
- ✓ **Generation prune (#128):** weekly `nomarchy-gen-prune` (system + HM):
|
||||||
delete only gens older than **14 days** that are also beyond the **3 most
|
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
|
recent past** gens (current always kept). Stock `nix.gc` no longer uses
|
||||||
|
|||||||
@@ -20,24 +20,10 @@ let
|
|||||||
exit 1
|
exit 1
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Same transition binary hyprland.nix puts on PATH — re-derived here so
|
# Absolute store paths for hypridle (thin PATH). Wake reuses the shared
|
||||||
# hypridle can pin absolute store paths (thin PATH in the user unit).
|
# helper; a mini transition avoids a circular import on hyprland.nix.
|
||||||
# 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.
|
|
||||||
displayWake =
|
displayWake =
|
||||||
let
|
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" ''
|
miniTransition = pkgs.writeShellScriptBin "nomarchy-display-transition" ''
|
||||||
set -u
|
set -u
|
||||||
case "''${1:-}" in
|
case "''${1:-}" in
|
||||||
@@ -65,6 +51,31 @@ let
|
|||||||
inherit pkgs;
|
inherit pkgs;
|
||||||
displayTransition = miniTransition;
|
displayTransition = miniTransition;
|
||||||
}).displayWakeTool;
|
}).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
|
in
|
||||||
{
|
{
|
||||||
config = lib.mkIf cfg.idle.enable {
|
config = lib.mkIf cfg.idle.enable {
|
||||||
@@ -168,8 +179,8 @@ in
|
|||||||
{ timeout = 300; on-timeout = "loginctl lock-session"; }
|
{ timeout = 300; on-timeout = "loginctl lock-session"; }
|
||||||
{
|
{
|
||||||
timeout = 600;
|
timeout = 600;
|
||||||
on-timeout = "hyprctl dispatch dpms off";
|
# Docked: skip blanking sole output (#127). Undocked: DPMS off.
|
||||||
# Same rescue path as after_sleep (#127).
|
on-timeout = "${dpmsOff}";
|
||||||
on-resume = "${lib.getExe displayWake}";
|
on-resume = "${lib.getExe displayWake}";
|
||||||
}
|
}
|
||||||
# Suspend only on battery, and sooner than the old fixed 30 min
|
# Suspend only on battery, and sooner than the old fixed 30 min
|
||||||
|
|||||||
Reference in New Issue
Block a user