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,864
  • 6
  • 65
  • 90

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
  • 449
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,864
  • 6
  • 65
  • 90
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

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
  • 31,035