I received some bluetooth headphones for Christmas, so I thought I'd better set them up to work with QubesOS, a task which ought to be more complicated than the same operation on a standard, VM-less Operating System.

Indeed, Qubes isolates (assigns) the USB controllers to a VM (sys-usb or sys-net, in case you chose to re-use it for USB devices as well). So in order to assign any USB device to a Virtual Machine, the USB protocol is tunneled between the USB VM and the target VM.

In my case, the laptop's internal bluetooth device is a USB device, and thus for a VM to connect to bluetooth audio, the bluetooth USB device must first be assigned to the VM that will be streaming audio. Of course, additionally:

  • The VM must recognize the USB device (drivers)
  • A bluetooth daemon must run and talk to the bluetooth device
  • The bluetooth daemon must pair with the external headphones or speakers
  • Pulseaudio must forward the audio output to the connected external headphones or speakers

A word about security

Security-wise, bluetooth headphones make less sense that standard ones with a cable. The bluetooth protocol itself is vulnerable, the pairing process might be without authentication, and bluetooth devices can expose otherwise secure environments to external attacks.

That said, if you are OK with the risks, we'll be letting only one VM connect to the USB bluetooth device and stream to external headphones, this approach does not redirect all of the VMs' audio to the bluetooth headphones.

Here's a schematic of this approach, where VM1 will have its audio redirected to the bluetooth headphones, leaving other VMs untouched:

Setup and usage

I have installed the following packages on my debian-based template (might be similar for fedora-based templates):

sudo apt-get install qubes-usb-proxy bluez blueman pulseaudio-module-bluetooth pavucontrol

The following script can be used to enable bluetooth audio for a VM. Customize the USB bluetooth device and the bluetooth headphones' MAC address, and then save it in dom0 (for example under ~/bin/bluetooth-audio) and make it executable with chmod +x ~/bin/bluetooth-audio.

#!/bin/bash

set -o errexit
set -o nounset

# Customize the following value by the USB device ID (look in qvm-usb --list)
export usb_device="abcd:0123";
# Customize the following value with the bluetooth headphones' MAC address
export headphones="00:00:00:00:00:00"

if [ $# -ne 1 ]; then
        echo -en "Usage: bluetooth-audio VM-name\n\tStarts the bluetooth audio for the VM specified\n" >&2
        exit 1
else
    	export VM="$1";
        
	# Looks for the USB device
	qvm-usb --list | grep -F "$usb_device" >/dev/null || {
		echo "Error: device $usb_device is not connected." >&2
		exit 1;
	}

	# Checks if the device is already connected to the target VM
	if qvm-usb --list | grep -F "$usb_device" | grep -F "(attached to $VM)" >/dev/null; then
		echo "Device $usb_device is already connected to $VM, not doing anything."
		exit 1;
	else
		# Finds the sys-usb's assigned name for the USB device
		assignment="$(qvm-usb --list | grep "$usb_device" | awk '{ print $1; }')";

		echo "Connecting $assignment to $VM...";
		qvm-usb --attach "$VM" "$assignment" || {
			echo "Could not attach the device $usb_device to $VM." >&2
			exit 1;
		}
	fi;

	echo "Reconfiguring pulseaudio for bluetooth audio in $VM..."
        qvm-run "$VM" -- "killall pulseaudio"
        qvm-run "$VM" -- "pulseaudio --start"
        qvm-run "$VM" -- "blueman-assistant --device=${headphones}"
        qvm-run "$VM" -- "pavucontrol"
fi;

To use the script run it with the target VM name as the only parameter. The VM audio will be disconnected immediately, and the volume control in the VM will show only a "Dummy output" that produces no sound.

The Bluetooth assistant will as you to pair the Bluetooth headphones (if not yet paired), and then to connect them as Headphones. Once completed, the Volume control will show the headphones as outputs and the audio should start streaming to them.

Limitations

Unfortunately, once the bluetooth audio is activated for a VM, I have found it not possible to restore the standard pulseaudio forwarding to dom0 without rebooting the VM. Additionally, you might find an unstable bluetooth daemon or device, which might not allow you to switch bluetooth audio from one VM to another.