i3 - improved tiling WM


User-contributed article: responsive, customizable and featureful status bar with luastatus and i3bar

luastatus is a universal status bar content generator, meaning that it supports multiple status bars and many event sources.

Its main feature is that the content can be updated immediately as some event occurs, be it a change of keyboard layout, active window title, volume or a song in your favorite music player (provided that there is a plugin for it). This is in contrast to the “traditional” approach (for example, that of i3status, the recommended tool for status bar content generation) of updating the content on timer. Note that i3status also supports updating on signal, but this is arguably hard to use, and in common implementation requires at least one fork per update.

In this article, we will focus on some particularities of using luastatus with i3bar.

Documentation and examples

Please refer to:

Using luastatus with “i3” barlib

Install luastatus using the instructions from the documentation. After that, you must be able to run luastatus-i3-wrapper in terminal:


$ luastatus-i3-wrapper
luastatus: warning: no widgets specified (see luastatus(1) for usage info)
{"version":1,"click_events":true,"stop_signal":0,"cont_signal":0}
[

You may now terminate luastatus-i3-wrapper by killing it with SIGINT signal (the usual key combination for that is Ctrl+C).

The next step is to configure i3bar to use an appropriate status command. For the purposes of demonstration, we will use two widgets from luastatus’ examples/i3 directory: time and volume.

Copy those examples to the directory of your choice, for example, to ~/.config/luastatus:


$  # we assume we are in the luastatus repo directory
$ mkdir ~/.config/luastatus  # if this directory has not been already created
$ cp examples/i3/time-battery-combined.lua examples/i3/alsa.lua ~/.config/luastatus

Now, let’s edit the i3 config, usually found at ~/.config/i3/config. We need to change the status_command parameter inside the bar section to something like this:


bar {
    status_command cd ~/.config/luastatus && exec luastatus-i3-wrapper time-battery-combined.lua alsa.lua

Now, in order to see luastatus in action, you need to restart i3bar with the following command:


$ i3-msg restart

You should now see the time and volume indicators; and, in case you are using a laptop and it is not fully charged, also a battery indicator.

If you don’t like the separators, you can pass -B no_separators to luastatus-i3-wrapper:


bar {
    status_command cd ~/.config/luastatus && exec luastatus-i3-wrapper -B no_separators time-battery-combined.lua alsa.lua

Writing widgets

Widgets for luastatus are written in the Lua programming language. Don’t worry if you don’t yet know Lua — it’s simple and similar to other imperative programming languages.

Let’s see what the structure of a widget is, using the alsa.lua widget as example:


widget = {
    plugin = 'alsa',
    cb = function(t)
        if t.mute then
            return {full_text = '[mute]', color = '#e03838'}
        else
            local percent = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min) * 100
            return {full_text = string.format('[%3d%%]', math.floor(0.5 + percent)), color = '#718ba6'}
        end
    end,
}

The global variable widget is inspected by luastatus; it must be a table. It may have the following fields:

For more information on plugins, their options and arguments they pass to cb, see luastatus plugins; for more information on the format of cb return value, see i3 barlib description; for examples, see i3 examples.

Using derived plugins

Things we referred to as “plugins” previously, are written in the C programming language; we will also call them “regular plugins” here. But luastatus also features derived plugins, which are plugins written in Lua that use regular plugins.

One example of such a plugin is cpu-usage-linux, a CPU usage plugin for Linux. Here is how you use it:


widget = luastatus.require_plugin('cpu-usage-linux').widget{
    cb = function(usage)
        if usage ~= nil then
            return {full_text = string.format('[%5.1f%%]', usage * 100)}
        end
    end,
}

Another examples of derived plugins are: backlight-linux, battery-linux, file-contents-linux, imap, mem-usage-linux, pipe.

For more information on derived plugin, see the “DERIVED PLUGINS” section of the man page.

Handling click events

In order to handle click events on a particular widget, you need to define the event field of the global widget table. Its value might be either a function, or a string.

If the event field of the global widget table is a function, then it is invoked each time the i3 barlib reports a click event on this widget; its argument is simply a table wit all the click properties reported by i3bar; see i3bar protocol § Click events. (Although note that name is excluded as it is set automatically and is meaningless to the widget; in order to find out which segment was clicked, set the instance property on segments in the cb function.)

A simple example of handling click events would be opening a terminal emulator with alsamixer whenever a volume widget is clicked with left mouse button:


widget = {
    plugin = 'alsa',
    cb = function(t)
        if t.mute then
            return {full_text = '[mute]', color = '#e03838'}
        else
            local percent = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min) * 100
            return {full_text = string.format('[%3d%%]', math.floor(0.5 + percent)), color = '#718ba6'}
        end
    end,
    event = function(t)
        if t.button == 1 then     -- left mouse button
            os.execute('x-terminal-emulator -e alsamixer &')
        end
    end,
}

Some interesting examples of click events handlers can be found in:

Note that this function gets executed in the same Lua state in which the callback function gets executed, which means they can not be executed in parallel; so the click event handler should not block for non-trivial time.

If the event field of the global widget table is a string, then it is compiled as a function in a separate state. This way, it can be executed in parallel with the callback function, and so this is useful when the callback function may block for a long time. An example of using a separate-state click event handler can be found in the gmail example.

If you don’t want to handle click events at all, you may consider passing -B no_click_events to luastatus-i3-wrapper. This will change i3bar behaviour in that it will interpret “clicks” on segments as if an empty space on the bar was clicked, particularly, will switch workspaces if you scroll on a segment.