diff --git a/files/.config/polybar/config.ini b/files/.config/polybar/config.ini index 34de83d..114c36b 100644 --- a/files/.config/polybar/config.ini +++ b/files/.config/polybar/config.ini @@ -75,9 +75,9 @@ font-2 = FontAwesome5Free:style=Solid:size=10;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-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-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" exec-if = "pgrep spotify" 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} + diff --git a/files/.config/polybar/polybar-scripts/pulseaudio-control.bash b/files/.config/polybar/polybar-scripts/pulseaudio-control.bash new file mode 100755 index 0000000..4ba0539 --- /dev/null +++ b/files/.config/polybar/polybar-scripts/pulseaudio-control.bash @@ -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 #" 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 diff --git a/files/.config/polybar/polybar-scripts/spotify_status.py b/files/.config/polybar/polybar-scripts/spotify_status.py old mode 100644 new mode 100755 diff --git a/files/.config/polybar/polybar-scripts/updates-arch-combined.sh b/files/.config/polybar/polybar-scripts/updates-arch-combined.sh new file mode 100755 index 0000000..ee6fd8b --- /dev/null +++ b/files/.config/polybar/polybar-scripts/updates-arch-combined.sh @@ -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 diff --git a/files/.xmonad/lib/Config.hs b/files/.xmonad/lib/Config.hs index 0b29817..7dec984 100644 --- a/files/.xmonad/lib/Config.hs +++ b/files/.xmonad/lib/Config.hs @@ -26,7 +26,7 @@ import XMonad.Layout.BinarySpacePartition import XMonad.Hooks.DynamicLog import XMonad.Hooks.FadeInactive import XMonad.Hooks.ManageDocks -import XMonad.Hooks.EwmhDesktops (ewmh) +import XMonad.Hooks.EwmhDesktops (ewmh, fullscreenEventHook) import XMonad.Hooks.SetWMName (setWMName) import XMonad.Layout.Gaps import XMonad.Layout.LayoutCombinators ((|||)) @@ -45,7 +45,11 @@ import XMonad.Util.NamedScratchpad import XMonad.Util.Run import XMonad.Util.SpawnOnce (spawnOnce) 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 -------------------- {{{ @@ -99,7 +103,7 @@ aqua = "#8ec07c" -- Layout ---------------------------------------- {{{ --layoutHints . -myLayout = avoidStruts . smartBorders . toggleLayouts Full $ layouts +myLayout = BoringWindows.boringWindows . minimize . avoidStruts . smartBorders . toggleLayouts Full $ layouts where 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) [] @@ -169,18 +173,24 @@ myKeys = [ ("M-C-k", sendMessage MirrorExpand >> sendMessage ShrinkSlave ) -- BSP - , ("M-M1-h", sendMessage $ ExpandTowards L) - , ("M-M1-l", sendMessage $ ExpandTowards R) - , ("M-M1-k", sendMessage $ ExpandTowards U) - , ("M-M1-j", sendMessage $ ExpandTowards D) - , ("M-", sendMessage $ Swap) - , ("M-M1-", sendMessage $ Rotate) + , ("M-M1-h", sendMessage $ ExpandTowards L) + , ("M-M1-l", sendMessage $ ExpandTowards R) + , ("M-M1-k", sendMessage $ ExpandTowards U) + , ("M-M1-j", sendMessage $ ExpandTowards D) + , ("M-", sendMessage $ Swap) + , ("M-M1-", sendMessage $ Rotate) , ("M-S-M1-h", Nav2d.windowGo L False) , ("M-S-M1-l", Nav2d.windowGo R False) , ("M-S-M1-k", Nav2d.windowGo U 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 where copyToWorkspaceMappings :: [(String, X())] @@ -269,7 +279,7 @@ main = do , logHook = myLogHook <+> dynamicLogWithPP (polybarPP dbus) <+> logHook def , startupHook = myStartupHook <+> startupHook def , manageHook = myManageHook <+> manageHook def - --, handleEventHook = fullscreenEventHook + -- , handleEventHook = minimizeEventHook <+> handleEventHook def -- fullscreenEventHook , focusedBorderColor = aqua , normalBorderColor = "#282828" } `removeKeysP` removedKeys `additionalKeysP` myKeys diff --git a/files/.xmonad/xmonad-x86_64-linux b/files/.xmonad/xmonad-x86_64-linux index bc9b2e8..7e19495 100755 Binary files a/files/.xmonad/xmonad-x86_64-linux and b/files/.xmonad/xmonad-x86_64-linux differ