View on GitHub

I3cat

A simple and flexible program which gathers efficiently i3bar blocks from multiple sources (scripts, i3status, etc.).

download .ZIPdownload .TGZ

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.

wercker status

Motivation

Walkthrough

Install

Several options:

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.