Saving & Restoring Mate Terminal’s Color-Profile Information

Terminal palettes come in infinite variations. Almost everyone has a “favorite” palette, and if you are like me, you probably hate losing all your finely tuned terminal colors, because you wanted to experiment with this new color scheme and forgot to save your original color profile. The script I’m about to describe can save all color-related information for mate-terminal, and can be easily adapted to work at least for gnome-terminal in GNOME 2.X desktops.

The GUI-Based Way of Saving Profile State

One way to save your current color preferences, along with everything else related to the current terminal profile, is to create a full copy of your mate-terminal profile. This can be done through the “Profiles” dialog of mate-terminal. First you navigate to the “Edit ▸ Profiles” menu entry:

Profile menu of mate-terminal

Profile menu of mate-terminal

Then you create a new profile, based on your current one:

Profile dialog of mate-terminal

Profile dialog of mate-terminal

This way you can mess with any of the two profiles, either the original or the new copy, without worrying that you will lose any important settings.

The Scripted Way of Saving Color Settings

The good aspect of having a GUI to save your profile state is that it’s accessible to everyone, and very easy to use. One of the disadvantages is that your settings are all stored in your config database, which also contains a gazillion other options and is not necessarily easy to backup and restore in one step. A simple search for ‘delete your “.config” directory gnome‘ yields many web pages where people recommend deleting the entire ~/.config directory and starting over. This makes me feel rather cautious about depending on always having the full contents of .config around, so I started looking for an alternative way of saving mate-terminal color information: one that I can reliably script myself; one that stores results in a plain text file that I can read easily, fast and with any tool.

So I wrote the shell script shown below. The main idea behind the script is that it should be possible to run a single command and get as output a set of mateconftool-2 commands that will instantly restore my color settings. This way I can save my color profile information by e.g. typing:

mate-terminal-save-colors.sh > terminal-colors.sh

Then if I mess with my color settings, I can just run the resulting script to restore them:

sh terminal-colors.sh

Finding the right mate configuration database keys was not very hard. I originally saw a shell script that tweaks gconf2 database keys when I was experimenting with the solarized color theme for gnome-terminal. There is a nice set of shell scripts and palettes at Github, created by Sigurd Gartmann, that contains a full set of gconf2 keys for gnome-terminal’s color information, as part of its installed scripts. The keys are listed in the set_dark.sh and set_light.sh shell scripts of the gnome-terminal-colors-solarized project.

Adapting the keys and wrapping them in a bit of shell script code, I came up with the following mate-terminal-save-colors.sh script.

Note: The shell script is also available online, as part of my “miscellaneous scripts” collection, at: http://bitbucket.org/keramida/scripts/src/tip/mate-terminal-save-colors.sh.

#!/bin/sh

# Copyright (C) 2013, Giorgos Keramidas <gkeramidas@gmail.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.

# Dump an executable form of all the mate-terminal keys associated with
# colors.  Running the resulting script should restore all color-related
# state of the terminal to whatever it was at the time the script was
# created.
#
# Inspired by:
# The setup scripts of solarized theme for gnome-terminal from:
# https://github.com/sigurdga/gnome-terminal-colors-solarized

# ----- startup code ---------------------------------------------------

# Save the original program invocation name, and the real path of the
# startup directory, for later use.
progdir=$( cd $(dirname "$0") ; /bin/pwd -P )
progname=$( basename "$0" )

# ----- misc functions -------------------------------------------------

#
# err exitval message
#   Display message to stderr and to the logfile, if any, and then
#   exit with exitval as the return code of the script.
#
err()
{
    exitval=$1
    shift

    log "$0: ERROR: $*"
    exit $exitval
}

#
# warn message
#   Display message to stderr and the log file.
#
warn()
{
    log "$0: WARNING: $*"
}

#
# info message
#   Display informational message to stderr and to the logfile.
#
info()
{
    log "$0: INFO: $*"
}

