tonybtw.com

tonybtw.com

https://git.tonybtw.com/tonybtw.com.git git://git.tonybtw.com/tonybtw.com.git
23,176 bytes raw
1
#+TITLE: The Last Honest X11 Window Manager: Xmonad
2
#+AUTHOR: Tony, btw
3
#+date: 2025-11-26
4
#+HUGO_TITLE: The Last Honest X11 Window Manager: Xmonad
5
#+HUGO_FRONT_MATTER_FORMAT: yaml
6
#+HUGO_CUSTOM_FRONT_MATTER: :image "/img/xmonad.png" :showTableOfContents true
7
#+HUGO_BASE_DIR: ~/repos/tonybtw.com
8
#+HUGO_SECTION: tutorial/xmonad
9
#+EXPORT_FILE_NAME: index
10
#+OPTIONS: toc:nil broken-links:mark
11
#+HUGO_AUTO_SET_HEADLINE_SECTION: nil
12
#+DESCRIPTION: This is a quick and painless guide on how to install and configure XMonad, written in Haskell. An honest X11 window manager written in an honest functional programming language.
13
14
#+begin_quote
15
In an era of dishonesty, there is one X11 window manager that continues to flourish.
16
The final frontier of honesty.
17
The last pillar of hope in an unsettling ecosystem where windows are no longer managed by human users… but by committees, protocols, and corporations.
18
19
For nearly twenty years, this window manager has been battle-tested.
20
It has shined through the smoke of AUR DDoS attacks…
21
endless attempts to force Wayland adoption by Red Hat and Canonical…
22
and a relentless wave of dishonest “modern UX improvements” designed to undermine your ability to simply move a window one pixel to the left.
23
24
At the end of the day, that’s all an X11 window manager was ever supposed to be.
25
26
And XMonad is—
27
as YouTux might put it—
28
the last honest window manager.
29
#+end_quote
30
31
* Intro
32
What's up guys, my name is Tony, and today I'm gonna give you a quick and painless guide on installing and configuring Xmonad.
33
34
Let's jump into the installation.
35
36
* Install Dependencies for Xmonad
37
38
Today, we're going to be using NixOS for the installation and configuration, but Xmonad is available on virtually every package manager in existence, (at least all the honest ones.) If you are using a legacy distro such as Arch, Gentoo, or Debian, a link will be provided below the subscribe button for a written guide to accompany this video.
39
40
For nixos, we just need to do 3 things.
41
42
In our configuration.nix:
43
#+begin_src nix
44
services = {
45
  picom.enable = true;
46
  displayManager = {
47
    ly.enable = true;
48
  };
49
  xserver = {
50
    enable = true;
51
    autoRepeatDelay = 200;
52
    autoRepeatInterval = 35;
53
    windowManager = {
54
      xmonad = {
55
        enable = true;
56
        enableContribAndExtras = true;
57
        extraPackages = hpkgs: [
58
          hpkgs.xmonad
59
          hpkgs.xmonad-extras
60
          hpkgs.xmonad-contrib
61
        ];
62
      };
63
    };
64
    displayManager.sessionCommands = ''
65
      xwallpaper --zoom ~/walls/wall1.png
66
    '';
67
  };
68
};
69
#+end_src
70
71
And then in our home.nix:
72
#+begin_src nix
73
home.packages = with pkgs; [
74
  haskell-language-server
75
  xmobar
76
];
77
#+end_src
78
79
80
** Requirements for Xmonad
81
If you're on Arch, btw, here are the core dependencies:
82
- xorg-server
83
- xorg-xinit
84
- xmonad
85
- xmonad-contrib
86
- ghc (Glasgow Haskell Compiler)
87
- xmobar
88
- dmenu (or rofi if you prefer)
89
- picom (for compositing)
90
91
** Extra stuff for my setup today:
92
- alacritty (terminal emulator)
93
- dmenu/rofi (application launcher)
94
- feh or xwallpaper (for wallpapers, we're using xwallpaper in our nix config)
95
- firefox (web browser)
96
- ttf-jetbrains-mono-nerd (font)
97
- scrot or maim (for screenshots)
98
- picom (compositor for shadows and transparency)
99
100
So let's install these with pacman -Sy
101
#+begin_src sh
102
sudo pacman -Sy xorg-server xorg-xinit xmonad xmonad-contrib ghc xmobar dmenu alacritty xwallpaper firefox ttf-jetbrains-mono-nerd scrot picom
103
#+end_src
104
105
Alright, let's configure xmonad and get it up and running.
106
107
* Configure Xmonad
108
After running nixos-rebuild switch (or installing via pacman if you're on Arch), we need to create our xmonad.hs config file.
109
110
Let's create the directory and config:
111
112
#+begin_src sh
113
mkdir -p ~/.config/xmonad
114
vim ~/.config/xmonad/xmonad.hs
115
#+end_src
116
117
** Starting from Zero
118
119
Let's start with the absolute minimum xmonad configuration. This is a complete, working config that does nothing more than launch xmonad with default settings:
120
121
#+begin_src haskell
122
import XMonad
123
124
main = xmonad def
125
#+end_src
126
127
That's it. Three lines. This will give you a tiling window manager with default keybindings. Alt+Shift+Enter opens a terminal, Alt+p for dmenu, etc.
128
129
Let's compile and test it:
130
131
#+begin_src sh
132
xmonad --recompile
133
#+end_src
134
135
Now let's build it up piece by piece.
136
137
** Adding Custom Terminal and Mod Key
138
139
Most people want to use the Super key (Windows key) instead of Alt, and specify their preferred terminal:
140
141
#+begin_src haskell
142
import XMonad
143
144
main = xmonad def
145
    { modMask = mod4Mask      -- Use Super instead of Alt
146
    , terminal = "alacritty"  -- Use alacritty as terminal
147
    }
148
#+end_src
149
150
** Adding Basic Keybindings
151
152
Let's add some custom keybindings using EZConfig for a more readable syntax:
153
154
#+begin_src haskell
155
import XMonad
156
import XMonad.Util.EZConfig (additionalKeysP)
157
158
myKeys =
159
    [ ("M-<Return>", spawn "alacritty")
160
    , ("M-d", spawn "dmenu_run")
161
    , ("M-q", kill)
162
    ]
163
164
main = xmonad $ def
165
    { modMask = mod4Mask
166
    , terminal = "alacritty"
167
    }
168
    `additionalKeysP` myKeys
169
#+end_src
170
171
** Adding Colors and Borders
172
173
Let's add some visual customization:
174
175
#+begin_src haskell
176
import XMonad
177
import XMonad.Util.EZConfig (additionalKeysP)
178
179
myKeys =
180
    [ ("M-<Return>", spawn "alacritty")
181
    , ("M-d", spawn "dmenu_run")
182
    , ("M-q", kill)
183
    ]
184
185
main = xmonad $ def
186
    { modMask = mod4Mask
187
    , terminal = "alacritty"
188
    , borderWidth = 2
189
    , normalBorderColor = "#444b6a"
190
    , focusedBorderColor = "#ad8ee6"
191
    }
192
    `additionalKeysP` myKeys
193
#+end_src
194
195
** Adding Gaps and Spacing
196
197
Now let's add some breathing room with gaps between windows:
198
199
#+begin_src haskell
200
import XMonad
201
import XMonad.Util.EZConfig (additionalKeysP)
202
import XMonad.Layout.Spacing
203
204
myLayoutHook = spacingWithEdge 3 $ layoutHook def
205
206
myKeys =
207
    [ ("M-<Return>", spawn "alacritty")
208
    , ("M-d", spawn "dmenu_run")
209
    , ("M-q", kill)
210
    ]
211
212
main = xmonad $ def
213
    { modMask = mod4Mask
214
    , terminal = "alacritty"
215
    , borderWidth = 2
216
    , normalBorderColor = "#444b6a"
217
    , focusedBorderColor = "#ad8ee6"
218
    , layoutHook = myLayoutHook
219
    }
220
    `additionalKeysP` myKeys
221
#+end_src
222
223
** Adding XMobar Status Bar
224
225
Now let's integrate xmobar to show workspaces and window information:
226
227
#+begin_src haskell
228
import XMonad
229
import XMonad.Util.EZConfig (additionalKeysP)
230
import XMonad.Layout.Spacing
231
import XMonad.Hooks.DynamicLog
232
import XMonad.Hooks.StatusBar
233
import XMonad.Hooks.StatusBar.PP
234
import XMonad.Hooks.ManageDocks
235
236
myLayoutHook = avoidStruts $ spacingWithEdge 3 $ layoutHook def
237
238
myXmobarPP :: PP
239
myXmobarPP = def
240
    { ppCurrent = xmobarColor "#0db9d7" ""
241
    , ppHidden = xmobarColor "#a9b1d6" ""
242
    , ppHiddenNoWindows = xmobarColor "#444b6a" ""
243
    }
244
245
myStatusBar = statusBarProp "xmobar" (pure myXmobarPP)
246
247
myKeys =
248
    [ ("M-<Return>", spawn "alacritty")
249
    , ("M-d", spawn "dmenu_run")
250
    , ("M-q", kill)
251
    , ("M-S-r", spawn "xmonad --recompile && xmonad --restart")
252
    ]
253
254
main = xmonad $ withEasySB myStatusBar defToggleStrutsKey $ def
255
    { modMask = mod4Mask
256
    , terminal = "alacritty"
257
    , borderWidth = 2
258
    , normalBorderColor = "#444b6a"
259
    , focusedBorderColor = "#ad8ee6"
260
    , layoutHook = myLayoutHook
261
    , manageHook = manageDocks
262
    }
263
    `additionalKeysP` myKeys
264
#+end_src
265
266
Now we have a pretty solid foundation! But what does a full-featured config look like?
267
268
** My Full Xmonad Configuration
269
270
Here's my actual daily driver xmonad configuration with TokyoNight colors, custom layouts, workspace rules, gap controls, and all my keybindings:
271
272
#+begin_src haskell
273
import Data.Map qualified as M
274
import XMonad
275
import XMonad.Hooks.DynamicLog
276
import XMonad.Hooks.EwmhDesktops
277
import XMonad.Hooks.ManageDocks
278
import XMonad.Hooks.StatusBar
279
import XMonad.Hooks.StatusBar.PP
280
import XMonad.Hooks.InsertPosition
281
import XMonad.Layout.NoBorders
282
import XMonad.Layout.ResizableTile
283
import XMonad.Layout.Spacing
284
import XMonad.Layout.Spiral
285
import XMonad.Layout.Renamed
286
import XMonad.StackSet qualified as W
287
import XMonad.Util.EZConfig (additionalKeysP)
288
import XMonad.Util.Loggers
289
import XMonad.Util.SpawnOnce
290
291
-- TokyoNight Colors
292
colorBg = "#1a1b26" -- background
293
colorFg = "#a9b1d6" -- foreground
294
colorBlk = "#32344a" -- black
295
colorRed = "#f7768e" -- red
296
colorGrn = "#9ece6a" -- green
297
colorYlw = "#e0af68" -- yellow
298
colorBlu = "#7aa2f7" -- blue
299
colorMag = "#ad8ee6" -- magenta
300
colorCyn = "#0db9d7" -- cyan
301
colorBrBlk = "#444b6a" -- bright black
302
303
-- Appearance
304
myBorderWidth = 2
305
306
myNormalBorderColor = colorBrBlk
307
308
myFocusedBorderColor = colorMag
309
310
-- Gaps (matching dwm: 3px all around)
311
mySpacing = spacingWithEdge 3
312
313
-- Workspaces
314
myWorkspaces = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
315
316
-- Mod key (Super/Windows key)
317
myModMask = mod4Mask
318
319
-- Terminal
320
myTerminal = "st"
321
322
-- Layouts
323
myLayoutHook =
324
  avoidStruts $
325
        renamed [Replace "Tall"] (mySpacing tall)
326
        ||| renamed [Replace "Wide"] (mySpacing (Mirror tall))
327
        ||| renamed [Replace "Full"] (mySpacing Full)
328
        ||| renamed [Replace "Spiral"] (mySpacing (spiral (6 / 7)))
329
  where
330
    tall = ResizableTall 1 (3 / 100) (11 / 20) []
331
332
-- Window rules (matching dwm config)
333
myManageHook =
334
  composeAll
335
    [ className =? "Gimp" --> doFloat
336
    , className =? "Brave-browser" --> doShift "2"
337
    , className =? "firefox" --> doShift "3"
338
    , className =? "Slack" --> doShift "4"
339
    , className =? "kdenlive" --> doShift "8"
340
    ]
341
    <+> insertPosition Below Newer
342
343
-- Key bindings (matching dwm as closely as possible)
344
myKeys =
345
  -- Launch applications
346
  [ ("M-<Return>", spawn myTerminal)
347
  , ("M-d", spawn "rofi -show drun -theme ~/.config/rofi/config.rasi")
348
  , ("M-r", spawn "dmenu_run")
349
  , ("M-l", spawn "slock")
350
  , ("C-<Print>", spawn "maim -s | xclip -selection clipboard -t image/png")
351
  , -- Window management
352
    ("M-q", kill)
353
  , ("M-j", windows W.focusDown)
354
  , ("M-k", windows W.focusUp)
355
  , ("M-<Tab>", windows W.focusDown)
356
  , -- Master area
357
    ("M-h", sendMessage Expand)
358
  , ("M-g", sendMessage Shrink)
359
  , ("M-i", sendMessage (IncMasterN 1))
360
  , ("M-p", sendMessage (IncMasterN (-1)))
361
  , -- Layout switching
362
    ("M-t", sendMessage $ JumpToLayout "Tall")
363
  , ("M-f", sendMessage $ JumpToLayout "Full")
364
  , ("M-c", sendMessage $ JumpToLayout "Spiral")
365
  , ("M-S-<Return>", sendMessage NextLayout)
366
  , ("M-n", sendMessage NextLayout)
367
  , -- Floating
368
    ("M-S-<Space>", withFocused toggleFloat)
369
  , -- Gaps (z to increase, x to decrease, a to toggle)
370
    ("M-z", incWindowSpacing 3)
371
  , ("M-x", decWindowSpacing 3)
372
  , ("M-a", toggleWindowSpacingEnabled >> toggleScreenSpacingEnabled)
373
  , ("M-S-a", setWindowSpacing (Border 3 3 3 3) >> setScreenSpacing (Border 3 3 3 3))
374
  , -- Quit/Restart
375
    ("M-S-r", spawn "xmonad --recompile && xmonad --restart")
376
  , -- Keychords for tag navigation (Mod+Space then number)
377
    ("M-<Space> 1", windows $ W.greedyView "1")
378
  , ("M-<Space> 2", windows $ W.greedyView "2")
379
  , ("M-<Space> 3", windows $ W.greedyView "3")
380
  , ("M-<Space> 4", windows $ W.greedyView "4")
381
  , ("M-<Space> 5", windows $ W.greedyView "5")
382
  , ("M-<Space> 6", windows $ W.greedyView "6")
383
  , ("M-<Space> 7", windows $ W.greedyView "7")
384
  , ("M-<Space> 8", windows $ W.greedyView "8")
385
  , ("M-<Space> 9", windows $ W.greedyView "9")
386
  , ("M-<Space> f", spawn "firefox")
387
  , -- Volume controls
388
    ("<XF86AudioRaiseVolume>", spawn "pactl set-sink-volume @DEFAULT_SINK@ +3%")
389
  , ("<XF86AudioLowerVolume>", spawn "pactl set-sink-volume @DEFAULT_SINK@ -3%")
390
  , ("<XF86AudioMute>", spawn "pactl set-sink-mute @DEFAULT_SINK@ toggle")
391
  ]
392
    ++
393
    -- Standard TAGKEYS behavior (Mod+# to view, Mod+Shift+# to move)
394
    [ (mask ++ "M-" ++ [key], windows $ action tag)
395
    | (tag, key) <- zip myWorkspaces "123456789"
396
    , (action, mask) <- [(W.greedyView, ""), (W.shift, "S-")]
397
    ]
398
399
-- Helper function for toggling float
400
toggleFloat w =
401
  windows
402
    ( \s ->
403
        if M.member w (W.floating s)
404
          then W.sink w s
405
          else W.float w (W.RationalRect 0.15 0.15 0.7 0.7) s
406
    )
407
408
-- XMobar PP (Pretty Printer) configuration
409
myXmobarPP :: PP
410
myXmobarPP =
411
  def
412
    { ppSep = xmobarColor colorBrBlk "" " │ "
413
    , ppTitleSanitize = xmobarStrip
414
    , ppCurrent = xmobarColor colorCyn ""
415
    , ppHidden = xmobarColor colorFg ""
416
    , ppHiddenNoWindows = xmobarColor colorBrBlk ""
417
    , ppUrgent = xmobarColor colorRed colorYlw
418
    , ppOrder = \[ws, l, _, wins] -> [ws, l, wins]
419
    , ppExtras = [logTitles formatFocused formatUnfocused]
420
    }
421
  where
422
    formatFocused = wrap (xmobarColor colorCyn "" "[") (xmobarColor colorCyn "" "]") . xmobarColor colorFg "" . ppWindow
423
    formatUnfocused = wrap (xmobarColor colorBrBlk "" "[") (xmobarColor colorBrBlk "" "]") . xmobarColor colorBrBlk "" . ppWindow
424
    ppWindow :: String -> String
425
    ppWindow = xmobarRaw . (\w -> if null w then "untitled" else w) . shorten 30
426
427
-- Main configuration
428
myConfig =
429
  def
430
    { modMask = myModMask
431
    , terminal = myTerminal
432
    , workspaces = myWorkspaces
433
    , borderWidth = myBorderWidth
434
    , normalBorderColor = myNormalBorderColor
435
    , focusedBorderColor = myFocusedBorderColor
436
    , layoutHook = myLayoutHook
437
    , manageHook = myManageHook <+> manageDocks
438
    , startupHook = spawnOnce "xsetroot -cursor_name left_ptr"
439
    }
440
    `additionalKeysP` myKeys
441
442
-- XMobar status bar configuration
443
myStatusBar = statusBarProp "xmobar ~/.config/xmobar/xmobarrc" (pure myXmobarPP)
444
445
main :: IO ()
446
main = xmonad . ewmhFullscreen . ewmh . withEasySB myStatusBar defToggleStrutsKey $ myConfig
447
#+end_src
448
449
This config includes TokyoNight colors, custom layouts (Tall, Wide, Full, Spiral), gap controls, workspace rules for automatically moving apps to specific workspaces, and tons of keybindings.
450
451
Now let's compile and test it:
452
453
#+begin_src sh
454
xmonad --recompile
455
#+end_src
456
457
If you're on NixOS, you can just rebuild and then either log out and select xmonad from your display manager, or if you want to test it immediately:
458
459
#+begin_src sh
460
nixos-rebuild switch
461
startx
462
#+end_src
463
464
Alright, we're in xmonad now. As you can see, we have a clean slate with xmobar at the top. Super minimal, super honest.
465
466
* Xmobar Configuration
467
We already have xmobar launching from our xmonad config, but let's customize it. Let's create an xmobar config:
468
469
#+begin_src sh
470
mkdir -p ~/.config/xmobar
471
vim ~/.config/xmobar/xmobarrc
472
#+end_src
473
474
** Starting with a Minimal Xmobar Config
475
476
Here's the absolute minimal xmobar configuration:
477
478
#+begin_src haskell
479
Config {
480
     font     = "xft:monospace-10"
481
   , bgColor  = "#000000"
482
   , fgColor  = "#ffffff"
483
   , position = Top
484
   , commands = [ Run XMonadLog ]
485
   , template = "%XMonadLog%"
486
   }
487
#+end_src
488
489
This will show your workspaces and focused window. Nothing fancy, but it works.
490
491
** Adding System Information
492
493
Let's add some system monitors like CPU, memory, and the date:
494
495
#+begin_src haskell
496
Config {
497
     font     = "xft:monospace-10"
498
   , bgColor  = "#000000"
499
   , fgColor  = "#ffffff"
500
   , position = Top
501
   , sepChar  = "%"
502
   , alignSep = "}{"
503
   , template = "%XMonadLog% }{ CPU: %cpu% | MEM: %memory% | %date%"
504
   , commands =
505
        [ Run XMonadLog
506
        , Run Cpu ["-t", "<total>%"] 10
507
        , Run Memory ["-t", "<usedratio>%"] 10
508
        , Run Date "%a %b %_d %H:%M" "date" 10
509
        ]
510
   }
511
#+end_src
512
513
Now we have workspace info on the left, and system stats on the right.
514
515
** Adding Colors and Better Formatting
516
517
Let's make it prettier with some color coding:
518
519
#+begin_src haskell
520
Config {
521
     font     = "xft:JetBrainsMono Nerd Font-12"
522
   , bgColor  = "#1a1b26"
523
   , fgColor  = "#a9b1d6"
524
   , position = TopSize C 100 30
525
   , sepChar  = "%"
526
   , alignSep = "}{"
527
   , template = " %XMonadLog% }{ <fc=#7aa2f7>CPU:</fc> %cpu% | <fc=#7aa2f7>MEM:</fc> %memory% | <fc=#ad8ee6>%date%</fc> "
528
   , commands =
529
        [ Run XMonadLog
530
        , Run Cpu
531
            [ "-t", "<total>%"
532
            , "-L", "30"
533
            , "-H", "70"
534
            , "-l", "#9ece6a"
535
            , "-n", "#e0af68"
536
            , "-h", "#f7768e"
537
            ] 10
538
        , Run Memory
539
            [ "-t", "<usedratio>%"
540
            , "-L", "30"
541
            , "-H", "70"
542
            , "-l", "#9ece6a"
543
            , "-n", "#e0af68"
544
            , "-h", "#f7768e"
545
            ] 10
546
        , Run Date "<fc=#ad8ee6>%a %b %_d %H:%M</fc>" "date" 10
547
        ]
548
   }
549
#+end_src
550
551
Now we've got TokyoNight colors, with green for low usage, yellow for medium, and red for high.
552
553
** My Full Xmobar Configuration
554
555
Here's my actual daily driver xmobar config with borders, Nerd Font icons, battery monitoring, and full TokyoNight theming:
556
557
#+begin_src haskell
558
-- TokyoNight XMobar Config
559
Config {
560
   -- Appearance
561
     font            = "JetBrainsMono Nerd Font Mono Bold 16"
562
   , additionalFonts = [ "JetBrainsMono Nerd Font Mono 18" ]
563
   , bgColor         = "#1a1b26"
564
   , fgColor         = "#a9b1d6"
565
   , position        = TopSize C 100 35
566
   , border          = BottomB
567
   , borderColor     = "#444b6a"
568
   , borderWidth     = 2
569
570
   -- Layout
571
   , sepChar         = "%"
572
   , alignSep        = "}{"
573
   , template        = " %XMonadLog% }{ %cpu% <fc=#444b6a>│</fc> %memory% <fc=#444b6a>│</fc> %battery% <fc=#444b6a>│</fc> %date% "
574
575
   -- Plugins
576
   , commands        =
577
        [ Run XMonadLog
578
        , Run Cpu
579
            [ "-t", "<fc=#7aa2f7> CPU:</fc> <total>%"
580
            , "-L", "30"
581
            , "-H", "70"
582
            , "-l", "#9ece6a"
583
            , "-n", "#e0af68"
584
            , "-h", "#f7768e"
585
            ] 10
586
        , Run Memory
587
            [ "-t", "<fc=#7aa2f7>󰍛 MEM:</fc> <usedratio>%"
588
            , "-L", "30"
589
            , "-H", "70"
590
            , "-l", "#9ece6a"
591
            , "-n", "#e0af68"
592
            , "-h", "#f7768e"
593
            ] 10
594
        , Run Battery
595
            [ "-t", "<fc=#7aa2f7>󱐋 BAT:</fc> <acstatus>"
596
            , "-L", "20"
597
            , "-H", "80"
598
            , "-l", "#f7768e"
599
            , "-n", "#e0af68"
600
            , "-h", "#9ece6a"
601
            , "--"
602
            , "-o", "<left>% (<timeleft>)"
603
            , "-O", "<fc=#e0af68>Charging</fc> <left>%"
604
            , "-i", "<fc=#9ece6a>Charged</fc>"
605
            ] 50
606
        , Run Date "<fc=#ad8ee6> %a %b %_d %H:%M</fc>" "date" 10
607
        ]
608
   }
609
#+end_src
610
611
This config has Nerd Font icons, a bottom border, battery monitoring, and uses the full TokyoNight color palette with dynamic colors based on resource usage.
612
613
Now if we reload xmonad with Mod+Shift+r, we should see our customized xmobar. Sweet.
614
615
* Wallpaper
616
Good news - we already set up the wallpaper in our configuration.nix! The line we added earlier:
617
618
#+begin_src nix
619
displayManager.sessionCommands = ''
620
  xwallpaper --zoom ~/walls/wall1.png
621
'';
622
#+end_src
623
624
This will automatically set your wallpaper on startup. But we still need to grab a wallpaper. Let's open firefox with super+d, type firefox, and head over to wallhaven.cc to pick one out.
625
626
Let's grab this one and put it in ~/walls/wall1.png:
627
628
#+begin_src sh
629
mkdir -p ~/walls
630
# download your wallpaper and move it here
631
mv ~/Downloads/wallpaper.png ~/walls/wall1.png
632
#+end_src
633
634
If you want to test it without reloading X, you can run:
635
636
#+begin_src sh
637
xwallpaper --zoom ~/walls/wall1.png
638
#+end_src
639
640
And boom, there's our wallpaper.
641
642
* Screenshot Script
643
For screenshots, I'm using maim with xclip to copy directly to clipboard. In the xmonad config above, I already have it bound to Control+Print:
644
645
#+begin_src haskell
646
, ("C-<Print>", spawn "maim -s | xclip -selection clipboard -t image/png")
647
#+end_src
648
649
This lets you select an area with your mouse, and it copies the screenshot directly to your clipboard. Super convenient for pasting into Discord, Slack, or wherever.
650
651
If you want to save screenshots to a file instead, you can create a script:
652
653
#+begin_src sh
654
mkdir -p ~/.local/bin
655
vim ~/.local/bin/screenshot
656
#+end_src
657
658
Add this:
659
660
#+begin_src sh
661
#!/bin/sh
662
# Screenshot script using maim
663
maim -s ~/Pictures/screenshots/$(date +%Y-%m-%d_%H-%M-%S).png
664
#+end_src
665
666
Make it executable:
667
668
#+begin_src sh
669
chmod +x ~/.local/bin/screenshot
670
#+end_src
671
672
And you can add another keybind in your xmonad.hs if you want both options.
673
674
* Customization and Tweaks
675
So at this point, the world is really your oyster. Xmonad is written in Haskell, so if you know Haskell, you can literally do anything you want with this window manager. That's the beauty of it - your window manager IS your config file.
676
677
In my config, I've already set up a bunch of stuff:
678
679
The TokyoNight color scheme with that nice magenta focused border, 3 pixel gaps all around (matching my dwm setup), and I'm using st as my terminal because it's fast and minimal.
680
681
For layouts, I've got ResizableTall, Mirror ResizableTall, Full, and Spiral. You can jump between them with:
682
- Super+t for tiled
683
- Super+f or Super+m for fullscreen
684
- Super+c for spiral
685
- Super+Shift+Enter to cycle through all layouts
686
687
For gaps, I have some really nice keybinds:
688
- Super+a toggles gaps on/off
689
- Super+z increases gap size
690
- Super+x decreases gap size
691
- Super+Shift+a resets gaps back to 3 pixels
692
693
Window rules are set up so different apps automatically go to specific workspaces:
694
- Browsers (Chrome, Brave) go to workspace 2
695
- Firefox goes to workspace 3
696
- Slack goes to 4
697
- Discord goes to 5
698
- Kdenlive goes to 8
699
700
Here are my essential keybinds:
701
702
| Keybind | Action |
703
|---------|--------|
704
| Super+Enter | Launch terminal (st) |
705
| Super+d | Launch rofi application launcher |
706
| Super+r | Launch dmenu |
707
| Super+l | Lock screen with slock |
708
| Super+q | Close focused window |
709
| Super+j | Focus next window |
710
| Super+k | Focus previous window |
711
| Super+Tab | Focus next window |
712
| Super+h | Expand master area |
713
| Super+g | Shrink master area |
714
| Super+i | Increase number of windows in master |
715
| Super+p | Decrease number of windows in master |
716
| Super+t | Switch to Tall layout |
717
| Super+f | Switch to Full layout |
718
| Super+c | Switch to Spiral layout |
719
| Super+Shift+Enter | Cycle to next layout |
720
| Super+n | Cycle to next layout |
721
| Super+Shift+Space | Toggle floating for focused window |
722
| Super+z | Increase gap size |
723
| Super+x | Decrease gap size |
724
| Super+a | Toggle gaps on/off |
725
| Super+Shift+a | Reset gaps to default (3px) |
726
| Super+Shift+r | Recompile and restart xmonad |
727
| Super+Space [1-9] | Keychord: Jump to workspace 1-9 |
728
| Super+Space f | Keychord: Launch Firefox |
729
| Super+[1-9] | Switch to workspace 1-9 |
730
| Super+Shift+[1-9] | Move window to workspace 1-9 |
731
| Ctrl+Print | Screenshot (select area to clipboard) |
732
| XF86AudioRaiseVolume | Increase volume by 3% |
733
| XF86AudioLowerVolume | Decrease volume by 3% |
734
| XF86AudioMute | Toggle mute |
735
736
I've put together the full xmonad config above with all my customizations, the TokyoNight colors, and all my keybinds. If you want even more customization ideas, check out the xmonad documentation - it's honestly one of the best-documented window managers out there.
737
738
* Final Thoughts
739
740
You're now ready to use XMonad as an honest X11 window manager.
741
742
Thanks so much for checking out this tutorial. If you got value from it, and you want to find more tutorials like this, check out
743
my youtube channel here: [[https://youtube.com/@tony-btw][YouTube]], or my website here: [[https://www.tonybtw.com][tony,btw]]
744
745
You can support me here: [[https://ko-fi.com/tonybtw][kofi]]