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.

In this tutorial we go over the relevant ways to read and write workspace-related shortcuts for XMonad:

  1. how the default workspace shortcuts are written
  2. how to write and our own workspace shortcuts using the native Haskell and EZ notation.
  3. how to rename workspaces

More: Screens, workspaces and windows.
More: Arrange windows with layouts

Default workspace shortcuts

The default XMonad shortcuts to manage workspaces (green ones in ➡ XMonad cheat sheet ) are written as follows:

-- workspace shortcut code
keys conf@(XConfig {XMonad.modMask = modMask}) = M.fromList $
  ..
  [((modMask .|. m, k), windows $ f i)
  | (i, k) <- zip (XMonad.workspaces conf) [xK_1 .. xK_9]
  , (f, m) <- [(W.greedyView, 0), (W.shift, shiftMask)]]
  ..

This is hardcore Haskell, but can be more intuitive with descriptive variable names:

-- conceptual workspace shortcut code
  [((modMask .|. modKey2, key), windows $ windowOperation workspaceId)
  | (workspaceId, key) <- zip (workspaces conf) [xK_1 .. xK_9]
  , (windowOperation, modKey2) <- [(greedyView, 0), (shift, shiftMask)]]
[ f(x,y) | x <- xs, y <-ys ]

“List comprehension” syntax to loop over several lists simultaneously. ➡ List comprehension For example

[ (x, y) | x <- [xK_1, xK_2], y <- ["1", "2"] ]

results in a list of pairs:

[ (xK_1,"1"), (xK_1,"2"), (xK_2,"1"), (xK_2,"2") ]
modMask

XMonads “modKey” value that usually represents the left Alt key. It comes from the given XConfig value:

keys conf@(XConfig {XMonad.modMask = modMask}) = ...
--                                   |-here-|
➡ How to change the modKey
.|.
A function to combine two modifier keys. ➡ documentation
windows
A function that executes a given window operation like greedyView "1". ➡ Documentation
zip

Combine two lists into a single list of pairs (both first values, then both second values, etc. just like zippers ➡ zip function ), for example

--    |-          -|
zip [xK_1, xK_2] ["1", "2", "3"]
--           |-        -|

creates a list of pairs:

[ (xK_1,"1"), (xK_1,"2"), (xK_2,"1"), (xK_2,"2") ]
greedyView
A function that accepts the name of a workspace like "1" and returns a window operation that will display this workspace on the current screen. ➡ Documentation
shift
A function (not the Shift key!) that takes the name of a workspace like "1" and returns a window operation that will “shift” the currently focused window to that workspace. ➡ Documentation
shiftMask
A Haskell value that represents the Shift key.

In three short lines, using the expressive power of Haskell, we can define what would manually take 18 lines:

 -- shortcuts to show given workspace on current screen
 [ ((modMask .|. 0, xK_1), windows $ greedyView "1") 
 , ((modMask .|. 0, xK_2), windows $ greedyView "2") 
 ..
 , ((modMask .|. 0, xK_9), windows $ greedyView "9") 

 -- shortcuts to move focused window to given workspace
 , ((modMask .|. shiftMask, xK_1), windows $ shift "1") 
 , ((modMask .|. shiftMask, xK_2), windows $ shift "2") 
 ..
 , ((modMask .|. shiftMask, xK_9), windows $ shift "9") 
 ]

For example, the first shortcut brings the workspace "1" onto the current screen when we press Mod + 1 (the 0 in modMask .|. 0 represents “no key”). The last shortcut moves the currently focused window to workspace "9" on Mod + Shift + 9.
More: Screens, workspaces and windows.
More: How to write XMonad shortcuts.

Add our own shortcuts

To add our own workspace shortcuts to XMonad we can add them to our custom list of shortcuts:

-- myKeys :: XConfig -> List 
myKeys conf@(XConfig {modMask = modMask}) = 
  -- other shortcuts
  [ ((modMask, xK_f), spawn "firefox")
  ..
  ] ++
  -- workspace shortcuts -------
  [((modMask .|. modKey2, key), windows $ windowOperation workspaceId)
  | (workspaceId, key) <- zip (workspaces conf) [xK_1 .. xK_9]
  , (windowOperation, modKey2) <- [(lazyView, 0), (greedyView, controlMask), (shift, shiftMask)]]
  ]
  ---------------------------------

-- greedyView alternative
lazyView workspaceId stackSet =
  if isVisible workspaceId stackSet 
  then stackSet 
  else view workspaceId stackSet

