udev rule to run Python script

Solution 1:

My working solution

  1. Modify a2dp.py by replacing all instances of pacmd with pactl adjusting pacmd list-sinks to pactl list sinks (in my case saved as /usr/local/bin/a2dp_2.sh).

  2. 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
    fi
    
  3. Add 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:

  1. It finds out the $USER owning the running instance of pulseaudio, then sets the environment variables DISPLAY=:0 and XAUTHORITY=/home/$USER/.Xauthority necessary for pactl to 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.)

  2. It checks whether the corresponding sink is suspended and only then runs a2dp_2.py. This is necessary to prevent an infinite loop caused by a2dp_2.py reconnecting the device and thus triggering the rule.

  3. It runs a2dp_2.py as $USER. If run as root, a2dp_2.py will leave pulseaudio, and thus any audio settings, inaccessible without root privileges.

Alternatives: dbus loop/fixed package

  1. An alternative solution using a dbus loop can be found on the sript developer's page.

  2. A fix for the original bug is now available here and can be easily installed by adding ppa:ubuntu-audio-dev/pulse-testing and 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:

  1. Find the device path by running udevadm monitor and 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.

  2. 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:50 and also that it is stored as ATTR{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:

  1. 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.

  2. 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.)

  3. 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.