How to automatically set up external monitor

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.

Milosz Galazka