Files
obsidian-vault/family/how-to/magic4pc-webos.md
T
2026-05-29 18:03:32 +00:00

9.8 KiB
Raw Blame History

Magic4PC — WebOS Client

Управление LG MagicRemote как мышью/клавиатурой на HTPC через Bazzite.

Архитектура

  • TV (WebOS) — app me.wouterdek.magic4pc + JS-сервис me.wouterdek.magic4pc.service
    • App: React/Enact UI, слушает кнопки пульта, отправляет в сервис через luna
    • Сервис: Node.js, бродкастит UDP, принимает подключение от Go-клиента на HTPC
  • HTPC (Bazzite) — Go-клиент ~/magic4pc/, сервис magic4pc.service

Исходники

Компонент Путь
WebOS UI ~/Developer/magic4pc/webos/src/views/MainPanel.js
WebOS сервис ~/Developer/magic4pc/webos/service/m4p_service.js
Go клиент ~/Developer/magic4pc_altclient/
HTPC бинарник ~/magic4pc/magic4pc
HTPC сервис ~/.config/systemd/user/magic4pc.service (HTPC)
run.sh ~/magic4pc/run.sh (HTPC)

Кнопки пульта (keyCode)

Код Кнопка Действие
406 🔵 Синяя Открыть панель настроек
461 Back Esc (gamescope) / x1 (KDE)
458 Guide системный (не перехватывается)
403 🔴 Красная передаётся на HTPC
415 Play передаётся на HTPC
19 Pause передаётся на HTPC

Панель настроек (синяя кнопка)

  • Статус — Connected to IP:port / Waiting / Service disabled
  • v1.1.0 (YYYY-MM-DD HH:MM) — версия + дата сборки справа от статуса
  • Input source — выбор HDMI входа
  • Enable / Disable — старт/стоп сервиса
  • Default app on exit — приложение которое запускается при выходе из magic4pc (None / Last used / конкретное)
  • Show Log / Hide Log — лог событий и ошибок поверх видео

Версия сборки

Версия отображается как 1.1.0 (YYYY-MM-DD HH:MM) — дата инжектируется webpack автоматически при каждой сборке:

// config/webpack.config.js
'process.env.BUILD_DATE': JSON.stringify(new Date().toISOString().slice(0, 16).replace('T', ' '))

Сборка WebOS app

cd ~/Developer/magic4pc/webos
NODE_OPTIONS=--openssl-legacy-provider npm run build

⚠️ Требует --openssl-legacy-provider — старый webpack несовместим с Node 25.

Сборка IPK

/Users/admin/webOS_TV_SDK/CLI/bin/ares-package dist/ service/ --outdir .

⚠️ Оба аргумента dist/ service/ обязательны — иначе сервис не попадёт в IPK. ⚠️ npm run package = ares-package -n (unsigned) — не использовать, TV отклоняет.

Деплой на TV (192.168.1.75)

Правильный способ — deploy.sh

cd ~/Developer/magic4pc/webos
./deploy.sh

Скрипт делает всё: build → package → scp → close → remove → install → launch.

Использует ~/Automation/tv-luna-send.sh для close/remove/launch. Для install (-i subscribe) — прямой ssh+script с polling на "state":"installed".

⚠️ Не использовать ares-install / ares-launch — не ждут завершения, работают ненадёжно. ⚠️ luna-send без PTY молчит — см. tv-luna-send.

Ручной деплой
cd ~/Developer/magic4pc/webos
NODE_OPTIONS=--openssl-legacy-provider npm run build
/Users/admin/webOS_TV_SDK/CLI/bin/ares-package dist/ service/ --outdir .
scp me.wouterdek.magic4pc_1.1.0_all.ipk root@192.168.1.75:/tmp/magic4pc.ipk

~/Automation/tv-luna-send.sh -n 1 -f luna://com.webos.service.applicationmanager/closeByAppId '{"id":"me.wouterdek.magic4pc"}' || true
~/Automation/tv-luna-send.sh -n 1 -f luna://com.webos.appInstallService/dev/remove '{"id":"me.wouterdek.magic4pc"}' || true
sleep 2

# install — через прямой script (luna-send -i не работает через враппер)
ssh root@192.168.1.75 "script -q -c \"luna-send -i -f luna://com.webos.appInstallService/dev/install '{\\\"id\\\":\\\"me.wouterdek.magic4pc\\\",\\\"ipkUrl\\\":\\\"/tmp/magic4pc.ipk\\\",\\\"subscribe\\\":true}'\" /tmp/install.txt &
  for i in \$(seq 1 30); do sleep 1; grep -q '\"state\":\"installed\"' /tmp/install.txt 2>/dev/null && break; done
  kill %1 2>/dev/null; grep -v '^Script ' /tmp/install.txt; rm -f /tmp/install.txt"

