1274 lines
37 KiB
Bash
Executable File
1274 lines
37 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# i3-volume
|
|
#
|
|
# Volume control and volume notifications.
|
|
#
|
|
# Dependencies:
|
|
# awk (POSIX compatible)
|
|
# pulseaudio-utils - if using PulseAudio
|
|
# alsa-utils - if using amixer
|
|
#
|
|
# Copyright (c) 2016 Beau Hastings. All rights reserved.
|
|
# License: GNU General Public License v2
|
|
#
|
|
# Author: Beau Hastings <beau@saweet.net>
|
|
# URL: https://github.com/hastinbe/i3-volume
|
|
# Wiki: https://github.com/hastinbe/i3-volume/wiki/
|
|
|
|
define_helpers() {
|
|
empty() {
|
|
[[ -z $1 ]]
|
|
}
|
|
|
|
not_empty() {
|
|
[[ -n $1 ]]
|
|
}
|
|
|
|
isset() {
|
|
[[ -v $1 ]]
|
|
}
|
|
|
|
command_exists() {
|
|
command -v "$1" >/dev/null 2>&1;
|
|
}
|
|
|
|
error() {
|
|
echo "$COLOR_RED$*$COLOR_RESET"
|
|
}
|
|
|
|
has_color() {
|
|
(( $(tput colors 2>/dev/null || echo 0) >= 8 )) && [ -t 1 ]
|
|
}
|
|
|
|
# Converts milliseconds to seconds with rounding up
|
|
#
|
|
# Arguments:
|
|
# milliseconds (integer) An integer in milliseconds
|
|
ms_to_secs() {
|
|
echo $(( ($1 + (1000 - 1)) / 1000 ))
|
|
}
|
|
|
|
is_command_hookable() {
|
|
! [[ ${POST_HOOK_EXEMPT_COMMANDS[*]} =~ $1 ]]
|
|
}
|
|
|
|
has_capability() {
|
|
[[ "${NOTIFY_CAPS[*]}" =~ $1 ]]
|
|
}
|
|
|
|
max() {
|
|
echo $(( $1 > $2 ? $1 : $2 ))
|
|
}
|
|
}
|
|
|
|
define_notify() {
|
|
# Display a notification indicating muted or current volume.
|
|
notify_volume() {
|
|
local -r vol=$(get_volume)
|
|
local icon
|
|
|
|
if is_muted; then
|
|
text="Volume muted"
|
|
|
|
if $USE_FULLCOLOR_ICONS; then
|
|
icon=${ICONS[0]}
|
|
else
|
|
icon=${ICONS_SYMBOLIC[0]}
|
|
fi
|
|
else
|
|
printf -v text "Volume %3s%%" "$vol"
|
|
|
|
icon=$(get_volume_icon "$vol")
|
|
|
|
if $SHOW_VOLUME_PROGRESS; then
|
|
local -r progress=$(progress_bar "$vol")
|
|
text="$text $progress"
|
|
fi
|
|
fi
|
|
|
|
case "$NOTIFICATION_METHOD" in
|
|
xosd ) notify_volume_xosd "$vol" "$text" ;;
|
|
herbe ) notify_volume_herbe "$text" ;;
|
|
volnoti ) notify_volume_volnoti "$vol" ;;
|
|
kosd ) notify_volume_kosd "$vol" ;;
|
|
dunst ) notify_volume_libnotify "$vol" "$icon" "$text" ;;
|
|
notify-osd ) notify_volume_libnotify "$vol" "$icon" "$text" ;;
|
|
libnotify ) notify_volume_libnotify "$vol" "$icon" "$text" ;;
|
|
haskell-notification-daemon) notify_volume_libnotify "$vol" "$icon" "$text" ;;
|
|
* ) notify_volume_libnotify "$vol" "$icon" "$text" ;;
|
|
esac
|
|
}
|
|
|
|
list_notification_methods() {
|
|
awk -W posix 'match($0,/ notify_volume_([[:alnum:]]+)/) {print substr($0, 19, RLENGTH-18)}' "${BASH_SOURCE[0]}" || exit "$EX_USAGE"
|
|
exit "$EX_OK"
|
|
}
|
|
|
|
setup_notification_icons() {
|
|
if not_empty "$SYMBOLIC_ICON_SUFFIX"; then
|
|
apply_symbolic_icon_suffix
|
|
fi
|
|
}
|
|
|
|
show_volume_notification() {
|
|
$DISPLAY_NOTIFICATIONS || return
|
|
|
|
if empty "$NOTIFICATION_METHOD"; then
|
|
load_notify_server_info
|
|
NOTIFICATION_METHOD=$NOTIFY_SERVER
|
|
fi
|
|
|
|
setup_notification_icons
|
|
notify_volume
|
|
}
|
|
|
|
# Loads notification system information via DBus
|
|
load_notify_server_info() {
|
|
command_exists dbus-send || return
|
|
IFS=$'\t' read -r NOTIFY_SERVER _ _ _ < <(dbus-send --print-reply --dest=org.freedesktop.Notifications /org/freedesktop/Notifications org.freedesktop.Notifications.GetServerInformation | awk 'BEGIN { ORS="\t" }; match($0, /^ string ".*"/) {print substr($0, RSTART+11, RLENGTH-12)}')
|
|
}
|
|
|
|
# Load notification system capabilities via DBus
|
|
load_notify_server_caps() {
|
|
command_exists dbus-send || return
|
|
IFS= read -r -d '' -a NOTIFY_CAPS < <(dbus-send --print-reply=literal --dest="${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_FDN}.GetCapabilities" | awk 'RS=" " { if (NR > 2) print $1 }')
|
|
}
|
|
|
|
# Send notifcation for libnotify-compatible notification daemons.
|
|
#
|
|
# Arguments:
|
|
# Volume (integer) An integer indicating the volume.
|
|
# Icon (string) Icon to display.
|
|
# Text (string) Notification text.
|
|
notify_volume_libnotify() {
|
|
local -r vol=$1
|
|
local -r icon=$2
|
|
local -r text=${*:3}
|
|
local -a args=(
|
|
-t "$EXPIRES"
|
|
)
|
|
local -a hints=(
|
|
# Replaces previous notification in some notification servers
|
|
string:synchronous:volume
|
|
# Replaces previous notification in NotifyOSD
|
|
string:x-canonical-private-synchronous:volume
|
|
)
|
|
|
|
# If we're not drawing our own progress bar, allow the notification daemon to draw its own (if supported)
|
|
if ! $SHOW_VOLUME_PROGRESS; then
|
|
hints+=(int:value:"$vol")
|
|
fi
|
|
|
|
(( ${#NOTIFY_CAPS[@]} < 1 )) && load_notify_server_caps
|
|
|
|
if has_capability icon-static; then
|
|
args+=(-i "$icon")
|
|
|
|
# haskell-notification-daemon (aka deadd-notification-center) does not support -i|--icon
|
|
hints+=(string:image-path:"$icon")
|
|
fi
|
|
|
|
if $PLAY_SOUND && has_capability sound; then
|
|
hints+=(string:sound-name:audio-volume-change)
|
|
fi
|
|
|
|
if ! isset NO_NOTIFY_COLOR && [[ $NOTIFICATION_METHOD == "dunst" ]]; then
|
|
if is_muted; then
|
|
hints+=(string:fgcolor:"$COLOR_MUTED")
|
|
else
|
|
hints+=(string:fgcolor:"$(volume_color "$vol")")
|
|
fi
|
|
fi
|
|
|
|
if $USE_DUNSTIFY; then
|
|
args+=(-r 1000)
|
|
|
|
# Transient notifications will bypass the idle_threshold setting.
|
|
# Should be boolean, but Notify-OSD doesn't support boolean yet. Dunst checks
|
|
# for int and bool with transient so lets play nice with both servers.
|
|
hints+=(int:transient:1)
|
|
|
|
read -ra hints <<< "${hints[@]/#/-h }"
|
|
"${DUNSTIFY_PATH:+${DUNSTIFY_PATH%/}/}dunstify" "${hints[@]}" "${args[@]}" "$text"
|
|
elif isset USE_NOTIFY_SEND_PY; then
|
|
# Replaces previous notification, but leaves itself running in the bg to work
|
|
args+=(--replaces-process volume)
|
|
|
|
# By-pass the server's persistence capability, if it should exist
|
|
hints+=(boolean:transient:true)
|
|
|
|
"${NOTIFY_SEND_PATH:+${NOTIFY_SEND_PATH%/}/}notify-send.py" --hint "${hints[@]}" "${args[@]}" "$text" &
|
|
else
|
|
read -ra hints <<< "${hints[@]/#/-h }"
|
|
"${NOTIFY_SEND_PATH:+${NOTIFY_SEND_PATH%/}/}notify-send" "${hints[@]}" "${args[@]}" "$text"
|
|
fi
|
|
}
|
|
|
|
# Send notification to XOSD.
|
|
#
|
|
# Arguments:
|
|
# Volume (integer) An integer indicating the volume.
|
|
# Text (string) Notification text.
|
|
notify_volume_xosd() {
|
|
local -r vol=$1
|
|
local -r text=${*:2}
|
|
local -r delay=$(ms_to_secs "$EXPIRES")
|
|
local percentage
|
|
|
|
if is_muted; then
|
|
color=$COLOR_MUTED
|
|
percentage=0
|
|
else
|
|
color=$(volume_color "$vol")
|
|
percentage=$vol
|
|
fi
|
|
|
|
"${XOSD_PATH:+${XOSD_PATH%/}/}osd_cat" --align center -b percentage -P "$percentage" -d "$delay" -p top -A center -c "$color" -T "$text" -O 2 -u "$COLOR_XOSD_OUTLINE" & disown
|
|
}
|
|
|
|
# Send notification to herbe.
|
|
#
|
|
# Arguments:
|
|
# Text (string) Notification text.
|
|
#
|
|
# Note: a patch with a notify-send script for herbe, not in the current version at this
|
|
# time but would make this irrelevant. See https://github.com/dudik/herbe/pull/10
|
|
notify_volume_herbe() {
|
|
local -r text=$*
|
|
|
|
# Dismiss existing/pending notifications to prevent queuing
|
|
pkill -SIGUSR1 herbe
|
|
|
|
"${HERBE_PATH:+${HERBE_PATH%/}/}herbe" "$text" & disown
|
|
}
|
|
|
|
# Send notification to volnoti.
|
|
#
|
|
# Arguments:
|
|
# Volume (integer) An integer indicating the volume.
|
|
notify_volume_volnoti() {
|
|
local -r vol=$1
|
|
|
|
if is_muted; then
|
|
"${VOLNOTI_PATH:+${VOLNOTI_PATH%/}/}volnoti-show" -m "$vol"
|
|
else
|
|
"${VOLNOTI_PATH:+${VOLNOTI_PATH%/}/}volnoti-show" "$vol"
|
|
fi
|
|
}
|
|
|
|
# Send notification to KOSD.
|
|
#
|
|
# Arguments:
|
|
# Volume (integer) An integer indicating the volume.
|
|
notify_volume_kosd() {
|
|
local -r vol=$1
|
|
|
|
if is_muted; then
|
|
qdbus org.kde.kded /modules/kosd showVolume "$vol" 1
|
|
else
|
|
qdbus org.kde.kded /modules/kosd showVolume "$vol" 0
|
|
fi
|
|
}
|
|
}
|
|
|
|
define_output_formats() {
|
|
# Outputs the current volume in the default format.
|
|
output_volume_default() {
|
|
if is_muted; then
|
|
echo MUTE
|
|
else
|
|
echo "$(get_volume)%"
|
|
fi
|
|
}
|
|
|
|
# Outputs the current volume using a custom format string.
|
|
#
|
|
# Format options:
|
|
# %v = volume percentage or "MUTE" when muted
|
|
# %s = sink name (PulseAudio only)
|
|
# %c = card (alsamixer only)
|
|
# %m = mixer (alsamixer only)
|
|
# %p = volume progress bar
|
|
# %i = volume icon
|
|
output_volume_custom() {
|
|
local -r format=$*
|
|
local -r vol=$(get_volume)
|
|
local string
|
|
|
|
if is_muted; then
|
|
string=${format//\%v/MUTE}
|
|
else
|
|
string=${format//\%v/$vol%}
|
|
fi
|
|
|
|
string=${string//\%s/$SINK}
|
|
string=${string//\%c/$CARD}
|
|
string=${string//\%m/$MIXER}
|
|
string=${string//\%p/$(progress_bar "$vol")}
|
|
string=${string//\%i/$(get_volume_emoji "$vol")}
|
|
|
|
echo -ne "$string"
|
|
}
|
|
|
|
# Outputs the current volume for i3blocks.
|
|
output_volume_i3blocks() {
|
|
local short_text
|
|
local full_text
|
|
|
|
if is_muted; then
|
|
short_text="<span color=\"$COLOR_MUTED\">MUTE</span>\n"
|
|
full_text="<span color=\"$COLOR_MUTED\">MUTE</span>\n"
|
|
else
|
|
local -r vol=$(get_volume)
|
|
local -r color=$(volume_color "$vol")
|
|
|
|
short_text="<span color=\"$color\">${vol}%</span>\n"
|
|
full_text="<span color=\"$color\">${vol}%</span>\n"
|
|
|
|
if isset MAX_VOLUME && (( vol > MAX_VOLUME )); then
|
|
EXITCODE=$EX_URGENT
|
|
fi
|
|
fi
|
|
|
|
echo -ne "$full_text$short_text"
|
|
}
|
|
|
|
# Outputs the current volume for xob.
|
|
output_volume_xob() {
|
|
local -ir vol=$(get_volume)
|
|
|
|
if is_muted; then
|
|
echo "${vol}!"
|
|
else
|
|
echo "$vol"
|
|
fi
|
|
}
|
|
}
|
|
|
|
define_commands() {
|
|
# Increase volume relative to current volume.
|
|
#
|
|
# Arguments:
|
|
# Step (integer) Percentage to increase by.
|
|
# Max Volume (optional) (integer|percentage) Maximum volume limit.
|
|
increase_volume() {
|
|
local step=${1:?$(error 'Step is required')}
|
|
local -r max_volume=$2
|
|
|
|
if not_empty "$max_volume"; then
|
|
local -r vol=$(get_volume)
|
|
|
|
if (( vol + step > max_volume )); then
|
|
# Instead of doing nothing, step to max_volume
|
|
step=$( max "0" "$(( max_volume - vol ))" )
|
|
fi
|
|
fi
|
|
|
|
if $USE_AMIXER; then
|
|
amixer_increase_volume "$CARD" "$step"
|
|
else
|
|
pa_increase_volume "$SINK" "$step"
|
|
fi
|
|
}
|
|
|
|
# Decrease volume relative to current volume.
|
|
#
|
|
# Arguments:
|
|
# Step (integer) Percentage to decrease by.
|
|
decrease_volume() {
|
|
local -r step=${1:?$(error 'Step is required')}
|
|
|
|
if $USE_AMIXER; then
|
|
amixer_decrease_volume "$CARD" "$step"
|
|
else
|
|
pa_decrease_volume "$SINK" "$step"
|
|
fi
|
|
}
|
|
|
|
# Set volume.
|
|
#
|
|
# Arguments:
|
|
# Volume (integer|linear factor|percentage|decibel)
|
|
# Max Volume (optional) (integer|percentage) Maximum volume limit.
|
|
set_volume() {
|
|
local -r vol=${1:?$(error 'Volume is required')}
|
|
local -r max_volume=$2
|
|
|
|
if not_empty "$max_volume" && (( vol > max_volume )); then
|
|
return
|
|
fi
|
|
|
|
if $USE_AMIXER; then
|
|
amixer_set_volume "${vol}%" "$CARD"
|
|
else
|
|
pa_set_volume "$SINK" "${vol}%"
|
|
fi
|
|
}
|
|
|
|
toggle_mute() {
|
|
if $USE_AMIXER; then
|
|
amixer_toggle_mute "$CARD"
|
|
else
|
|
pa_toggle_mute "$SINK"
|
|
fi
|
|
}
|
|
|
|
# Outputs the current volume.
|
|
#
|
|
# Arguments
|
|
# Output method (string) Method to use to output volume.
|
|
output_volume() {
|
|
local -r for=${1:?$(error 'Output method is required')}
|
|
|
|
case "$for" in
|
|
i3blocks ) output_volume_i3blocks ;;
|
|
xob ) output_volume_xob ;;
|
|
default ) output_volume_default ;;
|
|
* ) output_volume_custom "$*" ;;
|
|
esac
|
|
}
|
|
|
|
list_output_formats() {
|
|
awk -W posix 'match($0,/ output_volume_([[:alnum:]]+)/) {print substr($0, 19, RLENGTH-18)}' "${BASH_SOURCE[0]}" || exit "$EX_USAGE"
|
|
exit "$EX_OK"
|
|
}
|
|
|
|
usage() {
|
|
cat <<- EOF 1>&2
|
|
${COLOR_YELLOW}Usage:${COLOR_RESET} $0 [<options>] <command> [<args>]
|
|
Control volume and related notifications.
|
|
|
|
${COLOR_YELLOW}Commands:${COLOR_RESET}
|
|
${COLOR_GREEN}up <value>${COLOR_RESET} increase volume
|
|
${COLOR_GREEN}down <value>${COLOR_RESET} decrease volume
|
|
${COLOR_GREEN}set <value>${COLOR_RESET} set volume
|
|
${COLOR_GREEN}mute${COLOR_RESET} toggle mute
|
|
${COLOR_GREEN}listen${COLOR_RESET} listen for changes to a PulseAudio sink
|
|
${COLOR_GREEN}output <format>${COLOR_RESET} output volume in a supported format
|
|
custom format substitutions:
|
|
%v = volume
|
|
%s = sink name (PulseAudio only)
|
|
%c = card (alsamixer only)
|
|
%m = mixer (alsamixer only)
|
|
%p = volume progress bar
|
|
%i = volume icon/emoji
|
|
|
|
examples:
|
|
"Volume is %v" = Volume is 50%
|
|
"%i %v %p \n" = 奔 50% ██████████
|
|
${COLOR_GREEN}outputs${COLOR_RESET} show available output formats
|
|
${COLOR_GREEN}notifications${COLOR_RESET} show available notification methods
|
|
${COLOR_GREEN}help${COLOR_RESET} display help
|
|
|
|
${COLOR_YELLOW}Options:${COLOR_RESET}
|
|
${COLOR_GREEN}-a${COLOR_RESET} use amixer
|
|
${COLOR_GREEN}-n${COLOR_RESET} enable notifications
|
|
${COLOR_GREEN}-C${COLOR_RESET} use libcanberra for playing event sounds
|
|
${COLOR_GREEN}-P${COLOR_RESET} play sound for volume changes
|
|
${COLOR_GREEN}-j <muted,high,low,medium>${COLOR_RESET} specify custom volume emojis as a comma separated list
|
|
${COLOR_GREEN}-t <process_name>${COLOR_RESET} process name of status bar (${COLOR_MAGENTA}requires -u${COLOR_RESET})
|
|
${COLOR_GREEN}-u <signal>${COLOR_RESET} signal to update status bar (${COLOR_MAGENTA}requires -t${COLOR_RESET})
|
|
${COLOR_GREEN}-x <value>${COLOR_RESET} maximum volume
|
|
${COLOR_GREEN}-X <value>${COLOR_RESET} maximum amplification; if supported (${COLOR_MAGENTA}default: 2${COLOR_RESET})
|
|
${COLOR_GREEN}-h${COLOR_RESET} display help
|
|
|
|
${COLOR_YELLOW}amixer Options:${COLOR_RESET}
|
|
${COLOR_GREEN}-c <card>${COLOR_RESET} card number to control
|
|
${COLOR_GREEN}-m <mixer>${COLOR_RESET} set mixer (${COLOR_MAGENTA}default: Master${COLOR_RESET})
|
|
|
|
${COLOR_YELLOW}PulseAudio Options:${COLOR_RESET}
|
|
${COLOR_GREEN}-s <sink>${COLOR_RESET} symbolic name of sink
|
|
|
|
${COLOR_YELLOW}Notification Options:${COLOR_RESET}
|
|
${COLOR_GREEN}-N <method>${COLOR_RESET} notification method (${COLOR_MAGENTA}default: libnotify${COLOR_RESET})
|
|
${COLOR_GREEN}-p${COLOR_RESET} enable progress bar
|
|
${COLOR_GREEN}-e <expires>${COLOR_RESET} expiration time of notifications in ms
|
|
${COLOR_GREEN}-l${COLOR_RESET} use fullcolor instead of symbolic icons
|
|
${COLOR_GREEN}-S <suffix>${COLOR_RESET} append suffix to symbolic icon names
|
|
${COLOR_GREEN}-y${COLOR_RESET} use dunstify (${COLOR_MAGENTA}default: notify-send${COLOR_RESET})
|
|
|
|
${COLOR_YELLOW}Environment Variables:${COLOR_RESET}
|
|
${COLOR_CYAN}XOSD_PATH${COLOR_RESET} path to osd_cat
|
|
${COLOR_CYAN}HERBE_PATH${COLOR_RESET} path to herbe
|
|
${COLOR_CYAN}VOLNOTI_PATH${COLOR_RESET} path to volnoti-show
|
|
${COLOR_CYAN}DUNSTIFY_PATH${COLOR_RESET} path to dunstify
|
|
${COLOR_CYAN}CANBERRA_PATH${COLOR_RESET} path to canberra-gtk-play
|
|
${COLOR_CYAN}NOTIFY_SEND_PATH${COLOR_RESET} path to notify-send or notify-send.py
|
|
${COLOR_CYAN}USE_NOTIFY_SEND_PY${COLOR_RESET} flag to use notify-send.py instead of notify-send
|
|
${COLOR_CYAN}NO_NOTIFY_COLOR${COLOR_RESET} flag to disable colors in notifications
|
|
EOF
|
|
exit "$EX_USAGE"
|
|
}
|
|
}
|
|
|
|
# Get the volume as a percentage.
|
|
get_volume() {
|
|
if $USE_AMIXER; then
|
|
amixer_get_volume "$CARD" "$MIXER"
|
|
else
|
|
pa_get_volume "$SINK"
|
|
fi
|
|
}
|
|
|
|
is_muted() {
|
|
if $USE_AMIXER; then
|
|
amixer_is_muted "$CARD"
|
|
return $?
|
|
else
|
|
pa_is_muted "$SINK"
|
|
return $?
|
|
fi
|
|
}
|
|
|
|
# Gets an icon for the provided volume.
|
|
#
|
|
# Arguments:
|
|
# Volume (integer) An integer indicating the volume.
|
|
#
|
|
# Returns:
|
|
# The volume icon name.
|
|
get_volume_icon() {
|
|
local -r vol=${1:?$(error 'Volume is required')}
|
|
local icon
|
|
|
|
if $USE_FULLCOLOR_ICONS; then
|
|
if (( vol >= 70 )); then icon=${ICONS[1]}
|
|
elif (( vol >= 40 )); then icon=${ICONS[3]}
|
|
elif (( vol > 0 )); then icon=${ICONS[2]}
|
|
else icon=${ICONS[2]}
|
|
fi
|
|
else
|
|
# Get overamplified icon if available, otherwise default to high volume icon
|
|
if (( vol > 100 )); then icon=${ICONS_SYMBOLIC[4]:-${ICONS_SYMBOLIC[1]}}
|
|
elif (( vol >= 70 )); then icon=${ICONS_SYMBOLIC[1]}
|
|
elif (( vol >= 40 )); then icon=${ICONS_SYMBOLIC[3]}
|
|
elif (( vol > 0 )); then icon=${ICONS_SYMBOLIC[2]}
|
|
else icon=${ICONS_SYMBOLIC[2]}
|
|
fi
|
|
fi
|
|
|
|
echo "$icon"
|
|
}
|
|
|
|
# Gets an emoji for the provided volume.
|
|
#
|
|
# Arguments:
|
|
# Volume (integer) An integer indicating the volume.
|
|
#
|
|
# Returns:
|
|
# The volume emoji.
|
|
get_volume_emoji() {
|
|
local -r vol=${1:?$(error 'Volume is required')}
|
|
local icon
|
|
|
|
if is_muted; then
|
|
icon=${ICONS_EMOJI[0]}
|
|
else
|
|
if (( vol >= 70 )); then icon=${ICONS_EMOJI[1]}
|
|
elif (( vol >= 40 )); then icon=${ICONS_EMOJI[3]}
|
|
elif (( vol > 0 )); then icon=${ICONS_EMOJI[2]}
|
|
else icon=${ICONS_EMOJI[2]}
|
|
fi
|
|
fi
|
|
|
|
echo "$icon"
|
|
}
|
|
|
|
# Updates the status line.
|
|
#
|
|
# Arguments:
|
|
# signal (string) The signal used to update the status line.
|
|
# proc (string) The name of the status line process.
|
|
update_statusline() {
|
|
local -r signal=${1:?$(error 'Signal is required')}
|
|
local -r proc=${2:?$(error 'Process name is required')}
|
|
|
|
pkill "-$signal" "$proc"
|
|
}
|
|
|
|
# Generates a progress bar for the provided value.
|
|
#
|
|
# Arguments:
|
|
# Percentage (integer) Percentage of progress.
|
|
# Maximum (integer) Maximum percentage. (default: 100)
|
|
# Divisor (integer) For calculating the ratio of blocks to progress (default: 5)
|
|
#
|
|
# Returns:
|
|
# The progress bar.
|
|
progress_bar() {
|
|
local -r percent=${1:?$(error 'Percentage is required')}
|
|
local -r max_percent=${2:-100}
|
|
local -r divisor=${3:-5}
|
|
local -r progress=$(( (percent > max_percent ? max_percent : percent) / divisor ))
|
|
|
|
printf -v bar "%*s" $progress ""
|
|
echo "${bar// /█}"
|
|
}
|
|
|
|
apply_symbolic_icon_suffix() {
|
|
for i in "${!ICONS_SYMBOLIC[@]}"; do
|
|
ICONS_SYMBOLIC[$i]="${ICONS_SYMBOLIC[$i]}${SYMBOLIC_ICON_SUFFIX}"
|
|
done
|
|
}
|
|
|
|
# Get color for the given volume
|
|
#
|
|
# Arguments:
|
|
# $1 - The volume
|
|
volume_color() {
|
|
local -ir vol=${1:?$(error 'A volume is required')}
|
|
|
|
if $USE_AMIXER; then
|
|
amixer_volume_color "$vol"
|
|
else
|
|
pa_volume_color "$vol"
|
|
fi
|
|
}
|
|
|
|
# Updates the status bars
|
|
#
|
|
# Returns
|
|
# 0 when no problem occurred
|
|
# 1 when one $of signal or $statusline are set but not both
|
|
update_statusbar() {
|
|
if not_empty "$SIGNAL"; then
|
|
if empty "$STATUSLINE"; then
|
|
return 1
|
|
fi
|
|
update_statusline "$SIGNAL" "$STATUSLINE"
|
|
else
|
|
if not_empty "$STATUSLINE"; then
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
setup_audio() {
|
|
if $USE_AMIXER; then
|
|
setup_amixer
|
|
else
|
|
setup_pulseaudio
|
|
fi
|
|
}
|
|
|
|
# All PulseAudio functions are defined here
|
|
define_pulseaudio_functions() {
|
|
# Executes `pactl list sinks` or return its output if called previously
|
|
pa_list_sinks() {
|
|
if $OPT_LISTEN || empty "$PA_LIST_SINKS"; then
|
|
PA_LIST_SINKS=$(pactl list sinks)
|
|
fi
|
|
echo "$PA_LIST_SINKS"
|
|
}
|
|
|
|
pa_invalidate_cache() {
|
|
unset PA_LIST_SINKS
|
|
}
|
|
|
|
pa_default_sink_name() {
|
|
pactl info | awk -F': ' '/^Default Sink: /{print $2}'
|
|
}
|
|
|
|
# Get the index of a sink name.
|
|
#
|
|
# Arguments
|
|
# Sink name (string) Symbolic name of sink.
|
|
pa_get_sink_index() {
|
|
local -r sink=${1:?$(error 'Sink name is required')}
|
|
|
|
pa_list_sinks | \
|
|
awk -W posix '/^Sink #/{gsub("#", ""); idx = $2}
|
|
/^[ \t]+Name: / {insink = $2 == "'"$sink"'"; if (insink) { print idx }; exit}'
|
|
}
|
|
|
|
# Get the volume as a percentage.
|
|
#
|
|
# Arguments
|
|
# Sink name (string) Symbolic name of sink.
|
|
pa_get_volume() {
|
|
local -r sink=${1:?$(error 'Sink name is required')}
|
|
|
|
pa_list_sinks | \
|
|
awk -W posix '/^[ \t]+Name: / {insink = $2 == "'"$sink"'"}
|
|
/^[ \t]+Volume: / && insink {gsub("%,?", ""); print $5; exit}'
|
|
}
|
|
|
|
# Get the max volume as a percentage.
|
|
#
|
|
# Arguments
|
|
# Sink name (string) Symbolic name of sink.
|
|
pa_get_base_volume() {
|
|
local -r sink=${1:?$(error 'Sink name is required')}
|
|
|
|
pa_list_sinks | \
|
|
awk -W posix '/^[ \t]+Name: / {insink = $2 == "'"$sink"'"}
|
|
/^[ \t]+Base Volume: / && insink {gsub("%", ""); print $5; exit}'
|
|
}
|
|
|
|
# Increase volume relative to current volume using pulseaudio.
|
|
#
|
|
# Arguments:
|
|
# Sink name (string) Symbolic name of sink.
|
|
# Step (integer) Percentage to increase by.
|
|
pa_increase_volume() {
|
|
local -r sink=$1
|
|
local -r step=${2:=-5}
|
|
|
|
pa_set_volume "$sink" "+${step}%"
|
|
}
|
|
|
|
# Decrease volume relative to current volume using pulseaudio.
|
|
#
|
|
# Arguments:
|
|
# Sink name (string) Symbolic name of sink.
|
|
# Step (integer|percentage) Percentage to decrease by.
|
|
pa_decrease_volume() {
|
|
local -r sink=$1
|
|
local -r step=${2:=-5}
|
|
|
|
pa_set_volume "$sink" "-${step}%"
|
|
}
|
|
|
|
# Set volume using pulseaudio.
|
|
#
|
|
# Arguments:
|
|
# Sink name (string) Symbolic name of sink.
|
|
# Volume (integer|linear factor|percentage|decibel)
|
|
pa_set_volume() {
|
|
local -r sink=${1:?$(error 'Sink name is required')}
|
|
local -r vol=${2:?$(error 'Volume is required')}
|
|
|
|
pa_invalidate_cache
|
|
|
|
pactl set-sink-volume "$sink" "$vol"
|
|
}
|
|
|
|
# Toggle mute using pulseaudio.
|
|
#
|
|
# Arguments:
|
|
# Sink name (string) Symbolic name of sink.
|
|
pa_toggle_mute() {
|
|
local -r sink=${1:?$(error 'Sink name is required')}
|
|
|
|
pa_invalidate_cache
|
|
|
|
pactl set-sink-mute "$sink" toggle
|
|
}
|
|
|
|
# Check if sink is muted.
|
|
#
|
|
# Arguments:
|
|
# Sink name (string) Symbolic name of sink.
|
|
#
|
|
# Returns:
|
|
# 0 when true, 1 when false.
|
|
pa_is_muted() {
|
|
local -r sink=${1:?$(error 'Sink name is required')}
|
|
|
|
pa_list_sinks | \
|
|
awk -W posix '/^[ \t]+Name: / {insink = $2 == "'"$sink"'"}
|
|
/^[ \t]+Mute: / && insink && $2 ~ /^yes$/ { exitcode=1 }; END { exit !exitcode }'
|
|
}
|
|
|
|
# Get the flags of the PulseAudio sink.
|
|
#
|
|
# Arguments
|
|
# Sink name (string) Symbolic name of sink.
|
|
pa_get_sink_flags() {
|
|
local -r sink=${1:?$(error 'Sink name is required')}
|
|
|
|
pa_list_sinks | \
|
|
awk -W posix '/^[ \t]+Name: / {insink = $2 == "'"$sink"'"}
|
|
/^[ \t]+Flags: / && insink { for(i = 2; i <= NF; ++i) printf $i FS; exit}'
|
|
}
|
|
|
|
# Get color for the given volume for PulseAudio
|
|
#
|
|
# Arguments:
|
|
# $1 - The volume
|
|
pa_volume_color() {
|
|
local -ir vol=${1:?$(error 'A volume is required')}
|
|
|
|
if (( vol >= PA_VOLUME_MUTED && vol < PA_BASE_VOLUME )); then
|
|
echo "$COLOR_MUTED_TO_BASE"
|
|
elif (( vol >= PA_BASE_VOLUME && vol <= PA_VOLUME_NORM )); then
|
|
echo "$COLOR_BASE_TO_NORM"
|
|
elif (( vol > PA_VOLUME_NORM && vol <= MAX_VOLUME )); then
|
|
echo "$COLOR_NORM_TO_MAX"
|
|
else
|
|
echo "$COLOR_OTHER"
|
|
fi
|
|
}
|
|
|
|
# Listens for PulseAudio events
|
|
#
|
|
# Arguments:
|
|
# Output (optional) (string) An output mode. When set, outputs volume in the output mode format.
|
|
listen() {
|
|
local -r output=$*
|
|
local -r index=$(pa_get_sink_index "$SINK")
|
|
|
|
# Output volume so statusbars have something to display before any event occurs
|
|
not_empty "$output" && output_volume "$output"
|
|
|
|
while IFS= read -r; do
|
|
show_volume_notification
|
|
update_statusbar
|
|
play_volume_changed
|
|
not_empty "$output" && output_volume "$output"
|
|
done < <(pactl subscribe | stdbuf -oL grep -e "Event 'change' on sink #$index")
|
|
}
|
|
|
|
# Play a sound file.
|
|
#
|
|
# Arguments:
|
|
# Sound file (string)
|
|
pa_play() {
|
|
local -r file=$1
|
|
|
|
paplay -d "$SINK" "$file" &
|
|
}
|
|
}
|
|
|
|
# Register PulseAudio related functions and settings
|
|
setup_pulseaudio() {
|
|
define_pulseaudio_functions
|
|
|
|
PA_LIST_SINKS=$(pactl list sinks) || exit 1
|
|
|
|
if empty "$SINK"; then
|
|
SINK="$(pa_default_sink_name)"
|
|
fi
|
|
|
|
# Determine a max volume when it's not specified
|
|
if ! isset MAX_VOLUME; then
|
|
read -ra SINK_FLAGS < <(pa_get_sink_flags "$SINK")
|
|
PA_BASE_VOLUME=$(pa_get_base_volume "$SINK")
|
|
|
|
# Does the sink support digital (software) amplification?
|
|
if [[ "${SINK_FLAGS[*]}" =~ "DECIBEL_VOLUME" ]]; then
|
|
MAX_VOLUME=$((PA_VOLUME_NORM * MAX_AMPLIFICATION))
|
|
else
|
|
MAX_VOLUME=$PA_VOLUME_NORM
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# All amixer functions are defined here
|
|
define_amixer_functions() {
|
|
# Get the volume as a percentage.
|
|
#
|
|
# Arguments
|
|
# Card (integer) Card number to control.
|
|
# Mixer (string) Name of the mixer.
|
|
amixer_get_volume() {
|
|
local -r card=$1
|
|
local -r mixer=${2:-Master}
|
|
|
|
amixer ${card:+-c "$card" --} sget "$mixer" | \
|
|
awk -W posix -F'[][]' '/dB/ { gsub("%", ""); print $2 }'
|
|
}
|
|
|
|
# Increase volume relative to current volume using amixer.
|
|
#
|
|
# Arguments:
|
|
# Card (integer) Card number to control.
|
|
# Step (integer) Percentage to increase by.
|
|
amixer_increase_volume() {
|
|
local -r card=$1
|
|
local -r step=${2:=-5}
|
|
|
|
amixer_set_volume "${step}%+" "$card"
|
|
}
|
|
|
|
# Decrease volume relative to current volume using amixer.
|
|
#
|
|
# Arguments:
|
|
# Card (integer) Card number to control.
|
|
# Step (integer) Percentage to decrease by.
|
|
amixer_decrease_volume() {
|
|
local -r card=$1
|
|
local -r step=${2:=-5}
|
|
|
|
amixer_set_volume "${step}%-" "$card"
|
|
}
|
|
|
|
# Set volume using amixer.
|
|
#
|
|
# Arguments:
|
|
# Volume (integer|linear factor|percentage|decibel)
|
|
# Card (optional) (integer) Card number to control.
|
|
amixer_set_volume() {
|
|
local -r vol=${1:?$(error 'Volume is required')}
|
|
local -r card=$2
|
|
|
|
amixer -q ${card:+-c "$card" --} set "$MIXER" "$vol"
|
|
}
|
|
|
|
# Toggle mute using amixer.
|
|
#
|
|
# Arguments:
|
|
# Card (integer) Card number to control.
|
|
amixer_toggle_mute() {
|
|
local -r card=$1
|
|
|
|
amixer -q ${card:+-c "$card" --} set "$MIXER" toggle
|
|
}
|
|
|
|
# Check if card is muted.
|
|
#
|
|
# Arguments:
|
|
# Card (optional) (integer) Card number to control.
|
|
#
|
|
# Returns:
|
|
# 0 when true, 1 when false.
|
|
amixer_is_muted() {
|
|
local -r card=$1
|
|
|
|
amixer ${card:+-c "$card" --} sget "$MIXER" | \
|
|
awk -W posix -F'[][]' '/dB/ && $6 ~ /^off$/ { exitcode=1 }; END { exit !exitcode }'
|
|
}
|
|
|
|
# Get color for the given volume for amixer
|
|
#
|
|
# Arguments:
|
|
# $1 - The volume
|
|
amixer_volume_color() {
|
|
local -ir vol=${1:?$(error 'A volume is required')}
|
|
|
|
if (( vol >= 0 && vol < 100 )); then
|
|
echo "$COLOR_MUTED_TO_BASE"
|
|
elif (( vol == 100 )); then
|
|
echo "$COLOR_BASE_TO_NORM"
|
|
elif (( vol > 100 && vol <= MAX_VOLUME )); then
|
|
echo "$COLOR_NORM_TO_MAX"
|
|
else
|
|
echo "$COLOR_OTHER"
|
|
fi
|
|
}
|
|
|
|
# Play a sound file.
|
|
#
|
|
# Arguments:
|
|
# Sound file (string)
|
|
amixer_play() {
|
|
local -r file=$1
|
|
|
|
aplay -q "$file" &
|
|
}
|
|
}
|
|
|
|
# Register amixer related functions and settings
|
|
setup_amixer() {
|
|
define_amixer_functions
|
|
}
|
|
|
|
setup_color() {
|
|
if has_color; then
|
|
COLOR_RESET=$'\033[0m'
|
|
COLOR_RED=$'\033[0;31m'
|
|
COLOR_GREEN=$'\033[0;32m'
|
|
COLOR_YELLOW=$'\033[0;33m'
|
|
COLOR_MAGENTA=$'\033[0;35m'
|
|
COLOR_CYAN=$'\033[0;36m'
|
|
fi
|
|
}
|
|
|
|
# Rearrange all options to place flags first
|
|
# Author: greycat
|
|
# URL: https://mywiki.wooledge.org/ComplexOptionParsing
|
|
arrange_opts() {
|
|
local flags args optstr=$1
|
|
shift
|
|
|
|
while (($#)); do
|
|
case $1 in
|
|
--)
|
|
args+=("$@")
|
|
break;
|
|
;;
|
|
-*)
|
|
flags+=("$1")
|
|
if [[ $optstr == *"${1: -1}:"* ]]; then
|
|
flags+=("$2")
|
|
shift
|
|
fi
|
|
;;
|
|
*)
|
|
args+=("$1")
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
OPTARR=("${flags[@]}" "${args[@]}")
|
|
}
|
|
|
|
parse_opts() {
|
|
local optstring=:ac:Ce:hj:lm:nN:pPs:S:t:u:x:X:y
|
|
|
|
arrange_opts "$optstring" "$@"
|
|
set -- "${OPTARR[@]}"
|
|
|
|
OPTIND=1
|
|
|
|
while getopts "$optstring" opt; do
|
|
case "$opt" in
|
|
a ) USE_AMIXER=true ;;
|
|
c ) CARD=$OPTARG ;;
|
|
C ) USE_CANBERRA=true ;;
|
|
e ) EXPIRES=$OPTARG ;;
|
|
j ) IFS=, read -ra ICONS_EMOJI <<< "$OPTARG" ;;
|
|
l ) USE_FULLCOLOR_ICONS=true ;;
|
|
m ) MIXER=${OPTARG@Q} ;;
|
|
n ) DISPLAY_NOTIFICATIONS=true ;;
|
|
N ) NOTIFICATION_METHOD=$OPTARG ;;
|
|
p ) SHOW_VOLUME_PROGRESS=true ;;
|
|
P ) PLAY_SOUND=true ;;
|
|
s ) SINK=$OPTARG ;;
|
|
S ) SYMBOLIC_ICON_SUFFIX=$OPTARG ;;
|
|
t ) STATUSLINE=$OPTARG ;;
|
|
u ) SIGNAL=$OPTARG ;;
|
|
x ) MAX_VOLUME=$OPTARG ;;
|
|
X ) MAX_AMPLIFICATION=$OPTARG ;;
|
|
y ) USE_DUNSTIFY=true ;;
|
|
h | *) usage ;;
|
|
esac
|
|
done
|
|
|
|
read -ra CMDARGS <<< "${OPTARR[@]:$((OPTIND-1))}"
|
|
}
|
|
|
|
exec_command() {
|
|
IFS=' ' read -ra ARGS <<< "$1"
|
|
set -- "${ARGS[@]}"
|
|
|
|
COMMAND=${1:?$(error 'A command is required')}
|
|
shift
|
|
|
|
case "$COMMAND" in
|
|
up|raise|increase)
|
|
case "$#" in 1) ;; *) usage ;; esac
|
|
increase_volume "$1" "$MAX_VOLUME"
|
|
;;
|
|
down|lower|decrease)
|
|
case "$#" in 1) ;; *) usage ;; esac
|
|
decrease_volume "$1"
|
|
;;
|
|
set)
|
|
case "$#" in 1) ;; *) usage ;; esac
|
|
case "$1" in
|
|
+*) increase_volume "${1:1}" "$MAX_VOLUME" ;;
|
|
-*) decrease_volume "${1:1}" ;;
|
|
*) set_volume "$1" "$MAX_VOLUME" ;;
|
|
esac
|
|
;;
|
|
mute)
|
|
toggle_mute
|
|
;;
|
|
listen)
|
|
listen "$*"
|
|
;;
|
|
output)
|
|
case "$#" in 0) usage ;; esac
|
|
output_volume "$*"
|
|
exit "${EXITCODE:-$EX_OK}"
|
|
;;
|
|
outputs)
|
|
list_output_formats
|
|
;;
|
|
notifications)
|
|
list_notification_methods
|
|
;;
|
|
*)
|
|
usage
|
|
;;
|
|
esac
|
|
}
|
|
|
|
play_volume_changed() {
|
|
$PLAY_SOUND || return
|
|
|
|
# Sound can be handled by the notification method
|
|
if $DISPLAY_NOTIFICATIONS && has_capability sound; then
|
|
return
|
|
fi
|
|
|
|
if $USE_CANBERRA; then
|
|
ca_play "$SOUND_VOLUME_CHANGED" "Volume Changed"
|
|
else
|
|
if $USE_AMIXER; then
|
|
amixer_play "$SOUND_VOLUME_CHANGED"
|
|
else
|
|
pa_play "$SOUND_VOLUME_CHANGED"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
ca_play() {
|
|
local -r file=$1 desc=$2
|
|
|
|
if [[ -f $file ]]; then
|
|
"${CANBERRA_PATH:+${CANBERRA_PATH%/}/}canberra-gtk-play" -f "$file" -d "$desc"
|
|
else
|
|
"${CANBERRA_PATH:+${CANBERRA_PATH%/}/}canberra-gtk-play" -i "audio-volume-change" -d "$desc"
|
|
fi
|
|
}
|
|
|
|
post_command_hook() {
|
|
if is_command_hookable "$COMMAND"; then
|
|
show_volume_notification
|
|
play_volume_changed
|
|
update_statusbar || usage
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
# Getopt parsing variables
|
|
declare OPTIND
|
|
declare -a OPTARR CMDARGS
|
|
|
|
###########################################################
|
|
# Non-command line option variables
|
|
###########################################################
|
|
|
|
# Commands which will not use post_command_hook(), usually because
|
|
# they handle notifications and/or statusbar updates manually
|
|
declare -a POST_HOOK_EXEMPT_COMMANDS=(
|
|
listen
|
|
)
|
|
|
|
# Exit codes
|
|
declare -ir \
|
|
EX_OK=0 \
|
|
EX_URGENT=33 \
|
|
EX_USAGE=64
|
|
|
|
# Main program exit code
|
|
declare -i EXITCODE=$EX_OK
|
|
|
|
# Standard notification icons. Usually full color
|
|
# Note: order matters; muted, high, low, medium, and optionally overamplified
|
|
declare -a ICONS=(
|
|
audio-volume-muted
|
|
audio-volume-high
|
|
audio-volume-low
|
|
audio-volume-medium
|
|
)
|
|
|
|
# Symbolic notification icons. Usually low color or monochrome
|
|
# Note: order matters; muted, high, low, medium, and optionally overamplified
|
|
declare -a ICONS_SYMBOLIC=(
|
|
audio-volume-muted-symbolic
|
|
audio-volume-high-symbolic
|
|
audio-volume-low-symbolic
|
|
audio-volume-medium-symbolic
|
|
## Only exists in some icon sets
|
|
# audio-volume-overamplified-symbolic
|
|
)
|
|
|
|
# Emoji-based icons.
|
|
declare -a ICONS_EMOJI=(
|
|
ﱝ
|
|
墳
|
|
奄
|
|
奔
|
|
)
|
|
|
|
# Volume changed sound.
|
|
declare SOUND_VOLUME_CHANGED=${SOUND_VOLUME_CHANGED:-/usr/share/sounds/freedesktop/stereo/audio-volume-change.oga}
|
|
|
|
# DBUS constants
|
|
declare -r \
|
|
DBUS_NAME=org.freedesktop.Notifications \
|
|
DBUS_PATH=/org/freedesktop/Notifications \
|
|
DBUS_IFAC_FDN=org.freedesktop.Notifications
|
|
|
|
# Notification server information
|
|
declare \
|
|
NOTIFY_SERVER
|
|
# NOTIFY_VENDOR \
|
|
# NOTIFY_VERSION \
|
|
# NOTIFY_SPEC_VERSION
|
|
|
|
# Notification capabilities
|
|
declare -a NOTIFY_CAPS=()
|
|
|
|
# PulseAudio sink flags
|
|
declare -a SINK_FLAGS=()
|
|
|
|
# PulseAudio volume variables and constants.
|
|
# Note: unlike in PA, PA_VOLUME_* here are percentages instead of integers
|
|
declare -i PA_BASE_VOLUME=100
|
|
declare -ir \
|
|
PA_VOLUME_NORM=100 \
|
|
PA_VOLUME_MUTED=0
|
|
|
|
# Cached output of `pactl list sinks`; so we don't have to call it each time we need it
|
|
declare PA_LIST_SINKS
|
|
|
|
# Output volume colors
|
|
declare -r \
|
|
COLOR_MUTED=${COLOR_MUTED:-#FFFF00} \
|
|
COLOR_MUTED_TO_BASE=${COLOR_MUTED_TO_BASE:-#00FF00} \
|
|
COLOR_BASE_TO_NORM=${COLOR_BASE_TO_NORM:-#FFFF00} \
|
|
COLOR_NORM_TO_MAX=${COLOR_NORM_TO_MAX:-#FF0000} \
|
|
COLOR_OTHER=${COLOR_OTHER:-#FFFFFF} \
|
|
COLOR_XOSD_OUTLINE=${COLOR_XOSD_OUTLINE:-#222222}
|
|
|
|
declare \
|
|
COLOR_RESET \
|
|
COLOR_RED \
|
|
COLOR_GREEN \
|
|
COLOR_YELLOW \
|
|
COLOR_MAGENTA \
|
|
COLOR_CYAN
|
|
|
|
###########################################################
|
|
# Command line option variables
|
|
###########################################################
|
|
declare -l NOTIFICATION_METHOD
|
|
|
|
declare \
|
|
COMMAND \
|
|
DISPLAY_NOTIFICATIONS=false \
|
|
SHOW_VOLUME_PROGRESS=false \
|
|
USE_AMIXER=false \
|
|
USE_DUNSTIFY=false \
|
|
USE_FULLCOLOR_ICONS=false \
|
|
CARD \
|
|
MIXER=Master \
|
|
SIGNAL \
|
|
SINK \
|
|
STATUSLINE \
|
|
SYMBOLIC_ICON_SUFFIX \
|
|
NOTIFICATION_METHOD \
|
|
PLAY_SOUND=false \
|
|
USE_CANBERRA=false
|
|
|
|
declare -i \
|
|
EXPIRES=1500 \
|
|
MAX_VOLUME \
|
|
MAX_AMPLIFICATION=2
|
|
|
|
define_helpers
|
|
define_notify
|
|
define_output_formats
|
|
define_commands
|
|
|
|
setup_color
|
|
|
|
parse_opts "$@"
|
|
|
|
# Requires options to be parsed first
|
|
setup_audio
|
|
|
|
exec_command "${CMDARGS[*]}" && post_command_hook
|
|
|
|
exit ${EXITCODE:-$EX_OK}
|
|
}
|
|
|
|
main "$@"
|