feat(audio): prefer dock/HDMI/USB sinks in WirePlumber (#87)
Some checks failed
Check / eval (push) Has been cancelled
Some checks failed
Check / eval (push) Has been cancelled
Raise priority.session for HDMI/DisplayPort (1100) and USB (1050) ALSA outputs so the default sink follows dock plug/unplug; built-in analog wins again when externals disappear. Rules live in modules/nixos/dock-audio-rules.nix; checks.dock-audio covers the contract. Verified: V2 — flake check --no-build; checks.dock-audio green. V3 pending: HARDWARE-QUEUE #87 real HDMI/USB-dock hotplug.
This commit is contained in:
@@ -278,7 +278,7 @@ option, e.g. `services.easyeffects.enable = lib.mkForce false;` — see
|
|||||||
| `nomarchy.system.fileManager.enable` | `true` | Thunar GUI + gvfs/tumbler/udisks2 (the "open folder" handler) |
|
| `nomarchy.system.fileManager.enable` | `true` | Thunar GUI + gvfs/tumbler/udisks2 (the "open folder" handler) |
|
||||||
| `nomarchy.system.greeter.enable` | `true` | greetd/tuigreet |
|
| `nomarchy.system.greeter.enable` | `true` | greetd/tuigreet |
|
||||||
| `nomarchy.system.greeter.autoLogin` | `null` | Auto-login this user into Hyprland (installer sets it on LUKS machines) |
|
| `nomarchy.system.greeter.autoLogin` | `null` | Auto-login this user into Hyprland (installer sets it on LUKS machines) |
|
||||||
| `nomarchy.system.audio.enable` | `true` | Pipewire stack |
|
| `nomarchy.system.audio.enable` | `true` | Pipewire stack; WirePlumber prefers HDMI/USB (dock) sinks when present so default output follows dock/undock |
|
||||||
| `nomarchy.system.bluetooth.enable` | `true` | Bluetooth + blueman |
|
| `nomarchy.system.bluetooth.enable` | `true` | Bluetooth + blueman |
|
||||||
| `nomarchy.system.autoTimezone.enable` | `false` | Automatic timezone (geoclue + automatic-timezoned) — the clock follows your location; toggle from System › Auto timezone (a menu enable rebuilds: it has to unset the static `time.timeZone`) |
|
| `nomarchy.system.autoTimezone.enable` | `false` | Automatic timezone (geoclue + automatic-timezoned) — the clock follows your location; toggle from System › Auto timezone (a menu enable rebuilds: it has to unset the static `time.timeZone`) |
|
||||||
| `nomarchy.system.snapper.enable` | `false` | Hourly/daily BTRFS timeline snapshots + `nixos-rebuild-snap` (installer enables it; no-op unless root is BTRFS) |
|
| `nomarchy.system.snapper.enable` | `false` | Hourly/daily BTRFS timeline snapshots + `nixos-rebuild-snap` (installer enables it; no-op unless root is BTRFS) |
|
||||||
|
|||||||
@@ -55,10 +55,6 @@ runtime-computed `resume_offset` into the boot cmdline, and the dev box runs
|
|||||||
exactly this layout (`/proc/cmdline` verified) but isn't Nomarchy. Steps in
|
exactly this layout (`/proc/cmdline` verified) but isn't Nomarchy. Steps in
|
||||||
HARDWARE-QUEUE. Close #76 on PASS.
|
HARDWARE-QUEUE. Close #76 on PASS.
|
||||||
|
|
||||||
### 87. Dock audio sink follows plug/unplug (`VISION § C`)
|
|
||||||
Default audio sink: dock sink on plug, internal on unplug (WirePlumber
|
|
||||||
policy or udev hook). Complements #86; can ship independently.
|
|
||||||
|
|
||||||
## LATER
|
## LATER
|
||||||
|
|
||||||
- **Wallpapers artifact split** (ROADMAP § Faster switches — decided,
|
- **Wallpapers artifact split** (ROADMAP § Faster switches — decided,
|
||||||
@@ -111,8 +107,8 @@ high-ROI, etc.) live in the journal + ROADMAP — not here.*
|
|||||||
2026-07-10). Keep #59 commented install guidance; no
|
2026-07-10). Keep #59 commented install guidance; no
|
||||||
`nomarchy.hardware.nvidia.*` until a hybrid maintainer + queue.
|
`nomarchy.hardware.nvidia.*` until a hybrid maintainer + queue.
|
||||||
|
|
||||||
_(#80–#83 + #85–#86 shipped 2026-07-11. Remaining dock slice: **#87**
|
_(#80–#83 + #85–#87 shipped 2026-07-11. Dock V3 residuals: #86
|
||||||
dock audio. #86 V3 clamshell on real external display → HARDWARE-QUEUE.)_
|
clamshell + #87 audio on real hardware → HARDWARE-QUEUE.)_
|
||||||
|
|
||||||
### v1.0 pointer
|
### v1.0 pointer
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,13 @@ the **T14s** (webcam case).
|
|||||||
— should suspend as usual. Optional: with a docked display-profile
|
— should suspend as usual. Optional: with a docked display-profile
|
||||||
(`eDP-1` disable), confirm clamshell layout still looks right after
|
(`eDP-1` disable), confirm clamshell layout still looks right after
|
||||||
lid close. Pass = no surprise suspend when docked.
|
lid close. Pass = no surprise suspend when docked.
|
||||||
|
- [ ] **#87 dock audio sink follow** — V2 asserts WirePlumber
|
||||||
|
`90-nomarchy-dock-audio` rules (HDMI/USB priority). On a laptop
|
||||||
|
with HDMI or USB-dock audio: play sound on speakers, plug external
|
||||||
|
audio → default sink should move to HDMI/dock (`wpctl status` shows
|
||||||
|
`*` on the external sink); unplug → `*` returns to built-in.
|
||||||
|
Manual `wpctl set-default` still works; Bluetooth may outrank dock
|
||||||
|
(by design). Pass = auto switch without menu intervention.
|
||||||
- [ ] **#55 fingerprint enroll on real reader** — with
|
- [ ] **#55 fingerprint enroll on real reader** — with
|
||||||
`nomarchy.hardware.fingerprint.enable` and a physical reader: System ›
|
`nomarchy.hardware.fingerprint.enable` and a physical reader: System ›
|
||||||
Fingerprint › Enroll a finger; List shows it; Verify succeeds; optional
|
Fingerprint › Enroll a finger; List shows it; Verify succeeds; optional
|
||||||
|
|||||||
@@ -19,6 +19,17 @@ Template:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-07-11 — #87 dock audio sink priority
|
||||||
|
- **Task:** NEXT #87 — default audio follows dock plug/unplug.
|
||||||
|
- **Did:** WirePlumber `90-nomarchy-dock-audio` (HDMI/DP session prio
|
||||||
|
1100, USB 1050) via `modules/nixos/dock-audio-rules.nix` + default.nix
|
||||||
|
pipewire. `checks.dock-audio` pure rule asserts + conf-file VM. V3
|
||||||
|
hotplug steps in HARDWARE-QUEUE. Closed #87.
|
||||||
|
- **Verified:** **V2** — `nix flake check --no-build`; `checks.dock-audio`
|
||||||
|
green (pure rule asserts + conf installed under wireplumber.conf.d).
|
||||||
|
- **Pending:** V3 real HDMI/USB-dock hotplug (HARDWARE-QUEUE).
|
||||||
|
- **Next suggestion:** QA sweep or LATER wallpapers split if promoted.
|
||||||
|
|
||||||
## 2026-07-11 — #86 clamshell HandleLidSwitchDocked
|
## 2026-07-11 — #86 clamshell HandleLidSwitchDocked
|
||||||
- **Task:** NEXT #86 — lid closed + external monitor must not suspend.
|
- **Task:** NEXT #86 — lid closed + external monitor must not suspend.
|
||||||
- **Did:** Explicit `HandleLidSwitchDocked=ignore` in
|
- **Did:** Explicit `HandleLidSwitchDocked=ignore` in
|
||||||
|
|||||||
@@ -394,6 +394,12 @@ Design/decision records and a running log of shipped work (items marked
|
|||||||
power management is on (systemd docked = external display present).
|
power management is on (systemd docked = external display present).
|
||||||
Undocked lid suspend left to upstream. `checks.clamshell-logind` asserts
|
Undocked lid suspend left to upstream. `checks.clamshell-logind` asserts
|
||||||
logind.conf + active logind. Real lid+external V3 in HARDWARE-QUEUE.
|
logind.conf + active logind. Real lid+external V3 in HARDWARE-QUEUE.
|
||||||
|
✓ **Dock audio sink priority (#87, `VISION § C`):** WirePlumber
|
||||||
|
`90-nomarchy-dock-audio` raises `priority.session` for HDMI/DP and USB
|
||||||
|
ALSA sinks so they become default when present; unplug falls back to
|
||||||
|
built-in. Rules in `modules/nixos/dock-audio-rules.nix`;
|
||||||
|
`checks.dock-audio` pure + conf-file VM. Real hotplug V3 in
|
||||||
|
HARDWARE-QUEUE.
|
||||||
- ✓ **Hibernate double-unlock:** on resume from hibernate the LUKS
|
- ✓ **Hibernate double-unlock:** on resume from hibernate the LUKS
|
||||||
passphrase already gates the machine, but locking hyprlock before sleep
|
passphrase already gates the machine, but locking hyprlock before sleep
|
||||||
meant a second password. Fixed by *not locking* before an encrypted
|
meant a second password. Fixed by *not locking* before an encrypted
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ Reference: [HARDWARE.md](HARDWARE.md) §4–§5, §11 (doctor).
|
|||||||
|
|
||||||
| Idea | Intent | Notes |
|
| Idea | Intent | Notes |
|
||||||
|------|--------|-------|
|
|------|--------|-------|
|
||||||
| **Dock life** | Lid closed + external only, wake, default audio sink on undock | ✓ #86 clamshell logind ignore when docked; #87 audio still open |
|
| **Dock life** ✓ | Lid closed + external only, wake, default audio sink on undock | ✓ #86 clamshell logind; ✓ #87 WirePlumber HDMI/USB priority (V3 hotplug QA) |
|
||||||
| **Hibernate/sleep confidence** | Doctor: resume device, swap size, clean suspend journal | Trust > new power UI |
|
| **Hibernate/sleep confidence** | Doctor: resume device, swap size, clean suspend journal | Trust > new power UI |
|
||||||
| **Battery health readout** ✓ | Cycles + design capacity % in doctor where sysfs allows | ✓ shipped #80 (report-only; charge limit is a separate row) |
|
| **Battery health readout** ✓ | Cycles + design capacity % in doctor where sysfs allows | ✓ shipped #80 (report-only; charge limit is a separate row) |
|
||||||
| **Charge-limit instant apply** | Already PROPOSED `[blocked:hw]` | Keep privilege tradeoff explicit |
|
| **Charge-limit instant apply** | Already PROPOSED `[blocked:hw]` | Keep privilege tradeoff explicit |
|
||||||
|
|||||||
55
flake.nix
55
flake.nix
@@ -510,6 +510,61 @@
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Dock audio sink priority (#87): WirePlumber rules prefer
|
||||||
|
# HDMI/DP/USB sinks over built-in analog. Pure unit test of the
|
||||||
|
# rule set + a minimal VM that the conf file is installed where
|
||||||
|
# WirePlumber reads it (XDG_DATA_DIRS / share/wireplumber).
|
||||||
|
dock-audio =
|
||||||
|
let
|
||||||
|
inherit (nixpkgs) lib;
|
||||||
|
rules = import ./modules/nixos/dock-audio-rules.nix;
|
||||||
|
alsa = rules."monitor.alsa.rules" or [ ];
|
||||||
|
expect = cond: msg: lib.assertMsg cond "dock-audio: ${msg}";
|
||||||
|
json = builtins.toJSON alsa;
|
||||||
|
pureOk =
|
||||||
|
expect (builtins.isList alsa && builtins.length alsa >= 2)
|
||||||
|
"expected ≥2 alsa rule groups"
|
||||||
|
&& expect (lib.hasInfix "hdmi" json)
|
||||||
|
"HDMI match missing from rules JSON"
|
||||||
|
&& expect (lib.hasInfix "usb-" json || lib.hasInfix "usb" json)
|
||||||
|
"USB match missing from rules JSON"
|
||||||
|
&& expect (lib.hasInfix "priority.session" json)
|
||||||
|
"priority.session missing"
|
||||||
|
&& expect (lib.hasInfix "1100" json)
|
||||||
|
"HDMI session priority 1100 missing";
|
||||||
|
in
|
||||||
|
assert pureOk;
|
||||||
|
pkgs.testers.runNixOSTest {
|
||||||
|
name = "nomarchy-dock-audio";
|
||||||
|
nodes.machine = { ... }: {
|
||||||
|
services.pipewire = {
|
||||||
|
enable = true;
|
||||||
|
alsa.enable = true;
|
||||||
|
pulse.enable = true;
|
||||||
|
wireplumber.enable = true;
|
||||||
|
wireplumber.extraConfig."90-nomarchy-dock-audio" =
|
||||||
|
import ./modules/nixos/dock-audio-rules.nix;
|
||||||
|
};
|
||||||
|
# WirePlumber is a user service; enable linger-less check via
|
||||||
|
# the system data dir the package puts conf into.
|
||||||
|
environment.systemPackages = [ pkgs.wireplumber ];
|
||||||
|
};
|
||||||
|
testScript = ''
|
||||||
|
machine.wait_for_unit("multi-user.target")
|
||||||
|
conf = machine.succeed(
|
||||||
|
"find /nix/store -path "
|
||||||
|
"'*wireplumber.conf.d/90-nomarchy-dock-audio.conf' "
|
||||||
|
"2>/dev/null | head -1"
|
||||||
|
).strip()
|
||||||
|
assert conf, "90-nomarchy-dock-audio.conf not in the store"
|
||||||
|
body = machine.succeed(f"cat '{conf}'")
|
||||||
|
assert "monitor.alsa.rules" in body, body
|
||||||
|
assert "priority.session" in body, body
|
||||||
|
assert "hdmi" in body.lower() or "HDMI" in body, body
|
||||||
|
assert "usb" in body.lower(), body
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
# Clamshell / dock lid policy (#86): logind must ignore lid close
|
# Clamshell / dock lid policy (#86): logind must ignore lid close
|
||||||
# when docked (external display present). Real multi-monitor lid
|
# when docked (external display present). Real multi-monitor lid
|
||||||
# events stay V3; this asserts the baked logind.conf + that
|
# events stay V3; this asserts the baked logind.conf + that
|
||||||
|
|||||||
@@ -125,6 +125,10 @@ in
|
|||||||
alsa.support32Bit = true;
|
alsa.support32Bit = true;
|
||||||
pulse.enable = true;
|
pulse.enable = true;
|
||||||
wireplumber.enable = true;
|
wireplumber.enable = true;
|
||||||
|
# Prefer dock/HDMI/USB sinks when present; fall back to built-in when
|
||||||
|
# they disappear (BACKLOG #87). Rules: ./dock-audio-rules.nix.
|
||||||
|
wireplumber.extraConfig."90-nomarchy-dock-audio" =
|
||||||
|
import ./dock-audio-rules.nix;
|
||||||
};
|
};
|
||||||
|
|
||||||
# ── Desktop services ─────────────────────────────────────────────
|
# ── Desktop services ─────────────────────────────────────────────
|
||||||
|
|||||||
38
modules/nixos/dock-audio-rules.nix
Normal file
38
modules/nixos/dock-audio-rules.nix
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# WirePlumber 0.5 ALSA rules: prefer dock / external sinks over built-in
|
||||||
|
# analog when they appear (BACKLOG #87). Higher priority.session wins as
|
||||||
|
# the default node; when HDMI/USB goes away, the next-highest (usually
|
||||||
|
# the laptop speakers/headphones) becomes default again.
|
||||||
|
#
|
||||||
|
# Pure attrset so checks.dock-audio can unit-test the contract without a
|
||||||
|
# full PipeWire stack. Consumed by modules/nixos/default.nix.
|
||||||
|
#
|
||||||
|
# Priorities stay ≤1500 (WirePlumber docs: sinks default ~600–1000; going
|
||||||
|
# much higher can outrank streams / BT in surprising ways).
|
||||||
|
{
|
||||||
|
"monitor.alsa.rules" = [
|
||||||
|
{
|
||||||
|
# HDMI / DisplayPort monitor audio (names vary: .hdmi-stereo,
|
||||||
|
# .hdmi-surround, HiFi__HDMI1__sink, …).
|
||||||
|
matches = [
|
||||||
|
{ "node.name" = "~alsa_output\\..*\\.hdmi-.*"; }
|
||||||
|
{ "node.name" = "~alsa_output\\..*HDMI.*"; }
|
||||||
|
{ "node.name" = "~alsa_output\\..*\\.DisplayPort.*"; }
|
||||||
|
{ "node.name" = "~alsa_output\\..*\\.dp-.*"; }
|
||||||
|
];
|
||||||
|
actions."update-props" = {
|
||||||
|
"priority.driver" = 1100;
|
||||||
|
"priority.session" = 1100;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
# USB dock / dongle audio (Thunderbolt dock, USB-C audio, …).
|
||||||
|
matches = [
|
||||||
|
{ "node.name" = "~alsa_output\\.usb-.*"; }
|
||||||
|
];
|
||||||
|
actions."update-props" = {
|
||||||
|
"priority.driver" = 1050;
|
||||||
|
"priority.session" = 1050;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user