Files
obsidian-vault/family/how-to/htpc-magic4pc.md
T
2026-05-30 09:01:02 +00:00

11 KiB
Executable File
Raw Blame History

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/.

Сборка

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-statefreshStart=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