Getting fancy keys working on X11

technologyx11 and xmonad

So the other day I decided to have a go at getting some of the fancy keys on my keyboard working under X11 (specifically, under Xmonad, but this should be useful for anybody not using Gnome/KDE, and maybe even if you are). Since it may be useful to others, I’m going to document it.

A little theory: what X11 receives from the keyboard are what’s called keycodes; these are just numbers (for example, the space key has keycode 65). To be useful, the keycodes must be mapped to keysyms; these have names as well as numbers (for example, the space key has keysym 0x20, “space”). Then, applications can bind themselves to keysyms. This means that, for example, a music player can bind itself to XF86AudioPlay, XF86AudioStop, and so on, without caring what keys these actually are or whether they even exist on this particular computer.

So, the first step is to bind the keys to a keycode. Fire up a terminal and run xev. xev will print every X11 event it receive to the terminal, which is very helpful when you’re trying to do what we’re doing now. It’s also an utter, utter pain in the arse if you’re not careful, because every tiny mouse movement is an X11 event, about which xev will print pages of information. I suggest you have three windows open: xev, the terminal, and a text editor. Focus on the xev window, and press a key that you want to configure. The output should look something like this:

KeyPress event, serial 24, synthetic NO, window 0x1800001,
root 0x62, subw 0x0, time 164893976, (281,913), root:(281,931),
state 0x0, keycode 162 (keysym 0x0, NoSymbol), same_screen YES,
XLookupString gives 0 bytes:
XmbLookupString gives 0 bytes:
XFilterEvent returns: False

The bit we’re interested in is on the third line: “keycode 161”. Make a note, and repeat for every key that you’re configuring. Make sure you note which key it is, as well…

Next, you need to find the names of the keysyms you can use; you can’t just make up your own, unfortunately, so you need to use (or abuse) the default ones. The file /usr/share/X11/XKeysymDB (on a Debian system) has a big list. The file’s also available here. Find ones that sound relevant, then fire up your editor again—create a file in your home directory called .xmodmaprc. The contents of the file should look something like this:

keycode 144 = XF86AudioPrev
keycode 153 = XF86AudioNext
keycode 162 = XF86AudioPlay
keycode 164 = XF86AudioStop
keycode 160 = XF86AudioMute
keycode 174 = XF86AudioLowerVolume
keycode 176 = XF86AudioRaiseVolume
keycode 237 = XF86AudioMedia

That’s the keycode you noted down earlier, followed by the keysym you want to assign to it.

The next step is to make sure this loads when you start up X11. If you have a .xinitrc or .xsession file, add the line xmodmap ~/.xmodmaprc to it somewhere before the line that starts your window manager; if you use Gnome or similar, there’s probably a shiny graphical way to do exactly the same thing but with more effort. Anyway, if you run that command now, it’ll save you having to log out of X11 and back in; on the other hand, you may want to test that it works. Your call. Either way, if you run xev again and press one of the buttons you just configured, you should get output like this:

KeyPress event, serial 24, synthetic NO, window 0x1800001,
root 0x62, subw 0x0, time 166122855, (222,795), root:(222,813),
state 0x8, keycode 162 (keysym 0x1008ff14, XF86AudioPlay), same_screen YES,
XLookupString gives 0 bytes:
XmbLookupString gives 0 bytes:
XFilterEvent returns: False

If you’re still getting keysym 0x0, NoSymbol, you’ve done something wrong. Try harder next time.

The next step is to configure things to respond to these key events. I’m going to talk about XMonad here; if you don’t use XMonad, you’re basically on your own as far as I’m concerned. XMonad can configure keybindings without a problem, but all of the examples usually given are the “normal” keys, with fancy aliases set up for them. If you don’t already have keybindings set up in your xmonad.hs, you’ll probably want something along these lines in there:

main = xmonad $ defaultConfig {
    keys              = \c -> myKeys `M.union` keys defaultConfig c
}

myKeys  = M.fromList $ [
    -- Stuff goes here.
]

There are many examples on the Haskell wiki. The important part is what goes in the myKeys section; as I mentioned, there aren’t any fancy aliases defined, so you need to use the hexadecimal number that also appears in the output of xev. You should end up with something like this:

myKeys  = M.fromList $
[
    ((0, 0x1008ff14), spawn "mpc toggle")       -- XF86AudioPlay
  , ((0, 0x1008ff15), spawn "mpc stop")         -- XF86AudioStop
  , ((0, 0x1008FF16), spawn "mpc prev")         -- XF86AudioPrev
  , ((0, 0x1008FF17), spawn "mpc next")         -- XF86AudioNext
  , ((0, 0x1008FF12), spawn "mute")             -- XF86AudioMute
  , ((0, 0x1008FF11), spawn "volume -10")       -- XF86AudioLowerVolume
  , ((0, 0x1008FF13), spawn "volume +10")       -- XF86AudioRaiseVolume
  , ((0, 0x1008FF32), spawn "mpc-notify-info")  -- XF86AudioMedia
]

I use mpd, so my keybindings are specific to that; other music players may not be so scriptable. mute and volume are hacks of my own that parse the output of amixer and do stuff to it. mpc-notify-info pops up the status output from mpc in a notification window.