I am trying to automatically run this script, whenever I connect to my bluetooth headset.
I have created the file /etc/udev/rules.d/80-bt-headset.rules with the line
ACTION=="add", SUBSYSTEM=="input" ATTR{name}=="00:22:37:3D:DA:50" RUN+="/home/USER/.local/bin/a2dp.py 00:22:37:3D:DA:50"but it doesn't do anything. The conditions are fine, a simple test comand is triggered when I enter that instead. The script itself also works fine when run manually.
What is going wrong here?
Update: There is an error when running the script with sudo -u USER (see below for details). Could this be the problem? And how does sudo-ing to the same user break things?
Update 2: After replacing all instances of pacmd with pactl in a2dp.py (and replacing list-sinks with list sinks to make it a valid pactl command), sudo -u USER works, however, the udev rule still doesn't. In /var/log/syslog I just see the line
systemd-udevd[32629]: Process '/home/USER/.local/bin/a2dp_2.py 00:22:37:3D:DA:50' failed with exit code 1.Update 3 (Solution): The modified skript (pacmd -> pactl, see Update 2) with the environment variables DISPLAY=:0 and XAUTHORITY=/home/USER/.Xauthority did the trick. The udev rule:
ACTION=="add", SUBSYSTEM=="input" ATTR{name}=="00:22:37:3D:DA:50" ENV{DISPLAY}=":0" ENV{XAUTHORITY}="/home/USER/.Xauthority" RUN+="/home/USER/.local/bin/a2dp_2.py 00:22:37:3D:DA:50"is working as intended.
(Now the only remaining problem is, that the script itself will trigger the rule, as it reconnects the headset, resulting in an infinite loop. However, that is a separate question, and it should not be too hard to find a workaround. In fact I was expecting that behaviour when I started this thread.)
What works:
The conditions are fine: The line:
ACTION=="add", SUBSYSTEM=="input" ATTR{name}=="00:22:37:3D:DA:50" RUN+="/bin/mkdir /tmp/testme"will create a new directory when I connect to the headset.
The script a2dp.py itself works fine when run from the terminal via
/home/USER/.local/bin/a2dp.py 00:22:37:3D:DA:50Running a simply Python script via udev:
ACTION=="add", SUBSYSTEM=="input" ATTR{name}=="00:22:37:3D:DA:50" RUN+="/home/USER/.local/bin/atestscript.py"where atestscript.py contains:
#!/usr/bin/python # -*- coding: utf-8 -*- import subprocess def main(): subprocess.Popen(['mkdir', '/tmp/atestdir']) if __name__ == '__main__': main()will again create a folder when the device is connected.
What works after replacing pacmd with pactl:
Running the script from the terminal with
sudo -u USERor evensudo -u rootnow works as intended (For the originial script this resulted in:USER@MACHINE:~$ sudo -u USER /usr/local/bin/a2dp.py 00:22:37:3D:DA:50 Connection MADE Device MAC: 00:22:37:3D:DA:50 Command: pacmd list-sinks failed with status: 1 stderr: No PulseAudio daemon running, or not running as session daemon. Exiting bluetoothctl
What does not work:
Running the script as above or with any of the following lines as the
RUN+=part:/usr/bin/sudo -u USER /usr/bin/python3 /home/USER/.local/bin/a2dp.py 00:22:37:3D:DA:50 /usr/bin/sudo -u USER /home/USER/.local/bin/a2dp.py 00:22:37:3D:DA:50 /usr/bin/python3.5 /usr/local/bin/a2dp.py 00:22:37:3D:DA:50 ENV{DISPLAY}=":0" RUN+="/usr/local/bin/a2dp.py 00:22:37:3D:DA:50"Even the modified script will not work:
ENV{DISPLAY}=":0" ENV{PULSE_RUNTIME_PATH}="/run/user/1000/pulse/" RUN+="sudo -u USER /home/USER/.local/bin/a2dp_2.py 00:22:37:3D:DA:50"
Further information: udevadm monitor output on connecting to headset:
KERNEL[104388.664737] add /devices/pci0000:00/0000:00:14.0/usb3/3-7/3-7:1.0/bluetooth/hci0/hci0:256 (bluetooth)
UDEV [104388.667185] add /devices/pci0000:00/0000:00:14.0/usb3/3-7/3-7:1.0/bluetooth/hci0/hci0:256 (bluetooth)
KERNEL[104390.848157] add /devices/virtual/input/input46 (input)
UDEV [104390.849150] add /devices/virtual/input/input46 (input)
KERNEL[104390.849471] add /devices/virtual/input/input46/event17 (input)
UDEV [104390.864692] add /devices/virtual/input/input46/event17 (input) 11 2 Answers
My working solution
Modify
a2dp.pyby replacing all instances ofpacmdwithpactladjustingpacmd list-sinkstopactl list sinks(in my case saved as/usr/local/bin/a2dp_2.sh).Create a wrapper script
/usr/local/bin/a2dp-wrapper.sh#!/bin/bash MAC=$1 MACMOD=$(echo $MAC | sed 's/:/_/g') PID=$(pgrep pulseaudio) USER=$(grep -z USER= /proc/$PID/environ | sed 's/.*=//') export DISPLAY=:0 export XAUTHORITY=/home/$USER/.Xauthority if pactl list sinks short | grep "bluez_sink\.$MACMOD.*SUSPENDED" then sudo -u $USER /usr/local/bin/a2dp_2.py $MAC fiAdd the following line to
/etc/udev/rules.d/80-bt-headset.rules:ACTION=="add", SUBSYSTEM=="input" ATTR{name}=="00:22:37:3D:DA:50" RUN+="/usr/local/bin/a2dp-wrapper.sh $attr{name}"
This wrapper script accomplishes the following:
It finds out the
$USERowning the running instance of pulseaudio, then sets the environment variablesDISPLAY=:0andXAUTHORITY=/home/$USER/.Xauthoritynecessary forpactlto work. This should make it work for all users on a machine. (I have not tested the effects of multiple users logged in at the same time.)It checks whether the corresponding sink is suspended and only then runs
a2dp_2.py. This is necessary to prevent an infinite loop caused bya2dp_2.pyreconnecting the device and thus triggering the rule.It runs
a2dp_2.pyas $USER. If run as root,a2dp_2.pywill leave pulseaudio, and thus any audio settings, inaccessible without root privileges.
Alternatives: dbus loop/fixed package
An alternative solution using a dbus loop can be found on the sript developer's page.
A fix for the original bug is now available here and can be easily installed by adding
ppa:ubuntu-audio-dev/pulse-testingand updating the available packages.
Hint: Finding your device's MAC address
Not strictly part of the original problem, but this might be useful for future reference. There are numerous ways to find your device's MAC address. The following is the one I consider most helpful for udev rules:
Find the device path by running
udevadm monitorand then connecting your device. Your output should look something like this:USER@MACHINE:~$ udevadm monitor monitor will print the received events for: UDEV - the event which udev sends out after rule processing KERNEL - the kernel uevent KERNEL[123043.617276] add /devices/pci0000:00/0000:00:14.0/usb3/3-7/3-7:1.0/bluetooth/hci0/hci0:256 (bluetooth) UDEV [123043.647291] add /devices/pci0000:00/0000:00:14.0/usb3/3-7/3-7:1.0/bluetooth/hci0/hci0:256 (bluetooth) KERNEL[123044.153776] add /devices/virtual/input/input68 (input) KERNEL[123044.153911] add /devices/virtual/input/input68/event17 (input) UDEV [123044.193415] add /devices/virtual/input/input68 (input) UDEV [123044.213213] add /devices/virtual/input/input68/event17 (input)Stop the monitor with
Ctrl+C. We have found three device paths. The one relevant for us is/devices/virtual/input/input68.Plug the obtained path into
udevadm info:USER@MACHINE:~$ udevadm info -a -p /devices/virtual/input/input68 Udevadm info starts with the device specified by the devpath and then walks up the chain of parent devices. It prints for every device found, all possible attributes in the udev rules key format. A rule to match, can be composed by the attributes of the device and the attributes from one single parent device. looking at device '/devices/virtual/input/input68': KERNEL=="input68" SUBSYSTEM=="input" DRIVER=="" ATTR{name}=="00:22:37:3D:DA:50" ATTR{phys}=="" ATTR{properties}=="0" ATTR{uniq}==""We learn that the MAC address is
00:22:37:3D:DA:50and also that it is stored asATTR{name}.
Even if the output looks completely different, these two commands will be a good start in looking for the relevant conditions for a udev rule.
Experimental: Catching arbitrary bluetooth audio devices
The rule:
ACTION=="add", SUBSYSTEM=="input" ATTR{name}=="??:??:??:??:??:??" RUN+="/usr/local/bin/a2dp-wrapper.sh $attr{name}"will trigger for any input device that has a name attribute that looks like a MAC address and the conditional in the wrapper script should make sure no unintended actions are taken.
I do not have any other bluetooth audio device readily available to test this, but I see a number of potential issues:
This will only work for a bluetooth device that is recognised as an input device containing the MAC address in the name attribute. Not every device may be recognised as such.
This solution is not very elegant, as the rule will be triggered for any input device. However, I have not been able to find clear indicators to identify a bluetooth audio device. (As seen above, the input device has no further attributes, and the bluetooth device shows no indication of being an audio device, nor does it contain the MAC address. Maybe ACPI would be better for this.)
You may not want to treat every bluetooth audio device the same: You may want to use the HSP protocol for your headset or you may not want to automatically switch to your housemate's speakers, that you've paired with at some point, whenever they are available. In those cases it is probably preferable to have a separate rule for each device.
I will keep updating this post as I learn more.
6I found a (I think easier and also Wayland-compatible) way to do this with systemctl.
The udev rule would be the same as the OP's answer:
ACTION=="add", SUBSYSTEM=="input" ATTR{name}=="00:22:37:3D:DA:50" RUN+="/usr/local/bin/a2dp-wrapper.sh $attr{name}"usr/local/bin/a2dp-wrapper.sh would be:
#!/bin/bash
MAC=$1
MACMOD=$(echo $MAC | sed 's/:/_/g')
PID=$(pgrep pulseaudio)
USER=$(grep -z USER= /proc/$PID/environ | sed 's/.*=//')
if pactl list sinks short | grep "bluez_sink\.$MACMOD.*SUSPENDED" then systemctl --machine=$USER@.host --user --now start /usr/local/bin/a2dp_2.py $MAC
fiAnd the appropriate systemd service file in ~/.config/systemd/user/hdmi_sound_toggle.service is:
[Unit]
Description=Runs /usr/local/bin/a2dp_2.py
[Service]
Type=oneshot
ExecStart=/usr/local/bin/hdmi_sound_toggle.py