kota's memex

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"