Instantly Responsive Sway Status using Unix Socket
In the Sway window manager, we can provide a program to be run to display system information by setting status_command
in the bar
section of our configuration file. The comment above it, in the default configuration file, explains "when the status_command prints a new line to stdout, swaybar updates."
Given that information, you may instinctively reach for a loop with a brief delay in order to keep the bar data up to date. Such an approach is commonly suggested, perhaps in part because the default command does exactly that:
status_command while date +'%Y-%m-%d %X'; do sleep 1; done
The trouble here is twofold.
First, as the bar command increases in complexity, it continues to be run every second, burning processing resources it might otherwise conserve.
Second, and more pressing, if you include volume, or brightness, or any other user changeable information, you will sometime experience a noticeable delay, even running the command once a second.
We will solve this delay using simple shell scripting and basic Linux command line tools, but first, we must write a status bar text generator shell script.
Generating the Status Text
The entire file is found below, broken into sections and explained.
Using a Nerd Font for Icons
Before we begin with the script itself, a prerequisite for including icons is a font with the correct symbols included. I use a nerd font to do this, specifically Meslo LGS (line-gap small) NF (nerd font), which I also include in this page to ensure the icons display correctly below.
Coloring the Status Icons
To ensure our small icons are easily differentiable we apply color to them.
Defining the Colors
The colors are from a custom theme I created called workmanlike. Many of them are not used below but are included for completeness and potential future use.
# Dark Colors
dBlack="#1e1c1a"
dGray="#7e7c7a"
dWhite="#ceccca"
dBlue="#20419a"
dPurple="#5d377b"
dPink="#883067"
dRed="#962a1c"
dYellow="#916518"
dGreen="#406d00"
dTeal="#2d746e"
# Bright Colors
bBlack="#4e4c4a"
bGray="#9e9c9a"
bWhite="#eeecea"
bBlue="#5273cd"
bPurple="#8f69ad"
bPink="#ba6299"
bRed="#c85c4e"
bYellow="#c3974a"
bGreen="#729f32"
bTeal="#5fa6a0"
Applying the Colors
We apply color to an icon with a small function. Argument $1
is the color, $2
is the icon to be colored, and $3
is the text value. All of this appears on an off-white background.
colorize() {
echo "<span background='$bWhite' foreground='$dBlack'><span foreground='$1'> $2 </span>$3 </span>"
}
Note, colorize
uses Pango markup instead of ANSI control sequences, making the colors and output results easier to reason about.
Backlight Level
We color the icon yellow once light
is used to get the brightness level, and awk
to retain just the integer part from before the decimal.
glow=$(colorize $dYellow "" $(light -G | awk -F '.' '{print $1}'))
Battery State
For the battery we use twenty-two separate icons to indicate the state granularly.
Discharging Icons
Here, $1
is the battery level, and we use case 1 in
to seek the first true check for what the level is less than or equal to.
battery_icon() {
case 1 in
$(($1<=5)) ) echo "";;
$(($1<=15))) echo "";;
$(($1<=25))) echo "";;
$(($1<=35))) echo "";;
$(($1<=45))) echo "";;
$(($1<=55))) echo "";;
$(($1<=65))) echo "";;
$(($1<=75))) echo "";;
$(($1<=85))) echo "";;
$(($1<=95))) echo "";;
* ) echo "";;
esac
}
Charging Icons
charging_icon() {
case 1 in
$(($1<=5)) ) echo "";;
$(($1<=15))) echo "";;
$(($1<=25))) echo "";;
$(($1<=35))) echo "";;
$(($1<=45))) echo "";;
$(($1<=55))) echo "";;
$(($1<=65))) echo "";;
$(($1<=75))) echo "";;
$(($1<=85))) echo "";;
$(($1<=95))) echo "";;
* ) echo "";;
esac
}
Low Battery Color
When at ten percent or below the color switches from green to red as a warning.
charge_color() {
if [[ $(($1<=10)) == 1 ]]
then
echo $dRed
else
echo $dGreen
fi
}
Formulate Return
The battery charge and status can is retrieved from /sys
files, the icon is selected based on the status and charge, and the icon is colored.
battery() {
charge=$(cat /sys/class/power_supply/BAT1/capacity)
status=$(cat /sys/class/power_supply/BAT1/status)
if [ "$status" = "Charging" ]
then
icon=$(charging_icon $charge)
else
icon=$(battery_icon $charge)
fi
echo "$(colorize $(charge_color $charge) $icon $charge)"
}
Volume State
Level Icon
Uses the same approach as the battery icons above.
volume_icon() {
case 1 in
$(($1<=34))) echo "";;
$(($1<=68))) echo "";;
* ) echo "";;
esac
}
Formulate Return
Getting the volume level and mute status lacks a universal approach. Here, I use pactl
with awk
to pull just the needed information from the returned strings.
Based on the mute status the correct icon is selected. The result is colored and returned with echo
.
volume() {
level=$(
pactl get-sink-volume @DEFAULT_SINK@ | head -n1 |
awk '{print substr($5, 1, length($5)-1)}'
)
mute=$(pactl get-sink-mute @DEFAULT_SINK@ | awk '{print $2}')
case $mute in
yes) icon="";;
no) icon=$(volume_icon $level);;
esac
echo "$(colorize $dPink $icon $level)"
}
Time
Time is separate from date, so each has its own section and icon, which is visually preferable.
Clock Icon
For each hour, which date +%-I
provides, a distinct clock icon with the hands in the correct position is used.
clock_icon() {
case `date +%-I` in
1) echo "";;
2) echo "";;
3) echo "";;
4) echo "";;
5) echo "";;
6) echo "";;
7) echo "";;
8) echo "";;
9) echo "";;
10) echo "";;
11) echo "";;
12) echo "";;
esac
}
Formulate Return
The time format is standard American am/pm.
time=$(colorize $dPurple `clock_icon` $(date +"%-I:%M%P"))
Date
Includes the day of the week and day of the month.
Suffix
The day of the month looks nicer with a suffix, and it is simple to add due to the limited set of numbers.
day_suffix() {
case `date +%-d` in
1|21|31) echo "st";;
2|22) echo "nd";;
3|23) echo "rd";;
*) echo "th";;
esac
}
Formulate Return
The date is formatted abbreviated day of the week name, abbreviated month name, day of the month number with suffix.
date=$(colorize $dBlue "" "$(date +"%a %b %-d`day_suffix`")")
Socket Writing
When we run all of this and build our status bar string, we ultimately return it not to standard out, but rather to a Unix socket.
We do this with netcat, in my case the OpenBSD variant, run as the nc
command, to write the information to the status_pipe
file, which we will read in our second shell script.
printf " $glow `volume` `battery` $time $date \n" | nc -N -U /tmp/status_pipe
Triggering a Status Text Update
Cleaning on Exit
To avoid leaving a service running and the pipe file lying around, we make use of trap
to run our cleanup function on EXIT and SIGINT.
cleanup() {
rm -f /tmp/status_pipe
systemctl --user stop Sway_Status.{service,timer}
systemctl --user reset-failed Sway_Status.{service,timer}
}
trap cleanup EXIT
trap "exit" INT
Listen to the Socket
We again use netcat, this time to -k
eep -l
istening on our -U
nix socket, and for each newline we print the generated status.
nc -k -l -U /tmp/status_pipe | while IFS='\n' read -r current_status; do
printf "$current_status\n"
done
Calling Status Generation
At this point we load the generation shell script path and run it immediately to get our initial status bar.
generate_status="$HOME/.local/bin/sway-status/socket-generate.sh"
$generate_status
on brightness with inotify
We -m
onitor the close_write
-e
vent on the brightness
file with inotifywait
. This will be immediately updated when any change to brightness occurs, and will therefore regenerate the status.
(
inotifywait -m -e close_write /sys/class/backlight/intel_backlight/brightness |
while read -r; do $generate_status; done
) &
on audio with pactl
By using the subscribe
command we can use pactl
to monitor all changes to the audio system. We use ripgerp (rg
) to continue the pipe only on lines that indicate a change to the audio sink. This will appear on volume changes and mute.
(
pactl subscribe |
rg --line-buffered 'change.*sink' |
while IFS= read -r line; do $generate_status; done
) &
on time with systemd-run
Finally, we create a systemd transient service which runs every minute on the minute to update our status.
systemd-run \
--unit=Sway_Status \
--quiet \
--user \
--on-calendar '*:0/1' \
$generate_status
Of all the monitors, this has tended to be the least stable.
Summary
While there are many existing flexible sway status bar programs you can easily customize, there is something valuable about writing your own, understanding how the pieces fit together. So you shouldn't take what I've written directly, but rather learn from it to build for yourself, to grow in knowledge and skill.
Further, what I have here is far from perfect. For example, instead of re-generating the entire status bar text, we could just regenerate the changed bit. I may in fact do this in the future, but for now I leave it as an exercise for the reader.