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:
Executable
+159
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env bash
|
||||
# Phase 0 — Grundinstallation in Termux.
|
||||
#
|
||||
# Richtet Termux-X11, den GPU-Pfad und ein Debian-13-Rootfs ein, damit die Beweise
|
||||
# S1 bis S4 danach laufen können. Idempotent: mehrfaches Ausführen ist unschädlich.
|
||||
#
|
||||
# Ausführen IN TERMUX auf dem Telefon: bash spike/00-bootstrap-termux.sh
|
||||
set -uo pipefail
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
require_termux
|
||||
|
||||
DISTRO=debian
|
||||
BIND_ROOT="$REPO_ROOT"
|
||||
|
||||
log "HPOS Phase 0 — Bootstrap"
|
||||
log "Protokoll: $LOG"
|
||||
geraete_steckbrief
|
||||
|
||||
# ── Repositories ─────────────────────────────────────────────────────────────
|
||||
# x11-repo bringt Termux-X11 und virglrenderer, tur-repo die Mesa-/Turnip-Pakete.
|
||||
section "Paketquellen"
|
||||
try pkg update -y
|
||||
try pkg install -y x11-repo tur-repo
|
||||
try pkg update -y
|
||||
|
||||
# ── Basispakete ──────────────────────────────────────────────────────────────
|
||||
# Einzeln installiert und einzeln protokolliert: Paketnamen wandern in Termux
|
||||
# gelegentlich, und wir wollen wissen, WELCHES Paket fehlt, statt nur dass etwas fehlt.
|
||||
section "Basispakete"
|
||||
BASIS=(termux-x11-nightly pulseaudio proot-distro termux-api git python openssl termux-tools)
|
||||
for p in "${BASIS[@]}"; do
|
||||
if try pkg install -y "$p"; then ok "$p"; else fail "$p — fehlt, betroffene Beweise siehe unten"; fi
|
||||
done
|
||||
|
||||
# ── GPU-Pfad ─────────────────────────────────────────────────────────────────
|
||||
# Adreno 6xx/7xx → Turnip greift direkt auf KGSL zu, das ist der schnellste Weg.
|
||||
# Mali und alles andere → virgl/ANGLE. Wir installieren beides und entscheiden
|
||||
# zur Laufzeit; das Ergebnis landet im GPU-Profil.
|
||||
section "GPU-Pakete"
|
||||
GPU_PKGS=(virglrenderer-android vulkan-loader-android vulkan-tools angle-android
|
||||
mesa-vulkan-icd-wrapper mesa-zink virglrenderer-mesa-zink)
|
||||
for p in "${GPU_PKGS[@]}"; do
|
||||
if try pkg install -y "$p"; then ok "$p"; else warn "$p nicht verfügbar — kein Beinbruch"; fi
|
||||
done
|
||||
|
||||
section "GPU erkennen"
|
||||
PLATFORM="$(getprop ro.board.platform 2>/dev/null)"
|
||||
HARDWARE="$(getprop ro.hardware 2>/dev/null)"
|
||||
GPU_PROFIL="virgl" # konservative Vorgabe
|
||||
if have vulkaninfo; then
|
||||
vulkaninfo --summary >>"$LOG" 2>&1 || true
|
||||
GPU_NAME="$(vulkaninfo --summary 2>/dev/null | grep -m1 -i 'deviceName' | cut -d= -f2- | xargs)"
|
||||
else
|
||||
GPU_NAME=""
|
||||
fi
|
||||
log " Plattform = ${PLATFORM:-?}"
|
||||
log " Hardware = ${HARDWARE:-?}"
|
||||
log " GPU = ${GPU_NAME:-unbekannt}"
|
||||
|
||||
case "${GPU_NAME}${PLATFORM}" in
|
||||
*[Aa]dreno*|*qcom*|*sm[0-9]*|*msm*)
|
||||
GPU_PROFIL="turnip"
|
||||
ok "Adreno erkannt → Turnip-Profil (direkter KGSL-Zugriff, schnellster Weg)"
|
||||
;;
|
||||
*[Mm]ali*)
|
||||
GPU_PROFIL="virgl"
|
||||
warn "Mali erkannt → virgl/ANGLE-Profil. Spürbar langsamer als Turnip."
|
||||
;;
|
||||
*)
|
||||
warn "GPU nicht eindeutig erkannt → virgl als Vorgabe."
|
||||
hint "Bitte im Spike-Protokoll vermerken, welcher Pfad tatsächlich getragen hat."
|
||||
;;
|
||||
esac
|
||||
|
||||
# Das Profil ist eine Datei, kein Sonderfall im Code — so bleibt es später
|
||||
# erweiterbar, ohne dass jemand Skripte anfassen muss.
|
||||
mkdir -p "$REPO_ROOT/rootfs/profiles"
|
||||
PROFIL_DATEI="$OUT_DIR/gpu-profil.env"
|
||||
{
|
||||
echo "# Automatisch erkannt von 00-bootstrap-termux.sh"
|
||||
echo "HPOS_GPU_PROFIL=$GPU_PROFIL"
|
||||
echo "HPOS_GPU_NAME=\"${GPU_NAME:-unbekannt}\""
|
||||
echo "HPOS_PLATFORM=\"${PLATFORM:-unbekannt}\""
|
||||
} >"$PROFIL_DATEI"
|
||||
ok "GPU-Profil geschrieben: $PROFIL_DATEI ($GPU_PROFIL)"
|
||||
|
||||
# ── Termux-Eigenschaften ─────────────────────────────────────────────────────
|
||||
# allow-external-apps ist die Voraussetzung dafür, dass die Bridge-APK später
|
||||
# Kommandos in Termux anstoßen darf. Hier gesetzt, damit Phase 2 es vorfindet.
|
||||
section "Termux-Eigenschaften"
|
||||
TP="$HOME/.termux/termux.properties"
|
||||
mkdir -p "$(dirname "$TP")"
|
||||
touch "$TP"
|
||||
if grep -q '^allow-external-apps' "$TP" 2>/dev/null; then
|
||||
ok "allow-external-apps bereits gesetzt"
|
||||
else
|
||||
echo 'allow-external-apps = true' >>"$TP"
|
||||
ok "allow-external-apps = true gesetzt"
|
||||
hint "Wird erst nach 'termux-reload-settings' oder Neustart von Termux wirksam."
|
||||
fi
|
||||
try termux-reload-settings
|
||||
|
||||
section "Speicherzugriff"
|
||||
if [ -d "$HOME/storage" ]; then
|
||||
ok "Speicherzugriff bereits eingerichtet"
|
||||
else
|
||||
warn "termux-setup-storage wird gleich einen Berechtigungsdialog zeigen — bitte erlauben."
|
||||
try termux-setup-storage
|
||||
sleep 2
|
||||
[ -d "$HOME/storage" ] && ok "Speicherzugriff eingerichtet" || fail "Speicherzugriff fehlt — Backup nach /sdcard geht später nicht"
|
||||
fi
|
||||
|
||||
# ── Wachhalten ───────────────────────────────────────────────────────────────
|
||||
# Ohne Wake-Lock schläft der Stack weg, sobald der Bildschirm aus geht.
|
||||
# Die herstellerseitigen Maßnahmen kommen zusätzlich, siehe provision/.
|
||||
section "Wake-Lock"
|
||||
try termux-wake-lock && ok "Wake-Lock aktiv" || warn "Wake-Lock nicht gesetzt"
|
||||
|
||||
# ── Debian-Rootfs ────────────────────────────────────────────────────────────
|
||||
section "Debian-Rootfs"
|
||||
if proot-distro list --installed 2>/dev/null | grep -q "^$DISTRO\b" \
|
||||
|| [ -d "$PREFIX/var/lib/proot-distro/installed-rootfs/$DISTRO" ]; then
|
||||
ok "$DISTRO bereits installiert"
|
||||
else
|
||||
log " Installiere $DISTRO — das dauert einige Minuten."
|
||||
need proot-distro install "$DISTRO"
|
||||
ok "$DISTRO installiert"
|
||||
fi
|
||||
|
||||
section "Debian-Grundausstattung"
|
||||
# Der Bind macht das Repo im Rootfs unter /hpos sichtbar, damit die inneren
|
||||
# Skripte dieselben Dateien benutzen und Ergebnisse am selben Ort landen.
|
||||
proot-distro login "$DISTRO" --bind "$BIND_ROOT:/hpos" -- bash -lc '
|
||||
set -uo pipefail
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates locales dbus-x11 sudo nano less procps \
|
||||
mesa-utils vulkan-tools pulseaudio-utils
|
||||
# Deutsche und englische Locale, sonst meckert die halbe Desktop-Software.
|
||||
sed -i "s/^# *\(de_DE.UTF-8\|en_US.UTF-8\)/\1/" /etc/locale.gen
|
||||
locale-gen >/dev/null 2>&1
|
||||
echo "--- Debian-Version ---"; cat /etc/debian_version
|
||||
' 2>&1 | tee -a "$LOG"
|
||||
|
||||
# ── Abschluss ────────────────────────────────────────────────────────────────
|
||||
section "Bereit"
|
||||
log "GPU-Profil : $GPU_PROFIL"
|
||||
log "Rootfs : $DISTRO"
|
||||
log "Repo im Rootfs sichtbar unter /hpos"
|
||||
log ""
|
||||
log "Nächste Schritte, in dieser Reihenfolge:"
|
||||
log " bash spike/01-s1-plasma-mobile.sh # der wackligste Beweis, deshalb zuerst"
|
||||
log " bash spike/02-s2-mikrofon.sh"
|
||||
log " bash spike/03-s3-pipewire-kamera.sh"
|
||||
log " bash spike/04-s4-intent-handoff.sh"
|
||||
log ""
|
||||
log "Vor dem Übernacht-Test bitte provision/provision.sh vom PC laufen lassen"
|
||||
log "und die herstellerseitigen Handgriffe aus docs/machbarkeit.md erledigen."
|
||||
Executable
+89
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env bash
|
||||
# Phase 0 · Beweis S1 — Läuft Plasma Mobile in proot?
|
||||
#
|
||||
# Der wackligste Beweis des ganzen Projekts, deshalb zuerst. Plasma Mobile will
|
||||
# Wayland, und KWin will normalerweise DRM/KMS und logind — beides gibt es ohne Root
|
||||
# nicht. Die Wette: KWins X11-Backend braucht weder DRM noch Seat, wenn es genestet
|
||||
# in Termux-X11 läuft.
|
||||
#
|
||||
# Scheitert der Beweis, wird XFCE der Hauptweg. Das ist kein Weltuntergang, ändert aber
|
||||
# Kamera-Portal, Bildschirmtastatur und Telefonie-Oberfläche — siehe docs/spike-protokoll.md.
|
||||
#
|
||||
# Ausführen IN TERMUX: bash spike/01-s1-plasma-mobile.sh
|
||||
set -uo pipefail
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
require_termux
|
||||
|
||||
DISTRO=debian
|
||||
# Volle Panel-Auflösung wäre 3120x1440. Für einen Telefon-Desktop ist das zu viel
|
||||
# Fläche — allein das Zeichnen frisst die Leistung. Überschreibbar per Umgebung.
|
||||
BREITE="${HPOS_BREITE:-1080}"
|
||||
HOEHE="${HPOS_HOEHE:-2340}"
|
||||
|
||||
[ -f "$OUT_DIR/gpu-profil.env" ] && source "$OUT_DIR/gpu-profil.env"
|
||||
GPU_PROFIL="${HPOS_GPU_PROFIL:-virgl}"
|
||||
|
||||
log "HPOS Phase 0 · S1 — Plasma Mobile in proot"
|
||||
log "Auflösung: ${BREITE}x${HOEHE} GPU-Profil: $GPU_PROFIL"
|
||||
log "Protokoll: $LOG"
|
||||
|
||||
# ── Alte Reste einsammeln ────────────────────────────────────────────────────
|
||||
section "Aufräumen"
|
||||
pkill -f termux-x11 2>/dev/null && ok "alten X-Server beendet" || hint "kein alter X-Server"
|
||||
pkill -f virgl_test_server 2>/dev/null && ok "alten virgl-Server beendet" || true
|
||||
sleep 1
|
||||
|
||||
# ── X-Server ─────────────────────────────────────────────────────────────────
|
||||
section "Termux-X11 starten"
|
||||
if ! have termux-x11; then
|
||||
fail "termux-x11 fehlt. Bitte 00-bootstrap-termux.sh laufen lassen."
|
||||
exit 1
|
||||
fi
|
||||
termux-x11 :0 >>"$LOG" 2>&1 &
|
||||
X11_PID=$!
|
||||
sleep 3
|
||||
if kill -0 "$X11_PID" 2>/dev/null; then
|
||||
ok "X-Server läuft (PID $X11_PID)"
|
||||
else
|
||||
fail "X-Server sofort beendet — siehe Protokoll"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Die App muss im Vordergrund sein, sonst sieht man nichts und Android drosselt.
|
||||
log " Hole die Termux:X11-App in den Vordergrund …"
|
||||
try am start --user 0 -n com.termux.x11/com.termux.x11.MainActivity
|
||||
sleep 2
|
||||
|
||||
# ── GPU-Server ───────────────────────────────────────────────────────────────
|
||||
section "GPU-Pfad vorbereiten ($GPU_PROFIL)"
|
||||
if [ "$GPU_PROFIL" = "virgl" ] && have virgl_test_server_android; then
|
||||
virgl_test_server_android >>"$LOG" 2>&1 &
|
||||
sleep 1
|
||||
ok "virgl-Server gestartet"
|
||||
elif [ "$GPU_PROFIL" = "turnip" ]; then
|
||||
ok "Turnip greift direkt auf KGSL zu — kein Proxy nötig"
|
||||
else
|
||||
warn "Kein GPU-Server aktiv, Software-Rendering wahrscheinlich"
|
||||
fi
|
||||
|
||||
# ── Audio, damit der Desktop nicht schon daran scheitert ─────────────────────
|
||||
section "PulseAudio"
|
||||
pulseaudio --kill 2>/dev/null || true
|
||||
try pulseaudio --start --exit-idle-time=-1 \
|
||||
--load="module-native-protocol-tcp auth-ip-acl=127.0.0.1 auth-anonymous=1" \
|
||||
&& ok "PulseAudio läuft" || warn "PulseAudio nicht gestartet — für S1 nicht kritisch"
|
||||
|
||||
# ── Übergabe an Debian ───────────────────────────────────────────────────────
|
||||
section "Plasma Mobile starten"
|
||||
log " Ab hier läuft alles im Debian-Rootfs. Erster Lauf lädt gut 1 GB Pakete."
|
||||
log " Auf dem Telefon in die Termux:X11-App wechseln, um das Ergebnis zu sehen."
|
||||
log ""
|
||||
|
||||
HPOS_GPU_PROFIL="$GPU_PROFIL" HPOS_BREITE="$BREITE" HPOS_HOEHE="$HOEHE" \
|
||||
proot-distro login "$DISTRO" --shared-tmp --bind "$REPO_ROOT:/hpos" -- \
|
||||
bash /hpos/spike/inner/s1-plasma.sh 2>&1 | tee -a "$LOG"
|
||||
|
||||
verdict "S1" "Zeigt die Termux:X11-App eine bedienbare Plasma-Mobile-Oberfläche?
|
||||
Prüfe: reagiert Touch, erscheint die Maliit-Tastatur beim Tippen in ein Feld?"
|
||||
|
||||
log "Aufräumen: pkill -f termux-x11 ; pkill -f virgl_test_server"
|
||||
Executable
+154
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env bash
|
||||
# Phase 0 · Beweis S2 — Erreicht das Mikrofon die Linux-Seite?
|
||||
#
|
||||
# Der stillste K.-o.-Punkt des Projekts. Ohne Mikrofon im Linux gibt es keine
|
||||
# Videocalls in Element oder im Browser, keine Sprachaufnahme, keine Spracheingabe.
|
||||
# Die Wette: Termux' PulseAudio kann über OpenSL ES eine Aufnahmequelle anbieten,
|
||||
# die per TCP in den proot durchgereicht wird.
|
||||
#
|
||||
# Ausführen IN TERMUX: bash spike/02-s2-mikrofon.sh
|
||||
set -uo pipefail
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
require_termux
|
||||
|
||||
DISTRO=debian
|
||||
DAUER="${HPOS_DAUER:-6}"
|
||||
WAV="$OUT_DIR/s2-aufnahme.wav"
|
||||
|
||||
log "HPOS Phase 0 · S2 — Mikrofon im Linux"
|
||||
log "Protokoll: $LOG"
|
||||
|
||||
# ── Berechtigung ─────────────────────────────────────────────────────────────
|
||||
# Ohne erteilte Mikrofonberechtigung liefert die OpenSL-Quelle Stille statt eines
|
||||
# Fehlers — der verwirrendste denkbare Fehlschlag. Deshalb vorher erzwingen.
|
||||
section "Mikrofonberechtigung"
|
||||
if have termux-microphone-record; then
|
||||
log " Kurzer Test über Termux:API, um den Berechtigungsdialog auszulösen …"
|
||||
try termux-microphone-record -d -l 2 -f "$OUT_DIR/s2-vortest.m4a"
|
||||
sleep 3
|
||||
try termux-microphone-record -q
|
||||
if [ -s "$OUT_DIR/s2-vortest.m4a" ]; then
|
||||
ok "Termux:API kann aufnehmen — Berechtigung liegt vor"
|
||||
else
|
||||
fail "Termux:API konnte nicht aufnehmen"
|
||||
hint "Einstellungen → Apps → Termux:API → Berechtigungen → Mikrofon erlauben."
|
||||
hint "Ohne das ist S2 nicht aussagekräftig."
|
||||
fi
|
||||
else
|
||||
warn "termux-microphone-record fehlt (Paket termux-api). Weiter ohne Vortest."
|
||||
fi
|
||||
|
||||
# ── PulseAudio mit Aufnahmequelle ────────────────────────────────────────────
|
||||
section "PulseAudio mit OpenSL-Quelle"
|
||||
pulseaudio --kill 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
# module-sles-source ist der Teil, der wackelt: er existiert nicht in jedem
|
||||
# Termux-Build. Deshalb erst mit, dann ohne — und der Unterschied wird protokolliert.
|
||||
MIT_QUELLE=0
|
||||
if pulseaudio --start --exit-idle-time=-1 \
|
||||
--load="module-sles-source" \
|
||||
--load="module-native-protocol-tcp auth-ip-acl=127.0.0.1 auth-anonymous=1" \
|
||||
>>"$LOG" 2>&1; then
|
||||
MIT_QUELLE=1
|
||||
ok "PulseAudio mit module-sles-source gestartet"
|
||||
else
|
||||
warn "module-sles-source ließ sich nicht laden — starte ohne"
|
||||
try pulseaudio --start --exit-idle-time=-1 \
|
||||
--load="module-native-protocol-tcp auth-ip-acl=127.0.0.1 auth-anonymous=1"
|
||||
fi
|
||||
sleep 2
|
||||
|
||||
section "Verfügbare Quellen"
|
||||
if have pactl; then
|
||||
pactl list sources short 2>&1 | tee -a "$LOG" | sed 's/^/ /'
|
||||
QUELLEN=$(pactl list sources short 2>/dev/null | wc -l)
|
||||
if [ "$QUELLEN" -gt 0 ]; then
|
||||
ok "$QUELLEN Aufnahmequelle(n) gemeldet"
|
||||
else
|
||||
fail "Keine einzige Aufnahmequelle — S2 wird so nicht gelingen"
|
||||
hint "Falls module-sles-source fehlt, ist dieser Termux-Build ohne OpenSL-Aufnahme gebaut."
|
||||
fi
|
||||
else
|
||||
warn "pactl fehlt, kann Quellen nicht auflisten"
|
||||
fi
|
||||
|
||||
# ── Aufnahme im Debian ───────────────────────────────────────────────────────
|
||||
section "Aufnahme aus dem Debian heraus"
|
||||
log " ${DAUER} Sekunden werden aufgenommen — bitte jetzt laut und deutlich sprechen."
|
||||
log ""
|
||||
sleep 1
|
||||
|
||||
proot-distro login "$DISTRO" --shared-tmp --bind "$REPO_ROOT:/hpos" -- bash -lc "
|
||||
set -uo pipefail
|
||||
export PULSE_SERVER=127.0.0.1
|
||||
echo ' Quellen aus Sicht des Debian:'
|
||||
pactl list sources short 2>&1 | sed 's/^/ /' || echo ' (pactl lieferte nichts)'
|
||||
echo ' Nehme auf …'
|
||||
timeout $((DAUER + 4)) parecord --channels=1 --rate=16000 --file-format=wav \
|
||||
/hpos/spike/out/$(basename "$WAV") &
|
||||
REC=\$!
|
||||
sleep $DAUER
|
||||
kill \$REC 2>/dev/null
|
||||
wait \$REC 2>/dev/null
|
||||
echo ' Aufnahme beendet.'
|
||||
" 2>&1 | tee -a "$LOG"
|
||||
|
||||
# ── Auswertung ───────────────────────────────────────────────────────────────
|
||||
# Eine Datei zu erzeugen beweist nichts: die häufigste Fehlerform ist eine
|
||||
# formal korrekte WAV-Datei voller Stille. Deshalb Größe UND Inhalt prüfen.
|
||||
section "Auswertung"
|
||||
if [ ! -s "$WAV" ]; then
|
||||
fail "Keine Aufnahmedatei entstanden → S2 gescheitert"
|
||||
else
|
||||
GROESSE=$(stat -c%s "$WAV" 2>/dev/null || echo 0)
|
||||
ok "Datei vorhanden: $WAV ($GROESSE Bytes)"
|
||||
ERWARTET=$((DAUER * 16000 * 2 * 8 / 10))
|
||||
if [ "$GROESSE" -lt "$ERWARTET" ]; then
|
||||
warn "Deutlich kleiner als erwartet (~$ERWARTET Bytes) — vermutlich abgebrochen"
|
||||
fi
|
||||
# Stilletest. Der typische Fehlschlag ist eine formal korrekte WAV-Datei voller
|
||||
# Nullen — sie sieht in jeder Dateiliste richtig aus und enthält doch nichts.
|
||||
# Deshalb wird der Pegel gemessen, nicht nur die Größe.
|
||||
if have python; then
|
||||
python - "$WAV" <<'PY' 2>&1 | tee -a "$LOG"
|
||||
import array, math, sys, wave
|
||||
|
||||
# Bewusst ohne audioop: das Modul ist in Python 3.13 entfernt worden, und Termux
|
||||
# liegt regelmäßig auf der aktuellen Version.
|
||||
try:
|
||||
with wave.open(sys.argv[1]) as w:
|
||||
breite = w.getsampwidth()
|
||||
roh = w.readframes(w.getnframes())
|
||||
if breite == 1:
|
||||
werte = array.array("b", bytes(b - 128 for b in roh))
|
||||
elif breite == 2:
|
||||
werte = array.array("h", roh[: len(roh) // 2 * 2])
|
||||
else:
|
||||
raise ValueError(f"{breite} Byte je Abtastwert werden hier nicht ausgewertet")
|
||||
if not werte:
|
||||
raise ValueError("keine Abtastwerte enthalten")
|
||||
pegel = int(math.sqrt(sum(v * v for v in werte) / len(werte)))
|
||||
spitze = max(abs(v) for v in werte)
|
||||
print(f" Effektivpegel: {pegel} Spitze: {spitze}")
|
||||
if spitze < 40:
|
||||
print(" ✗ Praktisch Stille — die Quelle liefert keine echten Daten.")
|
||||
print(" Das ist der typische Fehlschlag: Datei ja, Ton nein.")
|
||||
print(" Prüfen: Mikrofonberechtigung für Termux:API, und ob")
|
||||
print(" module-sles-source in diesem Termux-Build überhaupt existiert.")
|
||||
else:
|
||||
print(" ✓ Echtes Signal aufgenommen.")
|
||||
except Exception as e:
|
||||
print(f" ! Datei nicht auswertbar: {e}")
|
||||
PY
|
||||
fi
|
||||
log ""
|
||||
log " Zum Gegenhören: pactl upload-sample nicht nötig — einfach abspielen:"
|
||||
log " proot-distro login $DISTRO -- env PULSE_SERVER=127.0.0.1 paplay /hpos/spike/out/$(basename "$WAV")"
|
||||
fi
|
||||
|
||||
log ""
|
||||
log " module-sles-source geladen: $([ "$MIT_QUELLE" = 1 ] && echo ja || echo NEIN)"
|
||||
|
||||
verdict "S2" "Ist auf der Aufnahme deine Stimme hörbar?
|
||||
Abspielen mit dem Befehl oben. Eine Datei ohne Ton zählt als gescheitert."
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
# Phase 0 · Beweis S3 — Virtuelle Kamera ohne Kernel-Modul.
|
||||
#
|
||||
# v4l2loopback wäre der klassische Weg, ist aber ein Kernel-Modul und damit ohne Root
|
||||
# ausgeschlossen. Die Wette: ein GStreamer-pipewiresink mit media.class=Video/Source
|
||||
# erzeugt einen Kamera-Node komplett im Userspace, den Firefox über das Portal annimmt.
|
||||
#
|
||||
# Belegt der Beweis, funktionieren Videocalls im Browser und in Element.
|
||||
# Siehe: https://www.ideasonboard.com/news/pipewire-is-the-new-v4l2loopback/
|
||||
#
|
||||
# Ausführen IN TERMUX: bash spike/03-s3-pipewire-kamera.sh
|
||||
set -uo pipefail
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
require_termux
|
||||
|
||||
DISTRO=debian
|
||||
|
||||
log "HPOS Phase 0 · S3 — Virtuelle PipeWire-Kamera"
|
||||
log "Protokoll: $LOG"
|
||||
log ""
|
||||
log "Voraussetzung: ein laufender X-Server aus S1."
|
||||
log "Falls nicht, in einer zweiten Sitzung: termux-x11 :0 &"
|
||||
log ""
|
||||
|
||||
section "Übergabe an Debian"
|
||||
proot-distro login "$DISTRO" --shared-tmp --bind "$REPO_ROOT:/hpos" -- \
|
||||
bash /hpos/spike/inner/s3-kamera.sh 2>&1 | tee -a "$LOG"
|
||||
|
||||
verdict "S3" "Zeigt Firefox auf webcamtests.com ein Bild aus der Quelle 'HPOS-Testkamera'?
|
||||
Ein reines Auftauchen im Auswahlmenü genügt nicht — es muss Bild kommen."
|
||||
Executable
+96
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env bash
|
||||
# Phase 0 · Beweis S4 — Intent-Handoff vom Linux zur Android-App.
|
||||
#
|
||||
# Die Wette: statt Android-Apps ins Linux zu streamen, übergibt das Linux eine
|
||||
# Absicht und die App öffnet nativ im Vollbild. Für Navigation ist das nicht der
|
||||
# Kompromiss, sondern die bessere Lösung — volles GPU, volles GPS, und Overlay-Apps
|
||||
# funktionieren, was über einem gestreamten Fenster gar nicht ginge.
|
||||
#
|
||||
# Gebaut wird das nicht pro App, sondern einmal als xdg-open-Handler. Damit löst
|
||||
# jede Linux-Anwendung — Adressbuch, Browser, Kalender — die richtige Android-App aus,
|
||||
# ohne selbst etwas von der Bridge zu wissen.
|
||||
#
|
||||
# Ausführen IN TERMUX: bash spike/04-s4-intent-handoff.sh
|
||||
set -uo pipefail
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
require_termux
|
||||
|
||||
DISTRO=debian
|
||||
ZIEL="${HPOS_ZIEL:-Brandenburger Tor, Berlin}"
|
||||
# Paketname einer Begleit-App mit Overlay, die vor der Navigation starten soll.
|
||||
# Beispiel: HPOS_BEGLEIT_APP=de.blitzer bash spike/04-s4-intent-handoff.sh
|
||||
BEGLEIT_APP="${HPOS_BEGLEIT_APP:-}"
|
||||
|
||||
log "HPOS Phase 0 · S4 — Intent-Handoff"
|
||||
log "Navigationsziel: $ZIEL"
|
||||
log "Begleit-App: ${BEGLEIT_APP:-(keine)}"
|
||||
log "Protokoll: $LOG"
|
||||
|
||||
# ── Vorbedingungen ───────────────────────────────────────────────────────────
|
||||
section "Vorbedingungen"
|
||||
if have am; then
|
||||
ok "am vorhanden"
|
||||
else
|
||||
fail "am fehlt (Paket termux-tools) — ohne das geht nichts"
|
||||
exit 1
|
||||
fi
|
||||
have python && ok "python vorhanden" || { fail "python fehlt"; exit 1; }
|
||||
|
||||
if [ -n "$BEGLEIT_APP" ]; then
|
||||
if pm list packages 2>/dev/null | grep -q "package:$BEGLEIT_APP"; then
|
||||
ok "Begleit-App $BEGLEIT_APP ist installiert"
|
||||
hint "Overlay-Berechtigung nötig: Einstellungen → Apps → $BEGLEIT_APP → über anderen Apps anzeigen"
|
||||
else
|
||||
warn "Begleit-App $BEGLEIT_APP nicht gefunden — Overlay-Teil des Beweises entfällt"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Mini-Bridge starten ──────────────────────────────────────────────────────
|
||||
section "Mini-Bridge"
|
||||
pkill -f 'bridge-mini.py' 2>/dev/null && { hint "alte Instanz beendet"; sleep 1; } || true
|
||||
|
||||
HPOS_BEGLEIT_APP="$BEGLEIT_APP" python "$REPO_ROOT/spike/bridge-mini.py" \
|
||||
>>"$OUT_DIR/bridge-mini.log" 2>&1 &
|
||||
BRIDGE_PID=$!
|
||||
sleep 2
|
||||
|
||||
if kill -0 "$BRIDGE_PID" 2>/dev/null; then
|
||||
ok "Bridge läuft auf 127.0.0.1:8474 (PID $BRIDGE_PID)"
|
||||
else
|
||||
fail "Bridge startet nicht:"
|
||||
tail -n 20 "$OUT_DIR/bridge-mini.log" | sed 's/^/ /' | tee -a "$LOG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Direkttest, bevor Linux ins Spiel kommt ──────────────────────────────────
|
||||
# Erst beweisen, dass der Intent überhaupt zieht. Scheitert es hier, liegt es an
|
||||
# Android und nicht an der Brücke ins Linux — ein wichtiger Unterschied.
|
||||
section "Direkttest ohne Linux"
|
||||
TOKEN="$(cat "$OUT_DIR/bridge-token")"
|
||||
ANTWORT="$(printf '%s\t%s\n' "$TOKEN" "geo:0,0?q=$(python -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" "$ZIEL")" \
|
||||
| timeout 25 python -c '
|
||||
import socket,sys
|
||||
s=socket.create_connection(("127.0.0.1",8474),10)
|
||||
s.sendall(sys.stdin.buffer.read())
|
||||
s.shutdown(socket.SHUT_WR)
|
||||
print(s.recv(4096).decode().strip())
|
||||
' 2>&1)"
|
||||
log " Antwort: $ANTWORT"
|
||||
case "$ANTWORT" in
|
||||
OK*) ok "Android hat den Intent angenommen — schau aufs Telefon" ;;
|
||||
*) fail "Intent nicht angenommen"
|
||||
hint "Ist eine Navigations-App installiert? Ohne Google Maps o. ä. gibt es keinen Empfänger." ;;
|
||||
esac
|
||||
sleep 6
|
||||
|
||||
# ── Der eigentliche Beweis: aus dem Linux heraus ─────────────────────────────
|
||||
section "Aus dem Linux heraus"
|
||||
HPOS_ZIEL="$ZIEL" proot-distro login "$DISTRO" --shared-tmp --bind "$REPO_ROOT:/hpos" -- \
|
||||
bash /hpos/spike/inner/s4-handoff.sh 2>&1 | tee -a "$LOG"
|
||||
|
||||
log ""
|
||||
log " Bridge-Protokoll: $OUT_DIR/bridge-mini.log"
|
||||
log " Bridge beenden: kill $BRIDGE_PID"
|
||||
|
||||
verdict "S4" "Hat 'xdg-open geo:…' aus dem Debian heraus die Navigation auf dem Telefon gestartet?
|
||||
Und lag — falls konfiguriert — das Overlay der Begleit-App darüber?"
|
||||
@@ -0,0 +1,87 @@
|
||||
# Phase 0 — Machbarkeits-Spike
|
||||
|
||||
Vier Annahmen tragen das gesamte Projekt. Scheitert eine, sieht alles Weitere anders
|
||||
aus — und das will man **vor** dem Bau der Infrastruktur wissen, nicht danach.
|
||||
|
||||
Der Code hier ist bewusst Wegwerf-Code. Was zählt, sind die Protokolle: sie werden
|
||||
später zur Grundlage der Setup-Skripte in `rootfs/`.
|
||||
|
||||
| | Beweis | Wenn er scheitert |
|
||||
|---|---|---|
|
||||
| **S1** | Plasma Mobile läuft in proot (genestetes KWin) | XFCE-Profil wird Hauptweg; Kamera-Portal, Bildschirmtastatur und Telefonie-Oberfläche ändern sich mit |
|
||||
| **S2** | Mikrofon-Eingang erreicht die Linux-Seite | Keine Videocalls, keine Sprachaufnahme, keine Spracheingabe |
|
||||
| **S3** | Virtuelle PipeWire-Kamera ohne Kernel-Modul | Kamera nur in einer eigenständigen App, keine Browser-Videocalls |
|
||||
| **S4** | Intent-Handoff Linux → Android-App | Keine Navigation aus dem Linux-Adressbuch |
|
||||
|
||||
Dazu der **Übernacht-Test**: überlebt der Stack eine Nacht mit gesperrtem Bildschirm?
|
||||
Scheitert er, ist das ein früher Warnschuss fürs Gesamtkonzept, kein Detail.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- Android-Telefon, Adreno 6xx/7xx bevorzugt (Turnip-Pfad)
|
||||
- **Termux von F-Droid oder GitHub** — nicht aus dem Play Store, die Fassung dort ist
|
||||
veraltet und unbrauchbar
|
||||
- Einmalig `provision/provision.sh` vom PC, plus die dort genannten Handgriffe von Hand
|
||||
- Etwa 6 GB freier Speicher (Debian-Rootfs, Plasma Mobile, Firefox)
|
||||
- Geduld beim ersten Lauf: rund 1 GB Pakete
|
||||
|
||||
## Ablauf
|
||||
|
||||
```bash
|
||||
# Einmalig, vom PC mit angeschlossenem Telefon
|
||||
./provision/provision.sh
|
||||
|
||||
# Auf dem Telefon in Termux
|
||||
bash spike/00-bootstrap-termux.sh
|
||||
|
||||
# Beweise in dieser Reihenfolge — S1 zuerst, weil er der wackligste ist
|
||||
bash spike/01-s1-plasma-mobile.sh
|
||||
bash spike/02-s2-mikrofon.sh
|
||||
bash spike/03-s3-pipewire-kamera.sh
|
||||
bash spike/04-s4-intent-handoff.sh
|
||||
```
|
||||
|
||||
Jedes Skript fragt am Ende nach deiner Beurteilung und schreibt sie nach
|
||||
`spike/out/ergebnisse.tsv`. Rohprotokolle liegen daneben in `spike/out/*.log`.
|
||||
|
||||
**Übertrage die Ergebnisse anschließend nach [`docs/spike-protokoll.md`](../docs/spike-protokoll.md).**
|
||||
`spike/out/` ist absichtlich nicht im Repo — die Auswertung gehört dorthin, nicht die Rohdaten.
|
||||
|
||||
## Stellschrauben
|
||||
|
||||
| Variable | Vorgabe | Wofür |
|
||||
|---|---|---|
|
||||
| `HPOS_BREITE` / `HPOS_HOEHE` | `1080` / `2340` | Virtuelle Auflösung. Volle Panel-Auflösung ist für einen Telefon-Desktop zu viel Fläche |
|
||||
| `HPOS_DAUER` | `6` | Sekunden Aufnahme in S2 |
|
||||
| `HPOS_ZIEL` | Brandenburger Tor, Berlin | Navigationsziel in S4 |
|
||||
| `HPOS_BEGLEIT_APP` | leer | Paketname einer Overlay-App, die in S4 vor der Navigation startet |
|
||||
|
||||
Beispiel:
|
||||
|
||||
```bash
|
||||
HPOS_BEGLEIT_APP=de.blitzer HPOS_ZIEL="Kölner Dom" bash spike/04-s4-intent-handoff.sh
|
||||
```
|
||||
|
||||
## Wenn etwas schiefgeht
|
||||
|
||||
**S1 startet nicht.** Das Skript probiert von selbst zwei Wege und fällt am Ende auf
|
||||
XFCE zurück. Läuft XFCE, dann tragen X-Server und GPU-Pfad — der Fehlschlag liegt dann
|
||||
eindeutig bei Plasma. Läuft auch XFCE nicht, liegt es an X-Server oder GPU.
|
||||
|
||||
**S2 erzeugt eine Datei ohne Ton.** Der häufigste und tückischste Fehlschlag. Das Skript
|
||||
misst deshalb den Pegel. Ursache ist meist eine fehlende Mikrofonberechtigung für
|
||||
Termux:API oder ein Termux-Build ohne `module-sles-source`.
|
||||
|
||||
**S3: Firefox sieht die Kamera nicht.** Prüfen, ob `xdg-desktop-portal-kde` läuft — die
|
||||
GTK- und wlr-Portale können das Kamera-Portal nicht bedienen. In `about:config` muss
|
||||
`media.webrtc.camera.allow-pipewire` auf `true` stehen; das Skript setzt es vorab.
|
||||
|
||||
**S4: kein Empfänger für den Intent.** Ohne installierte Navigations-App gibt es nichts,
|
||||
was den `geo:`-Intent annehmen könnte. Das ist kein Fehler der Brücke.
|
||||
|
||||
## Aufräumen
|
||||
|
||||
```bash
|
||||
pkill -f 'termux-x11|virgl_test_server|kwin_wayland|pipewire|bridge-mini.py'
|
||||
pulseaudio --kill
|
||||
```
|
||||
Executable
+168
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env python3
|
||||
"""HPOS Mini-Bridge — Prototyp des Intent-Handoffs für Beweis S4.
|
||||
|
||||
Läuft in Termux, lauscht auf 127.0.0.1 und übersetzt URIs vom Linux in
|
||||
Android-Intents. Das ist die kleinste ehrliche Fassung dessen, was später die
|
||||
Bridge-APK macht: sie speichert nichts, sie reicht durch.
|
||||
|
||||
Bewusst eng gehalten:
|
||||
* nur 127.0.0.1, niemals extern erreichbar
|
||||
* gemeinsames Token, damit nicht jede App auf dem Telefon Intents auslösen kann
|
||||
* feste Liste erlaubter Schemata statt beliebiger Intents
|
||||
|
||||
Start: python spike/bridge-mini.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import socketserver
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import urllib.parse
|
||||
|
||||
HOST = "127.0.0.1"
|
||||
PORT = int(os.environ.get("HPOS_BRIDGE_PORT", "8474"))
|
||||
|
||||
BASIS = os.path.dirname(os.path.abspath(__file__))
|
||||
TOKEN_DATEI = os.path.join(BASIS, "out", "bridge-token")
|
||||
|
||||
# Paketname einer Begleit-App, die vor der Hauptanwendung starten soll — typisch
|
||||
# eine App mit Overlay, das über der Navigation liegen muss. Leer = keine.
|
||||
BEGLEIT_APP = os.environ.get("HPOS_BEGLEIT_APP", "").strip()
|
||||
BEGLEIT_VORLAUF = float(os.environ.get("HPOS_BEGLEIT_VORLAUF", "2.5"))
|
||||
|
||||
# Was aus dem Linux kommen darf. Alles andere wird abgelehnt — eine offene
|
||||
# Intent-Schnittstelle wäre ein Einfallstor für jede App auf dem Gerät.
|
||||
ERLAUBTE_SCHEMATA = {"geo", "tel", "sms", "smsto", "mailto", "http", "https"}
|
||||
|
||||
|
||||
def token_lesen() -> str:
|
||||
"""Token holen, beim ersten Start erzeugen."""
|
||||
os.makedirs(os.path.dirname(TOKEN_DATEI), exist_ok=True)
|
||||
if not os.path.exists(TOKEN_DATEI):
|
||||
with open(os.open(TOKEN_DATEI, os.O_CREAT | os.O_WRONLY, 0o600), "w") as f:
|
||||
f.write(os.urandom(16).hex())
|
||||
with open(TOKEN_DATEI) as f:
|
||||
return f.read().strip()
|
||||
|
||||
|
||||
TOKEN = token_lesen()
|
||||
|
||||
|
||||
def am(*args: str) -> tuple[int, str]:
|
||||
"""`am` aufrufen und Ergebnis zurückgeben."""
|
||||
befehl = ["am", *args]
|
||||
print(f" → {' '.join(shlex.quote(a) for a in befehl)}", flush=True)
|
||||
try:
|
||||
p = subprocess.run(befehl, capture_output=True, text=True, timeout=20)
|
||||
return p.returncode, (p.stdout + p.stderr).strip()
|
||||
except FileNotFoundError:
|
||||
return 127, "am nicht gefunden — Paket termux-tools fehlt"
|
||||
except subprocess.TimeoutExpired:
|
||||
return 124, "am hat nicht geantwortet"
|
||||
|
||||
|
||||
def app_starten(paket: str) -> None:
|
||||
am("start", "--user", "0", "-a", "android.intent.action.MAIN",
|
||||
"-c", "android.intent.category.LAUNCHER", "-p", paket)
|
||||
|
||||
|
||||
def navigation(ziel: str) -> tuple[int, str]:
|
||||
"""Turn-by-turn direkt starten statt nur die Karte zu zeigen.
|
||||
|
||||
Vorher optional die Begleit-App, damit deren Overlay schon steht, wenn die
|
||||
Navigation in den Vordergrund kommt — andersherum legt sich die Navigation
|
||||
darüber und das Overlay ist verdeckt.
|
||||
"""
|
||||
if BEGLEIT_APP:
|
||||
print(f" Begleit-App vorab: {BEGLEIT_APP}", flush=True)
|
||||
app_starten(BEGLEIT_APP)
|
||||
time.sleep(BEGLEIT_VORLAUF)
|
||||
uri = "google.navigation:q=" + urllib.parse.quote(ziel) + "&mode=d"
|
||||
return am("start", "--user", "0", "-a", "android.intent.action.VIEW", "-d", uri)
|
||||
|
||||
|
||||
def behandeln(zeile: str) -> str:
|
||||
"""Eine Anfrage verarbeiten. Format: <token>\\t<uri>"""
|
||||
teile = zeile.split("\t", 1)
|
||||
if len(teile) != 2:
|
||||
return "FEHLER ungueltiges-format"
|
||||
token, uri = teile[0].strip(), teile[1].strip()
|
||||
|
||||
if token != TOKEN:
|
||||
print(" ! Anfrage mit falschem Token abgelehnt", flush=True)
|
||||
return "FEHLER token"
|
||||
if not uri:
|
||||
return "FEHLER leere-uri"
|
||||
|
||||
schema = uri.split(":", 1)[0].lower()
|
||||
|
||||
# geo:-URIs mit Suchbegriff bedeuten in der Praxis fast immer "dahin navigieren",
|
||||
# nicht "Karte anschauen". Deshalb wird daraus Turn-by-turn.
|
||||
if schema == "geo":
|
||||
query = urllib.parse.parse_qs(urllib.parse.urlparse(uri).query).get("q", [""])[0]
|
||||
if query:
|
||||
rc, aus = navigation(query)
|
||||
return f"OK navigation {rc} {aus}" if rc == 0 else f"FEHLER am {rc} {aus}"
|
||||
|
||||
if schema == "tel":
|
||||
# DIAL statt CALL: die Nummer erscheint im Wähler, gewählt wird bewusst.
|
||||
rc, aus = am("start", "--user", "0", "-a", "android.intent.action.DIAL", "-d", uri)
|
||||
return f"OK dial {rc} {aus}" if rc == 0 else f"FEHLER am {rc} {aus}"
|
||||
|
||||
if schema in ERLAUBTE_SCHEMATA:
|
||||
rc, aus = am("start", "--user", "0", "-a", "android.intent.action.VIEW", "-d", uri)
|
||||
return f"OK view {rc} {aus}" if rc == 0 else f"FEHLER am {rc} {aus}"
|
||||
|
||||
print(f" ! Schema abgelehnt: {schema}", flush=True)
|
||||
return f"FEHLER schema-nicht-erlaubt {schema}"
|
||||
|
||||
|
||||
class Handler(socketserver.StreamRequestHandler):
|
||||
timeout = 10
|
||||
|
||||
def handle(self) -> None:
|
||||
try:
|
||||
zeile = self.rfile.readline(4096).decode("utf-8", "replace")
|
||||
except Exception as e:
|
||||
print(f" ! Lesefehler: {e}", flush=True)
|
||||
return
|
||||
if not zeile:
|
||||
return
|
||||
antwort = behandeln(zeile)
|
||||
print(f" ← {antwort}", flush=True)
|
||||
try:
|
||||
self.wfile.write((antwort + "\n").encode())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class Server(socketserver.ThreadingTCPServer):
|
||||
allow_reuse_address = True
|
||||
daemon_threads = True
|
||||
|
||||
|
||||
def main() -> int:
|
||||
print("HPOS Mini-Bridge (Prototyp für Beweis S4)")
|
||||
print(f" Lauscht auf {HOST}:{PORT}")
|
||||
print(f" Token-Datei {TOKEN_DATEI}")
|
||||
print(f" Begleit-App {BEGLEIT_APP or '(keine)'}")
|
||||
print(" Beenden mit Strg+C")
|
||||
print()
|
||||
try:
|
||||
with Server((HOST, PORT), Handler) as srv:
|
||||
srv.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\nBeendet.")
|
||||
except OSError as e:
|
||||
print(f"Start fehlgeschlagen: {e}", file=sys.stderr)
|
||||
print(f"Läuft schon etwas auf Port {PORT}?", file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Executable
+139
@@ -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
|
||||
Executable
+127
@@ -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'"
|
||||
Executable
+106
@@ -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."
|
||||
Executable
+105
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env bash
|
||||
# Gemeinsame Hilfsfunktionen für die Phase-0-Spike-Skripte.
|
||||
# Wird von den 0x-Skripten eingebunden, nicht direkt ausgeführt.
|
||||
|
||||
# Farben nur wenn wir auf einem Terminal landen — sonst verschmutzen sie das Protokoll.
|
||||
if [ -t 1 ]; then
|
||||
C_OK=$'\033[32m'; C_WARN=$'\033[33m'; C_ERR=$'\033[31m'
|
||||
C_HEAD=$'\033[1;36m'; C_DIM=$'\033[2m'; C_OFF=$'\033[0m'
|
||||
else
|
||||
C_OK=""; C_WARN=""; C_ERR=""; C_HEAD=""; C_DIM=""; C_OFF=""
|
||||
fi
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
OUT_DIR="$REPO_ROOT/spike/out"
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
# Jeder Lauf bekommt sein eigenes Protokoll. Die Sammlung ist später die halbe
|
||||
# Auswertung — deshalb wird alles mitgeschrieben, auch was funktioniert hat.
|
||||
LOG="${LOG:-$OUT_DIR/$(basename "${0%.sh}").log}"
|
||||
|
||||
log() { printf '%s\n' "$*" | tee -a "$LOG"; }
|
||||
section() { log ""; log "${C_HEAD}══ $* ══${C_OFF}"; }
|
||||
ok() { log "${C_OK} ✓${C_OFF} $*"; }
|
||||
warn() { log "${C_WARN} !${C_OFF} $*"; }
|
||||
fail() { log "${C_ERR} ✗${C_OFF} $*"; }
|
||||
hint() { log "${C_DIM} $*${C_OFF}"; }
|
||||
|
||||
# Befehl ausführen, Ausgabe ins Protokoll, Fehler nicht tödlich.
|
||||
# Für alles, was scheitern DARF, ohne den Spike abzubrechen.
|
||||
try() {
|
||||
log "${C_DIM} \$ $*${C_OFF}"
|
||||
if "$@" >>"$LOG" 2>&1; then
|
||||
return 0
|
||||
else
|
||||
local rc=$?
|
||||
warn "fehlgeschlagen (Code $rc): $*"
|
||||
return $rc
|
||||
fi
|
||||
}
|
||||
|
||||
# Wie try(), aber ein Fehlschlag beendet den Spike. Für echte Voraussetzungen.
|
||||
need() {
|
||||
log "${C_DIM} \$ $*${C_OFF}"
|
||||
if ! "$@" >>"$LOG" 2>&1; then
|
||||
fail "Abbruch — notwendiger Schritt fehlgeschlagen: $*"
|
||||
log ""
|
||||
log "Vollständiges Protokoll: $LOG"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
have() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
in_termux() {
|
||||
case "${PREFIX:-}" in
|
||||
*com.termux*) [ -d "$PREFIX/bin" ] ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
require_termux() {
|
||||
if ! in_termux; then
|
||||
fail "Dieses Skript läuft in Termux auf dem Telefon, nicht auf dem PC."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Fragt am Ende jedes Beweises nach dem, was nur ein Mensch beurteilen kann:
|
||||
# ob es tatsächlich funktioniert hat. Das Ergebnis geht ins Protokoll.
|
||||
verdict() {
|
||||
local beweis="$1" frage="$2"
|
||||
log ""
|
||||
log "${C_HEAD}── Beurteilung $beweis ──${C_OFF}"
|
||||
log "$frage"
|
||||
printf ' [j] ja [n] nein [t] teilweise > '
|
||||
read -r antwort </dev/tty || antwort="?"
|
||||
local text
|
||||
case "$antwort" in
|
||||
j|J) text="BESTANDEN" ;;
|
||||
n|N) text="GESCHEITERT" ;;
|
||||
t|T) text="TEILWEISE" ;;
|
||||
*) text="UNKLAR" ;;
|
||||
esac
|
||||
printf ' Anmerkung (eine Zeile, Enter für nichts) > '
|
||||
read -r notiz </dev/tty || notiz=""
|
||||
log " Ergebnis $beweis: $text ${notiz:+— $notiz}"
|
||||
printf '%s\t%s\t%s\n' "$beweis" "$text" "$notiz" >>"$OUT_DIR/ergebnisse.tsv"
|
||||
log ""
|
||||
log "Ins Protokoll übertragen: docs/spike-protokoll.md"
|
||||
log "Rohdaten: $LOG"
|
||||
}
|
||||
|
||||
# Gerätesteckbrief — landet in jedem Protokoll, damit Meldungen vergleichbar sind.
|
||||
geraete_steckbrief() {
|
||||
section "Gerät"
|
||||
local p
|
||||
for p in ro.product.manufacturer ro.product.model ro.product.device \
|
||||
ro.build.version.release ro.build.version.sdk ro.board.platform \
|
||||
ro.hardware ro.soc.model; do
|
||||
log " $p = $(getprop "$p" 2>/dev/null || echo '?')"
|
||||
done
|
||||
log " Arch = $(uname -m)"
|
||||
log " Kernel = $(uname -r)"
|
||||
if have free; then log " RAM = $(free -h 2>/dev/null | awk '/Mem:/{print $2}')"; fi
|
||||
}
|
||||
Reference in New Issue
Block a user