awesome window manager, i3lock, xautolock and suspend to disk

With my last laptop upgrade I started using awesome as a Window Manager.

I wasn't sure of the choice at first: I have never liked graphical interfaces, and the thought of having to write lua code to get my GUI to provide even basic functionalities wasn't very appealing to me.

However, I have largely enjoyed the process so far: even complex changes are relatively easy to make, while the customizability has improved my productivity while making the interface more enjoyable for me to use.

The switch, however, has forced me to change several things in my setup. Among others, I ended up abandoning xscreensaver for i3lock and xautolock, while changing a few things on my system to better integrate with the new environment.

In this article, you will find:

  • A description of how to use xautolock together with i3lock to automatically lock your screen after X minutes of inactivity and when the laptop goes to sleep via ACPI.

  • My own recipe to display the battery status on the top bar of Awesome. This is very similar to existing suggestions on the Awesome wiki, except there is support for displaying the status of multiple batteries at the same time. Which, for how rare this may sound, is something supported on my laptop which I regularly use (x230 with 19+ cell slice battery).

  • How I got NetworkManager to display properly.

  • Some details on what I had to do to get Suspend to disk to work.

In short: a handy list of things I had to plumb in manually to get the environment I wanted.

Using xautolock and i3lock

For those of you who are not familiar with these projects, i3lock is a very simple graphical screen lock program, part of the i3 window manager. When run, it just blanks your screen, and asks you for a password to unlock it.

xautolock instead just monitors your keyboard and mouse for activity. If both are inactive for a configurable amount of time, xautolock will run a command of your choice, like i3lock.

Before starting

... make sure you have xautolock and i3lock installed. On a Debian system, this means running:

sudo -s
apt-get install xautolock
apt-get install i3lock

Starting xautolock

To use xautolock with i3lock in awesome I started off writing a really simple shell script which I put in ~/.config/awesome/locker.sh:

#!/bin/sh

exec xautolock -detectsleep 
  -time 3 -locker "i3lock -d -c 000070" \
  -notify 30 \
  -notifier "notify-send -u critical -t 10000 -- 'LOCKING screen in 30 seconds'"

This script starts xautolock such us it will count time correctly even if the laptop goes to sleep (-detectsleep), lock the screen after 3 minutes of inactivity (-time 3) by running the command i3lock -d -c 000070 (-locker ...), but notify me that the screen is about to be locked 30 seconds early (-notify 30) by running the command notify-send -u critical ....

Let's look at the individual commands now.

notify-send is a really simple program that asks the dbus daemon on your system to notify you of an important event. To use it, you need to make sure that libnotify-bin in debian like systems is installed, with apt-get install libnotify-bin. You can then play with it by just running something like:

notify-send "hello world!"

On most window managers, this will result in a small pop up somewhere on your screen saying "hello world!". The -u critical option just hints the window manager that this is an important message, which colors it in a nice shade of red on awesome. -t 10000 tells the window manager to automatically delete the message in 10 seconds, so I don't really have to close it.

i3lock instead is what is really locking the screen. The -d option instructs i3lock to put the display to sleep, while the -c option picks the background color. For some reason, I did not like the default color, and preferred a nice shade of blue.

Now that I had this script, I had to instruct awesome to run it whenever the window manager was started. This was as simple as editing ~/.config/awesome/rc.lua and adding:

awful.util.spawn_with_shell('~/.config/awesome/locker')

at the end of the script.

Locking screen on command

One thing I really wanted to have is a key binding to allow to quickly lock the screen. Adding one was easy, I just had to edit ~/.config/awesome/rc.lua one more time and add:

awful.key({ modkey, "Control" }, "l",
          function ()
              awful.util.spawn("sync")
              awful.util.spawn("xautolock -locknow")
          end),

to make window + control + l lock my screen. Note that xautolock is able to both spawn a new xautolock daemon, or

Locking screen on sleep

Also, locking the screen when the laptop is put to sleep seemed like a really good idea. On my debian system, all I had to do was add a script /etc/pm.d/sleep/lock containing:

#!/bin/sh

logger "$0 - locking screen after sleep."
xautolock -locknow &> /dev/null

Just put your laptop to sleep now, and check /var/log/syslog to make sure the script was invoked. Don't forget to chmod 0755 /etc/pm.d/sleep/lock.

Battery status

Something that bothered me on awesome was the lack of an easy way to see the battery status.

The awesome wiki has several suggestions on how to show it. My main issue though is that my laptop allows me to have up to 2 batteries connected at the same time, and really, I want to see the status of both batteries.

What I came up with is a variation of what is already suggested on the wiki: 1. It runs the acpi command to get the status of each battery. 2. It formats it nicely and displays it in the top status bar.

Let's start with the function to get the battery status:

-- Create an ACPI widget
function GetBatteryState()
  local command = "acpi -b |sed -e 's@.*\\([0-9]:\\) [^,]*,@\\1@' -e 's@remaining@@' | sed -e :a -e '$!N;s@\\n@| @;ta'"
  local fh = assert(io.popen(command, "r"))
  local text = " | " .. fh:read("*l") .. " | "
  fh:close()
  return text
end

the function GetBatteryState just returns a string with the battery state. You can try to run command yourself to see the output, but here's what it looks like on my system:

acpi -b |sed -e 's@.*\\([0-9]:\\) [^,]*,@\\1@' -e 's@remaining@@' | sed -e :a -e '$!N;s@\\n@| @;ta'
0: 94%, 05:42:26 | 1: 76%

