add better volume control to polybar and add minimizing to xmoand

This commit is contained in:
Leon Kowarschick 2020-03-25 12:29:22 +01:00
parent 218f730d1c
commit 2d301c621e
6 changed files with 412 additions and 12 deletions

View file

@ -75,9 +75,9 @@ font-2 = FontAwesome5Free:style=Solid:size=10;1
;font-1 = "FontAwesome:fontformat=truetype:size=12;1" ;font-1 = "FontAwesome:fontformat=truetype:size=12;1"
modules-left = xmonad test modules-left = xmonad
modules-center = timerDisplay spotify mpd gitlab-pipeline player-mpv-tail modules-center = timerDisplay spotify mpd gitlab-pipeline player-mpv-tail
modules-right = network-traffic info-pingrtt pulseaudio filesystem memory cpu date modules-right = pulseaudio-control updates-arch network-traffic info-pingrtt pulseaudio filesystem memory cpu date
tray-position = right tray-position = right
tray-padding = 2 tray-padding = 2
@ -207,3 +207,25 @@ exec = python ~/.config/polybar/polybar-scripts/spotify_status.py -f '{artist} -
click-left = "dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause" click-left = "dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause"
exec-if = "pgrep spotify" exec-if = "pgrep spotify"
format-underline = #1db954 format-underline = #1db954
[module/updates-arch]
type = custom/script
exec = ~/.config/polybar/polybar-scripts/updates-arch-combined.sh
interval = 600
[module/pulseaudio-control]
type = custom/script
tail = true
label = %output%
click-right = exec pavucontrol &
exec = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash listen
click-left = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash togmute
click-middle = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash next-sink
scroll-up = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash up
scroll-down = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash down
label-padding = 2
label-foreground = ${colors.foreground}

View file

