Monitor Hotplug

Updated: 4 minute read

Background/Problem

Well, I’m running an Arch Linux [1] machine at the moment and recently switched from Qtile [2] to Xmonad [3] as my window manager. While still using qtile, I wrote a dmenu [4] script to switch between different monitor setups. I used that script when:

  • I wanted to turn external monitors or my laptop monitor on or off.
  • I connected or disconnected an external monitor.

I still use that script for the first mentioned purpose, but not anymore in case an external monitor is connected or disconnected. In the process of switching to xmonad, I thought it would be great if the system would automatically detected when an external monitor was added or removed, and adjust the monitor settings automatically.

The main inspiration for how to make this work came from a blog post that I found here [5]. But even with this post, it took me quite a while to make it work for me. That’s why I want to document my current setup.

Solution

All right, my solution to the mentions problem involves a couple of bash scripts, a udev rule, and a systemd service. Let’s get started with the bash scripts.

First of all, I used ARandR [6] to create scripts that call XRandR [7] for the different possible monitor setups that I use (e.g. just the laptop monitor, the laptop monitor plus one external one, the laptop monitor plus two external ones, …). The names of the scripts follow the following convention:

<host>_internal-<internal_num>_external-<external_num>.sh

With <host> being the host name of my laptop (I currently use two different ones, and the monitor setups are slightly different for them), <internal_num> may be 1 or 0, depending on if I want to use the laptop monitor, and <external_num> the number of external monitors that are supposed to be used. When I use my dmenu script to switch between setups, dmenu basically let’s me select one of these scripts and then invokes it.

These scripts are also called when X starts, but not directly. In my xinitrc file I have a line that triggers a script called startup_screen_setup.sh. Among some other stuff, this script checks the hostname and how many monitors are connected to the system:

hostname=`cat /etc/hostname`
monitors=(/sys/class/drm/*/status)
num_monitors=0
for monitor in "${monitors[@]}";
do
    if [[ "connected" == $(cat "${monitor}") ]]
    then
        num_monitors=$((num_monitors + 1))
    fi
done

The script assumes that, when I start my laptop, the lid is open, so there is one internal monitor, and all additional monitors that might be connected are external ones:

num_internal_monitors=1
num_external_monitors=$((num_monitors - num_internal_monitors))

It uses that information to call the correct xrandr script (see above). After that, xinitrc starts xmonad.

So, that’s what happens during the start of the X server. But what about hotplugging (does that word exist :-)?)? Well, I wrote a udev rule, and placed the file in /etc/udev/rules.d/95-external-monitors.rules:

ACTION=="change", KERNEL=="card0", SUBSYSTEM=="drm", RUN+="/path/to/startup_screen_setup.sh"

Unfortunately this didn’t work, at least not when I plugged an external monitor into the hdmi jack. It took me quite some time to figure out, that even though the system has detected a new monitor and the udev rule triggered the startup_screen_setup.sh script, the new monitor was not necessarily already recognized by the X server. So I added some more lines in the startup_screen_setup.sh file. These lines check whether X thinks that the same number of monitors is present as the system does. If not it just sleeps for a second and checks again:

while [ $(xrandr --listactivemonitors \
        | head -n 1 \
        | cut -d " " -f2) -ne $num_monitors \
]
do
    sleep 1
done

Unfortunately, as you can read here [8], you should not spawn long running processes from udev. That’s why I wrote a systemd service with the following content, and placed it in /etc/systemd/system/monitor-hotplug.service:

[Unit]
Description=Monitor Hotplug

[Service]
Type=simple
RemainAfterExit=no
User=andreas
ExecStart=/path/to/monitor_connection_chaged_script

[Install]
WantedBy=multi-user.target

This service does not call startup_screen_setup.sh directly, but another helper script. That helper script than calls the startup_screen_setup.sh script and restarts xmonad afterwards.

All that was left to do was change the udev rule slightly:

ACTION=="change", KERNEL=="card0", SUBSYSTEM=="drm", RUN+="/usr/bin/systemctl start monitor-hotplug.service"

Well, this post did get a little longer than I thought, but the whole process to come up with the solution also took a little longer than I had hoped ;-).

Change Log

2022-09-23:

  • Change wording a little bit.
  • Change formatting a little bit.



Take care,
Andreas


References

  1. J. Vinet and A. Griffin, “Arch Linux.” [Online]. Available at: https://archlinux.org/. [Accessed: 28-Aug-2020].
  2. Qtile, “Qtile – A hackable tiling window manager written in Python.” [Online]. Available at: https://qtile.org/. [Accessed: 07-Feb-2024].
  3. Xmonad, “xmonad - the tiling window manager that rocks.” [Online]. Available at: https://xmonad.org/. [Accessed: 07-Feb-2024].
  4. suckless.org, “dmenu.” [Online]. Available at: https://tools.suckless.org/dmenu/. [Accessed: 13-Nov-2020].
  5. A. Gumirov, “Xmonad and Xmobar for laptop,” 27-Dec-2020. [Online]. Available at: https://gumirov.xyz/posts/f15c07386b770f9be62364935f64db1f37853cf1500d55ca795064165abed740/. [Accessed: 15-May-2022].
  6. C. Amsuess, “ARandR: Another XRandR GUI.” [Online]. Available at: https://christian.amsuess.com/tools/arandr/. [Accessed: 15-May-2022].
  7. Xorg Foundation, “XRandR.” [Online]. Available at: https://www.x.org/wiki/Projects/XRandR/. [Accessed: 15-May-2022].
  8. ArchWiki, “udev: Spawning long-running processes,” 17-Apr-2022. [Online]. Available at: https://wiki.archlinux.org/title/udev#Spawning_long-running_processes. [Accessed: 15-May-2022].

Updated:

Leave a comment