Compare commits

..

10 commits
1.1 ... main

Author SHA1 Message Date
Tilman Kranz
03e3953eb8 completion supports multiple commands 2025-06-01 15:43:26 +02:00
Tilman Kranz
f08b857aff support for multiple commands (WIP) 2025-06-01 15:37:56 +02:00
Tilman Kranz
f78ae9d183 exclude vim swapfiles 2025-06-01 05:46:52 +02:00
Tilman Kranz
4c7b600c68 remove debug output; option --cookie expects Base64 value 2025-06-01 03:48:32 +02:00
Tilman Kranz
e32fb0a9d2 implement --commit option for stating PA cookie directly 2025-06-01 00:40:30 +02:00
Tilman Kranz
01dffe289a corrections to behavior of --debug option 2025-05-25 06:54:07 +02:00
Tilman Kranz
6c9e822ccf update completion 2025-05-25 06:47:35 +02:00
Tilman Kranz
d93979c527 correct completion option name to "--no-gui" 2025-05-25 06:15:14 +02:00
Tilman Kranz
43f4bd632b bugfix in determining XDG_SESSION_TYPE 2025-05-25 06:10:11 +02:00
Tilman Kranz
67cbc09f43 imporved option parsing and GUI detection 2025-05-25 05:59:24 +02:00
3 changed files with 128 additions and 68 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@ debian/debhelper-build-stamp
debian/files debian/files
debian/pulseaudio-tcp.substvars debian/pulseaudio-tcp.substvars
debian/pulseaudio-tcp debian/pulseaudio-tcp
*.sw*

View file

