summary refs log tree commit diff
path: root/bin/soundswitch
diff options
context:
space:
mode:
authorRobert Günzler <r@gnzler.io>2021-07-29 23:45:43 +0200
committerRobert Günzler <r@gnzler.io>2021-07-29 23:48:28 +0200
commite77881a72b735ca12aa055054656afdfbd6a8fc8 (patch)
tree53ed4778acca18f55141e4efbdc0a841b64116a5 /bin/soundswitch
parent3079e935c793a9aa1831fd165e28636eb7a87dae (diff)
bin: format scripts
Diffstat (limited to 'bin/soundswitch')
-rwxr-xr-xbin/soundswitch259
1 files changed, 149 insertions, 110 deletions
diff --git a/bin/soundswitch b/bin/soundswitch
index acd287a..e39a292 100755
--- a/bin/soundswitch
+++ b/bin/soundswitch
@@ -1,14 +1,17 @@
 #!/bin/sh
 #
 # DESCRIPTION:
-#  dmenu-based pulseaudio helper
+#  dmenu-based audio helper
 #
 
+# TODO
+# - auto-select the currently active profile
+
 set -e
 [ -n "$DEBUG" ] && set -x
 
 # check requirements
-for tool in jq swq pactl perl; do
+for tool in jq swq pactl perl pw-dump; do
 	if ! command -v "$tool" >/dev/null; then
 		printf "%s: %s not installed but required\n" "$(basename "$0")" "$tool" >&2
 		exit 1
@@ -17,9 +20,9 @@ done
 
 DEFAULT_PROFILE="output:analog-stereo+input:analog-stereo"
 HDMI_PROFILE="output:hdmi-stereo"
-export MENU_PROMPT="soundswitch"
 
 _menu() {
+	export MENU_PROMPT="soundswitch"
 	menu -l 8
 }
 
@@ -49,24 +52,44 @@ usage() {
 	exit 2
 }
 _args="$*"
-while getopts hIOgfpHRmMvV name; do
+while getopts hIOgfpHRmMvVB name; do
 	case ${name} in
-	g) 	mode=global ;;
-	f) 	mode=focused ;;
-	p) 	mode=profile ;;
-	H) 	mode=hdmi ;;
-	R)	mode=reset ;;
-	O)	output=1 ;;
-	I)	input=1 ;;
-	# quick actions, to make move to pipewire easier
-	m)	mode=none ; output=1 ; mute=1 ;;
-	M)	mode=none ; input=1 ; mute=1 ;;
-	v)	mode=none ; output=1 ; volume_down=1 ;;
-	V)	mode=none ; output=1 ; volume_up=1 ;;
-	h|?) 	echo "$*" ; usage ;;
+		g) mode=global ;;
+		f) mode=focused ;;
+		p) mode=profile output=1 input=1 ;;
+		H) mode=hdmi output=1 input=1 ;;
+		R) mode=reset ;;
+		B) mode=mute output=1 input=1 ;;
+		O) output=1 ;;
+		I) input=1 ;;
+		# quick actions, to make move to pipewire easier
+		m)
+			mode=none
+			output=1
+			mute=1
+			;;
+		M)
+			mode=none
+			input=1
+			mute=1
+			;;
+		v)
+			mode=none
+			output=1
+			volume_down=1
+			;;
+		V)
+			mode=none
+			output=1
+			volume_up=1
+			;;
+		h | ?)
+			echo "$*"
+			usage
+			;;
 	esac
 done
-shift $((OPTIND-1))
+shift $((OPTIND - 1))
 
 # ask mode
 _mode_global="󰣨 global"
@@ -74,17 +97,20 @@ _mode_focused="󰻿 focused"
 _mode_profile_card="󱂇 card profile"
 _mode_profile_hdmi="󰡁 hdmi profile"
 _mode_profile_reset="󰕍 reset profile"
+_mode_mute="󰂛 mute"
 if [ -z "$mode" ]; then
-	printf "$_mode_global\n$_mode_focused\n$_mode_profile_card\n$_mode_profile_hdmi\n$_mode_profile_reset\n" | _menu | {
+	printf "$_mode_global\n$_mode_focused\n$_mode_profile_card\n$_mode_profile_hdmi\n$_mode_profile_reset\n$_mode_mute\n" | _menu | {
 		read -r line
 		case ${line} in
-		$_mode_global)        exec "$0" -g "$*" ;;
-		$_mode_focused)       exec "$0" -f "$*" ;;
-		$_mode_profile_card)  exec "$0" -p "$*" ;;
-		$_mode_profile_hdmi)  exec "$0" -H "$*" ;;
-		$_mode_profile_reset) exec "$0" -R "$*" ;;
-		*)
-			usage ;;
+			$_mode_global) exec "$0" -g "$*" ;;
+			$_mode_focused) exec "$0" -f "$*" ;;
+			$_mode_profile_card) exec "$0" -p "$*" ;;
+			$_mode_profile_hdmi) exec "$0" -H "$*" ;;
+			$_mode_profile_reset) exec "$0" -R "$*" ;;
+			$_mode_mute) exec "$0" -B "$*" ;;
+			*)
+				usage
+				;;
 		esac
 	}
 	exit $?
