Recently I was wondering how to automatically set up an external monitor without using desktop utilities as I don’t like to configure it each time. This question provided me a lot of fun as it opens a couple of interesting possibilities.
Introduction
I am using Debian (testing) installed on a notebook equipped with an integrated graphics card, so there may be some differences in any other configuration.
Shell script
Use xrandr command to determine the port name (HDMI1 in this example):
$ xrandr
Screen 0: minimum 320 x 200, current 1366 x 768, maximum 8192 x 8192
LVDS1 connected 1366x768+0+0 (normal left inverted right x axis y axis) 309mm x 174mm
1366x768 60.0*+ 40.0
1360x768 59.8 60.0
1024x768 60.0
800x600 60.3 56.2
640x480 59.9
VGA1 disconnected (normal left inverted right x axis y axis)
HDMI1 connected (normal left inverted right x axis y axis)
1920x1200 60.0 +
1600x1200 60.0
1280x1024 75.0 60.0
1280x960 60.0
1152x864 75.0
1024x768 75.1 70.1 60.0
832x624 74.6
800x600 72.2 75.0 60.3 56.2
640x480 72.8 75.0 66.7 60.0
720x400 70.1
DP1 disconnected (normal left inverted right x axis y axis)
HDMI2 disconnected (normal left inverted right x axis y axis)
DP2 disconnected (normal left inverted right x axis y axis)
Create a simple script to automatically setup external monitor (above internal LCD screen):
#!/bin/sh
# Automatically setup external monitor
xrandr_command="/usr/bin/xrandr"
sed_command="/bin/sed"
is_hdmi_connected=`DISPLAY=:0 $xrandr_command | $sed_command -n '/HDMI1 connected/p'`
if [ -n "$is_hdmi_connected" ]; then
DISPLAY=:0 $xrandr_command --output HDMI1 --auto --above LVDS1
else
DISPLAY=:0 $xrandr_command --output HDMI1 --off
fi
Store it as /usr/bin/setup_external_monitor.sh. Connect the external monitor using the HDMI port and execute the script, disconnect it, and execute it again. It should work flawlessly.
udev
The best way to automate this task is to use udev as it can automatically execute setup_external_monitor.sh script after the external monitor is connected or disconnected.
To create the udev rule, you need to know ACTION (name of the event action), KERNEL (name of the event device), and SUBSYSTEM (a subsystem of the event device).
Use udevadm command to monitor for udev events and connect and then disconnect external monitor:
$ sudo udevadm monitor
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent
KERNEL[116342.884117] change /devices/pci0000:00/0000:00:02.0/drm/card0 (drm)
UDEV [116342.886074] change /devices/pci0000:00/0000:00:02.0/drm/card0 (drm)
^C
ACTION is “change”, now read KERNEL and SUSBSYTEM parameters:
# udevadm info -a -p /devices/pci0000:00/0000:00:02.0/drm/card0
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:02.0/drm/card0':
KERNEL=="card0"
SUBSYSTEM=="drm"
DRIVER==""
[...]
Create as root /etc/udev/rules.d/70-persistent-monitor.rules file to execute the script:
KERNEL=="card0", SUBSYSTEM=="drm", ACTION=="change", RUN+="su milosz -c /usr/bin/setup_external_monitor.sh"
Replace milosz with your username. It is important as you need to execute script with the permissions of the logged-in user.
Restart udev:
$ sudo /etc/init.d/udev restart
[ ok ] Stopping the hotplug events dispatcher: udevd.
[ ok ] Starting the hotplug events dispatcher: udevd.
Done. The script will be executed automatically.
Is it possible to distinguish between multiple devices?
You can modify the above-mentioned script to use different settings based on the manufacturer and model name. However, there is no easy way to uniquely identify the output device – you could try to read the Xorg log file or calculate MD5 hash of the edid file.
Install read-edid package to read EDID (Extended display identification data):
$ sudo apt-get install read-edid
Command used to show sample VendorName and ModelName values:
$ parse-edid /sys/devices/pci0000\:00/0000\:00\:02.0/drm/card0/card0-HDMI-A-1/edid 2>/dev/null | sed -n "/\(VendorName\|ModelName\)/p"
Samsung SyncMaster LCD monitor connected to the HDMI port:
VendorName "SAM"
ModelName "SyncMaster"
Dell N411z internal LCD screen:
VendorName "SEC"
ModelName "SEC:4154"
Panasonic TV set:
VendorName "MEI"
ModelName "PANASONIC-TV"
Now you can modify setup_external_monitor.sh script to configure LCD monitor according to VendorName and ModelName values:
#!/bin/sh
# Automatically setup external monitor
xrandr_command=`which xrandr`
sed_command=`which sed`
edid_command=`which parse-edid`
awk_command=`which awk`
is_hdmi_connected=`DISPLAY=:0 $xrandr_command | $sed_command -n '/HDMI1 connected/p'`
if [ -n "$is_hdmi_connected" ]; then
# Read vendor
vendor=`$edid_command /sys/devices/pci0000:00/0000:00:02.0/drm/card0/card0-HDMI-A-1/edid 2>/dev/null | $awk_command -F '"' '/VendorName/ {print $2}'`
# Read model
model=`$edid_command /sys/devices/pci0000:00/0000:00:02.0/drm/card0/card0-HDMI-A-1/edid 2>/dev/null | $awk_command -F '"' '/ModelName/ {print $2}'`
if [ -n "$model" ] && [ -n "$vendor" ]; then
if [ "$model" = "PANASONIC-TV" ] && [ "$vendor" = "MEI" ]; then
# Panasonic TV - right of the internal LCD
DISPLAY=:0 $xrandr_command --output HDMI1 --auto --right-of LVDS1
elif [ "$model" = "SyncMaster" ] && [ "$vendor" = "SAM" ]; then
# Samsung SyncMaster - left of the internal LCD
DISPLAY=:0 $xrandr_command --output HDMI1 --auto --left-of LVDS1
fi
else
# Default - above the internal LCD
DISPLAY=:0 $xrandr_command --output HDMI1 --auto --above LVDS1
fi
else
DISPLAY=:0 $xrandr_command --output HDMI1 --off
fi
You can use wireless network name (iwconfig), IP address (ifconfig), or even process name (ps) to extend it further.
How to exactly specify the screen resolution?
Use xrandr command combined with awk to define maximum resolution:
#!/bin/sh
# Automatically setup external monitor
xrandr_command="/usr/bin/xrandr"
awk_command="/bin/awk"
resolution=`${xrandr} | $awk_command '/HDMI1 connected/ { getline; print $1 }'`
if [ -n "$resolution" ]; then
DISPLAY=:0 $xrandr_command --output HDMI1 --mode $resolution --above LVDS1
else
DISPLAY=:0 $xrandr_command --output HDMI1 --off
fi
Additional notes
There is an easier way to find card0 device path:
$ sudo udevadm info -q path -n /dev/dri/card0
/devices/pci0000:00/0000:00:02.0/drm/card0
Use sysfs to read device parameters:
$ ls /sys/devices/pci0000:00/0000:00:02.0/drm/card0
card0-DP-1 card0-DP-2 card0-HDMI-A-1 card0-HDMI-A-2 card0-LVDS-1 card0-VGA-1 dev device power subsystem uevent
$ cat /sys/devices/pci0000\:00/0000\:00\:02.0/drm/card0/card0-HDMI-A-1/status
disconnected
Use udevadm to read device parameters:
$ sudo udevadm info -a -p /devices/pci0000:00/0000:00:02.0/drm/card0/card0-HDMI-A-1
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:02.0/drm/card0/card0-HDMI-A-1':
KERNEL=="card0-HDMI-A-1"
SUBSYSTEM=="drm"
DRIVER==""
ATTR{status}=="disconnected"
ATTR{enabled}=="disabled"
ATTR{dpms}=="Off"
ATTR{modes}==""
ATTR{edid}==""
looking at parent device '/devices/pci0000:00/0000:00:02.0/drm/card0':
KERNELS=="card0"
SUBSYSTEMS=="drm"
DRIVERS==""
looking at parent device '/devices/pci0000:00/0000:00:02.0':
KERNELS=="0000:00:02.0"
SUBSYSTEMS=="pci"
DRIVERS=="i915"
ATTRS{vendor}=="0x8086"
ATTRS{device}=="0x0116"
ATTRS{subsystem_vendor}=="0x1028"
ATTRS{subsystem_device}=="0x051b"
ATTRS{class}=="0x030000"
ATTRS{irq}=="47"
ATTRS{local_cpus}=="00000000,00000000,00000000,00000000,00000000,00000000,
00000000,00000000,00000000,00000000,00000000,00000000,
00000000,00000000,00000000,0000000f"
ATTRS{local_cpulist}=="0-3"
ATTRS{numa_node}=="-1"
ATTRS{dma_mask_bits}=="40"
ATTRS{consistent_dma_mask_bits}=="40"
ATTRS{enable}=="1"
ATTRS{broken_parity_status}=="0"
ATTRS{msi_bus}==""
ATTRS{boot_vga}=="1"
looking at parent device '/devices/pci0000:00':
KERNELS=="pci0000:00"
SUBSYSTEMS==""
DRIVERS==""
Full parse-edid output:
$ parse-edid /sys/devices/pci0000:00/0000:00:02.0/drm/card0/card0-HDMI-A-1/edid
parse-edid: parse-edid version 2.0.0
parse-edid: EDID checksum passed.
# EDID version 1 revision 3
Section "Monitor"
# Block type: 2:0 3:fd
# Block type: 2:0 3:fc
Identifier "SyncMaster"
VendorName "SAM"
ModelName "SyncMaster"
# Block type: 2:0 3:fd
HorizSync 30-81
VertRefresh 56-75
# Max dot clock (video bandwidth) 170 MHz
# Block type: 2:0 3:fc
# Block type: 2:0 3:ff
# DPMS capabilities: Active off:yes Suspend:no Standby:no
Mode "1920x1200" # vfreq 59.950Hz, hfreq 74.038kHz
DotClock 154.000000
HTimings 1920 1968 2000 2080
VTimings 1200 1203 1209 1235
Flags "-HSync" "+VSync"
EndMode
# Block type: 2:0 3:fd
# Block type: 2:0 3:fc
# Block type: 2:0 3:ff
EndSection
Update – 28/02/2013
I updated the “Automatically setup external monitor” script as I was pointed by M Pagey that the model name could contain white spaces.