Set HDMI sound output automatically on connect/disconnect

21,337

Solution 1

I finally managed to make this work using udev. So if someone wants the same behavior here are the steps:

First we need to create a file /etc/udev/rules.d/hdmi_sound.rules with the following content:

    SUBSYSTEM=="drm", ACTION=="change", RUN+="/usr/local/bin/hdmi_sound_toggle"

this will make udev execute the script hdmi_sound_toggle every time there is a change in HDMI connection. That script must have execution permission and the contents are as follows:

#!/usr/bin/env python

import subprocess
from syslog import syslog

def output(cmd):
    return subprocess.check_output(cmd, shell=True)

# the following variables may need some modification.
user = "my_username"
card = "/sys/class/drm/card0"
dev_speaker = "output:analog-stereo+input:analog-stereo"
dev_hdmi = "output:hdmi-stereo+input:analog-stereo"
#

interfaces = output("ls {0}".format(card), ).split("\n")

vga = filter(lambda x: "VGA" in x, interfaces)[0]
hdmi = filter(lambda x: "HDMI" in x, interfaces)[0]

syslog("HDMI connection was changed!")

hdmi_connected = output("cat {0}/{1}/status".format(card,hdmi)).startswith("connected")
title = "HDMI was {0}".format("connected" if hdmi_connected else "disconnected")
message = "Audio output has changed to {opt}.".format(opt = "HDMI" if hdmi_connected else "built-in speakers")

cmd = "sudo -u " + user + " /usr/bin/pactl set-card-profile 0 " + (dev_hdmi if hdmi_connected else dev_speaker)

syslog("HDMI was connected." if hdmi_connected else "HDMI was disconnected.")
try:
    a = output(cmd)
    output("sudo -u {0} notify-send \"{1}\" \"{2}\"".format(user, title, message))
    syslog("Audio output changed.")
except Exception as ex:
    syslog("Error changing output device: " + str(ex))

Probably this can be easily made in bash, but as my main language is python I used it. Everything works except the notification: it doesn't show up, I really don't know why. If someone knows how to fix it please say something.

Note: the names of script/udev rule can be changed, but you need to use the full path.

Solution 2

For the benefit of people who stumble upon this question - Salem's solution almost worked for me in 13.04, I ended up gathering bits and pieces from all around the web, I think the deal breaker for me was the lack of the environment variable PULSE_SERVER

Here is my full solution, which is basically repeating Salem's solution with the few missing pieces. I also redid it as a shell script (despite my love for Python) because I was afraid at first that my Python script is running into import path issues:


