1
0
streamdeck-lets-go/config.example.json
Maksim Totmin e676b74c32 Dynamic key generators: plugin system for auto-generated pages
Add dynamic_keys support to PageConfig — a contract-based plugin
system where any executable outputs a JSON array of KeyConfig to stdout.
The daemon runs the generator on a configurable interval and renders
the keys dynamically on the Stream Deck.

Core changes:
- config: DynamicKeyGen struct (command/script, interval, timeout, max_keys)
- config: full validation for dynamic_keys fields
- action: execDynamicKeys() with safe JSON parsing (stderr logged separately,
  env vars STREAMDECK_KEY_COUNT / STREAMDECK_CONFIG_DIR)
- page: activeKeys() merges static + dynamic keys (static wins by index)
- page: startDynamicKeys / stopDynamicKeys lifecycle, per-page results cache
- page: rerenderActivePage() with deadlock-safe lock ordering
- daemon: event handler uses activeKeys() for dynamic key actions

Web UI:
- GET /api/pages returns effective_keys (merged static + cached dynamic)
- app.js: gridKeys uses effective_keys when available
- app.js: guard against editing dynamic keys through the UI
- index.html: " dynamic" badge + effective key count in page switcher
- polls /api/pages every 10s to keep the UI in sync

Image rendering:
- loadImage: gift.ResizeToFill instead of gift.Resize (preserves aspect
  ratio, crops to fill — no more stretched cover art)

Example & docs:
- config.example.json: games page with dynamic_keys + back button
- README.md: comprehensive Dynamic Key Generators section (architecture
  diagram, config reference, generator contract, merge behavior,
  env vars, error handling, Python + Shell examples, tips)
2026-06-21 21:05:32 +07:00

105 lines
2.1 KiB
JSON

{
"version": 1,
"log_level": "info",
"default_page": "main",
"font": "medium",
"show_label_background": true,
"devices": [
{
"serial": "",
"brightness": 75
}
],
"pages": [
{
"name": "main",
"keys": [
{
"index": 0,
"icon": "fa:terminal",
"actions": [
{
"trigger": "tap",
"type": "command",
"command": "kitty",
"background": true
}
]
},
{
"index": 1,
"icon": "fab:firefox",
"label": "Browser",
"font_size": 11,
"actions": [
{
"trigger": "tap",
"type": "command",
"command": "firefox",
"background": true
}
]
},
{
"index": 2,
"icon": "fa:gamepad",
"label": "Games",
"font_size": 11,
"actions": [
{
"trigger": "tap",
"type": "page",
"page": "games"
}
]
},
{
"index": 7,
"icon": "fa:chevron-right",
"label": "Next",
"actions": [
{
"trigger": "tap",
"type": "builtin",
"builtin": "page:next"
},
{
"trigger": "long_press",
"type": "builtin",
"builtin": "page:prev"
}
]
}
]
},
{
"name": "games",
"icon": "fa:gamepad",
"keys": [
{
"index": 0,
"icon": "fa:arrow-left",
"actions": [
{ "trigger": "tap", "type": "page", "page": "main" }
]
}
],
"dynamic_keys": {
"script": "scripts/lutris-keys",
"interval": "120s"
}
}
],
"auto_switch": [],
"screensaver": {
"enabled": false,
"idle_seconds": 30,
"image": "",
"brightness": 10
},
"timing": {
"long_press_ms": 500,
"double_tap_ms": 300
}
}