
phone-deck v3.0.1
Самостоятельно размещаемая панель управления и удаленный рабочий стол для Linux-рабочих станций через Tailscale, с поддержкой WebRTC-стриминга, голосового управления с локальной LLM, сценарных макросов, виртуального ввода, передачи файлов и телеметрии системы в реальном времени.
phone-deck
Превратите запасной телефон на Android (или любое устройство с браузером) в собственный пульт управления и удаленный рабочий стол для вашей Linux-рабочей станции — через вашу частную сеть Tailscale, без сторонних приложений.
Это началось с вопроса «что мне делать со старым телефоном?» и превратилось в управляющую плоскость для системы Hyprland: управление окнами/рабочими столами, сценарные макросы, голосовой маршрутизатор PTT с локальным LLM-ассистентом, контекстно-зависимые элементы управления в приложении, двустороннее аудио + трансляция экрана, сенсорный удаленный рабочий стол, виртуальная клавиатура/мышь, передача файлов + отправка с телефона на ПК и телеметрия в реальном времени — всё это предоставляется в виде установленного PWA с фосфорным терминальным оформлением.
Примечание о переносимости. Этот инструмент создавался для одной конкретной конфигурации: Arch-based Linux (Garuda) + Hyprland + PipeWire + NVIDIA + Tailscale с dotfiles end-4 / illogical-impulse. Архитектура универсальна, но многие интеграции зависят от окружения (имена мониторов, привязки рабочих столов, путь к теме matugen,
cpupower/nvidia-smi). Воспринимайте это как рабочий справочник для адаптации, а не готовое решение.
Возможности
Интерфейс представляет собой веб-приложение с вкладками, темное, ландшафтное (синхронизировано по теме с вашим рабочим столом):
| Вкладка | Что делает |
|---|---|
| Рабочие столы | Сетка рабочих столов для каждого монитора в реальном времени (нажатие для переключения), список окон в реальном времени (нажатие для фокуса), переключатель DPMS для каждого монитора + яркость ddcutil |
| Удаленное управление | Экранная тачпад (перетаскивание/нажатие/два пальца) + виртуальная клавиатура с модификаторами и комбинациями, через устройство ядра uinput |
| Режимы | Макросы сцен одним нажатием — запуск/закрытие/расположение целых макетов приложений на нескольких мониторах (например, «Работа» / «Отдых») |
| Голос | Голосовой маршрутизатор PTT (faster-whisper, CPU): полосы ключевых слов — macro (запуск режима/команды), type (диктовка), input (фраза → клавишная комбинация) — с подтверждением перед выполнением, а также "friday", readonly локальный LLM-ответчик (Ollama + Qwen) с собственным веб-поиском и отслеживанием состояния системы в реальном времени |
| Аудио | Отключение микрофона/динамика, громкость, выбор устройств вывода и ввода, транспорт playerctl + обложка (отслеживает активный MPRIS-плеер) |
| Поток | Двустороннее WebRTC аудио (ПК↔телефон, с телефоном в качестве микрофона и только вывод на телефон) + видео экрана, и нажатие для управления транслируемым экраном = настоящий удаленный рабочий стол |
| Система | Переключатель режима производительности, живое переключение темы, статус Tailscale, топ-процессы (нажатие для завершения), блокировка, перезапуск NetworkManager, приостановка/перезагрузка/выключение |
| Файлы | Сделать снимок экрана монитора → просмотр/скачивание на телефоне; перетащить файл с телефона на ПК |
| Настройки | Редактирование конфигурации commands.json / modes.json (и контекстных/голосовых) с телефона (проверка JSON) |
Всегда поверх вкладок — контекстная полоса, показывающая, что происходит прямо сейчас: активный звонок (отключение / переход к нему), воспроизводимый медиаконтент + транспорт, и внутриприкладные элементы управления клавиатурой для активного приложения (YouTube / Brave / Teams), без перехвата фокуса.
Еще две функции за пределами вкладок:
- Send-to-Rig — PWA регистрируется как цель обмена в Android: поделитесь изображением, текстом или ссылкой из любого приложения на телефоне, и они окажутся на компьютере — изображения сохраняются в каталог сброса и помещаются в буфер обмена как готовый к вставке PNG, текст в буфер обмена, голая ссылка открывается.
- Ambient Cogitator — после нескольких минут бездействия пульт превращается в фосфорную приборную панель: стрелочные измерители телеметрии с фиксацией пиков («было ли на пределе, пока меня не было?»), часы, текущее воспроизведение.
Плюс: вход с помощью экранной цифровой клавиатуры PIN, блокировка пробуждения экрана, полноэкранное ландшафтное PWA и блокировка при переборе паролей при входе.
Архитектура
phone / laptop (browser PWA)
│ HTTPS + WSS (Tailscale-only)
▼
tailscale serve ──► FastAPI web app ── Unix socket ──► deckd
(real TLS cert) (runs as your user) (action names) (runs as root)
│ │
hyprctl · pactl · ddcutil · grim · fixed allowlist of
wf-recorder · uinput · WebRTC privileged commands
(cpupower, nvidia-smi,
systemctl, …)
Два процесса:
app/— веб-приложение работает от имени обычного пользователя. Оно выполняет всё, что не требует прав root: управление Hyprland, аудио, яркость, захват экрана, виртуальный ввод, WebRTC-стриминг, передача файлов.deckd/— крошечный помощник с правами root (только stdlib, без зависимостей) для нескольких привилегированных действий. Веб-приложение никогда не отправляет ему строки shell — только имена действий из фиксированного разрешенного списка (governor_performance,gpu_power_limit,suspend, …), проверяемые вdeckd/actions.pyперед выполнением. Даже если веб-приложение будет полностью скомпрометировано, радиус поражения ограничен разрешенными действиями без возможности внедрения аргументов. Сокет имеет праваroot:<group>режим0660.
Модель безопасности
- Доступно только внутри вашей tailnet —
tailscale serveпредоставляет доступ по вашему имени MagicDNS с реальным сертификатом Let's Encrypt; никогда не привязывается к0.0.0.0. - Вход по JWT (PIN → подписанная кука) с блокировкой с экспоненциальной задержкой после повторных неудач.
- Привилегированные действия изолированы за разрешенным демоном-помощником.
- Загрузки очищаются от базового имени (невозможно покинуть каталог сброса).
Веб-приложение может выполнять ваши настроенные shell-команды и вводить данные от вашего имени — по замыслу, это пульт управления вашей машиной. Держите его внутри tailnet, за PIN-кодом, и не выставляйте публично.
Требования
- Linux с Hyprland (wlroots), PipeWire (с совместимостью
pactl/PulseAudio) - Python ≥ 3.11 и uv
- Tailscale (с включенными HTTPS-сертификатами для вашей tailnet)
- Инструменты командной строки, используемые различными функциями (установите то, что хотите использовать):
hyprctl,pactl/pw-record/pw-play,playerctl,ddcutil,wf-recorder,grim,cpupower,nvidia-smi,kitty(или ваш терминал) - Ваш пользователь в группе
input(для/dev/uinput) иi2c(дляddcutil) aiortc+ PyAV (устанавливается черезuv) для аудио/видео стриминга
Установка
git clone <your-repo-url> phone-deck
cd phone-deck
uv sync # creates .venv and installs dependencies
uv run python -m app.set_pin # set your unlock PIN
1. Помощник с правами root (deckd)
Сначала отредактируйте systemd/deckd.service — укажите пути и DECK_SOCKET_GROUP в группу, к которой принадлежит ваш пользователь (обычно основная группа):
sudo cp systemd/deckd.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now deckd
deckd запускает системный Python (только stdlib — venv не нужен).
2. Веб-приложение
Запустите напрямую для разработки:
uv run uvicorn app.main:app --host 127.0.0.1 --port 8765
…или как пользовательский сервис. Сначала отредактируйте пути в systemd/phone-deck.service, затем установите его. Важно: в конфигурациях, где Hyprland не запускается через uwsm, graphical-session.target systemd никогда не активируется, поэтому пользовательский юнит не запустится автоматически при входе. Надежное решение — запускать его из Hyprland с импортированным окружением сеанса — добавьте в автозапуск Hyprland (например, в ~/.config/hypr/custom/execs.conf от end-4):
exec-once = systemctl --user import-environment WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_RUNTIME_DIR DBUS_SESSION_BUS_ADDRESS XDG_CURRENT_DESKTOP && systemctl --user start phone-deck
3. HTTPS через Tailscale
-
Включите сертификаты однократно: консоль администратора Tailscale → DNS → Включить HTTPS-сертификаты.
-
Обеспечьте приложение реальным сертификатом:
sudo tailscale serve --bg 127.0.0.1:8765 -
Установите
DECK_SECURE_COOKIES=1в окружении веб-приложения после включения HTTPS.
4. Установка на телефон
Откройте https://<your-host>.<your-tailnet>.ts.net/ в Chrome на телефоне (он должен быть в вашей tailnet), войдите, затем ⋮ → Установить приложение. Оно запускается в полноэкранном, заблокированном в ландшафт режиме с удержанием экрана включенным. HTTPS требуется для сервис-воркера PWA и wake-lock.
Работает с любого устройства в tailnet — включая ноутбуки (сенсорные поверхности используют Pointer Events, так что мышь тоже подходит). WebRTC-поток одноранговый (один зритель одновременно).
Настройка
Все конфигурации времени выполнения находятся в ~/.config/phone-deck/ и доступны для редактирования на вкладке Настройки (проверка JSON, сохраняется .bak). Изменения вступают в силу при следующем действии — перезагрузка не требуется.
commands.json — пользовательские shell-действия
[
{ "id": "reload_wm", "label": "Reload WM", "run": "hyprctl reload" },
{ "id": "deploy", "label": "Deploy site", "run": "cd /srv/app && ./deploy.sh",
"confirm": true, "timeout": 120 }
]
run может быть строкой (выполняется через bash -lc) или списком (argv, без shell). Они выполняются от вашего пользователя — это локальный доверенный файл.
modes.json — сценарные макросы
Каждый режим — упорядоченный список шагов, выполняемых через hyprctl:
launch {cmd, workspace, match?, once?}— открыть приложение на рабочем столе. С параметрамиmatch(класс окна) +onceне будет дублироваться и переместит окно, если оно попадет на неправильный рабочий стол (необходимо для приложений, таких как браузерные PWA, окно которых приходит от существующего процесса).close {match, workspaces?}— закрыть окна класса, опционально только на указанных рабочих столах.move {match, workspace}— переместить соответствующие окна на рабочий стол.focus {workspace}— переключить рабочий стол в активный вид (например, сбросить каждый монитор на его домашний рабочий стол).
Привязка рабочих столов Hyprland к мониторам
Размещение режимов детерминировано только в том случае, если ваши рабочие столы привязаны к мониторам. Добавьте привязки в конфигурацию Hyprland (подставьте свои номера рабочих столов и имена мониторов):
workspace = 1, monitor:DP-3, default:true
workspace = 2, monitor:DP-3
# … etc
Тема
Пульт использует фосфорно-терминальный стиль с четырьмя переключаемыми на лету цветовыми профилями: зеленый (по умолчанию), янтарный, ледяной и авто (выводит фосфорный оттенок из палитры обоев matugen, ~/.local/state/quickshell/user/generated/colors.json). Переключение на вкладке Система; выбор сохраняется в ~/.config/phone-deck/theme.json. app/theme.py отдает его как /theme.css. Здесь же настраиваются таймаут бездействия Ambient Cogitator и строка литургии.
Как работает стриминг
Двусторонний WebRTC через aiortc + PyAV, связанный с PipeWire/Hyprland через каналы подпроцессов (избегает хрупких привязок ввода-вывода устройств):
- ПК → телефон аудио: захват
.monitorстандартного выхода с помощьюparec→ Opus. - Телефон → ПК микрофон: прием микрофона телефона → нулевой приемник PipeWire, который мониторирующие приложения выбирают как вход («Monitor of PhoneDeckMic»).
- Только вывод на телефон: перенаправление воспроизведения ПК в виртуальный приемник, чтобы динамики замолчали, пока вы слушаете на телефоне; восстанавливается при остановке.
- ПК → телефон видео:
wf-recorderзахватывает монитор → aiortc, с предустановками качества (540p / 720p / 1080p), чтобы битрейт оставался разумным на нестабильных соединениях. - Сенсорный удаленный рабочий стол: нажатие/перетаскивание по стримированному видео → абсолютное позиционирование курсора (
hyprctl movecursor) + кликиuinput. Нажатие = клик, перетаскивание = движение, долгое нажатие = правый клик.
Ограничения: задержка ~150–300 мс; надежно только когда PWA активно (Android приостанавливает фоновые вкладки); используйте наушники, чтобы избежать эха; один стриминговый пир за раз; браузер не может захватывать аудио собственных приложений телефона (только микрофон).
Технологический стек
- Бэкенд: FastAPI + Uvicorn, WebSockets, aiortc/PyAV, python-evdev, psutil, PyJWT
- Фронтенд: HTML + Tailwind (CDN) + vanilla JS, PWA (манифест + сервис-воркер + wake-lock)
- Демон-помощник: только стандартная библиотека Python
- Транспорт: Tailscale (
tailscale serveдля HTTPS/WSS)
Тесты
uv run pytest
Покрывает разрешенный список deckd, JWT + блокировку входа, хеширование PIN, проверку редактора конфигураций, разбор режимов, логику качества стриминга/команд и раскладку ввода. Реальные пути WebRTC/PipeWire/Hyprland проверяются через петлю обратной связи во время разработки.
Структура проекта
app/ FastAPI web app
main.py routes + WebSockets
hypr.py Hyprland (snapshot, dispatch, event socket, cursor)
audio.py PipeWire device control + playerctl
audio_rtc.py WebRTC audio + screen video bridge
hid.py virtual keyboard/mouse via uinput
modes.py scene-macro engine
brightness.py ddcutil
grab.py screenshot + file upload
theme.py matugen → CSS
...
deckd/ root helper (stdlib only) + action allowlist
systemd/ service units
tests/ pytest suite
Лицензия
MIT — см. LICENSE (добавьте свой файл перед публикацией, если хотите другую лицензию).
Благодарности
Создано для Hyprland, PipeWire, Tailscale, aiortc и dotfiles end-4.