#+title: Xmonad Config
#+author: Emmet

* XMonad Config
The main configuration file for XMonad is [[./xmonad.hs][~/.xmonad/xmonad.hs]].
** Imports
First I import a bunch of libraries:
#+BEGIN_SRC haskell :tangle xmonad.hs
-- IMPORTS
import qualified Data.Map as M
import Control.Monad as C
import Data.List
import Data.Monoid
import Data.Maybe (fromJust)
import Graphics.X11.ExtraTypes.XF86
import System.Exit
import System.IO
import XMonad
import XMonad.Actions.Navigation2D
import XMonad.Actions.SpawnOn
import XMonad.Actions.TiledWindowDragging
import XMonad.Actions.Warp
import XMonad.Actions.WindowNavigation
import XMonad.Actions.WithAll
import XMonad.Hooks.DynamicLog
import qualified XMonad.Hooks.EwmhDesktops as EWMHD
import XMonad.Hooks.FadeWindows
import XMonad.Hooks.ManageDocks
import XMonad.Hooks.RefocusLast
import XMonad.Hooks.ServerMode
import XMonad.Hooks.StatusBar
import XMonad.Hooks.StatusBar.PP
import XMonad.Layout.DraggingVisualizer
import XMonad.Layout.Dwindle
import XMonad.Layout.Fullscreen
import XMonad.Layout.Gaps
import XMonad.Layout.LayoutHints
import XMonad.Layout.LimitWindows
import XMonad.Layout.MouseResizableTile
import XMonad.Layout.Spacing
import XMonad.ManageHook
import qualified XMonad.StackSet as W
--import qualified DBus as D
--import qualified DBus.Client as D
import XMonad.Util.NamedScratchpad
import XMonad.Util.Run
import XMonad.Util.SpawnOnce

#+END_SRC
** Theme Setup
*** Custom Color Library Template
#+BEGIN_SRC haskell :tangle ./lib/Colors/Stylix.hs.mustache
module Colors.Stylix where

import XMonad

colorBg = "#{{base00-hex}}"
colorFg = "#{{base05-hex}}"
color01 = "#{{base01-hex}}" -- usually black
color02 = "#{{base08-hex}}" -- usually red
color03 = "#{{base0B-hex}}" -- usually green
color04 = "#{{base0A-hex}}" -- usually yellow
color05 = "#{{base0E-hex}}" -- usually blue
color06 = "#{{base0F-hex}}" -- usually magenta
color07 = "#{{base0D-hex}}" -- usually cyan
color08 = "#{{base07-hex}}" -- usually white

-- Select focus and secondary color
colorFocus = color02
colorSecondary = color07

#+END_SRC
*** Import Custom Color Library
#+BEGIN_SRC haskell :tangle xmonad.hs
-- setup color variables
import Colors.Stylix

#+END_SRC
** Settings
*** Border Color
#+BEGIN_SRC haskell :tangle xmonad.hs
-- Border colors for unfocused and focused windows, respectively.
myNormalBorderColor, myFocusedBorderColor :: String
myNormalBorderColor = colorBg
myFocusedBorderColor = colorFocus

#+END_SRC
*** Default Apps
#+BEGIN_SRC haskell :tangle xmonad.hs
-- Default apps
myTerminal, myBrowser :: String
myTerminal = "$TERM"
myBrowser = "$BROWSER"
myEditor = "$EDITOR"
mySpawnEditor = "$SPAWNEDITOR"

#+END_SRC
*** Mouse Focus
#+BEGIN_SRC haskell :tangle xmonad.hs
-- Whether focus follows the mouse pointer.
myFocusFollowsMouse :: Bool
myFocusFollowsMouse = False

-- Whether clicking on a window to focus also passes the click to the window
myClickJustFocuses :: Bool
myClickJustFocuses = False

#+END_SRC
*** Border Width
#+BEGIN_SRC haskell :tangle xmonad.hs
-- Width of the window border in pixels.
myBorderWidth :: Dimension
myBorderWidth = 3

#+END_SRC
*** Select Modkey
The default modkey is =mod1Mask= which is bound to left alt.  =mod3Mask= can be used for right alt, but most people (including myself) simply use =mod4Mask= which is bound to the super key.
#+BEGIN_SRC haskell :tangle xmonad.hs
-- Modmask
myModMask :: KeyMask
myModMask = mod4Mask

