In XMonad any action is done with a keyboard shortcut: start an app, switch between workspaces, change the layout of your windows, running scripts, etc. This is more efficient than using a mouse, and highly customizable.

XMonad’s focus on keyboard shortcuts, often called “keymaps” or “key bindings”, makes those frequent tasks faster and convenient to perform.
More: Windows, workspaces and screens.
More: Arrange windows with layouts

In this tutorial and following posts we go over the relevant ways to write shortcuts for XMonad:

  1. the keys setting in XMonad’s main datastructure XConfig.
  2. how to write shortcuts using the standard Haskell notation.
  3. how to write shortcuts to manipualte windows and workspaces.
  4. how to write shortcuts with the easier, Emacs-like “EZ” notation.
  5. typical actions to perform with XMonad.
info

When writing XMonad shortcuts, we aren’t just configuring an app. We are actually full-blown programming our own window manager as Haskell developers.

Keys in XConfig structure

In order for XMonad to know about our shortcuts we have to set or extend the keys field in XConfig, the structure that holds all our XMonad config data:

-- conceptual XConfig type 
data XConfig = XConfig
  { 
  ..
  , modMask :: KeyMask
  , keys :: XConfig -> Map (ButtonMask,KeySym) (X ())
  ..                    -- |-key-combination-| |XMonad-action|
  }
data
Haskell keyword to signal that the rest of the expression defines a new data type. ➡ in-depth explanation
data XConfig = XConfig
New custom data type definition. The left XConfig is the type name, the right XConfig is the value constructor to build actual values. Both usually have the same name for simplicity, but are used differently. ➡ in-depth explanation
{ .. }
Definition of the payload; define what data a value of XConfig contains.
::
Read as “has type”.
KeyMask
A type for special “modifier” keyboard keys like Ctrl. ➡ mod-keys
->
Haskell keyword for a function type. ➡ Haskell function syntax
XConfig -> Map ..
The type of a function that takes an XConfig value as input and produces a Map value. ➡ Haskell function syntax
Map (ButtonMask,KeySym) (X ())
A mapping from key combinations to XMonad actions.
(ButtonMask,KeySym)
Pair-Type of a special keyboard key and a regular keyboard key.
ButtonMask
Another type for “modifier” keyboard keys, identical to KeyMask. ➡ mod-keys
KeySym
Short for “Keyboard symbol”. A type for regular keyboard keys like A. ➡ all KeySym values

This type definition lets the Haskell compiler check that what we will build has the correct structure (the real type is a little more complicated ➡ XConfig documentation).

The relevant thing we have to build here is a mapping from key combinations (ButtonMask,KeySym) to actions (X ()). That’s what the XMonad authors do for the default shortcuts:

-- conceptual XMonad default config code
defaultKeys conf@(XConfig {modMask = modMask}) = Map.fromList $
 [ ((modMask .|. shiftMask, xK_Return), spawn $ terminal conf) 
 , ((modMask,               xK_p     ), spawn "dmenu_run") 
 ..
 ]

def = XConfig
  { 
  ..
  , modMask = mod1Mask -- "left alt"
  , keys    = defaultKeys
  ..
  }

This is a simplified version of the real XMonad code ➡ default keys source code and ➡ default XConfig source code.

defaultKeys
Custom name for our function that takes an XConfig value as input and returns a Map value. ➡ Haskell function syntax
conf@(XConfig {modMask = modMask})
The single argument to the defaultKeys function.
conf@
Haskell’s “As pattern”. The whole argument value gets a name conf that we can use in the rest of the function, like in spawn $ terminal conf. ➡ as pattern
(XConfig {modMask = modMask})
Pattern matching/destructuring the argument value of defaultKeys to extract only the modifier key value modMask. ➡ pattern matching
{modMask = modMask}

Extract the modifier key value from an XConfig value. The left modMask is what to pattern match against, and the right is a custom name for that value that we can use in the rest of the function, as in (modMask .|. shiftMask).... ➡ pattern matching We could have also used another local name:

-- conceptual XMonad default config code
defaultKeys conf@(XConfig {modMask = localModKey}) = Map.fromList $
 [ ((localModKey .|. shiftMask, xK_Return), spawn $ terminal conf) 
 , ((localModKey,               xK_p     ), spawn "dmenu_run") 
 ..
 ]
Map.fromList
A helper function to construct a Map from the given list of keys-and-action pairs. ➡ fromList documentation
.|.
A function to combine two modifier keys. ➡ documentation
shiftMask
A Haskell value that represents the Shift key.
xK_Return
A Haskell value that represent the Enter key. ➡ all keyboard values
spawn
A function that executes the given terminal command, e.g. spawn "firefox" launches the Firefox browser. ➡ documentation
$
A helper function to write fewer parentheses. ➡ $ explanation
terminal
A function that extracts the terminal app name from a given XConfig value, e.g. terminal conf usually returns the string "xterm" (the default terminal app).
[ .. , .. ]
A list in Haskell. Elements are separated by comma.
XConfig { .. }
Construct a XConfig value. Here we only focus on the shortcut-related modMask and keys fields.
def
Variable name that holds the standard XConfig value as configured by the XMonad authors. ➡ source code

That’s quite heavy Haskell syntax, but the most important part for us is the list of shortcuts.

Native Haskell shortcut notation

A single shortcut is represented as a pair of a key combination and an action:

(<key-combination>, <action>)

