#!/bin/sh

# vol - Adjust volume, with on-screen display.

# (c) 2020 B. Watson <urchlay@slackware.uk>
# Released under the WTFPL, see http://www.wtfpl.net/txt/copying/

# Requires xosd.
# 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. 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, or leave it off for percentage.
ADJ=3dB

# osd_cat seems a bit picky about what fonts it'll use. If you give a
# nonexistent font, it won't run at all. If you give a font it "doesn't
# like", you'll see the percentage bar, but no "Volume" text.
FONT=12x24

# What color is the OSD bar and text? See /usr/share/X11/rgb.txt.
# COLOR is for when we're unmuted, MUTECOLOR is for muted.
COLOR=orange
MUTECOLOR=red

# Text drop shadow, 0 to disable.
SHADOW=3

# Where does the OSD show up on screen? One of: top middle bottom
POSITION=bottom

# How long does the OSD persist? In seconds, integer only.
DELAY=2

# 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"

# osd() will kill any previously-spawned osd_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 text="Volume $db ($volpct%)"
  local color="$COLOR"
  local oldpid

  if [ -n "$muted" ]; then
    text="$text [Muted]"
    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 '^osd_cat' /proc/$oldpid/cmdline 2>/dev/null && \
      kill "$oldpid" 2>/dev/null
  fi

  osd_cat -b percentage \
          -d $DELAY \
          -P $volpct \
          -p $POSITION \
          -c $color \
          -s $SHADOW \
          -f $FONT \
          -T "$text" &

  echo "$!" > $PIDFILE
}

amixer_set() {
  amixer -q $AMIXER_OPTS set $CHANNEL $1
}

vol() {
  amixer_set ${ADJ}${1}
}

mute() {
  amixer_set toggle
}

usage() {
  cat <<EOF
$SELF [up|down|mute|<nnn>]

<nnn> is a numeric volume, possibly followed by "dB" 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 osd_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

# For reference:
#$ amixer get Master
#Simple mixer control 'Master',0
#  Capabilities: pvolume pvolume-joined pswitch pswitch-joined
#  Playback channels: Mono
#  Limits: Playback 0 - 87
#  Mono: Playback 31 [36%] [-42.00dB] [on]