11 KiB
Executable File
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а) Под курсором —xdotool getmouselocationпоказывает window ID под мышью
1б) По bounding box — собираются все visible окна, отсекаются те что внутри другого (popup меню), из оставшихся берётся с наибольшим ID (последнее открытое) - Вычисляется letterbox viewport в пропорции 16:9 на основе ширины окна:
lb_h = win_w * 9/16 - Вычисляется отступ:
off_y = -(lb_h - win_h) / 2(отрицательный — сдвигает viewport вверх, чтобы TV y=0 совпадал с верхом чёрной полосы) - 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 - Отрицательные координаты обрезаются до 0 (xdotool их не принимает)
- Если окно не найдено — фолбэк на
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. Нет → gamescopeactiveDisplay()— находит 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и:1xdotool перезапускается. Если переключение происходит часто (мигающие окна) — возможны краткие потери мыши.
WebOS TV App — сборка и деплой
TV-часть — React/Enact WebOS приложение + Node.js сервис, упакованные в IPK. Исходники: ~/Developer/magic4pc/webos/.
Сборка
cd ~/Developer/magic4pc/webos
NODE_OPTIONS=--openssl-legacy-provider npm run build
Питфолл: --openssl-legacy-provider обязателен — старый webpack несовместим с Node.js 25+.
Упаковка IPK
/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)
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):
// 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>
Деплой новой версии:
# сборка на 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