~/Automation/tv-luna-send.sh -n 1 -f luna://com.webos.applicationManager/launch '{"id":"me.wouterdek.magic4pc"}'

Проверка статуса сервиса

~/Automation/tv-luna-send.sh -n 1 -f luna://me.wouterdek.magic4pc.service/query '{}'

Ожидаемый результат: serviceActive: true, broadcastAdsActive: true.

Деплой Go-клиента на HTPC

cd ~/Developer/magic4pc_altclient
GOOS=linux GOARCH=amd64 go build -o magic4pc_new .
scp magic4pc_new bazzite@192.168.1.86:~/magic4pc/
ssh bazzite@192.168.1.86 "
  systemctl --user stop magic4pc
  mv ~/magic4pc/magic4pc_new ~/magic4pc/magic4pc
  systemctl --user start magic4pc
"

HTPC сервис

ssh bazzite@192.168.1.86 "systemctl --user status magic4pc"
ssh bazzite@192.168.1.86 "systemctl --user restart magic4pc"

enable не используется — autostart через EIM на стороне TV.

Auto-launch при boot/wake

Magic4pc автоматически запускает выбранный app при включении TV.

Логика

  • Boot/tmp чистится → нет run-statefreshStart=true → запускает app
  • Wake — init.d при Suspend удаляет run-state → при wake freshStart=true → запускает app
  • Ручной запускrun-state=running уже есть → freshStart=false → ничего не запускает

Файлы persistent storage

PERSISTENT_DIR = /media/developer/apps/usr/palm/services/me.wouterdek.magic4pc.service

Файл Где Что
magic4pc-settings PERSISTENT_DIR/ Выбранный app: appId / __last_used__ / none
magic4pc-last-app PERSISTENT_DIR/ Последний foreground app
magic4pc-run-state /tmp/ running = уже запускался после boot/wake

Init.d скрипт

Файл на TV: /var/lib/webosbrew/init.d/magic4pc Исходник: tv-scripts/init.d-magic4pc.sh в репо

# Деплой
scp tv-scripts/init.d-magic4pc.sh root@192.168.1.75:/var/lib/webosbrew/init.d/magic4pc
ssh root@192.168.1.75 "chmod +x /var/lib/webosbrew/init.d/magic4pc"
# Применяется после ребута TV

⚠️ Всегда править в репо, не на TV напрямую.

Отладка

# Лог UI (appendLogPersist пишет сюда)
ssh root@192.168.1.75 "tail -30 /tmp/m4p_debug.log"

# Run-state
ssh root@192.168.1.75 "cat /tmp/magic4pc-run-state"

# Settings / last-app
ssh root@192.168.1.75 "cat $PERSISTENT_DIR/magic4pc-settings"
ssh root@192.168.1.75 "cat $PERSISTENT_DIR/magic4pc-last-app"

⚠️ appendLog() пишет только в UI. В файл попадает только appendLogPersist(). При отладке использовать appendLogPersist.

Back и клавиатура — особенности WebOS

Как работает Back когда открыта системная клавиатура:

  • Back #1 — система поглощает полностью: закрывает клавиатуру, вызывает Input.onClose. DOM keydown не приходит, onButtonDown не вызывается.
  • Back #2 — приходит в DOM → onButtonDown.

Следствие: нельзя детектировать первый Back через keydown. Единственный способ — флаг в onActivate.

Реализованный фикс (_kbWasOpen в MainPanel.js):

// Input.onActivate:
this._kbWasOpen = true;

// onButtonDown при Back:
if (this.state.wolMacActive || this._kbWasOpen) {
    this._kbWasOpen = false;
    return; // не закрывать панель
}

Что не работает на WebOS:

  • Popup.noAutoDismiss — Back закрывает через onButtonDown, не через Popup.onClose
  • Таймаут (Date.now() < ts + 1000ms) — ненадёжно
  • Polling document.activeElement — фокус никогда не переходит на INPUT, клавиатура — системный overlay

Известные проблемы

  • Guide (458) — перехватывается WebOS системно. Нет решения.
  • listAppsdev/listApps недоступен без dev mode. Используется applicationManager/listApps.
  • Self-launch — фильтруется в checkAutoLaunch по appId.
  • HDMI как last-app — при __last_used__ и последнем активном HDMI не запустит ничего полезного. Решение: выбрать конкретный app.
  • ares-install ненадёжен — молчит без PTY, не ждёт завершения. Использовать luna-send dev/install через script.