xmodmap is dead

I'm using an Apple keyboard at work since a day or two, but for some reason this keyboard has a special key for eject, because in 1898 that was useful when you wanted to eject a cdrom.

Apple Keyboard

Anyway making this key do something useful proved incredible painful, especially since xmodmap is deprecated and we should all use xkb (setxkbmap andxkbcomp). In our new world of hotplugging everything this kinda makes sense (xmodmap settings are lost when your USB keyboard goes to sleep).

I use i3, but with gnome-settings-daemon running. So first I removed the eject functionality in there (not tested if this still is needed, with the later fix). In dconf-editor, go to org.gnome.settings-daemon.plugins.media-keys and remove the Value from the key named eject.

Searching more I found this site, which gave me to solution I settled on. With xev I found out the keycode sent is 169.

Dump the current config with xkbcomp $DISPLAY, this creates a file that will be called server-0.xkb

Find the line where keycode 169 is mapped:

key <I167> {         [     XF86Forward ] };
key <I169> {         [       XF86Eject ] };
key <I170> {         [       XF86Eject,       XF86Eject ] };

And change the key <I169> on to use BackSpace (for instance). Compile it to an .xkm file and load the keymap:

xkbcomp server_0.xkb
xkbcomp server_0.xkm $DISPLAY

In xmodmap this would have something like (untested) keycode 169 = BackSpace, which is of course so simple it needed a better solution.

Tagged , ,

Vim live preview (sort of)

When editing Markdown files or internet drafts in Pandoc's Markdown, I wanted to see some live preview window. I looked around a bit, but the solutions presented on the Internet, seemed to be insufficient, either to clumsy or don't work at all.

My usual routine is: edit -> write -> make -> reload "rendered" file.

Turns out you can automate most of this. Vim has a feature: --servername <id> which allows you to send commands to another vim instance using that <id>. So we need two pieces to make this work.

  1. start a vim instance with --servername markdown and;
  2. some way to send commands to it when building the file, turns out makeprg is an excellent candidate for this, as I was already using Make.

So to start with (2), add an autocmd:

autocmd Filetype pandoc set makeprg=makepandoc\ markdown

Which calls this little shell script:

make && \
vim --servername "${1}" --remote-send '<C-\><C-N>:e<CR>'

And then (1) in a terminal, next to the one you're typing in:

vim -R --servername markdown draft.txt

Now every time you call :make (after writing it), the other vim window reloads it's file. Some things that obviously don't work: scrollbind, so you need to manual scroll to the interesting area of your file.

The fancy stuff right now would be to show a screencast, but ... no.

Tagged , ,

IDN and Private RR in Go DNS

