udev 规则运行 Python 脚本

udev 规则运行 Python 脚本

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

我已经创建了文件 /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"

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

这里出了什么问题?

更新:使用脚本运行脚本时出现错误sudo -u USER(详情见下文)。这可能是问题所在吗?对同一用户执行 sudo 操作会如何破坏程序?

更新 2pacmd:在替换所有实例后pactla2dp.py并替换list-sinkslist 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.

更新 3(解决方案):修改后的脚本(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"

工作正常。

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

有效的方法:

  1. 条件很好:线路:

    ACTION=="add", SUBSYSTEM=="input" ATTR{name}=="00:22:37:3D:DA:50" RUN+="/bin/mkdir /tmp/testme"
    

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

  2. 脚本 a2dp.py 本身在通过终端运行时运行良好

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

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

    其中 atestscript.py 包含:

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

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

使用 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
    

无效的方法:

  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"
    

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

    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"
    

更多信息: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)

答案1

我的工作解决方案

  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
    
  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}"
    

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

  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 loop/fixed 包

  1. 可以在以下位置找到使用 dbus 循环的替代解决方案sript 开发者页面

  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)
    

    使用 停止监控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}==""
    

    我们了解到 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}"

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

我没有其他可用的蓝牙音频设备来测试这一点,但我发现了许多潜在的问题:

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

  2. 这个解决方案不太优雅,因为任何输入设备都会触发规则。但是,我找不到明确的指标来识别蓝牙音频设备。(如上所示,输入设备没有其他属性,蓝牙设备没有显示音频设备的迹象,也不包含 MAC 地址。也许 ACPI 更适合这种情况。)

  3. 您可能不想对每个蓝牙音频设备都一视同仁:您可能想对耳机使用 HSP 协议,或者您可能不想在室友的扬声器可用时自动切换到您曾与之配对的扬声器。在这些情况下,最好为每个设备设置单独的规则。

当我了解更多信息后,我会持续更新这篇文章。

答案2

我找到了一种(我认为更简单并且与 Wayland 兼容的)方法来做到这一点systemctl

udev 规则与 OP 的答案相同:

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将会:

#!/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 [email protected] --user --now start /usr/local/bin/a2dp_2.py $MAC
fi

相应的 systemd 服务文件为~/.config/systemd/user/hdmi_sound_toggle.service

[Unit]
Description=Runs /usr/local/bin/a2dp_2.py
  
[Service]
Type=oneshot
ExecStart=/usr/local/bin/hdmi_sound_toggle.py

相关内容