
Plataforma de control autoalojada y escritorio remoto para estaciones de trabajo Linux a través de Tailscale, con transmisión WebRTC, control por voz con LLM local, macros de escena, entrada virtual, transferencia de archivos y telemetría del sistema en vivo.
Convierte un teléfono Android de repuesto (o cualquier dispositivo con un navegador) en una consola de control autogestionada y escritorio remoto para tu estación de trabajo Linux, a través de tu red privada de Tailscale, sin aplicaciones de terceros.
Comenzó como "¿qué hago con un teléfono viejo?" y se convirtió en un plano de control para un rig Hyprland: control de ventanas/espacios de trabajo, macros de escena, un enrutador de voz push-to-talk con un asistente LLM local, controles contextuales dentro de las aplicaciones, transmisión bidireccional de audio + pantalla, un escritorio remoto táctil, un teclado/ratón virtual, transferencia de archivos + uso compartido teléfono→PC, y telemetría en vivo, todo servido como un PWA instalado con apariencia de terminal de fósforo.
Nota sobre portabilidad. Esto fue construido para una configuración específica: Linux basado en Arch (Garuda) + Hyprland + PipeWire + NVIDIA + Tailscale, con los dotfiles end-4 / illogical-impulse. La arquitectura es genérica, pero varias integraciones son específicas del entorno (nombres de monitores, enlaces de espacios de trabajo, ruta del tema matugen,
cpupower/nvidia-smi). Tómalo como una referencia funcional para adaptar, no como un paquete listo para usar.
La interfaz es una aplicación web oscura, con pestañas y apaisada (sincronizada con el tema de tu escritorio):
| Pestaña | Qué hace |
|---|
| Workspaces | Cuadrícula de espacios de trabajo en vivo por monitor (toca para cambiar), lista de ventanas en vivo (toca para enfocar), alternancia DPMS por monitor + brillo con ddcutil |
| Remote | Trackpad en pantalla (arrastrar/tocar/dos dedos) + teclado virtual con modificadores y combinaciones, a través de un dispositivo uinput del kernel |
| Modes | Macros de escena con un toque: lanzar/cerrar/organizar diseños completos de aplicaciones en todos los monitores (p. ej., "Trabajo" / "Ocio") |
| Voice | Enrutador de voz push-to-talk (faster-whisper, CPU): canales de palabra clave — macro (ejecutar un modo/comando), type (dictado), input (frase → combinación de teclas) — con confirmar antes de ejecutar, además de "friday", un respondedor LLM local de solo lectura (Ollama + Qwen) con búsqueda web autogestionada y conocimiento del estado del sistema en vivo |
| Audio | Silenciar micrófono/altavoz, volumen, selectores de dispositivos de salida y entrada, transporte playerctl + carátula (sigue el reproductor MPRIS activo) |
| Stream | Audio WebRTC bidireccional (PC↔teléfono, con el teléfono como micrófono y salida solo para el teléfono) + vídeo de pantalla, y toca para controlar la pantalla transmitida = un verdadero escritorio remoto |
| System | Alternancia del modo de rendimiento, cambio de tema en vivo, estado de Tailscale, procesos principales (toca para matar), bloqueo, reinicio de NetworkManager, suspender/reiniciar/apagar |
| Files | Captura de pantalla de un monitor → ver/descargar en el teléfono; soltar un archivo del teléfono al PC |
| Config | Editar los archivos de configuración commands.json / modes.json (y contexto/voz) desde el teléfono (validación JSON) |
Siempre visible, sobre las pestañas: una barra de contexto que muestra lo que está sucediendo ahora mismo: una llamada activa (silenciar / saltar a ella), multimedia en reproducción + transporte, y controles de teclado en la aplicación para la aplicación enfocada (YouTube / Brave / Teams), entregados sin robar el foco.
Dos más, fuera de las pestañas:
Además: inicio de sesión con PIN de teclado numérico en pantalla, bloqueo de pantalla, PWA a pantalla completa apaisada, y bloqueo por fuerza bruta en el inicio de sesión.
teléfono / portátil (navegador PWA)
│ HTTPS + WSS (solo Tailscale)
▼
tailscale serve ──► Aplicación web FastAPI ── Socket Unix ──► deckd
(certificado TLS real) (se ejecuta como tu usuario) (nombres de acción) (se ejecuta como root)
│ │
hyprctl · pactl · ddcutil · grim · lista blanca fija de
wf-recorder · uinput · WebRTC comandos privilegiados
(cpupower, nvidia-smi,
systemctl, …)
Dos procesos:
app/ — la aplicación web se ejecuta como tu usuario normal. Hace todo lo que no necesita root: control de Hyprland, audio, brillo, captura de pantalla, entrada virtual, transmisión WebRTC, transferencia de archivos.deckd/ — un pequeño ayudante root (solo stdlib, sin dependencias) para las pocas acciones privilegiadas. La aplicación web nunca le envía cadenas de shell, solo nombres de acción de una lista blanca fija (governor_performance, gpu_power_limit, suspend, …), validados en deckd/actions.py antes de ejecutar cualquier cosa. Incluso si la aplicación web estuviera completamente comprometida, el radio de explosión son exactamente las acciones de la lista blanca, sin inyección de argumentos. El socket es root:<grupo> modo 0660.tailscale serve lo expone en tu nombre MagicDNS con un certificado real de Let's Encrypt; nunca se vincula a 0.0.0.0.La aplicación web puede ejecutar tus comandos de shell configurados e inyectar entrada como tu usuario — es, por diseño, un control remoto para tu máquina. Mantenla en tu tailnet, detrás del PIN, y no la expongas públicamente.
pactl/PulseAudio)hyprctl, pactl / pw-record / pw-play, playerctl, ddcutil,
wf-recorder, grim, cpupower, nvidia-smi, kitty (o tu terminal)input (para /dev/uinput) y i2c (para ddcutil)aiortc + PyAV (instalado vía uv) para transmisión de audio/vídeogit clone <tu-url-del-repositorio> phone-deck
cd phone-deck
uv sync # crea .venv e instala dependencias
uv run python -m app.set_pin # establece tu PIN de desbloqueo
deckd)Primero edita systemd/deckd.service — establece las rutas y DECK_SOCKET_GROUP a un grupo al que pertenezca tu usuario (normalmente tu grupo principal):
sudo cp systemd/deckd.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now deckd
deckd ejecuta el Python del sistema (solo stdlib — no necesita venv).
Ejecútala directamente para desarrollo:
uv run uvicorn app.main:app --host 127.0.0.1 --port 8765
…o como un servicio de usuario. Primero edita las rutas de systemd/phone-deck.service, luego instálalo.
Importante: en una configuración donde Hyprland no se inicia a través de uwsm, el graphical-session.target de systemd nunca se activa, por lo que la unidad de usuario no se iniciará automáticamente al iniciar sesión. La solución confiable es iniciarlo desde Hyprland con el entorno de sesión importado — agrega a tu inicio automático de Hyprland (p. ej., ~/.config/hypr/custom/execs.conf de 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
Habilita los certificados una vez: Consola de administración de Tailscale → DNS → Enable HTTPS Certificates.
Pon la aplicación detrás de un certificado real:
sudo tailscale serve --bg 127.0.0.1:8765
Establece DECK_SECURE_COOKIES=1 en el entorno de la aplicación web una vez que HTTPS esté activo.
Abre https://<tu-host>.<tu-tailnet>.ts.net/ en Chrome en el teléfono (debe estar en tu tailnet), inicia sesión, luego ⋮ → Instalar aplicación. Se inicia a pantalla completa, bloqueado en apaisado, con la pantalla activa. HTTPS es necesario para el service worker del PWA + wake-lock.
Funciona desde cualquier dispositivo del tailnet, incluidos portátiles (las superficies táctiles usan Pointer Events, por lo que un ratón también funciona). La transmisión WebRTC es de un solo par (un visor a la vez).
Toda la configuración en tiempo de ejecución reside en ~/.config/phone-deck/ y se puede editar desde la pestaña Config (validación JSON, guarda un .bak). Los cambios surten efecto en la siguiente acción — sin necesidad de reiniciar.
commands.json — acciones de shell personalizadas[
{ "id": "reload_wm", "label": "Recargar WM", "run": "hyprctl reload" },
{ "id": "deploy", "label": "Desplegar sitio", "run": "cd /srv/app && ./deploy.sh",
"confirm": true, "timeout": 120 }
]
run puede ser una cadena (ejecutada vía bash -lc) o una lista (argv, sin shell). Se ejecutan como tu usuario — es un archivo local de confianza.
modes.json — macros de escenaCada modo es una lista ordenada de pasos ejecutados vía hyprctl:
launch {cmd, workspace, match?, once?} — abre una aplicación en un espacio de trabajo. Con match (clase de ventana) + once, no se duplica, y reubica la ventana si esta cae en el espacio de trabajo incorrecto (necesario para aplicaciones como PWAs del navegador cuya ventana proviene de un proceso existente).close {match, workspaces?} — cierra ventanas de una clase, opcionalmente solo en los espacios de trabajo listados.move {match, workspace} — mueve las ventanas coincidentes a un espacio de trabajo.focus {workspace} — cambia un espacio de trabajo a la vista (p. ej., restablecer cada monitor a su espacio de trabajo principal).La ubicación del modo es determinista solo si tus espacios de trabajo están vinculados a monitores. Agrega enlaces a tu configuración de Hyprland (ajusta los números de espacio de trabajo y nombres de monitor a los tuyos):
workspace = 1, monitor:DP-3, default:true
workspace = 2, monitor:DP-3
# … etc
La consola tiene una apariencia de terminal de fósforo con cuatro perfiles de color intercambiables en vivo: verde (predeterminado), ámbar, hielo y auto (deriva el tono de fósforo de la paleta de fondos de pantalla de matugen, ~/.local/state/quickshell/user/generated/colors.json). Cámbialo en la pestaña System; la elección persiste en ~/.config/phone-deck/theme.json. app/theme.py lo sirve como /theme.css. El tiempo de espera de inactividad del Ambient Cogitator y la línea de liturgia también se pueden configurar aquí.
WebRTC bidireccional a través de aiortc + PyAV, conectado a PipeWire/Hyprland con tuberías de subprocesos (evita enlaces frágiles de dispositivos de E/S):
.monitor del sink predeterminado con parec → Opus.wf-recorder captura un monitor → aiortc, con preajustes de calidad (540p / 720p / 1080p) para mantener la tasa de bits razonable en enlaces inestables.hyprctl movecursor) + clics uinput. Tocar = clic, arrastrar = mover, pulsación larga = clic derecho.Limitaciones: latencia de ~150–300 ms; confiable solo mientras el PWA esté en primer plano (Android suspende las pestañas en segundo plano); usa auriculares para evitar eco; un solo par de transmisión a la vez; un navegador no puede capturar el audio de la propia aplicación del teléfono (solo micrófono).
tailscale serve para HTTPS/WSS)uv run pytest
Cubre la lista blanca de deckd, JWT + bloqueo de inicio de sesión, hashing de PIN, validación del editor de configuración, análisis de modos, calidad de transmisión/lógica de comandos y el mapa de teclas de entrada. Las rutas en vivo de WebRTC/PipeWire/Hyprland se validan mediante bucle de retroceso durante el desarrollo.
app/ Aplicación web FastAPI
main.py rutas + WebSockets
hypr.py Hyprland (instantánea, dispatch, socket de eventos, cursor)
audio.py Control de dispositivos PipeWire + playerctl
audio_rtc.py Puente WebRTC de audio + vídeo de pantalla
hid.py teclado/ratón virtual vía uinput
modes.py motor de macros de escena
brightness.py ddcutil
grab.py captura de pantalla + subida de archivos
theme.py matugen → CSS
...
deckd/ ayudante root (solo stdlib) + lista blanca de acciones
systemd/ unidades de servicio
tests/ suite de pruebas
MIT — consulta LICENSE (añade uno antes de publicar si deseas una licencia diferente).
Construido para Hyprland, PipeWire, Tailscale, aiortc y los dotfiles de end-4.