Skip to content

Instantly share code, notes, and snippets.

@Earnestly
Last active August 7, 2023 09:06
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Earnestly/6ff1fa506d58f5a2a6f611f76e5f80b4 to your computer and use it in GitHub Desktop.
Save Earnestly/6ff1fa506d58f5a2a6f611f76e5f80b4 to your computer and use it in GitHub Desktop.
Simple reactive statusbar

The main event loop

statusdir=$XDG_RUNTIME_DIR/statusbar
mkdir -pm0700 "$statusdir"

inotifywait -rqme close_write --format %f "$statusdir" | while read -r file; do
    if read -r -- "$file" < "$statusdir/$file"; then
        # ...
    fi
done

inotifywait can watch a directory of files. When one of those files triggers the close_write event a new variable can be synthesised based on the file name using read. The new variable will contain the first line of data from the file itself.

These new variables can be used with printf to create a formatted string designed for use in a typical statusbar.

If using bash it may also be possible to load the sleep loadable so in the rare case it is needed the cost of fork and exec can be eschewed.

enable -f "$(pkg-config --variable=loadablesdir bash)"/sleep sleep
Note
This event loop can be arbitrarily fast. Any pre-formatting should be done before the values are written to files watched by inotifywait so only a minimum amount of work is needed display them inside the loop.

Using crond(8) or systemd.timer(5)

Some status updates which cannot be done using notification and have usually long intervals can be easily provided with per-user crond or systemd.timer services.

For example given a battery-status script, we could run it every five minutes using the following crontab:

*/5 *  *  *  * battery-status > "$XDG_RUNTIME_DIR"/statusbar/battery

Considerations

  • Pre-initialised data

  • Multi-user

  • Persistence

Examples

Although all these examples are simple sh scripts it does not mean only shell can be used. Any language capable of writing files (even to stdout) is suitable for this.

Currently playing title from MPD

#!/bin/sh --

statusdir=$XDG_RUNTIME_DIR/statusbar
mkdir -pm0700 "$statusdir"

fmt='[[%artist% - ]%title%]|[%file%]'

mpc current -f "$fmt" > "%statusdir"/mpd

mpc idleloop | while read -r event; do
    case $event in
        player) mpc current -f "$fmt" > "%statusdir"/mpd
    esac
done

Real-time pulseaudio volume monitor

Use pactl subscribe to provide real-time volume notification for default sinks.

Note
One potential improvement would be to pair this with another pacmd running as a "daemon" using exec_with_pipe to accept commands over a control fifo thereby avoiding execution of multiple pulseaudio clients.
#!/bin/sh --

statusdir=$XDG_RUNTIME_DIR/statusbar
datadir=$statusdir/data

mkdir -pm0700 -- "$statusdir" "$datadir"

if ! pulseaudio --check; then
    printf 'statusbar: pulsemonitor: pulseaudio not running\n' >&2
    exit 1
fi

pastat() {
    pactl -f json list "$1" | jq -r '
        .[] | select(.state == "RUNNING" and (.muted | not)).volume
            | [keys_unsorted[] as $k | .[$k].value_percent[:-1] | tonumber]
            | add / length'
}

# Initial values.
pastat sinks > "$datadir"/vol
pastat sources > "$datadir"/mic

mkfifo -- "$statusdir"/pulsemonitor.pipe
pactl subscribe > "$statusdir"/pulsemonitor.pipe &

while read -r _ event _ type _; do
    if [[ $event = \'change\' ]]; then
        case $type in
            sink) pastat sinks > "$datadir"/vol ;;
            source) pastat sources > "$datadir"/mic
        esac
    fi
done < "$statusdir"/pulsemonitor.pipe
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment