Integrating the Bose QuietComfort 35 II into Linux

June 13, 2019

This article demonstrates how to connect the QuietComfort 35 II wireless headphones within Arch Linux, persisting the connection and integrating them within the i3 window manager. Although the instructions revolve around Arch, I’m sure they can be easily adapted to work with any other distribution or OS that is similar enough.

Logo of Arch Linux and photograph of the Bose QuietComfort 35 II

  1. Connection setup
    1. Pairing device
    2. Deactivate Bluetooth LE
    3. Actual pairing
    4. Auto-connect
    5. Using multimedia buttons
  2. Integration in i3
    1. Setting up hotkeys
    2. Context-sensitive controls
    3. Toggle Bluetooth
    4. Display in i3blocks
    5. Checking battery level
    6. Debugging
      1. A2DP unavailable
      2. Low volume
        1. Editing service file
        2. Repairing with high volume
  3. Final thoughts

Connection setup

Pairing device

First of all it is required to connect the device through Bluetooth. To secure this connection in a comfortable way, pairing is needed to be performed first. The following sections require you to install the required libraries:

root@host # pacman -S bluez-tools bluez-utils

Deactivate Bluetooth LE

As stated in this helpful note, the Bose QuietComfort 35 II may have problems pairing, when using Bluetooth LE on Linux. To temporarily disable it, the following line needs to be added to /etc/bluetooth/main.conf:

ControllerMode = bredr

Afterwards the Bluetooth service has to be restarted:

root@host # systemctl restart bluetooth

Actual pairing

To now pair your device you need to slide the Power/Bluetooth button to the right and hold for two seconds as instructed by the manual. The headphone is now in pairing mode.

Illustration of the headphones’ buttons: It displays the usage of the power & Bluetooth button for pairing and how the LED behaves. It was extracted from the official manual.

Following the very helpful Arch wiki page you can pair by using the command line by using the program bluetoothctl:

[bluetooth]# power on
[bluetooth]# agent on
[bluetooth]# default-agent
[bluetooth]# scan on

You should now see your headphones pop up with either the default name or the one you have given them earlier using the Bose Connect application on Android or iPhone, along with their Bluetooth device address (BD_ADDR):

[NEW] Device XX:XX:XX:XX:XX:XX Bose QuietComfort 35 II

The BD_ADDR of XX:XX:XX:XX:XX:XX should be adapted to your specific address. You then need to pair the device and connect to it, also trusting it allows for easier reconnects later:

[bluetooth]# pair XX:XX:XX:XX:XX:XX
[bluetooth]# connect XX:XX:XX:XX:XX:XX
[bluetooth]# trust XX:XX:XX:XX:XX:XX

When connected the headphones should spell your hostname. It is now possible to revert the changes to the /etc/bluetooth/main.conf configuration file and restart the Bluetooth service, to use Bluetooth LE again.

Auto-connect

For not having to reconnect manually each time using connect within bluetoothctl adding the following line to your /etc/pulse/default.pa will remedy this problem:

load-module module-switch-on-connect

Afterwards, it is needed to restart PulseAudio:

user@host $ pulseaudio -k

After killing PulseAudio, the automatically reconnected headphones had bad sound. I assume this is because of an error of their Bluetooth LE usage. If this happens, simply turning them off and on again helps. You need to either manually enable Bluetooth after a reboot, automatically enable it, or follow this article to enable Bluetooth only on demand. The latter option is recommended, because of its lower energy consumption.

Using multimedia buttons

As can be seen on the photograph below, the headphones have multiple buttons used for controlling noise cancellation modes and volume. There is also a dedicated button for playback in the center of the three buttons on the right earpiece.

Illustration of the headphones’ buttons: the multimedia button is highlighted in dark grey. It was extracted from the official manual and modified slightly.

For multimedia buttons on the headphones to be recognized as input, it is needed to enable them first by creating the file /etc/modules-load.d/uinput.conf containing:

uinput

Integration in i3

Setting up hotkeys

With a connected pair of heaphones, the information from the manual and a running instance of xev, the following table can be constructed:

Button Action Keycode
Single press Play/Pause XF86AudioPlay
Double press Next Track XF86AudioNext
Triple press Previous Track XF86AudioPrev
Double press, hold last Seek forward XF86AudioForward
Triple press, hold last Seek backward XF86AudioRewind

With this information we can now add the following bindings to our i3 configuration in ~/.config/i3/config to control mpd with the hotkeys:

bindsym XF86AudioPlay exec "mpc toggle"
bindsym XF86AudioPrev exec "mpc prev"
bindsym XF86AudioNext exec "mpc next"
bindsym XF86AudioForward exec "mpc seek +1"
bindsym XF86AudioRewind exec "mpc seek -1"

Context-sensitive controls

Taking into account which window currently has focus and decide which command to run based on that, a more sophisticated approach is required. The following shell script does exactly this and decides its action based on whether the video player mpv has focus, or some other program. For future reference it is saved in ~/.util/xf86audio:

#!/bin/bash
actions="toggle|prev|next|forw|back"

