Recently I was wondering how to automatically set up 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 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 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 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 external monitor using 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 external monitor is connected or disconnected.
To create udev rule you need to know ACTION (name of the event action), KERNEL (name of the event device) and SUBSYSTEM (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 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 above-mentioned script to use different settings based on the manufacturer and model name, however there is no easy way to uniquely identify output device - you could try to read xorg log file or calculate MD5 hash of the edid file.
To read EDID (Extended display identification data) you need to install read-edid package:
$ 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 if you have similar devices.
How to exactly specify the screen resolution?
To define maximum resolution use xrandr command combined with awk:
#!/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 "Automatically setup external monitor" script as I was pointed by M Pagey that model name could contain white spaces.