Knowledge Base

Preserving for the future: Shell scripts, AoC, and more

Helper script: pulsemixer-notification

Story

Due to a minor hardware problem where my sound card keeps jumping from aplay -l position 0 to position 1, my wine malfunctioned with audio. I set the specific card to use in the winecfg tab for Audio. After a reboot (and the alsa card position switched again), winecfg would crash on the Audio tab and I was unable to figure how to wipe the settings back to all defaults. This wine audio problem meant everything in wine that uses audio (every game) crashes. It was frustrating.

So I bit the bullet and installed pulseaudio on my Devuan GNU+Linux desktop. It was a matter of adding start-pulseaudio-x11 & to ~/.fluxbox/startup. I learned you need to keep alsa-utils and alsamixergui installed. Pulseaudio sits on top of alsa instead of replacing it.

There is a great program named pulsemixer that is a very powerful TUI and CLI tool for controlling your inputs and outputs. But I still needed alsamixer to disable the "auto-mute" when I plug in headphones, because I accidentally discovered a few years ago that if sound comes out of both speakers and headphones, it makes my life easier. I'm probably weird for that reason (and dozens of others), but I like it and I'm going to keep it that way, and it takes alsamixer to control that. (Take that, ... atheists? pulseaudio peeps anyways.)

So, pulsemixer does provide a great, simple way to toggle mute, and change volume up and down. I was only missing notifications about changing the volume.

Configuring other apps

volumeicon

I still use volumeicon but now I have configured middle-click to "open mixer," with mixer defined as pulsemixer --togle-mute. I found that while the volumeicon can still use its built-in "Mute Volume" setting, it mutes it on the alsa level, which mutes the master volume in pulseaudio, but it cannot unmute that pulseaudio master volume. So it is now always muted until I use pulsemixer.

I also had to disable volumeicon from handling the XF86AudioRaiseVolume keypresses. For that, I modified my fluxbox config.

fluxbox

For bare pulsemixer control you can just run:

122 :Exec pulsemixer --change-volume -5
123 :Exec pulsemixer --change-volume +5
121 :Exec pulsemixer --toggle-mute

Which worked well but it doesn't have a cute little popup telling me what the current volume level is. So I had to write the following script, and configure fluxbox to use it.

121 :Exec pulsemixer-notification toggle
122 :Exec pulsemixer-notification down
123 :Exec pulsemixer-notification up

The script

Explanation

This script displays a relative volume level (0/33/66/100) and muted symbol. It will always unmute if you change volume, which is the behavior I expect because that's how alsa handled volume. A neat little feature is that it uses a notification ID, so it can replace it if you're scrolling up on the volume; it will just replace the current notification instead of slowly (1.25s per step) trickling through them.

Listing

files/2023/10/listings/pulsemixer-notification (Source)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#!/bin/sh
# File: pulsemixer-notification
# Location: /usr/bin
# Author: bgstack15
# Startdate: 2023-10-04-4 09:06
# SPDX-License-Identifier: GPL-3.0
# Title: pulsemixer wrapper script that includes notifications
# Package: bgscripts
# Purpose: wrap around pulsemixer hotkey invocations to include status notifications
# History:
# Usage:
#    In ~/.fluxbox/keys:
#       121 :Exec pulsemixer-notification toggle
#       122 :Exec pulsemixer-notification down
#       123 :Exec pulsemixer-notification up
# Reference:
# Improve:
# Dependencies:
#    devuan-dep: pulsemixer, pulseaudio, notify-send
# Documentation:

# Load configuration
test -z "${devtty}" && devtty=/dev/null
test -z "${PN_TEMPFILE}" && PN_TEMPFILE=~/.cache/audio.temp
test -z "${PN_STEP}" && PN_STEP=3
test -z "${PN_EXPIRE_MS}" && PN_EXPIRE_MS=1250
test -f "${HOME}/.config/pulsemixer-notification" && . "${HOME}/.config/pulsemixer-notification"
test -f "/etc/pulsemixer-notification.conf" && . "/etc/pulsemixer-notification.conf"

# runtime
exec 1>>"${devtty}"
action="${1}"
unset icon vol relative_level message replacestring

case "${action}" in
   toggle)
      pulsemixer --toggle-mute
      case "$( pulsemixer --get-mute )" in
         0)
            action="unmute"
            ;;
         1)
            icon="audio-volume-muted"
            message="muted"
            ;;
      esac
      ;;
   up) pulsemixer --unmute ; pulsemixer --change-volume +"${PN_STEP}" ;;
   down) pulsemixer --unmute ; pulsemixer --change-volume -"${PN_STEP}" ;;
   *)
      echo "unknown: ${action}"
      ;;
esac

get_vol_level() {
   vol="$( pulsemixer --get-volume | awk '{print $1}' )"
   is_low="$( printf '%s\n' "${vol}<=33" | bc )"
   is_med="$( printf '%s\n' "(${vol}<=66)*(${vol}>33)" | bc )"
   is_hih="$( printf '%s\n' "${vol}>66" | bc )"
   is_off="$( printf '%s\n' "${vol}==0" | bc )"
   test "${is_low}" = "1" && printf '%s' "low" && return 0
   test "${is_med}" = "1" && printf '%s' "medium" && return 0
   test "${is_hih}" = "1" && printf '%s' "high" && return 0
   test "${is_off}" = "1" && printf '%s' "muted" && return 0
}

# awk: cheat and just use left-side volume of stereo volume.
vol="$( pulsemixer --get-volume | awk '{print $1}' )"
relative_level="$( get_vol_level )"

case "${action}" in
   up|down)
      icon="audio-volume-${relative_level}"
      message="${vol}"
      ;;
   unmute)
      icon="audio-volume-${relative_level}"
      message="unmuted"
      ;;
esac

test -f "${PN_TEMPFILE}" && replacestring="--replace-id=$( cat "${PN_TEMPFILE}" 2>/dev/null )"
# leave replacestring unquoted in case it is empty
notify-send ${replacestring} --icon "${icon}" --transient "${message}" --urgency low --expire-time "${PN_EXPIRE_MS}" --print-id > "${PN_TEMPFILE}"

Comments