
Plataforma de controle auto-hospedada e área de trabalho remota para estações de trabalho Linux via Tailscale, com streaming WebRTC, controle por voz com LLM local, macros de cena, entrada virtual, transferência de arquivos e telemetria de sistema ao vivo.
Transforme um telefone Android sobressalente (ou qualquer dispositivo com navegador) em um painel de controle auto-hospedado e área de trabalho remota para sua estação de trabalho Linux — através de sua rede Tailscale privada, sem aplicativos de terceiros.
Começou como "o que fazer com um telefone antigo?" e se tornou um plano de controle para um rig Hyprland: controle de janela/workspace, macros de cena, um roteador de voz push-to-talk com assistente LLM local, controles contextuais no aplicativo, áudio bidirecional + streaming de tela, uma área de trabalho remota por toque, um teclado/mouse virtual, transferência de arquivos + compartilhamento telefone→PC, e telemetria ao vivo — tudo servido como um PWA instalado com aparência de terminal fósforo.
Nota sobre portabilidade. Isto foi construído para uma configuração específica: Linux baseado em Arch (Garuda) + Hyprland + PipeWire + NVIDIA + Tailscale, com os dotfiles end-4 / illogical-impulse. A arquitetura é genérica, mas várias integrações são específicas do ambiente (nomes de monitores, vinculações de workspaces, o caminho do tema matugen,
cpupower/nvidia-smi). Trate como uma referência funcional para adaptar, não um pacote chave na mão.
A interface é um aplicativo web com abas, escuro, em modo paisagem (sincronizado com o tema do seu desktop):
| Aba | O que faz |
|---|---|
| Workspaces | Grade de workspaces ao vivo por monitor (toque para alternar), lista de janelas ao vivo (toque para focar), alternância DPMS por monitor + brilho ddcutil |
| Remote | Touchpad na tela (arrastar/tocar/dois dedos) + teclado virtual com modificadores e combinações, via dispositivo uinput do kernel |
| Modes | Macros de cena com um toque — iniciar/fechar/organizar layouts completos de aplicativos entre monitores (ex: 'Trabalho' / 'Lazer') |
| Voice | Roteador de voz push-to-talk (faster-whisper, CPU): faixas de palavra-chave — macro (executar um modo/comando), type (ditar), input (frase → acorde de teclas) — com confirmar-antes-executar, mais "friday", um respondedor LLM local somente leitura (Ollama + Qwen) com pesquisa web auto-hospedada e consciência de estado do sistema ao vivo |
| Audio | Mudo de microfone/auto-falante, volume, seletores de dispositivo de saída e entrada, transporte playerctl + capa do álbum (rastreia o player MPRIS ativo) |
| Stream | Áudio WebRTC bidirecional (PC↔telefone, com telefone como microfone e saída apenas no telefone) + vídeo da tela, e toque para controlar a tela transmitida = uma verdadeira área de trabalho remota |
| System | Alternância de modo de desempenho, troca de tema ao vivo, status do Tailscale, principais processos (toque para matar), bloqueio, reiniciar NetworkManager, suspender/reiniciar/desligar |
| Files | Captura de tela de um monitor → visualizar/baixar no telefone; soltar um arquivo telefone→PC |
| Config | Editar as configurações commands.json / modes.json (e contexto/voz) do telefone (validado JSON) |
Sempre ativa, acima das abas — uma faixa de contexto que mostra o que está acontecendo agora: uma chamada ativa (mudo / pule para ela), mídia reproduzindo agora + transporte, e controles de teclado no aplicativo para o aplicativo focado (YouTube / Brave / Teams), entregues sem roubar o foco.
Dois recursos adicionais, além das abas:
Além disso: login com PIN numérico na tela, wake-lock de tela, PWA em tela cheia paisagem e bloqueio de força bruta no login.
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, …)
Dois processos:
app/ — o aplicativo web é executado como seu usuário normal. Ele faz tudo que não precisa de root: controle do Hyprland, áudio, brilho, captura de tela, entrada virtual, streaming WebRTC, transferência de arquivos.deckd/ — um pequeno helper root (apenas stdlib, sem dependências) para as poucas ações privilegiadas. O aplicativo web nunca envia strings de shell para ele — apenas nomes de ações de uma lista de permissões fixa (governor_performance, gpu_power_limit, suspend, …), validados em deckd/actions.py antes de qualquer execução. Mesmo que o aplicativo web seja totalmente comprometido, o raio de explosão é exatamente as ações permitidas, sem injeção de argumentos. O socket é root:<group> modo 0660.tailscale serve expõe no seu nome MagicDNS com um certificado Let's Encrypt real; nunca liga em 0.0.0.0.Nota: O aplicativo web pode executar seus comandos de shell configurados e injetar entrada como seu usuário — é, por design, um controle remoto para sua máquina. Mantenha-o em sua tailnet, atrás do PIN, e não o exponha publicamente.
pactl/PulseAudio)hyprctl, pactl / pw-record / pw-play, playerctl, ddcutil,
wf-recorder, grim, cpupower, nvidia-smi, kitty (ou seu terminal)input (para ) e (para )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
deckd)Edite systemd/deckd.service primeiro — defina os caminhos e DECK_SOCKET_GROUP para um grupo ao qual seu usuário pertence (geralmente seu grupo primário):
sudo cp systemd/deckd.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now deckd
deckd é executado com o Python do sistema (apenas stdlib — sem necessidade de venv).
Execute diretamente para desenvolvimento:
uv run uvicorn app.main:app --host 127.0.0.1 --port 8765
…ou como um serviço de usuário. Edite os caminhos de systemd/phone-deck.service primeiro, depois instale-o.
Importante: em uma configuração onde o Hyprland não é iniciado via uwsm, o target graphical-session.target do systemd nunca é ativado, então a unit de usuário não será iniciada automaticamente no login. A correção confiável é iniciá-lo a partir do Hyprland com o ambiente de sessão importado — adicione ao autostart do Hyprland (ex: ~/.config/hypr/custom/execs.conf do 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
Habilite os certificados uma vez: Console de administração Tailscale → DNS → Enable HTTPS Certificates.
Coloque o aplicativo atrás de um certificado real:
sudo tailscale serve --bg 127.0.0.1:8765
Defina DECK_SECURE_COOKIES=1 no ambiente do aplicativo web assim que HTTPS estiver ativo.
Abra https://<seu-host>.<sua-tailnet>.ts.net/ no Chrome no telefone (ele deve estar na sua tailnet), faça login, então ⋮ → Install app. Ele inicia em tela cheia, bloqueado em modo paisagem, com a tela mantida ativa. HTTPS é necessário para o service worker do PWA + wake-lock.
Funciona de qualquer dispositivo na tailnet — laptops incluídos (as superfícies de toque usam Pointer Events, então um mouse também funciona). O stream WebRTC é de par único (um visualizador por vez).
Toda configuração em tempo de execução reside em ~/.config/phone-deck/ e é editável na aba Config (validado JSON, mantém um .bak). As alterações entram em vigor na próxima ação — sem reinicialização.
commands.json — ações de shell personalizadas[
{ "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 pode ser uma string (executada via bash -lc) ou uma lista (argv, sem shell). Estes são executados como seu usuário — é um arquivo local e confiável.
modes.json — macros de cenaCada modo é uma lista ordenada de etapas executadas via hyprctl:
launch {cmd, workspace, match?, once?} — abre um aplicativo em um workspace. Com match (classe da janela) + once, não duplica, e realoca a janela se ela cair no workspace errado (necessário para aplicativos como PWAs de navegador cuja janela vem de um processo existente).close {match, workspaces?} — fecha janelas de uma classe, opcionalmente apenas nos workspaces listados.move {match, workspace} — move janelas correspondentes para um workspace.focus {workspace} — alterna um workspace para visualização (ex: redefinir cada monitor para seu workspace inicial).O posicionamento do modo é determinístico apenas se seus workspaces estiverem fixados em monitores. Adicione vinculações à sua configuração do Hyprland (ajuste números de workspace e nomes de monitores para os seus):
workspace = 1, monitor:DP-3, default:true
workspace = 2, monitor:DP-3
# … etc
O deck usa uma skin phosphor-terminal com quatro perfis de cor alternáveis ao vivo — green (padrão), amber, ice e auto (deriva o matiz phosphor da paleta de wallpapers do matugen, ~/.local/state/quickshell/user/generated/colors.json). Alterne na aba System; a escolha persiste em ~/.config/phone-deck/theme.json. app/theme.py o serve como /theme.css. O tempo limite de inatividade do Ambient Cogitator e a linha de liturgia também são configuráveis aqui.
WebRTC bidirecional via aiortc + PyAV, conectado ao PipeWire/Hyprland com pipes de subprocesso (evita associações frágeis de E/S de dispositivo):
.monitor do sink padrão com parec → Opus.wf-recorder captura um monitor → aiortc, com predefinições de qualidade (540p / 720p / 1080p) para manter a taxa de bits razoável em links instáveis.hyprctl movecursor) + cliques uinput. Toque = clique, arrastar = mover, toque longo = clique direito.Restrições: latência de ~150–300 ms; confiável apenas enquanto o PWA está em primeiro plano (Android suspende abas em segundo plano); use fones de ouvido para evitar eco; um par de streaming por vez; um navegador não pode capturar o áudio do próprio aplicativo do telefone (apenas microfone).
tailscale serve para HTTPS/WSS)uv run pytest
Cobre a lista de permissões do deckd, JWT + bloqueio de login, hash de PIN, validação do editor de configuração, análise de modos, lógica de qualidade/comando de streaming e o mapa de teclado de entrada. Os caminhos ao vivo WebRTC/PipeWire/Hyprland são validados por loopback durante o desenvolvimento.
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 — veja LICENSE (adicione um antes de publicar se quiser uma licença diferente).
Construído para Hyprland, PipeWire, Tailscale, aiortc e os dotfiles do end-4.
/dev/uinputi2cddcutilaiortc + PyAV (instalado via uv) para streaming de áudio/vídeo