29

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?

Salem
  • 19,744
  • Same problem here. – ThiagoPonte Mar 04 '13 at 10:47
  • Check if my answer works with you. – Salem Mar 08 '13 at 16:19
  • 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: http://voices.canonical.com/david.henningsson/2012/04/14/audio-over-hdmi-and-displayport-in-ubuntu-12-04/ It's set that way on purpose. That's horrible. – user138216 Mar 06 '13 at 23:06

5 Answers5

14

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

Daniel
  • 431
  • 1
  • 4
  • 10
  • 1
    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).

    – FuegoFro Jul 07 '14 at 01:24
  • 1
    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. – elemer82 Oct 07 '14 at 22:46
  • 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. – Auspex Mar 08 '15 at 01:49
  • Keep getting "Connection failure: Connection refused pa_context_connect() failed: Connection refused" when running pactl. :\ – Tomislav Nakic-Alfirevic Jan 05 '18 at 23:09
9

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.

8

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.

Salem
  • 19,744
3

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.

elemer82
  • 491
  • 1
  • 6
  • 15
0

There is an easier way:

Define default-sink and default-source in /etc/pulse/default.pa, like described here: https://rastating.github.io/setting-default-audio-device-in-ubuntu-18-04/

grep default- /etc/pulse/default.pa
load-module module-default-device-restore
set-default-sink alsa_output.pci-0000_00_1f.3.analog-stereo
set-default-source alsa_input.pci-0000_00_1f.3.analog-stereo

Don't forget to restart pulseaudio before retry:

pulseaudio -k
Artur Meinild
  • 26,018