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.greeter.enable` | `true` | greetd/tuigreet |
|
||||
| `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.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) |
|
||||
|
||||
@@ -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
|
||||
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
|
||||
|
||||
- **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
|
||||
`nomarchy.hardware.nvidia.*` until a hybrid maintainer + queue.
|
||||
|
||||
_(#80–#83 + #85–#86 shipped 2026-07-11. Remaining dock slice: **#87**
|
||||
dock audio. #86 V3 clamshell on real external display → HARDWARE-QUEUE.)_
|
||||
_(#80–#83 + #85–#87 shipped 2026-07-11. Dock V3 residuals: #86
|
||||
clamshell + #87 audio on real hardware → HARDWARE-QUEUE.)_
|
||||
|
||||
### v1.0 pointer
|
||||
|
||||
|
||||
@@ -19,6 +19,13 @@ the **T14s** (webcam case).
|
||||
— should suspend as usual. Optional: with a docked display-profile
|
||||
(`eDP-1` disable), confirm clamshell layout still looks right after
|
||||
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
|
||||
`nomarchy.hardware.fingerprint.enable` and a physical reader: System ›
|
||||
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
|
||||
- **Task:** NEXT #86 — lid closed + external monitor must not suspend.
|
||||
- **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).
|
||||
Undocked lid suspend left to upstream. `checks.clamshell-logind` asserts
|
||||
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
|
||||
passphrase already gates the machine, but locking hyprlock before sleep
|
||||
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 |
|
||||
|------|--------|-------|
|
||||
| **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 |
|
||||
| **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 |
|
||||
|
||||
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
|
||||
# when docked (external display present). Real multi-monitor lid
|
||||
# events stay V3; this asserts the baked logind.conf + that
|
||||
|
||||
@@ -125,6 +125,10 @@ in
|
||||
alsa.support32Bit = true;
|
||||
pulse.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 ─────────────────────────────────────────────
|
||||
|
||||
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