-- Base
import XMonad
import System.Exit (exitSuccess)
import System.IO (hPutStrLn, Handle)
import qualified XMonad.StackSet as W
-- Actions
import XMonad.Actions.CopyWindow (kill1)
import XMonad.Actions.CycleWS (Direction1D(..), moveTo, shiftTo, WSType(..), nextScreen, prevScreen)
import XMonad.Actions.MouseResize
import XMonad.Actions.Promote
import XMonad.Actions.RotSlaves (rotSlavesDown, rotAllDown)
import XMonad.Actions.WithAll (sinkAll, killAll)
-- Data
import Data.Maybe (isJust, fromJust)
import Data.Monoid
import qualified Data.Map as M
-- Hooks
import XMonad.Hooks.DynamicLog (dynamicLogWithPP, wrap, xmobarPP, xmobarColor, shorten, PP(..))
import XMonad.Hooks.ManageDocks (avoidStruts, docksEventHook, manageDocks, ToggleStruts(..))
import XMonad.Hooks.EwmhDesktops
import XMonad.Hooks.ManageHelpers (isFullscreen, doFullFloat)
-- Layouts
import XMonad.Layout.GridVariants (Grid(Grid))
import XMonad.Layout.SimplestFloat
import XMonad.Layout.Spiral
import XMonad.Layout.Tabbed
import XMonad.Layout.ResizableTile
-- Layouts modifiers
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.WindowArranger (windowArrange)
import XMonad.Layout.WindowNavigation
import qualified XMonad.Layout.ToggleLayouts as T (toggleLayouts, ToggleLayout(Toggle))
import qualified XMonad.Layout.MultiToggle as MT (Toggle(..))
-- Utilities
import XMonad.Util.Dmenu
import XMonad.Util.EZConfig (additionalKeysP)
import XMonad.Util.Run (spawnPipe)
import XMonad.Util.SpawnOnce
-----------------------------------------------------------------------------
-- Basic settings:
-- Set the modkey
-- mod1Mask: left alt, mod4Mask: super key.
myModMask :: KeyMask
myModMask = mod4Mask
-- Preferred programs
myTerminal = "alacritty"
myBrowser = "firefox"
-- Preferred font
myFont :: String
myFont = "xft:SauceCodePro Nerd Font Mono:regular:size=9:antialias=true:hinting=true"
-- Width of the window border in pixels
myBorderWidth :: Dimension
myBorderWidth = 2
-- Border color of normal windows
myNormalBorderColor :: String
myNormalBorderColor = "#3b4252"
-- Border color of focused windows
myFocusedBorderColor :: String
myFocusedBorderColor = "#bc96da"
-- Default workspaces. Number of workspaces is determined by the list length.
myWorkspaces = ["dev", "www", "sys", "chat", "mus", "vid", "doc", "virt", "etc"]
myWorkspaceIndices = M.fromList $ zipWith (,) myWorkspaces [1..] -- (,) == \x y -> (x,y)
-- Make the workspaces clickable
clickable ws = ""++ws++""
where i = fromJust $ M.lookup ws myWorkspaceIndices
-- Keep track of the number of windows in current workspace
windowCount :: X (Maybe String)
windowCount = gets $ Just . show . length . W.integrate' . W.stack . W.workspace . W.current . windowset
-------------------------------------------------------------------------------
-- Key bindings with EZConfig:
-- C: Ctrl, S: Shift, M: Mod, M1: Alt
myKeys :: [(String, X ())]
myKeys =
-- XMonad
[ ("M-S-r", spawn "xmonad --recompile; xmonad --restart") -- Recompiles xmonad
, ("M-S-q", io exitSuccess) -- Quits xmonad
-- Programs
, ("M-b", spawn (myBrowser))
, ("M-", spawn (myTerminal))
, ("M-M1-h", spawn (myTerminal ++ " -e htop"))
, ("M-M1-b", spawn (myTerminal ++ " -e bpytop"))
, ("M-M1-p", spawn (myTerminal ++ " -e ipython"))
-- Dmenu
, ("M-S-", spawn "dmenu_run -i -p \"Run: \"")
-- Screenshots
, ("", spawn "flameshot gui")
, ("M-", spawn "flameshot screen -p ~/Pictures/Screenshots")
, ("M-S-", spawn "flameshot screen -c")
, ("C-", spawn "flameshot full -p ~/Pictures/Screenshots")
, ("C-S-", spawn "flameshot full -c")
-- Script shortcuts
, ("M-S-p", spawn "setbg ~/Pictures/Wallpapers/Active") -- Set random background
, ("M-S-d", spawn "displayselect")
-- Kill windows
, ("M-w", kill1) -- Kill the currently focused client
, ("M-S-w", killAll) -- Kill all windows on current workspace
-- Compositor
, ("M-C-x", spawn "picom -b") -- Run picom compositor
, ("M-S-x", spawn "killall picom") -- Kill picom compositor
-- Workspaces
, ("M-.", nextScreen) -- Switch focus to next monitor
, ("M-,", prevScreen) -- Switch focus to prev monitor
, ("M-S-", shiftTo Next nonNSP >> moveTo Next nonNSP) -- Shifts focused window to next ws
, ("M-S-", shiftTo Prev nonNSP >> moveTo Prev nonNSP) -- Shifts focused window to prev ws
-- Floating windows
, ("M-f", sendMessage (T.Toggle "floats")) -- Toggles 'floats' layout
, ("M-t", withFocused $ windows . W.sink) -- Push floating window back to tile
, ("M-S-t", sinkAll) -- Push all floating windows to tile
-- Increase/decrease spacing (gaps)
, ("C-M1-j", decWindowSpacing 4) -- Decrease window spacing
, ("C-M1-k", incWindowSpacing 4) -- Increase window spacing
, ("C-M1-h", decScreenSpacing 4) -- Decrease screen spacing
, ("C-M1-l", incScreenSpacing 4) -- Increase screen spacing
-- Windows navigation
, ("M-m", windows W.focusMaster) -- Move focus to the master window
, ("M-j", windows W.focusDown) -- Move focus to the next window
, ("M-k", windows W.focusUp) -- Move focus to the prev window
, ("M-S-m", windows W.swapMaster) -- Swap the focused window and the master window
, ("M-S-j", windows W.swapDown) -- Swap focused window with next window
, ("M-S-k", windows W.swapUp) -- Swap focused window with prev window
, ("M-S-", rotSlavesDown) -- Rotate all windows except master and keep focus in place
, ("M-C-", rotAllDown) -- Rotate all windows in the current stack
, ("M-", promote) -- Moves focused window to master, others maintain order
-- Layouts
, ("M-", sendMessage NextLayout) -- Switch to next layout
, ("M-", sendMessage (MT.Toggle NBFULL) >> sendMessage ToggleStruts) -- Toggles noborder/full
-- Increase/decrease windows in the master pane or the stack
, ("M-S-", sendMessage (IncMasterN 1)) -- Increase # of clients master pane
, ("M-S-", sendMessage (IncMasterN (-1))) -- Decrease # of clients master pane
, ("M-C-", increaseLimit) -- Increase # of windows
, ("M-C-", decreaseLimit) -- Decrease # of windows
-- Window resizing
, ("M-h", sendMessage Shrink) -- Shrink horiz window width
, ("M-l", sendMessage Expand) -- Expand horiz window width
, ("M-M1-j", sendMessage MirrorShrink) -- Shrink vert window width
, ("M-M1-k", sendMessage MirrorExpand) -- Expand vert window width
-- Multimedia keys
, ("", spawn "amixer set Master toggle")
, ("", spawn "amixer set Master 5%- unmute")
, ("", spawn "amixer set Master 5%+ unmute")
, ("", spawn "brightness + 10 %")
, ("", spawn "brightness - 10 %")
-- Map media keys to meta + arrows for keyboards without special keys
, ("M-", spawn "amixer set Master 5%- unmute")
, ("M-", spawn "amixer set Master 5%+ unmute")
, ("M-", spawn "brightness + 10 %")
, ("M-", spawn "brightness - 10 %")
]
where nonNSP = WSIs (return (\ws -> W.tag ws /= "NSP"))
nonEmptyNonNSP = WSIs (return (\ws -> isJust (W.stack ws) && W.tag ws /= "NSP"))
-------------------------------------------------------------------------------
-- Layout vars:
--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 False (Border i i i i) True (Border i i i i) True
-- Below is a variation of the above except no borders are applied
-- if fewer than two windows. So a single window has no gaps.
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
-- Colors for tabs layout and tabs sublayout.
myTabTheme = def { fontName = myFont
, activeColor = "#46d9ff"
, inactiveColor = "#313846"
, activeBorderColor = "#46d9ff"
, inactiveBorderColor = "#282c34"
, activeTextColor = "#282c34"
, inactiveTextColor = "#d0d0d0"
}
-- Theme for showWName which prints current workspace when you change workspaces.
myShowWNameTheme :: SWNConfig
myShowWNameTheme = def
{ swn_font = "xft:Ubuntu:bold:size=60"
, swn_fade = 1.0
, swn_bgcolor = "#1c1f24"
, swn_color = "#ffffff"
}
-------------------------------------------------------------------------------
-- Layouts:
-- All of these layouts have to be defined in myLayoutHook, otherwise
-- type errors will occur
tall = renamed [Replace "tall"]
$ smartBorders
$ windowNavigation
$ addTabs shrinkText myTabTheme
$ subLayout [] (smartBorders Simplest)
$ limitWindows 12
$ mySpacing 8
$ ResizableTall 1 (3/100) (1/2) []
magnify = renamed [Replace "magnify"]
$ smartBorders
$ windowNavigation
$ addTabs shrinkText myTabTheme
$ subLayout [] (smartBorders Simplest)
$ magnifier
$ limitWindows 12
$ mySpacing 8
$ ResizableTall 1 (3/100) (1/2) []
floats = renamed [Replace "floats"]
$ smartBorders
$ limitWindows 20 simplestFloat
grid = renamed [Replace "grid"]
$ smartBorders
$ windowNavigation
$ addTabs shrinkText myTabTheme
$ subLayout [] (smartBorders Simplest)
$ limitWindows 12
$ mySpacing 8
$ mkToggle (single MIRROR)
$ Grid (16/10)
spirals = renamed [Replace "spirals"]
$ smartBorders
$ windowNavigation
$ addTabs shrinkText myTabTheme
$ subLayout [] (smartBorders Simplest)
$ mySpacing' 8
$ spiral (6/7)
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
myLayoutHook = avoidStruts $ mouseResize $ windowArrange $ T.toggleLayouts floats
$ mkToggle (NBFULL ?? NOBORDERS ?? EOT) myDefaultLayout
where
myDefaultLayout = withBorder myBorderWidth tall
||| magnify
||| floats
||| grid
||| spirals
||| tabs
-------------------------------------------------------------------------------
-- 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.
--
-- `doFloat` forces a window to float, useful for dialog boxes and such.
-- `doShift (myWorkspaces !! 7)` sends program to workspace 8
myManageHook :: XMonad.Query (Data.Monoid.Endo WindowSet)
myManageHook = composeAll
-- Make dialog boxes floating, don't tile them
[ className =? "notification" --> doFloat
, className =? "confirm" --> doFloat
, className =? "dialog" --> doFloat
, className =? "error" --> doFloat
, className =? "download" --> doFloat
, className =? "file_progress" --> doFloat
, className =? "splash" --> doFloat
, className =? "toolbar" --> doFloat
, className =? "Qalculate-gtk" --> doFloat
, isFullscreen --> doFullFloat
-- auto-shift applications to their respecitve workspaces
, className =? "discord" --> doShift ( myWorkspaces !! 3 )
, className =? "Code" --> doShift ( myWorkspaces !! 0 )
, title =? "Mozilla Firefox" --> doShift ( myWorkspaces !! 1 )
]
------------------------------------------------------------------------
-- 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.
--
-- I don't really use this because I define these applications
-- in ~/.config/x11/xprofile instead, that way it will apply for
-- all WMs, not just for XMonad
myStartupHook :: X ()
myStartupHook = do
-- Automatically run autostart.sh script which will start
-- .desktop applications defined in ~/.config/autostart
spawnOnce "$HOME/.config/xmonad/scripts/autostart.sh &"
-------------------------------------------------------------------------------
-- Log hook: this sends info to xmobar process(es)
myLogHook :: Handle -> Handle -> X ()
myLogHook xmproc0 xmproc1 = dynamicLogWithPP $ xmobarPP
{ ppOutput = \x -> hPutStrLn xmproc0 x -- xmobar on monitor 1
>> hPutStrLn xmproc1 x -- xmobar on monitor 2
, ppCurrent = xmobarColor "#98be65" "" . wrap " [" "] " -- Current workspace
, ppVisible = xmobarColor "#98be65" "" . wrap " " " " . clickable -- Visible but not current workspace
, ppHidden = xmobarColor "#82AAFF" "" . wrap " *" " " . clickable -- Hidden workspaces
, ppHiddenNoWindows = xmobarColor "#c792ea" "" . wrap " " " " . clickable -- Hidden workspaces (no windows)
, ppTitle = xmobarColor "#b3afc2" "" . shorten 60 -- Title of active window
, ppSep = " | " -- Separator character
, ppUrgent = xmobarColor "#C45500" "" . wrap " !" "! " -- Urgent workspace
, ppExtras = [windowCount] -- # of windows current workspace
, ppOrder = \(ws:l:t:ex) -> [ws,l]++ex++[t] -- order of things in xmobar
}
-------------------------------------------------------------------------------
-- Run xmonad with all the defaults we set up.
main :: IO ()
main = do
-- Launching 2 instances of xmobar on their respective monitors.
xmproc0 <- spawnPipe "xmobar -x 0 $HOME/.config/xmobar/xmobarrc0"
xmproc1 <- spawnPipe "xmobar -x 1 $HOME/.config/xmobar/xmobarrc1"
-- Xmonad config definitions
xmonad $ ewmh def
{ modMask = myModMask
, terminal = myTerminal
, workspaces = myWorkspaces
, startupHook = myStartupHook
, manageHook = myManageHook <+> manageDocks
, handleEventHook = docksEventHook
, layoutHook = showWName' myShowWNameTheme $ myLayoutHook
, borderWidth = myBorderWidth
, normalBorderColor = myNormalBorderColor
, focusedBorderColor = myFocusedBorderColor
, logHook = myLogHook xmproc0 xmproc1
} `additionalKeysP` myKeys