#
# debug message
#   Output message to stderr if debug_output_enabled is set to
#   'yes', 'true' or '1'.  Please AVOID calling any shell subroutine
#   that may recursively call debug().
#
debug()
{
    case ${debug_enabled} in
    [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
        log "$0: DEBUG: $*"
        ;;
    esac
}

#
# log message
#   Print a log message to standard error.  If ${LOGFILE} is set
#   Output message to "${LOGFILE}" if it is set and is writable.
#
log()
{
    __timestamp="`date -u '+%Y-%m-%d %H:%M:%S'`"
    __msg="${__timestamp} [${progname}] -- $*"
    echo >&2 "${__msg}" 2>&1
    if [ -n "${LOGFILE}" ]; then
        echo "${__msg}" >> "${LOGFILE}"
    fi
}

# ----- main script body ------------------------------------------------

# The gconf-compatible tool to use for reading and writing gconf keys
# for the MATE desktop, and the application name under /apps/ to
# configure.  These are provisionaly set to work for the MATE desktop,
# but they can also be tweaked to work for GNOME 2.X by setting:
#
#   conftool='gconftool-2'
#   appname='gnome-terminal'

conftool='mateconftool-2'
appname='mate-terminal'

# Basic command-line sanity checking.
if test $# -ne 0 && test $# -ne 1 ; then
    echo >&2 "usage: ${progname} [ PROFILE ]"
    exit 1
fi

# The name of the profile we are dumping can be passed as a command line
# argument, or auto-detected by peeking at:
# '/apps/${appname}/global/default_profile'
if test $# -eq 1 ; then
    profile="$1"
else
    key="/apps/${appname}/global/default_profile"
    profile=$( ${conftool} --get "${key}" 2>/dev/null )
    if test $? -ne 0 ; then
        debug "Cannot read configuration key: ${key}"
        err 1 "Cannot detect default profile name."
    fi
    unset key
fi

# Verify that the profile we are looking for really exists, by trying to
# read at least one key from it:
# '/apps/${appname}/profiles/${profile}/foreground_color'
key="/apps/${appname}/profiles/${profile}/foreground_color"
${conftool} --get "${key}" > /dev/null 2>&1
if test $? -ne 0 ; then
    debug "Cannot read configuration key: ${key}"
    err 1 "Profile ${profile} cannot be found."
fi
unset key

# dumpkey TYPE KEY
#   Dump a configuration key to standard output, as a shell command that
#   will _set_ it to its current value, using the associated type.
dumpkey()
{
    if test $# -ne 2 || test -z "$1" || test -z "$2" ; then
        debug "dumpkey() requires exactly 2 non-empty arguments,"
        debug "but it was invoked with:"
        debug "    \"$1\""
        debug "    \"$2\""
        return 1
    fi
    __type="$1"
    __key="$2"

    __value=$( ${conftool} --get "${__key}" )
    if test $? -ne 0 ; then
        err 1 "Cannot read key \"${__key}\""
    fi
    echo "${conftool} --set --type \"${__type}\""                       \
        "\"${__key}\" \"${__value}\""
}

dumpkey "string" "/apps/${appname}/profiles/${profile}/background_color"
dumpkey "string" "/apps/${appname}/profiles/${profile}/bold_color"
dumpkey "bool"   "/apps/${appname}/profiles/${profile}/bold_color_same_as_fg"
dumpkey "string" "/apps/${appname}/profiles/${profile}/foreground_color"
dumpkey "string" "/apps/${appname}/profiles/${profile}/palette"

Using The Script

Using the script should be pretty easy to discern by now, but here’s a sample run from my laptop:

$ sh mate-terminal-save-colors.sh 
mateconftool-2 --set --type "string" "/apps/mate-terminal/profiles/Default/background_color" "#000000000000"
mateconftool-2 --set --type "string" "/apps/mate-terminal/profiles/Default/bold_color" "#000000000000"
mateconftool-2 --set --type "bool" "/apps/mate-terminal/profiles/Default/bold_color_same_as_fg" "true"
mateconftool-2 --set --type "string" "/apps/mate-terminal/profiles/Default/foreground_color" "#E8E8E8E8ECEC"
mateconftool-2 --set --type "string" "/apps/mate-terminal/profiles/Default/palette" "#000000000000:#D7D700000000:#5F5F87870000:#CFCFA7A70000:#26268B8BD2D2:#ADAD7F7FA8A8:#2A2AB1B1A8A8:#D3D3D7D7CFCF:#555557575353:#DCDC32322F2F:#9595C9C90000:#F5F5D9D90000:#00008787FFFF:#CDCD9F9FC8C8:#4A4AE1E1D8D8:#EEEEEEEEECEC"

The color values are slurped directly from the MATE desktop’s configuration database entries for mate-terminal. Redirecting this output to a plain text file yields a nice, compact sh(1)-compatible script, which restores the colors of the “Default” mate-terminal profile to the values of the script, i.e. the color setup I was using when this color profile was saved:

# Save the current color settings.
sh mate-terminal-save-colors.sh > ~/term-colors.sh

# Some time later... Restore them once more.
sh ~/term-colors.sh
Advertisement

Controlling the Keyboard Backlight from CLI

I found out today how to query and set the state of the keyboard backlight of my Asus Zenbook laptop through dbus-send calls, so I instantly thought “hey this would be nice to have in a shell script”.

So I wrote a small script called “backlight“, whose basic usage is a simple set of three commands:

backlight up
backlight down
backlight [ 0 | 1 | 2 | 3 ]

With this script installed as ‘~/bin/backlight’ in my home directory I can control the brightness of my keyboard’s backlight with simple shell commands, which is rather convenient, because all that usually runs on my desktop is a tiling window manager and a couple of terminals.

The script itself is rather small and it may be useful to someone else too, so here it is:

#!/bin/sh

# backlight_get
#       Print current keyboard brightness from UPower to stdout.
backlight_get()
{
    dbus-send --type=method_call --print-reply=literal --system         \
        --dest='org.freedesktop.UPower'                                 \
        '/org/freedesktop/UPower/KbdBacklight'                          \
        'org.freedesktop.UPower.KbdBacklight.GetBrightness'             \
        | awk '{print $2}'
}

# backlight_get_max
#       Print the maximum keyboard brightness from UPower to stdout.
backlight_get_max()
{
    dbus-send --type=method_call --print-reply=literal --system       \
        --dest='org.freedesktop.UPower'                               \
        '/org/freedesktop/UPower/KbdBacklight'                        \
        'org.freedesktop.UPower.KbdBacklight.GetMaxBrightness'        \
        | awk '{print $2}'
}

# backlight_set NUMBER
#       Set the current backlight brighness to NUMBER, through UPower
backlight_set()
{
    value="$1"
    if test -z "${value}" ; then
        echo "Invalid backlight value ${value}"
    fi

    dbus-send --type=method_call --print-reply=literal --system       \
        --dest='org.freedesktop.UPower'                               \
        '/org/freedesktop/UPower/KbdBacklight'                        \
        'org.freedesktop.UPower.KbdBacklight.SetBrightness'           \
        "int32:${value}}"
}

# backlight_change [ UP | DOWN | NUMBER ]
#       Change the current backlight value upwards or downwards, or
#       set it to a specific numeric value.
backlight_change()
{
    change="$1"
    if test -z "${change}" ; then
        echo "Invalid backlight change ${change}."                    \
            "Should be 'up' or 'down'." >&2
        return 1
    fi

    case ${change} in
    [1234567890]|[[1234567890][[1234567890])
        current=$( backlight_get )
        max=$( backlight_get_max )
        value=$( expr ${change} + 0 )
        if test ${value} -lt 0 || test ${value} -gt ${max} ; then
            echo "Invalid backlight value ${value}."                  \
                "Should be a number between 0 .. ${max}" >&2
            return 1
        else
            backlight_set "${value}"
            notify-send -t 800 "Keyboard brightness set to ${value}"
        fi
        ;;

    [uU][pP])
        current=$( backlight_get )
        max=$( backlight_get_max )
        if test "${current}" -lt "${max}" ; then
            value=$(( ${current} + 1 ))
            backlight_set "${value}"
            notify-send -t 800 "Keyboard brightness set to ${value}"
        fi
        ;;

    [dD][oO][wW][nN])
        current=$( backlight_get )
        if test "${current}" -gt 0 ; then
            value=$(( ${current}  - 1 ))
            backlight_set "${value}"
            notify-send -t 800 "Keyboard brightness set to ${value}"
        fi
        ;;

    *)
        echo "Invalid backlight change ${change}." >&2
        echo "Should be 'up' or 'down' or a number between"           \
            "1 .. $( backlight_get_max )" >&2
        return 1
        ;;
    esac
}

if test $# -eq 0 ; then
    current_brightness=$( backlight_get )
    notify-send -t 800 "Keyboard brightness is ${current_brightness}"
else
    # Handle multiple backlight changes, e.g.:
    #   backlight.sh up up down down up
    for change in "$@" ; do
        backlight_change "${change}"
    done
fi

Fixing Shifted-Arrow Keys in 256-Color Terminals on Linux

The terminfo entry for “xterm-256color” that ships by default as part of ncurses-base on Debian Linux and its derivatives is a bit annoying. In particular, shifted up-arrow key presses work fine in some programs, but fail in others. It’s a bit of a gamble if Shift-Up works in joe, pico, vim, emacs, mutt, slrn, or what have you.

