Files
obsidian-vault/family/how-to/htpc-magic4pc.md
T

122 lines
7.0 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.
# magic4pc — LG Magic Remote для HTPC
## Что это
Go-клиент для LG Magic Remote (webOS приложение на TV).
Форк: https://github.com/mallexxx/magic4pc_altclient (бранч `bazzite-linux`)
Оригинал: https://github.com/netham45/magic4pc_altclient
## Архитектура
- **TV сервер**: webOS приложение на LG TV (192.168.1.75:42831), шлёт UDP
- **HTPC клиент**: `/usr/local/bin/magic4pc`, UDP-клиент на фиксированном source port **9106**
- **xdotool**: единый persistent процесс через stdin pipe — мышь и клавиши
- **ydotool**: для gamescope-специфичных команд (Ctrl+1, Ctrl+2) через `/run/user/1000/.ydotool_socket`
## Ключевые фиксы (история)
| Проблема | Решение |
|----------|---------|
| TV "service error" / не реконнектится | Фиксированный source UDP port 9106 (`ListenUDP(":9106")`) |
| TV зависает в "waiting for client" | Server keepalive timeout 10s → auto-reconnect |
| KDE XTest диалог при каждом клике | Единый xdotool процесс (не новый на каждую кнопку) |
| KDE XTest диалог при рестарте xdotool | `XwaylandEisNoPrompt=true` в kwinrc |
| Кривые координаты мыши | Динамический scale через `xdotool getdisplaygeometry` |
| Крестик слал Ctrl/a/s/d | TV keycodes 37-40 → `Left/Up/Right/Down` keysym |
| Цифры слали мусор | ASCII keycodes → `string(rune(key))` |
| Мышь не двигалась на gamescope алерте | Letterbox-алгоритм: `xdotool search --onlyvisible` → 16:9 viewport → scale+offset с clamp нуля |
## Маппинг кнопок
| Кнопка | Keycode | Gaming Mode (gamescope) | KDE Desktop |
|--------|---------|------------------------|-------------|
| Красная | 403 | Ctrl+1 (Steam меню) | Super (Win) |
| Зелёная | 404 | Escape | Escape |
| Жёлтая | 405 | Ctrl+2 (Steam QAM) | Средняя кнопка мыши |
| Синяя | 406 | Правый клик | Правый клик |
| Back | 461 | Mouse X1 (back) | Mouse X1 (back) |
| Enter/OK | 13 | Return | Return |
| Ch Up | 33 | PageUp | PageUp |
| Ch Down | 34 | PageDown | PageDown |
| D-pad Left | 37 | Left | Left |
| D-pad Up | 38 | Up | Up |
| D-pad Right | 39 | Right | Right |
| D-pad Down | 40 | Down | Down |
| Play | 415 | XF86AudioPlay | XF86AudioPlay |
| Stop | 413 | XF86AudioStop | XF86AudioStop |
| Pause | 19 | XF86AudioPause | XF86AudioPause |
| Цифры 0-9 | 48-57 | 0-9 | 0-9 |
| GUIDE | 458 | Правый клик | Правый клик |
| Мышь (тач) | — | mousemove (scaled) | mousemove (scaled) |
| Тап | mousedown/up | Left click | Left click |
| Скролл | wheel | scroll 4/5 | scroll 4/5 |
## Определение сессии
`isGamescopeSession()` — читает `/proc/*/cmdline`, ищет `kwin_wayland`. Если нет → gamescope.
## Координаты мыши
TV шлёт координаты в пространстве 0–1920 × 0–1080.
### В gamescope (Steam Big Picture)
При открытии окна (алерт, диалог) клиент определяет его размер через `xdotool search --onlyvisible`, так как `getactivewindow` не работает в gamescope (нет `_NET_ACTIVE_WINDOW`). Если окно единственное видимое — используется **letterbox-алгоритм**:
1. Активное окно определяется двумя стратегиями:
1а) **Под курсором**`xdotool getmouselocation` показывает window ID под мышью
1б) **По bounding box** — собираются все visible окна, отсекаются те что внутри другого (popup меню), из оставшихся берётся с наибольшим ID (последнее открытое)
2. Вычисляется **letterbox viewport** в пропорции 16:9 на основе ширины окна: `lb_h = win_w * 9/16`
3. Вычисляется **отступ**: `off_y = -(lb_h - win_h) / 2` (отрицательный — сдвигает viewport вверх, чтобы TV y=0 совпадал с верхом чёрной полосы)
4. TV-координаты маппятся в X11 через scale и offset:
`x11_x = tv_x * scale_x + off_x`
`x11_y = tv_y * scale_y + off_y`
где `scale_x = scale_y = lb_w / 1920`
5. Отрицательные координаты обрезаются до 0 (xdotool их не принимает)
6. Если окно не найдено — фолбэк на `getdisplaygeometry` (весь дисплей)
**Пример для алерта 288×89:**
- Letterbox viewport: 288×162
- Scale: 0.15×0.15
- Offset: 0, -36.5
- TV (0,0) → X11 (0,0); TV (288,89) → X11 (43, 13)
### В KDE десктопе
Используется `getdisplaygeometry` — scale = display_size / 1920×1080. Офсет 0,0.
### Определение сессии
- `isGamescopeSession()` — ищет `kwin_wayland` по argv[0] в /proc. Нет → gamescope
- `activeDisplay()` — находит X display для инъекции:
- KDE: берёт Xwayland display из аргументов kwin_wayland
- Gamescope: использует `:1` (game Xwayland) если есть visible окна, иначе `:0` (Steam BP)
## Проблемы которые могут вернуться
- **xdotool умирает** (write error) → KDE показывает XTest диалог. Разрешить и включить `XwaylandEisNoPrompt=true`.
- **TV "service error"** — если висит намертво, cold reboot TV (выдернуть из розетки).
- **Бинарь слетает** после `rpm-ostree upgrade` → пересобрать и скопировать.
- **Display switching** — при смене между `:0` и `:1` xdotool перезапускается. Если переключение происходит часто (мигающие окна) — возможны краткие потери мыши.
## Деплой и sudoers
На HTPC настроен sudo без пароля для systemctl:
```bash
echo 'bazzite ALL=(ALL) NOPASSWD: /usr/bin/systemctl' | sudo tee /etc/sudoers.d/bazzite-systemctl
```
Деплой новой версии:
```bash
# сборка на mac
cd ~/Developer/magic4pc_altclient
GOOS=linux GOARCH=amd64 go build -o magic4pc .
# копирование на HTPC
scp magic4pc htpc:/home/bazzite/magic4pc/magic4pc
# на HTPC — остановить старый, скопировать, запустить
echo bazzite | sudo -S systemctl stop magic4pc.service
echo bazzite | sudo -S cp /home/bazzite/magic4pc/magic4pc /usr/local/bin/magic4pc
echo bazzite | sudo -S systemctl start magic4pc.service
# проверить
systemctl is-active magic4pc.service
journalctl -u magic4pc.service -n 5
```