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)
- Replace wtype with ydotool as primary keyboard input tool on Wayland
(wtype had a bug sending ESC instead of correct keys to GUI apps)
- Use 'ydotool type' command for better Wine/game compatibility
- Automatically start ydotoold daemon on startup if available
- Keep wtype as fallback for backward compatibility
- Improve keyboard action support for games running in Wine
- index.html: replace three conditional render paths (FA CSS icon,
image img, label span) with a single img always routed through
/api/render, which correctly applies icon_scale, font_size, etc.
FA icons were previously rendered via CSS font classes, bypassing
the backend render endpoint and ignoring icon_scale
- daemon.go: call stopPeriodicKeys/startPeriodicKeys after page switch
from device gesture and auto-switch (was missing, causing goroutines
to never restart for the correct page)
- app.js: forward display output background to /api/render preview URL
- index.html: show preview image and text from display output when key
has no configured background/icon/label
- daemon.go: save/restore active page on config reload instead of
always activating default_page (fixes page switch on save)
- render.go: replace basicfont.Face7x13 with parseDisplayFace() for
unicode/Cyrillic support in edit key preview
- web.go: add SSE endpoint (GET /api/events) for real-time page_changed
events and POST /api/activate-page API
- app.js: persist active page via localStorage, listen for SSE
page_changed events
- styles.css, index.html: fix grid sizing (width 100%, minmax(0, 1fr),
aspect-ratio on key buttons, max-width 525px)
- Multi-action system: each key supports multiple actions with different triggers
(tap, long_press, double_tap, hold_start, hold_end)
- GestureEngine: timing-based gesture detection with configurable thresholds
- New config types: KeyAction, TimingConfig, DisplayOutput
- Display output now supports JSON format (text/background/text_color) and
first-line-as-hex-color format for dynamic key backgrounds
- Display background colors are reflected in the web UI on the grid
- renderUnicodeText/renderUnicodeTextOnImage accept custom text color (fg)
- Global font setting (medium/regular) in Settings UI
- PageManager.defaultFont used as fallback for per-key font
- Alpine.js bundled locally (no CDN dependency)
- Web UI: action tabs (Tap/Long Press/Double Tap/Hold), unified More section
with appearance + display settings, gesture timing sliders in Settings
- Grid: subtle action type icons in top-left corner matching display indicator style
- Backward compatible: old config 'action' field auto-migrated to 'actions' array
- M+ 1m font files in assets/ for reference
- Updated config.example.json and README with all new features