Files
obsidian-vault/family/how-to/htpc-magic4pc.md
T
2026-05-30 10:03:30 +00:00

193 lines
11 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 перезапускается. Если переключение происходит часто (мигающие окна) — возможны краткие потери мыши.
## WebOS TV App — сборка и деплой
TV-часть — React/Enact WebOS приложение + Node.js сервис, упакованные в IPK. Исходники: `~/Developer/magic4pc/webos/`.
### Сборка
```bash
cd ~/Developer/magic4pc/webos
NODE_OPTIONS=--openssl-legacy-provider npm run build
```
**Питфолл:** `--openssl-legacy-provider` обязателен — старый webpack несовместим с Node.js 25+.
### Упаковка IPK
```bash
/Users/admin/webOS_TV_SDK/CLI/bin/ares-package dist/ service/ --outdir .
```
**Питфолл:** нужны оба аргумента `dist/` и `service/` — без `service/` Node.js сервис не попадает в IPK.
**Питфолл:** `npm run package` = `ares-package -n` (unsigned) — TV отклоняет неподписанные пакеты. Использовать SDK-команду напрямую.
### Деплой (deploy.sh)
```bash
cd ~/Developer/magic4pc/webos && ./deploy.sh
```
Скрипт: build → package → scp → close → remove → install → launch.
Для install (subscribe mode) — прямой `ssh+script` с polling на `"state":"installed"`.
**Питфолл:** `ares-install` / `ares-launch` ненадёжны — не ждут завершения. Использовать `luna-send dev/install` через script-враппер.
Версия в UI отображается как `1.1.0 (YYYY-MM-DD HH:MM)` — инжектируется webpack через `process.env.BUILD_DATE`.
## Auto-launch при включении / пробуждении TV
Magic4pc запускает настроенное приложение при включении или пробуждении TV.
| Файл | Расположение | Назначение |
|------|-------------|-----------|
| `magic4pc-settings` | PERSISTENT_DIR | ID выбранного приложения |
| `magic4pc-last-app` | PERSISTENT_DIR | Последнее активное приложение |
| `magic4pc-run-state` | `/tmp/` | `running` после первого запуска |
`PERSISTENT_DIR = /media/developer/apps/usr/palm/services/me.wouterdek.magic4pc.service`
Логика:
- **Boot/wake:** `/tmp` очищается → нет `run-state``freshStart=true` → запускает настроенное приложение
- **Ручной запуск:** `run-state=running` уже есть → auto-launch пропускается
`init.d` скрипт на TV (`/var/lib/webosbrew/init.d/magic4pc`) удаляет `run-state` при suspend — wake снова тригерит fresh launch. Редактировать только в репо (`tv-scripts/init.d-magic4pc.sh`), не напрямую на TV.
## WebOS Back Key + системная клавиатура
Когда системная клавиатура открыта, **первый Back** поглощается OS (закрывает клавиатуру) — `keydown` и `onButtonDown` **не стреляют**. Второй Back приходит нормально.
Фикс в `MainPanel.js` (флаг `_kbWasOpen`):
```js
// Input.onActivate:
this._kbWasOpen = true;
// onButtonDown на Back:
if (this.state.wolMacActive || this._kbWasOpen) {
this._kbWasOpen = false;
return; // подавить закрытие панели
}
```
Подходы, которые не работают: `Popup.noAutoDismiss`, timeout-эвристика, polling `document.activeElement` (клавиатура — системный оверлей, фокус не переходит на INPUT).
## Деплой и sudoers
**Пароль sudo на HTPC:** `bazzite` (см. [[htpc-access]]).
> ⚠️ `/etc/sudoers.d/bazzite-systemctl` не существует — nopasswd для systemctl не настроен.
> Для удалённых команд через SSH использовать: `echo bazzite | sudo -S <cmd>`
Деплой новой версии:
```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
```