@ -0,0 +1,351 @@
#!/bin/bash
##################################################################
# Polybar Pulseaudio Control #
# https://github.com/marioortizmanero/polybar-pulseaudio-control #
##################################################################
# Script configuration (more info in the README)
OSD="no" # On Screen Display message for KDE if enabled
INC=2 # Increment when lowering/rising the volume
MAX_VOL=130 # Maximum volume
AUTOSYNC="no" # All programs have the same volume if enabled
VOLUME_ICONS=("🔈 " "🔉 " "🔊 ")
MUTED_ICON=" " # Muted volume icon
MUTED_COLOR="%{F#6b6b6b}" # Color when the audio is muted
NOTIFICATIONS="no" # Notifications when switching sinks if enabled
#SINK_ICON="🔈 " # The default sink icon if a custom one isn't found
SINK_ICON=""
# Blacklist of PulseAudio sink names when switching between them. To obtain
# the names of your active sinks, use `pactl list sinks short`.
SINK_BLACKLIST=(
"alsa_output.pci-0000_00_1f.3.iec958-stereo"
)
# Maps PulseAudio sink names to human-readable names
declare -A SINK_NICKNAMES
SINK_NICKNAMES["alsa_output.usb-Native_Instruments_Komplete_Audio_6_77316682-00.analog-surround-21"]="📢"
SINK_NICKNAMES["alsa_output.usb-Fujitsu_UC_C_USB_Value_Headset_Fujitsu_UC_C_USB_Value_Headset-00.analog-stereo"]="🎧"
# Environment & global constants for the script
LANGUAGE=en_US # Some calls depend on English outputs of pactl
END_COLOR="%{F-}"
# Saves the currently default sink into a variable named `curSink`. It will
# return an error code when pulseaudio isn't running.
function getCurSink() {
if ! pulseaudio --check; then return 1; fi
curSink=$(pacmd list-sinks | awk '/\* index:/{print $3}')
}
# Saves the sink passed by parameter's volume into a variable named `curVol`.
function getCurVol() {
curVol=$(pacmd list-sinks | grep -A 15 'index: '"$1"'' | grep 'volume:' | grep -E -v 'base volume:' | awk -F : '{print $3}' | grep -o -P '.{0,3}%' | sed s/.$// | tr -d ' ')
}
# Saves the name of the sink passed by parameter into a variable named
# `sinkName`.
function getSinkName() {
sinkName=$(pactl list sinks short | awk -v sink="$1" '{ if ($1 == sink) {print $2} }')
}
# Saves the name to be displayed for the sink passed by parameter into a
# variable called `nickname`.
# If a mapping for the sink name exists, that is used. Otherwise, the string
# "Sink #<index>" is used.
function getNickname() {
getSinkName "$1"
if [ -n "${SINK_NICKNAMES[$sinkName]}" ]; then
nickname="${SINK_NICKNAMES[$sinkName]}"
else
nickname="Sink #$1"
fi
}
# Saves the status of the sink passed by parameter into a variable named
# `isMuted`.
function getIsMuted() {
isMuted=$(pacmd list-sinks | grep -A 15 "index: $1" | awk '/muted/{print $2}')
}
# Saves all the sink inputs of the sink passed by parameter into a string
# named `sinkInputs`.
function getSinkInputs() {
sinkInputs=$(pacmd list-sink-inputs | grep -B 4 "sink: $1 " | awk '/index:/{print $2}')
}
function volUp() {
# Obtaining the current volume from pacmd into $curVol.
if ! getCurSink; then
echo "PulseAudio not running"
return 1
fi
getCurVol "$curSink"
local maxLimit=$((MAX_VOL - INC))
# Checking the volume upper bounds so that if MAX_VOL was 100% and the
# increase percentage was 3%, a 99% volume would top at 100% instead
# of 102%. If the volume is above the maximum limit, nothing is done.
if [ "$curVol" -le "$MAX_VOL" ] && [ "$curVol" -ge "$maxLimit" ]; then
pactl set-sink-volume "$curSink" "$MAX_VOL%"
elif [ "$curVol" -lt "$maxLimit" ]; then
pactl set-sink-volume "$curSink" "+$INC%"
fi
if [ $OSD = "yes" ]; then showOSD "$curSink"; fi
if [ $AUTOSYNC = "yes" ]; then volSync; fi
}
function volDown() {
# Pactl already handles the volume lower bounds so that negative values
# are ignored.
if ! getCurSink; then
echo "PulseAudio not running"
return 1
fi
pactl set-sink-volume "$curSink" "-$INC%"
if [ $OSD = "yes" ]; then showOSD "$curSink"; fi
if [ $AUTOSYNC = "yes" ]; then volSync; fi
}
function volSync() {
if ! getCurSink; then
echo "PulseAudio not running"
return 1
fi
getSinkInputs "$curSink"
getCurVol "$curSink"
# Every output found in the active sink has their volume set to the
# current one. This will only be called if $AUTOSYNC is `yes`.
for each in $sinkInputs; do
pactl set-sink-input-volume "$each" "$curVol%"
done
}
function volMute() {
# Switch to mute/unmute the volume with pactl.
if ! getCurSink; then
echo "PulseAudio not running"
return 1
fi
if [ "$1" = "toggle" ]; then
getIsMuted "$curSink"
if [ "$isMuted" = "yes" ]; then
pactl set-sink-mute "$curSink" "no"
else
pactl set-sink-mute "$curSink" "yes"
fi
elif [ "$1" = "mute" ]; then
pactl set-sink-mute "$curSink" "yes"
elif [ "$1" = "unmute" ]; then
pactl set-sink-mute "$curSink" "no"
fi
if [ $OSD = "yes" ]; then showOSD "$curSink"; fi
}
function nextSink() {
# The final sinks list, removing the blacklisted ones from the list of
# currently available sinks.
if ! getCurSink; then
echo "PulseAudio not running"
return 1
fi
# Obtaining a tuple of sink indexes after removing the blacklisted devices
# with their name.
sinks=()
local i=0
while read -r line; do
index=$(echo "$line" | cut -f1)
name=$(echo "$line" | cut -f2)
# If it's in the blacklist, continue the main loop. Otherwise, add
# it to the list.
for sink in "${SINK_BLACKLIST[@]}"; do
if [ "$sink" = "$name" ]; then
continue 2
fi
done
sinks[$i]="$index"
i=$((i + 1))
done < <(pactl list short sinks)
# If the resulting list is empty, nothing is done
if [ ${#sinks[@]} -eq 0 ]; then return; fi
# If the current sink is greater or equal than last one, pick the first
# sink in the list. Otherwise just pick the next sink avaliable.
local newSink
if [ "$curSink" -ge "${sinks[-1]}" ]; then
newSink=${sinks[0]}
else
for sink in "${sinks[@]}"; do
if [ "$curSink" -lt "$sink" ]; then
newSink=$sink
break
fi
done
fi
# The new sink is set
pacmd set-default-sink "$newSink"
# Move all audio threads to new sink
local inputs=$(pactl list short sink-inputs | cut -f 1)
for i in $inputs; do
pacmd move-sink-input "$i" "$newSink"
done
if [ $NOTIFICATIONS = "yes" ]; then
getNickname "$newSink"
notify-send "PulseAudio" "Changed output to $nickname" --icon=audio-headphones-symbolic &
fi
}
# This function assumes that PulseAudio is already running. It only supports
# KDE OSDs for now. It will show a system message with the status of the
# sink passed by parameter, or the currently active one by default.
function showOSD() {
if [ -z "$1" ]; then
curSink="$1"
else
getCurSink
fi
getCurVol "$curSink"
getIsMuted "$curSink"
qdbus org.kde.kded /modules/kosd showVolume "$curVol" "$isMuted"
}
function listen() {
local firstRun=0
# Listen for changes and immediately create new output for the bar.
# This is faster than having the script on an interval.
LANG=$LANGUAGE pactl subscribe 2>/dev/null | {
while true; do
{
# If this is the first time just continue and print the current
# state. Otherwise wait for events. This is to prevent the
# module being empty until an event occurs.
if [ $firstRun -eq 0 ]; then
firstRun=1
else
read -r event || break
# Avoid double events
if ! echo "$event" | grep -e "on card" -e "on sink" -e "on server"; then
continue
fi
fi
} &>/dev/null
output
done
}
}
function output() {
if ! getCurSink; then
echo "PulseAudio not running"
return 1
fi
getCurVol "$curSink"
getIsMuted "$curSink"
# Fixed volume icons over max volume
local iconsLen=${#VOLUME_ICONS[@]}
if [ "$iconsLen" -ne 0 ]; then
local volSplit=$((MAX_VOL / iconsLen))
for i in $(seq 1 "$iconsLen"); do
if [ $((i * volSplit)) -ge "$curVol" ]; then
volIcon="${VOLUME_ICONS[$((i-1))]}"
break
fi
done
else
volIcon=""
fi
getNickname "$curSink"
# Showing the formatted message
if [ "$isMuted" = "yes" ]; then
echo "${MUTED_COLOR}${MUTED_ICON}${curVol}% ${SINK_ICON}${nickname}${END_COLOR}"
else
echo "${volIcon}${curVol}% ${SINK_ICON}${nickname}"
fi
}
function usage() {
echo "Usage: $0 ACTION"
echo ""
echo "Actions:"
echo " help display this help and exit"
echo " output print the PulseAudio status once"
echo " listen listen for changes in PulseAudio to automatically"
echo " update this script's output"
echo " up, down increase or decrease the default sink's volume"
echo " mute, unmute mute or unmute the default sink's audio"
echo " togmute switch between muted and unmuted"
echo " next-sink switch to the next available sink"
echo " sync synchronize all the output streams volume to"
echo " the be the same as the current sink's volume"
echo ""
echo "Author:"
echo " Mario O. M."
echo "More info on GitHub:"
echo " https://github.com/marioortizmanero/polybar-pulseaudio-control"
}
case "$1" in
up)
volUp
;;
down)
volDown
;;
togmute)
volMute toggle
;;
mute)
volMute mute
;;
unmute)
volMute unmute
;;
sync)
volSync
;;
listen)
listen
;;
next-sink)
nextSink
;;
output)
output
;;
*)
usage
;;
esac

View file

View file

@ -0,0 +1,17 @@
#!/bin/sh
if ! updates_arch=$(checkupdates 2> /dev/null | wc -l ); then
updates_arch=0
fi
if ! updates_aur=$(yay -Qum 2> /dev/null | wc -l); then
updates_aur=0
fi
updates=$(("$updates_arch" + "$updates_aur"))
if [ "$updates" -gt 0 ]; then
echo "# $updates"
else
echo ""
fi

View file

@ -26,7 +26,7 @@ import XMonad.Layout.BinarySpacePartition
import XMonad.Hooks.DynamicLog import XMonad.Hooks.DynamicLog
import XMonad.Hooks.FadeInactive import XMonad.Hooks.FadeInactive
import XMonad.Hooks.ManageDocks import XMonad.Hooks.ManageDocks
import XMonad.Hooks.EwmhDesktops (ewmh) import XMonad.Hooks.EwmhDesktops (ewmh, fullscreenEventHook)
import XMonad.Hooks.SetWMName (setWMName) import XMonad.Hooks.SetWMName (setWMName)
import XMonad.Layout.Gaps import XMonad.Layout.Gaps
import XMonad.Layout.LayoutCombinators ((|||)) import XMonad.Layout.LayoutCombinators ((|||))
@ -45,7 +45,11 @@ import XMonad.Util.NamedScratchpad
import XMonad.Util.Run import XMonad.Util.Run
import XMonad.Util.SpawnOnce (spawnOnce) import XMonad.Util.SpawnOnce (spawnOnce)
import qualified XMonad.Actions.Navigation2D as Nav2d import qualified XMonad.Actions.Navigation2D as Nav2d
-- Minimize stuff
import XMonad.Layout.Minimize
import qualified XMonad.Layout.BoringWindows as BoringWindows
import XMonad.Hooks.Minimize
import XMonad.Actions.Minimize
-- }}} -- }}}
-- Values -------------------- {{{ -- Values -------------------- {{{
@ -99,7 +103,7 @@ aqua = "#8ec07c"
-- Layout ---------------------------------------- {{{ -- Layout ---------------------------------------- {{{
--layoutHints . --layoutHints .
myLayout = avoidStruts . smartBorders . toggleLayouts Full $ layouts myLayout = BoringWindows.boringWindows . minimize . avoidStruts . smartBorders . toggleLayouts Full $ layouts
where where
layouts =((rename "tall" $ onlyGaps $ mouseResizableTile {draggerType = dragger}) -- ResizableTall 1 (3/100) (1/2) [] layouts =((rename "tall" $ onlyGaps $ mouseResizableTile {draggerType = dragger}) -- ResizableTall 1 (3/100) (1/2) []
||| (rename "horizon" $ onlyGaps $ mouseResizableTileMirrored {draggerType = dragger}) -- Mirror $ ResizableTall 1 (3/100) (3/4) [] ||| (rename "horizon" $ onlyGaps $ mouseResizableTileMirrored {draggerType = dragger}) -- Mirror $ ResizableTall 1 (3/100) (3/4) []
@ -169,18 +173,24 @@ myKeys = [ ("M-C-k", sendMessage MirrorExpand >> sendMessage ShrinkSlave )
-- BSP -- BSP
, ("M-M1-h", sendMessage $ ExpandTowards L) , ("M-M1-h", sendMessage $ ExpandTowards L)
, ("M-M1-l", sendMessage $ ExpandTowards R) , ("M-M1-l", sendMessage $ ExpandTowards R)
, ("M-M1-k", sendMessage $ ExpandTowards U) , ("M-M1-k", sendMessage $ ExpandTowards U)
, ("M-M1-j", sendMessage $ ExpandTowards D) , ("M-M1-j", sendMessage $ ExpandTowards D)
, ("M-<Backspace>", sendMessage $ Swap) , ("M-<Backspace>", sendMessage $ Swap)
, ("M-M1-<Backspace>", sendMessage $ Rotate) , ("M-M1-<Backspace>", sendMessage $ Rotate)
, ("M-S-M1-h", Nav2d.windowGo L False) , ("M-S-M1-h", Nav2d.windowGo L False)
, ("M-S-M1-l", Nav2d.windowGo R False) , ("M-S-M1-l", Nav2d.windowGo R False)
, ("M-S-M1-k", Nav2d.windowGo U False) , ("M-S-M1-k", Nav2d.windowGo U False)
, ("M-S-M1-j", Nav2d.windowGo D False) , ("M-S-M1-j", Nav2d.windowGo D False)
-- Minimization
, ("M-k", BoringWindows.focusUp)
, ("M-j", BoringWindows.focusDown)
, ("M-ü", withFocused minimizeWindow)
, ("M-S-ü", withLastMinimized maximizeWindow)
] ++ copyToWorkspaceMappings ] ++ copyToWorkspaceMappings
where where
copyToWorkspaceMappings :: [(String, X())] copyToWorkspaceMappings :: [(String, X())]
@ -269,7 +279,7 @@ main = do
, logHook = myLogHook <+> dynamicLogWithPP (polybarPP dbus) <+> logHook def , logHook = myLogHook <+> dynamicLogWithPP (polybarPP dbus) <+> logHook def
, startupHook = myStartupHook <+> startupHook def , startupHook = myStartupHook <+> startupHook def
, manageHook = myManageHook <+> manageHook def , manageHook = myManageHook <+> manageHook def
--, handleEventHook = fullscreenEventHook -- , handleEventHook = minimizeEventHook <+> handleEventHook def -- fullscreenEventHook
, focusedBorderColor = aqua , focusedBorderColor = aqua
, normalBorderColor = "#282828" , normalBorderColor = "#282828"
} `removeKeysP` removedKeys `additionalKeysP` myKeys } `removeKeysP` removedKeys `additionalKeysP` myKeys

Binary file not shown.