An open API service indexing awesome lists of open source software.

https://github.com/twidi/streamdeckfs

SteamDeckFS is a tool, written in Python (3.9+), to configure a StreamDeck (by Elgato), using the file system
https://github.com/twidi/streamdeckfs

elgato elgato-stream-deck inotify streamdeck

Last synced: about 1 year ago
JSON representation

SteamDeckFS is a tool, written in Python (3.9+), to configure a StreamDeck (by Elgato), using the file system

Awesome Lists containing this project

README

          

[![PyPI](https://img.shields.io/pypi/v/streamdeckfs) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/streamdeckfs) ![PyPI - Wheel](https://img.shields.io/pypi/wheel/streamdeckfs) ![PyPI - Status](https://img.shields.io/pypi/status/streamdeckfs) ![PyPI - License](https://img.shields.io/pypi/l/streamdeckfs)](https://pypi.org/project/streamdeckfs/)
[![GitHub last commit](https://img.shields.io/github/last-commit/twidi/streamdeckfs)](https://github.com/twidi/streamdeckfs)
[![Isshub.io](https://img.shields.io/badge/Sponsor-isshub.io-%23cc133f)](https://isshub.io)

**Sections**: [StreamDeckFS](#streamdeckfs) • [Examples](#examples) • [Why](#why) • [Installation](#installation) • [Starting](#starting) • [Configuration](#configuration-format) ([Images](#images-layers) • [Drawings](#drawings) • [Texts](#texts) • [Events](#configuring-key-events-press-long-press-release-start-end)) • [Pages](#pages) • [References](#references) • [Variables](#variables) • [API](#api) • [Example configurations](#example-configurations) • [Web renderer and virtual decks](#web-renderer)

# StreamDeckFS

StreamDeckFS is a tool, written in Python (3.9+), to configure a StreamDeck ([by Elgato](https://www.elgato.com/fr/stream-deck)) for Linux (and maybe soon, I need help for this!) Darwin (mac) and Windows)

It's not a graphical interface, but if you can use a file system and create directories and files (no content needed, see later), you'll have all the necessary power.

It provides numerous features:

- page management, with overlays
- image composition, with layers, drawings, and texts, with, for all of them, many configuration options
- advanced key management (on press, release, long press, repeat, delay, and more)
- references (explained later, but see this as a way to have templates, or to repeat keys on pages, or have many times the same key with a few differences)
- variables with (small) logic and cascading
- api
- web access
- virtual decks (create a deck the size you want and access it via your browser)

`streamdeckfs` will look at the directory passed on the command line and read all the configuration from directories and files.

And while running, it will catch any changes to them to update in real-time the StreamDeck

# Examples

Here are a few examples of what is possible with `streamdeckfs`. Remember that `streamdeckfs` only display (and compose) images and call programs by providing a powerful configuration system to let your imagination do the rest.

## Example 1: first page

![image](https://user-images.githubusercontent.com/193474/120529198-849c7680-c3dc-11eb-9fbd-2115dc03182d.png)

- First key of first row: toggle play/pause on Spotify on press, and on long press, open a page on the StreamDeck dedicated to Spotify (see example 3). Displays the progress bar of the current playing song, or a "pause" icon on top of the Spotify logo if Spotify is not playing at the moment.
- First keys of second and third rows: increase/decrease volume on press, and can be pressed longer to increase/decrease more.
- First key of last row: toggle mute the sound. Displays the current volume, and becomes red when the sound is muted
- Second key of last row: toggle mute the microphone on press, and open an overlay to increase/decrease the microphone sensitivity (see example 2). Displays the current sensitivity, and becomes red when the microphone is muted
- Last keys of second and third rows: increase/decrease the StreamDeck brightness on press, and can be pressed longer to increase/decrease more.
- Last key of last row: open a page dedicated to controlling lights (example not shown)

## Example 2: Spotify page

![image](https://user-images.githubusercontent.com/193474/120530470-d7c2f900-c3dd-11eb-9c11-d6bd7f37fed9.png)

- First key of first row: same as in first page. Toggle play/pause on press, and go back to previous page on long press.
- Other green keys: previous/next track, restart track, go backward/forward in the track
- First keys of second, third and last row: same as in first page, to control the volume
- Fifth key of first row: close the Spotify page (go back to previous page)
- Fourth key of second row: progress and duration of the current song
- 3x3 square of keys in the top right corner: cover of currently playing album
- Last three keys of last row: some information about the current playing track

## Example 3: Microphone overlay

![image](https://user-images.githubusercontent.com/193474/120531215-ae569d00-c3de-11eb-9a2d-edb2fd2f83cd.png)

Here only the "bright" keys are active. Think of an overlay as a modal window.

- Second key of first row: close the microphone overlay (go back to previous page)
- Second keys of second and third rows: increase/decrease sensitivity on press, and can be pressed longer to increase/decrease more.
- Second key of last row: same as in the first page. Toggle the microphone on press, and close the overlay on long press.

## Example 4: OBS Page

[OBS](https://obsproject.com/) is a software to stream and is often used with a StreamDeck.

Here is a video where I show how to configure a StreamDeck for OBS with StreamDeckFS (audio in french but with english subtitles):

[![Configure a StreamDeck for OBS with StreamDeckFS (Linux) ](https://user-images.githubusercontent.com/193474/150781269-7dc62dc5-31b1-44e8-a5e5-c3708abfd30b.png)](https://www.youtube.com/watch?v=H_4cRXmRS98)

https://www.youtube.com/watch?v=H_4cRXmRS98

# Why?

As a linux user I couldn't use the official application and I quickly felt very limited by the one available on linux, [streamdeck-ui](https://timothycrosley.github.io/streamdeck-ui/), because I had many things in my head that couldn't be done. And I'm not a Linux GUI developer so I preferred to do my own tool. I, however, yse the same [underlying Python library to interface the Stream Deck](https://python-elgato-streamdeck.readthedocs.io). And [SnakeDeck](https://github.com/jpetazzo/snakedeck) was written, by an extreme coincidence, at the same time as StreamDeckFS, without either of us knowing it.

# Installation

## Prerequisites

- Linux (compatibility with Darwin(mac) and Windows in a near future, I just don't have those OS at my disposal)
- Python 3.9

## System

You need to make your system ready to communicate with HID devices.

You first need to install the HID api library

For Ubuntu/Debian:

```bash
sudo apt install -y libhidapi-libusb0
```

For Fedora:

```bash
sudo dnf install hidapi
```

Then you need to make your OS recognise the StreamDeck devices:

```bash
sudo tee /etc/udev/rules.d/70-streamdeck.rules << EOF
SUBSYSTEM=="usb", ATTRS{idVendor}=="0fd9", ATTRS{idProduct}=="0060", TAG+="uaccess"
SUBSYSTEM=="usb", ATTRS{idVendor}=="0fd9", ATTRS{idProduct}=="0063", TAG+="uaccess"
SUBSYSTEM=="usb", ATTRS{idVendor}=="0fd9", ATTRS{idProduct}=="006c", TAG+="uaccess"
SUBSYSTEM=="usb", ATTRS{idVendor}=="0fd9", ATTRS{idProduct}=="006d", TAG+="uaccess"
EOF
sudo udevadm control --reload-rules
```

And finally you need to unplug then plug back your StreamDeck devices to ensure they adopt the new permissions.

## StreamDeckFS

To install `streamdeckfs`, you can use `pip`, either in a virtual environment, or in your system python (reminder: you need python 3.9 minimum). use `pip install streamdeckfs` or `pip install --user streamdeckfs` depending on your case.

For the rest of this README, we assume that the package is correctly installed, and that a `streamdeckfs` executable should now be available.

If you don't know how to install a python package, I redirect you to your favorite search engine where you'll find a lot more complete and accurate explanation on how to do it.

Note: in addition to `streamdeckfs`, a shorter version, `sdfs`, is available.

# Starting

## Knowing your StreamDeck(s)

The first thing to do is to discover your StreamDeck devices.

For this, use the `inspect` command:

```bash
streamdeckfs inspect
```

It will output some information about the connected decks (no other program must be connected to them as only one connection to the decks is possible).

The main useful thing is the serial number, as it will be the name of the directory containing its configuration (you'll place it where you want)

## Preparing the configuration directory

You can create the directories by hand (we'll explain how later), but we provide a command to create the tree for you, `make-dirs`:

```bash
streamdeckfs make-dirs SERIAL BASE_DIRECTORY
```

`SERIAL` is the serial number you got from the `inspect` command. Note that if you have only one connected StreamDeck, you can ignore this argument as `streamdeckfs` will automatically find it for you.

`BASE_DIRECTORY` is the directory that will contain the configuration directory of this StreamDeck. So it will create (if it does not exist yet) a directory named `YOUR_SERIAL_NUMBER` in `BASE_DIRECTORY`.

Before creating (or updating) the tree, you'll be asked to confirm (unless you pass `--yes` on the command line).

No pages will be created unless you pass `--page XX`, `XX` being the number of pages to create.

Once confirmed, `streamdeckfs` will create all missing directories.

The resulting tree will look like this, for example for a deck with 3 rows of 5 keys:

```
BASE_DIRECTORY/YOUR_SERIAL_NUMBER
├── PAGE_1
│ ├── KEY_1,1
│ ├── KEY_1,2
│ ├── KEY_1,3
│ ├── KEY_1,4
│ ├── KEY_1,5
│ ├── KEY_2,1
│ ├── KEY_2,2
│ ├── KEY_2,3
│ ├── KEY_2,4
│ ├── KEY_2,5
│ ├── KEY_3,1
│ ├── KEY_3,2
│ ├── KEY_3,3
│ ├── KEY_3,4
│ ├── KEY_3,5
├── PAGE_2
...
```

In `KEY_1,3`, `1` is the row, and `3` is the column

Now you are ready to configure the keys. Here is the most straightforward configuration:

- copy an image file (or make a symbolic link) into a `KEY...` directory (in the `PAGE_1` directory, as the first displayed page is the one with the lowest number) and name it `IMAGE`. It's the image that will be displayed for the key.
- copy a script/program (or make a symbolic link) into the same `KEY...` directory and name it `ON_PRESS`. It's the script/program that will be executed when the key will be pressed.

That's it; you now know how to configure your StreamDeck in the simplest way possible. We'll see later the numerous configuration options.

PS: you can have `IMAGE` without `ON_PRESS` or `ON_PRESS` without `IMAGE`.

## Running streamdeckfs

Now that you have your configuration directory, run:

```bash
streamdeckfs run SERIAL CONFIG_DIRECTORY
```

And voila!

Note that, like for `make-dirs`, the `SERIAL` argument is optional if you have only one connected StreamDeck.

And `CONFIG_DIRECTORY` can be either the exact directory, i.e., `BASE_DIRECTORY/YOUR_SERIAL_NUMBER`, or the directory used in `make-dirs` i.e., `BASE_DIRECTORY` (`streamdeckfs` will then complete it. It's helpful if you have only one deck connected and don't want to have to remember the serial number, so in this case, the command can be only `streamdeckfs run BASE_DIRECTORY`)

Now that you have your StreamDeck running, try adding an image for another key (on the first page). You'll see that the deck automatically updates. And maybe you're starting to see the infinite possibilities.

If you have many StreamDecks, each with its config directory (for example if 2 decks `BASE_DIRECTORY/YOUR_SERIAL_NUMBER_1` and `BASE_DIRECTORY/YOUR_SERIAL_NUMBER_2`, you have many options:

- run without passing any serials and passing `BASE_DIRECTORY`: it will run for all decks that have a config directory in `BASE_DIRECTORY`
- run with passing one or many serials and passing `BASE_DIRECTORY`: it will run for all wanted decks that have a config directory in `BASE_DIRECTORY`
- run without passing any serials and passing the serial config directory `BASE_DIRECTORY/YOUR_SERIAL_NUMBER_1`: it will run for this deck only

You can also run many instances of the program, one for each deck (so you can easily stop one). There is two ways to do this:
- for each deck, run without passing any serials and passing the serial config directory `BASE_DIRECTORY/YOUR_SERIAL_NUMBER_1`
- for each deck, run with passing its serial and passing the global or the serial config directory, i.e., `BASE_DIRECTORY` or `BASE_DIRECTORY/YOUR_SERIAL_NUMBER_1`

Note that `streamdeckfs` can be launched before the StreamDecks being plugged or their configuration directory being ready. It will patiently wait for everything to be ok for a StreamDeck to be rendered. And if theses directory become unavailable later, or if the StreamDecks are unplugged, it will stop rendering them and wait for them to be ready again.

Images

There are two things you can configure on your StreamDeck: "appearance", i.e., what is displayed on the keys, and "events" that happen when you "act" with them.

But first, let's talk a bit about the way configuration works.

## Configuration format

Everything is done in the name of the file. Say you want to configure the `IMAGE` with the option `foo` taking the value `bar`, then you have to rename the file from `IMAGE` to `IMAGE;foo=bar`.

And if you want to add a name (explained later) of `my-key`, the file's name will be `IMAGE;foo=bar;name=my-key`.

So the three things to know are:

- if the image (or event, or text) has some configuration options, the "normal" name (like `IMAGE`, `ON_PRESS`...) is separated from the configuration with a semi-colon: `IMAGE;here=is;the=config`
- key/value configuration pairs are grouped with a `=` between the key and the value: `key=value`
- different configuration pairs are separated by a semi-colon: `key1=value1;key2=value2`

Note that the order of the configuration options is not essential.

About the limitations, there are a few things to consider:

- some characters are not allowed in a file name (only `/` on Linux)
- the length of a file name is limited (generally 256 characters on Linux)

## Common configuration options

Two configuration options are common to every directory or file:

### `name`

It's the name of the page, key, image, event, or text. It can be whatever you want and can be used to "reference" other pages, keys, images, events, and texts. For example, if you have a page dedicated to displaying keys for Spotify, you can have the page directory named `PAGE_50;name=spotify`, so when you'll want to have a key to go to this page, you'll be able to use the page number or the name. Using the name allows reordering the pages as you want. We'll see later how to make keys display a different page when pressed.

### `disabled`

To make `streamdeckfs` ignore a page, image, text, event..., you can use the `disabled` option, which is a flag, meaning that simply adding `;disabled` is enough to have it disabled, but you can also set a boolean value `;disabled=true` or `;disabled=false`.

### `enabled`

It's the exact opposite of `disabled`. Used as a flag `enabled` only or via `enabled=true` (equivalent to `disabled=false`) or `enabled=false` (equivalent to `disabled=true`)

Useful when using [variables](#variables). Only one of `enabled` or `disabled` can be used.

## Configuring appearance (images, drawings, texts)

First, let's talk about the image formats that are supported. You can use every [image format supported by the `pillow` python library](https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html). For example, jpeg, png, gif (we don't handle animated gif yet). The transparency level ("alpha layer") is respected.

The image will be resized to fit in the key: a 72 or 96 pixel square, depending on which StreamDeck you have (the `inspect` command gives us this information). The ratio of the original image is kept, with transparent bands of the top/bottom or left/right depending on the ratio.

### Images layers

The simplest thing to do to display something on a key is to drop (or link) an image on the corresponding `KEY...` directory and name it `IMAGE`.

But you can make an advanced composition with layers, for example a background, an icon, and an overlay.

#### Option "layer"

To make an image a layer, set the `layer` configuration option to a number: `IMAGE;layer=1`, `IMAGE;layer=2`... Layers will be drawn on top of each other in numerical order. Also, note that when there is at least one image with the layer number defined, the default image (i.e., `IMAGE` without the `layer` configuration) is ignored.

If two images have the same layer number, the most recent one will be displayed, the other(s) will be ignored.

So ok, we can have many layers that will be rendered on top of each other. But in this case, we'll only be able to see the last layer, right? Not exactly. There are many configuration options that you can apply on each layer, as you can see below.

The list below presents the configuration options in the same order as they are applied on a layer (the order in the file name is not essential).

Note that the following options are also valid when you have a single image without a defined layer.

#### Option "crop"

The `crop` option allows displaying only a specific part of the image. Coordinates can be defined in pixels from the source image or in percents.

It must be defined like this: `crop=LEFT,TOP,RIGHT,BOTTOM`, representing the two points (x,y) of the image to be used to "cut" a rectangle, based on the (0,0) coordinate being in the top left corner of the image: the top-left corner and the bottom-right corner. Values are:

- `LEFT`: the distance, in pixels or percents (of the original width) from the left
- `TOP`: the distance, in pixels or percents (of the original height) from the top
- `RIGHT`: the distance, in pixels or percents (of the original width) from the left
- `BOTTOM`: the distance, in pixels or percents (of the original height) from the top

Examples:
In all image examples we'll use the Elgato logo (made white on transparent) that looks like this by default (and examples are photos from a real device):

![image](https://user-images.githubusercontent.com/193474/120549465-ee744a80-c3f3-11eb-9b44-d9ebdfdfaaee.png)

- `IMAGE;crop=10,10,90,90` applied on a 100x100 pixels image, will remove a border of 10 pixels on all sides:

![image](https://user-images.githubusercontent.com/193474/120549855-66db0b80-c3f4-11eb-853e-9291655ecd92.png)

- `IMAGE;crop=0,0,33.33%,33.33%` will only keep the top left third of the image (It can be used to display an image on a 9-keys square, each key containing the same image but with a different crop configuration):

![image](https://user-images.githubusercontent.com/193474/120550039-a570c600-c3f4-11eb-8ff9-b32438a64e36.png)

Once cropped, the part that is kept will be the source image for the other configuration options.

In addition to `crop`, it's also possible to override the individual parts. For example `crop=10,10 10,10;crop.1=20` will be equal to `crop=10,20,10,10`. For this to work, `crop` must have been defined. And indexes (the `1` in `crop.1` starts at 0: 4 parts (from `0` to `3`) can be overridden. It may not seems useful for now, but we'll see later that it can be powerful.

To be more readable, instead of `crop.0`, `crop.1`, `crop.2` and `crop.3` you can use `crop.left`, `crop.top`, `crop.right` and `crop.bottom`.

#### Option "rotate"

The `rotate` option takes the source image (or the one already updated by previous options) and rotates it the number given of degrees clockwise.

It must be defined like this: `rotate=ANGLE`, with:

- `ANGLE`: the angle, in degrees, from 0 to 360, or in percents (100%=360 degrees) (it can be negative or more than 360/100%, in which case it will work as you expect)

Examples:

- `IMAGE;rotate=180` will make the image upside down:

![image](https://user-images.githubusercontent.com/193474/120550234-db15af00-c3f4-11eb-9825-9a8a0f288547.png)

- `IMAGE;rotate=50%` same but expressed in percents

Once rotated, the updated image will be the source image for the other configuration options.

#### Option "margin"

The `margin` option allows placing the source image (or the one already updated by the previous options) in a key area by defining margin on all sides. The way it works is to remove the margins from the original key size and fit the image in the remaining area, keeping the aspect ratio.

Values can be defined in pixels from the key or in percents (Using percents allow to have the same rendering whatever the key size is)

It must be defined like this: `margin=TOP,RIGHT,BOTTOM,LEFT`, with:

- `TOP`: the height of the top margin, in pixels or percents (of the key height)
- `RIGHT`: the width of the right margin, in pixels or percents (of the key width)
- `BOTTOM`: the height of the bottom margin, in pixels or percents (of the key height)
- `LEFT`: the width of the left margin, in pixels or percents (of the key width)

Examples:

- `IMAGE;margin=10,10,10,10` makes a margin of 10 pixels on all size:

![image](https://user-images.githubusercontent.com/193474/120550395-19ab6980-c3f5-11eb-8ee9-805ae0ea65f7.png)

- `IMAGE;margin=0,33.33%,0,33.33%` will fit the image if the middle third of the key (margin of 33.33% on left and right, so 33.33% are available in the middle):

![image](https://user-images.githubusercontent.com/193474/120550547-4e1f2580-c3f5-11eb-8aaa-497d037d7f87.png)

In addition to `margin`, it's also possible to override the individual parts. For example `margin=10,10 10,10;margin.1=20` will be equal to `margin=10,20,10,10`. For this to work, `margin` must have been defined. And indexes (the `1` in `margin.1` starts at 0: 4 parts (from `0` to `3`) can be overridden. It may not seems useful for now, but we'll see later that it can be powerful.

To be more readable, instead of `margin.0`, `margin.1`, `margin.2` and `margin.3` you can use `margin.top`, `margin.right`, `margin.bottom` and `margin.left`.

#### Option "colorize"

The `colorize` option takes a color and will replace every color of the image with this one, keeping the alpha layer (opacity) intact. Useful, for example, if you want to display an icon, given in black with transparency, in a specific color.

It must be defined like this: `colorize=COLOR` with:

- `COLOR`: the color to use. It can be a common HTML color name (red, green, blue, etc.) or a hexadecimal RGB value, like `#ff0000` (here, pure red). Color names or hexadecimal values are case-insensitive.

Examples:

- `IMAGE;colorize=red` colorizes the image in red:

![image](https://user-images.githubusercontent.com/193474/120551192-077dfb00-c3f6-11eb-95fb-c5de838e362c.png)

- `IMAGE;colorize=#00FFFF` colorizes the image in cyan:

![image](https://user-images.githubusercontent.com/193474/120551454-562b9500-c3f6-11eb-99dc-b7846b713ced.png)

#### Option "opacity"

The `opacity` option allows defining how transparent the image will be, i.e., how the layers below will be visible.

It must be defined like this: `opacity=NUMBER` with:

- `NUMBER`: the level of opacity, from 0 to 100, 0 being the less opaque (fully transparent, the layer won't be visible at all), and 100 being the most opaque (not transparent at all, except the parts already transparent)

For parts of the image already partially transparent, they will become more transparent with the opacity decreasing.

Examples:

- `IMAGE;opacity=100` does not change the transparency at all
- `IMAGE;opacity=50` makes the image 50% transparent (here with a red background):

![image](https://user-images.githubusercontent.com/193474/120552009-fc779a80-c3f6-11eb-85b3-1c55fd45adc2.png)

#### Option "file"

If you don't want do copy or link the image to the `IMAGE...` file in the `KEY...` directory, you can use the `file` configuration option to define the path where to find the image to render.

You have to respect the known limitations of the file name (max length and no slash `/`) and avoid semi-colons `;` as it is interpreted as the end of the path (because it's the configuration options separator) . For the slash, you can replace it with any suite of characters defined in the `slash` option (default to `\\`). For the semi-colon, you can replace it with any suite of caracters defined in the `semicolon` option (default to `^`).

Note that the `IMAGE...` file can be empty when this option is set, as its content will be ignored. And you cannot set both `file` and `draw` options (see `Drawings` for this last one).

It must be defined like this: `file=PATH` with:

- `PATH`: the path of the image file to render. It can starts with `~`.

Examples:

- `IMAGE;file=|home|myself|Images|logo.png;slash=|` will use the image at `/home/myself/Images/logo.png`. Note that the `/` are replaced by `|` as defined by the `slash` configuration option.
- `IMAGE;file=\\home\\myself\\Images\\logo.png` same but using the default value of the `slash` configuration option when not passed.

The key will be updated when the referenced image changes.

If you don't want to deal with special characters, you can use `file=__inside__` and write the path of the image in the first line of the file.

#### Option "slash"

When using the `file` option, it's impossible to use slashes in the filename, so you can replace it with any character or suite of characters you defined with the `slash` option. If not defined, the default value of `\\` is used.

It must be defined like this: `slash=REPLACEMENT` with:

- `REPLACEMENT`: a character or suite of characters to use as a replacement in the `command` configuration option for the `/` character

Examples:

- `IMAGE;file=|home|myself|Images|logo.png;slash=|` will use the image at `/home/myself/Images/logo.png`. Note that the `/` are replaced by `|` as defined by the `slash` configuration option.
- `IMAGE;file=XXXhomeXXXmyselfXXXImagesXXXlogo.png;slash=XXX` same but using `XXX` instead of `|`
- `IMAGE;file=\\home\\myself\\Images\\logo.png` same but using the default value of the `slash` configuration option when not passed.

#### Option "semicolon"

When using the `file` option, it's impossible to use semi-colons in the filename, so you can replace it with any character or suite of characters you defined with the `semicolon` option. If not defined, the default value of `^` is used/

It must be defined like this: `semicolon=REPLACEMENT` with:

- `REPLACEMENT`: a character or suite of characters to use as a replacement in the `command` configuration option for the `;` character

Examples:

- `IMAGE;file=\\home\\myself\\Images\\logo^version2.png` will use the image at `/home/myself/Images/logo;version2.png` using default value for `/` (`\\`) and for `;` (`^`)
- `IMAGE;file=|home|myself|Images|logo,version2.png;slash=|;semicolon=,` same but using `|` for slashes and `,` for semicolons

### Drawings

In addition to image files, layers can be simple drawings: points, lines, rectangles/polygons, circles/ellipses, arch, chords, and pie slices.

Drawings can be used to add a progress bar (horizontal, circular), a separation line between a text title and a text content, etc.

A drawing layer is named like an image (i.e., with the name starting with `IMAGE;`) but can be an empty file (or any file you want, the content will be ignored) and is defined with `draw=KIND` and other configuration options depending on the drawing kind.

As said above, drawings are image layers, defined like it, so all configuration options described above for images also apply for drawing. The drawing is like the source of the image, so all configuration options for images seen above can be used.

Common things to know about drawings configuration options:

- coordinates are based on the size of the key, based on the (0,0) coordinate being in the top left corner of the image
- coordinates can be given in pixels or percents. If percents they will be from the width or the height of the key size
- coordinates can be negative
- coordinates are given as a suite of `X,Y` pairs, separated by commas, like `X1,Y1,X2,Y2` for two points
- default line ("outline") color if not defined is `white`
- width if the width of the line, in pixels, and if it is more than 1, it will spread equally around the "center" of the line
- default line width, if not defined, is 1
- default "fill" is not set, i.e., by default, when not defining any color/width, you'll have a thin white line
- colors ("outline" and "fill") can be set as a name, a simple hexadecimal value (#RRGGBB), or a hexadecimal value with opacity (#RRGGBBAA)
- angles can be expressed in degrees (from 0 to 360, but can be negative or more than 360, 0 is at midnight) or in percents (100%=360 degrees)

For `coords` and `angles` (both described below), in addition to the full configuration option, it's also possible to override the individual parts. For example `angles=0,90;angles.1=180` will be equal to `angles=0,180`. For this to work, the full configuration option must have been defined. And indexes (the `1` in `angles.1` starts at 0: for angles, 2 parts ( `0` and `1`) can be overridden, and for coords, it must be an index present in the original coords. It may not seems useful for now, but we'll see later that it can be powerful.

#### Kind "points"

Many points (pixels) can be drawn at once with the same color.

It must be defined like this: `draw=points;coords=COORDS;outline=COLOR`, with:

- `COORDS`: a suite of `X,Y` pairs, each one representing a point to draw
- `COLOR`: the color of the points. Optional

It is impossible to define the "size" of a point: it's always a single pixel.

Examples:

- `IMAGE;draw=points;coords=50%,50%` will draw a single white point at the center of the key:

![image](https://user-images.githubusercontent.com/193474/120552300-5aa47d80-c3f7-11eb-9e92-bb2b70e87dfd.png)

- `IMAGE;draw=points;coords=10,10,20,20,30,30;outline=yellow` will draw 3 yellow points, at coordinates (10,10), (20,20), and (30,30):

![image](https://user-images.githubusercontent.com/193474/120552547-ace59e80-c3f7-11eb-9621-3ae801059242.png)

#### Kind "line"

The line is drawn between the different given points (at least two).

It must be defined like this: `draw=line;coords=COORDS;outline=COLOR;width=WIDTH`, with:

- `COORDS`: a suite of `X,Y` pairs, each one representing the start of a line, and, if not the first, the end of the previous one
- `COLOR`: the color of the line. Optional
- `WIDTH`: the width of the line. Optional

Examples:

- `IMAGE;draw=line;coords=0,0,100%,100%` will draw a white diagonal from the top left corner to the bottom right corner:

![image](https://user-images.githubusercontent.com/193474/120552668-d1417b00-c3f7-11eb-8dbc-a5a71f30a1da.png)

- `IMAGE;draw=line;coords=10,10,20,10,10,20,20,20;outline=red;width=2` will draw a red "Z" with a thickness of 2 pixels near the top left corner:

![image](https://user-images.githubusercontent.com/193474/120553152-75c3bd00-c3f8-11eb-88b8-de755b339156.png)

#### Kind "rectangle"

The rectangle is represented by two points: the top-left and bottom-right corners.

It must be defined like this: `draw=rectangle;coords=X1,Y1,X2,Y2;outline=LINE_COLOR;width=WIDTH;fill=FILL_COLOR;radius=RADIUS`, with:

- `X1,Y1`: the coordinates of the top left corner
- `X2,Y2`: the coordinates of the bottom right corner
- `LINE_COLOR`: the color of the line. Optional
- `WIDTH`: the width of the line. Optional
- `FILL_COLOR`: the color to fill the inside of the rectangle. Optional
- `RADIUS`: radius, in pixels of the circle used to have a rounded rectangle. Optional

Examples:

- `IMAGE;draw=rectangle;coords=0,0,100%,100%;fill=red;width=0` will fill the whole key area with red (note that you can use `draw=fill` as described below to achieve the same effect):

![image](https://user-images.githubusercontent.com/193474/120553282-98ee6c80-c3f8-11eb-8e4e-54e526bfc677.png)

- `IMAGE;draw=rectangle;coords=10,10,40,40;outline=blue;width=5;fill=#0000FF80` will draw a thick blue rectangle in the top left area, with the inner filled in semi (via the ending `80`) transparent blue:

![image](https://user-images.githubusercontent.com/193474/120553434-c6d3b100-c3f8-11eb-9961-835c192e5405.png)

- `IMAGE;draw=rectangle;coords=10%,10%,90%,90%;radius=20` will draw a rounded rectangle with rounded angles:

![image](https://user-images.githubusercontent.com/193474/120553743-2762ee00-c3f9-11eb-942e-31d25f4397b2.png)

#### Kind "fill"

The `fill` kind is a shortcut to a rectangle covering the full key without outline.

So `IMAGE;draw=fill;fill=red` is the same as `IMAGE;draw=rectangle;coords=0,0,100%,100%;width=0;fill=red`. Only the `fill` configuration option needs to be specified (see kind `rectangle` above).

#### Kind "polygon"

The polygon is like the `line`, plus a line between the last and first points, and can be filled with a color. There is one drawback: due to a limitation of the used library, it's not possible to define the width of the line (for this, it's possible to add a `draw=line` layer with the same `coords` but with adding the first `X,Y` at the end to close the line)

It must be defined like this: `draw=polygon;coords=COORDS;outline=LINE_COLOR;fill=FILL_COLOR`, with:

- `COORDS`: a suite of `X,Y` pairs, each one "corner" of the polygon
- `LINE_COLOR`: the color of the line. Optional
- `FILL_COLOR`: the color to fill the inside of the polygon. Optional

Example:

- `IMAGE;draw=polygon;coords=50%,0,100%,50%,50%,100%,0,50%;color=yellow` will draw a yellow diamond touching the middle of the four sides:

![image](https://user-images.githubusercontent.com/193474/120553905-57aa8c80-c3f9-11eb-8050-a43be149e0fd.png)

#### Kind "ellipse"

The ellipse is defined by its bounding box, represented by two points: the top-left and bottom-right corners of the box. If the width and height of the bounding box are equal, we have a circle.

It must be defined like this: `draw=ellipse;coords=X1,Y1,X2,Y2;outline=LINE_COLOR;width=WIDTH;fill=FILL_COLOR`, with:

- `X1,Y1`: the coordinates of the top left corner of the bounding box
- `X2,Y2`: the coordinates of the bottom right corner of the bounding box
- `LINE_COLOR`: the color of the line. Optional
- `WIDTH`: the width of the line. Optional
- `FILL_COLOR`: the color to fill the inside of the ellipse. Optional

Examples:

- `IMAGE;draw=ellipse;coords=0,0,100%,100%` will draw a circle touching the middle of the four sides:

![image](https://user-images.githubusercontent.com/193474/120554023-888ac180-c3f9-11eb-9954-393c73c2197d.png)

- `IMAGE;draw=ellipse;coords=10,10,60,40;outline=blue;width=5;fill=#0000FF80` will draw a thick flat blue ellipse in the top area, with the inner filled in semi (via the ending `80`) transparent blue:

![image](https://user-images.githubusercontent.com/193474/120554134-aa844400-c3f9-11eb-817b-ac1084fc9ce1.png)

#### Kind "arc"

An arc is a portion of an ellipse outline. It is defined by the bounding box of the ellipse (see `ellipse` above) and two angles (start and end)

It must be defined like this: `draw=arch;coords=X1,Y1,X2,Y2;angles=START,END;outline=LINE_COLOR;width=WIDTH`, with:

- `X1,Y1`: the coordinates of the top left corner of the bounding box
- `X2,Y2`: the coordinates of the bottom right corner of the bounding box
- `START`: the start angle of the arc.
- `END`: the end angle of the arc. The arc is drawn clockwise.
- `LINE_COLOR`: the color of the line. Optional
- `WIDTH`: the width of the line. Optional

Examples:

- `IMAGE;draw=arc;coords=10%,10%,90%,90%;angles=0,270;width=5;outline=red` will draw a thick red arc representing a circular progress bar of 75% starting at midnight and ending a 9 o'clock:

![image](https://user-images.githubusercontent.com/193474/120554343-e919fe80-c3f9-11eb-9034-1d76c53ea209.png)

- `IMAGE;draw=arc;coords=10%,10%,90%,90%;angles=0,75%;width=5;outline=red` same but end angle expressed as percents

#### Kind "chord"

A chord is like an arc, plus a line between its two ends, and can be filled with a color.

It must be defined like this: `draw=chord;coords=X1,Y1,X2,Y2;angles=START,END;outline=LINE_COLOR;width=WIDTH;fill=FILL_COLOR`, with:

- `X1,Y1`: the coordinates of the top left corner of the bounding box
- `X2,Y2`: the coordinates of the bottom right corner of the bounding box
- `START`: the start angle of the arc.
- `END`: the end angle of the arc. The arc is drawn clockwise.
- `LINE_COLOR`: the color of the line. Optional
- `WIDTH`: the width of the line. Optional
- `FILL_COLOR`: the color to fill the inside of the chord. Optional

Examples:

- `IMAGE;draw=chord;coords=20%,20%,80%,80%;angles=270,90` will draw a closed semi circle on the top half:

![image](https://user-images.githubusercontent.com/193474/120554476-149ce900-c3fa-11eb-992d-64fd5378543e.png)

- `IMAGE;draw=chord;coords=20%,20%,80%,80%;angles=-25%,25%` same but angles expressed as percents

#### Kind "pieslice"

A chord is like an arc, plus a line between each end and the center of the bounding box. It can be filled with a color.

It must be defined like this: `draw=pieslice;coords=X1,Y1,X2,Y2;angles=START,END;outline=LINE_COLOR;width=WIDTH;fill=FILL_COLOR`, with:

- `X1,Y1`: the coordinates of the top left corner of the bounding box
- `X2,Y2`: the coordinates of the bottom right corner of the bounding box
- `START`: the start angle of the arc.
- `END`: the end angle of the arc. The arc is drawn clockwise.
- `LINE_COLOR`: the color of the line. Optional
- `WIDTH`: the width of the line. Optional
- `FILL_COLOR`: the color to fill the inside of the pie slice. Optional

Examples:

- `IMAGE;draw=pieslice;coords=20%,20%,80%,80%;angles=0,90` will draw a quarter circle on the top right quarter "pointing" towards the middle:

![image](https://user-images.githubusercontent.com/193474/120554614-3d24e300-c3fa-11eb-91fc-f4a9cd0a5c1e.png)

- `IMAGE;draw=pieslice;coords=-50%,-50%,50%,50%;angles=90,180;width=4` will draw a quarter circle on the top left quarter "pointing" towards the top left corner:

![image](https://user-images.githubusercontent.com/193474/120554693-575ec100-c3fa-11eb-865c-272a41d50693.png)

### Texts

Images are great to have keys with meaning, but having a way to display text can be helpful, to add a visible title on the key or present some information.

Texts are not defined like image layers and drawings but on their own. The most basic text is `TEXT;text=foo`. It will write "foo" above all image layers (if any) in white color in the top left corner. Or it can simlpy be `TEXT` without the `text` configuration option, in which case the text will be read from the file content

Like layers, you can either have one text line or many, using the `line=XX` configuration option (same as the `layer=XX` configuration option of images/drawings).

"Lines" of texts can have different configuration options and will be written on top of each other in their numerical order. Note that all `IMAGE` layers will be drawn BEFORE the text lines.

All text will be written in the same font ([Roboto](https://fonts.google.com/specimen/Roboto)), which has many "styles" (combination of weight and italic). Emojis are rendered via the [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) font (you can use emojis directly, like `❤️`, or [emojis codes](https://www.webfx.com/tools/emoji-cheat-sheet/), like `:heart:`). Both fonts are provided with the `streamdeckfs` package.

Text is not wrapped by default and will be truncated to fit on a line. See the `wrap` and `scroll` options below to change this behavior.

All consecutive white space will be merged on a single space. And `^` and `\\` will be replaced by `;` and `/` (can be changed via the `slash` and `semicolon` configuration options).

The configuration options for the texts are:

#### Option "line"

It's the number of the line to write. It is only needed if many lines are defined. And like layers, if many lines are present, if one has no `line=` configuration option, it will be ignored.

#### Option "text"

If defined, it's the text to write instead of reading if from the file content. New lines are replaced by spaces (except when wrapping is enabled.

When setting the text using `text` in the file name, don't forget to consider the rules regarding the file names limitation: no `/` and length not longer than the max authorized on the operating system (256 characters on Linux). Plus, a last rule: the text cannot contain semi-colon as it is interpreted as the end of the text (because it's the configuration options separator)

If you need to bypass these rules, see the `file` configuration option below.

The text must be defined like this: `text=foo with space | or whatever (really)`.

#### Option "size"

The size of the font.

It must be defined like this: `size=SIZE` with:

- `SIZE`: the size in pixels of the text to write, or in percents of the key height (will be converted to pixels). The default is `20%`.

Examples:

- `TEXT;text=foobar;size=10` will draw a very small text:

![image](https://user-images.githubusercontent.com/193474/120554984-b45a7700-c3fa-11eb-9817-99bb917946d2.png)

- `TEXT;text=foobar;size=40` will draw a very big text:

![image](https://user-images.githubusercontent.com/193474/120555088-d653f980-c3fa-11eb-889c-e9f0cea62b76.png)

#### Option "fit"

This configuration option can replace the `size` one when you want the size to be calculated to have the text occupy the maximum available space.

It is compatible with `wrap`, ie if wrapping is not activated, it will find a font size to make the whole text fit in one line.

The default values for `valign` and `align` are different if `fit` is activated: `middle` (instead of `top`) for `valign`, and `center` (instead of `left`) for `align`.

This configuration option is especially useful to display a single emoji at the maximum size possible.

It must be defined like this:

- `fit` or `fit=true` to fit the text
- `fit=false` to not fit the text (it's the same as not defining the fit option at all)

Examples:

- `TEXT;text=😂;fit` or `TEXT;text=😂;fit=true` will make the emoji as big as possible:

![image](https://user-images.githubusercontent.com/193474/121516559-a4d8c080-c9ee-11eb-8829-991f691a864f.png)

- `TEXT;text=😂;fit=false` or `TEXT;text=😂` will use the default size

#### Option "weight"

It's the font-weight to use.

It must be defined like this: `weight=WEIGHT` with:

- `WEIGHT`: one of the available weights: `thin`, `light`, `regular`, `medium`, `bold`, `black` (here in the thinner to the largest order)

Default is `medium`.

Examples:

- `TEXT;text=foobar;weight=thin` will draw a very thin text:

![image](https://user-images.githubusercontent.com/193474/120555213-07342e80-c3fb-11eb-94fd-79b5dde01546.png)

- `TEXT;text=foobar;weight=black` will draw a very thick text:

![image](https://user-images.githubusercontent.com/193474/120555401-4d898d80-c3fb-11eb-8c24-6ad4ffbba4d2.png)

#### Option "italic"

A flag to tell if the text must be written in italic.

It must be defined like this:

- `italic` or `italic=true` to use italic
- `italic=false` to not use italic (it's the same as not defining the italic option at all)

Examples:

- `TEXT;text=foobar;italic` or `TEXT;text=foobar;italic=true` will draw a text in italic:

![image](https://user-images.githubusercontent.com/193474/120555514-790c7800-c3fb-11eb-87c6-b545b5d3a1c5.png)

- `TEXT;text=foobar;italic=false` or `TEXT;text=foobar` will draw a regular text (not in italic)

#### Option "align"

Horizontal alignment of the text.

It must be defined like this: `align=ALIGN` with:

- `ALIGN`: the horizontal alignment to use between `left`, `center`, and `right`. Default if not set is `left`.

Example:

- `TEXT;text=foobar;align=center` will center the text horizontally in the key:

![image](https://user-images.githubusercontent.com/193474/120555601-96414680-c3fb-11eb-8149-783beb9dd05a.png)

#### Option "valign"

Vertical alignment of the text.

It must be defined like this: `valign=ALIGN` with:

- `ALIGN`: the horizontal alignment to use between `top`, `middle`, and `bottom`. Default if not set is `top`.

Example:

- `TEXT;text=foobar;valign=middle` will center the text vertically in the key:

![image](https://user-images.githubusercontent.com/193474/120555672-b4a74200-c3fb-11eb-8023-b6a9435fa529.png)

#### Option "color"

The color of the text to write.

It must be defined like this: `color=COLOR` with:

- `COLOR`: the color to use. It can be a common HTML color name (red, green, blue, etc...) or a hexadecimal RGB value, like `#ff0000` (here, pure red). Color names or hexadecimal values are case-insensitive.

Example:

- `TEXT;text=foobar;color=red` will write text in red:

![image](https://user-images.githubusercontent.com/193474/120555770-d7d1f180-c3fb-11eb-93b0-850023ec0b2e.png)

#### Option "opacity"

The `opacity` option allows defining how transparent the text will be, i.e., how the layers below will be visible.

It must be defined like this: `opacity=NUMBER` with:

- `NUMBER`: the level of opacity, from 0 to 100, 0 being the less opaque (fully transparent, the text won't be visible at all), and 100 being the most opaque (not transparent at all)

Examples:

- `TEXT;text=foobar;opacity=100` does not change the transparency at all
- `TEXT;text=foobar;opacity=50` makes the text 50% transparent (here with a red background):

![image](https://user-images.githubusercontent.com/193474/120556200-765e5280-c3fc-11eb-8711-cfa71a8e44ce.png)

#### Option "wrap"

It's a flag defining if the text must be wrapped if it does not fit in one line. If not set (the default), the text will be truncated to stay on one line.

In wrap mode, the text will be split on words, and if a word is too long to fit in one line, it will be divided into at least two parts.

It must be defined like this:

- `wrap` or `wrap=true` to wrap the text on my lines
- `wrap=false` to not wrap the text (it's the same as not defining the wrap option at all)

Examples:

- `TEXT;text=foobar baz qux;wrap` or `TEXT;text=foobar baz qux;wrap=true` will wrap the text if too long:

![image](https://user-images.githubusercontent.com/193474/120556279-92fa8a80-c3fc-11eb-88d6-1b142486e13a.png)

- `TEXT;text=foobar baz qux;wrap=false` or `TEXT;text=foobar baz qux` will not wrap the text

#### Option "margin"

The `margin` option allows placing the text in a key area by defining margin on all sides. The way it works is to remove the margins from the original key size and fit the text in the remaining area.

The `align` and `valign` options will then be applied in the area limited by the margins (same for the `wrap` option: text will be displayed on many lines but only in this area).

Values can be defined in pixels from the key or in percents (Using percents allow to have the same rendering whatever the key size is)

It must be defined like this: `margin=TOP,RIGHT,BOTTOM,LEFT`, with:

- `TOP`: the height of the top margin, in pixels or percents (of the key height)
- `RIGHT`: the width of the right margin, in pixels or percents (of the key width)
- `BOTTOM`: the height of the bottom margin, in pixels or percents (of the key height)
- `LEFT`: the width of the left margin, in pixels or percents (of the key width)

Examples:

- `TEXT;text=foobar;margin=70%,0,0,0` will display the text only in the bottom 70%:

![image](https://user-images.githubusercontent.com/193474/120556402-bf160b80-c3fc-11eb-97d1-e6910eb5af52.png)

#### Option "scroll"

The `scroll` option is useful when the text is not fully visible. It allows scrolling text horizontally if the wrap option is not set or vertically if not.

It must be defined like this: `scroll=SIZE`, with:

- `SIZE`: the number of pixels to scroll per second. Ccan be negative, and can be percents (percents of key height if `wrap` is activated or key width if not)

There will be no scroll if the text is small enough to fit in its defined area (the whole key or the area left inside the margins).

Note about the alignment, if the text needs to scroll because it doesn't fit:

- if `wrap` is not activated, the `align` option will be ignored, and the text will be:
- if scroll is positive: left-aligned (and will move from right to left)
- if scroll is negative: right-aligned (and will move from left to right)

- if `wrap` is activated, the `valign` option will be ignored, and the text will be:
- if scroll is positive: aligned to the top (and will move from the bottom to the top)
- if scroll is negaive: aligned to the bottom (and will move from the top to the bottom)

Examples:

- `TEXT;text=this is a long text for a single line;wrap=false;scroll=20` will keep the text on one line but will scroll at a speed of 20 pixels per second

https://user-images.githubusercontent.com/193474/120557135-c38ef400-c3fd-11eb-8f2a-9cf45d4e3f79.mp4

- `TEXT;text=this is a very long text that even when wrapped, will not fit;wrap;scroll=20` will wrap the text and scroll it at a speed of 20 pixels per second

https://user-images.githubusercontent.com/193474/120557406-26808b00-c3fe-11eb-8477-b551bb2937c6.mp4

#### Option "file"

If you don't want do copy or link the text to the `TEXT...` file in the `KEY...` directory, you can use the `file` configuration option to define the path where to find the text to render. The file will then be read to get the text.

You have to respect the known limitations of the file name (max length and no slash `/`) and avoid semi-colons `;` as it is interpreted as the end of the path (because it's the configuration options separator) . For the slash, you can replace it with any suite of characters defined in the `slash` option (default to `\\`). For the semi-colon, you can replace it with any suite of caracters defined in the `semicolon` option (default to `^`).

Note that the `TEXT...` file can be empty when this option is set, as its content will be ignored. And you cannot set both `file` and `text` options.

It must be defined like this: `file=PATH` with:

- `PATH`: the path of the text file to read. It can starts with `~`.

Examples:

- `TEXT;file=|home|myself|texts|intro.txt;slash=|` will use the text in the file at `/home/myself/texts/intro.txt`. Note that the `/` are replaced by `|` as defined by the `slash` configuration option.
- `TEXT;file=\\home\\myself\\texts\\intro.txt` same but using the default value of the `slash` configuration option when not passed.

The key will be updated when the referenced image changes.

If you don't want to deal with special characters, you can use `file=__inside__` and write the path of the text file in the first line of the file.

#### Option "slash"

When using the `file` option, it's impossible to use slashes in the filename, so you can replace it with any character or suite of characters you defined with the `slash` option. If not defined, the default value of `\\` is used.

It must be defined like this: `slash=REPLACEMENT` with:

- `REPLACEMENT`: a character or suite of characters to use as a replacement in the `command` configuration option for the `/` character

Examples:

- `TEXT;file=|home|myself|texts|intro.txt;slash=|` will use text in the file at `/home/myself/texts/intro.txt`. Note that the `/` are replaced by `|` as defined by the `slash` configuration option.
- `TEXT;file=XXXhomeXXXmyselfXXXtextsXXXintro.txt;slash=XXX` same but using `XXX` instead of `|`
- `TEXT;file=\\home\\myself\\texts\\intro.txt` same but using the default value of the `slash` configuration option when not passed.

#### Option "semicolon"

When using the `file` option, it's impossible to use semi-colons in the filename, so you can replace it with any character or suite of characters you defined with the `semicolon` option. If not defined, the default value of `^` is used/

It must be defined like this: `semicolon=REPLACEMENT` with:

- `REPLACEMENT`: a character or suite of characters to use as a replacement in the `command` configuration option for the `;` character

Examples:

- `TEXT;file=\\home\\myself\\texts\\intro^version2.txt` will use the text in the file at `/home/myself/texts/intro;version2.txt` using default value for `/` (`\\`) and for `;` (`^`)
- `TEXT;file=|home|myself|texts|intro,version2.txt;slash=|;semicolon=,` same but using `|` for slashes and `,` for semicolons

#### Option "emojis"

This configuration option, active by default, allow to disable emojis when setting it to `false`. The main reason is to allow text within `:` to not be converted to emojis.

It must be defined like this:

- `emojis` or `emojis=true` to enable emojis (the default)
- `emojis=false` to not display emojis

Examples:

- `TEXT;text=:joy:`, `TEXT;text=:joy:;emojis`, or `TEXT;text=:joy:;emojis=true` will convert `:joy:` to the 😂 emoji

- `TEXT;text=:joy:;emojis=false` will display `:joy:` without converting it to the 😂 emoji

## Configuring key events (press, long-press, release, start, end)

`streamdeckfs` handles five different events from your StreamDeck that are listed below. But first, let see how events are defined.

An event for a key is a file in a `KEY...` directory that starts with `ON_`, followed by the event's name uppercased: `ON_PRESS`, `ON_RELEASE`, `ON_LONGPRESS`, `ON_START`, `ON_END`.

And it is configured the same way as images, texts, with configurations options, like this: `ON_PRESS;conf1=value1;conf2=value2`

An event is an action that is triggered when the key is pressed, released, etc., and, like images, it will use the file itself as the script the run. So to run a script/program for a specific action, copy the script/program (or make a link to it) in the `KEY...` directory and rename it `ON__...`. It can be any executable the OS knows to execute or a script with the correct shebang.

There can be only one of each event for each key. If the same `ON_XXX` is defined many times, the most recent will be used and the others ignored.

If you want many actions to be done when a key is, for example, pressed, the file can be a bash script with many commands, periods of sleep, etc.

Two other kinds of actions can be triggered on an event instead of running a script/program: changing page (see later the `page` configuration option) or adjusting the brightness of the StreamDeck (see later the `brightness` configuration option)

Note that when the commands are executed, the working directory is set to the deck/page/event triggering the command.

### Environment variables

Each command is executed with the environment variables received by `streamdeckfs` when it started, plus some others:

- `SDFS_EXECUTABLE`: executable to run `streamdeckfs` (the same used to run the current instance of `streamdeckfs`)
- `SDFS_DEVICE_TYPE`: type of StreamDeck model
- `SDFS_DEVICE_SERIAL`: serial number of the current StreamDeck
- `SDFS_DEVICE_DIRECTORY`: configuration directory for the current StreamDeck
- `SDFS_DEVICE_NB_ROWS`: number of rows of the current StreamDeck
- `SDFS_DEVICE_NB_COLS`: number of cols of the current StreamDeck
- `SDFS_DEVICE_NB_KEYS`: number of keys of the current StreamDeck
- `SDFS_DEVICE_KEY_WIDTH`: width, in pixels, of a key on the current Streamdeck
- `SDFS_DEVICE_KEY_HEIGHT`: height, in pixels, of a key on the current Streamdeck
- `SDFS_DEVICE_BRIGHTNESS`: the brightness (integer from 0 to 100) of the current StreamDeck
- `SDFS_VERBOSITY`: the verbosity level (one of `CRITICAL`, `ERROR`, `WARNING`, `INFO` or `DEBUG`)
- `SDFS_PAGE`: number of the page from which the event was triggered
- `SDFS_PAGE_NAME`: name, if defined, of the page from which the event was triggered
- `SDFS_PAGE_DIRECTORY`: directory configuration of the page from which the event was triggered
- `SDFS_KEY`: key from which the event was triggered (format `row,col`)
- `SDFS_KEY_ROW`: row of the key from which the event was triggered
- `SDFS_KEY_COL`: column of the key from which the event was triggered
- `SDFS_KEY_INDEX`: Position of the key as a single number (starting at 1, going from left to right, top to bottom)
- `SDFS_KEY_INDEX0`: Same as `SDFS_KEY_INDEX` but starting at 0 instead of 1
- `SDFS_KEY_NAME`: name, if defined, of the key from which the event was triggered
- `SDFS_KEY_DIRECTORY`: directory configuration of the key from which the event was triggered
- `SDFS_EVENT`: the kind of the triggered event (one of `start`, `end`, press`, `longpress`, `release`)
- `SDFS_EVENT_NAME`: name, if defined, of the triggered event
- `SDFS_EVENT_FILE`: file configuration of the triggered event
- `SDFS_QUIET`: set to `True` if the `quiet` configuration option of the event was set, else set to an empty string
- `SDFS_PRESSED_AT`: for key press related events (ie not `ON_START` or `ON_END`), the moment the key was pressed, as a timestamp (with decimals)
- `SDFS_PRESS_DURATION`: for key press related events (ie not `ON_START` or `ON_END`), the duration, in milliseconds (with decimals), elapsed between the press of the key and the execution of the command

Note that all [variables](#variables) will also be passed as environment variables.

Now let see the different events, then how they can be configured:

### The available events

#### Event "ON_START"

When a key is displayed, the `ON_START` command is executed. And if it still runs when the key stops to be displayed (when `streamdeckfs` ends or when you change page), the command will be terminated. It can be used, for example, to start a script that will periodically fetch some information and update a key, like the temperature of your CPU, the title of the current Spotify song, etc.

If the command must still run when the key stops to be displayed, it can be "detached" (and in this case, it will not even be stopped when `streamdeckfs` ends)

Note that if the key is hidden by an overlay, the `ON_START` command, if still running, won't be stopped. And when the overlay is closed, it won't be launched again.

#### Event "ON_END"

When a key is hidden (by the page being closed, or with an other page being displayed, but not an overlay), the `ON_END` command is executed.

Note that if the key is hidden by an overlay, the `ON_END` command won't be launched.

#### Event "ON_PRESS"

When a key is pressed (note that we have a different event for "press" and "release"), the `ON_PRESS` command is executed. Among the configuration options, it's possible to run the command only if the key is pressed more, or less, than a specific period; it can be repeated if pressed long enough, etc.

Same as for the `ON_START` event, the command, if it still runs when the key stops being displayed, will be terminated, except if the `detach` option is set.

#### Event "ON_RELEASE"

The `ON_RELEASE` event works like `ON_PRESS`, but the action is triggered when the key was pressed but is now released.

#### Event "ON_LONGPRESS"

The `ON_LONGPRESS` event is an event that is triggered when the key is pressed for more than a specific period, which is, by default, 300 milliseconds (0.3 seconds) but can be configured.

It's possible to have both the `ON_PRESS` and `ON_LONGPRESS` events defined on the same key, for example to have a specific action when pressed, and to display a page with more options when long-pressed (play/pause Spotify on simple press and display a page specific to Spotify on long press).

It's best to define the `duration-max` configuration option of the `ON_PRESS` event to be at max the `duration-min` of the `ON_LONGPRESS` event if you don't want the `ON_PRESS` event triggered when you do a long press.

### The events configuration options

Except when said so, all options are available for all events.

Common things to know about events configuration options:

- all durations are expressed in milliseconds. For example, `300` (it's 0.3 seconds)

### Option "wait"

The `wait` option is the delay between the event happening and the action executed. For the `ON_PRESS` event, it will happen even if the key is released.

It must be defined like this: `delay=DURATION` with:

- `DURATION`: duration in milliseconds

Examples:

- `ON_START;delay=5000` starts the action defined by the `ON_START` file 5 seconds after the key is displayed
- `ON_PRESS;delay=300` starts the action defined by the `ON_PRESS` file 0.3 seconds after the key being pressed

### Option "every"

The `every` option allows repeating the action every XXX milliseconds.

Only works for `ON_PRESS` and `ON_START` events. For `ON_PRESS`, the repeat stops when the key is released. For `ON_START`, it's when the key stops being displayed.

It must be defined like this: `every=DURATION` with:

- `DURATION`: delay between two executions of the action, in milliseconds

Examples:

- `ON_START;every=5000` will run the action defined by the `ON_START` file every 5 seconds as long as the key is displayed
- `ON_PRESS;every=500` will run the action defined by the `ON_PRESS` file every 0.5 seconds as long as the key is not released

To limit the number of times the action is repeated, see the `max-runs` option.

### Option "max-runs"

The `max-runs` option works with the `every` option and allows setting a maximum of times the action must be repeated.

It must be defined like this: `max-runs=COUNT` with:

- `COUNT`: maximum number of times the action can be repeated

Examples:

- `ON_START;every=5000;max-runs=3` will run the action every 5 seconds until the key is released, but a maximum of 3 times
- `ON_PRESS;every=500;max-runs=10` will run the action every 0.5 seconds until the key is released, but a maximum of 10 times

### Option "duration-min"

The `duration-min` option specifies the minimum duration for which the key must have been pressed before running the `ON_RELEASE` or `ON_LONGPRESS` action, the only events for which this option is available.

If must be defined like this: `duration-min=DURATION` with:

- `DURATION`: the minimum duration, in milliseconds, for the key to having been pressed to trigger the event. For the `ON_RELEASE` event, there is no default, so no minimum duration by default, but for the `ON_LONGPRESS` event, there is a default value of 300 (milliseconds).

The main difference between `ON_RELEASE` and `ON_LONGPRESS` here is that for `ON_LONGPRESS`, the action will be executed as soon as the duration is reached, but for `ON_RELEASE`, when releasing the key only if the duration was reached, but it may be much more than this delay.

Examples:

- `ON_RELEASE;duration-min=2000`: run the action when releasing the key only if the key was pressed at least 2 seconds
- `ON_LONGPRESS;duration-min=1000`: run after 1 second of the key being pressed, if the key is not yet released, but does not wait for the key to be released

### Option "duration-max"

The `duration-max` option is only for the `ON_PRESS` event and is created specifically to handle the event `ON_PRESS` OR `ON_LONGPRESS` to be executed.

If not set on the `ON_PRESS` event, the `ON_PRESS` event will be executed even if the `ON_LONGPRESS` is configured. To avoid that, you must configure the `duration-max` of the `ON_PRESS` key to be at max the `duration-min` (which is 300 by default) of the `ON_LONGPRESS` key.

It must be defined like this: `duration-max=DURATION` with:

- `DURATION`: the maximum duration, in milliseconds, for the key to having been pressed to trigger the event.

When using this configuration option, the action will not be triggered directly when the key is pressed but after this delay (or when the key is released if it is released before this delay).

Example:

- `ON_PRESS;duration-max=300` will only run the action if the key is pressed at max 300 milliseconds. After this delay, if the key is not released, the action won't be triggered.

### Option "detach"

By default, all commands executed by a StreamDeck event are "tied" to the `streamdeckfs` process. And they are stopped, if still running, when the key stops being displayed for `ON_START` events, or, for others, when `streamdeckfs` ends.

It's common to want to run an external program that should stay open even if the `streamdeckfs` ends. The `detach` flag is here for that.

It must be defined like this:

- `detach` or `detach=true` to detach the program from the `streamdeckfs` process
- `detach=false` to not detach the program (when key stops being displayed or when `streamdeckfs` ends)

Examples:

- `ON_PRESS;detach` or `ON_PRESS;detach=true` will detach the program
- `ON_PRESS;detach=false` or `ON_PRESS` will not detach the program

### Option "unique"

The `unique` flag avoids running a command when its previous execution (from the same event) is not yet finished. It's useful with the `every` option to wait for the previous iteration to be done before running the next one. Or for multiple presses.

It must be defined like this:

- `unique` or `unique=true` to deny the execution of a program if it's still running from the same event. It's the default for events `ON_START` and `ON_END`
- `unique=false` won't check if the program is already running, and it's the default for events other than `ON_START` and `ON_END`

Examples:

- `ON_PRESS;every=100;detach` or `ON_PRESS;every=100;detach=true` will run the program every 100 milliseconds but will skip an iteration if the execution from the previous one is not finished yet, so if the program takes 140ms, it will run at 0ms, 200ms, 400ms... instead of 0ms, 100ms, 200ms...
- `ON_PRESS;every=100;detach=false` or `ON_PRESS;every=100` will not detach the program, so it will run at 0ms, 100ms, 200ms, and many occurrences of the same program may be running at the same time

### Option "quiet"

The `quiet` flag avoids displaying in the `streamdeckfs` output the start (with PID) and stop (with return code) of the command. Useful when [every](#option-every) configuration option is used.

It must be defined like this:

- `quiet` or `quiet=true` to not display the start and stop of the command
- `quiet=false` to display the start and stop of the command. It's the default.

Examples:

- `ON_PRESS;every=100;quiet` or `ON_PRESS;every=100;quiet=true` will run the program every 100 milliseconds but will not display alls starts and stops
- `ON_PRESS;every=100;quiet=false` or `ON_PRESS;every=100` will display all starts and stops

### Option "command"

By default, the action executed by an event is the script/program of the file itself (or the one it links to), but it may not be convenient. Imagine if you want to run the gnome calculator on a press, you'd have to find the path of the gnome-calculator binary and link it to your `ON_PRESS` file, or make your `ON_PRESS` file a bash script that would call `gnome-calculator`.

And another example could be to open a specific page in your default browser. As there is an argument (the page to open), you cannot make a link and need to make this bash script (it's not complicated, but maybe you want to stick to the whole configuration in file names)

The `command` configuration option allows you to define the full command to execute, and it will be run as-is. You still have to respect the known limitations of the file name (max length and no slash `/`) and avoid semi-colons `;` as it is interpreted as the end of the command (because it's the configuration options separator) . For the slash, which is common if you have a path in the command, you can replace it with any suite of characters defined in the `slash` option (default to `\\`). For the semi-colon, you can replace it with any suite of caracters defined in the `semicolon` option (default to `^`).

The `command` configuration option can include `|`, `>`, etc., as you would do in a console.

Note that the `KEY...` file can be empty when this option is set, as its content will be ignored.

It must be defined like this: `command=COMMAND` with:

- `COMMAND`: the command to execute

Examples:

- `ON_PRESS;command=gnome-calculator` will run the gnome calculator
- `ON_PRESS;command=browse https:||elgato.com;slash=|` will open your default browser on the `https://elgato.com` web page. Note that the `/` in `https://` are replaced by `|` as defined by the `slash` configuration option.
- `ON_PRESS;command=browse https:\\\\elgato.com` same but using the default value of the `slash` configuration option when not passed.

If you don't want to deal with special characters, you can use `command=__inside__` and write the command inside the event file. The whole content of the file will be passed as a command to execute.

### Option "slash"

When using the `command` option, it's impossible to use slashes in the filename, so you can replace it with any character or suite of characters you defined with the `slash` option. If not defined, the default value of `\\` is used.

It must be defined like this: `slash=REPLACEMENT` with:

- `REPLACEMENT`: a character or suite of characters to use as a replacement in the `command` configuration option for the `/` character

Examples:

- `ON_PRESS;command=@path@to@myscript | grep foobar > @path@to@log;slash=@` will run the command `/path/to/myscript | grep foobar > /path/to/log`
- `ON_PRESS;command=XXXpathXXXtoXXXmyscript;slash=XXX` will run the command `/path/to/myscript`
- `ON_PRESS;command=\\path\\to\\myscript` will run the command `/path/to/myscript` using the default value of `slash`, `\\`

### Option "semicolon"

When using the `command` option, it's impossible to use semi-colons in the filename, so you can replace it with any character or suite of characters you defined with the `semicolon` option. If not defined, the default value of `^` is used/

It must be defined like this: `semicolon=REPLACEMENT` with:

- `REPLACEMENT`: a character or suite of characters to use as a replacement in the `command` configuration option for the `;` character

Examples:

- `ON_PRESS;command=browse https:\\\\elgato.com^browse https:\\\\github.com` will do two actions: open your default browser on the `https://elgato.com` web page and then on the `https://github.com` one, using default replacement values for `slash` and `semicolon`
- `ON_PRESS;command=browse https:XXelgato.com AND browse https:XXgithub.com;slash=X;semicolon= AND ` same but using `X` for the `slash` and ` AND ` for the `semicolon`

### Option "brightness"

Another possible action when pressing a key is, instead of running a command, simply change the brightness of the connected `StreamDeck`.

It must be defined like this: `brightness=BRIGHTNESS` with:

- `BRIGHTNESS`: A value between 0 and 100 (both included), or a delta, like `+10` or `-20`... The final value will be capped to fit in the accepted range.

- `ON_PRESS;brightness=+10` Increase the brightness from the actual value to the actual value plus 10
- `ON_PRESS;brightness=100` Set the brightness at the maximum

### Option "page"

Another possible action when pressing a key is to go to a different page. See the `Pages` section below to have more details about this feature.

It must be defined like this: `page=PAGE` with:

- `PAGE`: the number or name (it will use the name in the page directory name: `PAGE_NUMBER;name=NAME`) of the page to display or one of these specific values:

- `__first__` will go to the first page number available
- `__previous__` will go to the previous page number (i.e., the actual page number minus 1)
- `__next__` will go to the following page number (i.e., the actual page number plus 1)
- `__back__` will go to the previous page that was displayed before the current one

See the `Pages` section below to know more about how pages work.

Note that this configuration option is not available for `ON_START` and `ON_END` events.

## Configuring page events

Like keys, pages can have `start` and `end` events, defined by `ON_START` and `ON_END` files placed in the page directory.

When a page is displayed, the `ON_START` action is executed. And when the page is removed (by opening a new page (but not an overlay), going back to the previous one, or when `streamdeckfs` is terminated), the `ON_END` action is executed.

All [configuration options defined above for key events](#the-events-configuration-options) that are available for `start` and `end` events are also available for page events, except for `page` and `brightness`.

It's not possible to set [variables](#variables) on page events.

## Configuring deck events

Like pages, decks can have `start` and `end` events, defined by `ON_START` and `ON_END` files placed in the deck directory.

When a deck is started, the `ON_START` action is executed. And when it is stopped, the `ON_END` action is executed.

All [configuration options defined above for key events](#the-events-configuration-options) that are available for `start` and `end` events are also available for deck events, except for `page` and `brightness`.

It's not possible to set [variables](#variables) on deck events.

# Pages

Pages are a way to extend the number of keys available on your StreamDeck and regroup some actions together.

Some examples:

- a key on your first page with a simple press that will toggle your microphone, and, on a long press, display an overlay with keys to decrease/increase the microphone sensitivity (and a key to close the overlay)

- a key on your first page that will open a page dedicated to Spotify controls

Each page is a directory with at least a page number: `PAGE_NUMBER` with `NUMBER` being a positive number. If two pages have the same number, the most recent directory will be used.

A page can also have a name: `PAGE_NUMBER;name=NAME`, and then this name is available for the `page` configuration of key events. So if you have a page directory named `PAGE_50;name=spotify`, you can say "go to page 50" or "go to page spotify".

In a page directory, you only need to define the key you need, not all, in the format `KEY_ROW,COL` (`ROW` being the row on the StreamDeck, starting at 1, and `COL`, the column). If a key directory exists but has no images/texts/events (or only disabled ones), it will be ignored.

Page navigation history is kept so you can easily go back to the previous page seen. It's helpful, for example, for overlays. Let's take the first example about the "microphone overlay". Let's say you have a directory `PAGE_60;name=microphone;overlay`; you would have a key with the long press event to display this overlay defined like this: `ON_LONGPRESS;page=microphone`, and in this `PAGE_60;name=microphone;overlay` directory, you would have a key to close this overlay (i.e., go back to the previous page), like this: `ON_PRESS;page=__back__`. Until you press this key, as this page is opened as an overlay, you would see the keys of this new page as regular keys and the others from the page below, still visible but darker and without any effect when pressing them.

Pages are numbered, but it's not at all mandatory to have consecutive numbers unless you want to, for example to use the `page=__next__` and `page=__previous__` configuration options for your key event because they only work for consecutive pages.

For example, say you have three pages of classic actions and want to navigate between them easily, you can number them 1, 2, and 3. But you can also have pages triggered by some keys that should not be accessed this way, so the number can be higher. For example `PAGE_50;name=spotify` and `PAGE_60;name=microphone`.

Using names is very useful when you configure your page actions: having `page=spotify` is a lot more meaningful than `page=50` (and it allows reorganizing your pages as you want)

The `run` command will start with the first page, using page numbers. You can change that by passing the page argument: `--page PAGE` (or `-p PAGE`) with `PAGE` being the number or name of an available page.

## Option "overlay"

The `overlay` flag allows opening the page as an overlay over the current one. The keys defined on the new page will be displayed, and for the others, the keys from the current page will be displayed with a black overlay and all events deactivated. It's like a "modal" on a website.

It must be defined like this:

- `overlay` or `overlay=true` to have the page displayed as an overlay
- `overlay=false` to have the new page hiding the current one, including non defined keys that will then be black

Examples:

- `PAGE_50;overlay` or `PAGE=50;overlay=true` will make the page number 50 open as an overlay
- `PAGE_50;overlay=false` or `PAGE_50` will make the page number 50 open without any key of the current page being visible

# References

References are a way for a key, event, image, and text to inherit from another.

Say you have a page dedicated to Spotify, and some of your keys are Spotify controls and should have the same background.

You can, on each key, have a file named `IMAGE;layer=0;name=background;draw=rectangle;coords=0,0,100%,100%;width=0;fill=#8cc63f`

OR, you can have it only defined on the first key using it, say it's `KEY_1,1;name=toggle` and in other keys, add a file named `IMAGE;ref=:toggle:background`. This will take the image named "background" in the key named "toggle" of the current page (nothing before the first `:` in the `ref` configuration option means "current page")

So you can easily change how this background should look in one place, affecting all keys referencing this background. All configuration options are inherited. In this example the image defined by `IMAGE;ref=:toggle:background` will inherit the `name`, `layer`, `draw`, `coords`, `width` and `fill`. But these can be overridden. If you want to change the color but still have a rectangle, you can use `IMAGE;ref=toggle:background;fill=red`, and you'll have a red rectangle as the background.

Last important thing about references: you can have references of references (of references, etc.). Just be careful to avoid cyclic references, as it's not checked, and `streamdeckfs` may crash.

## References configuration

References can be defined for image layers, text lines, events, and keys. They are defined like this:

### Images

An image layer can reference another image layer like this: `ref=PAGE:KEY:LAYER`, with:

- `PAGE` is the name or number of the page where the reference image is, and not setting the page (`ref=:KEY:LAYER`) means looking on the same page as the image defining the `ref`
- `KEY` is the name or coordinates (`ROW,COL`) of the key where the reference image is
- `LAYER` is the name or layer number of the reference image, and not setting the layer (`ref=PAGE:KEY:`) means referencing the text on the `KEY` that has no layer defined

As with all configuration options, `name` and `layer` are inherited too (if defined on the reference) if not specified on the image having the `ref` option.

### Texts

A text line can reference another text line like this: `ref=PAGE:KEY:LINE`, with:

- `PAGE` is the name or number of the page where the reference text is, and not setting a page (`ref=:KEY:LINE`) means looking on the same page as the image defining the `ref`
- `KEY` is the name or coordinates (`ROW,COL`) of the key where the reference text is
- `LINE` is the name or line number of the reference text, and not setting the line (`ref=PAGE:KEY:`) means referencing the text on the `KEY` that has no line defined

As with all configuration options, `name` and `line` are inherited too (if defined on the reference) if not specified on the text having the `ref` option.

### Key events

A key event can reference another key event like this: `ref=PAGE:KEY:EVENT`, with:

- `PAGE` is the name or number of the page where the reference event is, and not setting a page (`ref=:KEY:EVENT`) means looking on the same page as the event defining the `ref`
- `KEY` is the name or coordinates (`ROW,COL`) of the key where the reference event is
- `EVENT` is the name or kind (`press`, `longpress`, `release`, `start`, `end`) of the reference event, and not setting the event (`ref=PAGE:KEY:`) means referencing the event for the `KEY` with the same kind (`ON_PRESS;ref=PAGE:KEY:` = looking for a `press` event in the key `KEY` of the page `PAGE`)

### Page events

A page event can reference another page event like this: `ref=PAGE:EVENT`, with:

- `PAGE` is the name or number of the page where the reference event is, and not setting a page (`ref=:EVENT`) means looking on the same page as the event defining the `ref`
- `EVENT` is the name or kind (`start`, `end`) of the reference event, and not setting the event (`ref=PAGE:`) means referencing the event for the `PAGE` with the same kind (`ON_START;ref=PAGE:` = looking for a `start` event in the page `PAGE`)

### Keys

A key can reference another key like this: `ref=PAGE:KEY`, with:

- `PAGE` is the name or number of the page where the reference key is, and not setting a page (`ref=:KEY`) means looking on the same page as the event defining the `ref`
- `KEY` is the name or coordinates (`ROW,COL`) of the reference key, and not setting the key (`ref=PAGE:`) means referencing the key on the `PAGE` with the same coordinates

Keys references are particular because a key can contain text, images, events, etc. The way it works is simple: by default, everything that is available in the reference key is "imported" in the key referencing it, but in the directory key referencing it, you can add texts, images, events... that will "replace" the ones in the reference key. So you can add layers, texts, and if you want to change one configuration of, say, an image, you can reference it and only add the configuration to update. See below when using the "close" reference.

## Partial configuration updates

When describing `margin`, `crop`, `coords` and `angles` in the previous sections we saw that it was possible to define for example `margin.1`, but it seemed useless at the time. Now, with references and overriding the power of this feature is visible: you can define the full option on the reference, and update just the part you need on the object defining the reference.

Say you have many keys that need a "progress bar", with a different length. You can define your reference layer like this: `IMAGE;name=progress;draw=line;coords=0,92,0,92;outline=white;width=7`. You can see that the third of the `coords` configuration option is `0`. It's the `X2` coordinate of the line, ie where it ends. When you need to reference it, you only have to set the `coords.2` (`2` for the third part as indexes starts at `0`): `IMAGE;ref=page:key:progress;coords.2=50%`. Now you have a line that spreads 50% of the key width.

For `crop` and `margin` you can use `.left`, `.right`, `.top` and `.bottom` instead of position.

## Usage example: references page

Among many things that are possible with references, one way of using them is to have a "references" page where you put things that are common among your configuration.

Here is an example of such a page:

```
└── PAGE_999;name=ref
├── KEY_1,1;name=img
│   └── IMAGE;layer=999;name=overlay -> /home/twidi/dev/streamdeck-scripts/assets/overlay.png
├── KEY_1,2;name=draw
│   ├── IMAGE;layer=0;name=background;draw=fill;fill=
│   └── IMAGE;name=progress;draw=line;coords=0%,92,32%,92;outline=white;width=7
├── KEY_2,1;name=close
│   ├── IMAGE;layer=1;colorize=white;margin=20,10,10,10;name=icon -> /home/twidi/dev/streamdeck-scripts/assets/close.png
│   ├── IMAGE;ref=:images:overlay
│   └── ON_PRESS;page=__back__
└── KEY_2,2;name=titled-text
├── IMAGE;layer=1;name=separator;draw=line;coords=0,25,100%,25;outline=
├── TEXT;line=1;name=title;text=Title;weight=bold;align=center;valign=top;color=
└── TEXT;line=2;name=content;text=Some content that will scroll if too long;size=18;align=center;valign=middle;margin=28,0,0,0;scroll=20;wrap
```

Here you can see a page, number 999 (could have been any number) with the name `ref`.

On this page, many keys are defined:

- `KEY_1,1;name=img`

This key contains an image with a layer number of 999, named "overlay". The goal is to have an overlay over each defined key with a specific style (like a "glass" rendering). As the layer number is 999, it's almost certain that it will be the top layer. To use it, add an empty file in your `KEY...` directories named `IMAGE;ref=ref:img:overlay`.

- `KEY_2,2;name=draw`

This key contains two drawings:

One named `background` (where only the fill color is missing), which can be referenced like this: `IMAGE;ref=ref:draw:background;fill=ref`

One named `progress` which draws a progress bar on the bottom of the key (where the `X2` coordinate must be updated to the proper progress value (here at `32%`)), which can be referenced like this: `IMAGE;layer=3;ref=ref:draw:progress;coords.2=50%` (here we change the progress to `50%` and set the layer number to `3` as the reference does not have it defined because each key using it may want to place the `progress` at a different layer)

- `KEY_2,1;name=close`

This key represents a complete "close" key that can be used to close an overlay on press. To use it, add in your page a directory named `KEY_ROW,COL;ref=ref:close` (set your row and col according to your needs): and voila, you have a close key that will work as expected.

And if you want to change the color of the close key, you add in your directory an image that will reference the `icon`: `IMAGE;ref=ref:close:icon;colorize=red`. It will have, by inheritance, the same layer number as the image in the reference key, and it will be used instead of that one.

- `KEY_2,2;name=titled-text`

This key represents a key rendered with a title on top, a small line as a separator, and a text in the central area that is wrapped and will scroll if it does not fit. The key itself is not meant to be used as a reference because each part must be configured, so you define your key directory as usual, and inside, you add three empty files `IMAGE;ref=ref:titled-text:separator;outline=COLOR` (with `COLOR` being the color you want for the separator), `TEXT;ref=ref:titled-text:title;text=TITLE` (with `TITLE` being the text you want for the title) and `TEXT;ref=ref:titled-text:content;text=TEXT` (with `TEXT` being the text you want in the central area, or you can set `file=__self__` instead of `text=...` and put the text in the file itself)

# Variables

`streamdeckfs` can use variables to ease configuration or to used as a store for the commands launched by its events. For this it uses files that can be in decks, pages or keys directories, that are named `VAR_NAME` with `NAME` a name of your choice (it must only contains capital letters from `A` to `Z`, digits from `0` to `9` and the character `_`).

As for other kinds of objects, it can be disabled by adding `;disabled` (or `;disabled=true`, see [above](#disabled), or `;enabled=false`)

The value of the variable will be read from the file itself, of from the `value` configuration option in the file name. For example the file `VAR_FOO;value=bar` defines a variable `FOO` having `bar` as value. When read from the `value` configuration option, if you want semicolon `;` or slash `/` you must use `^` or `\\` (or specify the characters to use via the [semicolon](#option-semicolon) and [slash](#option-slash) configuration options (like for paths in texts/images `file` and events `command` options).

Note that to read the value from another file, the [`file` configuration option as defined for the `TEXT*` files](#option-file-1) can also be used.

When getting the value of a variable, we use "cascading": if the variable is not defined in the asked directory, it will be looked for in the parent directory (page, then deck, for a key variable, or deck for a page variable). It allows to have a "default value" at a upper level, that is overridden at a lower one.

Variables can be used in filenames for pages, keys, images, texts... and vars themselves, and will be remplaced when needed.

Say you have a key directory containing an empty file named `VAR_FOO;value=bar`, or a file named `VAR_FOO` containing `bar`. You can then have a text file `TEXT;text=$VAR_FOO` and the text `bar` will be displayed. Cascading is respected: if the `VAR` file is not found in the key directory, it will be looked for in the page directory or the deck directory.

Variables can be created, updated or deleted while `streamdeckfs` is running and these updates will be reflected as expected (in the previous example, if you rename `VAR_FOO;value=bar` to `VAR_FOO;value=BAR`, the key will be automatically updated to display `BAR`)

If a file name contains a variable that cannot be found, this file will be ignored until the variable file is available.

When a command is triggered following an event (for example a key press), all variables available to this event will be passed as environment variable (previxed by `SDFS_`, so in our example we'll have `SDFS_VAR_FOO`, containing the value `BAR`)

## Placements

A variable can be used:

- in the file/directory name , and it's not limited to the value of an option configuration, as the configuration parsing will be done after replacing the variables. So `TEXT;$VAR_TEXT_STYLE` is possible, with for example `VAR_TEXT_STYLE;value=color=red^fit^wrap` (here the default semicolon replacement is used, but if the value was in the content of the `VAR_TEXT_STYLE` file, the real `;` characters could have been used)

- in a variable `value` configuration option, because a variable can depend on another variable (as long as their is no circular dependency)

- in the content of a `TEXT` or `VAR` file

- in the paths defined in the content of `TEXT`, `IMAGE`, or `VAR` files when configured with `file=__inside__`, or `ON_` (events) files when configured with `command=__inside__`

Note that a variable can be composed with other variables: `text=$VAR_$VAR_DISPLAY` will work, because the first `$VAR_` is not a valid variable, but `$VAR_DISPLAY` is, so, `$VAR_DISPLAY` will be converted, for example to `LASTNAME` if we have `VAR_DISPLAY;value=LASTNAME`, so we'll have `text=$VAR_LASTNAME`, then the `VAR_LASTNAME` variable will be read to have the final text. Likewise, in `text=$VAR_TEXT$VAR_INDEX`, if `VAR_TEXT` is not an existing variable, then on the first pass `$VAR_INDEX` will be converted, for example to `1`, then we have `$VAR_TEXT1` which is an existing variable, so its' converted

## Lines indexing

If the `VAR_` has its value defined by its content (i.e., not using `value=`) and its content is on multiple lines, it's possible to access a specific line by using `$VAR_FOO[INDEX]`, with `INDEX` being the line to access, starting at `0`. It's also possible to use reverse indexing (`-1` is the last line, `-2` the line before the last line...).

And to get the number of lines, you can use `[#]`.

For example if the file `VAR_FOO` contains:

```
foo
bar
baz
```

You'll have:

- `$VAR_FOO[0]` => `foo`
- `$VAR_FOO[1]` => `bar`
- `$VAR_FOO[2]` => `baz`
- `$VAR_FOO[-1]` => `baz`
- `$VAR_FOO[-2]` => `bar`
- `$VAR_FOO[-3]` => `foo`
- `$VAR_FOO[#]` => `3`

## Using environment variables

It's possible to use the [environment variables](#environment-variables) defined by `streamdeckfs` as variables, like `$VAR_SDFS_PAGE` to get the value of the `SDFS_PAGE` environment variable, ie the current page number. Consequently, it's not possible to create a variable starting with `$VAR_SDFS_`.

Note that environment variables related to events (`SDFS_EVENT*` and `SDFS_PRESS*`) cannot be used.

## Conditionals

When defining a varialbe (not when using it), it's possible to use if/then/else to set it's value.

All are configuration options to set in the file name, like `VAR_FOO;if=CONDITION;then=VALUE_IF;else=?VALUE_ELSE;`, with

- `CONDITION`: the condition to check. Must be `true` or `false` (case not sensitive), and can be composed with variables (because it does not make a lot of sense to set `if=true` directly). For example `if=$VAR_FOO="bar"`
- `VALUE_IF`: it's the value that will be used if the `CONDITION` is `true`
- `VALUE_ELSE`: it's the value that will be used if the `CONDITION` is `false`

In addition you can also use additional `elif` + `else` groups, like `VAR_FOO;if=CONDITION1;then=VALUE_IF1;elif=CONDITION2;then=VALUE_IF2;elif=CONDITION3;then=VALUE_IF3;else=VALUE_ELSE;`

See in the examples below how it can be used.

## Expressions

It's possible to have for value the result of some expressions. For example `{100/3}%` will give `33.3333%`.

Expressions are surrounded by `{` and `}`, and everything that is possible via the [py-expression-eval](https://github.com/axiacore/py-expression-eval/) library is possible (we included a forked, slightly modified version of this library into the `streamdeckfs` package).

Some examples:

- `disabled={"$VAR_FOO" == "foo"}` will set disable to `true` if `VAR_FOO` as `foo` for value. This example shows that variables containing string must be surrounded by double-quotes, because all variables are replaced first by their exact content, and after that the expressions will be evaluated.

- `VAR_NEXT_INDEX;if={$VAR_INDEX==$VAR_TEXTS[#]-1};then=0;else={$VAR_INDEX+1}` will set in `$VAR_NEXT_INDEX` the next line to use in a `VAR_TEXTs` file containing many lines, except where we are at the last line (`if={$VAR_INDEX==$VAR_TEXTS[#]-1}`) in which case we go back to 0

Check the [py-expression-eval](https://github.com/axiacore/py-expression-eval/) library page to see what is possible. The version we use has two differences with the original one:

- you must use `|` instead of `/` for divisions, because `/` cannot be used in a filename
- we added the floor division, using `||` (`//` is available if not used in filename)
- we added the `int()` and `float()` functions
- we added the `str()` function
- we removed the `concat()` function (`||` is used for floor division, and `+` can be used, in addition to `str()`, to emulate `concat()`)
- we added the `True` and `False` values
- we added the `format()` function: `format(value, "FORMAT")` with `FORMAT` being the part after the `:` when using the normal `format` function (without the `{` and `}`). For example, `format(7, "02")` will output `07`.

The expressions are evaluated from the filename, but also in the content of variable files.

They are evaluated after all the contained variables are replaced, and updated each time a variable is updated.

## Key event to set variables

In addition to actions on [key events that were previously described](#configuring-key-events-press-long-press-release-start-end)), ie `page`, `brightness` and `command`, it's also possible to set the value of one or many variable (while still running the other actions)

To make an event set a variable, it must be defined like this: `VAR_NAME=VALUE`, with:

- `VAR_NAME`: the variable to set (starting with `VAR_`)
- `VALUE`: the new value for this variable (can be a fixed value, another variable (using `$VAR_...`) or an expression)

Repeat for each variable you want to set.

If the variable file does not exist, it will be created, else it will be updated (and, if present, its `disabled` or `enabled` attribute will be removed)

By default, the variable will be created/updated in the directory of the key. But it's possible to put it at another place:

- `:VAR_NAME` to put it in the the directory of the page holding the key
- `::VAR_NAME` to put it in the directory of the deck
- `:KEY:VAR_NAME` to put it in the directory of another key (defined by name of by coordinate in the `row,col` format) of the page holding the current key
- `::PAGE:KEY:VAR_NAME` to put it in the directory of aanother key in another page (defined by name or number)
- `::PAGE:VAR_NAME` to put it in the directory of another page

The value will be set on the name (on the `value` configuration option) of the variable `VAR_NAME;value=VALUE`.

But it can be set as the content of the file, by suffixing the name of the var by `<`: `VAR_NAME<=VALUE`. In this case the file name of the variable will be `VAR_NAME` and its content will be `VALUE`. You can remember this by seeing the `<=` operator as an arrow saying that we put the `VALUE` into `VAR_NAME`.

Example setting a variable named `FOO` to the value `bar` in the page named `mypage` on press of a key would be:

```
ON_PRESS;::mypage:VAR_FOO<=bar

```

## Examples

### Example 1

The first example below shows how this can be used to display different texts depending on a state. This example will also show how to use "expressions" (surrounded by `{` and `}`, and, via `VAR_STATE=value=state_$VAR_STATE_VALUE` that a variable value can use a variable itself, and that a variable does not need to be the full value of a configuration option:

```
TEXT;text=1;fit;enabled={"$VAR_STATE" == "state_one"}
TEXT;text=2;fit;enabled={"$VAR_STATE" == "state_two"}
TEXT;text=3;fit;enabled={"$VAR_STATE" == "state_three"}
VAR_STATE;value=state_$VAR_STATE_VALUE
VAR_STATE_VALUE;value=one
```

This example will display `1`, with the current value of `VAR_STATE_VALUE` (`one`).

### Example 2

The second example below shows how a variable can be used for many configurations options at once (`VAR_TEXT_STYLE`), and that how variables can be set in the content of files.

The files are:

```
TEXT;$VAR_TEXT_STYLE
VAR_FULLNAME
VAR_FIRSTNAME
VAR_LASTNAME
VAR_TEXT_STYLE
```

And their content:

- `TEXT;$VAR_TEXT_STYLE`:

```
Hello
$VAR_FULLNAME!
```

- `VAR_FULLNAME`

```
$VAR_FIRSTNAME $VAR_LASTNAME
```

- `VAR_FIRSTNAME`

```
Foo
```

- `VAR_LASTNAME`

```
Bar
```

- `VAR_TEXT_STYLE`

```
color=red;fit;wrap
```

This will show the text "Hello Foo Bar" in red on the key.

### Example 3

The third example below shows how to use a variable in a path. Here we want to change the icon displayed depending on a variable.

We have two files:

- an image file named `IMAGE;colorize=white;file=__inside__` containing `/path/to/icons/$VAR_ICON.png`
- a variable file named `VAR_ICON` containg `thumbs-up`

An event or an external script can then change the content of the `VAR_ICON` file to update the key.

### Example 4

The fourth example below shows how to alternate between 3 emojis.

We use the fact that we can have many times the same event defined (here `ON_PRESS`) but the disabled ones are ignored. So we only keep one enabled.
And the action of the event is to set the next emoji in a variable, variable that is used to display the emoji on the key, and to know which events to disable.

So we have:

- A text defined like this: `TEXT;fit;text=$VAR_EMOJI`
- A variable file defined like this: `VAR_EMOJI;value=:joy:`
- And three `ON_PRESS` where only one will be activated depending on the emoji, to display the next one:
- `ON_PRESS;VAR_EMOJI=:joy:;enabled={"$VAR_EMOJI" == ":sob:"}`
- `ON_PRESS;VAR_EMOJI=:neutral_face:;enabled={"$VAR_EMOJI" == ":joy:"}`
- `ON_PRESS;VAR_EMOJI=:sob:;enabled={"$VAR_EMOJI" == ":neutral_face:"}`

Another way to write this example if we don't want to hardcode emojis in many files:

- Same text as above: `TEXT;fit;text=$VAR_EMOJI`
- A variable file defined like this `VAR_EMOJI;value=$VAR_EMOJI1`
- Three variables for our emojis, so we can change them without changing anything else:
- `VAR_EMOJI1;value=:joy:`
- `VAR_EMOJI2;value=:neutral_face:`
- `VAR_EMOJI3;value=:sob:`
- And our three `ON_PRESS`:
- `ON_PRESS;VAR_EMOJI=$VAR_EMOJI1;enabled={"$VAR_EMOJI" == "$VAR_EMOJI3"}`
- `ON_PRESS;VAR_EMOJI=$VAR_EMOJI2;enabled={"$VAR_EMOJI" == "$VAR_EMOJI1"}`
- `ON_PRESS;VAR_EMOJI=$VAR_EMOJI3;enabled={"$VAR_EMOJI" == "$VAR_EMOJI2"}`

It could even be made more generic:

- A text defined like this: `TEXT;fit;text=$VAR_TEXT$VAR_INDEX`
- A variable file defined like this: `VAR_INDEX;value=1`
- Three variables for our texts, so we can change them without changing anything else:
- `VAR_TEXT1;value=:joy:`
- `VAR_TEXT2;value=:neutral_face:`
- `VAR_TEXT3;value=:sob:`
- And three `ON_PRESS` where only one will be activated depending on the index, to display the next one:
- `ON_PRESS;VAR_INDEX=1;enabled={$VAR_INDEX == 3}`
-