Skip to content

Instantly share code, notes, and snippets.

@Atrate
Last active August 28, 2023 09:32
Show Gist options
  • Save Atrate/e65027fdde2aa962a11a3f4a94f6fb08 to your computer and use it in GitHub Desktop.
Save Atrate/e65027fdde2aa962a11a3f4a94f6fb08 to your computer and use it in GitHub Desktop.
Bash script to adjust the volume of the currently focused window
#!/bin/bash --posix
# Copyright (C) 2020-2023 Atrate <atrate@protonmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>
# $1 is the variable passed to this script. It should be in the format:
# '-x%' or '+x%'. For example: '-2%' will lower the volume by 2%. with each
# execution of the script.
# Perform [[ comparisons in a case-insensitive way
# ------------------------------------------------
shopt -s nocasematch
# descendent_pids() courtesy of https://unix.stackexchange.com/a/299198
# descendent_pids() function lists all descendant process IDs of a given PID
# --------------------------------------------------------------------------
descendent_pids()
{
# Get the child process IDs of the given process ID
# -------------------------------------------------
pids=$(pgrep -P "$1")
echo "$pids"
# For each child process, get its descendant process IDs
# ------------------------------------------------------
for pid in $pids;
do
descendent_pids "$pid"
done
}
# For space-delimited strings, check whether they contain an exact word match
# ---------------------------------------------------------------------------
word_match()
{
for word in $2
do
if [ "$word" = "$1" ]
then
return 0
fi
done
return 1
}
# TODO: Make this nicer, maybe with an associative array?
# -------------------------------------------------------
process_paout()
{
while read -r word
do
if [ -z "$papid" ]; then
local papid=$word
elif [ -z "$paname" ]; then
local paname=$word
elif [ -z "$index" ]; then
local index=$word
fi
if [ -n "$index" ]; then
if [[ "$wclass" == *"$paname"* ]] \
|| word_match "$papid" "$2"
then
pactl set-sink-input-volume "$index" "$1" &
return 0
fi
unset index papid paname
fi
done <<< "$paout"
# Return 1 if nothing has been found
# ----------------------------------
return 1
}
# Main program functionality
# --------------------------
main()
{
# Get the process ID of the window that has focus and the process IDs of its
# descendants
# --------------------------------------------------------------------------
wpid=$(xdotool getwindowfocus getwindowpid)
wclass=$(xdotool getwindowfocus getwindowclassname)
# Exit if no window is focused
# ----------------------------
if [ -z "$wpid" ] || [ -z "$wclass" ]
then
exit 1
fi
# Get the list of sink inputs, including their process IDs, and process names
# and indices (serials)
# ---------------------------------------------------------------------------
paout=$(pactl list sink-inputs \
| grep -e "object.serial" -e "process.id" -e "process.binary" \
| cut -d "\"" -f2)
# For each sink input, check if its process ID or process name is associated
# with the focused window or its descendants if we *really* need to
# --------------------------------------------------------------------------
process_paout "$1" "$wpid" && exit 0
pstree=$(echo "$wpid"; descendent_pids "$wpid")
process_paout "$1" "$pstree" || exit 2
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment