headsetsink

Switch Linux audio to a Logitech wireless headset when it powers on, and back when it powers off

Go MIT created updated
  • golang
  • headset
  • hidpp
  • linux
  • logitech
  • pipewire
readme · slastra/headsetsink

headsetsink

Turn a Logitech wireless headset on and Linux audio moves to it. Turn it off and audio goes back to the speakers you were using.

Why

A wireless headset like the G733 talks to a USB dongle that stays plugged in. PipeWire sees the dongle, not the headset, so the headset's sink exists whether the headset is on or off. Nothing switches when you put it on, and when you turn it off your audio keeps playing into a headset that isn't there.

headsetsink listens to the dongle itself. When the headset links up or drops, the dongle reports it within a second or two, and headsetsink changes the default sink to match.

Install

Needs Go 1.22+, PipeWire with pipewire-pulse, and pactl 16 or newer (for pactl -f json).

go install github.com/slastra/headsetsink@latest
headsetsink -check

-check finds the headset and prints what it sees:

headset            Logitech G733 Gaming Headset (046d:0afe)
hidraw             /dev/hidraw10
power              on, 3775 mV
sink               alsa_output.usb-Logitech_G733_Gaming_Headset_0000000000000000-00.analog-stereo
default sink       alsa_output.usb-Logitech_G733_Gaming_Headset_0000000000000000-00.analog-stereo
remembered sink    alsa_output.pci-0000_00_1f.3.analog-stereo
source             alsa_input.usb-Logitech_G733_Gaming_Headset_0000000000000000-00.mono-fallback
default source     alsa_input.pci-0000_00_1f.3.analog-stereo
remembered source  -

Access

headsetsink opens the dongle's hidraw node as you. If -check reports permission denied, install a udev rule. If you have HeadsetControl installed, its rules already cover this. Otherwise:

sudo install -m 644 udev/70-headsetsink.rules /etc/udev/rules.d/
sudo udevadm control --reload
sudo udevadm trigger --subsystem-match=hidraw --attr-match=idVendor=046d

The rule names the G733's dongle (046d:0afe). For another model, change the product id to your dongle's, from lsusb.

Run at login

mkdir -p ~/.config/systemd/user
cp systemd/headsetsink.service ~/.config/systemd/user/
systemctl --user enable --now headsetsink
journalctl --user -u headsetsink -f

Usage

Flag
-mic Also switch the default source (microphone). Off by default, since many people keep a separate mic.
-fallback SINK Sink to use on power-off if the remembered one has gone away (a TV that's off, say). Takes a sink name from pactl list short sinks.
-hook CMD Run CMD on or CMD off after each switch, for a sound or a notification.
-usb VID:PID Watch this dongle instead of searching for one.
-check Print what was found and the headset's state, then exit. Exits 1 if no headset is found.
-v Log every voltage event from the headset.

What it remembers

The sink to go back to is the last default that wasn't the headset. headsetsink watches pactl subscribe, so any change of default counts, whether you made it in pavucontrol, a desktop picker, or anything else. It's kept in $XDG_STATE_HOME/headsetsink/ (usually ~/.local/state/headsetsink/), so it survives restarts and reboots.

If you move audio somewhere else yourself while the headset is on, turning the headset off leaves that alone. It only switches back when the headset is still the default.

Supported headsets

Tested on the Logitech G733 (046d:0afe).

Without -usb, headsetsink considers every Logitech USB device that has a PipeWire sink and uses the first whose hidraw node answers HID++ with feature 0x1F20. Other Logitech wireless headsets that report battery voltage this way should work too, and -check will tell you. If yours does, or doesn't, please open an issue with the -check output and the model.

How it works

  • The signal. The dongle speaks HID++ 2.0 on a hidraw node. Its feature 0x1F20 (battery voltage, ADC measurement) sends an unsolicited event when the headset links up, carrying the voltage in millivolts, and another with a voltage of 0 when it drops. headsetsink looks the feature's index up through the root feature instead of assuming one.
  • A false "off". The G733 can also send a zero-voltage event a few seconds after power-on while it is still linked. So a zero only starts a 2 second timer; after it, headsetsink asks for the voltage directly. A headset that is really off makes the dongle answer with an error or 0, and one that is still on answers with its voltage.
  • Audio. Everything on the PipeWire side goes through pactl. The headset's sink and source are found by the USB vendor and product ids in their properties (monitor sources share those ids and are skipped), so nothing depends on the long ALSA node names.
  • Dongle unplugged. headsetsink notices the hidraw node closing, looks for a headset every 3 seconds, and picks it back up when it returns.

Files

File
main.go Flags, the watch loop, switching, -check
hidpp.go HID++ requests and events, finding hidraw nodes
audio.go pactl sinks and sources, defaults, what to go back to
systemd/ User unit
udev/ Access rule for the G733 dongle

License

MIT