adding support for if\else type situations
This commit is contained in:
63
README.md
63
README.md
@@ -15,6 +15,7 @@ No Elgato software required — communicates directly with the device over USB H
|
|||||||
- PNG and JPEG icons, automatically scaled to key size
|
- PNG and JPEG icons, automatically scaled to key size
|
||||||
- Animated GIF support — frames pre-encoded at startup, cycled at the GIF's native rate
|
- Animated GIF support — frames pre-encoded at startup, cycled at the GIF's native rate
|
||||||
- Runs any shell command on key press
|
- Runs any shell command on key press
|
||||||
|
- **Status/toggle keys** — poll any shell command on an interval, swap icons based on output; icon updates on press
|
||||||
- **Live config reload** — save your config and the deck updates instantly, no restart needed
|
- **Live config reload** — save your config and the deck updates instantly, no restart needed
|
||||||
- Privileged command helper — run whitelisted root commands via a Unix socket; supports polkit auth dialogs
|
- Privileged command helper — run whitelisted root commands via a Unix socket; supports polkit auth dialogs
|
||||||
- Automatic reconnect — survives USB unplug, KVM switches, and suspend/resume
|
- Automatic reconnect — survives USB unplug, KVM switches, and suspend/resume
|
||||||
@@ -60,6 +61,10 @@ streamdeck-go/
|
|||||||
│ └──▶ goroutine/key: loop frames, sleep per-frame delay
|
│ └──▶ goroutine/key: loop frames, sleep per-frame delay
|
||||||
│ (cancelled via ctx on reload)
|
│ (cancelled via ctx on reload)
|
||||||
│
|
│
|
||||||
|
├── poll goroutine/key ──▶ exec poll command every interval
|
||||||
|
│ └──▶ match in stdout? swap icon_true / icon_false
|
||||||
|
│ (also triggered immediately on button press)
|
||||||
|
│
|
||||||
└── event loop ──▶ device.ReadButtons() (250 ms timeout, checks ctx)
|
└── event loop ──▶ device.ReadButtons() (250 ms timeout, checks ctx)
|
||||||
└──▶ key-down: exec.Command("sh", "-c", command)
|
└──▶ key-down: exec.Command("sh", "-c", command)
|
||||||
```
|
```
|
||||||
@@ -272,6 +277,63 @@ keys:
|
|||||||
command: ""
|
command: ""
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Status / toggle keys
|
||||||
|
|
||||||
|
A key can poll any shell command on an interval and show one of two icons based on the result. Pressing the button runs `command` as usual, and the icon re-checks ~400 ms later so it reflects the new state immediately.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
keys:
|
||||||
|
3:
|
||||||
|
command: pactl set-source-mute @DEFAULT_SOURCE@ toggle
|
||||||
|
icon_true: mic-muted.png # shown when poll output contains match
|
||||||
|
icon_false: mic-active.png # shown when poll output does not contain match
|
||||||
|
poll:
|
||||||
|
command: pactl get-source-mute @DEFAULT_SOURCE@
|
||||||
|
interval: 2s # how often to check (default: 2s)
|
||||||
|
match: "yes" # substring to find in stdout → true
|
||||||
|
```
|
||||||
|
|
||||||
|
**How matching works:**
|
||||||
|
|
||||||
|
| `match` set? | True condition | False condition |
|
||||||
|
|---|---|---|
|
||||||
|
| Yes | stdout contains the string | stdout does not contain it |
|
||||||
|
| No (omitted) | command exits 0 | command exits non-zero |
|
||||||
|
|
||||||
|
**More examples:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# VPN status (exit-code match — no match string needed)
|
||||||
|
4:
|
||||||
|
command: nmcli connection up my-vpn
|
||||||
|
icon_true: vpn-on.png
|
||||||
|
icon_false: vpn-off.png
|
||||||
|
poll:
|
||||||
|
command: nmcli connection show --active my-vpn
|
||||||
|
interval: 5s
|
||||||
|
|
||||||
|
# Systemd service toggle
|
||||||
|
5:
|
||||||
|
command: systemctl --user toggle my-service
|
||||||
|
icon_true: service-running.png
|
||||||
|
icon_false: service-stopped.png
|
||||||
|
poll:
|
||||||
|
command: systemctl --user is-active my-service
|
||||||
|
interval: 3s
|
||||||
|
|
||||||
|
# Speaker mute
|
||||||
|
6:
|
||||||
|
command: pactl set-sink-mute @DEFAULT_SINK@ toggle
|
||||||
|
icon_true: speaker-muted.png
|
||||||
|
icon_false: speaker-on.png
|
||||||
|
poll:
|
||||||
|
command: pactl get-sink-mute @DEFAULT_SINK@
|
||||||
|
interval: 2s
|
||||||
|
match: "yes"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Terminal & SSH commands
|
### Terminal & SSH commands
|
||||||
|
|
||||||
Any shell command works — including launching terminals and SSH sessions.
|
Any shell command works — including launching terminals and SSH sessions.
|
||||||
@@ -358,6 +420,7 @@ Works as long as SSH key auth is set up and the key has no passphrase (or the ag
|
|||||||
| PNG | Recommended for static icons |
|
| PNG | Recommended for static icons |
|
||||||
| JPEG | Good for photos / complex images |
|
| JPEG | Good for photos / complex images |
|
||||||
| GIF | Animated — all frames pre-encoded at startup, cycled in a background goroutine |
|
| GIF | Animated — all frames pre-encoded at startup, cycled in a background goroutine |
|
||||||
|
| SVG | Rasterised to 96×96 at startup via `oksvg` |
|
||||||
|
|
||||||
Icons are scaled to 96×96 px using bi-linear filtering. The XL renders images
|
Icons are scaled to 96×96 px using bi-linear filtering. The XL renders images
|
||||||
mirrored, so they are pre-flipped before sending — your icons will appear the right
|
mirrored, so they are pre-flipped before sending — your icons will appear the right
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ device:
|
|||||||
# command: any shell command
|
# command: any shell command
|
||||||
|
|
||||||
keys:
|
keys:
|
||||||
|
# Regular key: show an icon and run a command on press.
|
||||||
0:
|
0:
|
||||||
icon: ghostty.png
|
icon: ghostty.png
|
||||||
command: ghostty
|
command: ghostty
|
||||||
@@ -28,3 +29,35 @@ keys:
|
|||||||
2:
|
2:
|
||||||
icon: files.png
|
icon: files.png
|
||||||
command: nautilus
|
command: nautilus
|
||||||
|
|
||||||
|
# Toggle/status key: polls a command to determine state and shows one of two
|
||||||
|
# icons. Pressing the button runs `command` and the icon updates automatically.
|
||||||
|
#
|
||||||
|
# `match` is a substring to find in the poll command's stdout.
|
||||||
|
# If match is found → icon_on is shown; otherwise → icon_off.
|
||||||
|
# Omit `match` entirely to use exit code instead (0 = on, non-zero = off).
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# Microphone mute:
|
||||||
|
# command: pactl set-source-mute @DEFAULT_SOURCE@ toggle
|
||||||
|
# poll.command: pactl get-source-mute @DEFAULT_SOURCE@
|
||||||
|
# poll.match: "yes" # "Mute: yes" → muted → show icon_on (mic-muted.png)
|
||||||
|
#
|
||||||
|
# VPN toggle:
|
||||||
|
# command: nmcli connection up my-vpn / nmcli connection down my-vpn
|
||||||
|
# poll.command: nmcli connection show --active my-vpn
|
||||||
|
# (no match — uses exit code: 0 = connected)
|
||||||
|
#
|
||||||
|
# Systemd service:
|
||||||
|
# command: systemctl --user toggle my-service
|
||||||
|
# poll.command: systemctl --user is-active my-service
|
||||||
|
# (no match — uses exit code)
|
||||||
|
#
|
||||||
|
# 3:
|
||||||
|
# command: pactl set-source-mute @DEFAULT_SOURCE@ toggle
|
||||||
|
# icon_true: mic-muted.png
|
||||||
|
# icon_false: mic-active.png
|
||||||
|
# poll:
|
||||||
|
# command: pactl get-source-mute @DEFAULT_SOURCE@
|
||||||
|
# interval: 2s
|
||||||
|
# match: "yes"
|
||||||
|
|||||||
8
go.mod
8
go.mod
@@ -9,4 +9,10 @@ require (
|
|||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/sys v0.13.0 // indirect
|
require (
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
||||||
|
golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 // indirect
|
||||||
|
golang.org/x/sys v0.13.0 // indirect
|
||||||
|
golang.org/x/text v0.35.0 // indirect
|
||||||
|
)
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -1,11 +1,19 @@
|
|||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
|
||||||
github.com/sstallion/go-hid v0.15.0 h1:WERW/VW3Us6N73V2qa7HjdqWQvwHd0CoRDOP/N707/w=
|
github.com/sstallion/go-hid v0.15.0 h1:WERW/VW3Us6N73V2qa7HjdqWQvwHd0CoRDOP/N707/w=
|
||||||
github.com/sstallion/go-hid v0.15.0/go.mod h1:fPKp4rqx0xuoTV94gwKojsPG++KNKhxuU88goGuGM7I=
|
github.com/sstallion/go-hid v0.15.0/go.mod h1:fPKp4rqx0xuoTV94gwKojsPG++KNKhxuU88goGuGM7I=
|
||||||
golang.org/x/image v0.37.0 h1:ZiRjArKI8GwxZOoEtUfhrBtaCN+4b/7709dlT6SSnQA=
|
golang.org/x/image v0.37.0 h1:ZiRjArKI8GwxZOoEtUfhrBtaCN+4b/7709dlT6SSnQA=
|
||||||
golang.org/x/image v0.37.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
|
golang.org/x/image v0.37.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
|
||||||
|
golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 h1:DZshvxDdVoeKIbudAdFEKi+f70l51luSy/7b76ibTY0=
|
||||||
|
golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||||
|
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
@@ -8,10 +8,22 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PollConfig defines how to poll for the current state of a toggle key.
|
||||||
|
type PollConfig struct {
|
||||||
|
Command string `yaml:"command"` // shell command whose output is checked
|
||||||
|
Interval string `yaml:"interval"` // how often to poll, e.g. "2s" (default: "2s")
|
||||||
|
Match string `yaml:"match"` // substring to find in output → "on" state; omit to use exit code 0
|
||||||
|
}
|
||||||
|
|
||||||
// KeyConfig defines what a single Stream Deck key does.
|
// KeyConfig defines what a single Stream Deck key does.
|
||||||
type KeyConfig struct {
|
type KeyConfig struct {
|
||||||
Icon string `yaml:"icon"` // filename relative to icons_dir
|
Icon string `yaml:"icon"` // filename relative to icons_dir (regular keys)
|
||||||
Command string `yaml:"command"` // shell command to run on press
|
Command string `yaml:"command"` // shell command to run on press
|
||||||
|
|
||||||
|
// Toggle/status keys: show different icons based on polled state.
|
||||||
|
IconTrue string `yaml:"icon_true"` // icon when poll match is true
|
||||||
|
IconFalse string `yaml:"icon_false"` // icon when poll match is false
|
||||||
|
Poll *PollConfig `yaml:"poll"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is the top-level structure of the YAML config file.
|
// Config is the top-level structure of the YAML config file.
|
||||||
|
|||||||
Reference in New Issue
Block a user