A <key-combination> itself is a pair of a special “modifier key” like Ctrl, Shift, or Windows, and a normal keyboard key like a letter or a digit.
More: What are mod-keys?

For example, the pair

((modMask .|. shiftMask, xK_Return), spawn $ terminal conf) 

represents the shortcut that opens a terminal when we press Mod + Shift + Enter at the same time.

This is the Haskell native notation that can be checked by XMonad (it doesn’t compile if we make a mistake) but are quite long to write. There is an Emacs-like notation with plain strings, called “EZ”, that is much nicer to read and write

("M-S-<Enter>", spawn $ terminal conf)

but cannot be checked (that easy) by XMonad.
More: Useful shortcut actions
More: Using EZ shortcuts
More: The best editor: (Doom) Emacs

Add our own shortcuts

To add our own shortcuts we first have to define them and then add them to our XConfig value:

import XMonad.EZConfig (additionalKeys)

-- myKeys :: XConfig -> List 
myKeys conf@(XConfig {modMask = modKey}) = 
 [ ((modKey, xK_f), spawn "firefox")
 , ((modKey, xK_s), spawn "steam")
 ]

-- myConfig :: XConfig
-- Set everything up except keys
myConfig = def
  { --  all other settings
  ..
  , modMask = mod4Mask
  ..
  }

-- Now add our keys
myConfig' = myConfig `additionalKeys` (myKeys myConfig)
import
Load functions and values from other Haskell packages. ➡ Import syntax
myKeys
Custom name for our function that takes an XConfig value as input and returns a Map value with our shortcuts. ➡ Haskell function syntax
conf@(XConfig {modMask = modKey})
The single argument to the myKeys function.
conf@
Haskell’s “As pattern”. The whole argument value gets a name conf that we can use in the rest of the function, e.g. spawn $ terminal conf. ➡ as pattern
(XConfig {modMask = modKey})
Pattern matching/destructuring the argument value of myKeys to extract only the modifier key value modMask. The left modMask is what to pattern match against, and the right modKey is a custom name for that value that we can use in the rest of the function, as in (modKey, xK_f).... ➡ pattern matching
xK_f
A Haskell value that represent the f key. ➡ all keyboard values
spawn
An XMonad function that executes the given terminal command, e.g. spawn "firefox" launches a Firefox browser. ➡ documentation
((modKey, xK_f), spawn "firefox")
A shorcut that opens the Firefox browser, when we press Mod + f.
[ .. , .. ]
A list in Haskell. Elements are separated by comma.
def
Variable that holds a default XConfig value configured by the XMonad creators. ➡ source code
myConfig
Custom variable for our configured XConfig value but without our shortcuts.
def { modMask = mod4Mask }
Create a copy of def containing our modifications. We replace the old modMask value with mod4Mask, which represents the Win key. ➡ Override notation
myConfig'
A slight variant of myConfig that also contains our custom shortcuts. The single quote ' at the end (pronounced “prime” in Math and Haskell) makes it a completely different name to XMonad, but we use it to express that it’s just a small variation of myConfig (without the single quote). In Haskell it is common to add ' to a name of a value that is just a small variation of another value, because inventing new good names is hard.
`additionalKeys`
A helper function (in infix notation) to add our shortcuts to the built-in ones. It takes two arguments, an XConfig value and a list(! not a Map) of shortcuts, and creates a new XConfig value. ➡ Function infix notation
➡ Documentation

This quirk with the two values, myConfig and myConfig', is only needed if our shortcuts should automatically use the correct, configured modMask value. If we don’t care, we can hardcode a modKey like mod4Mask (Win key) into the shortcuts and simplify the code to:

import XMonad.EZConfig (additionalKeys)

-- myKeys :: List 
myKeys  = 
 [ ((mod4Mask, xK_f), spawn "firefox")
 , ((mod4Mask, xK_s), spawn "steam")
 ]

-- myConfig :: XConfig
myConfig = def
  {
  ..
  , modMask = mod4Mask
  ..
  } `additionalKeys` myKeys

More: Use the EZ notation
More: Useful shortcut actions
More: How to (re)name workspaces.
More: How to extract values from XConfig.
More: What are mod-keys?
More: Haskell functions.

warning

If we directly set the keys value without the additionalKeys helper function, we delete all the previous, built-in shortcuts:

..
myKeys conf@(XConfig {modMask = modKey}) = ..

myConfig = def
  { 
  ..
  keys = myKeys -- replace previous shortcuts
  ..
  }

Then we are left with only our own shortcuts, probably without any window management.

Tags: xmonad shortcut haskell key binding keymap beginner

Malte Neuss

Java Software Engineer by day, Haskell enthusiast by night.

Other Posts In Series

Just Enough Haskell For XMonad

XMonad Overview

Most computer users use the standard window manager of their operating system that is built to be accessible for newcomers. They are quite happy with it, but only because they don’t know about time-saving tiling window managers like XMonad.

Read More

Just Enough Haskell For XMonad

How To Change XMonad's Modifier Key

All XMonad shortcuts use the modifier key modMask, which by default is bound to the left Alt key. Often however, we need another key.

Read More

Just Enough Haskell For XMonad

How To Configure Workspace Shortcuts

The keyboard shortcuts in XMonad to manage screens, workspaces and windows often use hardcore Haskell syntax that may be difficult to understand. But it’s just unfamilar.

Read More