Thanks to the excellent work from Alex Sergeyev, Go DNS has gotten some new features. I want to highlight two: IDN (https://www.ietf.org/rfc/rfc3492.txt) and Private RR support (http://tools.ietf.org/html/rfc6895).


This adds support for converting from and to Punycode. There is no explicit support, you will need to call idn.ToPunycode and idn.FromPunyCode yourself if you are dealing with IDNs.

The examples give in the code:

name := "インターネット.テスト"
fmt.Printf("%s -> %s", name, idn.ToPunycode(name))

Which outputs:

インターネット.テスト -> xn--eckucmux0ukc.xn--zckzah

Private RR

Another thing that was added is the ability to add private RR types: that is new RR type with a code in the 65,280 - 65,534 range. This makes it possible to specify your own (vanity) types, which then work just as the normal ones in Go DNS.

Say for instance I want to add a new RR called "MIEK" (with type code 65281), this RR has two rdata elements: a domain name and it ends with a hexadecimal string. Basically it looks like:

www.example.com.    IN MIEK     example.com. AABB1234

Note that you can also use TYPExxxx and RFC3597 (http://www.ietf.org/rfc/rfc3597.txt) for this, but then the above RR would look something like this:

www.example.com.    IN TYPE65281 \# 8 AABBCCDDAABB1234

Which works, but of course looks bad in the vanity department.

To do this you will need (see example_test.go for example code in the library) create a new record type, implement the PrivateRdata interface and register the new type with the library.

const typeMiek = 65281
type MIEK struct {
    Target string
    Extra  string

Implement PrivateRdata:

  1. String - returns the string presentation of the rdata;
  2. Len - returns the length of the rdata;
  3. Pack and Unpack to convert to and from wire format;
  4. Parse to parse a text presentation of the rdata;
  5. Copy to allow the private RR to be copied inside Go DNS (sometimes needed for DNSSEC).

So lets start with the implementation beginning with String() and Len()

func (m *MIEK) String() string { return m.Target + " " + m.Extra }
func (m *MIEK) Len() int  { return len(m.Target) + len(m.Extra)/2 }

And the functions to convert from and to wire format:

func (m *MIEK) Pack(buf []byte) (int, error) {
        off, err := dns.PackDomainName(m.Target, buf, 0, nil, false)
        if err != nil {
                return off, err
        h, err := hex.DecodeString(m.Extra)
        if err != nil {
                return off, err
        if off+hex.DecodedLen(len(m.Extra)) > len(buf) {
                return len(buf), errors.New("overflow packing hex")
        copy(buf[off:off+hex.DecodedLen(len(m.Extra))], h)
        off += hex.DecodedLen(len(m.Extra))
        return off, nil

func (m *MIEK) Unpack(buf []byte) (int, error) {
        s, off, err := dns.UnpackDomainName(buf, 0)
        if err != nil {
                return len(buf), err
        m.Target = s
        s = hex.EncodeToString(buf[off:])
        m.Extra = s
        return len(buf), nil

And our Parse function that parses the string slice sx into MIEK's rdata.

func (m *MIEK) Parse(sx []string) error {
        if len(sx) < 2 {
                return errors.New("need at least 2 pieces of rdata")
        m.Target = sx[0]
        if  _, ok := dns.IsDomainName(m.Target); !ok {
                return errors.New("bad MIEK Target")
        // Hex data can contain spaces.
        for _, s := range sx[1:] {
                m.Extra += s
        return nil

And Copy which is needed to copy the rdata:

func (m *MIEK) Copy(dest dns.PrivateRdata) error {
        m1, ok := dest.(*MIEK)
        if !ok {
                return dns.ErrRdata
        m1.Target = m.Target
        m1.Extra = m.Extra
        return nil

And finally we can register the new type:

dns.PrivateHandle("MIEK", typeMiek, func() dns.PrivateRdata { return new(MIEK) })
defer dns.PrivateHandleRemove(typeMiek) // when removing again

We can now use all this code to do the following:

m1, _ := dns.NewRR("example.com. IN MIEK target.example.com. DEADBEEF")
log.Printf("%s\n", m1.String())

That prints:

 example.com.   3600    IN  MIEK    target.example.com. DEADBEEF

Mission accomplished :) This is all pretty new code, but I'm pretty happy how this is shaping up, but there might be some changes here in the future.

If you need to export this new type, your best bet is to convert it to an unknown RR:

u := new(dns.RFC3597)                                                                                                                                                                                                  
log.Printf("%s\n", u.String())

example.com.    3600    IN  MIEK    \# 24 06746172676574076578616d706c6503636f6d00deadbeef
Tagged ,

Go DNS package

Go DNS is a package that implements a DNS interface in Go. This library takes a new, innovative and enterprise ready approach sends and receives queries to and from the DNS. It is licensed under the same license as the official Go code, as this is a fork of that code.

The aim is to be powerful, simple and fast.


  • All RR types;
  • Synchronous and asynchronous queries and replies;
  • DNSSEC: validation, signing, key generation, reading .private key files
  • (Fast) sending/receiving/printing packets, RRs;
  • Full control over what is being send;
  • Zone transfers, EDNS0, TSIG, NSID;
  • Server side programming (a full blown nameserver).
  • (Fast) reading zones/RRs from files/strings.


The git repository is hosted on github.

Examples using this library can be found in exdns repository over at github.

Tutorials and more info.

Printing MX records

A small peek in to how to print MX records with Go DNS.

We want to create a little program that prints out the MX records of domains, like so:

% mx miek.nl
miek.nl.        86400   IN      MX      10 elektron.atoom.net.


% mx microsoft.com 
microsoft.com.  3600    IN      MX      10 mail.messaging.microsoft.com.

First the normal header of a Go program, with a bunch of imports. We need the dns package:

package main

import (

Next we need to get the local nameserver to use:

config, _ := dns.ClientConfigFromFile("/etc/resolv.conf")

Then we create a dns.Client to perform the queries for us. In Go:

c := new(dns.Client)

We skip some error handling and assume a zone name is given. So we prepare our question. For that to work, we need:

  1. a new packet (dns.Msg);
  2. setting some header bits;
  3. define a question section;
  4. fill out the question section: os.Args[1] contains the zone name.

Which translates into:

m := new(dns.Msg)
m.SetQuestion(dns.Fqdn(os.Args[1]), dns.TypeMX)
m.RecursionDesired = true

Then we need to finally 'ask' the question. We do this by calling the Exchange() function. The unused return value is the rtt (round trip time).

r, _, err := c.Exchange(m, net.JoinHostPort(config.Servers[0], config.Port))

Check if we got something sane. The following code snippet prints the answer section of the received packet:

Bail out on an error:

if r == nil {
    log.Fatalf("*** error: %s\n", err.Error())

if r.Rcode != dns.RcodeSuccess {
        log.Fatalf(" *** invalid answer name %s after MX query for %s\n", os.Args[1], os.Args[1])

// Stuff must be in the answer section
for _, a := range r.Answer {
        fmt.Printf("%v\n", a)

And we are done.

Full Source

package main

import (

func main() {
    config, _ := dns.ClientConfigFromFile("/etc/resolv.conf")
    c := new(dns.Client)

    m := new(dns.Msg)
    m.SetQuestion(dns.Fqdn(os.Args[1]), dns.TypeMX)
    m.RecursionDesired = true

    r, _, err := c.Exchange(m, net.JoinHostPort(config.Servers[0], config.Port))
    if r == nil {
        log.Fatalf("*** error: %s\n", err.Error())

    if r.Rcode != dns.RcodeSuccess {
            log.Fatalf(" *** invalid answer name %s after MX query for %s\n", os.Args[1], os.Args[1])
    // Stuff must be in the answer section
    for _, a := range r.Answer {
            fmt.Printf("%v\n", a)
Tagged ,

SkyDNS running live

SkyDNS is able to do DNSSEC. It generates signatures and NSEC3 records on the fly. For authenticated denial of existence SkyDNS uses NSEC3 white lies, of course implementing (and testing!) this isn't completely trivial.

To aid in debugging I've setup a live version of SkyDNS on voordeur.atoom.net, under the name the zone http://dnssex.nl:

% dig +mul +noall +answer @voordeur.atoom.net soa skydns.dnssex.nl
skydns.dnssex.nl.    3600 IN SOA ns1.dns.skydns.dnssex.nl. hostmaster.skydns.local. (
                            1403942400 ; serial
                            28800      ; refresh (8 hours)
                            7200       ; retry (2 hours)
                            604800     ; expire (1 week)
                            60         ; minimum (1 minute)

To help getting DNSSEC support 100% working this zone has been delegated and has an DS record in the parent zone. With unbound-host you can see the validation status of this zone:

% unbound-host -C /etc/unbound/unbound.conf -vt SOA skydns.dnssex.nl 
skydns.dnssex.nl has SOA record ns1.dns.skydns.dnssex.nl. hostmaster.skydns.local. 
    1403942400 28800 7200 604800 60 (secure)

Where (secure) indicates DNSSEC is in order.


However getting NXDOMAIN and NODATA response it gets a bit more flaky, but some stuff is working:

% unbound-host -C /etc/unbound/unbound.conf -vt TXT dns.skydns.dnssex.nl
dns.skydns.dnssex.nl has no TXT record (secure)

And some is not:

% unbound-host -C /etc/unbound/unbound.conf -vt SRV server2.miek.skydns.dnssex.nl
Host server2.miek.skydns.dnssex.nl not found: 3(NXDOMAIN). (BOGUS (security failure))
validation failure <server2.miek.skydns.dnssex.nl. SRV IN>: 
    nameerror proof failed from

sadface I believe this is due to defaulting to skydns.dnssex.nl as the closest encloser and *.skydns.dnssex.nl as the source of synthesis, but I haven't had the time to dig deeper into this.


In the near future I hope to update the current test, to include NSEC3 white lies tests.

Tagged , , , , , ,

SkyDNS version 2

SkyDNS version 1 was announced some time ago, since then it has seen some developments, which resulted in SkyDNS version 2. This new version uses Etcd as its backend. This blog post will walk you through the installation and shows how to use it.


SkyDNS(2) is a service discovery tool that utilizes the DNS to find hosts in a distributed environment. But using DNS means "legacy" clients can be used. Want to know if you MariaDB cluster is still up? ping mariadb.skydns.local can be used for that. By default SkyDNS will use skydns.local. as the domain to anchor all names.


If not already installed, install Go for your system, either via the package manager or from source. After that you will need Etcd and SkyDNS:

  • go get github.com/coreos/etcd
  • go get github.com/skynetservices/skydns

After the installation, start Etcd: ./etcd. This will run a lonely, non clusterized Etcd on port 4001 on your local machine. SkyDNS has the ability to use configuration stored in Etcd, but for now we use the command line flags to start SkyDNS:

% ./skydns -addr= -machines= \
[skydns] Jun  8 08:30:19.761 INFO      | ready for queries

Let's see if it works, by using dig:

% dig @ -p 1054 +noall +answer +add SOA skydns.local
skydns.local. 3600 IN SOA ns1.dns.skydns.local. hostmaster.skydns.local. (
                1402210800 ; serial
                28800      ; refresh (8 hours)
                7200       ; retry (2 hours)
                604800     ; expire (1 week)
                60         ; minimum (1 minute)

Somebody is answering! Note in the other examples, I will use the same command line for, but remove all the flags and options. If a query aimed at SkyDNS does not fall under skydns.local. it will forward it to and returns the answer from that:

% dig a miek.nl
miek.nl.        19827 IN A

With this you can configure SkyDNS as your nameserver in /etc/resolv.conf.


The original SkyDNS used a fix naming scheme, environment.service.version.region.skydns.local., SkyDNS2 does away with this, but still it makes sense to defines some scheme to be used in your environment. In this blog post I will use a very simple scheme that only uses a region, like "east", "west", etc.

Let register a service in Etcd, we want the register the name 'web01.east.skydns.local', which listens on port 80 and has an IP4 address of All names used by SkyDNS in Etcd are stored under /skydns/ and we need to reverse the domain name. So to register the name we need to use the key: /v2/keys/skydns/local/skydns/east/web01, the payload of it must be JSON like so:

% curl -XPUT \
    -d value='{"Port":80,"Host": ""}'

And retrieving it via DNS:

% dig A web01.east.skydns.local
web01.east.skydns.local. 3600 IN A

Now we also add another webservice in the east region, web02.east.skydns.local, with IP4 address of

Now suppose you want to have a list of all webservers in the east region? Simple just query for east.skydns.local:

% dig A east.skydns.local
east.skydns.local.  3600 IN A
east.skydns.local.  3600 IN A

Of course IP6 is also supported. Using A and AAAA records allows for "legacy" support, however the port number must be know by the client connection, because that information is not in the returned records. To fix this you can also query for SRV records.

SRV Records

SRV records return much more information than A/AAAA records, it includes a port number, a priority a weight and a name (not an address record). As the service information for web01 only includes an address, SkyDNS will synthesise the SRV record and includes the actual IP address in the additional section:

% dig SRV web01.east.skydns.local
web01.east.skydns.local. 3600 IN SRV 10 100 80 web01.east.skydns.local.
web01.east.skydns.local. 3600 IN A

The numbers "10", "100" and "80" in the SRV records are respectively:

  • 10: priority.
  • 20: weight (when multiple SRV records have the same priority, look at the weight). In SkyDNS weight is a percentage.
  • 80: the port number for the service, if the port is not given in the service, it defaults to 0.

Of course this all works when you query for east.skydns.local as well.


The DNS standards supports wildcards, but SkyDNS extends this usage to allow wildcards within a domainname. To show how this we add another service, this time web01.west.skydns.local. Suppose we want to target all web01 servers? With plain DNS you will need to do two queries (and know about west and east!), with SkyDNS only one is needed:

% dig web01.*.skydns.local
web01.*.skydns.local.   3600    IN  SRV 10 50 80 web01.east.skydns.local.
web01.*.skydns.local.   3600    IN  SRV 10 50 80 web01.west.skydns.local.
web01.east.skydns.local. 3600   IN  A
web01.west.skydns.local. 3600   IN  A


Signed responses are also supported, although authenticated denial of existence based on NSEC3 is a work in progress. A quick primer on how to enable it, as there are a few steps.

  1. Generate a DNSSEC keypair for SkyDNS:

    % dnssec-keygen skydns.local
    Generating key pair........................................++++++ .....++++++ 
  2. Use the basename of the generated key pair as an argument to SkyDNS:

    % ./skydns -addr= -machines= \
        -nameservers= -dnssec Kskydns.local.+005+04821
    [skydns] Jun  8 12:28:37.981 INFO      | ready for queries, signing with Kskydns.local.+005+04821

When you know query with the DO bit on (+dnssec in dig) you will get signed responses:

% dig +dnssec web01.*.skydns.local
web01.*.skydns.local.   3600 IN SRV 10 50 80 web01.east.skydns.local.
web01.*.skydns.local.   3600 IN SRV 10 50 80 web01.west.skydns.local.
web01.*.skydns.local.   3600 IN RRSIG SRV 5 4 3600 (
                     20140615113057 20140608083057 4821 skydns.local.
                     4ixafFhbJSD+Rc4eK764Rberhik/zUtuXDe8kXM= )
web01.east.skydns.local. 3600 IN A
web01.west.skydns.local. 3600 IN A
web01.east.skydns.local. 3600 IN RRSIG A 5 4 3600 (
                     20140615113057 20140608083057 4821 skydns.local.
                     BfPkVwACwBAWaPJWrxy90v43NXdSunl55eUVoP4= )
web01.west.skydns.local. 3600 IN RRSIG A 5 4 3600 (
                     20140615113057 20140608083057 4821 skydns.local.
                     HkbwFHe4Y9qNTF4ygvU0BtObbJ3+e0hW8wr6YIU= )

The signatures are cached, so this does not turn into an easy DDoS at once.

Other responses you expect from a DNS server are supported, like SOA, NS, TXT, etc.

Tagged , , , ,

Learning Go

"Learning Go" is a book that gives an introduction into the Go language of Google. It is licensed under a copy-left license. The book currently consists out +/- 120 (A4 sized) pages and the following chapters:

  1. Introduction
    Show how to install Go and details the lineage of the language Go.
  2. Basics
    Types, variables and control structures.
  3. Functions
    How to make and use functions.
  4. Packages
    Functions and data is grouped together in packages. Here you will see how to make your own package. How to unit test your package is also described.
  5. Beyond the basics
    Learn how to create your own data types and define function on them (called methods in Go).
  6. Interfaces
    Go does not support Object Orientation in the traditional sense. In Go the central concept is interfaces.
  7. Concurrency
    With the go keyword function can be started in separate routines (called goroutines). Communication with those goroutines is done via channels.
  8. Communication
    How to create/read/write from and to files. And how to do networking.

Each chapter concludes with a number of exercises (and answers) to may help you to get some hands on experience. Currently it has more than 30 exercises.

There is also a Chinese translation by Mike Spook.

What readers say:

I am really glad that I found your Go book. It's been a couple of weeks since I started learning Go, but didn't make much progress till I found your book.

I also read with great interest the (successive versions of the) free E-book by Miek Gieben & Co. Which I find definitely very well crafted and very useful. Definitely an extremely laudable initiative.

Prebuild PDFs can be found at /downloads/Go. The source code of the book can be found on github.

It is written in LaTeX with the Memoir class (and a bunch a extra classes).

Questions, patches, text, bug reports and general discussions can be directed to miek@miek.nl (in English or Dutch). If you like this work you may choose support it by sending me money :)

Tagged , , ,

DNS Router

Say you have a zone that does not fit in the memory of one machine. Who hasn't these zones nowadays? How would you solve such a problem? With a DNS router of course!

Dns router is a small Go program I whipped together that acts as a DNS router. Clients register an <ip:port, regexp> combination and will then only receive queries that match that regular expression. The registration happens in Etcd. Of course "Dns router" (I need a better name), has some features, it will:

  • health checks the server every 5 seconds using TCP using a id.server. TXT CH query;
  • set an Ectd watch to get updates when a new server is added or removed.

So it's pretty dynamic, but the health checking could be better, as servers will never be re-added once removed.

Ldns actually has an utility to split a zonefile into chunks (with a new SOA, called zsplit, see http://git.nlnetlabs.nl/ldns/tree/examples/ldns-zsplit.1. In this case I just manually split a zone into 2 chunks, one with names starting with [ab] and another with [cd]. Of course the apex of the zone needs to go somewhere, so this has to be specified somewhere. See the examples later in this article.

For the purpose of this article I've used 2 docker images with BIND9 and the 2 (split) zones I have prepared.

The whole "how-do-I-prepare-an-Docker-image" will be left out, there is plenty of documentation on the Net on this. In all I've created two docker images, two running BIND9 and pieces of miek.nl. After fiddling with docker I found the following command line would start my VMs OK:

docker run -p 5300:53/udp -p 5300:53 -d miek/bind:bind9a

And run the other docker container on a different port:

docker run -p 5301:53/udp -p 5301:53 -d miek/bind:bind9c

So, all a-b names are reachable on port 5300 and all c-d names can be found via port 5301.

Assuming we have an etcd running on our host we register our two docker VMs with it and then start dnsrouter.

curl -L -XPUT -d value=",^[ab]\.miek"
curl -L -XPUT -d value=",^[cd]\.miek"

And two routes for the apex of the zone, dnsrouter will round robin between the two servers.

% DNS_ADDR= ./dnsrouter
2014/05/17 10:54:38 enabling health checking
2014/05/17 10:54:38 setting watch
2014/05/17 10:54:38 getting initial list
2014/05/17 10:54:38 unable to parse node /dnsrouter with value # small bug I need to fix
2014/05/17 10:54:38 adding route ^[ab]\.miek for
2014/05/17 10:54:38 adding route ^[cd]\.miek for
2014/05/17 10:54:38 ready for queries

So dnsrouter is running on port 5299, lets try some queries and check the logs of dnsrouter.

% dig @localhost +noall +ans -p 5299 TXT a.miek.nl
a.miek.nl.      43200   IN  TXT "aa"
% dig @localhost +noall +ans -p 5299 TXT c.miek.nl
c.miek.nl.      43200   IN  TXT "cc"

And the logs from dnsrouters:

2014/05/17 11:04:07 routing a.miek.nl. to
2014/05/17 11:04:12 routing c.miek.nl. to

A request for the apex of the zone fails because we don't have setup a route for it, so let's add two:

2014/05/17 11:06:51 adding route ^miek for
2014/05/17 11:06:58 adding route ^miek for

And dig again:

% dig @localhost +noall +ans -p 5299 SOA miek.nl
miek.nl. 43200   IN  SOA linode.atoom.net. miek.miek.nl. 1282630056 14400 3600 604800 86400

And we even see some round robin at work:

2014/05/17 11:07:23 routing miek.nl. to
2014/05/17 11:07:23 routing miek.nl. to

Let's kill one of the docker VMs. Dnsrouter should detect this and disable that server. It does not autmatically re-add it, for that you need to write again to etcd, which will then automatically be picked up by Dnsrouter.

% docker stop 29fde54f64f8
2014/05/17 11:22:00 healthcheck failed for
2014/05/17 11:22:00 removing

And starting it again:

% docker start 29fde54f64f8
% curl -L -XPUT -d value=",^[ab]\.miek"
2014/05/17 11:23:41 adding route ^[ab]\.miek for

In an upcoming article I will describe how I got this running on CoreOS.

Tagged , ,


During two Ubuntu 14.04 upgrades, both on a Mac (so needing an EFI boot), grub was borked after the install resulting in a grub rescue prompt when booting.

The actual error was error: symbol 'grub_term_highlight_color' not found.

Needless to say I couldn't get the system to boot from this prompt.

I had a Fedora boot USB stick laying around, but using that did not really fix the problem, in any case I could use it to copy off /home if I could not rescue the system.

HOWEVER, the following procedure worked great!

  • Download super grub disk;
    • 1.4 MB download!
  • Copy it to an USB stick: dd if=super_grub_disk_hybrid-1.98s1.iso of=/dev/sdb;
  • Boot from this USB stick;
  • In the boot menu, choose detect OSs;
  • Boot you newly minted, unbootable, Ubuntu 14.04;
  • If all goes well it should boot Ubuntu and you can log in;
  • Install and use boot repair;
  • If you use EFI, check this: https://help.ubuntu.com/community/UEFI.
Tagged , ,


GNOME 3 finally pushed me over the edge. After I brief stint with cinnamon, I decided the only thing left was to configure a tiling window manager and some tweaks to make it more usable. For no reason at all, I settled on i3, which seems really nice and simple to configure.

But how to use i3 comfortable? i3 is a tiling window manager, which makes it ubercool, but with it you loose things like automount, brightness keys, etc.; all the things you expect from a Linux desktop nowadays. This blog item deals with getting the goodies from i3, without giving up on all the other things you like.

See http://i3wm.org/docs/user-contributed/lzap-config.html, and the Arch Wiki for some good documentation too https://wiki.archlinux.org/index.php/I3 to get this going.

I took the following steps to get it going, this is still rough around the edges, because I'm lazy. See this screenshot: i3 comfy screenshot

  • gnome-session-daemon
    1. Start this daemon, it makes stuff much easier, like default keys for brightness, etc.
    2. Also makes your gtk-apps look nice, because they are themed.
  • Keys
    1. Volume - works with gnome-session-daemon.
    2. Keyboard - idem.
    3. Screen - idem.
  • Locking

    1. i3lock
    2. When running gnome-settings-daemon, disable the screensaver:

      gconftool-2 --type boolean -s /apps/gnome_settings_daemon/screensaver/start_screensaver false

    3. Create a keycombo in your config to lock your screen, i3lock -i <image.png> -t can be used for that.

    4. And better yet: sudo apt-get remove gnome-screensaver
  • Network manager

    1. Start nm-applet.
  • Bluetooth
    1. apt-get install blueman, it gives you blueman-applet.
  • Notification
    1. There is dunst, but this was also fixed by running gnome-settings-daemon, but needs some extra configuration for automounting notifications.
  • Mounting external disks with notifications.

    1. Install the debian package of udisks-glue (64 bit) from: https://packages.debian.org/sid/amd64/udisks-glue/download
    2. Add some config for notifications: start with: udisks-glue -c <file> and in that config file add or set:
      match disks {
          post_insertion_command = "udisks --mount %device_file --mount-options sync"
          post_mount_command = "notify-send %device_file 'mounted %device_file %mount_point'"
          post_unmount_command = "notify-send %device_file 'unmounted %device_file %mount_point'"
  • puvacontrol for sound stuff other than volume control

    1. Add a shortcut that starts pavucontrol.
  • Solarized colors: https://github.com/lkraav/dotfiles/tree/master/.i3, which you can then tweak, to make them even better.
  • Lock screen when suspending:
    1. exec --no-startup-id xautolock -detectsleep -locker 'i3lock -i ~/.backgrounds/eunight2_pv_big.png -t'
    2. Actually this patch http://searchcode.com/codesearch/raw/40065437 is what you want.
    3. Actually you this is a patch that applies cleanly: https://gist.github.com/miekg/9430422

Config file


Add the following lines, to implement some of the above.

exec --no-startup-id /usr/bin/gnome-settings-daemon
exec --no-startup-id nm-applet
exec --no-startup-id blueman-applet
exec --no-startup-id udisks-glue
exec --no-startup-id export $(gnome-keyring-daemon)
exec --no-startup-id feh --bg-scale ~/.backgrounds/Early_Morning_by_Robert_Katzki.jpg


I had some truetype font problems with demnu, this was fix by:

~/.i3/dmenu_run -b -fn 'Source Code Pro-9', basically use dmenu.xft instead of dmenu, fix dmenu_run for this:

if [ -d "$cachedir" ]; then
        cache=$HOME/.dmenu_cache # if no xdg dir, fall back to dotfile in ~
        if stest -dqr -n "$cache" $PATH; then
                stest -flx $PATH | sort -u | tee "$cache" | dmenu.xft "$@"
                dmenu.xft "$@" < "$cache"
) | ${SHELL:-"/bin/sh"} &
Tagged , ,