Keep audio settings when plugged into dock | Crash course on Udev rules - jacobmoroni/ubuntu_hacks GitHub Wiki

Setting up audio stuff for Ubuntu 20

I am trying to setup my computer to use default speaker and microphone when plugged into my dock because it doesnt have speakers and it drives me crazy to set it each time.

One simple solution is to edit the /etc/pulse/default.pa file and comment out the lines that make it automatically switch to whatever is plugged in. These are the lines to comment out (lines 36-38 for me)

.ifexists module-switch-on-connect.so
load-module module-switch-on-connect
.endif

The problem is this disables it always, but I only want it disabled for my dock

So here is another attempt:

To see current speaker options: pactl list short sinks

Will return something like this:

0	alsa_output.pci-0000_01_00.1.hdmi-stereo-extra4	module-alsa-card.c	s16le 2ch 44100Hz	SUSPENDED
1	alsa_output.usb-Lenovo_ThinkPad_Thunderbolt_3_Dock_USB_Audio_000000000000-00.analog-stereo	module-alsa-card.c	s16le 2ch 44100Hz	SUSPENDED
2	alsa_output.pci-0000_00_1f.3.analog-stereo	module-alsa-card.c	s16le 2ch 48000Hz	SUSPENDED

To set the speaker option (This is an example to set it to internal speaker) will likely differ per computer: pactl set-default-sink alsa_output.pci-0000_00_1f.3.analog-stereo

To see current microphone options: pactl list short sources

Will return something like this:

0	alsa_output.pci-0000_01_00.1.hdmi-stereo-extra4.monitor	module-alsa-card.c	s16le 2ch 44100HzSUSPENDED
1	alsa_output.usb-Lenovo_ThinkPad_Thunderbolt_3_Dock_USB_Audio_000000000000-00.analog-stereo.monitor	module-alsa-card.c	s16le 2ch 44100Hz	SUSPENDED
2	alsa_input.usb-Lenovo_ThinkPad_Thunderbolt_3_Dock_USB_Audio_000000000000-00.mono-fallback	module-alsa-card.c	s16le 1ch 44100Hz	SUSPENDED
3	alsa_output.pci-0000_00_1f.3.analog-stereo.monitor	module-alsa-card.c	s16le 2ch 48000HzSUSPENDED
4	alsa_input.pci-0000_00_1f.3.analog-stereo	module-alsa-card.c	s16le 2ch 48000Hz	SUSPENDED

To set microphone option (This is an example to set it to internal microphone) will likely differ per computer: pactl set-default-source alsa_input.pci-0000_00_1f.3.analog-stereo

Then lets move to udev stuff. First we need to figure out what we want to capture. running udevadm monitor --udev --property will capture udev events and print them to the screen and you can go through and see what happened so you can capture it. I wanted to capture a sound card event so I searched through the print out after running it for sound and found this event

UDEV  [2081.044657] add      /devices/pci0000:00/0000:00:1c.0/0000:04:00.0/0000:05:04.0/0000:2d:00.0/0000:2e:02.0/0000:2f:00.0/usb7/7-2/7-2.1/7-2.1.1/7-2.1.1.4/7-2.1.1.4:1.0/sound/card2/pcmC2D0c (sound)
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:1c.0/0000:04:00.0/0000:05:04.0/0000:2d:00.0/0000:2e:02.0/0000:2f:00.0/usb7/7-2/7-2.1/7-2.1.1/7-2.1.1.4/7-2.1.1.4:1.0/sound/card2/pcmC2D0c
SUBSYSTEM=sound
DEVNAME=/dev/snd/pcmC2D0c
DEVTYPE=pcm
SEQNUM=7239
USEC_INITIALIZED=2081044536
MAJOR=116
MINOR=16
TAGS=:uaccess:

I did a bit more research and wanted to specifically capture the ThinkPad Dock so I searched for that and found this event:

