# 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`) ## Исходники | Компонент | Путь | |-----------|------| | 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 автоматически при каждой сборке: ```js // config/webpack.config.js 'process.env.BUILD_DATE': JSON.stringify(new Date().toISOString().slice(0, 16).replace('T', ' ')) ``` ## Сборка WebOS app ```bash cd ~/Developer/magic4pc/webos NODE_OPTIONS=--openssl-legacy-provider npm run build ``` Требует `--openssl-legacy-provider` — старый webpack несовместим с Node 25. ## Сборка IPK ```bash cd ~/Developer/magic4pc/webos NODE_OPTIONS=--openssl-legacy-provider npm run build /Users/admin/webOS_TV_SDK/CLI/bin/ares-package dist/ service/ --outdir . # Создаёт me.wouterdek.magic4pc_1.1.0_all.ipk ``` > ⚠️ `ares-package dist/ service/` — оба аргумента обязательны, иначе сервис не попадёт в IPK. > `npm run package` = `ares-package -n` (без подписи) — **не использовать**, TV отклоняет IPK. ## Деплой на TV (192.168.1.75) TV рутован, SSH без пароля: `ssh root@192.168.1.75` > ⚠️ `luna-send` через SSH не выводит ответ в stdout — нужен PTY через `script`: > `ssh root@192.168.1.75 "script -q -c \"luna-send -n 1 -f luna://...\" /tmp/o.txt; cat /tmp/o.txt"` ```bash # 1. Скопировать IPK на TV scp me.wouterdek.magic4pc_1.1.0_all.ipk root@192.168.1.75:/tmp/magic4pc.ipk # 2. Закрыть приложение ssh root@192.168.1.75 "script -q -c \"luna-send -n 1 -f luna://com.webos.service.applicationmanager/closeByAppId '{\\\"id\\\":\\\"me.wouterdek.magic4pc\\\"}'\" /tmp/o.txt; cat /tmp/o.txt" # 3. Удалить старую версию ssh root@192.168.1.75 "script -q -c \"luna-send -n 1 -f luna://com.webos.appInstallService/dev/remove '{\\\"id\\\":\\\"me.wouterdek.magic4pc\\\"}'\" /tmp/o.txt; cat /tmp/o.txt" # 4. Установить новую (через PTY — иначе нет вывода) 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/o.txt; cat /tmp/o.txt" # Ждать "state": "installed" в выводе # 5. Запустить ssh root@192.168.1.75 "script -q -c \"luna-send -n 1 -f luna://com.webos.applicationManager/launch '{\\\"id\\\":\\\"me.wouterdek.magic4pc\\\"}'\" /tmp/o.txt; cat /tmp/o.txt" ``` ### Проверка логов сервиса ```bash ssh root@192.168.1.75 "script -q -c \"luna-send -n 1 -f luna://me.wouterdek.magic4pc.service/query '{}'\" /tmp/o.txt; cat /tmp/o.txt" ``` Ожидаемый результат: `serviceActive: true`, `broadcastAdsActive: true` (или `isConnected: true` если HTPC подключён), в логах — `listApps: returned N apps`. ## Деплой Go-клиента на HTPC ```bash 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 сервис ```bash 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 (VK Video, Кинопоиск, last used) при включении TV. ### Логика - **Boot (полный ребут)** — `/tmp` чистится → нет `/tmp/magic4pc-run-state` → `freshStart=true` → запускает app - **Wake (standby→on) с последним активным app** — init.d при Suspend удаляет `run-state` → при wake нет файла → `freshStart=true` → запускает app - **Wake после HDMI/EIM source** — если последний активный был HDMI (`com.webos.app.hdmi*`) — init.d при Suspend тоже удаляет `run-state` (TODO: можно улучшить), magic4pc по wake не запускает чужой app если пользователь вручную переключился на HDMI перед сном - **Ручной запуск magic4pc** (пользователь сам открыл) — `run-state = running` уже есть → `freshStart=false` → ничего не запускает ✅ ### Файлы persistent storage Сервис (UID 5078, jail) имеет доступ только к `/tmp` и своей директории: | Файл | Где | Что | |------|-----|-----| | `magic4pc-settings` | `PERSISTENT_DIR/` | Выбранный app: appId / `__last_used__` / `none` | | `magic4pc-last-app` | `PERSISTENT_DIR/` | Последний foreground app (пишет init.d + сервис) | | `magic4pc-run-state` | `/tmp/` | `running` = уже запускался после boot/wake | `PERSISTENT_DIR = /media/developer/apps/usr/palm/services/me.wouterdek.magic4pc.service` Init.d (root) копирует `last-app` в `PERSISTENT_DIR` при каждом обновлении foreground. ### Init.d скрипт Файл на TV: `/var/lib/webosbrew/init.d/magic4pc` Исходник в репо: `tv-scripts/init.d-magic4pc.sh` **Что делает:** - При старте: удаляет `/tmp/magic4pc-run-state` (fresh boot) - `track_foreground` — каждые 3 сек пишет foreground app в `/tmp/magic4pc-last-app` и `PERSISTENT_DIR/magic4pc-last-app` - `monitor_power` — при Suspend удаляет `run-state` чтобы wake выглядел как fresh start - Каждые 5 мин обновляет `/tmp/magic4pc-apps.json` (список app для Dropdown) **Деплой init.d:** ```bash 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 (init.d запускается Homebrew Channel при старте) ``` > ⚠️ **Правило**: init.d всегда правится локально в репо (`tv-scripts/init.d-magic4pc.sh`), затем деплоится на TV через `scp`. Никогда не редактировать файл на TV напрямую — изменения не попадут в репо. ### Деплой IPK (правильный способ) ```bash cd ~/Developer/magic4pc/webos ./deploy.sh ``` Скрипт делает всё: build → package → scp → install → launch. Ждёт `"state": "installed"` и запускает. > ⚠️ `npm run package` = `ares-package -n` (unsigned) — **не использовать**, TV отклоняет. > ⚠️ `luna-send` без PTY (`script`) молчит — в скрипте уже учтено.
Ручной деплой (если нужно) ```bash # 1. Собрать и упаковать cd ~/Developer/magic4pc/webos NODE_OPTIONS=--openssl-legacy-provider npm run build /Users/admin/webOS_TV_SDK/CLI/bin/ares-package dist/ service/ --outdir . # 2. Скопировать на TV scp me.wouterdek.magic4pc_1.1.0_all.ipk root@192.168.1.75:/tmp/magic4pc.ipk # 3. Установить (через PTY — иначе нет вывода) 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/o.txt; cat /tmp/o.txt' # Ждать "state": "installed" в выводе # 4. Запустить ssh root@192.168.1.75 'script -q -c "luna-send -n 1 -f luna://com.webos.applicationManager/launch '"'"'{\"id\":\"me.wouterdek.magic4pc\"}'"'"'" /dev/null' ```
### Отладка ```bash # Лог сервиса ssh root@192.168.1.75 "cat /tmp/m4p_debug.log" # Текущий выбранный app и last-app ssh root@192.168.1.75 "cat /media/developer/apps/usr/palm/services/me.wouterdek.magic4pc.service/magic4pc-settings" ssh root@192.168.1.75 "cat /media/developer/apps/usr/palm/services/me.wouterdek.magic4pc.service/magic4pc-last-app" # Run-state (есть = уже запускался, нет = fresh start) ssh root@192.168.1.75 "cat /tmp/magic4pc-run-state" # Лог init.d ssh root@192.168.1.75 "cat /tmp/m4p_debug.log | grep 'magic4pc init'" ``` ## Известные проблемы - **Guide (458)** — перехватывается WebOS системно, EIM-регистрация не помогает. Нет решения. - **listApps** — `applicationManager/dev/listApps` недоступен без dev mode. Используется `applicationManager/listApps`. - **Self-launch** — magic4pc не должен запускать сам себя. Фильтруется в `checkAutoLaunch` по appId. - **HDMI как last-app** — init.d трекает foreground включая HDMI sources. При `__last_used__` и последнем активном HDMI — не запустит ничего полезного. Решение: выбрать конкретный app вместо `last_used`.