@@ -98,9 +124,9 @@ if [ -n "$mode" ]; then
 		printf "$_mode_output\n$_mode_input\n" | _menu | {
 			read -r line
 			case ${line} in
-			$_mode_output) exec "$0" -O $_args ;;
-			$_mode_input)  exec "$0" -I $_args ;;
-			*)      usage ;;
+				$_mode_output) exec "$0" -O $_args ;;
+				$_mode_input) exec "$0" -I $_args ;;
+				*) usage ;;
 			esac
 		}
 	fi
@@ -112,110 +138,123 @@ fi
 [ -n "$volume_down" ] && exec pactl set-sink-volume '@DEFAULT_SINK@' '-5%'
 [ -n "$volume_up" ] && exec pactl set-sink-volume '@DEFAULT_SINK@' '+5%'
 
-[ -n "$output" ] && keyword="sink"   && what="sink-input"
-[ -n "$input" ]  && keyword="source" && what="source-output"
+[ -n "$output" ] && keyword="sink" && what="sink-input"
+[ -n "$input" ] && keyword="source" && what="source-output"
 
 # returns the sink_id
-selects()
-{
-	pactl list short "${1}s" | cut -f2 | menu
+selects() {
+	pactl list short "${1}s" | cut -f2 | _menu
 }
-notif()
-{
+notif() {
 	topic=$1
 	mesg=$2
 	urgency=normal
 	[ -n "$3" ] && urgency=critical
-	notify-send -u ${urgency} -a soundswitch "soundswitch: ${topic}" "${mesg}"
+	notify-send -i /usr/share/icons/mdi/scalable/cast-audio.svg -u ${urgency} -a soundswitch "soundswitch: ${topic}" "${mesg}"
 }
 
 case ${mode} in
-global)
-	target=$(selects "${keyword}")
-	if [ -z "${target}" ]; then
-		notif "focused" "no target ${what}" err
-		exit 1
-	fi
+	global)
+		target=$(selects "${keyword}")
+		if [ -z "${target}" ]; then
+			notif "focused" "no target ${what}" err
+			exit 1
+		fi
 
-	notif "global" "switch to <i>${target}</i>"
-	# set new default
-	pactl "set-default-${keyword}" "${target}"
-	# move all attached to the new target
-	pactl list "${what}s" short | cut -f1 \
-		| xargs -i pactl "move-${what}" "{}" "${target}"
-	;;
-
-focused)
-	# parse pid of currently focused container
-	# then get the sink-inputs for it and parse the sink-input-id
-	focused_binary=$(swq current con | jq -r '.pid' | xargs ps -o comm=)
-	what_id=$(printf "%s\n" "${focused_binary}" \
-		| xargs pactl list "${what}s" \
+		notif "global" "switch to <i>${target}</i>"
+		# set new default
+		pactl "set-default-${keyword}" "${target}"
+		# move all attached to the new target
+		pactl list "${what}s" short | cut -f1 \
+			| xargs -i pactl "move-${what}" "{}" "${target}"
+		;;
+
+	focused)
+		# parse pid of currently focused container
+		# then get the sink-inputs for it and parse the sink-input-id
+		focused_binary=$(swq current con | jq -r '.pid' | xargs ps -o comm=)
+		what_id=$(printf "%s\n" "${focused_binary}" \
+			| xargs pactl list "${what}s" \
 			| tr '\n' '\r' \
 			| perl -pe 's/[^\r]*index: ([0-9]+).+?application\.process\.binary = "([^\r]+)"\r.+?(?=index:|$)/\2=\1\r/g' \
 			| tr '\r' '\n' \
 			| grep "^{}" \
-		| cut -d'=' -f2)
-	if [ -z "${what_id}" ]; then
-		notif "focused" "no ${what}" err
-		exit 1
-	fi
+			| cut -d'=' -f2)
+		if [ -z "${what_id}" ]; then
+			notif "focused" "no ${what}" err
+			exit 1
+		fi
 
-	# select where to switch to
-	target=$(selects "${keyword}")
-	if [ -z "${target}" ]; then
-		exit 1
-	fi
+		# select where to switch to
+		target=$(selects "${keyword}")
+		if [ -z "${target}" ]; then
+			exit 1
+		fi
 
-	notif "${focused_binary}" "switch to <i>${target}</i>"
-        pactl move-sink-input "${what_id}" "${target}"
-	;;
+		notif "${focused_binary}" "switch to <i>${target}</i>"
+		pactl move-sink-input "${what_id}" "${target}"
+		;;
 
