1# XMonad Configuration Tutorial
2
3We're going to take you, step-by-step, through the process of
4configuring xmonad, setting up [xmobar] as a status bar, configuring
5[trayer-srg] as a tray, and making it all play nicely together.
6
7We assume that you have read the [xmonad guided tour] already.  It is a
8bit dated at this point but, because xmonad is stable, the guide should
9still give you a good overview of the most basic functionality.
10
11Before we begin, here are two screenshots of the kind of desktop we will
12be creating together.  In particular, a useful layout we'll conjure into
13being—the three columns layout from [XMonad.Layout.ThreeColumns] with
14the ability to magnify stack windows via [XMonad.Layout.Magnifier]:
15
16<p>
17<img alt="blank desktop" src="https://user-images.githubusercontent.com/50166980/111586872-c0eb0e00-87c1-11eb-9129-c2781bbbf227.png" width="550">
18<img alt="blank desktop" src="https://user-images.githubusercontent.com/50166980/111586885-c47e9500-87c1-11eb-8e8e-83d4d33c4c8d.png" width="550">
19</p>
20
21So let's get started!
22
23## Preliminaries
24
25First you'll want to install xmonad.  You can either do this with your
26system's package manager or via `stack`/`cabal`.  The latter may be
27necessary if your distribution does not package `xmonad` and
28`xmonad-contrib` (or packages a version that's very old) or you want to
29use the git versions instead of a tagged release.  You can find
30instructions for how to do this in [INSTALL.md].
31
32One more word of warning: if you are on a distribution that installs
33every Haskell package dynamically linked—thus essentially breaking
34Haskell—(Arch Linux being a prominent example) then we would highly
35recommend installing via `stack` or `cabal` as instructed in
36[INSTALL.md].  If you still want to install xmonad via your system's
37package manager, you will need to `xmonad --recompile` _every time_ a
38Haskell dependency is updated—else xmonad may fail to start when you
39want to log in!
40
41We're going to assume xmonad version `0.17.0` and xmonad-contrib version
42`0.17.0` here, though most of these steps should work with older
43versions as well.  When we get to the relevant parts, will point you to
44alternatives that work with at least xmonad version `0.15` and
45xmonad-contrib version `0.16`.  This will usually be accompanied by a
46big "_IF YOU ARE ON A VERSION `< 0.17.0`_", so don't worry about missing
47it!
48
49Throughout the tutorial we will use, for keybindings, a syntax very akin
50to the [GNU Emacs conventions] for the same thing—so `C-x` means "hold
51down the control key and then press the `x` key".  One exception is that
52in our case `M` will not necessarily mean Alt (also called `Meta`), but
53"your modifier key"; this is Alt by default, although many people map it
54to Super instead (I will show you how to do this below).
55
56This guide should work for any GNU/Linux distribution and even for BSD
57folks.  Because debian-based distributions are still rather popular, we
58will give you the `apt` commands when it comes to installing software.
59If you use another distribution, just substitute the appropriate
60commands for your system.
61
62To install xmonad, as well as some utilities, via `apt` you can just run
63
64``` console
65$ apt-get install xmonad libghc-xmonad-contrib-dev libghc-xmonad-dev suckless-tools
66```
67
68This installs xmonad itself, everything you need to configure it, and
69`suckless-tools`, which provides the application launcher `dmenu`.  This
70program is used as the default application launcher on `M-p`.
71
72If you are installing xmonad with `stack` or `cabal`, remember to _not_
73install `xmonad` and its libraries here!
74
75For the remainder of this document, we will assume that you are running
76a live xmonad session in some capacity.  If you have set up your
77`~/.xinitrc` as directed in the xmonad guided tour, you should be good
78to go!  If not, just smack an `exec xmonad` at the bottom of that file.
79
80## Installing Xmobar
81
82What we need to do now—provided we want to use a bar at all—is to
83install [xmobar].  If you visit [xmobar's `Installation` section] you
84will find everything from how to install it with your system's package
85manager all the way to how to compile it yourself.
86
87We will show you how we make these programs talk to each other a bit
88later on.  For now, let's start to explore how we can customize this
89window manager of ours!
90
91## Customizing XMonad
92
93Xmonad's configuration file is written in [Haskell]—but don't worry, we
94won't assume that you know the language for the purposes of this
95tutorial.  The configuration file can either reside within
96`$XDG_CONFIG_HOME/xmonad`, `~/.xmonad`, or `$XMONAD_CONFIG_DIR`; see
97`man 1 xmonad` for further details (the likes of `$XDG_CONFIG_HOME` is
98called a [shell variable]).  Let's use `$XDG_CONFIG_HOME/xmonad` for the
99purposes of this tutorial, which resolves to `~/.config/xmonad` on most
100systems—the `~/.config` directory is also the place where things will
101default to should `$XDG_CONFIG_HOME` not be set.
102
103First, we need to create `~/.config/xmonad` and, in this directory, a
104file called `xmonad.hs`.  We'll start off with importing some of the
105utility modules we will use.  At the very top of the file, write
106
107``` haskell
108import XMonad
109
110import XMonad.Util.EZConfig
111import XMonad.Util.Ungrab
112```
113
114All of these imports are _unqualified_, meaning we are importing all of
115the functions in the respective modules.  For configuration files this
116is what most people want.  If you prefer to import things explicitly
117you can do so by adding the necessary function to the `import` statement
118in parentheses.  For example
119
120``` haskell
121import XMonad.Util.EZConfig (additionalKeysP)
122```
123
124For the purposes of this tutorial, we will be importing everything
125coming from xmonad directly unqualified.
126
127Next, a basic configuration—which is the same as the default config—is
128this:
129
130``` haskell
131main :: IO ()
132main = xmonad def
133```
134
135In case you're interested in what this default configuration actually
136looks like, you can find it under [XMonad.Config].  Do note that it is
137_not_ advised to copy that file and use it as the basis of your
138configuration, as you won't notice when a default changes!
139
140You should be able to save the above file, with the import lines plus
141the other two and then press `M-q` to load it up.  Another way to
142validate your `xmonad.hs` is to simply run `xmonad --recompile` in a
143terminal.  You'll see errors (in an `xmessage` popup, so make sure that
144is installed on your system) if it's bad, and nothing if it's good.
145It's not the end of the world if you restart xmonad and get errors, as
146you will still be on your old, working, configuration and have all the
147time in the world to fix your errors before trying again!
148
149Let's add a few additional things.  The default modifier key is Alt,
150which is also used in Emacs.  Sometimes Emacs and xmonad want to use the
151same key for different actions.  Rather than remap every common key,
152many people just change Mod to be the Super key—the one between Ctrl and
153Alt on most keyboards.  We can do this by changing the above `main`
154function in the following way:
155
156``` haskell
157main :: IO ()
158main = xmonad $ def
159    { modMask = mod4Mask  -- Rebind Mod to the Super key
160    }
161```
162
163The two dashes signify a comment to the end of the line.  Notice the
164curly braces; these stand for a [record update] in Haskell (records are
165sometimes called "structs" in C-like languages).  What it means is "take
166`def` and change its `modMask` field to the thing **I** want".  Taking a
167record that already has some defaults set and modifying only the fields
168one cares about is a pattern that is often used within xmonad, so it's
169worth pausing for a bit here to really take this new syntax in.
170
171Don't mind the dollar sign too much; it essentially serves to split
172apart the `xmonad` function and the `def { .. }` record update visually.
173Haskell people really don't like writing parentheses, so they sometimes
174use a dollar sign instead.  For us this is particularly nice, because
175now we don't have to awkwardly write
176
177``` haskell
178main :: IO ()
179main = xmonad (def
180    { modMask = mod4Mask  -- Rebind Mod to the Super key
181    })
182```
183
184This will be especially handy when adding more combinators; something we
185will talk about later on.  The dollar sign is superfluous in this
186example, but that will change soon enough so it's worth introducing it
187here as well.
188
189What if we wanted to add other keybindings?  Say you also want to bind
190`M-S-z` to lock your screen with the screensaver, `M-S-=` to take a
191snapshot of one window, and `M-]` to spawn Firefox.  This can be
192achieved with the `additionalKeysP` function from the
193[XMonad.Util.EZConfig] module—luckily we already have it imported!  Our
194config file, starting with `main`, now looks like:
195
196``` haskell
197main :: IO ()
198main = xmonad $ def
199    { modMask = mod4Mask  -- Rebind Mod to the Super key
200    }
201  `additionalKeysP`
202    [ ("M-S-z", spawn "xscreensaver-command -lock")
203    , ("M-S-=", unGrab *> spawn "scrot -s"        )
204    , ("M-]"  , spawn "firefox"                   )
205    ]
206```
207
208That syntax look familiar?
209
210You can find the names for special keys, like `Print` or the `F`-keys,
211in the [XMonad.Util.EZConfig] documentation.
212
213We will cover setting up the screensaver later in this tutorial.
214
215The `unGrab` before running the `scrot -s` command tells xmonad to
216release its keyboard grab before `scrot -s` tries to grab the keyboard
217itself.  The little `*>` operator essentially just sequences two
218functions, i.e. `f *> g` says
219
220> first do `f` and then, discarding any result that `f` may have given
221> me, do `g`.
222
223Do note that you may need to install `scrot` if you don't have it on
224your system already.
225
226What if we wanted to augment our xmonad experience just a little more?
227We already have `xmonad-contrib`, which means endless possibilities!
228Say we want to add a three column layout to our layouts and also magnify
229focused stack windows, making it more useful on smaller screens.
230
231We start by visiting the documentation for [XMonad.Layout.ThreeColumns].
232The very first sentence of the `Usage` section tells us exactly what we
233want to start with:
234
235> You can use this module with the following in your `~/.xmonad/xmonad.hs`:
236>
237> `import XMonad.Layout.ThreeColumns`
238
239Ignoring the part about where exactly our `xmonad.hs` is (we have opted
240to put it into `~/.config/xmonad/xmonad.hs`, remember?) we can follow
241that documentation word for word.  Let's add
242
243``` haskell
244import XMonad.Layout.ThreeColumns
245```
246
247to the top of our configuration file.  Most modules have a lot of
248accompanying text and usage examples in them—so while the type
249signatures may seem scary, don't be afraid to look up the
250[xmonad-contrib documentation] on hackage!
251
252Next we just need to tell xmonad that we want to use that particular
253layout.  To do this, there is the `layoutHook`.  Let's use the default
254layout as a base:
255
256``` haskell
257myLayout = tiled ||| Mirror tiled ||| Full
258  where
259    tiled   = Tall nmaster delta ratio
260    nmaster = 1      -- Default number of windows in the master pane
261    ratio   = 1/2    -- Default proportion of screen occupied by master pane
262    delta   = 3/100  -- Percent of screen to increment by when resizing panes
263```
264
265The so-called `where`-clause above simply consists of local declarations
266that might clutter things up where they all declared at the top-level
267like this
268
269``` haskell
270myLayout = Tall 1 (3/100) (1/2) ||| Mirror (Tall 1 (3/100) (1/2)) ||| Full
271```
272
273It also gives us the chance of documenting what the individual numbers
274mean!
275
276Now we can add the layout according to the [XMonad.Layout.ThreeColumns]
277documentation.  At this point, we would encourage you to try this
278yourself with just the docs guiding you.  If you can't do it, don't
279worry; it'll come with time!
280
281We can, for example, add the additional layout like this:
282
283``` haskell
284myLayout = tiled ||| Mirror tiled ||| Full ||| ThreeColMid 1 (3/100) (1/2)
285  where
286    tiled   = Tall nmaster delta ratio
287    nmaster = 1      -- Default number of windows in the master pane
288    ratio   = 1/2    -- Default proportion of screen occupied by master pane
289    delta   = 3/100  -- Percent of screen to increment by when resizing panes
290```
291
292or even, because the numbers happen to line up, like this:
293
294``` haskell
295myLayout = tiled ||| Mirror tiled ||| Full ||| threeCol
296  where
297    threeCol = ThreeColMid nmaster delta ratio
298    tiled    = Tall nmaster delta ratio
299    nmaster  = 1      -- Default number of windows in the master pane
300    ratio    = 1/2    -- Default proportion of screen occupied by master pane
301    delta    = 3/100  -- Percent of screen to increment by when resizing panes
302```
303
304Now we just need to tell xmonad that we want to use this modified
305`layoutHook` instead of the default.  Again, try to reason this out for
306yourself by just looking at the documentation.  Ready?  Here we go:
307
308``` haskell
309main :: IO ()
310main = xmonad $ def
311    { modMask    = mod4Mask  -- Rebind Mod to the Super key
312    , layoutHook = myLayout  -- Use custom layouts
313    }
314  `additionalKeysP`
315    [ ("M-S-z", spawn "xscreensaver-command -lock")
316    , ("M-S-=", unGrab *> spawn "scrot -s"        )
317    , ("M-]"  , spawn "firefox"                   )
318    ]
319```
320
321But we also wanted to add magnification, right?  Luckily for us, there's
322a module for that as well!  It's called [XMonad.Layout.Magnifier].
323Again, take a look at the documentation before reading on—see if you can
324reason out what to do by yourself.  Let's pick the `magnifiercz'`
325modifier from the library; it magnifies a window by a given amount, but
326only if it's a stack window.  Add it to your three column layout thusly:
327
328``` haskell
329myLayout = tiled ||| Mirror tiled ||| Full ||| threeCol
330  where
331    threeCol = magnifiercz' 1.3 $ ThreeColMid nmaster delta ratio
332    tiled    = Tall nmaster delta ratio
333    nmaster  = 1      -- Default number of windows in the master pane
334    ratio    = 1/2    -- Default proportion of screen occupied by master pane
335    delta    = 3/100  -- Percent of screen to increment by when resizing panes
336```
337
338Don't forget to import the module!
339
340You can think of the `$` here as putting everything into parentheses
341from the dollar to the end of the line.  If you don't like that you can
342also write
343
344``` haskell
345threeCol = magnifiercz' 1.3 (ThreeColMid nmaster delta ratio)
346```
347
348instead.
349
350That's it!  Now we have a perfectly functioning three column layout with
351a magnified stack.  If you compare this with the starting screenshots,
352you will see that that's exactly the behaviour we wanted to achieve!
353
354A last thing that's worth knowing about are so-called "combinators"—at
355least we call them that, we can't tell you what to do.  These are
356functions that compose with the `xmonad` function and add a lot of hooks
357and other things for you (trying to achieve a specific goal), so you
358don't have to do all the manual work yourself.  For example, xmonad—by
359default—is only [ICCCM] compliant.  Nowadays, however, a lot of programs
360(including many compositors) expect the window manager to _also_ be
361[EWMH] compliant.  So let's save ourselves a lot of future trouble and
362add that to xmonad straight away!
363
364This functionality is to be found in the [XMonad.Hooks.EwmhDesktops]
365module, so let's import it:
366
367``` haskell
368import XMonad.Hooks.EwmhDesktops
369```
370
371We might also consider using the `ewmhFullscreen` combinator.  By
372default, a "fullscreened" application is still bound by its window
373dimensions; this means that if the window occupies half of the screen
374before it was fullscreened, it will also do so afterwards.  Some people
375really like this behaviour, as applications thinking they're in
376fullscreen mode tend to remove a lot of clutter (looking at you,
377Firefox).  However, because a lot of people explicitly do not want this
378effect (and some applications, like chromium, will misbehave and need
379some [Hacks] to make this work), we will also add the relevant function
380to get "proper" fullscreen behaviour here.
381
382_IF YOU ARE ON A VERSION `< 0.17.0`_: The `ewmhFullscreen` function does
383  not exist in these versions.  Instead of it, you can try to add
384  `fullscreenEventHook` to your `handleEventHook` to achieve similar
385  functionality (how to do this is explained in the documentation of
386  [XMonad.Hooks.EwmhDesktops]).
387
388To use the two combinators, we compose them with the `xmonad` function
389in the following way:
390
391``` haskell
392main :: IO ()
393main = xmonad $ ewmhFullscreen $ ewmh $ def
394    { modMask    = mod4Mask  -- Rebind Mod to the Super key
395    , layoutHook = myLayout  -- Use custom layouts
396    }
397  `additionalKeysP`
398    [ ("M-S-z", spawn "xscreensaver-command -lock")
399    , ("M-S-=", unGrab *> spawn "scrot -s"        )
400    , ("M-]"  , spawn "firefox"                   )
401    ]
402```
403
404Do mind the order of the two combinators—by a particularly awkward set
405of circumstances, they do not commute!
406
407This `main` function is getting pretty crowded now, so let's refactor it
408a little bit.  A good way to do this is to split the config part into
409one function and the "main and all the combinators" part into another.
410Let's call the config part `myConfig` for... obvious reasons.  It would
411look like this:
412
413``` haskell
414main :: IO ()
415main = xmonad $ ewmhFullscreen $ ewmh $ myConfig
416
417myConfig = def
418    { modMask    = mod4Mask  -- Rebind Mod to the Super key
419    , layoutHook = myLayout  -- Use custom layouts
420    }
421  `additionalKeysP`
422    [ ("M-S-z", spawn "xscreensaver-command -lock")
423    , ("M-S-=", unGrab *> spawn "scrot -s"        )
424    , ("M-]"  , spawn "firefox"                   )
425    ]
426```
427
428Much better!
429
430## Make XMonad and Xmobar Talk to Each Other
431
432Onto the main dish.  First, we have to import the necessary modules.
433Add the following to your list of imports:
434
435``` haskell
436import XMonad.Hooks.DynamicLog
437import XMonad.Hooks.StatusBar
438import XMonad.Hooks.StatusBar.PP
439```
440
441_IF YOU ARE ON A VERSION `< 0.17.0`_: The `XMonad.Hooks.StatusBar` and
442  `XMonad.Hooks.StatusBar.PP` modules don't exist yet.  You can find
443  everything you need in the `XMonad.Hooks.DynamicLog` module, so remove
444  these two imports.
445
446Replace your `main` function above with:
447
448``` haskell
449main :: IO ()
450main = xmonad $ ewmhFullscreen $ ewmh $ xmobarProp $ myConfig
451```
452
453_IF YOU ARE ON A VERSION `< 0.17.0`_: The `xmobarProp` function does not
454  exist in these versions.  Instead of it, use `xmobar` via
455  `main = xmonad . ewmh =<< xmobar myConfig` and carefully read the part
456  about pipes later on (`xmobar` uses pipes to make xmobar talk to
457  xmonad).
458
459As a quick side-note, we could have also written
460
461``` haskell
462main :: IO ()
463main = xmonad . ewmhFullscreen . ewmh . xmobarProp $ myConfig
464```
465
466Notice how `$` became `.`!  The dot operator `(.)` in Haskell means
467function composition and is read from right to left.  What this means in
468this specific case is essentially the following:
469
470> take the four functions `xmonad`, `ewmhFullscreen`, `ewmh`, and
471> `xmobarProp` and give me the big new function
472> `xmonad . ewmhFullscreen . ewmh . xmobarProp` that first executes
473> `xmobarProp`, then `ewmh`, then `ewmhFullscreen`, and finally
474> `xmonad`.  Then give it `myConfig` as its argument so it can do its
475> thing.
476
477This should strike you as nothing more than a syntactical quirk, at
478least in this case.  And indeed, since `($)` is just function
479application there is a very nice relationship between `(.)` and `($)`.
480This may be more obvious if we write everything with parentheses and
481apply the `(.)` operator (because we do have an argument):
482
483``` haskell
484-- ($) version
485main = xmonad $ ewmhFullscreen $ ewmh $ xmobarProp $ myConfig
486-- ($) version with parentheses
487main = xmonad (ewmhFullscreen (ewmh (xmobarProp (myConfig))))
488
489-- (.) version with parentheses
490main = (xmonad . ewmhFullscreen . ewmh . xmobarProp) (myConfig)
491-- xmobarProp applied
492main = (xmonad . ewmhFullscreen . ewmh) (xmobarProp (myConfig))
493-- ewmh applied
494main = (xmonad . ewmhFullscreen) (ewmh (xmobarProp (myConfig)))
495-- xmonad and ewmhFullscreen applied
496main = (xmonad (ewmhFullscreen (ewmh (xmobarProp (myConfig))))
497```
498
499It's the same!  This is special to the interplay with `(.)` and `($)`
500though; if you're on an older version of xmonad and xmonad-contrib and
501use `xmobar` instead of `xmobarProp`, then you _have_ to write
502
503``` haskell
504main = xmonad . ewmhFullscreen . ewmh =<< xmobar myConfig
505```
506
507and this is _not_ equivalent to
508
509``` haskell
510main = xmonad (ewmhFullscreen (ewmh =<< xmobar myConfig))
511```
512
513Consult a Haskell book of your choice for why this is the case.
514
515Back to our actual goal: customizing xmonad.  What the code we've
516written does is take our tweaked default configuration `myConfig` and
517add the support we need to make xmobar our status bar.  Do note that you
518will also need to add the `XMonadLog` plugin to your xmobar
519configuration; we will do this together below, so don't sweat it for
520now.
521
522To understand why this is necessary, let's talk a little bit about how
523xmonad and xmobar fit together.  You can make them talk to each other in
524several different ways.
525
526By default, xmobar accepts input on its stdin, which it can display at
527an arbitrary position on the screen.  We want xmonad to send xmobar the
528stuff that you can see at the upper left of the starting screenshots:
529information about available workspaces, current layout, and open
530windows.  Naïvely, we can achieve this by spawning a pipe and letting
531xmonad feed the relevant information to that pipe.  The problem with
532that approach is that when the pipe is not being read and gets full,
533xmonad will freeze!
534
535It is thus much better to switch over to property based logging, where
536we are writing to an X11 property and having xmobar read that; no danger
537when things are not being read!  For this reason we have to use
538`XMonadLog` instead of `StdinReader` in our xmobar.  There's also an
539`UnsafeXMonadLog` available, should you want to send actions to xmobar
540(this is useful, for example, for [XMonad.Util.ClickableWorkspaces],
541which is a new feature in `0.17.0`).
542
543_IF YOU ARE ON A VERSION `< 0.17.0`_: As discussed above, the `xmobar`
544  function uses pipes, so you actually do want to use the `StdinReader`.
545  Simply replace _all_ occurences of `XMonadLog` with `StdinReader`
546  below (don't forget the template!)
547
548## Configuring Xmobar
549
550Now, before this will work, we have to configure xmobar.  Here's a nice
551starting point.  Be aware that, while Haskell syntax highlighting is
552used here to make this pretty, xmobar's config is _not_ a Haskell file
553and thus can't execute arbitrary code—at least not by default.  If you
554do want to configure xmobar in Haskell there is a note about that at the
555end of this section.
556
557``` haskell
558Config { overrideRedirect = False
559       , font     = "xft:iosevka-9"
560       , bgColor  = "#5f5f5f"
561       , fgColor  = "#f8f8f2"
562       , position = TopW L 90
563       , commands = [ Run Weather "EGPF"
564                        [ "--template", "<weather> <tempC>°C"
565                        , "-L", "0"
566                        , "-H", "25"
567                        , "--low"   , "lightblue"
568                        , "--normal", "#f8f8f2"
569                        , "--high"  , "red"
570                        ] 36000
571                    , Run Cpu
572                        [ "-L", "3"
573                        , "-H", "50"
574                        , "--high"  , "red"
575                        , "--normal", "green"
576                        ] 10
577                    , Run Alsa "default" "Master"
578                        [ "--template", "<volumestatus>"
579                        , "--suffix"  , "True"
580                        , "--"
581                        , "--on", ""
582                        ]
583                    , Run Memory ["--template", "Mem: <usedratio>%"] 10
584                    , Run Swap [] 10
585                    , Run Date "%a %Y-%m-%d <fc=#8be9fd>%H:%M</fc>" "date" 10
586                    , Run XMonadLog
587                    ]
588       , sepChar  = "%"
589       , alignSep = "}{"
590       , template = "%XMonadLog% }{ %alsa:default:Master% | %cpu% | %memory% * %swap% | %EGPF% | %date% "
591       }
592```
593
594First, we set the font to use for the bar, as well as the colors.  The
595position options are documented well on the [xmobar home page] or,
596alternatively, in the [quick-start.org] on GitHub.  The particular
597option of `TopW L 90` says to put the bar in the upper left of the
598screen, and make it consume 90% of the width of the screen (we need to
599leave a little bit of space for `trayer-srg`).  If you're up for it—and
600this really requires more shell-scripting than Haskell knowledge—you can
601also try to seamlessly embed trayer into xmobar by using
602[trayer-padding-icon.sh] and following the advice given in that thread.
603
604In the commands list you, well, define commands.  Commands are the
605pieces that generate the content to be displayed in your bar.  These
606will later be combined together in the `template`.  Here, we have
607defined a weather widget, a CPU widget, memory and swap widgets, a date,
608a volume indicator, and of course the data from xmonad via `XMonadLog`.
609
610The `EGPF` in the weather command is a particular station.  Replace both
611(!) occurences of it with your choice of ICAO weather stations.  For a
612list of ICAO codes you can visit the relevant [Wikipedia page].  You can
613of course monitor more than one if you like; see xmobar's [weather
614monitor] documentation for further details.
615
616The `template` then combines everything together.  The `alignSep`
617variable controls the alignment of all of the monitors.  Stuff to be
618left-justified goes before the `}` character, things to be centered
619after it, and things to be right justified after `{`.  We have nothing
620centered so there is nothing in-between them.
621
622Save the file to `~/.xmobarrc`.  Now press `M-q` to reload xmonad; you
623should now see xmobar with your new configuration!  Please note that, at
624this point, the config _has_ to reside in `~/.xmobarrc`.  We will,
625however, discuss how to change this soon.
626
627It is also possible to completely configure xmobar in Haskell, just like
628xmonad.  If you want to know more about that, you can check out the
629[xmobar.hs] example in the official documentation.  For a more
630complicated example, you can also check out [jao's xmobar.hs] (he's the
631current maintainer of xmobar).
632
633## Changing What XMonad Sends to Xmobar
634
635Now that the xmobar side of the picture looks nice, what about the stuff
636that xmonad sends to xmobar?  It would be nice to visually match these
637two.  Sadly, this is not quite possible with our `xmobarProp` function;
638however, looking at the implementation of the function (or, indeed, the
639top-level documentation of the module!) should give us some ideas for
640how to proceed:
641
642``` haskell
643xmobarProp config =
644  withEasySB (statusBarProp "xmobar" (pure xmobarPP)) toggleStrutsKey config
645```
646
647This means that `xmobarProp` just calls the functions `withEasySB` and
648`statusBarProp` with some arguments; crucially for us, notice the
649`xmobarPP`.  In this context "PP" stands for "pretty-printer"—exactly
650what we want to modify!
651
652Let's copy the implementation over into our main function:
653
654``` haskell
655main :: IO ()
656main = xmonad
657     . ewmhFullscreen
658     . ewmh
659     . withEasySB (statusBarProp "xmobar" (pure def)) defToggleStrutsKey
660     $ myConfig
661```
662
663_IF YOU ARE ON A VERSION `< 0.17.0`_: `xmobar` has a similar definition,
664  relying on `statusBar` alone: `xmobar = statusBar "xmobar" xmobarPP
665    toggleStrutsKey`.  Sadly, the `defToggleStrutsKey` function is not yet
666  exported, so you will have to define it yourself:
667
668``` haskell
669main :: IO ()
670main = xmonad
671     . ewmhFullscreen
672     . ewmh
673   =<< statusBar "xmobar" def toggleStrutsKey myConfig
674  where
675    toggleStrutsKey :: XConfig Layout -> (KeyMask, KeySym)
676    toggleStrutsKey XConfig{ modMask = m } = (m, xK_b)
677```
678
679The `defToggleStrutsKey` here is just the key with which you can toggle
680the bar; it is bound to `M-b`.  If you want to change this, you can also
681define your own:
682
683``` haskell
684main :: IO ()
685main = xmonad
686     . ewmhFullscreen
687     . ewmh
688     . withEasySB (statusBarProp "xmobar" (pure def)) toggleStrutsKey
689     $ myConfig
690  where
691    toggleStrutsKey :: XConfig Layout -> (KeyMask, KeySym)
692    toggleStrutsKey XConfig{ modMask = m } = (m, xK_b)
693```
694
695Feel free to change the binding by modifying the `(m, xK_b)` tuple to
696your liking.
697
698If you want your xmobar configuration file to reside somewhere else than
699`~/.xmobarrc`, you can now simply give the file to xmobar as a
700positional argument!  For example:
701
702``` haskell
703main :: IO ()
704main = xmonad
705     . ewmhFullscreen
706     . ewmh
707     . withEasySB (statusBarProp "xmobar ~/.config/xmobar/xmobarrc" (pure def)) defToggleStrutsKey
708     $ myConfig
709```
710
711Back to controlling what exactly we send to xmobar.  The `def`
712pretty-printer just gives us the same result that the internal
713`xmobarPP` would have given us.  Let's try to build something on top of
714this.  To prepare, we can first create a new function `myXmobarPP` with
715the default configuration:
716
717``` haskell
718myXmobarPP :: PP
719myXmobarPP = def
720```
721
722and then plug that into our main function:
723
724``` haskell
725main :: IO ()
726main = xmonad
727     . ewmhFullscreen
728     . ewmh
729     . withEasySB (statusBarProp "xmobar" (pure myXmobarPP)) defToggleStrutsKey
730     $ myConfig
731```
732
733As before, we now change things by modifying that `def` record until we
734find something that we like.  First, for some functionality that we need
735further down we need to import one more module:
736
737``` haskell
738import XMonad.Util.Loggers
739```
740
741Now we are finally ready to make things pretty.  There are _a lot_ of
742options for the [PP record]; I'd advise you to read through all of them
743now, so you don't get lost!
744
745``` haskell
746myXmobarPP :: PP
747myXmobarPP = def
748    { ppSep             = magenta " • "
749    , ppTitleSanitize   = xmobarStrip
750    , ppCurrent         = wrap " " "" . xmobarBorder "Top" "#8be9fd" 2
751    , ppHidden          = white . wrap " " ""
752    , ppHiddenNoWindows = lowWhite . wrap " " ""
753    , ppUrgent          = red . wrap (yellow "!") (yellow "!")
754    , ppOrder           = \[ws, l, _, wins] -> [ws, l, wins]
755    , ppExtras          = [logTitles formatFocused formatUnfocused]
756    }
757  where
758    formatFocused   = wrap (white    "[") (white    "]") . magenta . ppWindow
759    formatUnfocused = wrap (lowWhite "[") (lowWhite "]") . blue    . ppWindow
760
761    -- | Windows should have *some* title, which should not not exceed a
762    -- sane length.
763    ppWindow :: String -> String
764    ppWindow = xmobarRaw . (\w -> if null w then "untitled" else w) . shorten 30
765
766    blue, lowWhite, magenta, red, white, yellow :: String -> String
767    magenta  = xmobarColor "#ff79c6" ""
768    blue     = xmobarColor "#bd93f9" ""
769    white    = xmobarColor "#f8f8f2" ""
770    yellow   = xmobarColor "#f1fa8c" ""
771    red      = xmobarColor "#ff5555" ""
772    lowWhite = xmobarColor "#bbbbbb" ""
773```
774
775_IF YOU ARE ON A VERSION `< 0.17`_: Both `logTitles` and `xmobarBorder`
776  are not available yet, so you will have to remove them.  As an
777  alternative to `xmobarBorder`, a common way to "mark" the currently
778  focused workspace is by using brackets; you can try something like
779  `ppCurrent = wrap (blue "[") (blue "]")` and see if you like it.  Also
780  read the bit about `ppOrder` further down!
781
782That's a lot!  But don't worry, take a deep breath and remind yourself
783of what you read above in the documentation of the [PP record].  Even if
784you haven't read the documentation yet, most of the fields should be
785pretty self-explanatory; `ppTitle` formats the title of the currently
786focused window, `ppCurrent` formats the currently focused workspace,
787`ppHidden` is for the hidden workspaces that have windows on them, etc.
788The rest is just deciding on some pretty colours and formatting things
789just how we like it.
790
791An important thing to talk about may be `ppOrder`.  Quoting from its
792documentation:
793
794> By default, this function receives a list with three formatted
795> strings, representing the workspaces, the layout, and the current
796> window title, respectively. If you have specified any extra loggers
797> in ppExtras, their output will also be appended to the list.
798
799So the first three argument of the list (`ws`, `l`, and `_` in our case,
800where the `_` just means we want to ignore that argument and not give it
801a name) are the workspaces to show, the currently active layout, and the
802title of the focused window.  The last element—`wins`—is what we gave to
803`ppExtras`; if you added more loggers to that then you would have to add
804more items to the list, like this:
805
806``` haskell
807ppOrder = \[ws, l, _, wins, more, more2] -> [ws, l, wins, more, more2]
808```
809
810However, many people want to show _all_ window titles on the currently
811focused workspace instead.  For that, one can use `logTitles` from
812[XMonad.Util.Loggers] (remember that module we just imported?).
813However, `logTitles` logs _all_ titles.  Naturally, we don't want to
814show the focused window twice and so we suppress it here by ignoring the
815third argument of `ppOrder` and not returning it.  The functions
816`formatFocused` and `formatUnfocused` should be relatively self
817explanitory—they decide how to format the focused resp. unfocused
818windows.
819
820By the way, the `\ ... ->` syntax in there is Haskell's way to express a
821[lambda abstraction] (or anonymous function, as some languages call it).
822All of the arguments of the function come directly after the `\` and
823before the `->`; in our case, this is a list with exactly four elements
824in it.  Basically, it's a nice way to write a function inline and not
825having to define it inside e.g. a `where` clause.  The above could have
826also be written as
827
828``` haskell
829myXmobarPP :: PP
830myXmobarPP = def
831    { -- stuff here
832    , ppOrder = myOrder
833      -- more stuff here
834    }
835  where
836    myOrder [ws, l, _, wins] = [ws, l, wins]
837    -- more stuff here
838```
839
840If you're unsure of the number of elements that your `ppOrder` will
841take, you can also specify the list like this:
842
843``` haskell
844ppOrder = \(ws : l : _ : wins : _) -> [ws, l, wins]
845```
846
847This says that it is a list of _at least_ four elements (`ws`, `l`, the
848unnamed argument, and `wins`), but that afterwards everything is
849possible.
850
851This config is really quite complicated.  If this is too much for you,
852you can also really just start with the blank
853
854``` haskell
855myXmobarPP :: PP
856myXmobarPP = def
857```
858
859then add something, reload xmonad, see how things change and whether you
860like them.  If not, remove that part and try something else.  If you do,
861try to understand how that particular piece of code works.  You'll have
862something approaching the above that you fully understand in no time!
863
864## Configuring Related Utilities
865
866So now you've got a status bar and xmonad.  We still need a few more
867things: a screensaver, a tray for our apps that have tray icons, a way
868to set our desktop background, and the like.
869
870For this, we will need a few pieces of software.
871
872``` shell
873apt-get install trayer xscreensaver
874```
875
876If you want a network applet, something to set your desktop background,
877and a power-manager:
878
879``` shell
880apt-get install nm-applet feh xfce4-power-manager
881```
882
883First, configure xscreensaver how you like it with the
884`xscreensaver-demo` command.  Now, we will set these things up in
885`~/.xinitrc` (we could also do most of this in xmonad's `startupHook`,
886but `~/.xinitrc` is perhaps more standard).  If you want to use xmonad
887with a desktop environment, see [Basic Desktop Environment Integration]
888for how to do this.
889
890Your `~/.xinitrc` may wind up looking like this:
891
892``` shell
893#!/bin/sh
894
895# [... default stuff that your distro may throw in here ...] #
896
897# Set up an icon tray
898trayer --edge top --align right --SetDockType true --SetPartialStrut true \
899 --expand true --width 10 --transparent true --tint 0x5f5f5f --height 18 &
900
901# Set the default X cursor to the usual pointer
902xsetroot -cursor_name left_ptr
903
904# Set a nice background
905feh --bg-fill --no-fehbg ~/.wallpapers/haskell-red-noise.png
906
907# Fire up screensaver
908xscreensaver -no-splash &
909
910# Power Management
911xfce4-power-manager &
912
913if [ -x /usr/bin/nm-applet ] ; then
914   nm-applet --sm-disable &
915fi
916
917exec xmonad
918```
919
920Notice the call to `trayer` above.  The options tell it to go on the top
921right, with a default width of 10% of the screen (to nicely match up
922with xmobar, which we set to a width of 90% of the screen).  We give it
923a color and a height.
924
925Then we fire up the rest of the programs that interest us.
926
927Finally, we start xmonad.
928
929<img alt="blank desktop" src="https://user-images.githubusercontent.com/50166980/111529498-84d49080-8762-11eb-9e81-c15dd844b0a9.png" width="660">
930
931Mission accomplished!
932
933Of course substitute the wallpaper for one of your own.  If you like the
934one used above, you can find it [here](https://i.imgur.com/9MQHuZx.png).
935
936## Final Touches
937
938There may be some programs that you don't want xmonad to tile.  The
939classic example here is the [GNU Image Manipulation Program]; it pops up
940all sorts of new windows all the time, and they work best at defined
941sizes.  It makes sense for xmonad to float these kinds of windows by
942default.
943
944This kind of behaviour can be achieved via the `manageHook`, which runs
945when windows are created.  There are several functions to help you match
946on a certain window in [XMonad.ManageHook].  For example, suppose we'd
947want to match on the class name of the application.  With the
948application open, open another terminal and invoke the `xprop` command.
949Then click on the application that you would like to know the properties
950of.  In our case you should see (among other things)
951
952``` shell
953WM_CLASS(STRING) = "gimp", "Gimp"
954```
955
956The second string in `WM_CLASS` is the class name, which we can access
957with `className` from [XMonad.ManageHook].  The first one is usually
958called the instance name and is matched-on via `appName` from the same
959module.
960
961Let's use the class name for now.  We can tell all windows with that
962class name to float by defining the following manageHook:
963
964``` haskell
965myManageHook = (className =? "Gimp" --> doFloat)
966```
967
968Say we also want to float all dialogs.  This is easy with the `isDialog`
969function from [XMonad.Hooks.ManageHelpers] (which you should import) and
970a little modification to the `myManageHook` function:
971
972``` haskell
973myManageHook :: ManageHook
974myManageHook = composeAll
975    [ className =? "Gimp" --> doFloat
976    , isDialog            --> doFloat
977    ]
978```
979
980Now we just need to tell xmonad to actually use our manageHook.  This is
981as easy as overriding the `manageHook` field in `myConfig`.  You can do
982it like this:
983
984``` haskell
985myConfig = def
986    { modMask    = mod4Mask      -- Rebind Mod to the Super key
987    , layoutHook = myLayout      -- Use custom layouts
988    , manageHook = myManageHook  -- Match on certain windows
989    }
990  `additionalKeysP`
991    [ ("M-S-z", spawn "xscreensaver-command -lock")
992    , ("M-S-=", unGrab *> spawn "scrot -s"        )
993    , ("M-]"  , spawn "firefox"                   )
994    ]
995```
996
997## The Whole Thing
998
999The full `~/.config/xmonad/xmonad.hs`, in all its glory, now looks like
1000this:
1001
1002``` haskell
1003import XMonad
1004
1005import XMonad.Hooks.DynamicLog
1006import XMonad.Hooks.ManageDocks
1007import XMonad.Hooks.ManageHelpers
1008import XMonad.Hooks.StatusBar
1009import XMonad.Hooks.StatusBar.PP
1010
1011import XMonad.Util.EZConfig
1012import XMonad.Util.Loggers
1013import XMonad.Util.Ungrab
1014
1015import XMonad.Layout.Magnifier
1016import XMonad.Layout.ThreeColumns
1017
1018import XMonad.Hooks.EwmhDesktops
1019
1020
1021main :: IO ()
1022main = xmonad
1023     . ewmhFullscreen
1024     . ewmh
1025     . withEasySB (statusBarProp "xmobar" (pure myXmobarPP)) defToggleStrutsKey
1026     $ myConfig
1027
1028myConfig = def
1029    { modMask    = mod4Mask      -- Rebind Mod to the Super key
1030    , layoutHook = myLayout      -- Use custom layouts
1031    , manageHook = myManageHook  -- Match on certain windows
1032    }
1033  `additionalKeysP`
1034    [ ("M-S-z", spawn "xscreensaver-command -lock")
1035    , ("M-S-=", unGrab *> spawn "scrot -s"        )
1036    , ("M-]"  , spawn "firefox"                   )
1037    ]
1038
1039myManageHook :: ManageHook
1040myManageHook = composeAll
1041    [ className =? "Gimp" --> doFloat
1042    , isDialog            --> doFloat
1043    ]
1044
1045myLayout = tiled ||| Mirror tiled ||| Full ||| threeCol
1046  where
1047    threeCol = magnifiercz' 1.3 $ ThreeColMid nmaster delta ratio
1048    tiled    = Tall nmaster delta ratio
1049    nmaster  = 1      -- Default number of windows in the master pane
1050    ratio    = 1/2    -- Default proportion of screen occupied by master pane
1051    delta    = 3/100  -- Percent of screen to increment by when resizing panes
1052
1053myXmobarPP :: PP
1054myXmobarPP = def
1055    { ppSep             = magenta " • "
1056    , ppTitleSanitize   = xmobarStrip
1057    , ppCurrent         = wrap " " "" . xmobarBorder "Top" "#8be9fd" 2
1058    , ppHidden          = white . wrap " " ""
1059    , ppHiddenNoWindows = lowWhite . wrap " " ""
1060    , ppUrgent          = red . wrap (yellow "!") (yellow "!")
1061    , ppOrder           = \[ws, l, _, wins] -> [ws, l, wins]
1062    , ppExtras          = [logTitles formatFocused formatUnfocused]
1063    }
1064  where
1065    formatFocused   = wrap (white    "[") (white    "]") . magenta . ppWindow
1066    formatUnfocused = wrap (lowWhite "[") (lowWhite "]") . blue    . ppWindow
1067
1068    -- | Windows should have *some* title, which should not not exceed a
1069    -- sane length.
1070    ppWindow :: String -> String
1071    ppWindow = xmobarRaw . (\w -> if null w then "untitled" else w) . shorten 30
1072
1073    blue, lowWhite, magenta, red, white, yellow :: String -> String
1074    magenta  = xmobarColor "#ff79c6" ""
1075    blue     = xmobarColor "#bd93f9" ""
1076    white    = xmobarColor "#f8f8f2" ""
1077    yellow   = xmobarColor "#f1fa8c" ""
1078    red      = xmobarColor "#ff5555" ""
1079    lowWhite = xmobarColor "#bbbbbb" ""
1080```
1081
1082## Further Customizations
1083
1084The following section mostly consists of extra bits—feel free to skip
1085this and jump directly to [Get in Touch](#get-in-touch).  We've covered
1086a lot of ground here and sometimes it's really better to let things
1087settle a bit before going further; much more so if you're happy with how
1088things are looking and feeling right now!
1089
1090### Beautifying Xmobar
1091
1092A usual starting point for beautifying xmobar is either to use `xpm`
1093icons, or a font like `font-awesome` to add icons to the rest of the
1094text.  We will show you how to do the latter.  Xmobar, thankfully, makes
1095this very easy; just put a font down under `additionalFonts` and wrap
1096your Icons with `<fn>` tags and the respective index of the font
1097(starting from `1`).  As an example, consider how we would extend our
1098configuration above with this new functionality:
1099
1100``` haskell
1101Config { overrideRedirect = False
1102       , font             = "xft:iosevka-9"
1103       , additionalFonts  = ["xft:FontAwesome-9"]
1104       ...
1105       , Run Battery
1106           [ ...
1107           , "--lows"   , "<fn=1>\62020</fn>  "
1108           , "--mediums", "<fn=1>\62018</fn>  "
1109           , "--highs"  , "<fn=1>\62016</fn>  "
1110             ...
1111           ]
1112       ...
1113       }
1114```
1115
1116For an explanation of the battery commands used above, see xmobars
1117[battery] documentation.
1118
1119You can also specify workspaces in the same way and feed them to xmobar
1120via the property (e.g. have `"<fn=1>\xf120</fn>"` as one of your
1121workspace names).
1122
1123As an example how this would look like in a real configuration, you can
1124look at [Liskin's], [slotThe's], or [TheMC47's] xmobar configuration.
1125Do note that the last two are Haskell-based and thus may be a little
1126hard to understand for newcomers.
1127
1128[Liskin's]: https://github.com/liskin/dotfiles/blob/home/.xmobarrc
1129[TheMC47's]: https://github.com/TheMC47/dotfiles/tree/master/xmobar/xmobarrc
1130[slotThe's]: https://gitlab.com/slotThe/dotfiles/-/blob/master/xmobar/.config/xmobarrc/src/xmobarrc.hs
1131
1132### Renaming Layouts
1133
1134`Magnifier NoMaster ThreeCol` is quite a mouthful to show in your bar,
1135right?  Thankfully there is the nifty [XMonad.Layout.Renamed], which
1136makes renaming layouts easy!  We will focus on the `Replace` constructor
1137here, as a lot of people will find that that's all they need.  To use it
1138we again follow the documentation (try it yourself!)—import the module
1139and then change `myLayout` like this:
1140
1141``` haskell
1142myLayout = tiled ||| Mirror tiled ||| Full ||| threeCol
1143  where
1144    threeCol
1145        = renamed [Replace "ThreeCol"]
1146        $ magnifiercz' 1.3
1147        $ ThreeColMid nmaster delta ratio
1148    tiled   = Tall nmaster delta ratio
1149    nmaster = 1      -- Default number of windows in the master pane
1150    ratio   = 1/2    -- Default proportion of screen occupied by master pane
1151    delta   = 3/100  -- Percent of screen to increment by when resizing panes
1152```
1153
1154The new line `renamed [Replace "ThreeCol"]` tells the layout to throw
1155its current name away and use `ThreeCol` instead.  After reloading
1156xmonad, you should now see this shorter name in your bar.  The line
1157breaks here are just cosmetic, by the way; if you want you can write
1158everything in one line:
1159
1160``` haskell
1161threeCol = renamed [Replace "ThreeCol"] $ magnifiercz' 1.3 $ ThreeColMid nmaster delta ratio
1162```
1163
1164## Get in Touch
1165
1166The `irc.libera.chat/#xmonad` channel is very friendly and helpful.  It
1167is possible that people will not immediately answer—we do have lives as
1168well, after all :).  Eventually though, people will usually chime in if
1169they have something helpful to say; sometimes this takes 10 minutes,
1170other times it may well take 10 hours.  If you don't have an IRC client
1171ready to go, the easiest way to join is via [webchat]—just jot down a
1172username and you should be good to go!  There is a [log] of the channel
1173available, in case you do have to disconnect at some point, so don't
1174worry about missing any messages.
1175
1176If you're not a fan of real-time interactions, you can also post to the
1177[xmonad mailing list] or the [xmonad subreddit].
1178
1179## Trouble?
1180
1181Check `~/.xsession-errors` or your distribution's equivalent first.  If
1182you're using a distribution that does not log into a file automatically,
1183you will have to set this up manually.  For example, you could put
1184something like
1185
1186``` shell
1187if [[ ! $DISPLAY ]]; then
1188  exec startx >& ~/.xsession-errors
1189fi
1190```
1191
1192into your `~/.profile` file to explicitly log everything into
1193`~/.xsession-errors`.
1194
1195If you can't figure out what's wrong, don't hesitate to
1196[get in touch](#get-in-touch)!
1197
1198## Closing Thoughts
1199
1200That was quite a ride!  Don't worry if you didn't understand everything
1201perfectly, these things take time.  You can re-read parts of this guide
1202as often as you need to and—with the risk of sounding like a broken
1203record—if you can't figure something out really do not be afraid to
1204[get in touch](#get-in-touch).
1205
1206If you want to see a few more complicated examples of other peoples
1207xmonad configurations, look no further!  Below are (in alphabetical
1208order) the configurations of a few of xmonad's maintainers.  Just keep
1209in mind that these setups are very customized and perhaps a little bit
1210hard to replicate (some may rely on features only available in personal
1211forks or git), may or may not be documented, and most aren't very pretty
1212either :)
1213
1214  - [byorgey](https://github.com/byorgey/dotfiles)
1215  - [geekosaur](https://github.com/geekosaur/xmonad.hs/tree/pyanfar)
1216  - [liskin](https://github.com/liskin/dotfiles/tree/home/.xmonad)
1217  - [psibi](https://github.com/psibi/dotfiles/tree/master/xmonad)
1218  - [slotThe](https://gitlab.com/slotThe/dotfiles/-/tree/master/xmonad/.config/xmonad)
1219  - [TheMC47](https://github.com/TheMC47/dotfiles/tree/master/xmonad/.xmonad)
1220
1221
1222[log]: https://ircbrowse.tomsmeding.com/browse/lcxmonad
1223[EWMH]: https://specifications.freedesktop.org/wm-spec/wm-spec-1.3.html
1224[ICCCM]: https://tronche.com/gui/x/icccm/
1225[webchat]: https://kiwiirc.com/nextclient/irc.libera.chat/?#xmonad
1226[about xmonad]: https://xmonad.org/about.html
1227[shell variable]: https://www.shellscript.sh/variables1.html
1228[xmonad-testing]: https://github.com/xmonad/xmonad-testing
1229[xmonad subreddit]: https://old.reddit.com/r/xmonad/
1230[xmonad guided tour]: https://xmonad.org/tour.html
1231[xmonad mailing list]: https://mail.haskell.org/mailman/listinfo/xmonad
1232[xmonad's GitHub page]: https://github.com/xmonad/xmonad
1233[trayer-padding-icon.sh]: https://github.com/jaor/xmobar/issues/239#issuecomment-233206552
1234[xmonad-contrib documentation]: https://hackage.haskell.org/package/xmonad-contrib
1235[GNU Image Manipulation Program]: https://www.gimp.org/
1236[Basic Desktop Environment Integration]: https://wiki.haskell.org/Xmonad/Basic_Desktop_Environment_Integration
1237
1238[Hacks]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Util-Hacks.html
1239[PP record]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Hooks-DynamicLog.html#t:PP
1240[INSTALL.md]: INSTALL.md
1241[XMonad.Config]: https://github.com/xmonad/xmonad/blob/master/src/XMonad/Config.hs
1242[XMonad.ManageHook]: https://xmonad.github.io/xmonad-docs/xmonad/XMonad-ManageHook.html
1243[XMonad.Util.Loggers]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Util-Loggers.html
1244[XMonad.Util.EZConfig]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Util-EZConfig.html
1245[XMonad.Layout.Renamed]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Layout-Renamed.html
1246[XMonad.Layout.Magnifier]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Layout-Magnifier.html
1247[XMonad.Doc.Contributing]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Doc-Configuring.html
1248[XMonad.Hooks.EwmhDesktops]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Hooks-EwmhDesktops.html
1249[XMonad.Layout.ThreeColumns]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Layout-ThreeColumns.html
1250[XMonad.Hooks.ManageHelpers]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Hooks-ManageHelpers.html
1251[XMonad.Util.ClickableWorkspaces]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Util-ClickableWorkspaces.html
1252
1253[xmobar]: https://xmobar.org/
1254[battery]: https://github.com/jaor/xmobar/blob/master/doc/plugins.org#batteryp-dirs-args-refreshrate
1255[xmobar.hs]: https://github.com/jaor/xmobar/blob/master/examples/xmobar.hs
1256[Wikipedia page]: https://en.wikipedia.org/wiki/ICAO_airport_code#Prefixes
1257[quick-start.org]: https://github.com/jaor/xmobar/blob/master/doc/quick-start.org#configuration-options
1258[jao's xmobar.hs]: https://codeberg.org/jao/xmobar-config
1259[weather monitor]: https://github.com/jaor/xmobar/blob/master/doc/plugins.org#weather-monitors
1260[xmobar home page]: https://xmobar.org/
1261[xmobar's `Installation` section]: https://github.com/jaor/xmobar#installation
1262
1263[Haskell]: https://www.haskell.org/
1264[trayer-srg]: https://github.com/sargon/trayer-srg
1265[record update]: http://learnyouahaskell.com/making-our-own-types-and-typeclasses
1266[lambda abstraction]: https://wiki.haskell.org/Lambda_abstraction
1267[GNU Emacs conventions]: https://www.gnu.org/software/emacs/manual/html_node/elisp/Key-Sequences.html#Key-Sequences
1268