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/pulseaudio-tcp.substvars
debian/pulseaudio-tcp
*.sw*

View file

@ -22,16 +22,24 @@
usage() {
cat >&2 <<EOF
Usage: $0 [OPTIONS] OPERATION
Options:
--debug Enable additional debug output for start and stop operations.
--no-gui Do not display GUI dialogs, use terminal for input and output.
Operations:
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.
# pulseaudio-tcp - Connect to remote PulseAudio/Pipewire Server
## Usage:
\`$0 [OPTION ...] OPERATION ...\`
## Options:
* \`--debug\`: Enable additional debug output for start and stop operations.
* \`--no-gui\`: Do not display GUI dialogs, use terminal for input and output.
* \`--cookie COOKIE\`: Pass Pulseaudio cookie as option value instead of copying
from remote_host. Expects value to be Base64-encoded.
## Operations:
* \`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
}
@ -44,7 +52,7 @@ log() {
if [[ -t 1 ]] && $gui && [[ -n $(type -p zenity) ]] ; then
case "$level" in
ERROR)
ERROR|WARNING)
zenity --error --text="$msg"
;;
*)
@ -58,20 +66,22 @@ error() {
log ERROR "$@"
}
warning() {
log WARNING "$@"
}
info() {
log INFO "$@"
}
debug() {
if "$debug" ; then
log DEBUG "$@"
else
gui=false log DEBUG "$@"
fi
}
_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
else
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
errors+=("PulseAudio module \"module-native-protocol-tcp\" is not loaded on remote host $remote_ip.")
rv=1
else
debug "PulseAudio module \"module-native-protocol-tcp\" is loaded on remote host $remote_ip."
fi
if "$outbound" ; then
if ! pactl list modules | grep -Fq "Name: module-tunnel-sink" ; then
errors+=("PulseAudio module \"module-tunnel-sink\" is not loaded.")
rv=1
else
debug "PulseAudio module \"module-tunnel-sink\" is loaded."
fi
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.")
rv=1
else
debug "\"tunnel-sink.tcp:127.0.0.1\" is the default PulseAudio sink."
fi
fi
@ -251,6 +267,8 @@ do_status() {
if ! pactl list modules | grep -Fq "Name: module-tunnel-source" ; then
errors+=("PulseAudio module \"module-tunnel-source\" is not loaded.")
rv=1
else
debug "PulseAudio module \"module-tunnel-source\" is loaded."
fi
fi
@ -263,9 +281,20 @@ do_status() {
return "$rv"
}
# Acquire PulseAudio cookie from remote host
# Use PulseAudio cookie from --cookie option value or remote host
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."
return 0
else
@ -446,10 +475,27 @@ do_stop() {
##
# 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
--)
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_cmdline=true
;;
@ -460,11 +506,10 @@ for arg in "$@" ; do
help_cmdline=true
;;
start|stop|restart|setup|status)
[[ -z $operation ]] || { error "Multiple operations are not supported." ; exit 1 ; }
operation=$arg
operations+=("$arg")
;;
*)
error "Unsupported argument (try \"--help\")"
gui=false error "Unsupported argument (try \"--help\")"
exit 1
;;
esac
@ -473,6 +518,16 @@ done
##
# 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=$config_dir/config.inc.sh
@ -489,6 +544,15 @@ fi
if [[ $gui_cmdline = false ]] ; then
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
gui=true
fi
@ -496,46 +560,46 @@ fi
##
# Main Program
rv=0
for operation in "${operations[@]}" ; do
rv=0
if test "$operation" = setup ; then
info "Entering setup mode ..."
else
if ! test -e "$config" ; then
error "Configfile $config does not exist (use \"$0 setup\" first)."
rv=1
elif ! test -r "$config" ; then
error "Configfile $config is not readable."
rv=1
elif ! test -f "$config" ; then
error "Configfile $config is not a regular file."
rv=1
else
. "$config"
if [[ "$operation" != setup ]] ; then
if ! test -e "$config" ; then
error "Configfile $config does not exist (use \"$0 setup\" first)."
rv=1
elif ! test -r "$config" ; then
error "Configfile $config is not readable."
rv=1
elif ! test -f "$config" ; then
error "Configfile $config is not a regular file."
rv=1
else
. "$config"
if [[ -z $remote_ip ]] ; then
error "\"remote_ip=<IP address>\" not set in configfile $config."
rv=1
elif [[ -z $remote_user ]] ; then
error "\"remote_user=<username>\" not set in configfile $config."
rv=1
if [[ -z $remote_ip ]] ; then
error "\"remote_ip=<IP address>\" not set in configfile $config."
rv=1
elif [[ -z $remote_user ]] ; then
error "\"remote_user=<username>\" not set in configfile $config."
rv=1
fi
fi
required_cmds=( jq pactl ssh )
for exe in "${required_cmds[@]}" ; do
if [[ -z $(type -p "$exe") ]] ; then
error "Required executable \"$exe\" not found."
rv=1
fi
done
fi
required_cmds=( jq pactl ssh )
"$gui" && required_cmds+=( zenity )
if [[ $rv -ne 0 ]] ; then
error "Preliminary checks failed, skipping operation."
break
fi
for exe in "${required_cmds[@]}" ; do
if [[ -z $(type -p "$exe") ]] ; then
error "Required executable \"$exe\" not found."
rv=1
fi
done
fi
if [[ $rv -ne 0 ]] ; then
error "Preliminary checks failed, skipping operation."
else
case "$operation" in
setup)
do_setup
@ -563,6 +627,8 @@ else
rv=1
;;
esac
fi
[[ $rv -ne 0 ]] && break
done
exit "$rv"

View file

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