运行 Python 脚本的 udev 规则

hif*_*ife 5 python pulseaudio udev

每当我连接到蓝牙耳机时,我都会尝试自动运行脚本。

我已经用以下行创建了文件 /etc/udev/rules.d/80-bt-headset.rules

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"
Run Code Online (Sandbox Code Playgroud)

但它没有做任何事情。条件很好,当我输入时会触发一个简单的测试命令。手动运行时,脚本本身也能正常工作。

这里出了什么问题?

更新:运行脚本时出错sudo -u USER(有关详细信息,请参见下文)。这可能是问题吗?对同一用户执行 sudo 操作会如何破坏事情?

更新 2:在替换pacmdwith pactlin 的所有实例a2dp.py(并替换list-sinkswithlist sinks使其成为有效的 pactl 命令)后,sudo -u USER工作,但是, udev 规则仍然没有。在/var/log/syslog我只看到这条线

systemd-udevd[32629]: Process '/home/USER/.local/bin/a2dp_2.py 00:22:37:3D:DA:50' failed with exit code 1.
Run Code Online (Sandbox Code Playgroud)

更新 3(解决方案):使用环境变量修改后的 skript(pacmd -> pactl,请参阅更新 2)DISPLAY=:0XAUTHORITY=/home/USER/.Xauthority成功。udev 规则:

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"
Run Code Online (Sandbox Code Playgroud)

正在按预期工作。

(现在唯一剩下的问题是,脚本本身会触发规则,因为它重新连接耳机,导致无限循环。但是,这是一个单独的问题,应该不难找到解决方法。在事实上,当我开始这个线程时,我期待这种行为。)

什么工作:

  1. 条件很好: 该行:

    ACTION=="add", SUBSYSTEM=="input" ATTR{name}=="00:22:37:3D:DA:50" RUN+="/bin/mkdir /tmp/testme"
    
    Run Code Online (Sandbox Code Playgroud)

    当我连接到耳机时将创建一个新目录。

  2. 当通过终端从终端运行时,脚本 a2dp.py 本身工作正常

    /home/USER/.local/bin/a2dp.py 00:22:37:3D:DA:50
    
    Run Code Online (Sandbox Code Playgroud)
  3. 通过 udev 运行一个简单的 Python 脚本:

    ACTION=="add", SUBSYSTEM=="input" ATTR{name}=="00:22:37:3D:DA:50" RUN+="/home/USER/.local/bin/atestscript.py"
    
    Run Code Online (Sandbox Code Playgroud)

    其中 atestscript.py 包含:

    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    
    import subprocess
    
    def main():
        subprocess.Popen(['mkdir', '/tmp/atestdir'])
    
    if __name__ == '__main__':
        main()
    
    Run Code Online (Sandbox Code Playgroud)

    连接设备后将再次创建一个文件夹。

用 pactl 替换 pacmd 后有什么作用:

  1. 从终端运行脚本,sudo -u USER甚至sudo -u root现在按预期工作(对于原始脚本,这导致:

    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
    
    Run Code Online (Sandbox Code Playgroud)

什么不起作用:

  1. 运行上述脚本或使用以下任一行作为RUN+=部分:

    /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"
    
    Run Code Online (Sandbox Code Playgroud)

    即使修改后的脚本也不起作用:

    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"
    
    Run Code Online (Sandbox Code Playgroud)

更多信息:连接到耳机的 udevadm 监视器输出:

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)
Run Code Online (Sandbox Code Playgroud)

hif*_*ife 5

我的工作解决方案

  1. a2dp.py通过将 的所有实例替换为pacmd调整pactl来进行pacmd list-sinks修改(pactl list sinks在我的情况下另存为/usr/local/bin/a2dp_2.sh)。

  2. 创建包装脚本/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
    
    Run Code Online (Sandbox Code Playgroud)
  3. 将以下行添加到/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}"
    
    Run Code Online (Sandbox Code Playgroud)

该包装器脚本完成以下任务:

  1. 它找到$USER正在运行的pulseaudio实例的拥有者,然后设置工作DISPLAY=:0所需XAUTHORITY=/home/$USER/.Xauthority的环境变量。pactl这应该使它适用于计算机上的所有用户。(我没有测试多用户同时登录的效果。)

  2. 它检查相应的接收器是否挂起,然后才运行a2dp_2.py。这对于防止因a2dp_2.py重新连接设备而触发规则而导致无限循环是必要的。

  3. a2dp_2.py以 $USER 身份运行。如果以 root 身份运行,a2dp_2.py则在没有 root 权限的情况下将无法访问pulseaudio,因此任何音频设置都将无法访问。

替代方案:dbus 循环/固定包

  1. 可以在sript 开发人员页面上找到使用 dbus 循环的替代解决方案。

  2. 现在可以在此处修复原始错误,并且可以通过添加ppa:ubuntu-audio-dev/pulse-testing和更新可用软件包轻松安装。

提示:查找设备的 MAC 地址

严格来说,这不是原始问题的一部分,但这可能对将来的参考有用。有多种方法可以查找设备的 MAC 地址。以下是我认为对 udev 规则最有帮助的一条:

  1. udevadm monitor通过运行然后连接您的设备来查找设备路径。你的输出应该是这样的:

    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)
    
    Run Code Online (Sandbox Code Playgroud)

    用 停止监视器Ctrl+C。我们找到了三个设备路径。与我们相关的是/devices/virtual/input/input68.

  2. 将获得的路径插入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}==""
    
    Run Code Online (Sandbox Code Playgroud)

    我们了解到 MAC 地址是00:22:37:3D:DA:50并且它存储为ATTR{name}

即使输出看起来完全不同,这两个命令也将是寻找 udev 规则的相关条件的良好开端。

实验:捕获任意蓝牙音频设备

规则:

ACTION=="add", SUBSYSTEM=="input" ATTR{name}=="??:??:??:??:??:??" RUN+="/usr/local/bin/a2dp-wrapper.sh $attr{name}"
Run Code Online (Sandbox Code Playgroud)

将触发任何具有类似于 MAC 地址的名称属性的输入设备,并且包装器脚本中的条件应确保不会采取意外的操作。

我没有任何其他蓝牙音频设备可供测试,但我发现了一些潜在的问题:

  1. 这仅适用于被识别为名称属性中包含 MAC 地址的输入设备的蓝牙设备。并非所有设备都可以被这样识别。

  2. 这个解决方案不是很优雅,因为任何输入设备都会触发该规则。然而,我一直无法找到明确的指标来识别蓝牙音频设备。(如上所示,输入设备没有其他属性,并且蓝牙设备没有显示出是音频设备的指示,也不包含 MAC 地址。也许 ACPI 对此会更好。)

  3. 您可能不希望对每个蓝牙音频设备都一视同仁:您可能希望对您的耳机使用 HSP 协议,或者您可能不希望自动切换到您室友的扬声器(无论何时,您在某个时刻已配对过)可用的。在这些情况下,最好为每个设备制定单独的规则。

随着我了解更多,我将继续更新这篇文章。

  • 仅供参考,如果您希望它适用于其他设备名称,您可以使用 RUN+="/usr/local/bin/a2dp-wrapper.sh $attr{name}"` 将实际设备名称传递给程序。这也是[不要重复自己](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)原则。 (2认同)