Olá pessoal! Neste post eu pretendo explicar como executar aplicativos de interface gráfica a partir de eventos disparados por dispositivos USB ou Bluetooth, através do systemd e de regras udev, enquanto aprendemos um pouco sobre Linux no caminho. Sou bem específico ao dizer “aplicativos de interface gráfica” porque a execução deste tipo de aplicativo, no contexto do udev, não é trivial; um ótimo tutorial para a execução de scripts e comandos simples a partir do mesmo mecanismo pode ser encontrado clicando aqui.
Como exemplo de aplicação para o que queremos fazer, vou mostrar como configurei meu sistema para executar o RetroArch toda vez que o meu controle DualSense é conectado!
Esse texto ensinará:
- O que são regras udev
- Como criar regras udev para um dispositivo específico
- Como executar aplicativos gráficos a partir de regras udev, com o systemd.
É esperado que você tenha um pouco de familiaridade com terminais.
udev
Nosso interesse no udev é basicamente na interface que ele dá para o sistema trabalhar com eventos relacionados a dispositivos, com a aplicação de regras. Mas para além disto, ele é um gerenciador de dispositivos que permite gerenciar suas permissões, renomeá-los, entre várias outras coisas que você checar na página do manual do udev.
The udev daemon, systemd-udevd.service(8), receives device uevents
directly from the kernel whenever a device is added or removed
from the system, or it changes its state. When udev receives a
device event, it matches its configured set of rules against
various device attributes to identify the device.
udev man page
regras udev
A cada evento que um dispositivo dispara no sistema – como uma inserção ou remoção – um certo conjunto de regras são testadas contra as propriedades do evento/dispositivo para saber se alguma ação deve ser tomada. Estas regras podem estar localizadas em /etc/udev/rules.d/ ou /usr/lib/udev/rules.d/, dependendo do nível de aplicação delas (em arquivos de mesmo nome, a execução de regras em /etc terá maior precedência)
Arquivos de regras udev nestas pastas devem ter a extensão “.rules”.
Uma regra é uma linha em um destes arquivos, que contenham expressões de “match” para as propriedades do evento/dispositivo, e expressões de atribuições a serem realizadas caso as regras sejam validadas. Aqui temos uma ótima referência para a sintaxe das regras.
criando regras udev para um dispositivo
A maior dificuldade em criar regras udev para a tomada de ações é determinar qual o melhor evento para isso, dentre aqueles relacionados ao dispositivo de seu interesse. A simples conexão ou inserção de um dispositivo pode disparar vários eventos, e achar um que seja únicamente determinado pelas suas propriedades pode ser dificíl.
Primeiro, vamos criar um log com todos eventos disparados pelo seu dispositivo de interesse, para depois vermos qual o melhor evento para criarmos nossa regra.
Isto pode ser feito a partir do comando “udevadm monitor -pk”. Após a execução do comando (que fica em foreground), insira ou remova o dispositivo, e encerre o monitoramento. Inspecionaremos este arquivo depois para criarmos nossa regra.
# o comando pode precisar de privilégios de administrador
udevadm monitor -pk > ~/udev.log
# Insira ou remova o seu dispositivo, e depois encerre o monitoramento com ctrl-c
Vejamos, no meu exemplo, os eventos e ações disparados pela conexão do DualSense no sistema (com uma ajuda do comando grep e cut para limpar a saída):
grep “^KERNEL” ~/udev.log | cut -f -2 -d ” “
KERNEL[11652.444791] add
KERNEL[11652.444895] add
KERNEL[11652.444995] add
KERNEL[11652.445204] add
KERNEL[11652.445295] add
KERNEL[11652.445421] add
KERNEL[11652.446172] bind
KERNEL[11652.454171] change
Pois é. São vários eventos e ações(add,bind,change..) diferentes. No caso, dei sorte de ter somente uma ação “bind”, e posso usá-lo junto de suas propriedades para montar minha regra. Porém, dependendo do seu dispositivo, você precisará inspecionar as propriedades de cada regra para montar uma específica o suficiente (para que sua ação não seja executada várias vezes…). Podemos inspecionar o evento de bind para ver suas propriedades com o seguinte comando (ajudado novamente pelo grep!):
grep “^KERNEL.*bind.*” -A 15 -B 1 ~/udev.log
KERNEL[11652.446172] bind /devices/pci0000:00/0000:00:14.0/usb1/1-5/1-5:1.0/bluetooth/hci0/hci0:21/0005:054C:0CE6.0009 (hid)
ACTION=bind
DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-5/1-5:1.0/bluetooth/hci0/hci0:21/0005:054C:0CE6.0009
SUBSYSTEM=hid
DRIVER=playstation
HID_ID=0005:0000054C:00000CE6
HID_NAME=Wireless Controller
HID_PHYS=fd:01:7a:bf:1f:ca
HID_UNIQ=7c:61:ef:4b:c4:b0
MODALIAS=hid:b0005g0001v0000054Cp00000CE6
SEQNUM=8533
Normalmente, procuramos por atributos únicos como modelos ou fabricantes, que podem aparecer como ID_MODEL ou ID_VENDOR, respectivamente. No meu caso, o endereço MAC do meu controle em “HID_UNIQ” e outra propriedade “DRIVER” serão usadas. Na verdade eu sequer sei se esta é a melhor escolha, mas não encontrei muita coisa que me ajudasse a montar a melhor regra. A sintaxe é bem simples, como poderemos ver em seguida.
Uma regra que podemos montar para executar um script “dualteste” na pasta home está no bloco abaixo, no arquivo “/usr/lib/udev/rules.d/99-dualsense.rules”. Note que algumas das chaves com as quais comparamos valores (DRIVER, HID_UNIQ) podem precisar ser precedidas do seu contexto, como ENV ou ATTR. O comando “udevadm info <device-path>” com o dev-path do seu dispositivo diferencia as propriedades quanto a isso.
sudo touch /usr/lib/udev/rules.d/99-dualsense.rules
cat /usr/lib/udev/rules.d/99-dualsense.rules
ACTION==”bind”, ENV{HID_UNIQ}==”7c:61:ef:4b:c4:b0″ , ENV{DRIVER}=”playstation”, RUN+=”/home/afonsolpjr/dualteste”
Com a regra criada, é só executar “sudo udevadm control -R” para recarregarmos as regras.
O script “/home/afonsolpjr/dualteste” mencionado na atribuição RUN contém o seguinte:
#!/bin/bash
echo "$(date +%F\ %T) $(whoami)" >> "/tmp/dualteste.log"
Assim, após conectar o controle, podemos ver a data e hora da conexão e o usuário que executa o comando da regra.
Bem, estamos quase lá! Já que podemos executar um script ou comandos com a regra criada, para abrirmos um aplicativo com interface gráfica parece um pulo, certo?
Só que não! A execução dos comandos pelo udev são feitas em um ambiente sem conexão nenhuma com as sessões gráficas e saídas que vemos na nossa tela. Tente você mesmo colocar uma linha com “xcalc &” ou outro aplicativo gŕafico no final do script e veja que não dará certo. 🙁
Aliás, discussões na internet desencorajam a execução de programas de longa duração pelo udev, porque parece que eles são marcados para terminarem rapidamente pelo sistema.
usando systemd para execução de aplicações gráficas
Para solucionarmos este problema e executarmos aplicações gráficas no contexto de seu usuário e da sua sessão gráfica, precisamos usar o gerenciador de serviços do Linux, o systemd.
Primeiro, crie um serviço systemd (terminando com a extensão .service) na pasta ~/.config/systemd/user/ . Para o meu propósito, eu criei um serviço chamado retroarch_controller.service.
mkdir -p ~/.config/systemd/user/
touch ~/.config/systemd/user/retroarch_controller.service
Para o nosso propósito, unidades de serviço systemd tem uma sintaxe simples. No arquivo do nosso serviço podemos colocar o seguinte, para execução do retroarch:
[Unit]
Description=Runs RetroArch on device detection
[Service]
Type=simple
ExecStart=/usr/bin/retroarch
Para carregar os arquivos de serviço e testar o que criamos, executamos:
systemctl --user daemon-reload
systemctl --user start retroarch_controller.service
E se tudo estiver funcionando, dentro do próprio systemd podemos adicionar o serviço que criamos como uma dependência para o dispositivo conectado. Os detalhes de como isto funciona podem ser encontrados aqui. A nossa nova regra ficará da seguinte forma:
# Arquivo salvo em: /usr/lib/udev/rules.d/99-dualsense.rules
ACTION=="bind", ENV{HID_UNIQ}=="7c:61:ef:4b:c4:b0" , ENV{DRIVER}="playstation", RUN+="/home/afonsolpjr/dualteste", ENV{SYSTEMD_USER_WANTS}+="retroarch_controller.service", TAG+="systemd"
Note que adicionamos as seguintes expressões de atribuições:
- ENV{SYSTEMD_USER_WANTS}+=”retroarch_controller.service”
- TAG+=”systemd”
E assim, após recarregar as regras com “sudo udevadm control -R”, tudo deve estar funcionando! Não testei com outros tipos de dispositivos, mais tarde testo se a conexão de outros monitores também disparam eventos no udev 🙂
Qualquer dúvida deixe seu comentário para discussão abaixo!
referẽncias:
https://man7.org/linux/man-pages/man7/udev.7.html
https://unix.stackexchange.com/questions/790915/how-to-automatically-start-a-gui-application-when-plugging-in-a-usb-device
https://www.freedesktop.org/software/systemd/man/latest/udev.html
https://www.freedesktop.org/software/systemd/man/latest/systemd.device.html