I recently purchased an Elgato Stream Deck. As the name suggests, a Stream Deck is geared towards people who stream content on YouTube or Twitch. I fall into neither of these camps, nor am I setting out to become a streamer.

Streamers will often setup their Stream Deck so that keys toggle actions such as starting and stopping streams, stream effects and other such actions. I wanted to see if I could use a Stream Deck to make myself more productive by having a handful of useful actions always readily available via a Stream Deck.

I actually bought the Stream Deck several months ago, and have already been using it on a daily basis for a while. I've only just had the time to finish writing the software so that it works how I originally intended it to work.

Here is a quick demo of what this looks like:

Why write your own software?

You might be wondering why I'd go to all of this effort and not just use the official software provided by Elgato? Well, Elgato only supports Mac and Windows, whereas I spend most of my time working in Ubuntu.

Elgato's download options – only Mac or Windows

There are also other open source projects out there for controlling a streamdeck-ui. With streamdeck-uii, you can setup a key to run a specified command and have a static icon. Earlier versions of the official Elgato software also worked like this.

Although there is nothing wrong with using the Stream Deck to display static icons, I felt there was an opportunity to make the control software a little smarter, so that:

  • The icon on a key could change dynamically based on state
  • Keys could be fully animated to show something like a clock or timer in real time
  • The software could be designed so that people can write their own plugins and controls for integration with other software (e.g. Slack, Home Assistant, etc.)

Building the Software

In this section I will take you through how the DevDeck software that I've built works.

Concepts and Terminology

I have an "Original" Stream Deck which is the 15-key version. You can also get a 6-key (Stream Deck Mini) and a 32-key (Stream Deck XL) variants.

My current Stream Deck configuration

Let's refer to each physical button as a key.

Within DevDeck there are two main concepts: Control and Deck.

A control is used to perform an action when a given key is pressed. A control represents the most basic element that can be displayed in a Stream Deck.

The top-left key is an example of a control. Pressing the key with toggle the microphone between being muted or un-muted and the icon will change accordingly.

A deck is indented to deal with either of the following scenarios:

  • Needing to display more controls than the device has physical keys for, or,
  • Showing a subset of relevant controls.

The Volume and Slack keys are examples of where a deck has been used:

The Volume and Slack keys link through to their own Decks.

So, if I select one of those two keys, the Stream Deck view will be re-rendered to show a set of keys (either controls or more decks) that are relevant to that specific action.

When I select the Volume key, I get the Volume deck:

After pressing the Volume key on the main deck I now see my Volume deck

When I select the Slack key, I get the Slack deck:

After pressing the Slack key on the main deck I now see my Slack deck

Programming Language

The first step was to identify what libraries existed for controlling the Stream Deck. I finally settled on the Python streamdeck library for a number of reasons:

  1. I ran through a load of publicly available example code and the library worked well and seemed robust
  2. The library was very well documented
  3. For me Python is a familiar language, so I'd be comfortable building the rest of the software in Python.

UI vs Configuration Files

As I'm writing this from a software developers perspective (and for other software developers) I decided to forgo the time consuming exercise of building a user interface, in favour of the user having to configure the software via a YAML file.

YAML should also make it easy for plugin developers to capture any necessary configuration from the user, such as 3rd party API keys.

Here is an example of a configuration file:

decks:
  - serial_number: "ABC123"
    name: 'devdeck.decks.single_page_deck_controller.SinglePageDeckController'
    settings:
      controls:
        - name: 'devdeck.controls.mic_mute_control.MicMuteControl'
          key: 0
          settings:
            microphone: Scarlett Solo USB Digital Stereo (IEC958)
        - name: 'devdeck.decks.volume_deck.VolumeDeck'
          key: 1
          settings:
            output: Built-in Audio Analogue Stereo

When DevDeck launches, it needs a Deck to display for the given Stream Deck device. In this instance I'm displaying a SinglePageDeckController which allows its keys to be either controls or decks. In this particular example, key 0 is a control for muting/un-muting a microphone and key 1 is a deck for controlling the volume.

Because SinglePageDeckController can be configured with either controls or decks, it's even possible to nest a SinglePageDeckController inside a SinglePageDeckController.

Graphics

Part of the driving factor behind DevDeck was to allow the keys on a Stream Deck to change and respond to state rather than showing a static icon. So there needs to be a solution for graphics which isn't reliant on static image files.

When implementing a control it is possible to render a static image file if that's what is needed:

with context.renderer() as r:
    r.image('/your/static/image/png').end()

However, DevDeck provides a DSL for graphical rendering which makes it possible to compose elements together to make the graphics that are required for a given key.

with context.renderer() as r:
    # Display the percetange
    r.text("{:.0f}%".format(round(self.volume, 2) * 100)) \
        .center_horizontally() \
        .end()
    # Display the icon below the percentage
    r.image('volume-up-solid.png')\
        .width(380)\
        .height(380) \
        .center_horizontally() \
        .y(132) \
        .end()
    # Colorize the whole image if the volume is currently set to this level
    if round(self.volume, 2) == round(sink.volume.value_flat, 2):
        r.colorize('red')

The above extract is from the implementation of the VolumeLevelControl that is used in the volume deck:

Each key uses the same speaker icon. The icon positioning, text rendering and colorizing (if applicable) is all handled with a few lines of code by the Rendering DSL that is provided in the DevDeck core library

Plugins

I decided early on that DevDeck should be plugin based because if other people find this useful and use it, the decks and controls that are important to me may be completely useless to someone else (and vice versa). A plugin system lets someone choose what's relevant to them.

DevDeck ships with a small handful of built-in controls and decks that are fairly generic and independent of any third-party integration. Anything out side of that has to be built as a plugin.

I've built a couple of plugins already for a few things that others may find useful. This has allowed me to test out the plugin development experience for myself, as well as providing good examples for anyone wanting to develop their own plugins.

DevDeck - Slack – Provides a Slack deck that lets a user quick change their presence, status or DND settings. As a direct result of building this plugin, emoji support has been added as a core feature of the Rendering DSL provided by the DevDeck Core library.

DevDeck - Home Assistant – Provides toggle controls for lights and switches that are managed by Home Assistant.

I've also built a custom plugin for anything that's entirely unique to my situation - such as controlling the homemade LED light strip on my desk.

Summary

Although the software has only recently reached all of the goals I set out to achieve, I have been using various prototypes of the software over the last few months whilst I've been adding features and making improvements. Over that time I've found the Stream Deck has been a great addition to my home office setup.

One of the things that I've enjoyed about using the Stream Deck is that it has made conference calls for work much easier and slicker for me. Regardless of whatever conferencing software we happen to be using at the time I can immediately mute or un-mute my microphone from the Stream Deck, as the Stream Deck mutes the microphone at the operating system level. I never have to hunt around to find the mute button which can be a challenge if you're unfamiliar with the conferencing software or are in the middle of a screen share and are getting buried by the various open windows.

And in a similar vein, I'm able to snooze Slack notification without having to open Slack, which can be very useful if I start receiving Slack messages in the middle of a screen share.

tl;dr – you don't have to be a streamer to make good use of a Stream Deck!

As with most of my projects, all the code that I've written is publicly available and open source. Feel free to make feature requests, submit your own contributions or write a plugin!

Links:

  • DevDeck
    The main control software for the Stream Deck
  • DevDeck - Core
    The core library that allows you to write plugins and your own custom controls/decks
  • DevDeck - Slack
    A plugin library containing controls and decks for Slack
  • DevDeck - Home Assistant
    A plugin library containing controls and decks for Home Assistant