1
0
Maksim Totmin 3ae704ec48 Initial commit: Stream Deck daemon with web UI
- Web-based key editor (Alpine.js SPA)
- Actions: shell commands, scripts, built-in media/volume/brightness, page switching
- Keyboard shortcut action (wtype/xdotool)
- On-device display with periodic command output
- Auto page switching (Hyprland, Sway, Niri, GNOME, KDE, X11)
- Screensaver with idle detection
- Config hot-reload
- Multi-device support
2026-06-16 22:30:38 +07:00

162 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# StreamController → Go: план портирования
## Цель
Портировать StreamController (Python/GTK4 GUI для Elgato Stream Deck) на Go.
Один статический бинарник, конфигурация через JSON, веб-интерфейс для настройки.
## Архитектура
```
streamdeck-lets-go
├── main.go # CLI entry: "serve" → web, иначе → daemon
├── daemon.go # главный event loop (deck + autoswitch + screensaver)
├── config.go # JSON config types + load/save/validate
├── deck.go # обёртка над dh1tw/streamdeck + PID config map
├── page.go # страницы, ключи, рендер
├── action.go # exec.Command + built-in D-Bus экшены
├── autoswitch.go # детект активного окна (GNOME/Hyprland/Sway/KDE/X11)
├── screensaver.go # idle/lock детекция (logind D-Bus)
├── media.go # MPRIS D-Bus helpers
└── web/
├── server.go # HTTP API + embed static
└── static/ # index.html + htmx app.js
```
## Зависимости
| Пакет | Зачем |
|---|---|
| `github.com/dh1tw/streamdeck` | HID общение с Stream Deck (bearsh/hid) |
| `github.com/godbus/dbus/v5` | D-Bus (медиа, screensaver, GNOME) |
| stdlib (embed, image, net/http, os/exec, slog, flag) | — |
CGO требуется из-за bearsh/hid (libusb).
## Поддерживаемые девайсы
| Модель | PID | Keys | Key Size | Format |
|---|---|---|---|---|
| Mini | 0x60 | 3×2=6 | 80px | BMP |
| Original V1 | 0x6d | 5×3=15 | 72px | JPEG |
| Original V2 | 0x63 | 5×3=15 | 72px | BMP |
| MK.2 | 0x80 | 5×3=15 | 72px | JPEG |
| XL | 0x6c | 8×4=32 | 96px | JPEG |
## JSON Config
```json
{
"version": 1,
"default_page": "main",
"log_level": "info",
"devices": [
{"serial": "AL12345", "brightness": 75}
],
"pages": [
{
"name": "main",
"icon": "icons/main.png",
"keys": [
{
"index": 0,
"icon": "icons/play.png",
"label": "Play/Pause",
"action": {"type": "builtin", "builtin": "media:playpause"}
}
],
"background": "wallpapers/bg.jpg",
"screensaver": {"image": "ss.png", "delay": 30, "brightness": 10}
}
],
"auto_switch": [
{
"wm_class": "firefox",
"title": ".*",
"page": "browser",
"devices": ["*"],
"stay": false
}
],
"screensaver": {
"enabled": true,
"idle_seconds": 30,
"image": "screensaver.png",
"brightness": 10
}
}
```
## Порядок реализации
1. **main.go** — CLI mux (daemon / serve / discover), signal handling
2. **config.go** — типы, Load, Save, Validate
3. **deck.go** — PID map, Deck wrapper (open, render, brightness, events)
4. **page.go** — PageManager, ActivatePage, Key render
5. **action.go** — ExecuteAction (command, builtin, script, page switch)
6. **daemon.go** — event loop (key press → action, reconnect, SIGHUP reload)
7. **autoswitch.go** — WindowDetector интерфейс, DE-specific реализации
8. **screensaver.go** — idle детекция, suspend/resume
9. **web/** — embed FS, REST API, HTMX frontend
## REST API (web)
| Endpoint | Method | Description |
|---|---|---|
| `/api/config` | GET | Получить config.json |
| `/api/config` | PUT | Сохранить config.json (и перезагрузить) |
| `/api/status` | GET | Состояние: девайс, страница, статистика |
| `/api/page/{name}` | POST | Переключить страницу |
| `/` | GET | index.html (SPA) |
## Что нужно доделать / Known Gaps
### P0 — Баги / неработающие фичи
#### P0.1 — Screensaver не вызывается ✅
**Проблема**: `ssTicker` создаётся в daemon loop (daemon.go:78), но в `select` нет `case <-ssTicker.C`. `Check()` никогда не вызывается. Кроме того, `NewScreensaver` не проставляет `ss.enabled` из конфига.
**Решение**:
1. `screensaver.go`: `NewScreensaver` — добавлен `ss.enabled = cfg.Enabled`
2. `screensaver.go`: добавлены методы `Activate(deck, cfg)` (гасит яркость, показывает image на панель) и `Deactivate(deck, pm)` (восстанавливает яркость)
3. `daemon.go`: добавлена переменная `ssCtrl`, добавлен `case <-ssTicker.C` в select, вызывающий `ssCtrl.Check()``ssCtrl.Activate()`
4. `daemon.go`: в обработчике key press вызывается `ssCtrl.NotifyInput()` и при необходимости `ssCtrl.Deactivate()`
5. `screensaver.go`: `Check()` теперь возвращает `true` только при переходе в active (не флудит Activate каждые 5с)
#### P0.2 — Brightness up/down — заглушка ✅
**Проблема**: `deckBuiltinAction` (action.go:210-221) всегда ставит 100 или 25, нет циклического переключения.
**Решение**:
1. `deck.go`: добавлено поле `brightness int` в `Deck`, инициализируется 75, обновляется в `SetBrightness`, добавлен геттер `Brightness()`
2. `action.go`: `deckBuiltinAction` — цикл 25→50→75→100→25 (up) и 100→75→50→25→100 (down)
3. `daemon.go`: добавлен `deviceBrightness(cfg)` helper, стартовый `SetBrightness` читает из `cfg.Devices[0].Brightness`
4. `daemon.go`: reconnectTicker заменён с `SetBrightness(75)` на `SetBrightness(deck.Brightness())` (no-op check) + `deviceBrightness(cfg)` после reconnect
5. `daemon.go`: configTicker при hot-reload теперь применяет `deviceBrightness(cfg)`
### P1 — Функциональные дыры
- [ ] **Multi-device**: `OpenDeck("")` игнорирует `devices[n].serial`. Первый девайс всегда открывается первым попавшимся HID.
- [ ] **Grid в UI жёстко 5×3**: Web UI не адаптируется под Mini (6 ключей) или XL (32). API возвращает всегда 15 слотов.
- [ ] **Нет проверки external зависимостей при старте**: `playerctl`, `wpctl`, `brightnessctl`, `rsvg-convert`, `xdotool` — если не установлены, ошибка всплывает в рантайме без понятного сообщения.
- [ ] **Конфиг хранит координаты клавиш (index)**, но UI не позволяет их перетаскивать — нет drag-and-drop, нет reorder.
- [ ] **No README**: ни описания установки, ни списка зависимостей, ни примера запуска.
### P2 — Качество кода / инфраструктура
- [ ] **Тесты**: только `autoswitch_test.go` (1 файл, 7 тестов). Нет покрытия для config, deck, page, web, action, render.
- [ ] **CI / сборка**: нет Makefile, Dockerfile, systemd unit, миграции конфига (только v1).
- [ ] **Паник-безопасность**: горутины display capture не имеют recover; одна паника убивает весь daemon.
- [ ] **Request logging**: HTTP сервер без middleware — нет access логов.
- [ ] **No WebSocket fallback**: Display outputs poll раз в 3с вместо push.
### Не в приоритете (зафиксировать)
- [ ] Folder/paging groups (вложенные страницы)
- [ ] Multi-action / макросы (chain команд)
- [ ] Поддержка SD+, SD Neo, SD LCD (touch + dials)
- [ ] Анимации (GIF/APNG на клавишах)
- [ ] HTTPS / auth
- [ ] D-Bus implementation (MPRIS, logind) вместо CLI тулов