Monitor hot-plugging with X
(This is part of a larger series on finding your footing on Arch Linux and part 2 in a two-part monitor sequence. You should be familiar with part 1 first.)
Goal: use udev
to detect when a monitor cable is physically connected or disconnected, and use xrandr
to automatically update the monitor display in response.
References:
Procedure
I’ll first concisely describe the steps to get things working, and then explain the reasoning and some of the technicalities at the end of this guide. By the way, the approach used here comes from Arch Forums: udev rule doesn’t work (hotplug monitor).
TLDR:
- Create a shell script that uses
xrandr
to update displays based HDMI/DisplayPort connection status in thesysfs
file system. - Wrap the shell script in a
systemd
service. - Create a
udev
rule that runs thesystemd
service in response to video cable hot-plugging.
Check monitor connection with sysfs
Goal: learn how to check a video output’s connected
/disconnected
status using the device files in /sys/class/drm
.
First make sure you know the xrandr
name (e.g. HDMI-1
, DP-1
, etc.) of the video output from part 1 that you plan on using to connect your monitor—I’ll use HDMI-1
in this guide, but change this as needed for your own case.
Then check the contents of the directory /sys/class/drm
.
Here’s what this looks like on my computer:
$ ls /sys/class/drm
card0-DP-1
card0-DP-2
card0-eDP-1
card0-HDMI-A-1
card0-HDMI-A-2
Each directory represents a display device.
The names should match the video outputs shown by xrandr
in part 1;
for example, card0-HDMI-A-1
corresponds to HDMI-1
.
There should be a status
file inside card0
directory that shows the connected
/disconnected
state of each device.
For example:
# *Connects HDMI cable to computer*
$ cat /sys/class/drm/card0-HDMI-A-1
connected
# *Unplugs HDMI cable from computer*
$ cat /sys/class/drm/card0-HDMI-A-1
disconnected
Check-in point: you should know the sysfs
directory name (e.g. card0-HDMI-A-1
, card0-DP-A-1
) of the device you’ll use to connect your monitor.
We’ll need this name in the following shell script.
Hotplug script
Here’s the shell script to implement hotplug and display-switching logic (explanation follows).
I’d suggest naming the script hotplug-monitor.sh
and placing it in /usr/local/bin
(a conventional location for local user programs not controlled by a package manager), but any system-wide readable location should work.
#!/bin/sh
# File location: /usr/local/bin/hotplug-monitor.sh
# Description: Sends X display to an external monitor and turns internal
# display off when an HDMI cable is physically connected; turns monitor display
# off and internal display back on when HDMI cable is physically disconnected.
# Specify your username and user ID
USER_NAME=<your-username> # find with `id -un` or `whoami`
USER_ID=<your-user-id> # find with `id -u`
# Example: `USER_NAME=ejmastnak`
# Example: `USER_NAME=1000`
# Export user's X-related environment variables
export DISPLAY=":0"
export XAUTHORITY="/home/${USER_NAME}/.Xauthority"
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/${USER_ID}/bus"
# Video output and device names recogized by xrandr/sysfs
internal="eDP-1" # change as needed
external="HDMI-1" # change as needed
device="card0-HDMI-A-1" # change as needed
# If external display was just physically connected, turn external display on
# and (optionally) turn internal display off to save battery.
if [ $(cat /sys/class/drm/${device}/status) == "connected" ];
then
xrandr --output "${external}" --auto # sends display to monitor
xrandr --output "${internal}" --off # optionally turn internal display off
# If external display was just physically disconnected, turn
# external display off and turn internal display on.
elif [ $(cat /sys/class/drm/${device}/status) == "disconnected" ];
then
xrandr --output "${external}" --off # turn monitor display off
xrandr --output "${internal}" --auto # turn internal display on (if needed)
else # Do nothing if device status is unreadable
exit
fi
Some comments:
-
The
internal
andexternal
variables store thexrandr
names of the video outputs for your internal display and HDMI connection. This should be clear from part 1. -
The
device
variable is the name ofsysfs
graphics device identified above in the section Check monitor connection withsysfs
. We use this variable in thecat /sys/class/drm/${device}/status
calls to check the device’s connection status. -
You need to export some X-related environment variables—I’m not satisfied with my understanding of why, but doing so makes the shell script, which runs from a location on the root partition, aware of your user’s X session information.
DISPLAY=:0
is standard X lingo for the first display on the local computer, and/home/${USER_NAME}/.Xauthority
is just the path to your.Xauthority
file. -
You’ll also need to know your username and user ID, which you can find with the
id
program:# Example: identifying your username $ id -un ejmastnak # Example: identifying your user ID $ id -u 1000
You can run
id
(with no flags) for more information; you should definitely read throughman id
(it’s really short!) if you haven’t used theid
program before.
systemd
unit
We’ll run the above shell script from a systemd
service.
Create a systemd
unit /etc/systemd/system/hotplug-monitor.service
with the following contents:
[Unit]
Description=Monitor hotplug service
[Service]
Type=simple
# Make the service run as your user and not as root
User=<your-username> # add your username here
# Change path to hotplug script as needed
ExecStart=/usr/local/bin/hotplug-monitor.sh
[Install]
WantedBy=multi-user.target
Make sure to update the User=<your-username>
field with your username,
then run systemctl daemon-reload
to make systemd
register the new service file.
No need to enable or start the service manually—it will started as needed from the following udev
rule.
udev
rule
Create a udev
rule in /etc/udev/rules.d/85-drm-hotplug.rules
with the following contents:
ACTION=="change", KERNEL=="card0", SUBSYSTEM=="drm", RUN+="/usr/bin/systemctl start hotplug-monitor.service"
This rule runs the above hotplug-monitor
service whenever udev
detects chages in devices with kernel name card0
in the drm
subsystem—basically when you plug/unplug display cables.
(DRM stands for “Direct Rendering Manager”—you can read more at Wikipedia: DRM.)
Appendix: Technicalities
There are (at least) two fair questions here:
-
Why are we complicating things with a
systemd
service instead of running the hotplug script directly from theudev
rule, as in e.g. in these ArchWiki examples? -
Why are we checking device connections using the
sysfs
device filesys/class/drm/*/status
instead of just runningxrandr
, as in part 1?
Based on my current understanding of udev
and systemd
best practices and some empirical testing, here are my answers:
-
udev
rules are meant to run only short processes, and will block events from the triggering device until the initial process completes (see e.g. ArchWiki: udev/Spawning long-running processes and the references therein). This is why we use asystemd
service to run the shell script, and then only use theudev
rule to start the service. -
From my own testing (and the experience of at least one other user), the
udev
rule for hotplugging a display device can be triggered beforexrandr
becomes aware of the corresponding video output’s updatedconnected
/disconnected
status. As far as I can tell, in such cases/sys/class/drm/*/status
will show the correct connection status, butxrandr
will still be lagging behind by a few hundred milliseconds or so.For hot-plugging, it is thus more reliable to check a display’s connection state from the contents of
/sys/class/drm/*/status
than to usegrep
to parse the output of thexrandr
command, as in e.g. part 1.