--
-- Xmonad config for Solomon Laing
--
-- Base
import XMonad
import System.IO (hPutStrLn)
import System.Exit (ExitCode(ExitSuccess), exitWith )
import qualified XMonad.StackSet as W
-- Actions
import XMonad.Actions.CopyWindow (kill1)
import XMonad.Actions.CycleWS (moveTo, shiftTo, WSType(..), nextScreen, prevScreen)
import XMonad.Actions.GridSelect
import XMonad.Actions.MouseResize
import XMonad.Actions.Promote
import XMonad.Actions.RotSlaves (rotSlavesDown, rotAllDown)
import qualified XMonad.Actions.TreeSelect as TS
import XMonad.Actions.WindowGo (runOrRaise)
import XMonad.Actions.WithAll (sinkAll, killAll)
import qualified XMonad.Actions.Search as S
-- Data
import Data.Char (isSpace, toUpper)
import Data.Maybe ( fromJust, isJust )
import Data.Monoid
import Data.Tree
import qualified Data.Map as M
-- Hooks
import XMonad.Hooks.DynamicLog (dynamicLogWithPP, wrap, xmobarPP, xmobarColor, shorten, PP(..))
import XMonad.Hooks.FadeInactive
import XMonad.Hooks.ManageDocks (docks, avoidStruts, docksEventHook, manageDocks, ToggleStruts(..))
import XMonad.Hooks.ManageHelpers (isFullscreen, doFullFloat, doCenterFloat)
import XMonad.Hooks.EwmhDesktops (ewmh, fullscreenEventHook)
import XMonad.Hooks.ServerMode
import XMonad.Hooks.SetWMName
import XMonad.Hooks.WorkspaceHistory
-- Layouts
import XMonad.Layout.GridVariants (Grid(Grid))
import XMonad.Layout.SimplestFloat
import XMonad.Layout.Spiral
import XMonad.Layout.ResizableTile
import XMonad.Layout.Tabbed
import XMonad.Layout.ThreeColumns
-- Layouts modifiers
--import XMonad.Layout.Fullscreen
import XMonad.Layout.LayoutModifier
import XMonad.Layout.LimitWindows (limitWindows, increaseLimit, decreaseLimit)
import XMonad.Layout.Magnifier
import XMonad.Layout.MultiToggle (mkToggle, single, EOT(EOT), (??))
import XMonad.Layout.MultiToggle.Instances (StdTransformers(NBFULL, MIRROR, NOBORDERS))
import XMonad.Layout.NoBorders
import XMonad.Layout.Renamed
import XMonad.Layout.ShowWName
import XMonad.Layout.Simplest
import XMonad.Layout.Spacing
import XMonad.Layout.SubLayouts
import XMonad.Layout.WindowNavigation
import XMonad.Layout.WindowArranger (windowArrange, WindowArrangerMsg(..))
import qualified XMonad.Layout.ToggleLayouts as T (toggleLayouts, ToggleLayout(Toggle))
import qualified XMonad.Layout.MultiToggle as MT (Toggle(..))
-- Utilities
import XMonad.Util.EZConfig (additionalKeysP)
import XMonad.Util.Run (runProcessWithInput, safeSpawn, spawnPipe)
import XMonad.Util.SpawnOnce
--THEME
-- #dfdfef (120,120,120)
-- #9da5ae (157,165,174)
-- #6b788b (107,120,139)
-- #5c6776 (92,103,118)
-- #45526d (69,82,109)
-- #121e32 (18,30,49)
-- The preferred terminal program, which is used in a binding below and by
-- certain contrib modules.
myTerminal :: String
myTerminal = "alacritty"
myFileManager :: String
myFileManager = "thunar"
-- The font to be used
myFont :: String
myFont = "xft:Fira Code:antialias=true:hinting=true"
-- Whether focus follows the mouse pointer.
myFocusFollowsMouse :: Bool
myFocusFollowsMouse = True
-- Whether clicking on a window to focus also passes the click to the window
myClickJustFocuses :: Bool
myClickJustFocuses = False
-- Width of the window border in pixels.
myBorderWidth :: Dimension
myBorderWidth = 3
-- Border colors for unfocused and focused windows, respectively.
myNormalBorderColor :: String
myNormalBorderColor = "#121e32"
myFocusedBorderColor :: String
myFocusedBorderColor = "#6b788b"
-- modMask lets you specify which modkey you want to use. The default
-- is mod1Mask ("left alt"). You may also consider using mod3Mask
-- ("right alt"), which does not conflict with emacs keybindings. The
-- "windows key" is usually mod4Mask.
myModMask :: KeyMask
myModMask = mod4Mask
-- The default number of workspaces (virtual screens) and their names.
-- By default we use numeric strings, but any string may be used as a
-- workspace name. The number of workspaces is determined by the length
-- of this list.
--
-- A tagging example:
--
-- > workspaces = ["web", "irc", "code" ] ++ map show [4..9]
xmobarEscape :: String -> String
xmobarEscape = concatMap doubleLts
where
doubleLts '<' = "<<"
doubleLts x = [x]
myWorkspaces :: [String]
-- myWorkspaces = [" 1 ", " 2 ", " 3 ", " 4 ", " 5 ", " 6 ", " 7 ", " 8 ", " 9 "]
myWorkspaces = ["web", "game", "dev", "doc", "mus", "vid", "sys", "chat", "virt"]
myWorkspaceIndices = M.fromList $ zip myWorkspaces [1..] -- (,) == \x y -> (x,y)
clickable ws = ""++ws++""
where i = fromJust $ M.lookup ws myWorkspaceIndices
------------------------------------------------------------------------
-- Key bindings. Add, modify or remove key bindings here.
-- These are general keybindings custom script bindings are not included.h
myEZKeys :: [(String, X ())]
myEZKeys =
--START_KEYS
--NOTE xmonad
[ ("M-'", spawn "xmonad --recompile; xmonad --restart") -- restart xmonad
--NOTE launchers
, ("M-a", spawn "dmenu_run -bw 3 -c -l 15 -h 26") -- open dmenu
--NOTE keybound programs
, ("M-S-", spawn (myTerminal)) -- open a terminal
, ("M-l", spawn (myFileManager)) -- open a file manager
--NOTE wm controls
, ("M-S-j", kill1) -- kill focused windown
, ("M-", sendMessage NextLayout) -- rotate through layouts
-- , ("M-S-", setLayout $ Xmonad.layoutHook conf) -- reset layout to default
, ("M-S-y", refresh) -- reset current window to correct size
, ("M-", windows W.focusDown) -- focus next window
, ("M-S-", windows W.focusUp) -- focus previous window
, ("M-m", windows W.focusMaster) -- focus master window
, ("M-", windows W.swapMaster) -- swap window with master
, ("M-S-", windows W.swapDown) -- swap window with next window
, ("M-S-", windows W.swapUp) -- swap window with previous window
, ("M-", sendMessage Shrink) -- shrink master area
, ("M-", sendMessage Expand) -- grow master area
, ("M-", sendMessage MirrorShrink) -- shrink master area
, ("M-", sendMessage MirrorExpand) -- grow master area
, ("M-y", withFocused $ windows . W.sink) -- push floating window back into tiling
, ("M-w", sendMessage (IncMasterN 1)) -- increment windows in master
, ("M-v", sendMessage (IncMasterN (-1))) -- decrement windows in master
, ("M-b", sendMessage ToggleStruts) -- toggle bar
--NOTE my custom keybind/script combinations
--NOTE system controls
, ("M-S-s", spawn "/usr/local/bin/prompt \"Are you sure you want to Shutdown?\" \"shutdown now\"") -- prompt computer shutdown
, ("M-S-r", spawn "/usr/local/bin/prompt \"Are you sure you want to Restart?\" \"reboot\"") -- prompt computer restart
, ("M-", spawn "/usr/local/bin/prompt \"Are you sure you want to lock?\" \"slock\" 1") -- prompt computer lock
--NOTE bitwarden controls
, ("M-S-x", spawn "$HOME/.local/bin/bw-unlock") -- unlock bitwarden cli
, ("M-x", spawn "$HOME/.local/bin/passwords") -- get password from bw cli
--NOTE demenu search and browser
, ("M-s s", spawn "/usr/local/bin/dmenusearch") -- launch searcher (dmenu and surf)
, ("M-s d", spawn "/usr/local/bin/dmenusearch duckduckgo") -- launch searcher, default to duckduckgo
, ("M-b b", spawn myBrowser) -- launch my browser
, ("M-b f", spawn "firefox") -- launch firefox
, ("M-b s", spawn "surf") -- launch surf
--NOTE drive mounting
, ("M-d m", spawn "/usr/local/bin/dmenumount") -- launch mounter
, ("M-d u", spawn "/usr/local/bin/dmenuumount") -- launch unmounter
--NOTE music and audio controls
, ("M-S-o", spawn "/usr/local/bin/set-default-sink") -- set default output
, ("M-S-i", spawn "/usr/local/bin/set-default-source") -- set default input
, ("", spawn "/usr/local/bin/dec-sink-volume") -- decrease output volume
, ("", spawn "/usr/local/bin/inc-sink-volume") -- increase output volume
, ("", spawn "/usr/local/bin/toggle-sink-mute") -- toggle output mute
, ("S-", spawn "/usr/local/bin/dec-source-volume") -- decrease input volume
, ("S-", spawn "/usr/local/bin/inc-source-volume") -- increase input volume
, ("S-", spawn "/usr/local/bin/toggle-source-mute") -- toggle input mute
--NOTE extras
, ("M-S-/", spawn "/usr/local/bin/xmonad-keys") -- show keybinds
, ("", spawn "scrot") -- take full (all monitors) screenshot
, ("M-", spawn "flameshot gui") -- launch flameshot for snipping
]
myKeys conf@XConfig {XMonad.modMask = modm} = M.fromList $
-- 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)]]
-- 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-button2, Raise the window to the top of the stack, push to master.
, ((modm, button2), (\w -> focus 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_KEYS
------------------------------------------------------------------------
-- Layouts:
--Makes setting the spacingRaw simpler to write. The spacingRaw module adds a configurable amount of space around windows.
mySpacing :: Integer -> l a -> XMonad.Layout.LayoutModifier.ModifiedLayout Spacing l a
mySpacing i = spacingRaw True (Border i i i i) True (Border i i i i) True
-- Defining a bunch of layouts, many that I don't use.
-- limitWindows n sets maximum number of windows displayed for layout.
-- mySpacing n sets the gap size around the windows.
tall = renamed [Replace "tall"]
$ limitWindows 12
$ mySpacing 8
-- $ Mirror
$ ResizableTall 1 (3/100) (1/2) []
magnify = renamed [Replace "magnify"]
$ magnifier
$ limitWindows 12
$ mySpacing 8
$ ResizableTall 1 (3/100) (1/2) []
monocle = renamed [Replace "monocle"]
$ limitWindows 20 Full
floats = renamed [Replace "floats"]
$ limitWindows 20 simplestFloat
grid = renamed [Replace "grid"]
$ limitWindows 12
$ mySpacing 8
$ mkToggle (single MIRROR)
$ Grid (16/10)
spirals = renamed [Replace "spirals"]
$ mySpacing 8
$ spiral (6/7)
threeCol = renamed [Replace "threeCol"]
$ limitWindows 7
$ ThreeCol 1 (3/100) (1/2)
threeRow = renamed [Replace "threeRow"]
$ limitWindows 7
-- Mirror takes a layout and rotates it by 90 degrees.
-- So we are applying Mirror to the ThreeCol layout.
$ Mirror
$ ThreeCol 1 (3/100) (1/2)
tabs = renamed [Replace "tabs"]
-- I cannot add spacing to this layout because it will
-- add spacing between window and tabs which looks bad.
$ tabbed shrinkText myTabTheme
-- setting colors for tabs layout and tabs sublayout.
myTabTheme = def { fontName = myFont ++ ":size=12"
, activeColor = "#6b788b"
, inactiveColor = "#121e32"
, activeBorderColor = "#6b788b"
, inactiveBorderColor = "#121e32"
, activeTextColor = "#121e32"
, inactiveTextColor = "#dfdfef"
}
-- Theme for showWName which prints current workspace when you change workspaces.
myShowWNameTheme :: SWNConfig
myShowWNameTheme = def
{ swn_font = myFont ++ ":size=32"
, swn_fade = 1.0
, swn_bgcolor = "#121e32"
, swn_color = "#dfdfef"
}
-- The layout hook
myLayout = avoidStruts $ mouseResize $ windowArrange $ T.toggleLayouts floats
$ mkToggle (NBFULL ?? NOBORDERS ?? EOT) myDefaultLayout
where
-- I've commented out the layouts I don't use.
myDefaultLayout =
spirals
||| tall
||| noBorders monocle
||| noBorders tabs
-- unused layouts
-- ||| grid
-- ||| floats
-- ||| magnify
-- ||| threeRow
-- ||| threeCol
------------------------------------------------------------------------
-- Window rules:
-- Execute arbitrary actions and WindowSet manipulations when managing
-- a new window. You can use this to, for example, always float a
-- particular program, or have a client always appear on a particular
-- workspace.
-- To find the property name associated with a program, use
-- > xprop | grep WM_CLASS
-- and click on the client you're interested in.
-- To match on the WM_NAME, you can use 'title' in the same way that
-- 'className' and 'resource' are used below.
myManageHook = composeAll
[ className =? "Gimp" --> doFloat
, className =? "guake" --> doIgnore
, className =? "barrier" --> doFloat
, className =? "net-runelite-client-RuneLite" --> doFloat
, className =? "Yad" --> doCenterFloat
, className =? "net-runelite-launcher-Launcher" --> doIgnore
, className =? "steam_app_221380" --> doIgnore
, resource =? "desktop_window" --> doIgnore
, (className =? "firefox" <&&> resource =? "Dialog")--> doFloat -- Float Firefox Dialog
, resource =? "kdesktop" --> doIgnore
, className =? "MusicBrainz Picard" --> doIgnore
, className =? "Steam" --> doShift ( myWorkspaces !! (7-1) )
, className =? "discord" --> doShift ( myWorkspaces !! (8-1) )
, className =? "telegram-desktop" --> doShift ( myWorkspaces !! (8-1) )
, className =? "Element" --> doShift ( myWorkspaces !! (8-1) )
, className =? "plexamp" --> doShift ( myWorkspaces !! (5-1) )
, className =? "Pavucontrol" --> doShift ( myWorkspaces !! (5-1) )]
------------------------------------------------------------------------
-- Event handling
-- * EwmhDesktops users should change this to ewmhDesktopsEventHook
-- Defines a custom handler function for X Events. The function should
-- return (All True) if the default handler is to be run afterwards. To
-- combine event hooks use mappend or mconcat from Data.Monoid.
myEventHook = mempty
-- Status bars and logging
-- Perform an arbitrary action on each internal state change or X event.
-- See the 'XMonad.Hooks.DynamicLog' extension for examples.
myLogHook :: X ()
myLogHook = fadeInactiveLogHook fadeAmount
where fadeAmount = 1.0
------------------------------------------------------------------------
-- Startup hook
-- Perform an arbitrary action each time xmonad starts or is restarted
-- with mod-q. Used by, e.g., XMonad.Layout.PerWorkspace to initialize
-- per-workspace layout choices.
-- By default, do nothing.
myStartupHook = do
-- Startup apps (tray) and settings
spawnOnce "volctl &"
spawnOnce "nm-applet &"
spawnOnce "blueman-applet &"
spawnOnce "/usr/lib/kdeconnectd &"
spawnOnce "kdeconnect-indicator &"
spawnOnce "dunst &" -- notification daemon
-- Startup Applications
spawnOnce "barrier &"
spawnOnce "nextcloud &"
spawnOnce "slack &"
spawnOnce "todoist &"
spawnOnce "element-desktop --hidden &"
spawnOnce "flameshot"
-- Start trayer last
spawnOnce "trayer --edge top --align right --widthtype request --padding 6 --SetDockType true --SetPartialStrut true --expand true --monitor 1 --transparent true --alpha 0 --tint 0x121e32 --height 20 &"
------------------------------------------------------------------------
-- Now run xmonad with all the defaults we set up.
-- Run xmonad with the settings you specify. No need to modify this.
main :: IO ()
main = do
-- need to learn more haskell but I want to do something like the following
-- need this because I share configs over computers
-- let toProc = ""
-- let host = spawnOnce "cat /etc/hostname"
-- if host isSubsequenceOf "archdesktop"
-- then let toProc = "xmobar -x 0 /home/solomon/.config/xmobar/xmobarrc-desktop.hs"
-- else let toProc = "xmobar -x 0 /home/solomon/.config/xmobar/xmobarrc-laptop.hs"
-- xmproc0 <- spawnPipe toProc
xmproc0 <- spawnPipe "xmobar -x 0 /home/solomon/.config/xmobar/xmobarrc-main.hs"
xmproc1 <- spawnPipe "xmobar -x 1 /home/solomon/.config/xmobar/xmobarrc-sub.hs"
xmproc2 <- spawnPipe "xmobar -x 2 /home/solomon/.config/xmobar/xmobarrc-sub.hs"
xmonad $ ewmh $ docks def
-- defaults = 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 = showWName' myShowWNameTheme myLayout,
manageHook = ( isFullscreen --> doFullFloat ) <+> myManageHook <+> manageDocks,
handleEventHook = myEventHook <+> fullscreenEventHook,
-- logHook = myLogHook,
logHook = myLogHook <+> dynamicLogWithPP xmobarPP
{ ppOutput = \x -> hPutStrLn xmproc0 x
>> hPutStrLn xmproc1 x
>> hPutStrLn xmproc2 x
, ppCurrent = xmobarColor "#a8de45" "" . wrap "[" "]" -- Current workspace in xmobar
, ppVisible = xmobarColor "#88ae55" "" . clickable -- Visible but not current workspace
, ppHidden = xmobarColor "#82AAFF" "" . wrap "*" "" . clickable -- Hidden workspaces in xmobar
, ppHiddenNoWindows = xmobarColor "#9da5ae" "" . clickable -- Hidden workspaces (no windows)
-- , ppTitle = xmobarColor "#b3afc2" "" . shorten 20 -- Title of active window in xmobar
, ppSep = " | " -- Separators in xmobar
, ppUrgent = xmobarColor "#C45500" "" . wrap "!" "!" -- Urgent workspace
-- , ppExtras = [windowCount] -- # of windows current workspace
-- , ppOrder = \(ws:l:t:ex) -> [ws,l]++ex++[t]
, ppOrder = \(ws:l:t:ex) -> [ws,l]
},
startupHook = myStartupHook
} `additionalKeysP` myEZKeys