which should be pretty self explanatory: battery 0 has 94% charge, and will take 5 hours to discharge, while battery 1 has 76% charge, and not being used.

Now we need to create a widget, a graphic element to display this text, and make sure the function above is run once per minute.

The tricky part here is that Awesome 3.4 and Awesome 3.5 have significantly different APIs, that are not compatible with each other.

So, if you are running awesome 3.4, you need to add something like:

batterywidget = widget({ type = "textbox" })
batterywidget.text = GetBatteryState()
batterywidgettimer = timer({ timeout = 60 })
batterywidgettimer:add_signal("timeout",
  function()
    batterywidget.text = GetBatteryState()
  end
)
batterywidgettimer:start()

While if you are running Awesome 3.5, you need something like:

batterywidget = wibox.widget.textbox()
batterywidget:set_text(GetBatteryState())
batterywidgettimer = timer({ timeout = 60 })
batterywidgettimer:connect_signal("timeout",
  function()
    batterywidget:set_text(GetBatteryState())
  end
)
batterywidgettimer:start()

Now there is one last thing you need to do: tell awesome to display this box somewhere on the screen.

This, once again, changes based on the version of Awesome you are using. Let's start with 3.4, you need to have something like below in your rc.lua:

for s = 1, screen.count() do
    -- Create a promptbox for each screen
    mypromptbox[s] = awful.widget.prompt({ layout = awful.widget.layout.horizontal.leftright })
    -- Create an imagebox widget which will contains an icon indicating which layout we're using.
    -- We need one layoutbox per screen.

    [...]

    mywibox[s].widgets = {
        {
            mylauncher,
            mytaglist[s],
            mypromptbox[s],
            layout = awful.widget.layout.horizontal.leftright
        },
        mylayoutbox[s],
        mytextclock,
        s == 1 and mysystray or nil,
        batterywidget,
        mytasklist[s],
        layout = awful.widget.layout.horizontal.rightleft
    }
end

For Awesome 3.5, instead:

for s = 1, screen.count() do
    -- Create a promptbox for each screen
    mypromptbox[s] = awful.widget.prompt()
    -- Create an imagebox widget which will contains an icon indicating which layout we're using.
    -- We need one layoutbox per screen.

    [...]

    -- Create the wibox
    mywibox[s] = awful.wibox({ position = "top", screen = s })

    -- Widgets that are aligned to the left
    local left_layout = wibox.layout.fixed.horizontal()
    left_layout:add(mylauncher)
    left_layout:add(mytaglist[s])
    left_layout:add(mypromptbox[s])

    -- Widgets that are aligned to the right
    local right_layout = wibox.layout.fixed.horizontal()
    right_layout:add(batterywidget)
    if s == 1 then right_layout:add(wibox.widget.systray()) end
    right_layout:add(mytextclock)
    right_layout:add(mylayoutbox[s])

    -- Now bring it all together (with the tasklist in the middle)
    local layout = wibox.layout.align.horizontal()
    layout:set_left(left_layout)
    layout:set_middle(mytasklist[s])
    layout:set_right(right_layout)

    mywibox[s]:set_widget(layout)

Eg, some code that for each configured screen defines which widgets are to be shown. Note that in both snippets above I added my batteryidget nearby the mytextclock (the time indication), and mysystray (where all the small icons for things like chat, network, sound... go), in a right to left layout. But really, you can put the widget wherever you like it. The order is important, just move it around, restart awesome with Mod4 + Control + r or by quitting it with Mod4 + Shift + q and look at the result.

And that's it, I now had my battery indication.

Network Manager

After a few months of using nmcli, the network manager command line interface, to configure wifi I really wanted to get back to a graphical widget. Two main reasons pushed me in this direction:

  1. Really, nmcli on my system is buggy. It is easy to crash it and often hard to parse the output (talking about 0.9.8.10).
  2. I don't use it often enough to remember the command line parameters, and every time is a new learning experience. This is because once a network is configured, NetworkManager will happily connect automatically without bothering you.
  3. When you are in a hurry, it's just nice to have a drop down menu with options to click on.

On the downside, it seems like nm-applet, in the network-manager-gnome, brought in lots of dependencies I really did not want on my system.

The compromise I reached was to install network-manager-gnome with --no-install-recommends, like:

sudo -s
apt-get --no-install-recommends install network-manager-gnome

Getting nm-applet to show up in awesome was a breeze: just added to my rc.lua the line:

awful.util.spawn_with_shell('nm-applet')

at the end of the file. The only annoying thing about the simple approach used here is that reloading the config file in awesome will cause multiple network manager statuses to be displayed.

Getting suspend to disk to work

Finally, I wanted suspend to disk to run at the simple press of a button. Unfortunately, it doesn't seem like my x230 has a pre-configured button for it. However, it has a ThinkVantage button that is pretty much useless in linux. So, all I had to do was tell acpid to turn ThinkVantage key presses in requests to suspend to disk. To do so, I created a file /etc/acpi/events/my-suspend with:

event=button/prog1 PROG1 00000080 00000000
action=/etc/acpi/sleep_suspendbtn.sh suspend

and run service acpid restart. Note that I discovered what to write in the line event=button/prog1 ... by running acpi_listen, and cutting and pasting the result after event=.


Other posts

Technology/System Administration