~~ XMONAD ~~
** A TILING WM **
The debate about the best window manager for Linux/BSD operating systems is as fierce as the “Great Editor War.” Yet there are significantly more window managers than the most discussed floating window managers. When I first stumbled upon XMonad and the principle of tiling window managers five years ago, I had no idea what I was getting myself into. I was faced with the choice of installing i3 or XMonad. Since I couldn’t get i3 to work at the time (I don’t remember the exact reason), the next step was to install XMonad. This was the beginning of many hours of learning, understanding, and writing configuration files.
WHAT IS A TILING WM
Before we talk about XMonad, it might be necessary to explain what a tiling window manager actually is. There are countless window managers under Linux/BSD. It’s important to distinguish between window managers and desktop environments.
DESKTOP ENVIRONMENT (DE)
The desktop environment is a collection of tools and logic to provide a complete desktop. DEs include a window manager, panel/taskbar, file manager, notification services, theming engines, and usually a whole range of pre-installed tools, such as text editors and the like.
WINDOW MANAGER (WM)
The Window Manager, on the other hand, actually has only one (albeit very complex) task, namely managing the windows the user sees. Firstly, the WM takes care of window placement and display, i.e., where a new window appears, the layout and size of the window, and, of course, whether windows can overlap or not. A WM also creates the window decoration, i.e., window borders, title bars, close/minimize/maximize buttons, and, of course, things like curves on the windows and everything that is often modified to the extreme during theming. What the WM also regulates, however, is the user’s interaction with the windows. This includes things like the behavior of the windows on mouse clicks, drag & drop actions by the user, etc., and also the focus of the windows. What is often used, of course, are virtual desktops that you can switch between, for example, to group individual windows or simply to keep track of things.
Now for Tiling WM. The main difference between Tiling WM and Stacking WM (or Floating WM) is the arrangement of the windows. Decorations and panels or system bars work quite similarly. However, the arrangement of the windows is different. Tiling WM, regardless of which one we’re talking about, places all windows visibly on the workspace without overlapping. This way, the user sees all open windows simultaneously on the workspace, making the best possible use of the available space. Almost all Tiling WMs use this concept (if you ignore Niri). Tiling WMs also focus on complete keyboard usability. Stacked WMs require a lot of interaction with the mouse/trackpad/trackball to move, close, or resize windows. Of course, you can also configure your Stacking WM to use key combos for this task, but the basic arrangement of the windows simplifies the keyboard-only workflow enormously.
Although the functions of the various tiling WMs are relatively similar, they differ in other factors. For example, the complexity of the configuration, the language in which the WM was programmed, the resources required during operation, the layout types, the window arrangement, floating support, extensibility, and so on. This also determines which tiling WM will end up on your computer of choice. Since there are already several articles on i3, bspwm, herbstluftwm, and others, I’ll stick up for XMonad here.
XMONAD
As luck would have it, or rather my inability to install i3, I ended up with XMonad. XMonad is characterized by high extensibility, very low resource consumption, and extremely high stability. Where there is light, there is also shadow. XMonad and its configuration are written entirely in Haskell. Not necessarily the easiest language to understand and learn. Even after the five years I’ve been using XMonad, I’m not particularly familiar with Haskell. Fortunately, there are ready-made configurations that make getting started easier. Furthermore, XMonad is based on X.Org, and nothing can be said about its future viability yet. But we don’t need to start the X.Org versus Wayland debate here. So, back to XMonad. XMonad is available in almost all package sources of the various Linux distros and can be easily installed via terminal or under NixOS with the following config entry:
1 { ... }:
2
3 {
4 services.xserver = {
5 windowManager.xmonad = {
6 enable = true;
7 enableContribAndExtras = true;
8 };
9 };
10 }
This allows you to select XMonad as the window manager the next time you reboot. Once you’ve done that and logged in, the computer boots into the XMonad environment. But there’s not much there at first—or rather, nothing at all. You get a very minimalistic WM with the option to access a terminal. This is where the fun begins.
CONFIGURATION
On most Linux distributions, XMonad looks for a configuration file called xmonad.hs. The default path for the config is ~/.xmonad/xmonad.hs
. If this path doesn’t exist, you’ll need to create it, as well as the xmonad.hs
.
Now you can either recompile it while running with the command xmonad --recompile
or restart XMonad and remain in the session with xmonad --restart
.
So far, a shallow introduction; now comes Haskell.
Our config is currently empty and quite boring. Of course, we want to customize our XMonad a bit—after all, your eyes are part of the game.
MODULES
At the beginning of the config, we import all the necessary modules. Since the config can quickly become too complex, it makes sense to maintain a clear structure from the start.
BASE MODULES
1 import XMonad
2 import XMonad.Config.Desktop
3 import Data.Monoid
4 import Data.Maybe (isJust)
5 import System.IO (hPutStrLn)
6 import System.Exit (exitSuccess)
7 import qualified XMonad.StackSet as W
The base modules take care of basic things. XMonad.Config.Desktop
provides useful defaults with common desktops. XMonad.StackSet
is imported here as W
and handles window manipulation. In addition, isJust
, exitSuccess
, etc., handle logic and input/output.
UTILITY MODULES
1 import XMonad.Util.Loggers
2 import XMonad.Util.EZConfig (additionalKeysP, additionalMouseBindings)
3 import XMonad.Util.NamedScratchpad
4 import XMonad.Util.Run (safeSpawn, unsafeSpawn, runInTerm, spawnPipe)
5 import XMonad.Util.SpawnOnce
The utility modules handle other tasks. For example, EZConfig
handles simpler keybindings, Run
can start processes, SpawnOnce
prevents duplicate autostarts, and NamedScratchpads
enables hidden windows that can be activated via keypress.
HOOKS
1 import XMonad.Hooks.DynamicLog (dynamicLogWithPP, wrap, pad, xmobarPP, xmobarColor, shorten, PP(..))
2 import XMonad.Hooks.ManageDocks (avoidStruts, docksStartupHook, manageDocks, ToggleStruts(..))
3 import XMonad.Hooks.ManageHelpers (isFullscreen, isDialog, doFullFloat, doCenterFloat)
4 import XMonad.Hooks.Place (placeHook, withGaps, smart)
5 import XMonad.Hooks.SetWMName
6 import XMonad.Hooks.EwmhDesktops
The hooks are for further integrations and functions of the WM itself. DynamicLog
(and the specific imports) manage communication with the xmobar. ManageDocks
controls the space required when using status bars like xmobar and prevents windows from overlapping them. Of course, you don’t want all dialogs to be fullscreen or fitted into the layout; ManageHelpers
handles float handling for dialogs and the like. Place
places windows, SetWMName
controls naming for the WM and optimizes handling with Java GUI tools. EwmhDesktops
optimizes compatibility with modern DEs and panels.
ACTIONS
1 import XMonad.Actions.Minimize (minimizeWindow)
2 import XMonad.Actions.Promote
3 import XMonad.Actions.RotSlaves (rotSlavesDown, rotAllDown)
4 import XMonad.Actions.CopyWindow (kill1, copyToAll, killAllOtherCopies, runOrCopy)
5 import XMonad.Actions.WindowGo (runOrRaise, raiseMaybe)
6 import XMonad.Actions.WithAll (sinkAll, killAll)
7 import XMonad.Actions.CycleWS (moveTo, shiftTo, WSType(..), nextScreen, prevScreen, shiftNextScreen, shiftPrevScreen)
8 import XMonad.Actions.GridSelect
9 import XMonad.Actions.DynamicWorkspaces (addWorkspacePrompt, removeEmptyWorkspace)
10 import XMonad.Actions.MouseResize
11 import qualified XMonad.Actions.ConstrainedResize as Sqr
Thanks to actions, it is possible to manipulate windows. Minimize
, Promote
, RotSlave
, CopyWindow
, WindowGo
, WithAll
, CycleWS
, and DynamicWorkspaces
handle actions for window movement, focus switching, workspaces, etc. GridSelect
provides an interface for quickly selecting open windows or workspaces. MouseResize
allows you to resize windows using the mouse. ConstrainedResize
allows you to constrain the aspect ratio of a floating window.
LAYOUTS MODIFIERS
1 import XMonad.Layout.PerWorkspace (onWorkspace)
2 import XMonad.Layout.Renamed (renamed, Rename(CutWordsLeft, Replace))
3 import XMonad.Layout.WorkspaceDir
4 import XMonad.Layout.Spacing (spacing)
5 import XMonad.Layout.NoBorders
6 import XMonad.Layout.LimitWindows (limitWindows, increaseLimit, decreaseLimit)
7 import XMonad.Layout.WindowArranger (windowArrange, WindowArrangerMsg(..))
8 import XMonad.Layout.Reflect (reflectVert, reflectHoriz, REFLECTX(..), REFLECTY(..))
9 import XMonad.Layout.MultiToggle (mkToggle, single, EOT(EOT), Toggle(..), (??))
10 import XMonad.Layout.MultiToggle.Instances (StdTransformers(NBFULL, MIRROR, NOBORDERS))
11 import qualified XMonad.Layout.ToggleLayouts as T (toggleLayouts, ToggleLayout(Toggle))
This is where the layout modifiers are imported. For example, spacing can be used to create spaces between windows. NoBorders creates borderless windows. LimitWindows limits the number of windows, and so on. ToggleLayouts switches between floating and tiling.
LAYOUTS
1 import XMonad.Layout.NoFrillsDecoration
2 import XMonad.Layout.GridVariants (Grid(Grid))
3 import XMonad.Layout.SimplestFloat
4 import XMonad.Layout.OneBig
5 import XMonad.Layout.ThreeColumns
6 import XMonad.Layout.ResizableTile
7 import XMonad.Layout.ZoomRow (zoomRow, zoomIn, zoomOut, zoomReset, ZoomMessage(ZoomFullToggle))
8 import XMonad.Layout.IM (withIM, Property(Role))
Layouts imports the specific layouts that XMonad can display. OneBig
maximizes the window to fullscreen, ThreeColumns
to three columns, NoFrillsDecoration
handles window decoration, etc.
PROMPTS
1 import XMonad.Prompt (XPConfig(..), XPPosition(Top), Direction1D(..))
This is where the prompt input is defined.
Now we’ve imported the most necessary prerequisites (some of which I no longer use) and can begin with the actual configuration. This also shows how complex your XMonad configuration can be, but doesn’t have to be.
CONFIG
This is where the actual configuration of XMonad begins. We define keymaps, default editors, colors, and the behavior of the individual layouts and windows. Let’s start with a few simple definitions:
1 myFont = "xft:Berkeley Mono Nerd Font:style=Regular:pixelsize=11"
2 myModMask = mod4Mask
3 myTerminal = "alacritty"
4 myTextEditor = "nvim"
5 myBorderWidth = 2
6 windowCount = gets $ Just . show . length . W.integrate' . W.stack . W.workspace . W.current . windowset
This part of the config is still quite simple and understandable. myFont
declares the font that XMonad should use globally. In Tiling-WM, there is usually a key as the main modifier for interacting with the WM. The key I rarely use in normal use is the GUI key or WIN key. Since XMonad defaults to the ALT key (for example: ALT + ENTER opens a terminal), this variable is adjusted. In my case, mxModMask
is mod4Mask
, i.e., the GUI key. myTerminal
defines the default terminal emulator, and I’m currently using alacritty
. The default text editor to be used is set to nvim
with myTextEditor
. Finally, we set the window border width to 2px with myBorderWidth
. windowCount
enables the display of open windows per workspace (I don’t use this option anymore, but more on that later).
The next section in the config is the main
function which handles the detailed configuration of XMonad.
1 main = do
2
3 xmonad $ ewmh desktopConfig
4 { manageHook = ( isFullscreen --> doFullFloat ) <+> myManageHook <+> manageHook desktopConfig <+> manageDocks
5
6 , modMask = myModMask
7 , terminal = myTerminal
8 , startupHook = myStartupHook
9 , layoutHook = myLayoutHook
10 , workspaces = myWorkspaces
11 , borderWidth = myBorderWidth
12 , normalBorderColor = "#44475A"
13 , focusedBorderColor = "#44475A"
14 } `additionalKeysP` myKeys
15
Here we integrate support for panels and other desktop functions with xmonad $ ewmh desktopConfig
and the subsequent definitions. This is followed by passing our default variables to XMonad. modMask
for the modifier key for controlling XMobar, terminal
for our terminal, startupHook
this passes our default autostart hook, which will be defined later, layoutHook
passes our layouts that we can use (also defined later), workspaces
, which will also be defined later, and then we pass our defined window border width with borderWidth
. What we also explicitly define here are normalBorderColor
and focusedBorderColor
. These two colors are for the window borders for inactive windows and for active windows. Last but not least, the call to the variable myKeys
via additionalKeysP
, which makes it easier to create your own keymaps.
Now we define our myStartupHook
to start things directly when the system boots:
1 myStartupHook = do
2 spawnOnce "nitrogen --restore &"
3 spawnOnce "xmobar -x 0"
spawnOnce
prevents the supplied tools from being started multiple times. Here we start Nitrogen with the flags --restore &
. This saves the last used wallpaper and sets it directly at startup. The second tool that is started here after boot is xmobar. Here with the flags -x 0
which displays xmobar on the first monitor. If there are multiple monitors, multiple instances of xmobar must be started if the panel is to be visible on all monitors. Specific configs for xmobar can also be specified here if the same panel design is not always to be displayed.
Now let’s move on to the part of the config that takes up the most space and is also essential for controlling XMonad – the keybindings. We write these into the myKeys
variable mentioned above.
1 myKeys =
2 [ ("M-C-r", spawn "xmonad --recompile")
3 , ("M-S-r", spawn "xmonad --restart")
4 , ("M-S-q", io exitSuccess)
5
6 , ("M-S-c", kill1)
7 , ("M-S-a", killAll)
8
9 , ("M-<Delete>", withFocused $ windows . W.sink)
10 , ("M-S-<Delete>", sinkAll)
11
12 , ("M-m", windows W.focusMaster)
13 , ("M-j", windows W.focusDown)
14 , ("M-k", windows W.focusUp)
15 , ("M-S-m", windows W.swapMaster)
16 , ("M-S-j", windows W.swapDown)
17 , ("M-S-k", windows W.swapUp)
18 , ("M-<Backspace>", promote)
19 , ("M1-S-<Tab>", rotSlavesDown)
20 , ("M1-C-<Tab>", rotAllDown)
21 , ("M-S-s", windows copyToAll)
22 , ("M-C-s", killAllOtherCopies)
23
24 , ("M-C-M1-<Up>", sendMessage Arrange)
25 , ("M-C-M1-<Down>", sendMessage DeArrange)
26 , ("M-<Up>", sendMessage (MoveUp 10))
27 , ("M-<Down>", sendMessage (MoveDown 10))
28 , ("M-<Right>", sendMessage (MoveRight 10))
29 , ("M-<Left>", sendMessage (MoveLeft 10))
30 , ("M-S-<Up>", sendMessage (IncreaseUp 10))
31 , ("M-S-<Down>", sendMessage (IncreaseDown 10))
32 , ("M-S-<Right>", sendMessage (IncreaseRight 10))
33 , ("M-S-<Left>", sendMessage (IncreaseLeft 10))
34 , ("M-C-<Up>", sendMessage (DecreaseUp 10))
35 , ("M-C-<Down>", sendMessage (DecreaseDown 10))
36 , ("M-C-<Right>", sendMessage (DecreaseRight 10))
37 , ("M-C-<Left>", sendMessage (DecreaseLeft 10))
38
39 , ("M-<Tab>", sendMessage NextLayout)
40 , ("M-S-<Space>", sendMessage ToggleStruts)
41 , ("M-S-n", sendMessage $ Toggle NOBORDERS)
42 , ("M-S-=", sendMessage (Toggle NBFULL) >> sendMessage ToggleStruts)
43 , ("M-S-f", sendMessage (T.Toggle "float"))
44 , ("M-S-x", sendMessage $ Toggle REFLECTX)
45 , ("M-S-y", sendMessage $ Toggle REFLECTY)
46 , ("M-S-m", sendMessage $ Toggle MIRROR)
47
48 , ("M-h", sendMessage Shrink)
49 , ("M-l", sendMessage Expand)
50 , ("M-C-j", sendMessage MirrorShrink)
51 , ("M-C-k", sendMessage MirrorExpand)
52 , ("M-S-;", sendMessage zoomReset)
53 , ("M-;", sendMessage ZoomFullToggle)
54
55 , ("M-<Return>", spawn myTerminal)
56 , ("M-f", spawn "librewolf")
57
58 , ("M-S-<Return>", spawn "rofi -show run")
59
60 , ("<xF86MonBrightnessUp>", spawn "xbacklight +100")
61 , ("<xF86MonBrightnessDown>", spawn "xbacklight -20")
62 ] where nonNSP = WSIs (return (\ws -> W.tag ws /= "nsp"))
63 nonEmptyNonNSP = WSIs (return (\ws -> isJust (W.stack ws) && W.tag ws /= "nsp"))
I won’t go through the individual keybindings here; that would be beyond the scope. Most of them should be self-explanatory. Generally, keybindings follow the syntax ("KEY-KEY-KEY", COMMAND (PARAMETER))
or ("KEY-KEY-KEY", FUNCTION "PARAMETER")
. Let’s take ("M-S-r", spawn "xmonad --recompile")
as an example. This keybinding works as follows: if the keys GUI
+ SHIFT
+ r
are pressed simultaneously, XMonad is recompiled. M
stands for the mod key we defined with myModMask
above. Another example is ("M-k", windows W.focusUp)
. Here we use the combination GUI
+ k
to call the XMonad internal function which changes the window focus, i.e. window
, and give it the parameter W.focusUp
, i.e. to the next open window.
At this point, XMonad is already halfway configured and ready to use.
If you work with many windows, you’ll eventually reach the limit of your monitors. Workspaces can help with this, for example, to group windows. We can, of course, also configure this in the XMonad config.
1 xmobarEscape = concatMap doubleLts
2 where
3 doubleLts '<' = "<<"
4 doubleLts x = [x]
5
6 myWorkspaces :: [String]
7 myWorkspaces = clickable . (map xmobarEscape)
8 $ ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
9 where
10 clickable l = [ "<action=xdotool key super+" ++ show (n) ++ ">" ++ ws ++ "</action>" |
11 (i,ws) <- zip [1..9] l,
12 let n = i ]
13 myManageHook :: Query (Data.Monoid.Endo WindowSet)
14 myManageHook = composeAll
15 [
16 className =? "Firefox" --> doShift "<action=xdotool key super+1>www</action>"
17 , title =? "Vivaldi" --> doShift "<action=xdotool key super+1>www</action>"
18 , title =? "irssi" --> doShift "<action=xdotool key super+9>irc</action>"
19 , (className =? "Firefox" <&&> resource =? "Dialog") --> doFloat -- Float Firefox Dialog
20 ]
We start by defining the variable xmobarEscape
. Here, we define the display behavior of xmobar. This ensures that <
is displayed correctly and doesn’t get lost in the code or cause error messages.
Next, we define the variable myWorkspaces
, which we already integrated into XMonad
above. myWorkspaces :: [String]
declares the variable type as a list of strings.
1 myWorkspaces = clickable . (map xmobarEscape)
2 $ ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
3 where
4 clickable l = [ "<action=xdotool key super+" ++ show (n) ++ ">" ++ ws ++ "</action>" |
5 (i,ws) <- zip [1..9] l,
6 let n = i ]
Here, we begin by defining that the workspaces are clickable in the xmobar, and in the next step, we define the display names of the workspaces (1…9).
We then define the variable myManageHooks
. With ManageHooks
, we have the option of assigning individual tools to specific workspaces.
The last block in the XMonad config defines the layouts in which the windows can be displayed. In the keybindings, we’ve defined that we can switch between the layouts using the combination GUI
+ SPACE
.
1 myLayoutHook = avoidStruts $ mouseResize $ windowArrange $ T.toggleLayouts floats $
2 mkToggle (NBFULL ?? NOBORDERS ?? EOT) $ myDefaultLayout
3 where
4 myDefaultLayout = deco ||| grid ||| threeCol ||| threeRow ||| oneBig ||| noBorders monocle ||| space ||| floats ||| tall
5
6
7 tall = renamed [Replace "T"] $ limitWindows 12 $ spacing 6 $ ResizableTall 1 (3/100) (1/2) []
8 grid = renamed [Replace "G"] $ limitWindows 12 $ spacing 6 $ mkToggle (single MIRROR) $ Grid (16/10)
9 threeCol = renamed [Replace "3C"] $ limitWindows 3 $ ThreeCol 1 (3/100) (1/2)
10 threeRow = renamed [Replace "3R"] $ limitWindows 3 $ Mirror $ mkToggle (single MIRROR) zoomRow
11 oneBig = renamed [Replace "1B"] $ limitWindows 6 $ Mirror $ mkToggle (single MIRROR) $ mkToggle (single REFLECTX) $ mkToggle (single REFLECTY) $ OneBig (5/9) (8/12)
12 monocle = renamed [Replace "M"] $ limitWindows 20 $ Full
13 space = renamed [Replace "S"] $ limitWindows 4 $ spacing 12 $ Mirror $ mkToggle (single MIRROR) $ mkToggle (single REFLECTX) $ mkToggle (single REFLECTY) $ OneBig (2/3) (2/3)
14 floats = renamed [Replace "F"] $ limitWindows 20 $ simplestFloat
15 deco = renamed [CutWordsLeft 1] $ noFrillsDeco shrinkText myTitleTheme tall
16
17 myTitleTheme :: Theme
18 myTitleTheme = def {
19 fontName = "xft:Berkeley Mono Nerd Font:style=Regular:pixelsize=11"
20 , inactiveBorderColor = "#44475A"
21 , inactiveColor = "#44475A"
22 , inactiveTextColor = "#BD93F9"
23 , activeBorderColor = "#44475A"
24 , activeColor = "#44475A"
25 , activeTextColor = "#50FA7B"
26 , urgentBorderColor = "#FF4242"
27 , urgentTextColor = "#262626"
28 , urgentColor = "#FF4242"
29 , decoHeight = 12
30
31 }
First, we pass the XMonad internal parameters to myLayoutHook
, which allow manipulating the windows. Next, we specify the layout order: myDefaultLayout = deco ||| grid ||| threeCol ||| threeRow ||| oneBig ||| noBorders monocle ||| space ||| floats ||| tall
. We start with deco
and continue with tall
. After that, we continue with the actual definition of the individual layouts:
1 tall = renamed [Replace "T"] $ limitWindows 12 $ spacing 6 $ ResizableTall 1 (3/100) (1/2) []
This defines the tall
layout. Note that Haskell interprets the following parameters from right to left. So, define ResizableTall
layout -> add spacing
-> limit the number of windows with limitWindows
-> rename the layout T
using renamed
.
Even more precisely: ResizeableTall
defines the layout as a variation of the classic “Tall” layout, but makes it resizable. 1
means there is one master window. (3/100)
allows the master window size to be changed in 3% increments. (1/2)
makes the master window occupy 50% of the total space. []
allows further options to be specified here.
spacing 6
is the layout modifier loaded from XMonad.Layout.Spacing
and generates 6px spacing between individual windows and the screen edge.
limitWindows 12
limits the number of simultaneously visible windows to a total of 12. If additional windows are loaded, they overlap with existing windows.
renamed [Replace "T"]
renames the display name to T
. This will later be passed on to xmobar.
The deco
layout is similarly structured, except that window decorations are also drawn with the names of the respective open windows. These decorations are defined with myTitleTheme
, and the corresponding colors are defined below:
1 myTitleTheme :: Theme
2 myTitleTheme = def {
3 fontName = "xft:Berkeley Mono Nerd Font:style=Regular:pixelsize=11"
4 , inactiveBorderColor = "#44475A"
5 , inactiveColor = "#44475A"
6 , inactiveTextColor = "#BD93F9"
7 , activeBorderColor = "#44475A"
8 , activeColor = "#44475A"
9 , activeTextColor = "#50FA7B"
10 , urgentBorderColor = "#FF4242"
11 , urgentTextColor = "#262626"
12 , urgentColor = "#FF4242"
13 , decoHeight = 12
14
15 }
These should be relatively self-explanatory.
This largely completes the configuration of XMonad.
Personally, I prefer fewer dotfiles than too many. Fortunately, NixOS offers the option of integrating XMonad directly into the configuration.
1 config = ''
2 -- YOUR XMONAD CONFIG HERE --
3 '';
4 enableConfiguredRecompile = true;
The last function, enableConfiguredRecompile = true;
, allows XMonad to be rebuilt on the fly. After a nixos-rebuild switch
, you should be able to use XMonad as a WM.
IMPROVEMENTS
I think most people feel the same way when I say that switching from a tiling WM back to a floating WM is a rare occurrence. The learning curve is certainly a bit steeper than with conventional WMs, but not as steep as that of VIM or EMACS, or even NixOS itself. Once you’ve mastered the most necessary keybindings, you’ll practically fly through your tiling WM and notice the significant increase in productivity. Since we’ve made significant progress in efficiency and ergonomics, it’s difficult to find further improvements at some point. Nevertheless, I’ve dug a little deeper.
NO STATUS BAR
When you’re browsing the internet and reading about tiling WMs, the question quickly arises as to which system bar is recommended. For XMonad, the xmobar is recommended. Highly configurable and offers everything you’d expect from a status bar. However, in my daily hacking, I noticed at some point that I very rarely look at the status bar. So I tried simply ignoring it. You eventually remember the corresponding workspace for all open windows. If you want to check the time and network traffic, you do so in the terminal, and the currently used layout is also deduced from the display of the windows themselves. This is the reason why all config entries related to xmobar are commented out in my config (yes, I’ve retained the fallback). This setup has been working excellently for me for some time now, and I have 12px more space on my monitor.
XMONAD MODAL
As a Vim/NeoVim user, you eventually learn to appreciate Vim’s modality. That is, working with different modes that combine their respective functions. Interestingly, XMonad offers a similar input called XMonad.Hooks.Modal
. Here, you can also define different modes, for example, to group window resizing or window movement. This feature is not currently integrated into my config, but will be added in the future.
THE LAST WM I WILL USE?
That’s hard to say. What I can say is that XMonad definitely has what it takes. I will (most likely) not switch back to a floating WM. The efficiency, speed, and ergonomics you can achieve with a tiling WM are incomparable to other WMs. However, there are a few small things that regularly give me headaches. First, XMonad is based on X11 – yes, the one from 1986. So older than Linux or Windows. There’s currently a fork of WayMonad to port the WM to Wayland, but the project is an alpha and still a work in progress. Sooner or later, the only option is probably to switch to a Wayland-compatible WM like sway or similar. Another issue is the use of the modifier key. For one thing, I don’t want to use ‘ALT’ because it would definitely overlap with other shortcuts at some point, so I used ‘GUI’ or ‘WIN’ as a modifier, as mentioned above. If you like to change keyboards and thus the actual keyboard layout, you quickly run into the problem that custom keyboards rarely make a point of placing the ‘GUI’ key on the keyboard at all. Sure, Homerow Mods help here, but I don’t have them active on all keyboards. The only thing that will probably help is a rethink.
For now, I’ll probably stick with XMonad. You can find my complete configuration here, including xmobar and other things.
Happy Hacking!
[~] BACK