From a1a45feb7ea560e2650f1cafa83a3c1389dbede1 Mon Sep 17 00:00:00 2001 From: Sfy Mantissa Date: Sat, 29 Oct 2022 18:10:32 +0600 Subject: [PATCH] Added ability to view the battery charge of the connected bluetooth device. * Updated new Bluetooth battery info functionality. * Added unit test for new Bluetooth battery info functionality. * Updated README. * Removed trailing whitespace from files. --- README.md | 19 ++++++++++++ pulseaudio-control | 77 +++++++++++++++++++++++++++++++++++++++++++--- tests.bats | 24 +++++++++++++-- 3 files changed, 114 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 560ddf8..de9e2b6 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,25 @@ Options: can specify what timeout to use to control the responsiveness, in seconds. Default: "0.05" + --icons-bluetooth-battery [,...] + Icons for battery level of connected bluetooth headphones. + If no icons are provided, the feature is disabled. + Requires bluez experimental features to be enabled. + For details, see: + https://wiki.archlinux.org/title/Bluetooth_headset + Default: none + --hide-bluetooth-battery-level + Hide the integer battery level representation. + Requires bluez experimental features to be enabled. + For details, see: + https://wiki.archlinux.org/title/Bluetooth_headset + Default: none + --battery-max + Set the maximum device battery level. + Requires bluez experimental features to be enabled. + For details, see: + https://wiki.archlinux.org/title/Bluetooth_headset + Default: \"$BAT_MAX\" Actions: help display this message and exit diff --git a/pulseaudio-control b/pulseaudio-control index 99c60ef..fde70af 100755 --- a/pulseaudio-control +++ b/pulseaudio-control @@ -22,11 +22,13 @@ OSD="no" NODE_NICKNAMES_PROP= VOLUME_STEP=2 VOLUME_MAX=130 +BAT_MAX=100 LISTEN_TIMEOUT=0.05 # shellcheck disable=SC2016 FORMAT='$VOL_ICON ${VOL_LEVEL}% $ICON_NODE $NODE_NICKNAME' declare -A NODE_NICKNAMES declare -a ICONS_VOLUME +declare -a ICONS_BLUETOOTH_BATTERY declare -a NODE_BLACKLIST # Special variable: within the script, pactl, grep, and awk commands are used @@ -46,6 +48,8 @@ SINK_OR_SOURCE="ink" # Environment & global constants for the script export LC_ALL=C # Some calls depend on English outputs of pactl END_COLOR="%{F-}" # For Polybar colors +BLUETOOTH_CONFIG="/etc/bluetooth/main.conf" # For Bluetooth configuration file +BLUETOOTH_EXPERIMENTAL_REGEX="^Experimental = true" # Regex to tell whether battery level can be fetched # Saves the currently default node into a variable named `curNode`. It will @@ -73,6 +77,22 @@ function getNodeName() { } +# Saves the current battery level of a connected Bluetooth device for the node +# passed by parameter into a variable named `BAT_LEVEL`. +# If a node is not a Bluetooth device, `BAT_LEVEL` would be an empty string. +# Requires bluez experimental features to be enabled. +# For details, see: https://wiki.archlinux.org/title/Bluetooth_headset +function getCurCharge() { + local bluetoothIsExperimental=$(grep -Eo "$BLUETOOTH_EXPERIMENTAL_REGEX" "$BLUETOOTH_CONFIG") + if [ "$bluetoothIsExperimental" ]; then + getNodeName "$1" + + local bluetoothDeviceMac=$(echo "$nodeName" | sed -e 's/^[a-z_]*\.//' -e 's/\..*$//' | tr '_' ':') + BAT_LEVEL=$(bluetoothctl info "$bluetoothDeviceMac" | grep Battery | sed -E 's/.*\((.*)\)/\1/') + fi +} + + # Saves the name to be displayed for the node passed by parameter into a # variable called `NODE_NICKNAME`. # If a mapping for the node name exists, that is used. Otherwise, the string @@ -195,9 +215,9 @@ function volSync() { echo "PulseAudio not running" return 1 fi - + getCurVol "$curNode" - + if [[ "$NODE_TYPE" = "output" ]]; then getSinkInputs "$curNode" @@ -382,19 +402,38 @@ function output() { fi getNickname "$curNode" + getCurCharge "$curNode" + + local iconsLen=${#ICONS_BLUETOOTH_BATTERY[@]} + if [ "$iconsLen" -ne 0 ] && [ "$BAT_LEVEL" != "" ]; then + local batSplit=$((BAT_MAX / iconsLen)) + for i in $(seq 1 "$iconsLen"); do + if [ $((i * batSplit)) -ge "$BAT_LEVEL" ]; then + BAT_ICON="${ICONS_BLUETOOTH_BATTERY[$((i-1))]}" + break + fi + done + + BLUETOOTH_FORMAT='$BAT_ICON ${BAT_LEVEL}%' + if [ "$HIDE_BAT" = "yes" ] && [ "$BAT_LEVEL" != '' ]; then + BLUETOOTH_FORMAT='$BAT_ICON' + fi + else + BLUETOOTH_FORMAT='' + fi # Showing the formatted message if [ "$IS_MUTED" = "yes" ]; then # shellcheck disable=SC2034 VOL_ICON=$ICON_MUTED - content="$(eval echo "$FORMAT")" + content="$(eval echo "$FORMAT" "$BLUETOOTH_FORMAT")" if [ -n "$COLOR_MUTED" ]; then echo "${COLOR_MUTED}${content}${END_COLOR}" else echo "$content" fi else - eval echo "$FORMAT" + eval echo "$FORMAT" "$BLUETOOTH_FORMAT" fi } @@ -484,6 +523,25 @@ Options: can specify what timeout to use to control the responsiveness, in seconds. Default: \"$LISTEN_TIMEOUT\" + --icons-bluetooth-battery [,...] + Icons for battery level of connected bluetooth headphones. + If no icons are provided, the feature is disabled. + Requires bluez experimental features to be enabled. + For details, see: + https://wiki.archlinux.org/title/Bluetooth_headset + Default: none + --hide-bluetooth-battery-level + Hide the integer battery level representation. + Requires bluez experimental features to be enabled. + For details, see: + https://wiki.archlinux.org/title/Bluetooth_headset + Default: none + --battery-max + Set the maximum device battery level. + Requires bluez experimental features to be enabled. + For details, see: + https://wiki.archlinux.org/title/Bluetooth_headset + Default: \"$BAT_MAX\" Actions: help display this message and exit @@ -551,6 +609,17 @@ while [[ "$1" = --* ]]; do # shellcheck disable=SC2034 ICON_NODE="$val" ;; + --hide-bluetooth-battery-level) + HIDE_BAT=yes + ;; + --icons-bluetooth-battery) + if getOptVal "$@"; then shift; fi + IFS=, read -r -a ICONS_BLUETOOTH_BATTERY <<< "${val//[[:space:]]/}" + ;; + --battery-max) + if getOptVal "$@"; then shift; fi + BAT_MAX="$val" + ;; --icons-volume) if getOptVal "$@"; then shift; fi IFS=, read -r -a ICONS_VOLUME <<< "${val//[[:space:]]/}" diff --git a/tests.bats b/tests.bats index cfd8de1..6374aa9 100644 --- a/tests.bats +++ b/tests.bats @@ -1,8 +1,8 @@ #!/usr/bin/env bats # vim: filetype=sh -# +# # Polybar PulseAudio Control - tests.bats -# +# # Simple test script to make sure the most basic functions in this script # always work as intended. The tests will modify the system's PulseAudio # setup until it's restarted, so either do that after running the test, or @@ -31,6 +31,10 @@ function setup() { restartPulseaudio echo "Loading script" source pulseaudio-control.bash output &>/dev/null + + # In case there's a Bluetooth device connected, 3 seconds are well enough + # to reestalish the connection after restarting PulseAudio. + sleep 3 } @@ -194,3 +198,19 @@ function setup() { getNickname "$((10 + offset))" # beyond what exists [ "$NODE_NICKNAME" = "Sink #$((10 + offset))" ] } + + +@test "getCurCharge()" { + getCurNode + getCurCharge "$curNode" + + local bluetoothIsExperimental=$(grep -Eo "$BLUETOOTH_EXPERIMENTAL_REGEX" "$BLUETOOTH_CONFIG") + local bluetoothDeviceMac=$(echo "$nodeName" | sed -e 's/^[a-z_]*\.//' -e 's/\..*$//' | tr '_' ':') + + if [ "$bluetoothIsExperimental" ] && [ "$bluetoothDeviceMac" ]; then + local expectedBatLevel=$(bluetoothctl info "$bluetoothDeviceMac" | grep Battery | sed -E 's/.*\((.*)\)/\1/') + [ "$BAT_LEVEL" == "$expectedBatLevel" ] + else + [ "$BAT_LEVEL" == '' ] + fi +}