I’ve been playing with udev rules for a couple of days and I’ve been quite interested in the capabilities it offers.
I highly recommend this two documents:
This guide reflects the whole process from connecting the device, identifying the events in linux, processing udev rules and executing an script every time the device is connected/disconnected.
Scenario:
- Notebook: HP 820 g3
- Dock: 2013 Ultra Slim Docking Station (two display ports)
- Notebook ports: (1) Display Port and (1) VGA ports.
My objective was to run a bash script every time a display port cable is connected/disconnected in any of those ports (with my Dock).
Detecting devices/events at kernel’s level
Kernel will be the first to be notified when something is connected, in this stage we can detect almost anything, any new device (USB Pendrives, Monitors or whatever we connect) usually will be detected and informed to the kernel.
This is the result of connecting one Display Port cable (attached to a monitor) to my dock:
root@lubuntu:~# udevadm monitor -p UDEV [362615.666734] change /devices/pci0000:00/0000:00:02.0/drm/card0 (drm) ACTION=change DEVNAME=/dev/dri/card0 DEVPATH=/devices/pci0000:00/0000:00:02.0/drm/card0 DEVTYPE=drm_minor HOTPLUG=1 ID_FOR_SEAT=drm-pci-0000_00_02_0 ID_PATH=pci-0000:00:02.0 ID_PATH_TAG=pci-0000_00_02_0 MAJOR=226 MINOR=0 SEQNUM=3084 SUBSYSTEM=drm TAGS=:master-of-seat:uaccess:seat: USEC_INITIALIZED=5157028 net.ifnames=0 KERNEL[362616.133667] add /devices/pci0000:00/0000:00:02.0/i2c-6/i2c-dev/i2c-6 (i2c-dev) ACTION=add DEVNAME=/dev/i2c-6 DEVPATH=/devices/pci0000:00/0000:00:02.0/i2c-6/i2c-dev/i2c-6 MAJOR=89 MINOR=6 SEQNUM=3101 SUBSYSTEM=i2c-dev KERNEL[362616.133849] add /devices/pci0000:00/0000:00:02.0/i2c-6 (i2c) ACTION=add DEVPATH=/devices/pci0000:00/0000:00:02.0/i2c-6 SEQNUM=3102 SUBSYSTEM=i2c KERNEL[362616.134155] add /devices/pci0000:00/0000:00:02.0/drm/card0/card0-DP-3 (drm) ACTION=add DEVPATH=/devices/pci0000:00/0000:00:02.0/drm/card0/card0-DP-3 SEQNUM=3103 SUBSYSTEM=drm KERNEL[362616.134348] change /devices/pci0000:00/0000:00:02.0/drm/card0 (drm) ACTION=change DEVNAME=/dev/dri/card0 DEVPATH=/devices/pci0000:00/0000:00:02.0/drm/card0 DEVTYPE=drm_minor HOTPLUG=1 MAJOR=226 MINOR=0 SEQNUM=3104 SUBSYSTEM=drm KERNEL[362616.134486] add /devices/pci0000:00/0000:00:02.0/drm/card0/card0-DP-4 (drm) ACTION=add DEVPATH=/devices/pci0000:00/0000:00:02.0/drm/card0/card0-DP-4 SEQNUM=3105 SUBSYSTEM=drm KERNEL[362616.134651] change /devices/pci0000:00/0000:00:02.0/drm/card0 (drm) ACTION=change DEVNAME=/dev/dri/card0 DEVPATH=/devices/pci0000:00/0000:00:02.0/drm/card0 DEVTYPE=drm_minor HOTPLUG=1 MAJOR=226 MINOR=0 SEQNUM=3106 SUBSYSTEM=drm KERNEL[362616.134781] add /devices/pci0000:00/0000:00:02.0/drm/card0/card0-DP-5 (drm) ACTION=add DEVPATH=/devices/pci0000:00/0000:00:02.0/drm/card0/card0-DP-5 SEQNUM=3107 SUBSYSTEM=drm KERNEL[362616.134934] change /devices/pci0000:00/0000:00:02.0/drm/card0 (drm) ACTION=change DEVNAME=/dev/dri/card0 DEVPATH=/devices/pci0000:00/0000:00:02.0/drm/card0 DEVTYPE=drm_minor HOTPLUG=1 MAJOR=226 MINOR=0 SEQNUM=3108 SUBSYSTEM=drm KERNEL[362616.135128] change /devices/pci0000:00/0000:00:02.0/drm/card0 (drm) ACTION=change DEVNAME=/dev/dri/card0 DEVPATH=/devices/pci0000:00/0000:00:02.0/drm/card0 DEVTYPE=drm_minor HOTPLUG=1 MAJOR=226 MINOR=0 SEQNUM=3109 SUBSYSTEM=drm
Woah, almost 10 events were triggered, so now which one we would choose?
UDEV:
- Detected 1 (one) change in the parent device card0
KERNEL:
- Detected 4 (four) changes
- The 4 (four) changes are for the parent device card0
- Detected 5 (five) add events
- 2 (two) added for /dev/i2c-6
- One for the subsystem: i2c-dev
- One for the subsystem: i2c
- 3 (three) for added display ports:
- One for card0-DP-3
- One for card0-DP-4
- One for card0-DP-5
- 2 (two) added for /dev/i2c-6
So in order to select an specific event I will choose the subsystem i2c-dev, this will be the event that will trigger the script in the UDEV rule:
KERNEL[362616.133667] add /devices/pci0000:00/0000:00:02.0/i2c-6/i2c-dev/i2c-6 (i2c-dev) ACTION=add DEVNAME=/dev/i2c-6 DEVPATH=/devices/pci0000:00/0000:00:02.0/i2c-6/i2c-dev/i2c-6 MAJOR=89 MINOR=6 SEQNUM=3101 SUBSYSTEM=i2c-dev
We will take the KERNEL event «add», the DEVNAME «/dev/i2c-6» and the SUBSYSTEM: «i2c-dev» because it happens only ONCE when I connect the DP cable. I didn’t used the UDEV rule for the parent device card0, after several testing the «change» event for card0 happens many times when disconnecting the cable, so it isn’t an option.
Well, now let’s take a look on xrandr to see which monitor was attached:
root@lubuntu:~# xrandr Screen 0: minimum 8 x 8, current 1600 x 900, maximum 32767 x 32767 eDP1 connected 1366x768+0+0 (normal left inverted right x axis y axis) 277mm x 156mm 1366x768 60.06*+ 40.04 1360x768 59.80 59.96 1280x720 60.00 1024x768 60.00 1024x576 60.00 960x540 60.00 800x600 60.32 56.25 864x486 60.00 640x480 59.94 720x405 60.00 680x384 60.00 640x360 60.00 DP1 disconnected (normal left inverted right x axis y axis) DP1-1 connected 1600x900+0+0 (normal left inverted right x axis y axis) 443mm x 249mm 1600x900 60.00*+ 1280x1024 60.02 1440x900 59.89 1280x800 59.81 1280x720 60.00 1024x768 60.00 800x600 60.32 640x480 60.00 720x400 70.08 DP1-2 disconnected (normal left inverted right x axis y axis) DP1-3 disconnected (normal left inverted right x axis y axis) DP2 disconnected (normal left inverted right x axis y axis) HDMI1 disconnected (normal left inverted right x axis y axis) HDMI2 disconnected (normal left inverted right x axis y axis) VIRTUAL1 disconnected (normal left inverted right x axis y axis)
As you can see, depending on which display port I connect the cable the respective device will be presented to xrandr, it could be DP1-1, DP1-2 or DP1-3, this must be considered when writting the bash script at the end of this document. eDP1 is the default screen.
Setting up udev rules
The secret of a good rule is to be quite specific. That means pointing to the child devices instead of the parent devices. We already did that when we selected the set of events (KERNEL event «add», the DEVNAME «/dev/i2c-6» and the SUBSYSTEM: «i2c-dev») in the first step, because it only happens once.
Now that we know our trigger, it’s time to set up a rule, I’ve created the file local.rules: /etc/udev/rules.d/local.rules
KERNEL=="i2c-[0-9]",ACTION=="add",SUBSYSTEM=="i2c-dev", RUN+="/usr/local/bin/monitor.sh"
Because I don’t know if the device name could change (when rebooting or reconnecting the dock to the laptop) I’ve used the bash matching listing «[0-9]» so this evaluate that one of the characters between brackets is a match, of course our i2c-6 will be a match.
The rest of the rules are the same we found previously, finally we execute the script when the event «add» for the device i2c-6 with the subsystem i2c-dev occurs.
I’ve also added a rule when the cable is disconnected from the dock:
KERNEL=="i2c-[0-9]",ACTION=="remove",SUBSYSTEM=="i2c-dev", RUN+="/usr/local/bin/monitor.sh"
But that would lead to the script being executed the same way when I connect or disconnect the cable, so I fixed that problem by passing parameters to the script:
KERNEL=="i2c-[0-9]",ACTION=="add",SUBSYSTEM=="i2c-dev", RUN+="/usr/local/bin/monitor.sh 'add'" KERNEL=="i2c-[0-9]",ACTION=="remove",SUBSYSTEM=="i2c-dev", RUN+="/usr/local/bin/monitor.sh 'remove'"
Finally the bash script. As I’ve mentioned before the monitor could be «DP1-1, DP1-2 or DP1-3»
#!/bin/bash # miguel.ortiz # Wait for the kernel to get the new devices sleep 5 #Set the screen export DISPLAY=:0 # udev event event=$1 # this come from the udev rule (add | remove) # identify the devices connected device=$(xrandr |grep "^DP.* connected" |awk '{print $1}') # if the event is "add" and a device "exists" if [ $event == "add" && ${#device[@]} == 1 ] ; then xrandr --output $device --auto --output eDP1 --mode 1366x768 --rate 60.06 --same-as $device # if the event is "remove" and there are no devices left elif [ $event == "remove" && ${#device[@]} == 0 ] ; then xrandr -s 0 # if something else happens, like connecting two monitors display a window alert else zenity --info --text "Something odd happened when adding/removing a monitor"
That’s it, now everytime I connect or disconnect display port cable with a monitor it works and when I remove the cable the main screen of the laptop returns to it’s default settings.
Trackbacks/Pingbacks