Phase 0: Repo-Gerüst, Dokumentation und Machbarkeits-Spike

Vier Beweise, die das Projekt tragen, als eigenständig lauffähige Skripte:

  S1  Plasma Mobile in proot über genestetes kwin_wayland im X11-Backend.
      Fällt selbsttätig auf startplasmamobile und dann auf XFCE zurück, damit
      ein Fehlschlag eindeutig Plasma zuzuordnen ist und nicht der Umgebung.
  S2  Mikrofon über PulseAudio mit OpenSL-Quelle. Misst den Pegel statt nur die
      Dateigröße — der typische Fehlschlag ist eine formal korrekte WAV-Datei
      voller Stille.
  S3  Virtuelle PipeWire-Kamera per GStreamer-pipewiresink, ohne Kernel-Modul.
      Setzt die Firefox-Einstellung vorab, damit der Test nicht an einem
      vergessenen Häkchen scheitert.
  S4  Intent-Handoff mit lauffähiger Mini-Bridge: xdg-open-Handler für geo:,
      tel: und sms:, Token-Authentifizierung, feste Liste erlaubter Schemata.

provision/provision.sh setzt die Hintergrundbeschränkungen per ADB und benennt,
was ADB nicht kann und von Hand erledigt werden muss.

Dokumentiert sind auch die sieben verworfenen Wege — Waydroid, AVF, QEMU, SIP,
APK-Daten-Redirect, Fenster-Durchreichung und der Android-Server — mit Belegen,
damit sie nicht erneut durchdacht werden müssen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan
2026-08-09 17:25:36 +02:00
co-authored by Claude Opus 5
commit ad2c98443b
21 changed files with 2818 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
#!/bin/bash
# Läuft INNERHALB des Debian-Rootfs, aufgerufen von 01-s1-plasma-mobile.sh.
# Direkt aufrufen ergibt keinen Sinn — die Umgebung kommt vom äußeren Skript.
set -uo pipefail
GPU_PROFIL="${HPOS_GPU_PROFIL:-virgl}"
BREITE="${HPOS_BREITE:-1080}"
HOEHE="${HPOS_HOEHE:-2340}"
echo "── Debian-Seite: Plasma Mobile ──"
export DEBIAN_FRONTEND=noninteractive
export DISPLAY=:0
export XDG_RUNTIME_DIR=/tmp/hpos-runtime
export PULSE_SERVER=127.0.0.1
export LANG=de_DE.UTF-8
mkdir -p "$XDG_RUNTIME_DIR" && chmod 700 "$XDG_RUNTIME_DIR"
# Plasma Mobile ist Touch-first: ohne diese Variablen kommt eine Desktop-Skalierung
# heraus, die auf einem Telefon unbedienbar ist.
export QT_QPA_PLATFORM=wayland
export QT_QUICK_CONTROLS_MOBILE=1
export PLASMA_PLATFORM=phone
export KDE_FULL_SESSION=true
case "$GPU_PROFIL" in
turnip)
# Zink übersetzt OpenGL auf Vulkan, Turnip liefert Vulkan direkt aus dem
# Adreno-Treiber — zusammen der schnellste Weg ohne Root.
export MESA_LOADER_DRIVER_OVERRIDE=zink
export GALLIUM_DRIVER=zink
export TU_DEBUG=noconform
;;
virgl)
export GALLIUM_DRIVER=virpipe
export MESA_GL_VERSION_OVERRIDE=4.3
;;
esac
echo " GPU-Profil: $GPU_PROFIL"
# ── Pakete ───────────────────────────────────────────────────────────────────
# Bewusst ohne empfohlene Pakete: sonst zieht Plasma Mobile eine komplette
# Desktop-Umgebung samt Drucksystem mit, was auf einem Telefon nur Platz kostet.
if ! command -v kwin_wayland >/dev/null 2>&1; then
echo "── Installiere Plasma Mobile (einmalig, gut 1 GB) ──"
apt-get update -qq
apt-get install -y --no-install-recommends \
plasma-mobile kwin-wayland plasma-workspace maliit-keyboard \
xdg-desktop-portal-kde qtwayland5 libqt5waylandclient5 \
|| echo " ! Paketinstallation unvollständig — Details oben"
fi
for b in kwin_wayland plasmashell; do
if command -v "$b" >/dev/null 2>&1; then echo "$b vorhanden"
else echo "$b fehlt"; fi
done
# ── GPU-Nachweis, bevor der Desktop startet ──────────────────────────────────
# Wenn hier schon nichts kommt, ist ein Fehlschlag von Plasma keine Aussage über
# Plasma, sondern über den GPU-Pfad. Deshalb vorher messen.
echo "── GPU-Nachweis ──"
timeout 20 glxinfo -B 2>&1 | grep -Ei 'OpenGL (renderer|version)|Device' || echo " ! glxinfo lieferte nichts"
timeout 20 vulkaninfo --summary 2>&1 | grep -Ei 'driverName|deviceName' || echo " ! vulkaninfo lieferte nichts"
# ── D-Bus ────────────────────────────────────────────────────────────────────
if [ ! -e /run/dbus/system_bus_socket ]; then
mkdir -p /run/dbus
dbus-daemon --system --fork 2>/dev/null && echo " ✓ System-D-Bus gestartet" \
|| echo " ! System-D-Bus nicht gestartet (ohne logind oft verschmerzbar)"
fi
# ── Versuch A: KWin genestet im X11-Backend ──────────────────────────────────
# Das ist die eigentliche Wette. In diesem Modus braucht KWin weder DRM noch Seat.
echo ""
echo "── Versuch A: kwin_wayland --x11-display ──"
LOGA=/tmp/hpos-s1-versuchA.log
dbus-run-session -- kwin_wayland \
--x11-display "$DISPLAY" \
--width "$BREITE" --height "$HOEHE" \
--xwayland \
-- plasmashell -p org.kde.plasma.phoneshell \
>"$LOGA" 2>&1 &
PID_A=$!
echo " gestartet (PID $PID_A), warte 25 s auf die Oberfläche …"
sleep 25
if kill -0 "$PID_A" 2>/dev/null; then
echo " ✓ Versuch A läuft noch — jetzt in die Termux:X11-App wechseln und schauen."
echo ""
echo " Beenden mit: pkill -f kwin_wayland"
echo " Protokoll: $LOGA"
tail -n 25 "$LOGA" 2>/dev/null | sed 's/^/ /'
exit 0
fi
echo " ✗ Versuch A beendet. Letzte Zeilen:"
tail -n 40 "$LOGA" 2>/dev/null | sed 's/^/ /'
# ── Versuch B: die von Debian mitgelieferte Sitzung ──────────────────────────
# Startet KWin selbst und wählt sein Backend eigenständig. Weniger Kontrolle,
# aber manchmal weiß das Distributionsskript etwas, das wir nicht wissen.
echo ""
echo "── Versuch B: startplasmamobile ──"
if command -v startplasmamobile >/dev/null 2>&1; then
LOGB=/tmp/hpos-s1-versuchB.log
KWIN_COMPOSE=O2 dbus-run-session -- startplasmamobile >"$LOGB" 2>&1 &
PID_B=$!
sleep 25
if kill -0 "$PID_B" 2>/dev/null; then
echo " ✓ Versuch B läuft — in die Termux:X11-App wechseln."
tail -n 25 "$LOGB" 2>/dev/null | sed 's/^/ /'
exit 0
fi
echo " ✗ Versuch B beendet. Letzte Zeilen:"
tail -n 40 "$LOGB" 2>/dev/null | sed 's/^/ /'
else
echo " ! startplasmamobile nicht vorhanden"
fi
# ── Rückfallebene: XFCE, damit der Abend nicht umsonst war ───────────────────
# Beweist zumindest, dass X-Server und GPU-Pfad tragen. Damit ist ein Fehlschlag
# von S1 eindeutig Plasma zuzuordnen und nicht der Umgebung.
echo ""
echo "── Rückfallebene: XFCE zur Gegenprobe ──"
if ! command -v startxfce4 >/dev/null 2>&1; then
apt-get install -y --no-install-recommends xfce4 xfce4-terminal >/dev/null 2>&1 \
|| echo " ! XFCE-Installation fehlgeschlagen"
fi
if command -v startxfce4 >/dev/null 2>&1; then
dbus-run-session -- startxfce4 >/tmp/hpos-s1-xfce.log 2>&1 &
sleep 12
if pgrep -f xfwm4 >/dev/null 2>&1; then
echo " ✓ XFCE läuft. S1 ist gescheitert, aber X-Server und GPU-Pfad tragen."
echo " → Im Protokoll als 'S1 gescheitert, XFCE-Profil wird Hauptweg' vermerken."
else
echo " ✗ Auch XFCE startet nicht — dann liegt es an X-Server oder GPU, nicht an Plasma."
fi
fi
exit 1
+127
View File
@@ -0,0 +1,127 @@
#!/bin/bash
# Läuft INNERHALB des Debian-Rootfs, aufgerufen von 03-s3-pipewire-kamera.sh.
set -uo pipefail
export DEBIAN_FRONTEND=noninteractive
export DISPLAY=:0
export XDG_RUNTIME_DIR=/tmp/hpos-runtime
export XDG_CURRENT_DESKTOP=KDE
export LANG=de_DE.UTF-8
mkdir -p "$XDG_RUNTIME_DIR" && chmod 700 "$XDG_RUNTIME_DIR"
echo "── Debian-Seite: virtuelle PipeWire-Kamera ──"
# ── Pakete ───────────────────────────────────────────────────────────────────
# xdg-desktop-portal-kde ist der Grund, warum Plasma Mobile hier im Vorteil ist:
# die GTK- und wlr-Portale können das Kamera-Portal nicht bedienen.
if ! command -v pw-cli >/dev/null 2>&1; then
echo "── Installiere PipeWire und GStreamer (einmalig) ──"
apt-get update -qq
apt-get install -y --no-install-recommends \
pipewire pipewire-pulse wireplumber pipewire-bin \
gstreamer1.0-pipewire gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
gstreamer1.0-tools xdg-desktop-portal xdg-desktop-portal-kde \
|| echo " ! Paketinstallation unvollständig"
fi
# ── PipeWire starten ─────────────────────────────────────────────────────────
# Kein systemd im proot, also die Dienste von Hand — und in dieser Reihenfolge,
# sonst findet WirePlumber den Daemon nicht.
echo "── Dienste starten ──"
pkill -f 'pipewire|wireplumber|xdg-desktop-portal' 2>/dev/null; sleep 1
if [ ! -e /run/dbus/system_bus_socket ]; then
mkdir -p /run/dbus && dbus-daemon --system --fork 2>/dev/null || true
fi
eval "$(dbus-launch --sh-syntax)" 2>/dev/null || true
pipewire >/tmp/hpos-pipewire.log 2>&1 & sleep 2
pipewire-pulse >/tmp/hpos-pw-pulse.log 2>&1 & sleep 1
wireplumber >/tmp/hpos-wireplumber.log 2>&1 & sleep 2
if pgrep -x pipewire >/dev/null; then echo " ✓ pipewire läuft"; else
echo " ✗ pipewire startet nicht:"; tail -n 20 /tmp/hpos-pipewire.log | sed 's/^/ /'; exit 1
fi
pgrep -x wireplumber >/dev/null && echo " ✓ wireplumber läuft" \
|| echo " ! wireplumber läuft nicht — Nodes werden evtl. nicht verwaltet"
/usr/libexec/xdg-desktop-portal-kde >/tmp/hpos-portal-kde.log 2>&1 & sleep 1
/usr/libexec/xdg-desktop-portal >/tmp/hpos-portal.log 2>&1 & sleep 2
pgrep -f xdg-desktop-portal >/dev/null && echo " ✓ Portal läuft" \
|| echo " ! Portal läuft nicht — Firefox wird die Kamera nicht sehen"
# ── Die virtuelle Kamera ─────────────────────────────────────────────────────
# Im Spike steht ein Testbild statt der echten Kamera. Das ist Absicht: bewiesen
# werden soll der PIPEWIRE-Weg, nicht die Bridge. Später ersetzt der Bridge-Stream
# videotestsrc — sonst nichts.
echo ""
echo "── Virtuelle Kamera starten ──"
gst-launch-1.0 -q videotestsrc pattern=ball is-live=true \
! video/x-raw,format=YUY2,width=640,height=480,framerate=30/1 \
! pipewiresink mode=provide \
stream-properties="props,media.class=Video/Source,node.description=HPOS-Testkamera,node.name=hpos_kamera" \
>/tmp/hpos-kamera.log 2>&1 &
GST_PID=$!
sleep 4
if ! kill -0 "$GST_PID" 2>/dev/null; then
echo " ✗ GStreamer sofort beendet:"
tail -n 25 /tmp/hpos-kamera.log | sed 's/^/ /'
echo " Häufigste Ursache: gstreamer1.0-pipewire fehlt."
exit 1
fi
echo " ✓ GStreamer läuft (PID $GST_PID)"
# ── Nachweis, dass der Node existiert ────────────────────────────────────────
echo ""
echo "── Node-Nachweis ──"
if command -v wpctl >/dev/null 2>&1; then
wpctl status 2>&1 | sed 's/^/ /'
fi
if pw-cli ls Node 2>/dev/null | grep -qi 'hpos_kamera\|HPOS-Testkamera'; then
echo " ✓ Kamera-Node ist in PipeWire registriert"
else
echo " ! Node nicht eindeutig gefunden — vollständige Liste:"
pw-cli ls Node 2>/dev/null | grep -Ei 'node.name|media.class' | sed 's/^/ /'
fi
# ── Firefox ──────────────────────────────────────────────────────────────────
# Ohne media.webrtc.camera.allow-pipewire fragt Firefox weiterhin nur V4L2 ab und
# findet folgerichtig nichts. Die Einstellung wird hier vorab ins Profil geschrieben,
# damit der Test nicht an einem vergessenen Häkchen scheitert.
echo ""
echo "── Firefox vorbereiten ──"
if ! command -v firefox >/dev/null 2>&1 && ! command -v firefox-esr >/dev/null 2>&1; then
apt-get install -y --no-install-recommends firefox-esr >/dev/null 2>&1 \
|| echo " ! Firefox-Installation fehlgeschlagen"
fi
FF=$(command -v firefox || command -v firefox-esr || true)
if [ -n "$FF" ]; then
PROFIL=$(find "$HOME/.mozilla/firefox" -maxdepth 1 -name '*.default*' -type d 2>/dev/null | head -1)
if [ -z "$PROFIL" ]; then
timeout 25 "$FF" --headless --screenshot /tmp/ff-init.png about:blank >/dev/null 2>&1 || true
PROFIL=$(find "$HOME/.mozilla/firefox" -maxdepth 1 -name '*.default*' -type d 2>/dev/null | head -1)
fi
if [ -n "$PROFIL" ]; then
{
echo 'user_pref("media.webrtc.camera.allow-pipewire", true);'
echo 'user_pref("widget.use-xdg-desktop-portal.camera", 1);'
} >>"$PROFIL/user.js"
echo " ✓ Einstellungen ins Profil geschrieben: $PROFIL"
else
echo " ! Kein Firefox-Profil gefunden — bitte manuell in about:config setzen:"
echo " media.webrtc.camera.allow-pipewire = true"
fi
echo ""
echo " Starte Firefox mit dem Kameratest …"
"$FF" https://webcamtests.com/ >/tmp/hpos-firefox.log 2>&1 &
sleep 3
echo " → In der Termux:X11-App die Quelle 'HPOS-Testkamera' auswählen."
echo " Erwartet: ein springender Ball. Erscheint er, ist S3 bestanden."
else
echo " ! Ohne Firefox lässt sich der entscheidende Teil nicht prüfen."
fi
echo ""
echo " Beenden mit: kill $GST_PID ; pkill -f 'pipewire|wireplumber'"
+106
View File
@@ -0,0 +1,106 @@
#!/bin/bash
# Läuft INNERHALB des Debian-Rootfs, aufgerufen von 04-s4-intent-handoff.sh.
#
# Richtet hpos-open als xdg-open-Handler für geo:, tel:, sms: und mailto: ein.
# Der Witz daran: nichts wird pro Anwendung gebaut. Adressbuch, Browser, Kalender
# und Dateimanager rufen alle xdg-open auf und landen dadurch automatisch hier.
set -uo pipefail
ZIEL="${HPOS_ZIEL:-Brandenburger Tor, Berlin}"
TOKEN_DATEI=/hpos/spike/out/bridge-token
echo "── Debian-Seite: Intent-Handoff ──"
export DEBIAN_FRONTEND=noninteractive
command -v xdg-open >/dev/null 2>&1 || apt-get install -y --no-install-recommends xdg-utils >/dev/null 2>&1
if [ ! -r "$TOKEN_DATEI" ]; then
echo " ✗ Token-Datei nicht lesbar: $TOKEN_DATEI"
echo " Läuft die Mini-Bridge? Sie erzeugt das Token beim ersten Start."
exit 1
fi
# ── hpos-open ────────────────────────────────────────────────────────────────
# Der Handler selbst: nimmt eine URI, schickt sie an die Bridge, fertig. Später
# ersetzt die Bridge-APK den Gegenpart, dieses Skript bleibt wie es ist.
install -d /usr/local/bin
cat >/usr/local/bin/hpos-open <<'HANDLER'
#!/bin/sh
# Reicht eine URI an die HPOS-Bridge auf der Android-Seite weiter.
# Aufruf: hpos-open "geo:0,0?q=Adresse"
set -eu
URI="${1:-}"
[ -n "$URI" ] || { echo "Aufruf: hpos-open <uri>" >&2; exit 2; }
TOKEN_DATEI="${HPOS_TOKEN_DATEI:-/hpos/spike/out/bridge-token}"
[ -r "$TOKEN_DATEI" ] || { echo "hpos-open: kein Token unter $TOKEN_DATEI" >&2; exit 1; }
TOKEN="$(cat "$TOKEN_DATEI")"
printf '%s\t%s\n' "$TOKEN" "$URI" | timeout 20 python3 -c '
import socket, sys
try:
s = socket.create_connection(("127.0.0.1", 8474), 10)
except OSError as e:
sys.exit(f"hpos-open: Bridge nicht erreichbar ({e})")
s.sendall(sys.stdin.buffer.read())
s.shutdown(socket.SHUT_WR)
antwort = s.recv(4096).decode().strip()
print(antwort)
sys.exit(0 if antwort.startswith("OK") else 1)
'
HANDLER
chmod 755 /usr/local/bin/hpos-open
echo " ✓ /usr/local/bin/hpos-open eingerichtet"
# ── Als Standardanwendung registrieren ───────────────────────────────────────
cat >/usr/share/applications/hpos-open.desktop <<'DESKTOP'
[Desktop Entry]
Type=Application
Name=Auf dem Telefon öffnen
Comment=Reicht Adressen, Rufnummern und Nachrichten an die Android-Seite weiter
Exec=hpos-open %u
Terminal=false
NoDisplay=true
MimeType=x-scheme-handler/geo;x-scheme-handler/tel;x-scheme-handler/sms;x-scheme-handler/smsto;x-scheme-handler/mailto;
DESKTOP
update-desktop-database /usr/share/applications >/dev/null 2>&1 || true
for schema in geo tel sms smsto; do
xdg-mime default hpos-open.desktop "x-scheme-handler/$schema" 2>/dev/null \
&& echo "$schema: → hpos-open" \
|| echo " ! $schema: Registrierung fehlgeschlagen"
done
echo " Zugeordnet laut System:"
for schema in geo tel sms; do
printf ' %-6s %s\n' "$schema:" "$(xdg-mime query default "x-scheme-handler/$schema" 2>/dev/null || echo '?')"
done
# ── Der Beweis ───────────────────────────────────────────────────────────────
echo ""
echo "── Test 1: hpos-open direkt ──"
KODIERT=$(python3 -c 'import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))' "$ZIEL")
if hpos-open "geo:0,0?q=$KODIERT"; then
echo " ✓ Bridge hat angenommen"
else
echo " ✗ Bridge hat abgelehnt oder war nicht erreichbar"
fi
sleep 5
echo ""
echo "── Test 2: über xdg-open ──"
echo " Das ist der eigentliche Beweis: der Weg, den jede Linux-Anwendung nimmt."
if xdg-open "geo:0,0?q=$KODIERT" 2>&1; then
echo " ✓ xdg-open hat den Handler aufgerufen"
else
echo " ✗ xdg-open ist gescheitert"
fi
sleep 5
echo ""
echo "── Test 3: Rufnummer ──"
hpos-open "tel:+4930901820" || echo " ! tel: fehlgeschlagen"
echo ""
echo " Jetzt aufs Telefon schauen: läuft die Navigation, liegt das Overlay darüber?"
echo " Zurück ins Linux über den Android-Task-Manager."