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