(same as Salem's answer) Create a file /etc/udev/rules.d/hdmi_sound.rules as root with the content:

SUBSYSTEM=="drm", ACTION=="change", RUN+="/usr/local/bin/hdmi_sound_toggle"

Create a file /usr/local/bin/hdmi_sound_toggle as root with the content:

#!/bin/sh
USER_NAME=`who | grep "(:0)" | cut -f 1 -d ' '`
USER_ID=`id -u $USER_NAME`
HDMI_STATUS=`cat /sys/class/drm/card0/*HDMI*/status`

export PULSE_SERVER="unix:/run/user/"$USER_ID"/pulse/native"

if [ $HDMI_STATUS = "connected" ]
then
    sudo -u $USER_NAME pactl --server $PULSE_SERVER set-card-profile 0 output:hdmi-stereo+input:analog-stereo
else
    sudo -u $USER_NAME pactl --server $PULSE_SERVER set-card-profile 0 output:analog-stereo+input:analog-stereo
fi

Then make it executable with chmod 0755 /usr/local/bin/hdmi_sound_toggle

I tried to make this script as generic as possible, but you still might need to change some lines, such as the HDMI_STATUS file path or the profiles used. You can see a list of profiles by running pactl list cards and looking under Profiles.

Note that the script failed for me when I removed the keyword "export" when setting PULSE_SERVER, I think pactl is looking for the env variable

Don't forget to reload your udev rules: sudo udevadm control --reload-rules

Update this script is updated for 14.04. Before that, you would use USER_NAME instead of USER_ID everywhere

Solution 3

Ubuntu 16.04 - 20.04 Answer

This works for Ubuntu 16.04 - 20.04 which introduced a bug with Pulse Audio 8. Create the file hotplugtv (or hotplug-hdmi if you prefer) and copy in the following lines:

#!/bin/bash

# NAME: hotplugtv
# PATH: /home/$USER/bin
# DESC: Update pulseaudio output device when HDMI TV plugged / unplugged
# CALL: called from /etc/udev/rules.d/99-hotplugtv.rules 
#       and /home/$USER/bin/lock-screen-timer
# DATE: Created Nov 26, 2016.
# NOTE: logs output using log-file
# UPDT: Dec 14, 2016 - Sometimes /sys/class/drm/card0 & sometimes /sys/class/drm/card1
#       so use /sys/class/dmcard* instead.
#       Dec 21, 2016 - Relocated to /home/$USER/bin for calling by lock-screen-timer
#       Aug 06, 2017 - Convert from home grown log-file to universal logger command.

if [[ $(cat /sys/class/drm/card*-HDMI-A-1/status | grep -Ec "^connected") -eq 1 ]]; then
        logger -t /home/rick/bin/log-hotplugtv "HDMI TV connected"
        /bin/sleep 2;
        export PULSE_RUNTIME_PATH="/run/user/1000/pulse/";
        sudo -u rick -E pacmd set-card-profile 0 output:hdmi-stereo;
else
        logger -t /home/rick/bin/log-hotplugtv "HDMI TV disconnected"
        export PULSE_RUNTIME_PATH="/run/user/1000/pulse/";
        sudo -u rick -E pacmd set-card-profile 0 output:analog-stereo;
fi

exit 0

IMPORTANT: Change the user name "rick" to your user name.

In order to call this script from udev during hot-plug events create the file /etc/udev/rules.d/99-hotplugtv.rules containing:

ACTION=="change", SUBSYSTEM=="drm", ENV{HOTPLUG}=="1", RUN+="/home/rick/bin/hotplugtv"

Change /home/rick/bin/ to the path where you placed hotplugtv script.

Solution 4

Based on Salem's answer and daniel's answer

I took Salem's answer and daniel's answer and made some necessary changes, their solution didn't worked for me out of the box:

(similar as Salem's answer).

Create a file /etc/udev/rules.d/hdmi_sound.rules as root with the content:

SUBSYSTEM=="drm", RUN+="/usr/local/bin/hdmi_sound_toggle"

Note ACTION=="change", is missing !

Create a file /usr/local/bin/hdmi_sound_toggle as root with the content:

#!/bin/sh
USER_NAME=`who | grep "(:0)" | cut -f 1 -d ' '| sort -u`
USER_ID=`id -u $USER_NAME`
HDMI_STATUS=`cat /sys/class/drm/card0/*HDMI*/status`

export PULSE_SERVER="unix:/run/user/"$USER_ID"/pulse/native"

if [ $HDMI_STATUS = "connected" ]
then
    sudo -u $USER_NAME pactl --server $PULSE_SERVER set-card-profile 0 output:hdmi-stereo+input:analog-stereo
else
    sudo -u $USER_NAME pactl --server $PULSE_SERVER set-card-profile 0 output:analog-stereo+input:analog-stereo
fi

Note USER_NAME=who | grep "(:0)" | cut -f 1 -d ' '| sort -u I added | sort -u because otherwise it came back with elemer elemer elemer --my username 3 times.

Then make it executable with chmod 0755 /usr/local/bin/hdmi_sound_toggle

Don't forget to reload your udev rules: sudo udevadm control --reload-rules

Important this script is updated for 14.04. Before that, you would use USER_NAME instead of USER_ID everywhere

Credits: Salem and daniel.

Share:
21,337

Related videos on Youtube

geethujoseph
Author by

geethujoseph

Updated on September 18, 2022

Comments

  • geethujoseph
    geethujoseph over 1 year

    I have a dual screen setup on my laptop (using 12.04 LTS) using a HDMI connected display. Everything works fine, but everytime I connect/disconnect the cable I have to go to Sound preferences and change the sound output device manually.

    Is there any way to change the sound output device on connect/disconnection of cable, so when I connect my display the sound output is set to HDMI and when I disconnect it the sound goes back to laptop speakers?

    • ThiagoPonte
      ThiagoPonte about 11 years
      Same problem here.
    • user138216
      user138216 about 11 years
      This is the first time I've come here for any help on this... I'm in the same boat on 12.10. It's annoying. It's been this way since 10.10 in my experience. There has to be a config somewhere that can handle this. EDIT: voices.canonical.com/david.henningsson/2012/04/14/… It's set that way on purpose. That's horrible.
  • FuegoFro
    FuegoFro almost 10 years
    Note that I needed to append | uniq to the USER_NAME command. Also note that the sounds settings in Ubuntu can be deceiving. I wasn't seeing the changes in the sound settings panel, but once I was playing something the audio would switch back and forth smoothly I also noticed that after a few times of plugging and unplugging the HDMI, the script stopped being run (maybe some sort of bug in udev).
  • elemer82
    elemer82 over 9 years
    I added | sort -u to the username because it was saying extra option elemer elemer --- My username. Same as FuegoFro's solution at the end of the day.
  • Auspex
    Auspex about 9 years
    I used | grep -v pts, coz I actually got a "root" user there, too—on account of the fact that I was editing the script via kate, which had a root terminal open.
  • Georgi Koemdzhiev
    Georgi Koemdzhiev almost 7 years
    Thank you so much! It worked on Ubuntu 17.04 as well! This has been bugging me for a long while and that was a simple solution :)
  • WinEunuuchs2Unix
    WinEunuuchs2Unix almost 7 years
    You are most welcome. Thank you for the comment confirming it is needed for 17.04 as well.
  • Tomislav Nakic-Alfirevic
    Tomislav Nakic-Alfirevic over 6 years
    Keep getting "Connection failure: Connection refused pa_context_connect() failed: Connection refused" when running pactl. :\
  • Kevin Robatel
    Kevin Robatel almost 4 years
    This solution works with Ubuntu 20 too 👍
  • WinEunuuchs2Unix
    WinEunuuchs2Unix almost 4 years
    @KevinRobatel Thank you for the feedback. I've updated answer to reflect.