
phone-deck v3.0.1
Selbst gehostetes Steuerungsdeck und Remote-Desktop für Linux-Workstations über Tailscale, mit WebRTC-Streaming, Sprachsteuerung mit lokalem LLM, Szenen-Makros, virtueller Eingabe, Dateiübertragung und Live-Systemtelemetrie.
phone-deck
Verwandle ein altes Android-Handy (oder jedes Gerät mit einem Browser) in eine selbst gehostete Steuerkonsole und Remote-Desktop für Ihren Linux-Workstation – über Ihr privates Tailscale-Netzwerk, ohne Apps von Drittanbietern.
Es begann als „Was mache ich mit einem alten Telefon?“ und wurde zu einer Kontrollebene für eine Hyprland-Maschine: Fenster-/Workspace-Steuerung, Szenenmakros, ein Push-to-Talk-Sprachrouter mit einem lokalen LLM-Assistenten, kontextbezogene In-App-Steuerung, bidirektionales Audio- + Bildschirm-Streaming, ein Touch-Remote-Desktop, eine virtuelle Tastatur/Maus, Dateitransfer + Telefon→PC-Freigabe und Live-Telemetrie – alles bereitgestellt als installierte PWA mit einem Phosphor-Terminal-Look.
Hinweis zur Portabilität. Dies wurde für ein bestimmtes Setup entwickelt: Arch-basiertes Linux (Garuda) + Hyprland + PipeWire + NVIDIA + Tailscale, mit den end-4 / illogical-impulse Dotfiles. Die Architektur ist generisch, aber mehrere Integrationen sind umgebungsspezifisch (Monitor-Namen, Workspace-Bindungen, der matugen-Theme-Pfad,
cpupower/nvidia-smi). Behandeln Sie es als funktionierende Referenz zur Anpassung, nicht als schlüsselfertiges Paket.
Funktionen
Die Benutzeroberfläche ist eine geteilte, dunkle, landschaftsorientierte Web-App (Thema synchronisiert mit Ihrem Desktop):
| Tab | Funktion |
|---|---|
| Workspaces | Live-Workspace-Raster pro Monitor (tippen zum Wechseln), Live-Fensterliste (tippen zum Fokussieren), DPMS-Umschaltung pro Monitor + ddcutil Helligkeit |
| Remote | Bildschirm-Trackpad (Ziehen/Tippen/Zwei-Finger) + virtuelle Tastatur mit Modifikatoren und Tastenkombinationen über ein Kernel-uinput-Gerät |
| Modes | One-Tap-Szenenmakros – ganze App-Layouts über Monitore starten/schließen/anordnen (z.B. „Arbeit“/„Frei“) |
| Voice | Push-to-Talk-Sprachrouter (faster-whisper, CPU): Lead-Word-Lanes – macro (führt einen Modus/Befehl aus), type (Diktieren), input (Phrase → Tastenkombination) – mit Bestätigung vor Ausführung, plus „friday“, einem schreibgeschützten lokalen LLM-Antworter (Ollama + Qwen) mit selbst gehosteter Websuche und Live-Systemstatus-Erfassung |
| Audio | Mikrofon/Lautsprecher-Stummschaltung, Lautstärke, Ausgabe- und Eingabegeräte-Auswahl, playerctl-Transport + Cover (verfolgt den aktiven MPRIS-Player) |
| Stream | Bidirektionales WebRTC-Audio (PC↔Telefon, mit Telefon-als-Mikrofon und Telefon-nur-Ausgabe) + Bildschirm-Video, und tippen zur Steuerung des gestreamten Bildschirms = ein echter Remote-Desktop |
| System | Leistungsmodus-Umschaltung, Live-Theme-Wechsel, Tailscale-Status, Top-Prozesse (tippen zum Beenden), Sperren, NetworkManager-Neustart, Aussetzen/Neustart/Ausschalten |
| Files | Screenshot eines Monitors → auf dem Telefon anzeigen/herunterladen; Datei Telefon→PC ablegen |
| Config | Bearbeiten der commands.json/modes.json (und Kontext/Sprach)-Konfiguration vom Telefon aus (JSON-validiert) |
Immer sichtbar, über den Registerkarten – ein Kontextstreifen, der anzeigt, was gerade passiert: ein aktiver Anruf (Stummschaltung / zu ihm springen), aktuell abgespielte Medien + Transport, und In-App-Tastatursteuerung für die fokussierte App (YouTube / Brave / Teams), geliefert ohne die Fokus zu stehlen.
Zwei weitere, außerhalb der Registerkarten:
- Send-to-Rig — die PWA registriert sich als Android- Freigabeziel: Teilen Sie ein Bild, etwas Text oder einen Link aus einer beliebigen App auf Ihrem Telefon und es landet auf dem Rechner – Bilder werden in einem Ablageverzeichnis gespeichert und in die Zwischenablage als einfügebereites PNG gelegt, Text in die Zwischenablage, ein nackter Link wird geöffnet.
- Ambient Cogitator — nach einigen Minuten Inaktivität wird das Deck zu einem Phosphor-Instrumentenpanel: Zeiger-Telemetrie mit Spitzenhaltung („War es während meiner Abwesenheit ausgelastet?“), Uhr, Now-Playing.
Plus: Bildschirm-Numpad-PIN-Anmeldung, Bildschirm-Wake-Lock, Vollbild-Querformat-PWA und Brute-Force-Aus sperren bei der Anmeldung.
Architektur
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, …)
Zwei Prozesse:
app/— die Web-App läuft als Ihr normaler Benutzer. Sie erledigt alles, was kein Root benötigt: Hyprland-Steuerung, Audio, Helligkeit, Bildschirmaufnahme, virtuelle Eingabe, WebRTC-Streaming, Dateitransfer.deckd/— ein kleiner Root-Helfer (nur stdlib, keine Abhängigkeiten) für die wenigen privilegierten Aktionen. Die Web-App sendet ihm niemals Shell-Zeichenketten – nur Aktionsnamen aus einer festen Allowlist (governor_performance,gpu_power_limit,suspend, …), validiert indeckd/actions.py, bevor etwas ausgeführt wird. Selbst wenn die Web-App vollständig kompromittiert wäre, ist die Schadenszone genau auf die allowlistierten Aktionen beschränkt, ohne Argument-Einschleusung. Der Socket istroot:<Gruppe>-Modus0660.
Sicherheitsmodell
- Erreichbar nur innerhalb Ihres Tailnets —
tailscale servemacht es unter Ihrem MagicDNS-Namen mit einem echten Let's-Encrypt-Zertifikat zugänglich; es bindet niemals an0.0.0.0. - JWT-Anmeldung (PIN → signiertes Cookie), mit exponentiellem Backoff-Lockout nach wiederholten Fehlversuchen.
- Privilegierte Aktionen sind hinter dem allowlistierten Helfer-Daemon isoliert.
- Uploads werden mit Basename-Sanierung versehen (können das Ablageverzeichnis nicht verlassen).
Die Web-App kann Ihre konfigurierten Shell-Befehle ausführen und als Ihr Benutzer Eingaben senden – es ist, von Natur aus, eine Fernsteuerung für Ihren Rechner. Halten Sie sie in Ihrem Tailnet, hinter der PIN, und setzen Sie sie nicht öffentlich aus.
Voraussetzungen
- Linux mit Hyprland (wlroots), PipeWire (mit
pactl/PulseAudio-Kompatibilität) - Python ≥ 3.11 und uv
- Tailscale (mit HTTPS-Zertifikaten für Ihr Tailnet aktiviert)
- CLI-Tools, die von verschiedenen Funktionen verwendet werden (installieren Sie, was Sie nutzen möchten):
hyprctl,pactl/pw-record/pw-play,playerctl,ddcutil,wf-recorder,grim,cpupower,nvidia-smi,kitty(oder Ihr Terminal) - Ihr Benutzer in der
input-Gruppe (für/dev/uinput) und deri2c-Gruppe (fürddcutil) aiortc+ PyAV (installiert überuv) für Audio/Video-Streaming
Installation
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. Der Root-Helfer (deckd)
Bearbeiten Sie zuerst systemd/deckd.service – setzen Sie die Pfade und DECK_SOCKET_GROUP auf eine Gruppe, zu der Ihr Benutzer gehört (normalerweise Ihre primäre Gruppe):
sudo cp systemd/deckd.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now deckd
deckd verwendet das System-Python (nur stdlib – kein venv erforderlich).
2. Die Web-App
Starten Sie sie direkt für die Entwicklung:
uv run uvicorn app.main:app --host 127.0.0.1 --port 8765
…oder als Benutzer-Service. Bearbeiten Sie zuerst die Pfade in systemd/phone-deck.service, dann installieren Sie es.
Wichtig: In einem Setup, in dem Hyprland nicht über uwsm gestartet wird, wird graphical-session.target nie aktiviert, sodass die Benutzer-Einheit beim Login nicht automatisch startet. Die zuverlässige Lösung ist, sie aus Hyprland mit den Sitzungsumgebungsvariablen zu starten – fügen Sie zu Ihrem Hyprland-Autostart hinzu (z.B. end-4's ~/.config/hypr/custom/execs.conf):
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 über Tailscale
-
Aktivieren Sie Zertifikate einmalig: Tailscale Admin-Konsole → DNS → Enable HTTPS Certificates.
-
Stellen Sie die App mit einem echten Zertifikat bereit:
sudo tailscale serve --bg 127.0.0.1:8765 -
Setzen Sie
DECK_SECURE_COOKIES=1in der Umgebung der Web-App, sobald HTTPS aktiv ist.
4. Auf dem Telefon installieren
Öffnen Sie https://<Ihr-Name>.<Ihr-Tailnet>.ts.net/ in Chrome auf dem Telefon (es muss in Ihrem Tailnet sein), melden Sie sich an, dann ⋮ → Install app. Es startet im Vollbild, Querformat erzwungen, mit Bildschirm-Wachhaltung. HTTPS ist für den PWA-Service-Worker + Wake-Lock erforderlich.
Funktioniert von jedem Tailnet-Gerät – Laptops inbegriffen (die Touch-Oberflächen verwenden Pointer Events, also funktioniert auch eine Maus). Der WebRTC-Stream ist Single-Peer (ein Betrachter gleichzeitig).
Konfiguration
Die gesamte Laufzeitkonfiguration befindet sich in ~/.config/phone-deck/ und kann vom Config-Tab aus bearbeitet werden (JSON-validiert, behält eine .bak). Änderungen werden bei der nächsten Aktion wirksam – kein Neustart erforderlich.
commands.json — benutzerdefinierte Shell-Aktionen
[
{ "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 kann ein String (ausgeführt über bash -lc) oder eine Liste sein (argv, kein Shell). Diese werden als Ihr Benutzer ausgeführt – es ist eine lokale, vertrauenswürdige Datei.
modes.json — Szenenmakros
Jeder Modus ist eine geordnete Liste von Schritten, die über hyprctl ausgeführt werden:
launch {cmd, workspace, match?, once?}— eine App auf einem Workspace öffnen. Mitmatch(Fensterklasse) +oncewird keine Dublette erstellt und das Fenster wird auf den richtigen Workspace verschoben, falls es auf dem falschen landet (nötig für Apps wie Browser-PWAs, deren Fenster von einem bestehenden Prozess stammen).close {match, workspaces?}— Fenster einer Klasse schließen, optional nur auf aufgelisteten Workspaces.move {match, workspace}— passende Fenster auf einen Workspace verschieben.focus {workspace}— einen Workspace in die Ansicht schalten (z.B. jeden Monitor auf seinen Start-Workspace zurücksetzen).
Hyprland-Workspace → Monitor-Bindung
Die Platzierung von Modi ist nur deterministisch, wenn Ihre Workspaces an Monitore gebunden sind. Fügen Sie Bindungen zu Ihrer Hyprland-Konfiguration hinzu (passen Sie Workspace-Nummern und Monitornamen an Ihre an):
workspace = 1, monitor:DP-3, default:true
workspace = 2, monitor:DP-3
# … usw.
Theme
Das Deck trägt ein Phosphor-Terminal-Erscheinungsbild mit vier live umschaltbaren Farbprofilen – grün (Standard), bernstein, eis und auto (leitet den Phosphor-Farbton aus Ihrer matugen-Hintergrundpalette ab, ~/.local/state/quickshell/user/generated/colors.json). Umschalten im System-Tab; die Wahl bleibt in ~/.config/phone-deck/theme.json erhalten.
app/theme.py stellt es als /theme.css bereit. Der Leerlauf-Timeout des Ambient Cogitator und die Liturgie-Zeile sind ebenfalls hier konfigurierbar.
Wie das Streaming funktioniert
Bidirektionales WebRTC über aiortc + PyAV, überbrückt zu PipeWire/Hyprland mit Subprocess-Pipes (vermeidet anfällige Geräte-I/O-Bindungen):
- PC → Telefon Audio: Den
.monitordes Standard-Sink mitparecaufnehmen → Opus. - Telefon → PC Mikrofon: Das Telefon-Mikrofon empfangen → einen PipeWire-Null-Sink, den Überwachungs-Apps als Eingang auswählen können („Monitor of PhoneDeckMic“).
- Nur-Telefon-Ausgabe: Die PC-Wiedergabe in einen virtuellen Sink umleiten, damit die Lautsprecher stumm bleiben, während Sie über das Telefon hören; bei Stop wiederhergestellt.
- PC → Telefon Video:
wf-recordernimmt einen Monitor auf → aiortc, mit Qualitätsvoreinstellungen (540p / 720p / 1080p), um die Bitrate über unzuverlässigen Verbindungen im Rahmen zu halten. - Touch-Remote-Desktop: Tippen/Ziehen auf dem gestreamten Video → absolute Cursorpositionierung (
hyprctl movecursor) +uinput-Klicks. Tippen = Klick, Ziehen = Bewegen, langes Drücken = Rechtsklick.
Einschränkungen: ~150–300 ms Latenz; zuverlässig nur, während die PWA im Vordergrund ist (Android setzt Hintergrund-Tabs aus); Kopfhörer verwenden, um Echo zu vermeiden; ein Streaming-Peer gleichzeitig; ein Browser kann die eigenen App-Audio des Telefons nicht aufnehmen (nur Mikrofon).
Technologie-Stack
- Backend: FastAPI + Uvicorn, WebSockets, aiortc/PyAV, python-evdev, psutil, PyJWT
- Frontend: HTML + Tailwind (CDN) + vanilla JS, PWA (Manifest + Service Worker + Wake-Lock)
- Helfer-Daemon: Nur Python-Standardbibliothek
- Transport: Tailscale (
tailscale servefür HTTPS/WSS)
Tests
uv run pytest
Deckt die deckd-Allowlist, JWT + Login-Lockout, PIN-Hashing, Konfigurationseditor-Validierung, Modi-Parsing, die Streaming-Qualitäts-/Befehlslogik und das Eingabe-Keymap ab. Die Live-WebRTC-/PipeWire-/Hyprland-Pfade werden während der Entwicklung per Loopback validiert.
Projektstruktur
app/ FastAPI-Web-App
main.py Routen + WebSockets
hypr.py Hyprland (Snapshot, Dispatch, Ereignis-Socket, Cursor)
audio.py PipeWire-Gerätesteuerung + playerctl
audio_rtc.py WebRTC-Audio + Bildschirm-Video-Brücke
hid.py Virtuelle Tastatur/Maus über uinput
modes.py Szenen-Makro-Engine
brightness.py ddcutil
grab.py Screenshot + Datei-Upload
theme.py matugen → CSS
...
deckd/ Root-Helfer (nur stdlib) + Aktions-Allowlist
systemd/ Service-Einheiten
tests/ pytest-Suite
Lizenz
MIT — siehe LICENSE (vor der Veröffentlichung hinzufügen, falls Sie eine andere Lizenz wünschen).
Danksagungen
Erstellt für Hyprland, PipeWire, Tailscale, aiortc und die end-4 Dotfiles.