- JavaScript 80.2%
- Nix 19.8%
- Menu action to power the AVR off when it is on but not the selected output (e.g. left on from an earlier session). - Auto-off on explicit move-away now also fires when we last selected the AVR, not only when its sink is still the default. - CLI avr-off command; VM mock tracks on/off state. |
||
|---|---|---|
| nix | ||
| scripts | ||
| src | ||
| tests | ||
| .envrc | ||
| .gitignore | ||
| CLAUDE.md | ||
| eslint.config.js | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
| todo.md | ||
Audio Switcher
A GNOME Shell extension that switches the default audio output between three targets from a panel menu:
- Scarlett — the USB audio interface.
- Bluetooth (Sony) — powers on Bluetooth, connects the configured headphones, then routes audio to them.
- AV Receiver — powers the amplifier on via Home Assistant, switches it to the configured input source, and shows its volume slider directly in the menu.
When the AV receiver is powered on but is not the selected output (e.g. left on from an earlier session), the menu shows a Power off AV receiver action so you can shut it down to save energy without switching to it first.
Audio is switched with WirePlumber (wpctl set-default), so it targets a
PipeWire session. GNOME Shell 45+ (tested on 50).
Configuration
Open the extension preferences (or gnome-extensions prefs audio-switcher@rammhold.de) and set:
| Setting | Example |
|---|---|
| Scarlett sink match | Scarlett |
| AVR sink match | S/PDIF |
| Headphones MAC | AA:BB:CC:DD:EE:FF |
| Home Assistant base URL | http://homeassistant.local:8123 |
| Home Assistant token | long-lived access token |
| AVR media_player entity | media_player.avr |
| AVR input source | DVR |
The Scarlett and AVR sink matches are case-insensitive substrings compared
against the names shown by wpctl status. The Bluetooth output is located by
the headphones' MAC (via the bluez pw-dump node name), so it needs no sink
match — only the correct MAC.
Development
The dev environment is provided by the flake; direnv allow (or
nix develop) drops you into a shell with gjs, glib, eslint and
nixpkgs-fmt.
gjs -m tests/parse.test.js # unit tests
eslint src tests # lint
nixpkgs-fmt flake.nix nix/*.nix # format nix
nix flake check # unit + VM test + eval
Testing
The logic, right now (no shell, no restart)
The src/lib modules only import gi, so they run under bare gjs and drive
the real hardware. scripts/cli.js exercises them:
nix develop --command gjs -m scripts/cli.js status # list sinks + current default
nix develop --command gjs -m scripts/cli.js scarlett # switch default to the Scarlett
nix develop --command gjs -m scripts/cli.js bluetooth # connect headphones + route
HASS_URL=http://ha:8123 HASS_TOKEN=... HASS_ENTITY=media_player.avr \
nix develop --command gjs -m scripts/cli.js avr # power-on + select source + route + read volume
HASS_URL=http://ha:8123 HASS_TOKEN=... HASS_ENTITY=media_player.avr \
nix develop --command gjs -m scripts/cli.js avr-off # power the AVR off
Sink matches default to the gschema defaults and can be overridden with
AUDIO_SCARLETT_MATCH / AUDIO_AVR_MATCH; the Bluetooth device is selected by
BT_MAC. The AVR input source defaults to DVR and can be overridden with
HASS_SOURCE.
The panel widget
GNOME Shell only scans the extensions directory at session start and Wayland
has no live-reload, so the widget appears after the shell re-inits. Install it
(nix profile install . or the symlink below) then log out and back in and:
gnome-extensions enable audio-switcher@rammhold.de
gnome-extensions prefs audio-switcher@rammhold.de # settings (runs in its own process)
journalctl --user -f -o cat /usr/bin/gnome-shell # watch logError() output
For a symlinked dev install that tracks the working tree:
ln -sfn "$PWD/src" ~/.local/share/gnome-shell/extensions/audio-switcher@rammhold.de
nix develop --command glib-compile-schemas src/schemas
A nested shell (gnome-shell --wayland) is not usable here: this mutter
build runs it as a full display server and fails to take the seat. The NixOS
VM test (nix build .#checks.x86_64-linux.vm) is the way to exercise the full
widget headlessly.
Nix
nix build # build the extension package
nix build .#checks.x86_64-linux.vm # run the NixOS VM test (heavy: builds GNOME)
The package installs into
share/gnome-shell/extensions/audio-switcher@rammhold.de/; add it to
environment.systemPackages on NixOS and enable it with
gnome-extensions enable audio-switcher@rammhold.de.