Update: The xmonad parts are out of date as of 0.5 because the config file changes.

Using xev and xmodmap

All of the tiling window managers rely on keyboard binds for performing actions. By default, they usually choose Mod1 (left alt) because it's on nearly all keyboards. However, this interferes with a lot of programs especially GNU tools such as Emacs and bash.

This tutorial has two parts: Tell your window manager that you want to treat a key as meta and a separate one for remapping a key and then telling the WM to treat that as meta. You will use one or the other depending on your situation.

You should choose a meta key that doesn't interfere. Typically a good choice is the Windows key since there are no binds to it by default.

Background

A key code is bound to a particular physical key regardless of what you call the key or how it is labeled. It's the response from the keyboard. An example is 0x67.

A key sym is how the OS interprets that key. You can have multiple key codes bound to the same keysym. There may be two independent ways to generate the same keysym. An example is "g". Keycode 0x67 is mapped to the keysym "g". I could have additional keycodes mapped to keysym "g" if I wanted.

An example of a modifier is shift. It changes the way the OS should handle the event. A modifier is like a keysym in that you can have multiple keysyms associated with the same modifier. Each of those keysyms will produce the modifier.

xev is a program that displays X events.

xmodmap is a program that lets you remap keys in X.

Find out the keysym for the windows key

You need to know what keysym the windows key is using. Start xev and move your mouse pointer into the box. Now when you hit any key, the console where you started xev will display lots of information.

Here's an example of what happens when you hit the windows key:

KeyPress event, serial 23, synthetic NO, window 0x5a00002,
    root 0x118, subw 0x0, time 1573517260, (735,905), root:(736,925),
    state 0x0, keycode 115 (keysym 0xffeb, Super_L), same_screen YES,
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

KeyRelease event, serial 26, synthetic NO, window 0x5a00002,
    root 0x118, subw 0x0, time 1573517414, (735,905), root:(736,925),
    state 0x40, keycode 115 (keysym 0xffeb, Super_L), same_screen YES,
    XLookupString gives 0 bytes:

You first notice there are two entries because one is a key press and the other is a release. What you are interested in is the keysym which in this case is Super_L.

Find the current modifier assignments

Next, run the command xmodmap which will display a table of information about modifiers and which keysyms produce those modifiers.

The way you read this table is the left hand column is the name of a modifier and the right hand side are the keys that produce those modifiers.

xmodmap:  up to 3 keys per modifier, (keycodes in parentheses):

shift       Shift_L (0x32),  Shift_R (0x3e)
lock        Caps_Lock (0x42)
control     Control_L (0x25),  Control_R (0x6d)
mod1        Alt_L (0x40),  Alt_L (0x7d),  Meta_L (0x9c)
mod2        Num_Lock (0x4d)
mod3
mod4        Super_L (0x7f),  Hyper_L (0x80)
mod5        Mode_switch (0x5d),  ISO_Level3_Shift (0x7c)

Search through this list for the keysym you picked out. For my example, Super_L is on the same line as mod4. This means when you press the Windows key (keysym Super_L) it will produce a mod4 modifier.

Now that you know the Windows key produces mod4, you need to tell your window manager that you want to use mod4 as the meta key.

Changing xmonad

In Config.hs, change this to have mod4 instead of mod1:

