nixos-config/user/wm/xmonad/xmonad.org
2023-05-15 18:19:26 -05:00

42 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

Color Variable Declarations

Next, I declare some variables necessary to setup 16-bit color schemes.

-- CUSTOM COLORS
colorSchemeList, colorSchemePrettyList :: [String]
colorBgNormalList, colorFgNormalList :: [String]
color01NormalList, color01BrightList, color02NormalList, color02BrightList :: [String]
color03NormalList, color03BrightList, color04NormalList, color04BrightList :: [String]
color05NormalList, color05BrightList, color06NormalList, color06BrightList :: [String]
color07NormalList, color07BrightList, color08NormalList, color08BrightList :: [String]
colorFocusList, colorSecondaryList :: [String]
colorScheme, colorSchemePretty :: String
colorBgNormal, colorFgNormal :: [Char]
color01Normal, color01Bright, color02Normal, color02Bright :: String
color03Normal, color03Bright, color04Normal, color04Bright :: String
color05Normal, color05Bright, color06Normal, color06Bright :: String
color07Normal, color07Bright, color08Normal, color08Bright :: String
colorFocus, colorSecondary :: String
gtkTheme :: String
alacrittyTheme :: String
doomEmacsTheme :: String

Color Scheme Enumerations

Next, I enumerate each color scheme as an integer.

gruvboxIndex, solarizedIndex, draculaIndex, tomorrowNightIndex, tokyoNightIndex, oceanicNextIndex, ubuntuIndex :: Int
gruvboxIndex = 0
solarizedIndex = 1
draculaIndex = 2
tomorrowNightIndex = 3
tokyoNightIndex = 4
oceanicNextIndex = 5
oldHopeIndex = 6
ubuntuIndex = 7

Color Scheme Arrays

Each enumerated color scheme from before corresponds to the index of the following arrays. These arrays are then used as the basis to select a color scheme.

-- color scheme arrays
colorSchemeList = ["gruvbox", "solarized", "dracula", "tomorrow-night", "tokyo-night", "oceanic-next", "old-hope"]
colorSchemePrettyList = ["Gruvbox Dark", "Solarized Dark", "Dracula", "Tomorrow Night", "Tokyo Night", "Oceanic Next", "Old Hope"]
gtkThemeList = ["MyGruvbox", "", "OfficialDracula", "MyGraphite", "", "MyOceanicNext", "SweetDark"] -- names of corresponding gtk themes
alacrittyThemeList = ["gruvbox_dark", "solarized_dark", "dracula", "tomorrow_night", "tokyo_night", "oceanic_next", "old_hope"]
doomEmacsThemeList = ["doom-gruvbox", "doom-solarized-dark", "doom-dracula", "doom-tomorrow-night", "doom-tokyo-night", "doom-oceanic-next", "doom-old-hope"]
colorBgNormalList = ["#282828", "#002b36", "#282a36", "#1d1f21", "#1a1b26", "#1b2b34", "#1c1d21"] -- normal bg
colorBgBrightList = ["#3b3838", "#113b3f", "#36343f", "#3d3f41", "#2a2b36", "#2b3b41", "#3c3d41"] -- lighter bg
trayerBgNormalList = ["0x00282828", "0x00002b36", "0x00282a36", "0x1d1f21", "0x1a1b26", "0x1b2b34", "0x1c1d21"] -- trayer tint
colorFgNormalList = ["#ebdbb2", "#839496", "#f8f8f2", "#c5c8c6", "#a9b1d6", "#d8dee9", "#cbcdd2"] -- normal fg
color01NormalList = ["#343428", "#073642", "#000000", "#1d1f21", "#32344a", "#29414f", "#45474f"] -- black
color01BrightList = ["#928374", "#002b36", "#555555", "#666666", "#444b6a", "#405860", "#65676f"] -- bright black
color02NormalList = ["#cc241d", "#dc3ddf", "#ff5555", "#cc6666", "#f7768e", "#ec5f67", "#eb3d54"] -- red
color02BrightList = ["#fb4934", "#cb4b16", "#ff1010", "#ff3334", "#ff7a93", "#ff3130", "#eb3d54"] -- bright red
color03NormalList = ["#98971a", "#859900", "#50fa7b", "#b5bd68", "#9ece6a", "#99c794", "#78bd65"] -- green
color03BrightList = ["#b8bb26", "#586e75", "#02fe03", "#9ec400", "#b9f27c", "#66fa56", "#78bd65"] -- bright green
color04NormalList = ["#d79921", "#b58900", "#f1fa8c", "#e6c547", "#e0af68", "#fac863", "#e5cd52"] -- yellow
color04BrightList = ["#fabd2f", "#657b83", "#ffff02", "#f0c674", "#ff9e64", "#ffca4f", "#e5cd52"] -- bright yellow
color05NormalList = ["#458588", "#268bd2", "#bd93f9", "#81a2be", "#7aa2f7", "#6699cc", "#4fb4d8"] -- blue
color05BrightList = ["#83a598", "#839496", "#4d31fd", "#81a2be", "#7da6ff", "#4477ee", "#4fb4d8"] -- bright blue
color06NormalList = ["#b16286", "#d33682", "#ff79c6", "#b29fbb", "#ad8ee6", "#c594c5", "#ef7c2a"] -- magenta
color06BrightList = ["#d3869b", "#6c71c4", "#ff20d8", "#b77ee0", "#bb9af7", "#d864d8", "#ef7c2a"] -- bright magenta
color07NormalList = ["#689d6a", "#2aa198", "#8be9fd", "#70c0ba", "#449dab", "#5fb3b3", "#4fb4d8"] -- cyan
color07BrightList = ["#8ec07c", "#93a1a1", "#03feff", "#54ced6", "#0db9d7", "#30d2d0", "#4fb4d8"] -- bright cyan
color08NormalList = ["#a89984", "#eee8d5", "#bbbbbb", "#676b71", "#787c99", "#65737e", "#cbcdd2"] -- white
color08BrightList = ["#ebdbb2", "#fdf6e3", "#ffffff", "#787a7e", "#acb0d0", "#d8dee9", "#cbcdd2"] -- bright white
colorFocusList = ["#458588", "#859900", "#ff79c6", "#e6c547", "#ff9e64", "#c594c5", "#eb3d54"] -- focus and run launcher color
colorSecondaryList = ["#d79921", "#dc3ddf", "#bbbbbb", "#70c0ba", "#0db9d7", "#fac863", "#4fb4d8"] -- secondary color