THis afternoon I got bored enough of losing my selected region in Emacs, because I forgot that I was typing in a terminal launched by a Linux desktop. SO I thought “what the heck… let’s give the FreeBSD termcap entry for xterm-256color a try”:

keramida> scp bsd:/etc/termcap /tmp/termcap-bsd
keramida> captoinfo -e $(                                  \
  echo $( grep '^xterm' termcap | sed -e 's/[:|].*//' ) |  \
  sed -e 's/ /,/g'                                         \
  ) /tmp/termcap  > /tmp/terminfo.src
keramida> tic /tmp/terminfo.src

Restarted my terminal, and quite unsurprisingly, the problem of Shift-Up keys was gone.

The broken xterm-256color terminfo entry from /lib/terminfo/x/xterm-256color is now shadowed by ~/.terminfo/x/xterm-256color, and I can happily keep typing without having to worry about losing mental state because of this annoying little misfeature of Linux terminfo entries.

The official terminfo database sources[1], also work fine. So now I think some extra digging is required to see what ncurses-base ships with. There’s definitely something broken in the terminfo entry of ncurses-base, but it will be nice to know which terminal capabilities the Linux package botched.

Notes:
[1] http://invisible-island.net/ncurses/ncurses.faq.html#which_terminfo

Avoid Double Negatives Like the Plague

Double negatives are very confusing. They probably classify as one of the most confusing things in written or spoken communication, if not as the one, most confusing thing ever.

A particularly striking example of a double negative that I’ve seen in the wild is:

gutter (true/false) — If false, the line numbering on the left side will be hidden. Defaults to true.”

WordPress Documentation
Source Code Posting Instructions

Now read quickly through the help of the “gutter” option, and then try to answer the following questions:

  • Can you understand immediately if setting gutter=true hides or shows the line numbering bits?
  • Is it entirely obvious why the default is true, with just a quick glance at this sentence?

The reason why the original sentences are confusing is that there’s a hidden double negative right in the middle of the first sentence. The combination of “false” and “hidden” works against the intention of the documenter, muddling the waters and effectively hiding the real information behind a barrier of miscommunication. The reader must first scale the obstacle of noticing the false-hidden combination; then read the following sentence; realize that this works as a negation of something that is true by default; combine all bits together to form an actual understanding of what the gutter option is all about.

The larger the context we have to keep in our head, the more difficult it is to understand the actual meaning. More importantly, this is true for both written and spoken communication. The double negative in the first of these sentences renders the first sentence difficult to parse and leaves the reader hanging for more context, provided much later then necessary, by the second sentence of the group.

Rewriting the first sentence, to remove the double negative, vastly improves our ability to grasp its meaning with one glance:

gutter (true/false) — Show line numbers on the left side. Defaults to true.”

As a bonus point the first sentence is now smaller too. Removing the noisy and confusing double negative — which, amusingly enough, had the gall to mask as an attempt to “clarify” things — resulted in a more compact, but also cleaner, easier to read sentence; one with arguably higher informational content!

Mate desktop support for xdg-open (tiny patch)

The Mate desktop really lives up to its promise of a “traditional desktop”, compatible in look and feel with Gnome 2.X versions.

After running it for a few hours now, I’m sold. I never really liked Unity very much, so Mate feels like ‘home’.

There are a few minor things that need tweaking though, mostly because the desktop is no longer called ‘Gnome’. One of them is the default xdg-open script that ships with the xdg-utils package. The current version of the script assumes that the desktop is called one of ‘KDE’, ‘LXDE’, ‘gnome’ or something else. The detectDE() function fails to recognize Mate, because it tries to look for GNOME_DESKTOP_SESSION_ID in its environment, and this is obviously no longer there for Mate.

The following patch fixes that, by checking for the string 'mate' in DESKTOP_SESSION instead, and adding a tiny bit of dispatch code based on DE='mate' further down, to call a new open_mate() function.

--- xdg-open.orig	2013-03-18 12:39:07.955516232 +0100
+++ xdg-open	2013-03-18 12:38:45.955515638 +0100
@@ -308,6 +308,7 @@
     elif `dbus-send --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.GetNameOwner string:org.gnome.SessionManager > /dev/null 2>&1` ; then DE=gnome;
     elif xprop -root _DT_SAVE_MODE 2> /dev/null | grep ' = \"xfce4\"$' >/dev/null 2>&1; then DE=xfce;
     elif [ x"$DESKTOP_SESSION" = x"LXDE" ]; then DE=lxde;
+    elif [ x"$DESKTOP_SESSION" = x'mate' ]; then DE=mate;
     else DE=""
     fi
 }
@@ -371,6 +372,21 @@
     fi
 }
 
+open_mate()
+{
+    if gvfs-open --help 2>/dev/null 1>&2; then
+        gvfs-open "$1"
+    else
+        mate-open "$1"
+    fi
+
+    if [ $? -eq 0 ]; then
+        exit_success
+    else
+        exit_failure_operation_failed
+    fi
+}
+
 open_xfce()
 {
     exo-open "$1"
@@ -545,6 +561,10 @@
     open_gnome "$url"
     ;;
 
+    mate)
+    open_mate "$url"
+    ;;
+
     xfce)
     open_xfce "$url"
     ;;

Now this is a tiny problem, but it’s one that improves the usability of Mate for every-day stuff. One of the things that this fixes it the handling of magnet: links in Chrome. When Chrome tries to open magnet: links without this patch, it seems to “do nothing” but open a new tab and stop. With this patch applied, xdg-open works fine for magnet links (depending on your current MIME application settings too, of course), and Chrome happily opens torrent links in Mate desktops without a glitch.

Automatically Joining Password-Enabled Channels in ERC

If, like me, you are using ERC to chat with your IRC friends all over the world, then the next few paragraphs describe how you can set up automatically joining channels in a secure manner, even if the channels require a key.

Being able to auto-join channels after connecting to an IRC server is a must have for any serious IRC client. This way the user doesn’t have to laboriously type /join commands again and again, every time the IRC client start, or when there’s a network problem and the IRC server temporarily disconnects. Typing long lists of channel names gets old really fast if one has to type stuff like this for example:

/join #public
/join #secret somekeyword
/join #interest
/join #hobby hobbykey

Most serious IRC clients have some form of ‘autojoin’ feature, that lets you configure the channels they should join when connected to a specific IRC network or to a specific IRC server. ERC supports auto-joining with a loadable module called erc-join. You can load this module in your ERC startup code, and then use erc-autojoin-channels-alist to specify which channels to join on a per-server basis.

