import System.IO import System.Exit import Data.Semigroup(All) import qualified Data.List as L import qualified Data.Map as M import XMonad import qualified XMonad.StackSet as W import XMonad.Hooks.ManageDocks import XMonad.Hooks.ServerMode import XMonad.Hooks.StatusBar import XMonad.Hooks.StatusBar.PP import XMonad.Hooks.EwmhDesktops import XMonad.Hooks.UrgencyHook import XMonad.Layout.Gaps import XMonad.Layout.NoBorders import XMonad.Layout.Spacing import XMonad.Layout.MultiToggle import XMonad.Layout.MultiToggle.Instances import XMonad.Layout.LayoutModifier import XMonad.Actions.CycleWS import XMonad.Actions.NoBorders import XMonad.Util.ClickableWorkspaces ------------------------------------------------------------------------ -- Workspaces -- The default number of workspaces (virtual screens) and their names. -- myWorkspaces :: [String] myWorkspaces = map show [1..9] ------------------------------------------------------------------------ -- 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 :: ManageHook myManageHook = composeAll [ ] ------------------------------------------------------------------------ -- Layouts -- You can specify and transform your layouts by modifying these values. -- If you change layout bindings be sure to use 'mod-shift-space' after -- restarting (with 'mod-q') to reset your layout state to the new -- defaults, as xmonad preserves your old layout settings by default. -- -- The available layouts. Note that each layout is separated by |||, -- which denotes layout choice. gap :: Int gap = 7 myGaps :: l a -> ModifiedLayout Gaps l a myGaps = gaps [(U, gap), (R, gap), (L, gap), (D, gap)] addSpace :: l a -> ModifiedLayout Spacing l a addSpace = spacing gap tiledLayout :: Tall Window tiledLayout = tiled where -- default tiling algorithm partitions the screen into two panes tiled = Tall nmaster delta ratio -- The default number of windows in the master pane nmaster = 1 -- Default proportion of screen occupied by master pane ratio = 1/2 -- Percent of screen to increment by when resizing panes delta = 3/100 layouts :: Tall Window layouts = tiledLayout {- Set up layouts. 1. Remove borders on floating windows. 2. Create NBFULL (No borders fullscreen toggle) 3. Configure layouts to avoid struts -} myLayout = lessBorders OnlyLayoutFloat $ mkToggle (NBFULL ?? EOT) $ avoidStruts $ myGaps $ addSpace $ layouts ------------------------------------------------------------------------ -- Colors and borders myBorderWidth :: Dimension myBorderWidth = 2 myNormalBorderColor :: String myNormalBorderColor = "#444444" backgroundColor :: String backgroundColor = "#005577" foregroundColor :: String foregroundColor = "#bbbbbb" urgentColor :: String urgentColor = "#ff0000" ------------------------------------------------------------------------ {- This sets the "_NET_WM_STATE_FULLSCREEN" window property, helping some programs such as firefox to adjust acoordingly to fullscreen mode In a perfect world we shouldnt need to do this manually but it seems like ewmhFullscreen/others dont implement this functionality -} setFullscreenProp :: Bool -> Window -> X () setFullscreenProp b win = withDisplay $ \dpy -> do state <- getAtom "_NET_WM_STATE" fullsc <- getAtom "_NET_WM_STATE_FULLSCREEN" let replaceWMStateProperty = changeProperty32 dpy win state 4 propModeReplace if b then io $ replaceWMStateProperty [fromIntegral fullsc] else io $ replaceWMStateProperty [] {- Fullscreen: Hide xmobar -> Hide borders -> Set fullscreen -> Set fullscreenprops Unfullscreen: ^^ but reverse -} toggleFullScreen :: X () toggleFullScreen = do mIsFullScreen <- withWindowSet (isToggleActive NBFULL . W.workspace . W.current) case mIsFullScreen of Just isFullScreen -> if isFullScreen then withFocused (setFullscreenProp False) >> sendMessage (Toggle NBFULL) >> sendMessage ToggleStruts else sendMessage ToggleStruts >> sendMessage (Toggle NBFULL) >> withFocused (setFullscreenProp True) Nothing -> return () toggleFloat :: Window -> X () toggleFloat w = windows (\s -> if M.member w (W.floating s) then W.sink w s else (W.float w (W.RationalRect 0.125 0.125 0.75 0.75) s)) ------------------------------------------------------------------------ -- External commands myCommands :: [(String, X ())] myCommands = [ ("decrease-master-size" , sendMessage Shrink ) , ("increase-master-size" , sendMessage Expand ) , ("decrease-master-count" , sendMessage $ IncMasterN (-1) ) , ("increase-master-count" , sendMessage $ IncMasterN ( 1) ) , ("focus-prev" , windows W.focusUp ) , ("focus-next" , windows W.focusDown ) , ("focus-master" , windows W.focusMaster ) , ("swap-with-prev" , windows W.swapUp ) , ("swap-with-next" , windows W.swapDown ) , ("swap-with-master" , windows W.swapMaster ) , ("togglefullscreen" , toggleFullScreen ) , ("togglefloating" , withFocused toggleFloat ) , ("next-layout" , sendMessage NextLayout ) , ("cycle-workspace" , toggleWS ) , ("kill-window" , kill ) , ("quit" , io $ exitWith ExitSuccess ) , ("restart" , spawn "xmonad --recompile; xmonad --restart" ) ] ----------------------------------------------------------------------- -- Custom server mode myServerModeEventHook :: Event -> X All myServerModeEventHook = serverModeEventHookCmd' $ return myCommands' myCommands' :: [(String, X ())] myCommands' = ("list-commands", listMyServerCmds) : myCommands ++ wscs ++ sccs where wscs = [((m ++ s), windows $f s) | s <- myWorkspaces , (f, m) <- [(W.view, "focus-workspace-"), (W.shift, "send-to-workspace-")] ] sccs = [((m ++ show sc), screenWorkspace (fromIntegral sc) >>= flip whenJust (windows . f)) | sc <- [0..10], (f, m) <- [(W.view, "focus-screen-"), (W.shift, "send-to-screen-")]] listMyServerCmds :: X () listMyServerCmds = spawn ("echo '" ++ asmc ++ "' | xmessage -file -") where asmc = concat $ "Available commands:" : map (\(x, _)-> " " ++ x) myCommands' ------------------------------------------------------------------------ -- Mouse bindings myFocusFollowsMouse :: Bool myFocusFollowsMouse = False myMouseBindings :: XConfig l -> M.Map (KeyMask, Button) (Window -> X ()) myMouseBindings (XConfig {XMonad.modMask = modMask}) = M.fromList $ [ -- mod-button1, Set the window to floating mode and move by dragging ((modMask, button1), (\w -> focus w >> mouseMoveWindow w)) -- mod-button2, Toggle float on the window , ((modMask, button2), (\w -> focus w >> toggleFloat w)) -- mod-button3, Set the window to floating mode and resize by dragging , ((modMask, button3), (\w -> focus w >> mouseResizeWindow w)) -- you may also bind events to the mouse scroll wheel (button4 and button5) ] ------------------------------------------------------------------------ -- Status bar {- Configure xmobar in a dwm style with statusBarPP 1. Get the active, inactive, urgent and full rectangles above workspace numbers. 2. Make workspaces clickable. 3. Set a 2 space separation between workspaces. -} myStatusBar :: StatusBarConfig myStatusBar = statusBarProp "xmobar" (do numWindows <- getNumberOfWindowsInWorkpace return $ def { ppCurrent = if numWindows > 0 then xmobarBorder "Top" foregroundColor 4 . xmobarColor foregroundColor backgroundColor . wrap " " " " else xmobarColor foregroundColor backgroundColor . wrap " " " " , ppTitle = id , ppSep = " | " , ppWsSep = "" , ppLayout = (\_ -> "") , ppHidden = (\s -> clickableWrap ((read s::Int) - 1) (createDwmBox foregroundColor (" " ++ s ++ " "))) --better way to clickablewrap . , ppHiddenNoWindows = (\s -> clickableWrap ((read s::Int) - 1) (" " ++ s ++ " ")) , ppUrgent = (\s -> clickableWrap ((read s::Int) - 1) (xmobarBorder "Top" urgentColor 4 (" " ++ s ++ " "))) } ) where getNumberOfWindowsInWorkpace = withWindowSet (pure . length . W.index) createDwmBox color prefix = "" ++ prefix ++ "" ------------------------------------------------------------------------ -- Launch xmonad with the aforementioned settings {- Main function (Launch xmonad) 1. Setup statusbar (with docks). 2. Enable fullscreen hook. 3. Enable urgency hook. 4. Setup xmonad defaults. -} main :: IO () main = do xmonad . withSB myStatusBar . docks . ewmhFullscreen . ewmh $ withUrgencyHookC BorderUrgencyHook { urgencyBorderColor = urgentColor } urgencyConfig { suppressWhen = XMonad.Hooks.UrgencyHook.Never } $ def { focusFollowsMouse = myFocusFollowsMouse, borderWidth = myBorderWidth, workspaces = myWorkspaces, normalBorderColor = myNormalBorderColor, focusedBorderColor = backgroundColor, mouseBindings = myMouseBindings, layoutHook = myLayout, handleEventHook = myServerModeEventHook, manageHook = myManageHook }