HDMI Autoswitch

A nice script that does everything you want when you plug a monitor to your HDMI plug: enable the monitor with xrandr, and redirect the sound to the HDMI output.

Introduction

Lately I started to plug my laptop on a TV using the HDMI plug. I hoped that it would work out the box, since HDMI has been around for quite a while. But I use a quite bare Debian, with Openbox as a window manager, and plain ALSA for the sound. That's it. In such environment, NO-THING works out of the box.

And as often, I'm amazed at how long and complicated is the way to get things done. Especially since, in this case, I just want to get the HDMI thing to work. I'm not trying to do anything fancy, instead I just want my hardware to do its job.

OK, so if you're in the same situation, follow my steps.

Screen test: playing around with lxrandr

There's a simple GUI tool to get started: lxrandr.

apt-get install lxrandr

Plug your HDMI cable, launch lxrandr, click around, and everything is easy!

Lxrandr is the LXDE graphical frontend for the command xrandr. It's pretty convenient to have it around, but it won't get anything automated. If you don't want to launch lxrandr each time you connect your laptop to your TV, read on.

Screen automation: udev solution

There are various solutions to automatically enable/disable an external monitor when HDMI is plugged. It's well covered on this StackExchange page: http://unix.stackexchange.com/q/4489

I opted for an udev script. Makes your hands dirty a bit, but it's quite simple and reliable.

First thing first, do some testing with udevadm. Launch the monitor command, plug your HDMI cable, and ensure it's alive.

udevadm monitor --environment --udev

Be sure to turn on the TV at the other side of the cable, otherwise nothing happens. You might get this kind of output when you plug/unplug:

UDEV  [2247.166677] change   /devices/pci0000:00/0000:00:02.0/drm/card0 (drm)
ACTION=change
DEVNAME=/dev/dri/card0
DEVPATH=/devices/pci0000:00/0000:00:02.0/drm/card0
DEVTYPE=drm_minor
HOTPLUG=1
ID_FOR_SEAT=drm-pci-0000_00_02_0
ID_PATH=pci-0000:00:02.0
ID_PATH_TAG=pci-0000_00_02_0
MAJOR=226
MINOR=0
SEQNUM=2120
SUBSYSTEM=drm
TAGS=:seat:master-of-seat:uaccess:
USEC_INITIALIZED=15405142

Based on this information, let's create an udev rule (edit as needed).

echo '
KERNEL=="card0", SUBSYSTEM=="drm", ENV{DISPLAY}=":0", ENV{XAUTHORITY}="/home/user/.Xauthority", RUN+="/usr/local/bin/hotplug_monitor.sh"
' > /etc/udev/rules.d/95-monitor-hotplug.rules

Now, let's write the script /usr/local/bin/hotplug_monitor.sh that will do the job.

#!/bin/bash

function connect() {
    xrandr --output HDMI-1 --same-as LVDS-1 --preferred --primary --output LVDS-1 --preferred
}

function disconnect() {
    xrandr --output HDMI-1 --off
}

xrandr | grep "HDMI-1 connected" &> /dev/null && connect || disconnect

I basically used the script provided in the StackExchange discussion here http://unix.stackexchange.com/a/171916, with minors modifications. You might need to adapt a bit according to your needs.

Sound test: ALSA just needs to be tamed

Up to now, we just mastered half of the HDMI: the image part. Now comes the other half: the sound.

If you're using plain ALSA as I do, you probably still have the sound coming out of your laptop, and you probably wish to have it go through the HDMI instead.

Well, at first, let's check that everything is working. Let's perform a quick sound check. My favorite command for that is:

speaker-test -c2 -twav

Here, we didn't specify any soundcard, so ALSA send the sound to the default sound card. In my case, the sound is played by my laptop speakers.

Now, we can tell ALSA to use the HDMI device to play the sound.

speaker-test -D hdmi -c2 -twav

In my case, I hear the sound from the TV now. It works!

So, if you're in the same situation, everything works fine. That is: any sound is played through the default soundcard, unless you ask otherwise. If you want an application to send the sound to the HDMI, you need to tell it explicitly.

For example, if you play a movie with VLC, you can go to Audio -> Audio Device and set it to HDMI.

That's not too bad, but you can already see that it will be a pain. Each time you plug the HDMI, you will need to reconfigure VLC, probably also your web browser, your music player... Want a better solution?

There is one. Don't reconfigure any application, but reconfigure ALSA instead, so that the HDMI becomes the default sound card.

Doing that is ridiculously easy in my case. First, let me show you what my sound devices are.

$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: PCH [HDA Intel PCH], device 0: VT1802 Analog [VT1802 Analog]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 0: PCH [HDA Intel PCH], device 2: VT1802 Alt Analog [VT1802 Alt Analog]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 0: PCH [HDA Intel PCH], device 3: HDMI 0 [HDMI 0]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

I've got one soundcard (integrated to the laptop), divided in three devices. All I want is that ALSA uses the device 3 instead of the device 0 for the default. And for that, I just need to create an asoundrc file:

echo '
# Use HDMI as the default playback device
defaults.pcm.device 3
' > ~/.asoundrc

It looks simple, but believe me it's not that simple, since the syntax of this asoundrc is somehow more about magic than anything else. I found many ways to achieve that on the Net, most of them looked roughly the same, but somehow broke the sound in the web browser, or just didn't work.

Anyway. With that simple line, the whole sound is send to HDMI. Notice, however, that if you had applications launched, you need to restart them for the change to take effect.

Sound automation

To automate this change, just add a few lines in the script hotplug_monitor.sh we wrote above.

#!/bin/bash

function connect() {
    # Screen
    xrandr --output HDMI-1 --same-as LVDS-1 --preferred --primary --output LVDS-1 --preferred
    # Sound
    [ -f /etc/asound.conf ] && mv -f /etc/asound.conf /etc/asound.before-hdmi.conf
    echo 'defaults.pcm.device 3' > /etc/asound.conf
}

function disconnect() {
    # Screen
    xrandr --output HDMI-1 --off
    # Sound
    rm -f /etc/asound.conf
    [ -f /etc/asound.before-hdmi.conf ] && mv -f /etc/asound.before-hdmi.conf /etc/asound.conf
}

xrandr | grep "HDMI-1 connected" &> /dev/null && connect || disconnect

As you can notice, here I chose to write changes in the global file /etc/asound.conf, but you can choose to edit the user file ~/.asoundrc. It works all the same.

Conclusion

Now it's alsmot all automated. The only things that we have to do, apart from plugging and unpluggin the HDMI, is to restart the applications that produce sound. It's not too bad!

One last word: with this setup, you will notice that you can't change the volume anymore from your laptop. That's normal, that's because the HDMI device has no hardware way to change the volume. It's just a numeric channel, basically. If you need to change the volume from your laptop, you can keep on fiddling with the asoundrc file and create a softvol channel. More explanations here: https://bbs.archlinux.org/viewtopic.php?id=136790

In my situation, it's more a trouble than a solution, so I won't cover that. I just change volume with the TV remote, and I'm happy with that :)