-profile)
-	target_card=$(selects "card")
-	if [ -z "${target_card}" ]; then
-		exit 1
-	fi
+	profile)
+		target_card=$(selects "card")
+		if [ -z "${target_card}" ]; then
+			exit 1
+		fi
 
-	# [^\r]#([0-9]+).*?Name: ([^\r]+).*?Profiles:\r(.*?)Active.*?(?=Card #|$)
-	target_profile=$(pactl list cards \
-		| tr '\n' '\r' \
-		| perl -pe 's/[^\r]#([0-9]+).*?Name: ([^\r]+).*?Profiles:\r(.*?)Active.*?(?=Card #|$)/\2~\3`/g' \
-		| tr -d '\t' \
-		| tr '`' '\n' \
-		| grep "${target_card}" \
-		| cut -d'~' -f2- \
-		| tr '\r' '\n' \
-		| xargs -L1 \
-		| _menu \
-		| perl -pe 's/^\s*([^\s]+):\s.*$/\1/')
-	if [ -z "${target_profile}" ]; then
-		exit 1
-	fi
+		# [^\r]#([0-9]+).*?Name: ([^\r]+).*?Profiles:\r(.*?)Active.*?(?=Card #|$)
+		target_profile=$(pactl list cards \
+			| tr '\n' '\r' \
+			| perl -pe 's/[^\r]#([0-9]+).*?Name: ([^\r]+).*?Profiles:\r(.*?)Active.*?(?=Card #|$)/\2~\3`/g' \
+			| tr -d '\t' \
+			| tr '`' '\n' \
+			| grep "${target_card}" \
+			| cut -d'~' -f2- \
+			| tr '\r' '\n' \
+			| xargs -L1 \
+			| _menu \
+			| perl -pe 's/^\s*([^\s]+):\s.*$/\1/')
+		if [ -z "${target_profile}" ]; then
+			exit 1
+		fi
 
-	notif "profile" "switch <i>${target_card}</i> to ${target_profile} profile"
-	pactl set-card-profile "${target_card}" "${target_profile}"
-	;;
+		notif "profile" "switch <i>${target_card}</i> to ${target_profile} profile"
+		pactl set-card-profile "${target_card}" "${target_profile}"
+		;;
 
-hdmi)
-	target_card=$(selects "card")
-	if [ -z "${target_card}" ]; then
-		exit 1
-	fi
+	hdmi)
+		target_card=$(selects "card")
+		if [ -z "${target_card}" ]; then
+			exit 1
+		fi
 
-	notif "hdmi" "switch <i>${target_card}</i> to HDMI profile"
-	pactl set-card-profile "${target_card}" ${HDMI_PROFILE}
-	exit $?
-	;;
+		notif "hdmi" "switch <i>${target_card}</i> to HDMI profile"
+		pactl set-card-profile "${target_card}" ${HDMI_PROFILE}
+		exit $?
+		;;
 
-reset)
-	target_card=$(selects "card")
-	if [ -z "${target_card}" ]; then
-		exit 1
-	fi
+	reset)
+		target_card=$(selects "card")
+		if [ -z "${target_card}" ]; then
+			exit 1
+		fi
+
+		notif "reset" "switch <i>${target_card}</i> to default profile"
+		pactl set-card-profile "${target_card}" ${DEFAULT_PROFILE}
+		;;
+
+	mute)
+		_data=$(pw-dump -N | jq -r '.. | select(.type?=="PipeWire:Interface:Node" and .info.props."media.class"?=="Stream/Output/Audio" and .info.props."stream.is-live"?==true) | @text')
+		_id=$(printf "${_data}" | jq -r '. | [ .info.props."application.name", .info.props."application.process.id", .id ] | @tsv' \
+			| awk '{ system("swaymsg -t get_tree | jq -r \".. | select(.pid?==" $2 ") | .name\" | grep -v Picture-in-Picture | tr -d \"\\n\""); printf " [%s]", $3 }' \
+			| _menu \
+			| perl -pe 's/^.*\[([0-9]+)\]$/\1/')
+		if [ -z "${_id}" ]; then
+			exit 1
+		fi
 
-	notif "reset" "switch <i>${target_card}</i> to default profile"
-	pactl set-card-profile "${target_card}" ${DEFAULT_PROFILE}
-	;;
+		_oid=$(printf "${_data}" | jq -r --argjson id "${_id}" '.. | select(.id?==$id) | .info.props."object.id"')
+		_target_state=$(printf "${_data}" | jq -r --argjson id "${_id}" '.. | select(.id?==$id) | .info.params.Props[0] | .mute = (.mute | not) | @text')
+		pw-cli set-param "${_oid}" Props "${_target_state}"
+		;;
 
 esac