Loading the erc-join module and enabling auto-join support is the easy bit:

(require 'erc-join)             ; autojoin support is implemented by erc-join.el
(erc-autojoin-enable)           ; enable channel autojoin support, by default

Setting up the list of channels in erc-autojoin-channel-alist is also easy, but it does lend itself to a bit of upfront explanation. When auto-join code looks at erc-autojoin-channel-alist it expects to find there a list of ‘server patterns’ and their associated channels. So its value should look like this:

((SERVER-PATTERN-1 . CHANNEL-1 CHANNEL-2 ...)
 (SERVER-PATTERN-2 . CHANNEL-3 CHANNEL-4 CHANNEL-5 ...))

When a connection to a new IRC server is established, erc-join has set things up so that erc-autojoin-channels is called. The erc-autojoin-channels function iterates through erc-autojoin-channels-alist and checks if the server pattern matches the current server-name. If it does, it calls erc-server-join-channel for each of the channels attached to this server pattern.

The server pattern can be a regular expression and pattern matching for the ‘server’ part of each erc-autojoin-channels-alist item does not stop when it finds a match. This means that you can mix and match server patterns in all sorts of amusing ways, e.g. to set up a channel that is automatically joined for all IRC networks, by having an erc-autojoin-channels-alist entry whose server pattern is ".*":

(setq erc-autojoin-channels-alist
      '((".*" . "#everywhere")))

Now, for the interesting part. What happens when you want to automatically join channels that require a key. That’s where the real magic behind erc-server-join-channel kicks in, and lets you store your passwords in a secure place, like e.g. a PGP-encrypted ~/.authinfo file in your home directory!

Every time erc-server-join-channel is called it tries to look up a matching entry for the current IRC server and channel names in what Emacs calls “authentication sources”. These are, basically, files in ‘netrc’ file format (traditionally used by FTP clients). If a matching entry is found for the current combination of server and channel in one of the configured netrc files, the password key of this netrc file entry is used as the key for joining the IRC channel. The service name used for netrc entry lookup is also conveniently set to “irc”, so that ERC will not pick up unrelated passwords from your netrc files.

A valid netrc file entry, one which ERC can use to locate the key for an IRC channel, looks like this:

machine irc.freenode.net login "#mychannel" password "supersecret" port irc

If this line is present in your ~/.authinfo or ~/.netrc file, and one of the erc-autojoin-channels-alist entries specifies that “#mychannel” should be automatically joined when connected to the IRC server “irc.freenode.net”, then ERC will use “supersecret” as the key for joining the channel. This is a much safer alternative than typing the channel passwords yourself at an ERC prompt (because the channel passwords may then be logged on disk, if you have enabled server-buffer logging), and it’s definitely safer than listing the password in your ~/.emacs startup code or other place that can be world-readable.

Putting it All Together

To summarize, configuring ERC to automatically join channels “#public” and “#secret” when connecting to IRC server “irc.example.net”, and to use the secret key “supersecret” for the “#secret” channel, you can put the following in your ERC startup code:

(require 'erc-join)
(erc-autojoin-enable)

(setq erc-autojoin-channels-alist
      '(("irc.example.net" . "#public" "#secret"))))

and the following in your ~/.authinfo or ~/.netrc file:

machine irc.example.net login "#secret" password "supersecret" port irc

Mutt-like Scrolling for Gnus

Mutt scrolls the index of email folders up or down, one line at a time, with the press of a single key: ‘<‘ or ‘>’. This is a very convenient way to skim through email folder listings, so I wrote a small bit of Emacs Lisp to do the same in Gnus tonight.

;;;
;; Scrolling like mutt for group, summary, and article buffers.
;;
;; Being able to scroll the current buffer view by one line with a
;; single key, rather than having to guess a random number and recenter
;; with `C-u NUM C-l' is _very_ convenient.  Mutt binds scrolling by one
;; line to '<' and '>', and it's something I often miss when working
;; with Gnus buffers.  Thanks to the practically infinite customizability
;; of Gnus, this doesn't have to be an annoyance anymore.

(defun keramida-mutt-like-scrolling ()
  "Set up '<' and '>' keys to scroll down/up one line, like mutt."
  ;; mutt-like scrolling of summary buffers with '<' and '>' keys.
  (local-set-key (kbd ">") 'scroll-up-line)
  (local-set-key (kbd "<") 'scroll-down-line))

(add-hook 'gnus-group-mode-hook 'keramida-mutt-like-scrolling)
(add-hook 'gnus-summary-mode-hook 'keramida-mutt-like-scrolling)
(add-hook 'gnus-article-prepare-hook 'keramida-mutt-like-scrolling)

This is now the latest addition to my ~/.gnus startup code, and we’re one step closer to making Gnus behave like my favorite old-time mailer.

Awesome WM in Ubuntu 12.04

I’ve been using awesome and other tiling window managers for a while now. I find it more comfortable when programs are using as much screen space as possible, and I like organizing my open windows in “workspaces” instead of having a huge pile of overlapping windows. Being able to extend the window manager’s default behavior with Lua is also rather appealing. So when a colleague recommended awesome a while ago, I started experimenting with it, and it stuck with me.

At first I was using the pre-packaged version of awesome, from the repository of Ubuntu Lucid. Then, as I started learning more about awesome, I installed a snapshot from the ‘master’ git branch of awesome’s development tree. For a while this worked fine, and when awesome started supporting fontconfig, pango and cairo, things were looking quite peachy!

My laptop was running Ubuntu Lucid though. This was not something I could change, for various reasons, security being one of the most important ones. So, when awesome’s git version switched from oocairo and oopango to lgi, various things broke. I could compile new git snapshots on Lucid just fine, but running awesome threw a traceback when it tried to load the very old “lgi” snapshot of Lucid. Compiling lgi and all its dependencies manually turned out to be a major undertaking, involving manual compiling of quite basic and central Gnome bits, like gobject-introspection. Replacing half of my Gnome desktop with manually compiled versions was a really bad idea, because it would require a major time and effort investment to keep up with all the fast-moving bits of Gnome. So I switched to another tiling window manager.

Why Awesome

The ‘container’ model of i3-wm is wonderful, but I was missing an easy way to extend the window manager, e.g. to tweak or extend its core features beyond what is supported by the relatively spartan roadmap of i3. Awesome’s scripting support fills that gap, using Lua for a large part of even the window-manager itself. A small core of awesome is written in C, and there’s a growing library of modules, extensions and plugins. In my not-so-humble opinion, having an extension language instantly makes a program a million times more useful. This is precisely why my favorite editor is GNU Emacs, for example.

So now that I had the opportunity to give awesome another try, I jumped right in. All I had to do was to make sure that lgi would work on my new Ubuntu installation. This post describes everything I tried, and how I eventually made my desktop look like this:

Tiled terminals and nautilus windows. A sample Gnome session with awesome as the window manager.

Sample awesome session, in Ubuntu Precise 12.04

I’m perfectly happy with this sort of setup. The rest of this article describes how I reached this installed and configured awesome, in the hope that other people who upgrade their machines to post-Gnome2 versions may find this useful too.

Installing Dependencies

The first thing I had to do was to make sure that all the dependencies of awesome are installed on my laptop.

Dependencies of Awesome Itself

Since awesome is already available in the package repositories of Ubuntu, the easiest way to do that is to use apt-get itself:

sudo apt-get install build-deps awesome

This will install all the dependencies of the pre-packaged awesome version. The latest development version of awesome uses the same libraries and one more: lgi, the Lua bindings to gnome libraries using gobject-introspection.

LGI: Lua bindings for gobject-introspection

Awesome depends on a fairly recent version of lgi. The one included in Precise repositories is, at the time of this writing, version 0.5-1, which is older than what awesome needs. So I had to compile lgi myself, from one of the newer releases. Release 0.6.2 is the latest version available at lgi’s Github repository, so I downloaded and unpacked this one:

wget -nd -np -c -r -O lgi-0.6.2.tar.gz \
    https://github.com/pavouk/lgi/tarball/0.6.2
tar xzvf lgi-0.6.2.tar.gz
mv pavouk-lgi-a4ad06c lgi-0.6.2

Before building lgi, it’s useful to have all its dependencies installed too. This is, again, rather easy to do with apt-get’s build-dep command:

sudo apt-get build-dep lua-lgi-dev

With the dependencies of lua-lgi out of the way, the next step is to compile and install lgi itself. The source of lgi uses a simple Makefile to compile everything, but you have to add the right directory to the C compiler’s include path, so that including “lua.h” works correctly. I did this by setting CPPFLAGS in the environment of make:

env CPPFLAGS='-I/usr/include/lua5.1' make

Then I installed the newly compiled lgi:

sudo env CPPFLAGS='-I/usr/include/lua5.1' make install

Gobject-introspection Repository

Lgi is not very useful without the necessary gobject-introspection data files. These are available as a package already, so the next things I installed are: libgirepository1.0-1 and libgirepository1.0-dev:

sudo apt-get install libgirepository1.0-1 libgirepository1.0-dev

That concludes the list of awesome and lgi dependencies, so it should be possible to build and run awesome just fine.

Getting the Sources

I normally run the git version of awesome. The master branch in the git repository is actively developed, and it’s always nice to be able to see recent developments in action, before they hit an official ‘stable’ release. I also happen to have git around “just in case”, since it’s now so popular that it threatens to dethrone Coca Cola or something similar from its popularity throne. So I branched awesome’s git repository and checked out the master branch:

cd ~/git
git clone git://git.naquadah.org/awesome.git
cd awesome
git checkout master

That’s all. A fairly recent, up to date, clean checkout of awesome’s development sources is now part of my permanent dumping ground of git clones, at ~gkeramidas/git/….

Compiling Awesome

The first thing you’ll notice when you look at a checkout of awesome’s sources is that it doesn’t use the ‘usual’ autoconf-based configure script to build. It uses CMake instead. If you don’t already have cmake around, now is a good time to install it:

sudo apt-get install cmake

Then you’re ready to build awesome from the sources. One of the interesting features of cmake is that it supports building in a “build directory”, separate from the source directory itself. This is very convenient, especially if you want to keep your git checkout ‘clean’. I often build stuff in “~/tmp/_build“, so I can quickly reclaim space by deleting old object files, without having to crawl my entire home directory. This is where cmake’s build-tree support came handy. To build awesome from its sources I typed:

cd ~/tmp/_build
mkdir awesome-git && cd awesome-git
cmake ~/git/awesome
make

If all goes well this should produce a full build of awesome and its associated files: an installable binary, Lua extensions of the core window-manager binary, manpages, etc. Installing this to the local system is then a piece of cake:

sudo make install

Note: By default this installs awesome in /usr/local. If you prefer to install awesome in /usr/bin instead, you should tell cmake to produce makefiles that use a different installation prefix directory:

cmake -DCMAKE_INSTALL_PREFIX:PATH='/usr' ~/git/awesome

Session Setup for Gnome

After awesome is installed to the system, some manual configuration is needed to launch awesome as part of the usual Gnome session. I created the following files, copying the xmonad instructions from another blog post.

The files I had to adapt are the following:

  • /usr/share/applications/awesome.desktop
  • /usr/share/gnome-session/sessions/awesome.session
  • /usr/share/xsessions/awesome-gnome-session.desktop

The first file, /usr/share/applications/awesome.desktop, registers awesome with the applications a Gnome user can run. It’s a simple text file, with the following contents:

[Desktop Entry]
Type=Application
Encoding=UTF-8
Name=Awesome
Exec=awesome
NoDisplay=true
X-GNOME-WMName=Awesome
X-GNOME-Autostart-Phase=WindowManager
X-GNOME-Provides=windowmanager
X-GNOME-Autostart-Notify=true

If you have installed awesome in /usr/local/bin/awesome and this directory is not in your default PATH for executables, you may have to replace “awesome” in the highlighted line 5 with the full path of the binary: /usr/local/bin/awesome.

The second file, /usr/share/gnome-session/sessions/awesome.session, defines what to start for a ‘Gnome session’ that uses awesome as the window manager. I use this file to launch awesome as the window-manager and a few useful applets: gnome-settings-daemon, gnome-sound-applet, bluetooth-applet and nm-applet.

[GNOME Session]
Name=Awesome Unity-2D Desktop
RequiredComponents=gnome-settings-daemon;gnome-sound-applet;nm-applet;bluetooth-applet;
RequiredProviders=windowmanager;
DefaultProvider-windowmanager=awesome

The third file, /usr/share/xsessions/awesome-gnome-session.desktop, configures the options that gnome-session will use when starting an ‘awesome style’ session. The most important bit is the –session option of line 5:

[Desktop Entry]
Name=Awesome GNOME
Comment=Tiling window manager
TryExec=/usr/bin/gnome-session
Exec=gnome-session --session=awesome
Type=XSession

Sample Awesome Desktops

Everything about awesome’s look and behavior is configured with Lua. This is very similar to what e.g. GNU Emacs does with Emacs Lisp, and it may seem a bit difficult to tweak at first. Fortunately there is a nice default startup file included in the sources of awesome itself. You will find it at awesomerc.lua in the source tree of awesome itself. This cna be immediately copied to ~/.config/awesome/rc.lua and you are ready to go. Incremental changes and small tweaks can then be added on top of the default configuration, to change the behavior of awesome or even to extend it with new features.

The two screenshots below show the default configuration of awesome, and how it looks with my own custom configuration file:

Screenshot of Default Awesome Configuration

Awesome running with its default configuration

Screenshot of Awesome with my custom configuration

Screenshot of Awesome with my custom configuration

You can find my own configuration attached below. Even more configuration examples are available at the wiki of awesome itself: http://awesome.naquadah.org/wiki/User_Configuration_Files

My Own Awesome Configuration

The rest of the post includes my current configuration file for awesome. This has diverged in several ways from the original configuration in the source tree of awesome, but I’m including it here for your reading pleasure.

-- -*- mode: lua; coding: utf-8; fill-column: 78; -*-
--
-- awesome/rc.lua -- startup code for the 'awesome' window manager
--
-- Copyright (C) 2010-2012, Giorgos Keramidas <gkeramidas@gmail.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions
-- are met:
-- 1. Redistributions of source code must retain the above copyright
--    notice, this list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above copyright
--    notice, this list of conditions and the following disclaimer in the
--    documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-- ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-- SUCH DAMAGE.

-- {{{ Important globals
-- Important global variables that have to be defined even before
-- loading modules or extensions, because they may affect where the
-- modules are loaded from.

-- Where most of the config files and loadable options live in my
-- current awesome setup.
home = os.getenv('HOME') or '/'
confdir = os.getenv("HOME") .. "/.config/awesome"

terminal = "term"
editor = os.getenv("EDITOR") or "utf-emacs"
filemgr = 'nautilus'
editor_cmd = editor

-- Standard awesome library
local gears = require("gears")
awful = require("awful")
awful.rules = require("awful.rules")
require("awful.autofocus")
-- Widget and layout library
local wibox = require("wibox")
-- Theme handling library
local beautiful = require("beautiful")
-- Notification library
local naughty = require("naughty")
local menubar = require("menubar")

-- {{{ Error handling
-- Check if awesome encountered an error during startup and fell back to
-- another config (This code will only ever execute for the fallback config)
if awesome.startup_errors then
    naughty.notify({ preset = naughty.config.presets.critical,
                     title = "Oops, there were errors during startup!",
                     text = awesome.startup_errors })
end

-- Handle runtime errors after startup
do
    local in_error = false
    awesome.connect_signal(
        "debug::error",
        function (err)
            -- Make sure we don't go into an endless error loop
            if in_error then
                return
            end
            in_error = true
            naughty.notify({ preset = naughty.config.presets.critical,
                             title = "Oops, an error happened!",
                             text = err })
            in_error = false
        end)
end
-- }}}

-- {{{ Variable definitions
-- Themes define colours, icons, and wallpapers
beautiful.init(confdir .. "/themes/keramida/theme.lua")

-- Default modkey.  Note that Mod1 is usually "Left Alt", so binding many
-- keys with this modifier may interfere with some of the shortcuts that
-- other programs want to use.  Use with caution.
modkey = "Mod1"

-- Table of layouts to cover with awful.layout.inc, order matters.
-- NOTE: These are not all the layouts supported by awesome; just the ones I
-- regularly find useful and want to have readily available.
local layouts = {
    awful.layout.suit.floating,
    awful.layout.suit.tile,
    awful.layout.suit.tile.left,
    awful.layout.suit.tile.bottom,
    awful.layout.suit.tile.top,
    awful.layout.suit.max,
}
-- }}}

-- {{{ Wallpaper
if beautiful.wallpaper then
    for s = 1, screen.count() do
        gears.wallpaper.maximized(beautiful.wallpaper, s, true)
    end
end
-- }}}

-- {{{ Tags
-- Define a tag table which hold all screen tags.
tags = {}
for s = 1, screen.count() do
    -- Each screen has its own tag table.
    tags[s] = awful.tag({ 1, 2, 3, 4 }, s, layouts[2])
end
-- }}}

-- {{{ Menu
-- Create a laucher widget and a main menu
myawesomemenu = {
    { "Terminal",    terminal },
    { "Preferences", "gnome-control-center" },
    { "Screenshot",  "gnome-screenshot -i" },
    { "Run",         "gmrun" },
    { "Restart",     awesome.restart },
    { "Lock",        "gnome-screensaver-command --lock" },
    { "Quit",        "pkill gnome-session" }
}
mymainmenu = awful.menu({ items = myawesomemenu })
mylauncher = awful.widget.launcher(
    { image = beautiful.awesome_icon, menu = mymainmenu })

-- Menubar plugin configuration
-- Just set the 'terminal' for those applications that require one.
menubar.utils.terminal = terminal
-- }}}

-- {{{ Wibox
-- Create a textclock widget
mytextclock = awful.widget.textclock(" %H:%M ")

-- Create a wibox for each screen and add it
mywibox = {}
mypromptbox = {}
mylayoutbox = {}
mytaglist = {}
mytaglist.buttons = awful.util.table.join(
    awful.button({        }, 1, awful.tag.viewonly),
    awful.button({ modkey }, 1, awful.client.movetotag),
    awful.button({        }, 3, awful.tag.viewtoggle),
    awful.button({ modkey }, 3, awful.client.toggletag),
    awful.button({        }, 4, function(t) awful.tag.viewprev(t.screen) end),
    awful.button({        }, 5, function(t) awful.tag.viewnext(t.screen) end)
)

mytasklist = {}
mytasklist.buttons = awful.util.table.join(
awful.button({ }, 1,
function (c)
    if c == client.focus then
        c.minimized = true
    else
        -- Without this, the following
        -- :isvisible() makes no sense
        c.minimized = false
        if not c:isvisible() then
            awful.tag.viewonly(c:tags()[1])
        end
        -- This will also un-minimize
        -- the client, if needed
        client.focus = c
        c:raise()
    end
end),
awful.button({ }, 3,
function ()
    if instance then
        instance:hide()
        instance = nil
    else
        instance = awful.menu.clients({ width=250 })
    end
end),
awful.button({ }, 4,
function ()
    awful.client.focus.byidx(1)
    if client.focus then client.focus:raise() end
end),
awful.button({ }, 5,
function ()
    awful.client.focus.byidx(-1)
    if client.focus then client.focus:raise() end
end))

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.
    mylayoutbox[s] = awful.widget.layoutbox(s)
    mylayoutbox[s]:buttons(awful.util.table.join(
        awful.button({ }, 1, function () awful.layout.inc(layouts,  1) end),
        awful.button({ }, 3, function () awful.layout.inc(layouts, -1) end),
        awful.button({ }, 4, function () awful.layout.inc(layouts,  1) end),
        awful.button({ }, 5, function () awful.layout.inc(layouts, -1) end)))
    -- Create a taglist widget
    mytaglist[s] = awful.widget.taglist(
        s, awful.widget.taglist.filter.all, mytaglist.buttons)

    -- Create a tasklist widget
    mytasklist[s] = awful.widget.tasklist(
        s, awful.widget.tasklist.filter.currenttags, mytasklist.buttons)

    -- Create the wibox
    mywibox[s] = awful.wibox({ height = "20", 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()
    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)
end
-- }}}

-- {{{ Mouse bindings
root.buttons(awful.util.table.join(
    awful.button({ }, 3, function () mymainmenu:toggle() end),
    awful.button({ }, 4, awful.tag.viewnext),
    awful.button({ }, 5, awful.tag.viewprev)
))
-- }}}

-- {{{ Key bindings
-- {{{
-- Experimental TAB focus cycling code for Awesome.
--
-- Copyright (C) 2011 Giorgos Keramidas <gkeramidas@gmail.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions
-- are met:
-- 1. Redistributions of source code must retain the above copyright
--    notice, this list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above copyright
--    notice, this list of conditions and the following disclaimer in the
--    documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-- ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-- SUCH DAMAGE.

-- The tabfocus function cycles through all the clients in the visible
-- screen, keeping the order of the clients unchanged.  Every time it is
-- called it returns the 'next' client in the list of clients of the
-- current screen.
function tabfocus()
    local focus = client.focus  -- the currently focused client
    local screen                -- the current screen

    -- If there's a currently focused client, cycle through the clients
    -- of the same screen.  Otherwise just use the one that the mouse is
    -- currently over.
    if focus then
        screen = focus.screen
    else
        screen = mouse.screen
    end

    -- Build a list of the clients listed in all the 'selected' tags of
    -- the current screen.
    local vc = {}
    local count = 1
    local stags = awful.tag.selectedlist(screen)

    for k, v in ipairs(stags) do
        local tag = v
        local tagclients = tag.clients(tag)
        for ck, cv in ipairs(tagclients) do
            if awful.client.focus.filter(cv) then
                vc[count] = cv
                count = count + 1
            end
        end
    end
    count = nil
    
    -- Cache the first client for later.  If the currently focused
    -- client is the last one, we will pick this one for focus.
    local first = vc[1]

    local found = nil           -- sentinel for focused client match
    local sel =  nil            -- the 'next' selected client for focus
    local first = vc[1]         -- cache first client for later

    for k, c in ipairs(vc) do
        if focus and c == focus then
            -- We just found the currently focused client.  Mark it as
            -- 'found' and let the next loop iteration pick up the next
            -- client in the list as the focus candidate.
            found = c
        end
        if sel == nil and found and c ~= focus then
            -- We don't have a focus candidate in 'sel' yet, but we
            -- found the currently focused client in a previous
            -- iteration and the current 'c' client is not the focused
            -- one.  Pick this one for the focus candidate.
            sel = c
        end
    end
    if sel == nil then
        -- We found the currently focused client as the last item in
        -- vc[], so loop back to the start of the client list and pick
        -- the first vc[] item for our focus candidate.
        sel = first
    end
    client.focus = sel
    if client.focus then
        client.focus:raise()
    end
end
-- }}}

-- A function that spawns 'gnome-screensaver-command' to lock the desktop.
-- Useful for binding to Alt-Ctrl-L to immitate Gnome's lock key behavior.
function lockdesktop ()
    command = "gnome-screensaver-command --lock"
    awful.util.spawn(command)
end

globalkeys = awful.util.table.join(
    awful.key({ modkey, "Control" }, "Left",   awful.tag.viewprev ),
    awful.key({ modkey, "Control" }, "Right",  awful.tag.viewnext ),
    awful.key({ modkey, "Control" }, "Escape", awful.tag.history.restore),

    -- Layout manipulation
    awful.key({ modkey, "Control" }, "j",
              function ()
                  awful.client.swap.byidx(1)
              end),
    awful.key({ modkey, "Control" }, "k",
              function ()
                  awful.client.swap.byidx(-1)
              end),
    awful.key({ modkey }, "Tab",
              function ()
                  tabfocus()
              end),
    awful.key({ modkey }, "space",
              function ()
                  awful.layout.inc(layouts,  1)
              end),

    -- Standard program
    awful.key({ modkey }, "Return",
              function ()
                  awful.util.spawn(terminal)
              end),
    awful.key({ modkey, "Control" }, "q",
              awesome.quit),
    awful.key({ modkey, "Control" }, "l",
              function()
                  lockdesktop()
              end),

    -- Menubar, and prompt.
    awful.key({ modkey, "Control" }, "p",
              function ()
                  menubar.show()
              end),
    awful.key({ modkey, "Control" }, "r",
              function ()
                  awful.util.spawn("gmrun")
              end)
)

clientkeys = awful.util.table.join(
    awful.key({ modkey,           }, "F11",
        function (c)
            -- TODO(gkeramidas): Save the state of the client window, to
            -- some place that allows restoring of all the 'important'
            -- attributes (c.maximized_horizontal, c,maximized_vertical
            -- and c.floating at least... maybe more).

            -- If the client-window is not already expanded to full-screen
            -- mode, maximize it at both directions first and float it before
            -- setting full-screen mode.  This avoids a small problem with
            -- compositing managers when a window is currently tiled.
            if not c.fullscreen then
                c.maximized_horizontal = true
                c.maximized_vertical = true
            end
            -- Now it should be safe to turn full-screen mode on or off.
            c.fullscreen = not c.fullscreen
        end),
    awful.key({ modkey,           }, "F4",
              function (c)
                  c:kill()
              end),
    awful.key({ modkey, "Control" }, "space",
              awful.client.floating.toggle),
    awful.key({ modkey, "Control" }, "Return",
              function (c)
                  c:swap(awful.client.getmaster())
              end),
    awful.key({ modkey,           }, "m",
              function (c)
                  c.minimized = not c.minimized
              end),
    awful.key({ modkey,           }, "F8",
        function (c)
          c.maximized_vertical = not c.maximized_vertical
        end),
    awful.key({ modkey, "Control" }, "m",
        function (c)
            c.maximized_horizontal = not c.maximized_horizontal
            c.maximized_vertical   = not c.maximized_vertical
        end)
)

-- Compute the maximum number of digit we need, limited to 9
keynumber = 0
for s = 1, screen.count() do
   keynumber = math.min(9, math.max(#tags[s], keynumber))
end

-- Bind all key numbers to tags.
-- Be careful: we use keycodes to make it works on any keyboard layout.
-- This should map on the top row of your keyboard, usually 1 to 9.
for i = 1, keynumber do
    globalkeys = awful.util.table.join(globalkeys,
        awful.key({ modkey }, "#" .. i + 9,
                  function ()
                        local screen = mouse.screen
                        if tags[screen][i] then
                            awful.tag.viewonly(tags[screen][i])
                        end
                  end),
        awful.key({ modkey, "Control" }, "#" .. i + 9,
                  function ()
                      if client.focus and tags[client.focus.screen][i] then
                          awful.client.movetotag(tags[client.focus.screen][i])
                      end
                  end))
end

clientbuttons = awful.util.table.join(
    awful.button({ }, 1, function (c) client.focus = c; c:raise() end),
    awful.button({ modkey }, 1, awful.mouse.client.move),
    awful.button({ modkey }, 3, awful.mouse.client.resize)
)

-- Enable the keys defined in the globalkeys table.
root.keys(globalkeys)
-- }}}

-- {{{ Rules
awful.rules.rules = {
    -- All clients will match this rule.
    {
        rule = { },
        properties = {
            border_width = beautiful.border_width,
            border_color = beautiful.border_normal,
            focus = true,
            size_hints_honor = false,
            keys = clientkeys,
            buttons = clientbuttons,
            maximized_vertical = false,
            maximized_horizontal = false
        }
    },

    -- Handle pidgin windows sensibly.  The buddy list is focused and
    -- set as the master window; conversations are slaves by default.
    { 
        rule = { class = "Pidgin", role = "buddy_list" },
        properties = {
        }
    },
    {
        rule = { class = "Pidgin", role = "conversation" },
        properties = {
            callback = awful.client.setslave
        }
    },

    -- Make the 'desktop' window of Nautilus sticky, so that it shows in
    -- all tags.  Other Nautilus windows, like 'file manager' views keep
    -- the normal, default window style.
    {
        rule = { class = "Nautilus", instance = "desktop_window" },
        properties = {
            border_width = 0,
            sticky = true
        }
    },

    -- Disable border for Chrome windows.  I usually run Chrome windows
    -- fully maximized, so a border doesn't appear useful.
    {
        rule = { class = "Google-chrome" },
        properties = {
            border_width = 0
        }
    },

    -- Some clients are floated by default.
    {
        rule = { class = "Gedit" },
        properties = {
            floating = true
        }
    },
}
-- }}}

-- {{{ Signals

--
-- Examples of what one can do in client signal callbacks:
--
--     c.opacity = 0.8        -- set transparency level
--     c.sticky = true        -- set the 'sticky window' property
--

-- Signal function to execute when a new client appears.
client.connect_signal("manage",
    function (c, startup)
        -- Enable sloppy focus
        c:connect_signal("mouse::enter", function(c)
            if awful.layout.get(c.screen) ~= awful.layout.suit.magnifier
                and awful.client.focus.filter(c) then
                client.focus = c
            end
        end)

        if not startup then
            -- Put windows in a smart way, only if they does not set an
            -- initial position.
            if ( not c.size_hints.user_position and
                 not c.size_hints.program_position ) then
                awful.placement.no_overlap(c)
                awful.placement.no_offscreen(c)
            end
        end
    end)

-- Signal that is triggered when a client window is focused.
client.connect_signal("focus",
    function(c)
        c.border_color = beautiful.border_focus
    end)

-- Signal that is triggered when a client window is unfocused.
client.connect_signal("unfocus",
    function(c)
        c.border_color = beautiful.border_normal
    end)
-- }}}

Driving in a Wet Road

We often hear that driving in snow is treacherous and tricky. That the behavior of brakes is different on a wet, winter road from what one may see on a dry, clean, summer road. Well, last morning I had a chance to experience this during my driving class, in a controlled manner.

I started driving near Oerlikon, Zurich. Early in the morning of 28 Jan 2012 we had a bit of snow. Not really that much snow, so the streets within the city limits were already clean by the time I started driving. Most of the snow had either melted away or been cleaned by the city-service teams. When I started driving the streets were merely ‘a bit wet’. As we reached places outside of the city though, the road started showing signs of snow too, with a hint of suspicion about ice too:

When we reached an industrial area, where we had enough space to experiment with braking, I started accelerating from 0kmh up to 60 kmh and then trying to stop the car. The road was really wet at this point, and after a few repetitions two things were apparent:

  • It takes a longer time to accelerate from 0 kmh to XX kmh on a wet surface.
    Traction is really important during acceleration time too. This is all,
    of course, obvious once you think a bit about what happens when the car
    accelerates. I tend not to think about it when driving on a dry surface though,
    so the surprise-factor when I observed what happens on a really wet road
    was pretty high.
  • When braking hard on a dry road, there will often be a screetching sound
    and some level of tire wear-out, as tires ‘melt’ under the effects of traction.
    Quite the opposite happens when braking hard on a wet road: the tires lose contact
    with the road, resulting in very ‘bumpy’ braking behavior and much longer
    braking-distance.

At 60 kmh the braking distance on a wet surface more than doubled! The car also lost contact with the ground several times until it fully stopped, even if only for a fraction of a second. A speed of 60 kmh may seem relatively ‘high’, but it isn’t so uncommon. It’s merely 20% higher than 50 kmh, and there are many build-up areas near or around cities where 50 kmh is the average vehicle speed.

After the fact, thinking about our little ‘wet-road experiment’ seems scarier than it was when I was actually doing the driving. But it’s really nice that we did this. Now I have first-hand experience of what happens when the road is even moderately wet.

The wet-road experiment went really well. Looking forward to our next ‘car behavior’ driving/training hours.

The Adobe Caslon font

One of the fonts I recently discovered and started enjoying a lot is Adobe Caslon. Here’s a small sample of what it looks at 600 dpi, rendered from XeLaTeX. The text sample is from “Advice to Little Girls”, a humorous short story written by Mark Twain in 1867.

Adobe Caslon sample (600 dpi)


The more I look at text typeset with Caslon the more I like the round, elegant shapes of the characters (despite the “serif” style of the font); the beautiful ligatures of “ff”, “fi”, and “fl”; the clear-cut serifs of the upper part of high glyphs; the punctuation marks that stand out out with their brightly distinct shapes (note the full stop characters, the commas, and the quotation marks near “chewing-gum”).