#!/bin/sh # vol - Adjust volume, with on-screen display. # (c) 2020 B. Watson # Released under the WTFPL, see http://www.wtfpl.net/txt/copying/ # Requires aosd_cat (on Slackware, libaosd from SBo). # Intended for use with xbindkeys, like so: #$ cat ~/.xbindkeysrc #"vol up &" # Control+Alt + Prior # #"vol down &" # Control+Alt + Next # #"vol mute &" # Control+Alt + Pause # "Next" and "Prior" are PageDown and PageUp. Replace with "Up" and "Down" # to use arrow keys. If you have multimedia keys, you can probably use # them (try XF86AudioRaiseVolume, XF86AudioLowerVolume, and # XF86AudioMute). See xbindkeys(1). # Can also be used from the command line. Run with no args for help. # -------------------- # Configurable options # -------------------- # Should be "Master" for most (all?) cards. Use "amixer scontrols" to # get a list. CHANNEL=Master # Each up/down adjustment is by this much. Use the dB suffix if you # prefer decibels, % percentage, no suffix for raw units (on one of my # sound cards, the raw unit range is 0 to 87; on another, 0-127). ADJ=1% # Pango font string, see: # https://docs.gtk.org/Pango/type_func.FontDescription.from_string.html # The size is in points; 30px would be pixels. FONT="Mono Normal 30" # What color is the OSD bar and text? See /usr/share/X11/rgb.txt, # or use hex escapes (with #). # COLOR is for when we're unmuted, MUTECOLOR is for muted. COLOR='#00ff00' MUTECOLOR='#ff0000' # How long does the OSD persist? In milliseconds. DELAY=2000 # amixer options. Could use -c, -D here. Don't use -q. # default is "-M", see amixer(1). AMIXER_OPTS="-M" # ------------------------------------------ # End of configurable options, rest is code. # ------------------------------------------ SELF="$( basename $0 )" PIDFILE="$HOME/.$SELF.osd.pid" BLOCKS="██████████████████████████████████████████████████" BLANKS="──────────────────────────────────────────────────" PARTIAL="▌" # progress() prints a UTF-8 progress bar with 50 characters. # takes a percentage, 0 to 100 progress() { local text="$1" local count="$2" local solidblocks local partial local trailing solidblocks="$(( $count / 2 ))" partial="$(( $count % 2 ))" trailing="$(( 50 - ( $solidblocks + $partial) ))" echo -n "$text${BLOCKS:0:$solidblocks}" [ "$partial" = "1" ] && echo -n $PARTIAL echo -n "${BLANKS:0:$trailing}" } # osd() will kill any previously-spawned aosd_cat process. Ideally this # means rapid multiple keypresses won't "step on" each other. In practice # it seems to work OK, but if you press the key really fast on a slow # system, or hold it down with your key-repeat rate cranked up (on any # system), the OSD bar will never get a chance to display. osd() { local got="$( amixer $AMIXER_OPTS get $CHANNEL | tail -1 )" local muted="$( echo "$got" | grep '\[off\]$' )" local volpct="$( echo "$got" | cut -d'[' -f2 | cut -d% -f1 )" local db="$( echo "$got" | cut -d'[' -f3 | cut -d']' -f1 )" local color="$COLOR" local text="$( printf "%8s" $db) $( printf "%-3s" $volpct% ) " local oldpid if [ -n "$muted" ]; then color="$MUTECOLOR" fi # the silliness with grepping in /proc is meant to avoid stale PID # files whose PIDs have been recycled. if [ -e $PIDFILE ]; then oldpid="$( cat $PIDFILE )" grep -q '^aosd_cat' /proc/$oldpid/cmdline 2>/dev/null && \ kill -9 "$oldpid" 2>/dev/null rm -f $PIDFILE fi # TODO: make these variables in the config section rather than # hardcoding them. progress "$text" "$volpct" | \ aosd_cat \ --fore-color $color \ --back-color '#808080' \ --font "$FONT" \ --output 1 \ --x-offset 159 \ --y-offset 0 \ --position 7 \ --transparency 1 \ --back-opacity 128 \ --padding 7 \ --alignment 0 \ --fade-in 0 \ --fade-out 0 \ --fade-full $DELAY & echo "$!" > $PIDFILE } amixer_set() { amixer -q $AMIXER_OPTS set $CHANNEL $1 } vol() { amixer_set ${ADJ}${1} } mute() { amixer_set toggle } usage() { cat <] is a numeric volume, possibly followed by "dB" or "%", and/or "+" or "-". It will be passed to 'amixer $AMIXER_OPTS set $CHANNEL' as-is. EOF exit 1 } check_deps() { local missing="no" for dep in aosd_cat amixer; do if ! type -p "$dep" > /dev/null; then echo "$SELF: missing required executable '$dep'" 1>&2 missing="yes" fi done if [ "$missing" = "yes" ]; then echo "$SELF: please install the missing executable(s)" 1>&2 exit 1 fi } ###main() check_deps case "$1" in up) vol + ;; down) vol - ;; mute) mute ;; [0-9]*) amixer_set "$1" ;; *) usage ;; esac osd exit 0