A simple and flexible program which gathers efficiently i3bar blocks from multiple sources (scripts, i3status, etc.).
Handles click events, multiple blocks from one script, signals forwarding and unsynced time intervals.
Motivation
- enjoy the simplicity of i3status, do not replace it with a fully featured wrapper
- use simple shell scripts to add new i3bar blocks
Walkthrough
Install
Several options:
- Download a binary for your platform
-
Install Go and run
go get github.com/vincent-petithory/i3cat
- If you're on Arch Linux, you can install from AUR.
Get what you had with i3status:
status_command i3status --config ~/.i3/status
becomes status_command echo "i3status --config ~/.i3/status" | i3cat
But since you will want to add other blocks, it's more handy to add the commands in a conf file:
$ cat ~/.i3/i3cat.conf
# i3 status
i3status -c ~/.i3/status
and the status command is now status_command i3cat
(~/.i3/i3cat.conf
is the default location for its conf file).
Note that your i3status'conf must have his output in i3bar format. If you didn't have it yet, modify it as follows:
general {
...
output_format = i3bar
...
}
Add a block
Say we want to display the current song played by MPD and its state. The script could be:
$ cat ~/.i3/mpd-nowplaying.sh
#!/bin/sh
display_song() {
status=
color=
case $(mpc status | sed 1d | head -n1 | awk '{ print $1 }') in
'[playing]')
status=
color='#36a8d5'
;;
'[paused]')
status=
color=
;;
esac
echo '[{"name": "mpd", "instance": "now playing", "full_text": " '${status}' '$1'", "color": "'${color}'"}]'
}
(while :; do
display_song "$(mpc current --wait)"
done) &
while :; do
display_song "$(mpc current)"
mpc idle player > /dev/null
done
Edit ~/.i3/i3cat.conf
:
$ cat i3cat.conf
# mpc status
~/.i3/mpd-nowplaying.sh
# i3 status
i3status -c ~/.i3/status
The order matters: the output of the commands are sent to i3bar in that order.
Lines starting with #
are comments and ignored.
Note the JSON output of the script is an array. i3cat
also supports variants like the output from i3status
: a i3bar header (or not) followed by an infinite array.
Replace echo by i3cat encode
Outputting JSON by hand works only for simple cases.
i3cat
provides a helper command to send blocks:
echo '[{"name": "mpd", "instance": "now playing", "full_text": " '${status}' '$1'", "color": "'${color}'"}]'
becomes
i3cat encode --name mpd --instance "now playing" --color "${color}" " ${status} $1"
Listen for click events on a block
i3cat listens for click events generated by the user and writes their JSON representation to the STDIN of the command which created the clicked block.
See the i3bar protocol for details on its structure.
Using our MPD script from above, we want that when we click on its block, we want i3 to focus a container marked as music (e.g ncmpcpp).
All that is needed is to read the process' STDIN
. Each i3bar click event is output on one line, so a generic recipe boils down to:
cat | while read line; do on_click_event "$line"; done
on_click_event
will parse the JSON output and perform the action.
To read properties of an incoming click event, you could use:
#!/bin/sh
click_event_prop() {
python -c "import json,sys; obj=json.load(sys.stdin); print(obj['$1'])"
}
...
# read the 'button' property
button=$(echo $@ | click_event_prop button)
But i3cat
provides a decode command for that:
button=$(echo $@ | i3cat decode button)
Full example below:
#!/bin/sh
display_song() {
status=
color=
case $(mpc status | sed 1d | head -n1 | awk '{ print $1 }') in
'[playing]')
status=
color='#36a8d5'
;;
'[paused]')
status=
color=
;;
esac
i3cat encode --name mpd --instance "now playing" --color "${color}" " ${status} $1"
}
on_click_event() {
button=$(echo $@ | i3cat decode button)
case $button in
1)
i3-msg '[con_mark="music"]' focus > /dev/null
;;
esac
}
(while :; do
display_song "$(mpc current --wait)"
done) &
(while :; do
display_song "$(mpc current)"
mpc idle player > /dev/null
done) &
cat | while read line; do on_click_event "$line"; done
Case of programs which you can't read stdin from
You simply need to wrap them in a script of your choice. Example with i3status and a Shell script:
#!/bin/sh
on_click_event() {
button=$(echo "$@" | i3cat decode button)
if [ $button != '1' ]; then
return
fi
name=$(echo "$@" | i3cat decode name)
instance=$(echo "$@" | i3cat decode instance)
# Do something with block $name::$instance ...
}
# Output i3status blocks
i3status -c $HOME/.i3/status &
# Read stdin for JSON click events
cat | while read line; do on_click_event "$line"; done
More
Run i3cat -h
for a list of options:
Usage: i3cat [COMMAND] [ARGS]
If COMMAND is not specified, i3cat will print i3bar blocks to stdout.
-cmd-file="$HOME/.i3/i3cat.conf": File listing of the commands to run. It will read from STDIN if - is provided
-debug-file="": Outputs JSON to this file as well; for debugging what is sent to i3bar.
-header-clickevents=false: The i3bar header click_events
-header-contsignal=0: The i3bar header cont_signal. i3cat will send this signal to the processes it manages.
-header-stopsignal=0: The i3bar header stop_signal. i3cat will send this signal to the processes it manages.
-header-version=1: The i3bar header version
-log-file="": Logs i3cat events in this file. Defaults to STDERR
decode: FIELD
Reads STDIN and decodes a JSON payload representing a click event; typically sent by i3bar.
It will print the FIELD from the JSON structure to stdout.
Possible fields are name, instance, button, x, y.
encode: [OPTS] [FULL_TEXT...]
Concats FULL_TEXT arguments, separated with spaces, and encodes it as an i3bar block JSON payload.
If FULL_TEXT is -, it will read from STDIN instead.
The other fields of an i3bar block are optional and specified with the following options:
-align="": the block.align field to encode.
-color="": the block.color field to encode.
-instance="": the block.instance field to encode.
-min-width=0: the block.min_width field to encode.
-name="": the block.name field to encode.
-separator=false: the block.separator field to encode.
-separator-block-width=0: the block.separator_block_width field to encode.
-short-text="": the block.short_text field to encode.
-single=false: If true, the block will not be in a JSON array. This allows to combine other blocks before sending to i3bar.
-urgent=false: the block.urgent field to encode.
Design
i3cat
sends data to i3bar only when necessary: when a command sends an updated output of its blocks, i3cat
caches it and sends to i3bar the updated output of all blocks, using the latest cached blocks of the other commands. This means commands don't need to have the same update frequency.
It is not advised to send SIGSTOP and SIGCONT signals toi3cat
, as its subprocesses will continue to output data anyway.
For pausing and resuming processing (usually asked by i3bar), i3cat
will listen for SIGUSR1 and SIGUSR2 for pausing and resuming, respectively. It will then forward the signals specified with -header-stopsignal
and -header-contsignal
flags (defaults to SIGSTOP and SIGCONT) to all its managed processes.