Making a Spotify “Now Playing” Widget for tmux

Jun 20, 2023·

6 min read

Featured on Hashnode
Making a Spotify “Now Playing” Widget for tmux
Play this article

This is going to be a "quick" little guide to customizing the tmux status bar to include a scrolling Spotify “Now Playing” widget. I’m going to be doing this on macOS, but you should be able to adapt it to Linux and I'll try to assist where I can. If you’re on Windows… I have no idea. Here are the tools we’re going to use:

  • tmux - Kind of obvious. I’m assuming you already have this.

  • spotify-tui - We’ll mostly just be using this for the spt playback CLI command to get the playback info.

  • spotifyd- We need this for the “song changed” hook and so we don’t need the Spotify app open.

  • bash - You already know.

  • homebrew - Skip this if you’re not on macOS. I’ll be using it to download all of these tools and also to launch the spotifyd daemon. Again, I’m going to assume you already have this.

  • Spotify - You'll probably need this, eh? I'm pretty sure you need Spotify Premium for this to work, but I'm not positive.

Prerequisites

Before we get started, go make a Spotify developer account if you don’t already have one. Then create a new app in the developer dashboard. The name and description don’t matter, but make sure you add http://localhost:8888/callback to the "Redirect URIs" section - spotify-tui requires this and we’ll revisit it later.

Installing the Tools

Now let’s get our tools. If you're on macOS, use the following command:

brew install spotify-tui
brew install spotifyd

For Linux users, you can install the tools using your package manager or by following the instructions on their respective GitHub repositories (spotify-tui and spotifyd).

Creating the Script

Now we can get a little crazy with some bash scripting. We’re going to gloss over this, but essentially it uses the spt playback subcommand from spotify-tui to get the current song in a specified format, then uses the current date in seconds to calculate the scroll position if the playback info is longer than a certain number of characters. It does some tempfile file things to store the state and saves us from calling the Spotify API too many times.

Save this script anywhere you want but let’s name it spt_status.sh.

#!/usr/bin/env bash

tmpdir="$TMPDIR/spt_status"
data_file="$tmpdir/current.txt"

if [ ! -d "$tmpdir" ]; then
    printf "\rInitializing..."

    # create the directory
    mkdir "$tmpdir"

    # initialize the data file
    echo "null" >"$data_file"
    echo "false" >>"$data_file"

    printf "\rInitialized successfully!"

    exit 0
fi

# the first line of the data file is the output of the last run
output=$(head -n 1 "$data_file" 2>/dev/null)

# the second line is whether or not we've already updated
updated=$(tail -n 1 "$data_file" 2>/dev/null)

if [ "$1" = "--update" ]; then
    printf "\rUpdating..."

    # only get the playback information if we haven't already updated
    if [ "$updated" != "true" ]; then
        # write the playback information to the data file
        echo "null" >"$data_file"

        # write that we've updated to the data file
        echo "true" >>"$data_file"
    fi

    exit 0
fi

# get the output from the data file
output=$(head -n 1 "$data_file" 2>/dev/null)

# if the output is null, get the playback information
if [ "$output" = "null" ]; then
    # format options:
    # %a: artist, %b: album, %p: playlist, %t: track, %h: show,
    # %f: flags (shuffle, repeat, like), %s: playback status, %v: volume, %d: current device

    # get the playback information
    pb=$(/usr/local/bin/spt playback --format "%s %a: %t" 2>/dev/null || echo "null")

    # write the playback information to the data file
    echo "$pb" >"$data_file"

    # write that we haven't updated to the data file
    echo "false" >>"$data_file"

    # get the output from the data file
    output=$(head -n 1 "$data_file" 2>/dev/null)
fi

# remove all newlines from the output
output=$(echo "$output" | tr -d '\n')

# max length of the output
max_length=35

# the speed at which the text should scroll (characters per second)
scroll_speed=10

if [ "$output" = "null" ]; then
    # if the output is null, print an error message and exit
    printf "\rSomething went wrong..."

    exit 1
fi

if [ ${#output} -gt $max_length ]; then
    # get the current time in seconds
    current_time=$(date +%s)

    # first character is the play/pause symbol
    play_pause_symbol="${output:0:2}"

    # the rest of the output is the track information
    output=${output:2}

    # calculate the scroll position based on the current time and scroll speed
    scroll_position=$((current_time * scroll_speed % (${#output} + max_length)))

    # create a padded version of the output with spaces
    padded_output="${output}$(printf '%*s' $((max_length)) '')${output}"

    # extract the substring to display based on the scroll position
    displayed_output="${padded_output:$scroll_position:(($max_length - 2))}"

    printf "\r%s%s " "$play_pause_symbol" "$displayed_output"

    exit 0
fi

padded_output="${output}$(printf '%*s' $((max_length + 1 - ${#output})) '')"
printf "\r%s" "$padded_output"

Make sure you run chmod +x spt_status.sh to make it executable.

Configuring the Daemon

Now, we’re almost ready to see it in action, but there are a couple of things we need to do first.

For macOS users, we’ll make a small modification to the .plist file that brew uses to launch the daemon. Open up /usr/local/Cellar/spotifyd/0.3.4/homebrew.mxcl.spotifyd.plist (modify to match your version) and look for this section around line 17:

<key>ProgramArguments</key>
<array>
    <string>/usr/local/opt/spotifyd/bin/spotifyd</string>
    <string>--no-daemon</string>
    <string>--backend</string>
    <string>portaudio</string>
</array>

Add the following to the array after <string>portaudio</string>:

<string>--on-song-change-hook</string>
<string>/path/to/spt_status.sh --update</string>

Note that this needs to be the full path to our script.

For Linux users, you can create a systemd service file to launch the daemon. Create a file named spotifyd.service in ~/.config/systemd/user/ with the following content:

[Unit]
Description=Spotifyd Daemon

[Service]
ExecStart=/usr/bin/spotifyd --no-daemon --backend alsa --on-song-change-hook "/path/to/spt_status.sh --update"
Restart=always
RestartSec=12

[Install]
WantedBy=default.target

Replace /path/to/spt_status.sh with the full path to the script you created earlier.

Launching the Daemon

From here, we can launch our daemon:

For macOS users:

brew services start spotifyd

For Linux users:

systemctl --user enable --now spotifyd.service

Configuring spotify-tui and tmux

We need to run spt one time to configure it, so do that now and follow the messages, entering your Spotify app’s client and secret tokens when prompted. Then press d and select spotifyd from the devices list. If it’s not showing up in the list of available devices, you might need to open the official app and select “spotifyd” as your playback device. You can then close the Spotify app.

select the spotifyd device in the official spotify app

We’re almost there, I promise. All that’s left is to open up your tmux config (probably at ~/.tmux.conf) and make a couple of small tweaks:

# .tmux.conf

# ...rest of file

# update the status bar every second
set -g status-interval 1

# show our widget
set -g status-right ' #(spt_status.sh)'

# ...rest of file

Wrapping Up

Whew! We did it. If you have any open tmux sessions, exit them with tmux kill-server and start up a new one. Play a song with either the official app or with spotify-tui, and you should see our new widget on the right.

If you liked this, let me know in the comments and give me a follow! Also, don’t hesitate to let me know if something isn’t working or if I overlooked anything.

I'm also on Twitter @techsavvytravvy and sometimes I stream on Twitch - come hang out!

Bye!

Did you find this article valuable?

Support trav by becoming a sponsor. Any amount is appreciated!