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:
- introduction to luastatus;
- documentation for the “i3” barlib;
- examples for the “i3” barlib;
- luastatus’ man page.
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:
-
plugin
: specifies the plugin to receive events from (we use the alsa plugin here); -
cb
: the callback function; it should convert the data received from a plugin to the content to be shown; - (optional)
opts
: options to be passed to the plugin; - (optional)
event
: click event handler; see the “Handling click events” section of this document.
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:
- alsa-interactive-gauge and pulse-interactive-gauge examples;
- btc-price example;
- update-on-click example.
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.