isVisible workspaceId stackSet =
  any ((workspaceId ==) . tag . workspace) (visible stackSet)
myKeys
Name for our function that takes an XConfig value as input and returns a list of our custom shortcuts. ➡ How to integrate myKeys
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 modMask is a local name for that value that we can use in the rest of the function, as in (modMask, xK_f).... ➡ pattern matching
xK_f
A Haskell value that represent the f key. ➡ all keyboard values
spawn
An XMonad function that executes a terminal script, e.g. spawn "firefox" launches the 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.
windows
A function that executes a given window operation like greedyView "1". ➡ Documentation
zip

Combine two lists into one list of pairs (both first values, then both second values, etc. like a zipper ➡ zip ), e.g.

--    |-          -|
zip [xK_1, xK_2] ["1", "2", "3"]
--           |-        -|

creates a list of pairs:

[ (xK_1,"1"), (xK_1,"2"), (xK_2,"1"), (xK_2,"2") ]
greedyView
A function that accepts the name of a workspace like "1" and returns a window operation that will display this workspace on the current screen. ➡ Documentation
lazyView
A better alternative greedyView and view when we have multiple monitors. A function that accepts the name of a workspace like "1" and returns a window operation that will display this workspace on the current screen. ➡ explanation
isVisible
A function to check if a workspace is currently visible on one of our screens.
stackSet
A value that represents all our screen, workspace, and window arrangement. Which workspace is assigned to which screen, and which one is visible.
shift
A function (not the Shift key!) that takes the name of a workspace like "1" and returns a window operation that will “shift” the currently focused window to that workspace. ➡ Documentation
shiftMask
A Haskell value that represents the Shift key.
controlMask
A Haskell value that represents the Ctrl key.

Here, we add a new window operation lazyView that is activated on e.g. Mod + 1, while the default greedyView operation on that key combination is moved to Mod + Ctrl + 1.
More: Change the modkey
More: Integrate myKeys into XConfig

If we use the Emacs-like “EZ” notation, we can write same shorctus as follows:

-- myKeys :: XConfig -> List 
myKeys conf@(XConfig {modMask = modMask}) = 
  ..
  [("M-" ++ modKey2 ++ [keyChar], windows $ windowOperation workspaceId)
  | (workspaceId, keyChar) <- zip (workspaces conf) "123456789"
  , (windowOperation, modKey2) <- [(lazyView, ""), (greedyView, "C-"), (shift, "C-")]]
  ]

This is a short expression for the following 27 lines:

[ ("M-1", windows $ lazyView "1") 
, ("M-2", windows $ lazyView "2") 
..
, ("M-9", windows $ lazyView "9") 

, ("M-C-1", windows $ greedyView "1") 
, ("M-C-2", windows $ greedyView "2") 
..
, ("M-C-9", windows $ greedyView "9") 

, ("M-S-1", windows $ shift "1") 
, ("M-S-2", windows $ shift "2") 
..
, ("M-S-9", windows $ shift "9") 
]

More: EZ shortcut notation
See: All built-in window operations

Rename workspace names

The default workspaces, whose name we also see in a status bar like Xmobar, are named "1", "2", .., "9". ➡ Source code If we just want to rename workspaces, we can do just that; we don’t have to rewrite all the shortcuts:

-- old spaces = ["1", "2", .., "9"]
myWorkspaces = ["1:chat", "2:web"]

myConfig = def
  { 
  ..
  , workspaces = myWorkspaces
  }

We simply override the previous workspace namess. The shortcuts will automatically adapt, because the default shortcuts and our myKeys functions use a reference to our XConfig value:

myKeys conf@(XConfig {modMask = modMask}) = 
  --   |here|
  ..
  | (workspaceId, keyChar) <- zip (workspaces conf) "123456789"
  --                             |-----here----|
  ..
Tags: xmonad workspace shortcut haskell key binding ez beginner

Malte Neuss

Java Software Engineer by day, Haskell enthusiast by night.

Other Posts In Series

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

Better XMonad Keyboard Shortcuts: EZ

Writing keyboard shortcuts for XMonad in native Haskell is straightforward. Yet there is an even shorter, “EZ” notation.

Read More

Just Enough Haskell For XMonad

Useful XMonad shortcuts

Knowing and customizing shortcuts is the essence of being productive with XMonad. To get some inspiration it’s helpful to look at how built-in and other shortcuts are defined.

Read More