9.8 KiB
Executable File
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- Получает UDP-пакеты от TV, инжектирует ввод через
/dev/uinput inputBackKey()→ Esc в gamescope, x1 в KDE- Репо: https://github.com/mallexxx/magic4pc_altclient (ветка
main)
- Получает UDP-пакеты от TV, инжектирует ввод через
Исходники
| Компонент | Путь |
|---|---|
| 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-state→freshStart=true→ запускает app - Wake — init.d при Suspend удаляет
run-state→ при wakefreshStart=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. DOMkeydownне приходит,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 системно. Нет решения.
- listApps —
dev/listAppsнедоступен без dev mode. ИспользуетсяapplicationManager/listApps. - Self-launch — фильтруется в
checkAutoLaunchпо appId. - HDMI как last-app — при
__last_used__и последнем активном HDMI не запустит ничего полезного. Решение: выбрать конкретный app. ares-installненадёжен — молчит без PTY, не ждёт завершения. Использоватьluna-send dev/installчерез script.