From e0c86784106ba31b76324f3360c3ac4bbf624f15 Mon Sep 17 00:00:00 2001 From: Tommy Rantti Date: Fri, 2 Oct 2026 14:24:35 +0300 Subject: [PATCH] Forward remote Chromium's audio to the travel laptop Waypipe only carries the window, so audio played on the remote's speakers. Load a PulseAudio tunnel sink on the remote pointing back over the tailnet and target only this Chromium at it via PULSE_SINK; the snap sandbox rules out forwarding the audio socket over ssh. Modules are unloaded on exit and leftovers cleared on the next run. Co-Authored-By: Claude Opus 5.5 --- remote-chromium.sh | 34 +++++++++++++++++++++++++++++++--- setup-local.sh | 2 +- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/remote-chromium.sh b/remote-chromium.sh index 0d41137..c2d6f3b 100755 --- a/remote-chromium.sh +++ b/remote-chromium.sh @@ -2,7 +2,7 @@ # Run on the travel laptop: closes any Chromium running for you on the remote # machine (Chromium allows one process per profile, so a running instance # would swallow the new window), then launches it there with its window -# drawn here. +# drawn here. Its audio is forwarded here too (needs pactl on both ends). # # Usage: remote-chromium.sh [--x11] [host] # --x11 classic X11 forwarding instead of waypipe @@ -42,7 +42,35 @@ if pgrep -u "$USER" -f "$PAT" >/dev/null; then fi EOF +# Audio: Chromium plays through the remote's audio system, so give that system +# a virtual speaker that forwards over the tailnet to this machine, and point +# only this Chromium at it (PULSE_SINK). The snap sandbox rules out simply +# forwarding the audio socket over ssh. Both ends need pactl (pulseaudio-utils). +AUDIO_ENV=() +LOCAL_MOD="" REMOTE_MOD="" +SINK="chromium_to_$(hostname | tr -c 'a-zA-Z0-9\n' '_')" +if command -v pactl >/dev/null 2>&1 && ssh "$HOST" command -v pactl >/dev/null 2>&1; then + LOCAL_IP=$(tailscale ip -4) + REMOTE_IP=$(tailscale ip -4 "$HOST") + # unload leftovers from a run that didn't clean up, so reruns don't fail + pactl list short modules | awk -v ip="$LOCAL_IP" '/module-native-protocol-tcp/ && index($0, ip) {print $1}' | xargs -r -n1 pactl unload-module + ssh "$HOST" "pactl list short modules | awk '/module-tunnel-sink/ && /sink_name=$SINK/ {print \$1}' | xargs -r -n1 pactl unload-module" + + LOCAL_MOD=$(pactl load-module module-native-protocol-tcp port=4713 listen="$LOCAL_IP" auth-ip-acl="$REMOTE_IP") + REMOTE_MOD=$(ssh "$HOST" pactl load-module module-tunnel-sink server="tcp:$LOCAL_IP:4713" sink_name="$SINK") + AUDIO_ENV=(env "PULSE_SINK=$SINK") +else + echo "pactl missing here or on $HOST - no audio forwarding (sudo apt install pulseaudio-utils on both)" +fi + +cleanup() { + [ -n "$REMOTE_MOD" ] && ssh "$HOST" pactl unload-module "$REMOTE_MOD" 2>/dev/null + [ -n "$LOCAL_MOD" ] && pactl unload-module "$LOCAL_MOD" 2>/dev/null + return 0 +} +trap cleanup EXIT + case "$MODE" in - waypipe) exec waypipe ssh "$HOST" chromium --ozone-platform=wayland ;; - x11) exec ssh -X "$HOST" chromium ;; + waypipe) waypipe ssh "$HOST" "${AUDIO_ENV[@]}" chromium --ozone-platform=wayland ;; + x11) ssh -X "$HOST" "${AUDIO_ENV[@]}" chromium ;; esac diff --git a/setup-local.sh b/setup-local.sh index ecdc568..0bd2ec2 100755 --- a/setup-local.sh +++ b/setup-local.sh @@ -159,7 +159,7 @@ install_packages() { fi if sudo apt-get update -qq && sudo apt-get install -y -qq \ - vim tmux mosh git curl ca-certificates jq gnupg firefox-esr code waypipe >/tmp/apt-install.log 2>&1; then + vim tmux mosh git curl ca-certificates jq gnupg firefox-esr code waypipe pulseaudio-utils >/tmp/apt-install.log 2>&1; then step_ok "apt packages" else step_fail "apt packages" "see /tmp/apt-install.log"