Choose Theme

A theme is selected by setting the myColorScheme variable to one of the color schemes enumerated before.

-- choose a color scheme
myColorScheme = oldHopeIndex

Setup Color Variables

-- setup color variables
colorScheme = colorSchemeList !! myColorScheme
colorSchemePretty = colorSchemePrettyList !! myColorScheme
gtkTheme = gtkThemeList !! myColorScheme
alacrittyTheme = alacrittyThemeList !! myColorScheme
doomEmacsTheme = doomEmacsThemeList !! myColorScheme
colorBgNormal = colorBgNormalList !! myColorScheme -- normal bg
colorBgBright = colorBgBrightList !! myColorScheme -- lighter bg
trayerBgNormal = trayerBgNormalList !! myColorScheme -- trayer tint
colorFgNormal = colorFgNormalList !! myColorScheme -- normal fg
color01Normal = color01NormalList !! myColorScheme -- black
color01Bright = color01BrightList !! myColorScheme -- bright black
color02Normal = color02NormalList !! myColorScheme -- red
color02Bright = color02BrightList !! myColorScheme -- bright red
color03Normal = color03NormalList !! myColorScheme -- green
color03Bright = color03BrightList !! myColorScheme -- bright green
color04Normal = color04NormalList !! myColorScheme -- yellow
color04Bright = color04BrightList !! myColorScheme -- bright yellow
color05Normal = color05NormalList !! myColorScheme -- blue
color05Bright = color05BrightList !! myColorScheme -- bright blue
color06Normal = color06NormalList !! myColorScheme -- magenta
color06Bright = color06BrightList !! myColorScheme -- bright magenta
color07Normal = color07NormalList !! myColorScheme -- cyan
color07Bright = color07BrightList !! myColorScheme -- bright cyan
color08Normal = color08NormalList !! myColorScheme -- white
color08Bright = color08BrightList !! myColorScheme -- bright white
colorFocus = colorFocusList !! myColorScheme -- focus and run launcher color
colorSecondary = colorSecondaryList !! myColorScheme

Settings

Border Color