UDEV  [2081.102542] add      /devices/pci0000:00/0000:00:1c.0/0000:04:00.0/0000:05:04.0/0000:2d:00.0/0000:2e:02.0/0000:2f:00.0/usb7/7-2/7-2.1/7-2.1.1/7-2.1.1.4/7-2.1.1.4:1.0/sound/card2/controlC2 (sound)
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:1c.0/0000:04:00.0/0000:05:04.0/0000:2d:00.0/0000:2e:02.0/0000:2f:00.0/usb7/7-2/7-2.1/7-2.1.1/7-2.1.1.4/7-2.1.1.4:1.0/sound/card2/controlC2
SUBSYSTEM=sound
DEVNAME=/dev/snd/controlC2
SEQNUM=7240
USEC_INITIALIZED=2081045442
ID_VENDOR=Lenovo
ID_VENDOR_ENC=Lenovo
ID_VENDOR_ID=17ef
ID_MODEL=ThinkPad_Thunderbolt_3_Dock_USB_Audio
ID_MODEL_ENC=ThinkPad\x20Thunderbolt\x203\x20Dock\x20USB\x20Audio
ID_MODEL_ID=3083
ID_REVISION=0083
ID_SERIAL=Lenovo_ThinkPad_Thunderbolt_3_Dock_USB_Audio_000000000000
ID_SERIAL_SHORT=000000000000
ID_TYPE=audio
ID_BUS=usb
ID_USB_INTERFACES=:010100:010200:030000:
ID_USB_INTERFACE_NUM=00
ID_USB_DRIVER=snd-usb-audio
ID_PATH=pci-0000:2f:00.0-usb-0:2.1.1.4:1.0
ID_PATH_TAG=pci-0000_2f_00_0-usb-0_2_1_1_4_1_0
MAJOR=116
MINOR=17
DEVLINKS=/dev/snd/by-id/usb-Lenovo_ThinkPad_Thunderbolt_3_Dock_USB_Audio_000000000000-00 /dev/snd/by-path/pci-0000:2f:00.0-usb-0:2.1.1.4:1.0
TAGS=:uaccess:

These provide the necessary information to create a udev rule to capture that event

create a rules file /etc/rules.d/95-dock-connect.rules -the number at the beginning of the file sets the order of the rules running from what I gather. I think the higher it is, the later rule to run.) I set it to 95 because the pulseaudio rule is a 90 and we need this to run after that -the actual name (between the number and the .rules) doesnt matter so name it what you want -probably the .rules does matter

in that file you can add some rules. Here are a couple examples:

SUBSYSTEM=="usb", ACTION=="add", ENV{DEVTYPE}=="usb_device",  RUN+="/bin/device_added.sh"
SUBSYSTEM=="usb", ACTION=="remove", ENV{DEVTYPE}=="usb_device", RUN+="/bin/device_removed.sh"
SUBSYSTEM=="sound", ACTION=="add", ENV{DEVTYPE}=="pcm", RUN+="/bin/sound_added.sh"
KERNEL=="controlC2", SUBSYSTEM=="sound", ACTION=="add", RUN+="/bin/thinkpad_dock_added.sh"

So those are capturing the event that happens with SUBSYSTEM ACTION and DEVTYPE and then when that triggers it runs the script after the RUN+

The first 2 are examples of a USB plugging and unplugging, the 3rd one is capturing the addition of a "pcm" devtype in the sound subsystem. And the last one is capturing the DEVNAME of the sound card controlC2 which is where the thinkpad sound was showing up. (KERNEL== matches with DEVNAME) So that is the trigger I wanted.

after changing a rule you can reload all the rules with this command sudo udevadm control --reload

Then we just need to write the script to handle the event: above we said that it will be in /bin/thinkpad_dock_added.sh

a quick and dirty way to test if you have it right is to put something like this in the file

#!/bin/bash
echo "thinkpad device added at $(date)" >>/home/jacobolson/testing.log

Then plug it in and see if that echo statement shows up in testing.log (or whatever you want to name it)

After verifying that it was correct, I just want to set up the sound how I want it when this is plugged in: final version of the script file /bin/thinkpad_dock_added.sh

#!/bin/bash
sleep 30
if [[ $(pactl list short sinks | grep "Lenovo_ThinkPad_Thunderbolt_3_Dock_USB_Audio") ]]; then
  eval $(pactl set-default-sink alsa_output.pci-0000_00_1f.3.analog-stereo) 
  eval $(pactl set-default-source alsa_input.pci-0000_00_1f.3.analog-stereo) 
fi

