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