nixos-config/user/wm/xmonad/xmonad.org
2023-05-25 20:27:11 -05:00

35 KiB

Xmonad Config

XMonad Config

The main configuration file for XMonad is ~/.xmonad/xmonad.hs.

Imports

First I import a bunch of libraries:

-- 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

Theme Setup

Custom Color Library Template

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

Import Custom Color Library

-- setup color variables
import Colors.Stylix

Settings

Border Color

-- Border colors for unfocused and focused windows, respectively.
myNormalBorderColor, myFocusedBorderColor :: String
myNormalBorderColor = colorBg
myFocusedBorderColor = colorFocus

Default Apps

-- Default apps
myTerminal, myBrowser :: String
myTerminal = "alacritty -o font.size=20"
myBrowser = "librewolf"

Mouse Focus

-- 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

Border Width

-- Width of the window border in pixels.
myBorderWidth :: Dimension
myBorderWidth = 3

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.

-- Modmask
myModMask :: KeyMask
myModMask = mod4Mask

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 nerd font symbol code. This works nicely because I have a Nerd Font as fn=1 in my xmobar, which renders the nerd font glyphs in xmobar.

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

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.

-- 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 "cmus" spawnCmus findCmus manageCmus,
    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 = myTerminal ++ " --title ranger-scratchpad -e ranger"
    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 = "flatpak run com.discordapp.Discord"
    findDiscord = className =? "discord"
    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
    spawnCmus = myTerminal ++ " -o font.size=28 --title cmus-scratchpad -e cmus && cmus-remote -R && cmus-remote -S"
    findCmus = title =? "cmus-scratchpad"
    manageCmus = 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

Keybindings

Keybinds can be set with an array of values like: (keybind, action). The array is declared like so:

myKeys conf@(XConfig {XMonad.modMask = modm}) =
  M.fromList $
    [
    -- insert keybinds with array values of ((keybind, action))

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)
      -- launch a terminal
      ((modm, xK_Return), spawn $ XMonad.terminal conf),

      -- launch emacsclient
      ((modm, xK_a), spawn "emacsclient -c -a 'emacs'"),

      -- 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"),
Generic Keybindings

These setup standard bindings for brightness and audio control from the keyboard.

      -- 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"),

      -- manage multiple monitors with kbd
      -- ((0, xF86XK_Explorer), spawn "/home/librephoenix/.local/bin/setup_external_monitor.sh"),
      -- ((0, xK_F8), spawn "/home/librephoenix/.local/bin/setup_external_monitor.sh"),
Launcher Keybinds

I have rofi bound to S-; for quick app access.

      -- launch rofi
      ((modm, xK_semicolon), spawn ("rofi -show drun -show-icons")),
      ((modm, xK_p), spawn ("keepmenu")),
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 Navigation2Dconfig)
S-{h,j,k,l} Switch to window visually {left,down,up,right} (requires a Navigation2Dconfig)
S-Shift-{h,j,k,l} Swap window visually {left,down,up,right} on current workspace (requires a 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 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:

      -- 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), 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))),
Scratchpad Keybinds

I have each 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:

      -- 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 "cmus"),
      ((modm, xK_c), namedScratchpadAction myScratchPads "cal"),
      ((modm, xK_y), namedScratchpadAction myScratchPads "pavucontrol"),
      ((modm, xK_slash), namedScratchpadAction myScratchPads "helpmenu")
End of Standard Keybinds

To finish the section of standard keybinds, we simply close the array started above.

      ]
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:

      ++
      -- 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)]
      ]
Custom Function Definitions

To have toggleFloat and warpToCurrentScreen, I must define them after setting up the keybinds like so:

  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
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
-- 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)
    ]

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 fullscreen manage hook)
  • draggingVisualizer so that I can drag tiling windows about via my mouse bindings
  • avoidStruts since I use 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 function:

-- 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
        }

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 function when starting xmonad.

-- 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 =? "discord" --> (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"

I also must set my fullscreen manage hook and fullscreen event hook here to fully enable fullscreen support mentioned earlier:

-- Apply fullscreen manage and event hooks
myFullscreenManageHook = fullscreenManageHook
myFullscreenEventHook = fullscreenEventHook

Next, I set up my event hook to put xmonad into server mode, which allows me to use xmonadctl from xmonad-contrib, which enables control of xmonad actions from the shell/scripts.

-- Server mode event hook
myEventHook = serverModeEventHook

Next I set up a navigation2DConfig for use with visual window movement:

-- 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}

New Xmobar Setup

--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)

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.

-- Startup hook
myStartupHook = do
  spawnOnce ("~/.config/xmonad/startup.sh '" ++ colorBg ++ "' '" ++ colorFg ++ "' '" ++ colorFocus ++ "' '" ++ colorSecondary ++ "'")

First I start by retrieving the colors passed to the script from xmonad.

colorBg=$1
colorFg=$2
colorFocus=$3
colorSecondary=$4

The autostart script kills all applications I am autostarting, which prevents multiple instances of background applications when I restart xmonad:

# 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 trayer
killall nm-applet
killall nextcloud
killall xautolock
killall caffeine
killall syncthing-gtk
killall discord
killall qjoypad

Then, desktop applications are started in the background.

# Launch necessary desktop applications
emacs --daemon &
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 &
twmnd &
alttab -w 1 -t 240x160 -i 64x64 -sc 1 -bg $colorBg -fg $colorFg -frame $colorSecondary -inact $colorFg &
autokey-gtk &
##/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 &
rclone mount adantium-nextcloud:/ ~/Nextcloud &
syncthing-gtk -m &
protonmail-bridge --no-window
~/.local/bin/setup-external-monitor.sh &
rm -rf ~/org &
gnome-keyring-daemon --daemonize --login &
gnome-keyring-daemon --start --components=secrets &

Main

Lastly, xmonad is started with all of the settings set up as variables. First xmobar is setup with spawnPipe so that it has access to the workspaces from xmonad. Then xmonad is executed with the settings.

-- 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
            }

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 copies this into xmobarrc with my current base16 color scheme. This also depends on Inconsolata and Symbols Nerd Font.

Config { font = "Inconsolata 18"
       , 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 "echo"
                      [ "<fc=#{{base0E-hex}}><fn=1>\xea77</fn> st</fc>"] "syncthing" 0
                    ]
       , sepChar = "%"
       , alignSep = "}{"
       , template = " %battery% %bright%<action=`xdotool key Super_L+y`>%default:Master%</action>}%XMonadLog%{<action=`librewolf localhost:8384`>%syncthing%</action> <action='networkmanager_dmenu'>%dynnetwork%</action><action=`xdotool key Super_L+c`>%date%</action> "
       }
}

Nix Integration

In order to have Nix put my xmonad/xmobar configuration in the proper places, I have xmonad.nix, which I source in the imports block of my home.nix.

{ config, pkgs, ... }:

{

  imports = [ ../picom/picom.nix ];

  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.packages = with pkgs; [
    xmobar
    dunst
  ];
}