# Check for correct usage
if [[ $# -ne 1 || ! "$1" =~ ^($actions)$ ]]; then
  echo "Usage: "
  echo "${0##*/} [${actions[*]}]"
  exit 1
fi

# Get focused process' name
pid=$(xdotool getactivewindow getwindowpid)
process=$(ps -p $pid -o comm=)

case $process in
# Control mpv if focused
mpv)
  case $1 in
  toggle)
    xdotool key space
    ;;
  prev)
    xdotool key less
    ;;
  next)
    xdotool key greater
    ;;
  forw)
    xdotool click 4
    ;;
  back)
    xdotool click 5
    ;;
  esac
  notify-send "; $1"
;;
# Control mpd per default
*)
  case $1 in
  toggle)
    mpc toggle
    ;;
  prev)
    mpc prev
    ;;
  next)
    mpc next
    ;;
  forw)
    mpc seek +1
    ;;
  back)
    mpc seek -1
    ;;
  esac
;;
esac

Afterwards it is verified that the script is executable:

user@host $ chmod +x ~/.util/xf86audio

With the custom script now available we adapt the i3 config at ~/.config/i3/config:

bindsym XF86AudioPlay exec "~/.util/xf86audio toggle"
bindsym XF86AudioPrev exec "~/.util/xf86audio prev"
bindsym XF86AudioNext exec "~/.util/xf86audio next"
bindsym XF86AudioForward exec "~/.util/xf86audio forw"
bindsym XF86AudioRewind exec "~/.util/xf86audio back"

Reloading the i3 config now enables the bindings and the multimedia button does what it was intended to. The only thing that still does not work completely is seeking in mpv, either with simulating a mouse wheel scroll or arrow presses, although this surely could be remedied by using mpv’s IPC socket.

Toggle Bluetooth

A small utility script that toggles Bluetooth on and off was added to ~/.bin/toggle-bt

#!/bin/bash
# Checks if bluetooth is already on and if so switches it off, or on otherwise.

# State file
sf=/tmp/bluetooth

if [[ -f "$sf" ]]; then
    bluetoothctl power off && (notify-send "Bluetooth turned off"; rm -f "$sf") || notify-send "Bluetooth could NOT be turned off"
else
    bluetoothctl power on && (notify-send "Bluetooth turned on"; touch "$sf") || notify-send "Bluetooth could NOT be turned on"
fi

To access this script with a shortcut, the following lines were added to ~/.config/i3/config:

# Toggle Bluetooth
bindsym $mod+B exec "~/.bin/toggle-bt"

Display in i3blocks

I’m using i3blocks as a replacement for i3bar. Adding the following snippet to the config in ~/.config/i3blocks/config integrates a display of the headphones symbol followed by either OK, signaling that the headphones can be connected, or N/A in grey which shows that they’re unavailable.

[headphones-bluetooth]
command=echo -n ' '; [[ -f /tmp/bluetooth ]] && echo 'OK' || echo -e 'N/A\n\n#504945'
interval=10

The symbol above is replaced by using Font Awesome within i3 status bar. This is achieved by the following parts of my ~/.config/i3/config, which also needs the package otf-font-awesome to be installed:

bar {
  status_command i3blocks
  # ...
  font pango:Droid Sans Mono, FontAwesome 13.5
  # ...
}

Make sure to have installed the otf-font-awesome package.

Checking battery level

Unfortunately, there is nothing available to be used easily. This Stackoverflow answer gives a starting point for developing your own software. If you are going down this route and have created something usable, I’d be happy to hear. I will be updating this post if something turns up.

Debugging

A2DP unavailable

If you are within pavucontrol and set the headphones’ profile to Headset Head Unit (HSP/HFP) and disconnect the device, after a reconnect the profile High Fidelity Playback (A2DP Sink) might become unavailable. This can be fixed in the following way:

  1. Connect headphones
  2. Open pavucontrol
  3. Select ConfigurationHeadphonesProfile › Off
  4. Run bluetoothctl disconnect XX:XX:XX:XX:XX:XX
  5. Reconnect from the headphones
  6. Profile is now available again

Low volume

You may be afflicted by low volume via Bluetooth on Arch compared to other devices. I found two methods to remedy this, of which the second one worked for me.

Editing service file

Following this post on the Arch forums this might help you. First, edit /lib/systemd/system/bluetooth.service as follows:

# Before change
ExecStart=/usr/lib/bluetooth/bluetoothd
# After change
ExecStart=/usr/lib/bluetooth/bluetoothd --plugin=a2dp

Afterwards you have to restart your Bluetooth connections:

root@host # systemctl daemon-reload
root@host # systemctl restart bluetooth.service
user@host $ bluetoothctl power on
Repairing with high volume

Another method mentioned in the same thread is the following:

  1. Unpair your device with your Arch host

     bluetoothctl remove XX:XX:XX:XX:XX:XX
    
  2. Connect to the device via another OS, preferably Android
  3. Set the maximum volume there
  4. Disconnect the device from the other OS
  5. Pair and connect the device with your Arch host

     bluetoothctl pair XX:XX:XX:XX:XX:XX
     bluetoothctl connect XX:XX:XX:XX:XX:XX
    

Final thoughts

Although the setup is not yet perfect, it does a well enough job. I wish for somebody to make improvements on the communication of battery levels, so they work analogous to Android’s implementation. Also I hope that this post did help you setting up either the Bose QuietComfort 35 II headphones or a similar pair on either Arch or a similar OS. Let me know if you made improvements to this setup or something is unclear.