@ -22,16 +22,24 @@
usage() { usage() {
cat >&2 <<EOF cat >&2 <<EOF
Usage: $0 [OPTIONS] OPERATION # pulseaudio-tcp - Connect to remote PulseAudio/Pipewire Server
Options: ## Usage:
--debug Enable additional debug output for start and stop operations. \`$0 [OPTION ...] OPERATION ...\`
--no-gui Do not display GUI dialogs, use terminal for input and output. ## Options:
Operations: * \`--debug\`: Enable additional debug output for start and stop operations.
setup Interactively gather settings. * \`--no-gui\`: Do not display GUI dialogs, use terminal for input and output.
start Start redirection to remote host. * \`--cookie COOKIE\`: Pass Pulseaudio cookie as option value instead of copying
stop Stop redirection to remote host. from remote_host. Expects value to be Base64-encoded.
restart Stop and then start redirection. ## Operations:
status Check if redirection is set up and enabled. * \`setup\`: Interactively gather settings.
* \`start\`: Start redirection to remote host.
* \`stop\`: Stop redirection to remote host.
* \`restart\`: Stop and then start redirection.
* \`status\`: Check if redirection is set up and enabled.
## Environment Variables:
* \`PULSEAUDIO_TCP_SSH_ADDARGS\`:
If set, specifies additional commandline arguments for calls to \`ssh\`.
Example: \`PULSEAUDIO_TCP_SSH_ADDARGS=-v pulseaudio-tcp --no-gui start\`
EOF EOF
} }
@ -44,7 +52,7 @@ log() {
if [[ -t 1 ]] && $gui && [[ -n $(type -p zenity) ]] ; then if [[ -t 1 ]] && $gui && [[ -n $(type -p zenity) ]] ; then
case "$level" in case "$level" in
ERROR) ERROR|WARNING)
zenity --error --text="$msg" zenity --error --text="$msg"
;; ;;
*) *)
@ -58,20 +66,22 @@ error() {
log ERROR "$@" log ERROR "$@"
} }
warning() {
log WARNING "$@"
}
info() { info() {
log INFO "$@" log INFO "$@"
} }
debug() { debug() {
if "$debug" ; then if "$debug" ; then
log DEBUG "$@"
else
gui=false log DEBUG "$@" gui=false log DEBUG "$@"
fi fi
} }
_ssh() { _ssh() {
if ssh -o PasswordAuthentication=no -S "$USER"-pulseaudio "$remote_user"@"$remote_ip" "$@" ; then if ssh "${_ssh_arguments[@]}" -S "$USER"-pulseaudio "$remote_user"@"$remote_ip" "$@" ; then
return 0 return 0
else else
error "SSH remote_ip=$remote_ip failed." error "SSH remote_ip=$remote_ip failed."
@ -233,17 +243,23 @@ do_status() {
if ! _ssh pactl list modules | grep -Fq "Name: module-native-protocol-tcp" ; then if ! _ssh pactl list modules | grep -Fq "Name: module-native-protocol-tcp" ; then
errors+=("PulseAudio module \"module-native-protocol-tcp\" is not loaded on remote host $remote_ip.") errors+=("PulseAudio module \"module-native-protocol-tcp\" is not loaded on remote host $remote_ip.")
rv=1 rv=1
else
debug "PulseAudio module \"module-native-protocol-tcp\" is loaded on remote host $remote_ip."
fi fi
if "$outbound" ; then if "$outbound" ; then
if ! pactl list modules | grep -Fq "Name: module-tunnel-sink" ; then if ! pactl list modules | grep -Fq "Name: module-tunnel-sink" ; then
errors+=("PulseAudio module \"module-tunnel-sink\" is not loaded.") errors+=("PulseAudio module \"module-tunnel-sink\" is not loaded.")
rv=1 rv=1
else
debug "PulseAudio module \"module-tunnel-sink\" is loaded."
fi fi
if ! pactl get-default-sink | grep -Fq "tunnel-sink.tcp:127.0.0.1" ; then if ! pactl get-default-sink | grep -Fq "tunnel-sink.tcp:127.0.0.1" ; then
errors+=("\"tunnel-sink.tcp:127.0.0.1\" is not the default PulseAudio sink.") errors+=("\"tunnel-sink.tcp:127.0.0.1\" is not the default PulseAudio sink.")
rv=1 rv=1
else
debug "\"tunnel-sink.tcp:127.0.0.1\" is the default PulseAudio sink."
fi fi
fi fi
@ -251,6 +267,8 @@ do_status() {
if ! pactl list modules | grep -Fq "Name: module-tunnel-source" ; then if ! pactl list modules | grep -Fq "Name: module-tunnel-source" ; then
errors+=("PulseAudio module \"module-tunnel-source\" is not loaded.") errors+=("PulseAudio module \"module-tunnel-source\" is not loaded.")
rv=1 rv=1
else
debug "PulseAudio module \"module-tunnel-source\" is loaded."
fi fi
fi fi
@ -263,9 +281,20 @@ do_status() {
return "$rv" return "$rv"
} }
# Acquire PulseAudio cookie from remote host # Use PulseAudio cookie from --cookie option value or remote host
sync_pa_cookie() { sync_pa_cookie() {
if _scp "$remote_user"@"$remote_ip":.config/pulse/cookie ~/.config/pulse/cookie ; then if ! [[ -d ~/.config/pulse ]] || ! [[ -w ~/.config/pulse ]] ; then
error "Local directory ~/.config/pulse does not exist or is not writeable."
return 1
elif [[ -n $cookie ]] ; then
if echo "$cookie" | base64 -d > ~/.config/pulse/cookie ; then
debug "Using PulseAudio cookie value as passed on the command line ..."
return 0
else
error "Decoding Base64 value of option --cookie failed."
return 1
fi
elif _scp "$remote_user"@"$remote_ip":.config/pulse/cookie ~/.config/pulse/cookie ; then
debug "Synced PulseAudio cookie from remote host $remote_ip." debug "Synced PulseAudio cookie from remote host $remote_ip."
return 0 return 0
else else
@ -446,10 +475,27 @@ do_stop() {
## ##
# Arguments # Arguments
operation= operations=()
cookie=
no_more_options=false
# shellcheck disable=SC2034
while [[ $# -gt 0 ]] ; do
arg=$1
shift
for arg in "$@" ; do
case "$arg" in case "$arg" in
--)
no_more_options=true
;;
--*)
"$no_more_options" && { gui=false error "Option arguments may not preceed non-option arguments." ; exit 1 ; }
;;&
--cookie)
[[ $# -eq 0 ]] && { gui=false error "Missing argument for option \"--cookie\"" ; exit 1 ; }
cookie=$1
shift
;;
--debug) --debug)
debug_cmdline=true debug_cmdline=true
;; ;;
@ -460,11 +506,10 @@ for arg in "$@" ; do
help_cmdline=true help_cmdline=true
;; ;;
start|stop|restart|setup|status) start|stop|restart|setup|status)
[[ -z $operation ]] || { error "Multiple operations are not supported." ; exit 1 ; } operations+=("$arg")
operation=$arg
;; ;;
*) *)
error "Unsupported argument (try \"--help\")" gui=false error "Unsupported argument (try \"--help\")"
exit 1 exit 1
;; ;;
esac esac
@ -473,6 +518,16 @@ done
## ##
# Configuration # Configuration
_ssh_arguments=(
-o
PasswordAuthentication=no
)
if [[ -n $PULSEAUDIO_TCP_SSH_ADDARGS ]] ; then
read -r -a _ssh_additional_arguments <<< "$PULSEAUDIO_TCP_SSH_ADDARGS"
_ssh_arguments+=("${_ssh_additional_arguments[@]}")
fi
config_dir="$HOME"/.config/pulseaudio-tcp config_dir="$HOME"/.config/pulseaudio-tcp
config=$config_dir/config.inc.sh config=$config_dir/config.inc.sh
@ -489,6 +544,15 @@ fi
if [[ $gui_cmdline = false ]] ; then if [[ $gui_cmdline = false ]] ; then
gui=false gui=false
elif [[ -z $(type -p zenity) ]] ; then
gui=false
warning "Disabling GUI support, because command \"zenity\" was not found."
elif ! [[ -v DISPLAY ]] && ! [[ -v XDG_SESSION_TYPE ]] ; then
gui=false
warning "Disabling GUI support, because neither \"DISPLAY\" not \"XDG_SESSION_TYPE\ is set."
elif [[ $XDG_SESSION_TYPE = tty ]] ; then
gui=false
warning "Disabling GUI support, because \"XDG_SESSION_TYPE\ is set to \"tty\"."
else else
gui=true gui=true
fi fi
@ -496,11 +560,10 @@ fi
## ##
# Main Program # Main Program
rv=0 for operation in "${operations[@]}" ; do
rv=0
if test "$operation" = setup ; then if [[ "$operation" != setup ]] ; then
info "Entering setup mode ..."
else
if ! test -e "$config" ; then if ! test -e "$config" ; then
error "Configfile $config does not exist (use \"$0 setup\" first)." error "Configfile $config does not exist (use \"$0 setup\" first)."
rv=1 rv=1
@ -523,7 +586,6 @@ else
fi fi
required_cmds=( jq pactl ssh ) required_cmds=( jq pactl ssh )
"$gui" && required_cmds+=( zenity )
for exe in "${required_cmds[@]}" ; do for exe in "${required_cmds[@]}" ; do
if [[ -z $(type -p "$exe") ]] ; then if [[ -z $(type -p "$exe") ]] ; then
@ -531,11 +593,13 @@ else
rv=1 rv=1
fi fi
done done
fi fi
if [[ $rv -ne 0 ]] ; then if [[ $rv -ne 0 ]] ; then
error "Preliminary checks failed, skipping operation." error "Preliminary checks failed, skipping operation."
else break
fi
case "$operation" in case "$operation" in
setup) setup)
do_setup do_setup
@ -563,6 +627,8 @@ else
rv=1 rv=1
;; ;;
esac esac
fi
[[ $rv -ne 0 ]] && break
done
exit "$rv" exit "$rv"

View file

@ -1,34 +1,27 @@
_pulseaudio_tcp_completions() { _pulseaudio_tcp_completions() {
local \ local \
command_list \ command_list \
command_pattern \
commands \ commands \
cur \ cur \
option_list \ option_list \
options \ options \
word word
options=( "--debug" "--help" "--nogui" ) options=( "--debug" "--help" "--no-gui" "--" )
commands=( "start" "stop" "status" "setup" "restart" ) commands=( "start" "stop" "status" "setup" "restart" )
cur=${COMP_WORDS[COMP_CWORD]} cur=${COMP_WORDS[COMP_CWORD]}
more_options=true
for word in "${COMP_WORDS[@]}" ; do for word in "${COMP_WORDS[@]}" ; do
[[ $word = "$cur" ]] && continue [[ $word = "$cur" ]] && continue
[[ $word = -- ]] && more_options=false
options=("${options[@]/$word}") options=("${options[@]/$word}")
commands=("${commands[@]/$word}") commands=("${commands[@]/$word}")
done done
option_list="${options[*]}" "$more_options" && option_list="${options[*]}"
printf -v command_pattern "%s|" "${commands[@]}"
command_pattern="(${command_pattern%?})"
if [[ ${COMP_WORDS[*]} =~ $command_pattern ]] ; then
command_list=""
else
command_list="${commands[*]}" command_list="${commands[*]}"
fi
mapfile -t COMPREPLY < <( compgen -W "$option_list $command_list" -- "$cur") mapfile -t COMPREPLY < <( compgen -W "$option_list $command_list" -- "$cur")