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:
- the
keys
setting in XMonad’s main datastructureXConfig
. ➡ - how to write shortcuts using the standard Haskell notation. ➡
- how to write shortcuts to manipualte windows and workspaces. ➡
- how to write shortcuts with the easier, Emacs-like “EZ” notation. ➡
- typical actions to perform with XMonad. ➡
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 rightXConfig
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
@(XConfig {modMask = modMask}) = Map.fromList $
defaultKeys conf.|. shiftMask, xK_Return), spawn $ terminal conf)
[ ((modMask "dmenu_run")
, ((modMask, xK_p ), spawn ..
]
= XConfig
def
{ ..
= mod1Mask -- "left alt"
, modMask = defaultKeys
, keys ..
}
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 inspawn $ terminal conf
. ➡ as pattern (XConfig {modMask = modMask})
- Pattern matching/destructuring the argument value of
defaultKeys
to extract only the modifier key valuemodMask
. ➡ pattern matching {modMask = modMask}
Extract the modifier key value from an
XConfig
value. The leftmodMask
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 @(XConfig {modMask = localModKey}) = Map.fromList $ defaultKeys conf.|. shiftMask, xK_Return), spawn $ terminal conf) [ ((localModKey "dmenu_run") , ((localModKey, xK_p ), spawn .. ]
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-relatedmodMask
andkeys
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
.|. shiftMask, xK_Return), spawn $ terminal conf) ((modMask
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
@(XConfig {modMask = modKey}) =
myKeys conf"firefox")
[ ((modKey, xK_f), spawn "steam")
, ((modKey, xK_s), spawn
]
-- myConfig :: XConfig
-- Set everything up except keys
= def
myConfig -- all other settings
{ ..
= mod4Mask
, modMask ..
}
-- Now add our keys
= myConfig `additionalKeys` (myKeys myConfig) 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 valuemodMask
. The leftmodMask
is what to pattern match against, and the rightmodKey
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 oldmodMask
value withmod4Mask
, 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 ofmyConfig
(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 newXConfig
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 "firefox")
[ ((mod4Mask, xK_f), spawn "steam")
, ((mod4Mask, xK_s), spawn
]
-- myConfig :: XConfig
= def
myConfig
{..
= mod4Mask
, modMask ..
`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.
If we directly set the keys value without the additionalKeys
helper function, we delete all the previous, built-in shortcuts:
..
@(XConfig {modMask = modKey}) = ..
myKeys conf
= def
myConfig
{ ..
= myKeys -- replace previous shortcuts
keys ..
}
Then we are left with only our own shortcuts, probably without any window management.