And the final version of the rules file: /etc/udev/rules.d/95-dock-connected.rules

KERNEL=="controlC2", SUBSYSTEM=="sound", ACTION=="add", RUN+="/bin/thinkpad_dock_added.sh"

Once that file is written, dont forget to make it executable and reload the udev rules

bada-bing bada-boom!

Here are some of the sites I used to make this. Here for a deeper dive if needed

So... Update on this. It didnt work. Turns out 2 things were wrong.

  1. pactl has to be run as a user not sudo, and the udev rules are run as sudo from what I gather. there is a work-around for that. you just have to change the command from

    pactl <command>

    to

    sudo -u <username> pactl --server "unix:/run/user/$(id -u <username>)/pulse/native" <command>

    and replace the <username> with your username and <command> with the command you want to run for the sake of not feeling like this was a complete waste of time. here are the scripts I ended up on with that:

/etc/udev/rules.d/99-dock-connected.rules

#KERNEL=="controlC2", SUBSYSTEM=="sound", ACTION=="add", RUN+="/bin/thinkpad_dock_added.sh"
SUBSYSTEM=="sound", ACTION=="change", RUN+="/bin/thinkpad_dock_added.sh"

/bin/thinkpad_dock_added.sh

#!/bin/bash
echo "running this audio change script $(date)" >> /home/jacobolson/testing.log &
{
  #sleep 10
  if [[ $(sudo -u jacobolson pactl --server "unix:/run/user/1000/pulse/native" info | grep "Lenovo_ThinkPad_Thunderbolt_3_Dock_USB_Audio") ]]; then
  #eval $(pactl set-default-sink alsa_output.pci-0000_00_1f.3.analog-stereo) 
  #eval $(pactl set-default-source alsa_input.pci-0000_00_1f.3.analog-stereo) 
    eval $(sudo -u jacobolson pactl --server "unix:/run/user/1000/pulse/native" set-default-sink alsa_output.pci-0000_00_1f.3.analog-stereo)
    eval $(sudo -u jacobolson pactl --server "unix:/run/user/1000/pulse/native" set-default-source alsa_input.pci-0000_00_1f.3.analog-stereo)
    echo "Set default audio $(date)" >> /home/jacobolson/testing.log
  else
    result=$(sudo -u jacobolson pactl --server "unix:/run/user/1000/pulse/native" list short sinks)
    echo $result >> /home/jacobolson/testing.log
    result2=$(pactl list short sources)
    echo $result2 >> /home/jacobolson/testing.log
    echo "this did not find the dock $(date)" >> /home/jacobolson/testing.log
  fi
}

#sudo -u jacobolson pactl --server "unix:/run/user/1000/pulse/native" set-default-sink alsa_output.pci-0000_00_1f.3.analog-stereo
#sudo -u jacobolson pactl --server "unix:/run/user/1000/pulse/native" set-default-source alsa_input.pci-0000_00_1f.3.analog-stereo

there is a similar example on this site. But it is a bit different. So maybe a slightly different implementation could have worked.

  1. The event I was trying to catch for the udev rule seems to be right before the audio settings changed, so I couldnt get it to actually work because it would wait until after the script was run to change my audio settings to the dock. I never figured this out but I found a few ways around it.
    • theoretically I could have called a systemd.service from the udev rule and that could have set the setting, but that was another rabbit hole that I didnt feel like going down
    • I created an alias that ran the following to let me at least switch to internal audio quickly when needed
    pactl set-default-sink alsa_output.pci-0000_00_1f.3.analog-stereo
    pactl set-default-source alsa_input.pci-0000_00_1f.3.analog-stereo
    
    • I created a script with the above lines and mapped that to a keyboard shortcut Shift + F1 to make it even faster

Then I stumbled across the easy solution that I should have just done from the beginning. I installed pavucontrol (sudo apt install) and then in there you can just turn off an audio device and it wont switch to that here is the link to the answer, but this is what it says:

run pavucontrol in a terminal or via your desktop.

  • First select the Output Devices tab and make sure the drop down in the lower right corner is set to All output devices.
  • Then select the Configuration tab.
  • Find the device you want Pulse to ignore and select off in the profile drop-down.
⚠️ **GitHub.com Fallback** ⚠️