-- Border colors for unfocused and focused windows, respectively.
myNormalBorderColor, myFocusedBorderColor :: String
myNormalBorderColor = colorBgNormal
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 = "alacritty -o font.size=18 --title cal-scratchpad -e calcurse"
    findCal = title =? "cal-scratchpad"
    manageCal = customFloating $ W.RationalRect l t w h
      where
        h = 0.6
        w = 0.6
        t = 0.65 - 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 dmenu
      --((modm, xK_semicolon), spawn ("dmenu_run -nb '" ++ colorBgNormal ++ "' -nf '" ++ color08Bright ++ "' -sb '" ++ colorFocus ++ "' -sf '" ++ color08Bright ++ "' -fn 'UbuntuMono-R:regular:pixelsize=28' -l 4 -p '➤'")),
      ((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}

Startup Script

I have a startup script at ~/.xmonad/startup.sh which starts various apps and sets up a few things. The script starts by taking some passed color values as strings from xmonad, which are set in the beginning of the config.

#!/bin/sh

trayertint=$1

nbColor=$2
nfColor=$3
sbColor=$4
sfColor=$5

themeGTKName=$6
themeAlacrittyName=$7
themeDoomEmacsName=$8

colorBgNormal=$2
colorBgBright=${27}
colorFgNormal=$3
color01Normal=$9
color01Bright=${10}
color02Normal=${11}
color02Bright=${12}
color03Normal=${13}
color03Bright=${14}
color04Normal=${15}
color04Bright=${16}
color05Normal=${17}
color05Bright=${18}
color06Normal=${19}
color06Bright=${20}
color07Normal=${21}
color07Bright=${22}
color08Normal=${23}
color08Bright=${24}
colorFocus=${25}
colorSecondary=${26}

In my xmonad config, it is then autostarted by setting a startupHook. Inside my startup hook, I pass the colors of my currently selected theme to the script:

-- Startup hook
myStartupHook = do
  spawnOnce ("~/.config/xmonad/startup.sh '" ++ trayerBgNormal ++ "' '" ++ colorBgNormal ++ "' '" ++ color08Bright ++ "' '" ++ colorFocus ++ "' '" ++ color08Bright ++ "' '" ++ gtkTheme ++ "' '" ++ alacrittyTheme ++ "' '" ++ doomEmacsTheme ++ "' '" ++ color01Normal ++ "' '" ++ color01Bright ++ "' '" ++ color02Normal ++ "' '" ++ color02Bright ++ "' '" ++ color03Normal ++ "' '" ++ color03Bright ++ "' '" ++ color04Normal ++ "' '" ++ color04Bright ++ "' '" ++ color05Normal ++ "' '" ++ color05Bright ++ "' '" ++ color06Normal ++ "' '" ++ color06Bright ++ "' '" ++ color07Normal ++ "' '" ++ color07Bright ++ "' '" ++ color08Normal ++ "' '" ++ color08Bright ++ "' '" ++ colorFocus ++ "' '" ++ colorSecondary ++ "' '" ++ colorBgBright ++ "'")

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 &
twmnd &
alttab -w 1 -t 240x160 -i 64x64 -sc 1 -bg $colorBgNormal -fg $colorFgNormal -frame $colorSecondary -inact $colorFgNormal &
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 &

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 colorFgNormal "",
                  ppHiddenNoWindows = xmobarStripTags ["NSP"] . xmobarColor colorBgBright "",
                  ppOrder = \(ws : _) -> [ws],
                  ppSep = " "
                }
mySB = statusBarProp "xmobar" (pure myPP)

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 configs, three main files are used:

  • base-xmobarrc which contains the structure of my xmobar config
  • startup.sh which has a section which generates a copy of my xmobarrcs for the current color scheme I set in xmonad.hs

Base xmobarrc

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 = "colorBgNormal"
       , alpha = 200
       , fgColor = "colorFgNormal"
       , 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=color06Normal> <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=colorFgNormal><fn=1>\xf313</fn></fc> <fc=color03Normal> <fn=1>\xf17e3</fn><left>% </fc>",
                      "-i","<fc=colorFgNormal><fn=1>\xf313</fn></fc> <fc=color03Normal> <fn=1>\xf17e7</fn><left>% </fc>",
                      "-o","<fc=colorFgNormal><fn=1>\xf313</fn></fc> <fc=color02Normal> <fn=1>\xf17e4</fn><left>% </fc>",
                      "-L", "-15", "-H", "-5",
                      "-l", "color02Normal", "-m", "color05Normal", "-h", "color03Normal"] 10
                    , Run Brightness
                      [ "-t", "<fc=color04Normal><fn=1>\xf0eb</fn> <percent>% </fc>", "--",
                        "-D", "amdgpu_bl1"
                      ] 2
                    , Run Volume "default" "Master"
                      [ "-t", "<status>", "--"
                      , "--on", "<fc=color07Normal> <fn=1>\xf028</fn> <volume>% </fc>"
                      , "--onc", "color07Normal"
                      , "--off", "<fc=color06Normal> <fn=1>\xf026</fn> Mute </fc>"
                      , "--offc", "color06Normal"
                      ] 1
                    ]
       , sepChar = "%"
       , alignSep = "}{"
       , template = " %battery% %bright%<action=`xdotool key Super_L+y`>%default:Master%</action>}<box color=colorBgBright width=0>%XMonadLog%</box>{<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/xmobar/base-xmobarrc".source = ./base-xmobarrc;
  home.file.".config/xmonad/startup.sh".source = ./startup.sh;
}