A very powerful daemon for detecting and notifying programs about things like peripheral connection/disconnection, battery updates, and other useful things that would otherwise require polling. Unfortunately, udev is part of systemd and is generally hard to seperate. The gentoo developers forked it a while back and created eudev, which works the same way and has the same api, but doesn't depend on systemd. Today it's maintained by alpine and devuan.
battery notifications with udev
A better solution than simple polling.
printing events
It's extremely useful to be able to see each event that gets triggered live on
your system. You can run this command and then try stuff like plugging in a usb
device or your laptop's charger. The -s
option allows filtering per subsystem
which can be useful for example to only see events related to the power_supply
system.
udevadm monitor -p
creating rules
Rules are stored in:
/etc/udev/rules.d
or system rules in:
/lib/udev/rules.d
reload rules in place
sudo udevadm control --reload-rules && sudo udevadm trigger
laptop charger connected
Here's an example of displaying a notification whenever you plug your laptop's charger in/out. This is super useful in aotearoa, because our outlets all have switches and can be individually turned on/off. Meaning once in a while I "plug" my laptop in, think it's charging, but the outlet is actually turned off.
I started by writing these two simple rules that match the "power_supply"
subsystem with those the POWER_SUPPLY_NAME=="AC"
and POWER_SUPPLY_ONLINE
being a 1 or 0. Probably didn't even need to include that, but I like that it
makes it clear to me that the script runs when it's plugged in or unplugged.
Initially, the script just wrote the current battery percent to a file so I
could test that the rule is working.
# Rules for switching to battery or to power supply
SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_NAME}=="AC", ENV{POWER_SUPPLY_ONLINE}=="0", RUN+="/usr/local/bin/battery.sh"
SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_NAME}=="AC", ENV{POWER_SUPPLY_ONLINE}=="1", RUN+="/usr/local/bin/battery.sh"
Once I knew that the rules themselves were working I improved the script a
little bit to actually display a notification. It checks the
POWER_SUPPLY_ONLINE
variable (which is set by udev when it runs the command
along with the other env variables shown in udevadm monitor
):
#!/bin/sh
b=$(battery)
if [ "$POWER_SUPPLY_ONLINE" -eq 1 ]
then
notify-send "b = $b+"
else
notify-send "b = $b-"
fi
Sadly, it didn't work. This is because x11 is a multiseat system and thus notify-send doesn't know which display server to use for the notification. We can just tweak out udev rules to essentially tell it to use the first (and normally only) display system:
SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_NAME}=="AC", ENV{POWER_SUPPLY_ONLINE}=="0", ENV{DISPLAY}=":0", ENV{XAUTHORITY}="/home/kota/.Xauthority" RUN+="/usr/bin/su kota -c /usr/local/bin/battery.sh"
SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_NAME}=="AC", ENV{POWER_SUPPLY_ONLINE}=="1", ENV{DISPLAY}=":0", ENV{XAUTHORITY}="/home/kota/.Xauthority" RUN+="/usr/bin/su kota -c /usr/local/bin/battery.sh"
video cam symlink
Below is an example of a rule that creates a symlink /dev/video-cam
when a
webcamera is connected. Let say this camera is currently connected and has
loaded with the device name /dev/video2.
The reason for writing this rule is
that at the next boot, the device could show up under a different name, like
/dev/video0
.
udevadm info --attribute-walk --path=$(udevadm info --query=path --name=/dev/video2)
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/pci0000:00/0000:00:04.1/usb3/3-2/3-2:1.0/video4linux/video2':
KERNEL=="video2"
SUBSYSTEM=="video4linux"
...
looking at parent device '/devices/pci0000:00/0000:00:04.1/usb3/3-2/3-2:1.0':
KERNELS=="3-2:1.0"
SUBSYSTEMS=="usb"
...
looking at parent device '/devices/pci0000:00/0000:00:04.1/usb3/3-2':
KERNELS=="3-2"
SUBSYSTEMS=="usb"
ATTRS{idVendor}=="05a9"
ATTRS{manufacturer}=="OmniVision Technologies, Inc."
ATTRS{removable}=="unknown"
ATTRS{idProduct}=="4519"
ATTRS{bDeviceClass}=="00"
ATTRS{product}=="USB Camera"
...
To identify the webcamera, from the video4linux
device we use
KERNEL=="video2"
and SUBSYSTEM=="video4linux"
, then walking up two levels
above, we match the webcamera using vendor and product ID's from the usb parent
SUBSYSTEMS=="usb"
, ATTRS{idVendor}=="05a9"
and ATTRS{idProduct}=="4519"
.
We are now able to create a rule match for this device as follows:
/etc/udev/rules.d/83-webcam.rules
KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", SUBSYSTEMS=="usb", ATTRS{idVendor}=="05a9", ATTRS{idProduct}=="4519", SYMLINK+="video-cam"
Here we create a symlink using SYMLINK+="video-cam"
but we could easily set
user OWNER="kota"
or group using GROUP="video"
or set the permissions using
MODE="0660"
. We could even do something more advanced with
RUN+="/path/to/your/script"