#+END_SRC
*** Workspaces
By default, workspaces are simply numeric strings ("1", "2", "3", etc..), but any strings can be used (i.e. "web", "irc", "code", etc..).  I set workspace names with <fn=1>\x____</fn> where the blank spaces represent a [[https://www.nerdfonts.com/][nerd font symbol code]].  This works nicely because I have a Nerd Font as fn=1 in my [[XMobar][xmobar]], which renders the nerd font glyphs in xmobar.
#+BEGIN_SRC haskell :tangle xmonad.hs
myWorkspaces :: [String]
myWorkspaces =
  [ "<fn=1>\xf15c¹</fn>", -- document icon for writing
    "<fn=1>\xeb01 ²</fn>", -- globe icon for browsing
    "<fn=1>\xf121³</fn>", -- dev icon for programming
    "<fn=1>\xf0cb9 ⁴</fn>", -- music file icon for composition
    "<fn=1>\xf1fc⁵</fn>", -- paint icon for art
    "<fn=1>\xf0bdc ⁶</fn>", -- video icon for recording/editing
    "<fn=1>\xf0d6⁷</fn>", -- money icon for finances
    "<fn=1>\xf19d⁸</fn>", -- cap icon for teaching
    "<fn=1>\xf11b⁹</fn>" -- gamepad icon for gaming
  ]

myWorkspaceIndices = M.fromList $ zipWith (,) myWorkspaces [1..] -- (,) == \x y -> (x,y)

clickable ws = "<action=xdotool key super+"++show i++">"++ws++"</action>"
    where i = fromJust $ M.lookup ws myWorkspaceIndices

#+END_SRC
*** Scratchpads
Scratchpads are single applications that are normally not visible (in a workspace called "NSP"), but can be brought into the current workspace with a quick keybind.  I find that this works really well for applications I use frequently for quick tasks, such as my terminal, password manager, email, and music player.
#+BEGIN_SRC haskell :tangle xmonad.hs
-- Scratchpads
myScratchPads :: [NamedScratchpad]
myScratchPads =
  [ NS "terminal" spawnTerm findTerm manageTerm,
    NS "ranger" spawnRanger findRanger manageRanger,
    NS "octave" spawnOctave findOctave manageOctave,
    NS "btm" spawnBtm findBtm manageBtm,
    NS "geary" spawnGeary findGeary manageGeary,
    NS "helpmenu" spawnHelp findHelp manageHelp,
    NS "musikcube" spawnMusikcube findMusikcube manageMusikcube,
    NS "cal" spawnCal findCal manageCal,
    NS "pavucontrol" spawnPavucontrol findPavucontrol managePavucontrol,
    NS "discord" spawnDiscord findDiscord manageDiscord
  ]
  where
    spawnTerm = myTerminal ++ " --title scratchpad"
    findTerm = title =? "scratchpad"
    manageTerm = customFloating $ W.RationalRect l t w h
      where
        h = 0.9
        w = 0.9
        t = 0.95 - h
        l = 0.95 - w
    spawnRanger = "kitty --title ranger-scratchpad -e ranger"
    findRanger = title =? "ranger-scratchpad"
    manageRanger = customFloating $ W.RationalRect l t w h
      where
        h = 0.9
        w = 0.9
        t = 0.95 - h
        l = 0.95 - w
    spawnOctave = myTerminal ++ " --title octave-scratchpad -e octave"
    findOctave = title =? "octave-scratchpad"
    manageOctave = customFloating $ W.RationalRect l t w h
      where
        h = 0.5
        w = 0.4
        t = 0.75 - h
        l = 0.70 - w
    spawnBtm = myTerminal ++ " -o font.size=12 --title btm-scratchpad -e btm"
    findBtm = title =? "btm-scratchpad"
    manageBtm = customFloating $ W.RationalRect l t w h
      where
        h = 0.5
        w = 0.4
        t = 0.75 - h
        l = 0.70 - w
    spawnDiscord = "gtkcord4"
    findDiscord = className =? "gtkcord4"
    manageDiscord = customFloating $ W.RationalRect l t w h
      where
        h = 0.5
        w = 0.4
        t = 0.75 - h
        l = 0.70 - w
    spawnGeary = "geary"
    findGeary = className =? "Geary"
    manageGeary = customFloating $ W.RationalRect l t w h
      where
        h = 0.5
        w = 0.4
        t = 0.75 - h
        l = 0.70 - w
    spawnHelp = myTerminal ++ " --title xmonad_helpmenu -e w3m ~/.xmonad/helpmenu.txt"
    findHelp = title =? "xmonad_helpmenu"
    manageHelp = customFloating $ W.RationalRect l t w h
      where
        h = 0.9
        w = 0.9
        t = 0.95 - h
        l = 0.95 - w
    spawnMusikcube = myTerminal ++ " -o font.size=14 --title musikcube-scratchpad -e musikcube"
    findMusikcube = title =? "musikcube-scratchpad"
    manageMusikcube = customFloating $ W.RationalRect l t w h
      where
        h = 0.9
        w = 0.9
        t = 0.95 - h
        l = 0.95 - w
    spawnCal = "gnome-calendar"
    findCal = className =? "gnome-calendar"
    manageCal = customFloating $ W.RationalRect l t w h
      where
        h = 0.4
        w = 0.3
        t = 0.45 - h
        l = 1 - w
    spawnPavucontrol = "pavucontrol"
    findPavucontrol = className =? "Pavucontrol"
    managePavucontrol = customFloating $ W.RationalRect l t w h
      where
        h = 0.5
        w = 0.3
        t = 0.9 - h
        l = 0.65 - w

#+END_SRC
*** Keybindings
Keybinds can be set with an array of values like: =(keybind, action)=.  The array is declared like so:
#+BEGIN_SRC haskell :tangle xmonad.hs
myKeys conf@(XConfig {XMonad.modMask = modm}) =
  M.fromList $
    [
    -- insert keybinds with array values of ((keybind, action))

#+END_SRC
Then, keybindings are setup line by line as in the following sections:
**** Quick App Keybindings
The following binds the following:
| Keybinding          | Action                                        |
|---------------------+-----------------------------------------------|
| S-Return            | New terminal                                  |
| S-a                 | New emacs frame                               |
| S-s                 | New browser window                            |
| PrintScreen         | Snip a screenshot                             |
| C-PrintScreen       | Snip a screenshot (to clipboard)              |
| Shift-PrintScreen   | Screen capture current monitor                |
| Shift-C-PrintScreen | Screen capture current monitor (to clipboard) |
#+BEGIN_SRC haskell :tangle xmonad.hs
      -- launch a terminal
      ((modm, xK_Return), spawn $ XMonad.terminal conf),

      -- launch emacsclient
      ((modm, xK_a), spawn mySpawnEditor),

      -- launch browser
      ((modm, xK_s), spawn myBrowser),

      -- take screenshots
      ((0, xK_Print), spawn "flameshot gui"), -- snip screenshot and save
      ((controlMask, xK_Print), spawn "flameshot gui --clipboard"), -- snip screenshot to clipboard
      ((shiftMask, xK_Print), spawn "flameshot screen"), -- screen capture current monitor and save
      ((controlMask .|. shiftMask, xK_Print), spawn "flameshot screen -c"), -- screen capture current monitor to clipboard

      -- launch game manager in gaming workspace
      ((modm, xK_g), spawn "xdotool key Super+9 && gamehub"),

#+END_SRC
**** Generic Keybindings
These setup standard bindings for brightness and audio control from the keyboard.
#+BEGIN_SRC haskell :tangle xmonad.hs
      -- control brightness from kbd
      ((0, xF86XK_MonBrightnessUp), spawn "brightnessctl set +15"),
      ((0, xF86XK_MonBrightnessDown), spawn "brightnessctl set 15-"),

      -- control kbd brightness from kbd
      ((0, xF86XK_KbdBrightnessUp), spawn "brightnessctl --device='asus::kbd_backlight' set +1 & xset r rate 350 100"),
      ((0, xF86XK_KbdBrightnessDown), spawn "brightnessctl --device='asus::kbd_backlight' set 1- & xset r rate 350 100"),
      ((shiftMask, xF86XK_MonBrightnessUp), spawn "brightnessctl --device='asus::kbd_backlight' set +1 & xset r rate 350 100"),
      ((shiftMask, xF86XK_MonBrightnessDown), spawn "brightnessctl --device='asus::kbd_backlight' set 1- & xset r rate 350 100"),

      -- control volume from kbd
      ((0, xF86XK_AudioLowerVolume), spawn "pamixer -d 10"),
      ((0, xF86XK_AudioRaiseVolume), spawn "pamixer -i 10"),
      ((0, xF86XK_AudioMute), spawn "pamixer -t"),

      -- control music from kbd
      --((0, xF86XK_AudioPlay), spawn "cmus-remote -u"),
      --((0, xF86XK_AudioStop), spawn "cmus-remote -s"),
      --((0, xF86XK_AudioNext), spawn "cmus-remote -n && ~/.local/bin/cmus-current-song-notify.sh"),
      --((0, xF86XK_AudioPrev), spawn "cmus-remote -r && ~/.local/bin/cmus-current-song-notify.sh"),

#+END_SRC
**** Launcher Keybinds
I have =rofi= bound to =S-;= for quick app access.
#+BEGIN_SRC haskell :tangle xmonad.hs
      -- launch rofi
      ((modm, xK_semicolon), spawn ("rofi -show drun -show-icons")),
      ((modm, xK_p), spawn ("keepmenu")),
      ((modm, xK_i), spawn ("networkmanager_dmenu")),

#+END_SRC
**** Window Management Keybinds
All of the following keybinds pertain to window management and layouts:
| Keybinding        | Action                                                                                         |
|-------------------+------------------------------------------------------------------------------------------------|
| S-q               | Kill window                                                                                    |
| S-Shift-c         | Kill all windows on current workspace                                                          |
| S-Shift-q         | Exit xmonad                                                                                    |
| S-Shift-Escape    | Lock xmonad                                                                                    |
| S-Shift-s         | Lock xmonad and suspend                                                                        |
| S-Shift-Escape    | Lock xmonad and suspend                                                                        |
| S-Space           | Switch to next layout                                                                          |
| S-Shift-Space     | Reset layout on current workspace                                                              |
| S-r               | Resize windows to correct size                                                                 |
| S-{←,↓,↑,→}       | Switch to screen visually {left,down,up,right} (requires a [[Window Rules and Hooks][Navigation2Dconfig]])                 |
| S-{h,j,k,l}       | Switch to window visually {left,down,up,right} (requires a [[Window Rules and Hooks][Navigation2Dconfig]])                 |
| S-Shift-{h,j,k,l} | Swap window visually {left,down,up,right} on current workspace (requires a [[Window Rules and Hooks][Navigation2Dconfig]]) |
| S-C-{h,l}         | Resize master window area                                                                      |
| S-m               | Move current window into master window area                                                    |
| S-t               | Toggle floating status of a window (this is a function defined [[Toggle Float Function Definition][here]])                           |
| S-,               | Increase number of windows in the master window area                                           |
| S-.               | Decrease number of windows in the master window area                                           |
These keybindings are then set via:
#+BEGIN_SRC haskell :tangle xmonad.hs
      -- close focused window
      ((modm, xK_q), kill),
      -- close all windows on current workspace
      ((modm .|. shiftMask, xK_c), killAll),
      -- exit xmonad
      ((modm .|. shiftMask, xK_q), spawn "killall xmonad-x86_64-linux"),
      -- Lock with dm-tool
      ((modm, xK_Escape), spawn "dm-tool switch-to-greeter"),
      -- Lock with dm-tool and suspend
      ((modm .|. shiftMask, xK_s), spawn "dm-tool switch-to-greeter & systemctl suspend"),
      ((modm .|. shiftMask, xK_Escape), spawn "dm-tool switch-to-greeter & systemctl suspend"),

      -- Rotate through the available layout algorithms
      ((modm, xK_space), sendMessage NextLayout),
      --  Reset the layouts on the current workspace to default
      ((modm .|. shiftMask, xK_space), setLayout $ XMonad.layoutHook conf),

      -- Resize viewed windows to the correct size
      ((modm, xK_r), C.sequence_ [spawn "killall xmobar; autorandr -c; xmonad --restart;", refresh]),

      -- Move focus to window below
      ((modm, xK_j), C.sequence_ [windowGo D True, switchLayer, warpToWindow 0.5 0.5]),
      -- Move focus to window above
      ((modm, xK_k), C.sequence_ [windowGo U True, switchLayer, warpToWindow 0.5 0.5]),
      -- Move focus to window left
      ((modm, xK_h), C.sequence_ [windowGo L True, switchLayer, warpToWindow 0.5 0.5]),
      -- Move focus to window right
      ((modm, xK_l), C.sequence_ [windowGo R True, switchLayer, warpToWindow 0.5 0.5]),

      -- Move focus to screen below
      ((modm, xK_Down), C.sequence_ [screenGo D True, warpToCurrentScreen 0.5 0.5]),
      -- Move focus to screen up
      ((modm, xK_Up), C.sequence_ [screenGo U True, warpToCurrentScreen 0.5 0.5]),
      -- Move focus to screen left
      ((modm, xK_Left), C.sequence_ [screenGo L True, warpToCurrentScreen 0.5 0.5]),
      -- Move focus to screen right
      ((modm, xK_Right), C.sequence_ [screenGo R True, warpToCurrentScreen 0.5 0.5]),

      -- Swap with window below
      ((modm .|. shiftMask, xK_j), C.sequence_ [windowSwap D True, windowGo U True, switchLayer]),
      -- Swap with window above
      ((modm .|. shiftMask, xK_k), C.sequence_ [windowSwap U True, windowGo D True, switchLayer]),
      -- Swap with window left
      ((modm .|. shiftMask, xK_h), C.sequence_ [windowSwap L True, windowGo R True, switchLayer]),
      -- Swap with window right
      ((modm .|. shiftMask, xK_l), C.sequence_ [windowSwap R True, windowGo L True, switchLayer]),

      -- Shrink the master area
      ((modm .|. controlMask, xK_h), sendMessage Shrink),
      -- Expand the master area
      ((modm .|. controlMask, xK_l), sendMessage Expand),

      -- Swap the focused window and the master window
      ((modm, xK_m), windows W.swapMaster),

      -- Toggle tiling/floating status of window
      ((modm, xK_t), withFocused toggleFloat),

      -- Increment the number of windows in the master area
      ((modm, xK_comma), sendMessage (IncMasterN 1)),
      -- Deincrement the number of windows in the master area
      ((modm, xK_period), sendMessage (IncMasterN (-1))),

#+END_SRC
**** Scratchpad Keybinds
I have each [[Scratchpads][scratchpad]] bound to a keybinding for quick access:
| Keybinding | Associated Scratchpad      |
|------------+----------------------------|
| S-f        | Ranger file manager        |
| S-x        | KeePassXC password manager |
| S-z        | Terminal                   |
| S-b        | Bottom control panel       |
| S-d        | Discord                    |
| S-o        | Octave (calculator)        |
| S-e        | mu4e (email)               |
| S-n        | Music player               |
| S-c        | cfw (calendar)             |
| S-y        | Pavucontrol (audio mixer)  |
| S-/        | Keybinding help menu       |
These are then bound:
#+BEGIN_SRC haskell :tangle xmonad.hs
      -- scratchpad keybindings
      ((modm, xK_f), namedScratchpadAction myScratchPads "ranger"),
      --((modm, xK_x), namedScratchpadAction myScratchPads "keepassxc"),
      ((modm, xK_z), namedScratchpadAction myScratchPads "terminal"),
      ((modm, xK_b), namedScratchpadAction myScratchPads "btm"),
      ((modm, xK_d), namedScratchpadAction myScratchPads "discord"),
      ((modm, xK_o), namedScratchpadAction myScratchPads "octave"),
      ((modm, xK_e), namedScratchpadAction myScratchPads "geary"),
      ((modm, xK_n), namedScratchpadAction myScratchPads "musikcube"),
      ((modm, xK_c), namedScratchpadAction myScratchPads "cal"),
      ((modm, xK_y), namedScratchpadAction myScratchPads "pavucontrol"),
      ((modm, xK_slash), namedScratchpadAction myScratchPads "helpmenu")

#+END_SRC
**** End of Standard Keybinds
To finish the section of standard keybinds, we simply close the array [[Keybindings][started above]].
#+BEGIN_SRC haskell :tangle xmonad.hs
      ]
#+END_SRC
**** Workspace Management Keybinds
Workspaces are generically managed via =mod-[1..9]= to shift to a workspace, and =mod-shift-[1..9]= to send a window to another workspace.  To generate this effect, the following code is added to the keybindings definition:
#+BEGIN_SRC haskell :tangle xmonad.hs
      ++
      -- mod-[1..9], Switch to workspace N
      -- mod-shift-[1..9], Move client to workspace N

      [ ((m .|. modm, k), windows $ f i)
        | (i, k) <- zip (XMonad.workspaces conf) [xK_1 .. xK_9],
          (f, m) <- [(W.greedyView, 0), (W.shift, shiftMask)]
      ]

#+END_SRC
**** Custom Function Definitions
To have =toggleFloat= and =warpToCurrentScreen=, I must define them after setting up the keybinds like so:
#+BEGIN_SRC haskell :tangle xmonad.hs
  where
    -- toggle float/tiling status of current window
    toggleFloat w =
      windows
        ( \s ->
            if M.member w (W.floating s)
              then W.sink w s
              else (W.float w (W.RationalRect (1 / 8) (1 / 8) (3 / 4) (3 / 4)) s)
        )
    -- warp cursor to (x, y) coordinate of current screen
    warpToCurrentScreen x y = do
      sid <- withWindowSet $ return . W.screen . W.current
      warpToScreen sid x y
    -- TODO goto and warp (coords x, y) to window in DIRECTION, or goto and warp (coords x, y) to screen in DIRECTION if no window is available
    windowOrScreenGoAndWarp direction x y =
      do windowGo direction True

#+END_SRC
**** Mouse Bindings
The following code sets up some convenient mouse bindings:
| Mouse Binding | Action                                       |
|---------------+----------------------------------------------|
| S-Left click  | Make window floating and drag to move window |
| S-Right click | Make window floating and resize window       |
#+BEGIN_SRC haskell :tangle xmonad.hs
-- Mouse bindings: default actions bound to mouse events
myMouseBindings (XConfig {XMonad.modMask = modm}) =
  M.fromList $
    --    -- mod-button1, Set the window to floating mode and move by dragging
    [ ( (modm,  button1),
        ( \w ->
            focus w
              >> mouseMoveWindow w
              >> windows W.shiftMaster
        )
      ),
      -- mod-button3, Set the window to floating mode and resize by dragging
      ( (modm, button3),
        ( \w ->
            focus w
              >> mouseResizeWindow w
              >> windows W.shiftMaster
        )
      )
      -- you may also bind events to the mouse scroll wheel (button4 and button5)
    ]

#+END_SRC
*** Layouts
By default, I utilize three layouts:
- =mouseResizable= which is a master/stack layout I have set up to have dwindling sizes
- =mouseResizableMirrored=, same as above except mirrored
- =Full= where only one window takes up the entire space of the screen

I embellish these layouts with a few modifiers:
- =fullscreenFocus= for fullscreen support (also requires a [[Window Rules][fullscreen manage hook]])
- =draggingVisualizer= so that I can drag tiling windows about via my [[Mouse Bindings][mouse bindings]]
- =avoidStruts= since I use [[XMobar][xmobar]]
- =spacingRaw= to put a few pixels of space between windows since it looks nice

This is all applied in the following code to set the =myLayout= variable, which gets used later in the [[Main][main function]]:
#+BEGIN_SRC haskell :tangle xmonad.hs
-- Layouts:

spcPx = 5

mySpacing = spacingRaw False (Border spcPx spcPx spcPx spcPx) True (Border spcPx spcPx spcPx spcPx) True

myLayout = fullscreenFocus $ draggingVisualizer $ avoidStruts $ layoutHintsToCenter $ (mySpacing $ (Full ||| mouseResizable ||| mouseResizableMirrored))
  where
    -- default tiling algorithm partitions the screen into two panes
    tiled = Tall 1 (5 / 100) (1 / 2)

    dwindled = Dwindle R CW 1.1 1.1

    mouseResizable =
      mouseResizableTile
        { masterFrac = 0.51,
          slaveFrac = 0.51,
          draggerType = BordersDragger
        }

    mouseResizableMirrored =
      mouseResizableTile
        { masterFrac = 0.51,
          slaveFrac = 0.51,
          draggerType = BordersDragger,
          isMirrored = True
        }

#+END_SRC
*** Window Rules and Hooks
Window rules apply actions when a new window matching a specific query is apprehended by xmonad.  I mainly use these to control my scratchpads (to make them all floating) and for some apps that don't behave nicely inside of a tiling window manager.

The easiest way to do a query is by either =className= or =title= which can both be found using =xprop=.

The list of window rules must be made into a manage hook, which gets used in the [[Main][main function]] when starting xmonad.
#+BEGIN_SRC haskell :tangle xmonad.hs
-- Window rules:
myManageHook =
  composeAll
    [ title =? "Myuzi" --> (customFloating $ W.RationalRect 0.05 0.05 0.9 0.9),
      title =? "octave-scratchpad" --> (customFloating $ W.RationalRect 0.1 0.1 0.8 0.8),
      title =? "scratchpad" --> (customFloating $ W.RationalRect 0.1 0.1 0.8 0.8),
      className =? "gtkcord4" --> (customFloating $ W.RationalRect 0.1 0.1 0.8 0.8),
      title =? "ranger-scratchpad" --> (customFloating $ W.RationalRect 0.05 0.05 0.9 0.9),
      title =? "btm-scratchpad" --> (customFloating $ W.RationalRect 0.1 0.1 0.8 0.8),
      className =? "Geary" --> (customFloating $ W.RationalRect 0.05 0.05 0.9 0.9),
      title =? "scratch_cfw" --> (customFloating $ W.RationalRect 0.58 0.04 0.42 0.7),
      title =? "xmonad_helpmenu" --> (customFloating $ W.RationalRect 0.05 0.05 0.9 0.9),
      className =? "Pavucontrol" --> (customFloating $ W.RationalRect 0.05 0.04 0.5 0.35),
      className =? "Syncthing GTK" --> (customFloating $ W.RationalRect 0.53 0.50 0.46 0.45),
      className =? "Proton Mail Bridge" --> (customFloating $ W.RationalRect 0.59 0.66 0.40 0.30),
      className =? "Zenity" --> (customFloating $ W.RationalRect 0.45 0.4 0.1 0.2),
      resource =? "desktop_window" --> doIgnore,
      -- this gimp snippet is from Kathryn Anderson (https://xmonad.haskell.narkive.com/bV34Aiw3/layout-for-gimp-how-to)
      (className =? "Gimp" <&&> fmap ("color-selector" `isSuffixOf`) role) --> doFloat,
      (className =? "Gimp" <&&> fmap ("layer-new" `isSuffixOf`) role) --> doFloat,
      (className =? "Gimp" <&&> fmap ("-dialog" `isSuffixOf`) role) --> doFloat,
      (className =? "Gimp" <&&> fmap ("-tool" `isSuffixOf`) role) --> doFloat,
      -- end snippet
      resource =? "kdesktop" --> doIgnore,
      manageDocks
    ]
   where role = stringProperty "WM_WINDOW_ROLE"

#+END_SRC

I also must set my fullscreen manage hook and fullscreen event hook here to fully enable fullscreen support mentioned [[Layouts][earlier]]:
#+BEGIN_SRC haskell :tangle xmonad.hs
-- Apply fullscreen manage and event hooks
myFullscreenManageHook = fullscreenManageHook
myFullscreenEventHook = fullscreenEventHook

#+END_SRC

Next, I set up my event hook to put xmonad into server mode, which allows me to use [[https://github.com/xmonad/xmonad-contrib/blob/master/scripts/xmonadctl.hs][xmonadctl]] from [[https://github.com/xmonad/xmonad-contrib][xmonad-contrib]], which enables control of xmonad actions from the shell/scripts.
#+BEGIN_SRC haskell :tangle xmonad.hs
-- Server mode event hook
myEventHook = serverModeEventHook

#+END_SRC

Next I set up a =navigation2DConfig= for use with [[Window Management Keybinds][visual window movement]]:
#+BEGIN_SRC haskell :tangle xmonad.hs
-- navigation 2d config required for visual window movement
myNavigation2DConfig = def {layoutNavigation = [("Tall", hybridOf sideNavigation $ hybridOf centerNavigation lineNavigation), ("Full", hybridOf sideNavigation centerNavigation)]
                          , floatNavigation = hybridOf lineNavigation centerNavigation
                          , screenNavigation = hybridOf lineNavigation centerNavigation}

#+END_SRC

*** New Xmobar Setup
#+BEGIN_SRC haskell :tangle xmonad.hs
--myPP = def { ppCurrent = xmobarColor colorFocus "" }
myPP = xmobarPP { ppTitle = xmobarColor colorFocus "",
                  ppCurrent = xmobarStripTags ["NSP"] . xmobarColor colorFocus "",
                  ppVisible = xmobarStripTags ["NSP"] . xmobarColor colorSecondary "",
                  ppHidden = xmobarStripTags ["NSP"] . xmobarColor colorFg "",
                  ppHiddenNoWindows = xmobarStripTags ["NSP"] . xmobarColor color01 "",
                  ppOrder = \(ws : _) -> [ws],
                  ppSep = " "
                }
mySB = statusBarProp "xmobar" (pure myPP)

#+END_SRC
*** Startup Script
I have a startup script at =~/.xmonad/startup.sh= which starts various apps and sets up a few things.  In my xmonad config, it is autostarted by setting a =startupHook=.
#+BEGIN_SRC haskell :tangle xmonad.hs
-- Startup hook
myStartupHook = do
  spawnOnce ("~/.config/xmonad/startup.sh '" ++ colorBg ++ "' '" ++ colorFg ++ "' '" ++ colorFocus ++ "' '" ++ colorSecondary ++ "'")

#+END_SRC

First I start by retrieving the colors passed to the script from xmonad.
#+BEGIN_SRC sh :tangle startup.sh :tangle-mode (identity #o755)
colorBg=$1
colorFg=$2
colorFocus=$3
colorSecondary=$4

#+END_SRC

The autostart script kills all applications I am autostarting, which prevents multiple instances of background applications when I restart xmonad:
#+BEGIN_SRC sh :tangle startup.sh :tangle-mode (identity #o755)
# Startup shell script called by xmonad to start necessary programs
#
## Kill previous instances of applications (Prevents multiple instances of the following if XMonad is restarted durin the X session)
killall xmobar
killall nm-applet

#+END_SRC

Then, desktop applications are started in the background.
#+BEGIN_SRC sh :tangle startup.sh :tangle-mode (identity #o755)
# Launch necessary desktop applications
autorandr;
picom --animations --animation-window-mass 1 --animation-for-open-window zoom --animation-stiffness 200 --experimental-backends && # requires picom-pijulius
xset r rate 350 50 &
setxkbmap -option caps:escape &
~/.fehbg-stylix &
~/.config/xmobar/xmobar-st-check.sh &
alttab -w 1 -t 240x160 -i 64x64 -sc 1 -bg $colorBg -fg $colorFg -frame $colorSecondary -inact $colorFg &
##/usr/bin/trayer --edge top --align right --SetDockType true --SetPartialStrut true --expand true --widthtype request --transparent true --alpha 0 --height 28 --tint $trayertint --monitor "primary" &
nm-applet &
GOMAXPROCS=1 syncthing --no-browser &
protonmail-bridge --noninteractive &
emacs --daemon &
gnome-keyring-daemon --daemonize --login &
gnome-keyring-daemon --start --components=secrets &
#+END_SRC
** Main
Lastly, xmonad is started with all of the [[Settings][settings set up as variables]].  First xmobar is setup with =spawnPipe= so that it has access to the [[Workspaces][workspaces from xmonad]].  Then xmonad is executed with the settings.
#+BEGIN_SRC haskell :tangle xmonad.hs
-- Now run xmonad with all the defaults we set up.
main = do
  spawn ("xmobar -x 0")
  spawn ("xmobar -x 1")
  spawn ("xmobar -x 2")
  xmonad . withSB mySB $
    withNavigation2DConfig myNavigation2DConfig $
      fullscreenSupportBorder $
        docks $
         EWMHD.ewmh
          def
            { -- simple stuff
              terminal = myTerminal,
              focusFollowsMouse = myFocusFollowsMouse,
              clickJustFocuses = myClickJustFocuses,
              borderWidth = myBorderWidth,
              modMask = myModMask,
              workspaces = myWorkspaces,
              normalBorderColor = myNormalBorderColor,
              focusedBorderColor = myFocusedBorderColor,
              -- key bindings
              keys = myKeys,
              mouseBindings = myMouseBindings,
              -- hooks, layouts
              layoutHook = myLayout,
              manageHook = myManageHook <+> myFullscreenManageHook <+> namedScratchpadManageHook myScratchPads,
              handleEventHook = myEventHook <+> myFullscreenEventHook <+> fadeWindowsEventHook,
              logHook = (refocusLastLogHook >> nsHideOnFocusLoss myScratchPads),
              startupHook = myStartupHook
            }
#+END_SRC
* XMobar Config
I utilize xmobar as a status bar on my monitors.  To manage my xmobar config, I start by creating a template file, and then style that using stylix.
** Xmobar Template
This is my base xmobarrc. This is a full xmobar config with placeholders for the colors (i.e. =colorFgNormal=, =colorBgNormal=, =color01Normal=, =color01Bright=, etc...). [[./startup.sh][startup.sh]] copies this into =xmobarrc= with my current base16 color scheme. This also depends on =Inconsolata= and =Symbols Nerd Font=.
#+BEGIN_SRC haskell :tangle xmobarrc.mustache
Config { font = "Inconsolata 16"
       , additionalFonts = ["Symbols Nerd Font 14"]
       , border = NoBorder
       , bgColor = "#{{base00-hex}}"
       , alpha = 200
       , fgColor = "#{{base05-hex}}"
       , position = TopSize C 100 28
       , textOffset = -1
       , iconOffset = -1
       , lowerOnStart = True
       , pickBroadest = False
       , persistent = False
       , hideOnStart = False
       , iconRoot = "."
       , allDesktops = True
       , overrideRedirect = True
       , commands = [
                      Run XMonadLog
                    , Run Date "<fc=#{{base09-hex}}> <fn=1>\xf073</fn> %a %-m/%-d/%y %-I:%M:%S%P</fc>" "date" 10
                    , Run BatteryP ["BAT0"]
                      ["-t", "<acstatus>",
                      "-L", "10", "-H", "80", "-p", "3", "--",
                      "-O","<fc=#{{base05-hex}}><fn=1>\xf313</fn></fc>  <fc=#{{base0B-hex}}> <fn=1>\xf17e3</fn> <left>% </fc>",
                      "-i","<fc=#{{base05-hex}}><fn=1>\xf313</fn></fc>  <fc=#{{base0B-hex}}> <fn=1>\xf17e7</fn> <left>% </fc>",
                      "-o","<fc=#{{base05-hex}}><fn=1>\xf313</fn></fc>  <fc=#{{base08-hex}}> <fn=1>\xf17e4</fn> <left>% </fc>",
                      "-L", "-15", "-H", "-5",
                      "-l", "#{{base08-hex}}", "-m", "#{{base05-hex}}", "-h", "#{{base0B-hex}}"] 10
                    , Run Brightness
                      [ "-t", "<fc=#{{base0A-hex}}><fn=1>\xf0eb</fn> <percent>% </fc>", "--",
                        "-D", "amdgpu_bl1"
                      ] 2
                    , Run Volume "default" "Master"
                      [ "-t", "<status>", "--"
                      , "--on", "<fc=#{{base0D-hex}}> <fn=1>\xf028</fn> <volume>% </fc>"
                      , "--onc", "#{{base0D-hex}}"
                      , "--off", "<fc=#{{base0F-hex}}> <fn=1>\xf026</fn> Mute </fc>"
                      , "--offc", "#{{base0F-hex}}"
                      ] 1
                    , Run DynNetwork
                      [ "-t", "<fc=#{{base0D-hex}}><fn=1>\xf0200</fn> <dev></fc>"] 1
                    , Run Com "cat"
                      [ "/home/emmet/.st-status"] "syncthing" 10
                    , Run Com "echo"
                      [ "<fn=1>\xea77</fn> "] "syncthingsymbol" 0
                    , Run Com "echo"
                      [ "<fn=1>\xeb5c</fn> "] "artsymbol" 0
                    , Run Com "cat"
                      [ "/home/emmet/.currenttheme"] "currenttheme" 0
                    , Run Memory [ "-t", "<fc=#{{base08-hex}}><fn=1>\xf035b</fn> <usedratio>% (<used> GB)</fc>", "-d", "1", "--", "--scale", "1024"] 20
                    ]
       , sepChar = "%"
       , alignSep = "}{"
       , template = " %battery% %bright%<action=`xdotool key Super_L+y`>%default:Master%</action> %memory%  %artsymbol%%currenttheme%}%XMonadLog%{<action=`librewolf localhost:8384`>%syncthing%</action>  <action='networkmanager_dmenu'>%dynnetwork%</action> <action=`xdotool key Super_L+c`>%date%</action> "
       }
}

#+END_SRC

#+BEGIN_SRC shell :tangle xmobar-st-check.sh.mustache
#!/bin/sh
while true
  do
    curl localhost:8384 -m 1 &> /dev/null || echo '<fc=#{{base08-hex}}>❄ st off</fc>' > ~/.st-status;
    curl localhost:8384 -m 1 &> /dev/null && echo '<fc=#{{base0E-hex}}>↺ st on</fc>' > ~/.st-status;
    sleep 5;
  done
#+END_SRC

* Nix Integration
In order to have Nix put my xmonad/xmobar configuration in the proper places, I have [[./xmonad.nix][xmonad.nix]], which I source in the =imports= block of my [[../../home.nix][home.nix]].
#+BEGIN_SRC nix :tangle xmonad.nix
{ config, pkgs, ... }:

{

  imports = [ ../picom/picom.nix
              ../../lang/haskell/haskell.nix
              ../../app/terminal/alacritty.nix
              ../../app/terminal/kitty.nix
              ( import ../../app/dmenu-scripts/networkmanager-dmenu.nix {dmenu_command = "rofi -show dmenu"; inherit pkgs;})
            ];

  home.packages = with pkgs; [
    xmobar
    networkmanagerapplet
    dunst
    pamixer
    autorandr
    alacritty
    kitty
    dmenu
    rofi
    keepmenu
    networkmanager_dmenu
    pavucontrol
    feh
    flameshot
    alttab
    xdotool
    xclip
    ddcutil
    sct
    libnotify
    xorg.xkill
    killall
    bottom
    brightnessctl
    xorg.xcursorthemes
    xorg.xev
    xdg-utils
    xdg-desktop-portal
    xdg-desktop-portal-gtk
  ];

  home.file.".config/xmonad/xmonad.hs".source = ./xmonad.hs;
  home.file.".config/xmonad/startup.sh".source = ./startup.sh;

  home.file.".config/xmonad/lib/Colors/Stylix.hs".source = config.lib.stylix.colors {
    template = builtins.readFile ./lib/Colors/Stylix.hs.mustache;
    extension = ".hs";
  };

  home.file.".config/xmobar/xmobarrc".source = config.lib.stylix.colors {
    template = builtins.readFile ./xmobarrc.mustache;
    extension = "";
  };


  home.file.".config/xmobar/xmobar-st-check.sh" = {
    source = config.lib.stylix.colors {
      template = builtins.readFile ./xmobar-st-check.sh.mustache;
      extension = ".sh";
    };
    executable = true;
  };

  programs.feh.enable = true;
  programs.rofi.enable = true;

  services.autorandr.enable = true;
  programs.autorandr.enable = true;
  programs.autorandr.profiles = {
      "default" = {
        fingerprint = {
          eDP1 = "00ffffffffffff0051b8601500000000171e0104a522137807ee91a3544c99260f5054000000010101010101010101010101010101011434805070381f402b20750458c210000018000000fd0e302d505043010a20202020202000000010000a202020202020202020202020000000fc00544c3135365644585030310a2001d67013790000030114630401847f074f002a001f0037041e00160004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000df90";
        };
        config = {
          eDP-1 = {
            enable = true;
            primary = true;
            position = "0x0";
            mode = "1920x1080";
          };
        };
        hooks.postswitch = "xmonad --restart; ~/.fehbg-stylix;";
      };
      "dock" = {
        fingerprint = {
          eDP1 = "00ffffffffffff0051b8601500000000171e0104a522137807ee91a3544c99260f5054000000010101010101010101010101010101011434805070381f402b20750458c210000018000000fd0e302d505043010a20202020202000000010000a202020202020202020202020000000fc00544c3135365644585030310a2001d67013790000030114630401847f074f002a001f0037041e00160004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000df90";
          HDMI-1 = "00ffffffffffff0010ac48f04c4a56470619010380342078ea1df5ae4f35b3250d5054a54b008180a940d100714f0101010101010101283c80a070b023403020360006442100001a000000ff00595947434e35323247564a4c0a000000fc0044454c4c2055323431330a2020000000fd00384c1e5111000a2020202020200163020325f15090050403020716011f1213142015110623091f0767030c001000382d83010000023a801871382d40582c450006442100001e011d8018711c1620582c250006442100009e011d007251d01e206e28550006442100001e8c0ad08a20e02d10103e960006442100001800000000000000000000000000000000000016";
          DP-1-1 = "00ffffffffffff0010ac2ca0533836310e12010380342078eab325ac5130b426105054a54b008180a940714f01010101010101010101283c80a070b023403020360007442100001a000000ff004a55343336383356313638530a000000fc0044454c4c20323430385746500a000000fd00384c1e5311000a202020202020012002031bf14890050403020716012309070765030c00100083010000023a801871382d40582c450007442100001e011d8018711c1620582c250007442100009e011d007251d01e206e28550007442100001e8c0ad08a20e02d10103e96000744210000180000000000000000000000000000000000000000000000000000000047";
        };
        config = {
          eDP-1 = {
            enable = true;
            primary = true;
            position = "1000x1200";
            mode = "1920x1080";
          };
          HDMI-1 = {
            enable = true;
            position = "1920x0";
            mode = "1920x1200";
          };
          DP-1-1 = {
            enable = true;
            position = "0x0";
            mode = "1920x1200";
          };
        };
        hooks.postswitch = "xmonad --restart; ~/.fehbg-stylix;";
      };
  };
}
#+END_SRC