-- |
-- modMask lets you specify which modkey you want to use. The default is
-- mod1Mask ("left alt").  You may also consider using mod3Mask ("right
-- alt"), which does not conflict with emacs keybindings. The "windows
-- key" is usually mod4Mask.
--
modMask :: KeyMask
modMask = mod1Mask

Changing a key to a modifier and setting that as META

Using the Windows key isn't always a good or viable choice. On one of my keyboards, there is no Windows key. Due to its layout, I rarely use one of the delete keys.

This example sets the delete key up as a keysym Hyper_R. I cannot use the existing keysym because lots of programs expect delete events. Very few programs expect a hyper key so this is a good choice to avoid collisions.

After choosing a keysym Hyper_R, I'll choose the modifer mod3 because nothing is bound to it.

Find out the keycode for the delete key

You need to know what keycode the delete key is using. Start xev and move your mouse pointer into the box. Now when you hit any key, the console where you started xev will display lots of information.

Here's an example of what happens when you hit the delete key:

KeyPress event, serial 26, synthetic NO, window 0x800002,
    root 0x60, subw 0x800003, time 48527, (32,42), root:(33,62),
    state 0x0, keycode 107 (keysym 0xffff, Delete), same_screen YES,
    XLookupString gives 1 bytes: (7f) ""
    XmbLookupString gives 1 bytes: (7f) ""
    XFilterEvent returns: False

KeyRelease event, serial 26, synthetic NO, window 0x800002,
    root 0x60, subw 0x800003, time 48607, (32,42), root:(33,62),
    state 0x0, keycode 107 (keysym 0xffff, Delete), same_screen YES,
    XLookupString gives 1 bytes: (7f) ""

You first notice there are two entries because one is a key press and the other is a release. What you are interested in are the keycode and keysym which in this case is 107 and Delete.

Find the current modifier assignments

Next, run the command xmodmap which will display a table of information about modifiers and which keysyms (keycodes) produce those modifiers. See the first example for more information.

modmap:  up to 3 keys per modifier, (keycodes in parentheses):

shift       Shift_L (0x32),  Shift_R (0x3e)
lock        Caps_Lock (0x42)
control     Control_L (0x25),  Control_R (0x6d)
mod1        Alt_L (0x40),  Alt_L (0x7d),  Meta_L (0x9c)
mod2        Num_Lock (0x4d)
mod3
mod4        Super_L (0x7f),  Hyper_L (0x80)
mod5        Mode_switch (0x5d),  ISO_Level3_Shift (0x7c)

You want to find a modifier that we either don't use or has nothing assigned to it so the WM doesn't interfere with our applications. The safest choice is to pick a modifier that has nothing assigned to it. In the above listing, there is nothing currently bound that will produce a mod3.

You'll notice Hyper_R isn't in the above list. That's another reason why I chose Hyper_R besides the fact that almost no programs use it.

Changing the Delete key into a Hyper_R

You will need to use xmodmap to tell X that you want your key labeled Delete to now function as a Hyper_R. It will also tell X that Hyper_R produces a mod3 modifier.

Go back (or start xev again) and find the keycode for your delete key. In my case, it is 107.

Create a file called ~/.xmodmaprc with the following lines:

! Map the delete key to Hyper_R
keycode 107 = Hyper_R

! Make Hyper_R generate a mod3 modifier
add Mod3 = Hyper_R

Test xmodmap to make sure it works

Run xmodmap ~/.xmodmaprc to make the change. Run xmodmap again without an argument to display the changes:

xmodmap:  up to 3 keys per modifier, (keycodes in parentheses):

shift       Shift_L (0x32),  Shift_R (0x3e)
lock        Caps_Lock (0x42)
control     Control_L (0x25),  Control_R (0x6d)
mod1        Alt_L (0x40),  Alt_L (0x7d),  Meta_L (0x9c)
mod2        Num_Lock (0x4d)
mod3        Hyper_R (0x6b)
mod4        Super_L (0x7f),  Hyper_L (0x80)
mod5        Mode_switch (0x5d),  ISO_Level3_Shift (0x7c)

Notice how there is now a mod3 bound to Hyper_R.

Then verify that the delete key now produces a Hyper_R in xev

KeyPress event, serial 26, synthetic NO, window 0x800002,
    root 0x60, subw 0x800003, time 375105, (35,24), root:(36,44),
    state 0x0, keycode 107 (keysym 0xffee, Hyper_R), same_screen YES,
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

KeyRelease event, serial 26, synthetic NO, window 0x800002,
    root 0x60, subw 0x800003, time 375177, (35,24), root:(36,44),
    state 0x20, keycode 107 (keysym 0xffee, Hyper_R), same_screen YES,
    XLookupString gives 0 bytes: 

Add a line to your ~/.xinitrc or ~/.xsession to start xmodmap

It's important to have xmodmap ~/.xmodmaprc before the line to exec your WM in your X startup scripts. Xmodmap isn't started by default.

Here's what the relevant part of my ~/.xsession looks like:

# Remove delete (rebind to fake key) and set it as Hyper_R (Mod3)
xmodmap ~/.xmodmaprc

# xmonad tiling window manager
exec $HOME/bin/xmonad

Note that you may need to use ~/.xsession or /.xinitrc. Some distributions use /.xsession for xdm/gdm/kdm and ~/.xinitrc for starting X manually. Others use ~/.xsession for both.

Debian uses /etc/alternatives/x-window-manager by default if you don't have a ~/.xsession or /.xinirc. Setup a /.xsession in Debian because it will work for both manual and xdm/gdm/kdm.