Getting fancy keys working on X11
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.