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.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
myLayout = lessBorders OnlyFloat
$ mkToggle (NBFULL ?? EOT)
$ avoidStruts $ myGaps $ addSpace
$ layouts
------------------------------------------------------------------------
-- Colors and borders
-- Width of the window border in pixels.
myBorderWidth :: Dimension
myBorderWidth = 2
myNormalBorderColor :: String
myNormalBorderColor = "#444444"
myFocusedBorderColor :: String
myFocusedBorderColor = "#005577"
--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"
if b
then io $ changeProperty32 dpy win state 4 propModeReplace [fromIntegral fullsc]
else io $ changeProperty32 dpy win state 4 propModeReplace []
--Hide xmobar -> Hide borders -> Set fullscreen -> Set fullscreenprops
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 ()
------------------------------------------------------------------------
-- 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
--
-- Focus rules
-- True if your focus should follow your mouse cursor.
myFocusFollowsMouse :: Bool
myFocusFollowsMouse = False
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))
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)
]
------------------------------------------------------------------------
-- Run xmonad with all the defaults we set up.
--
myStatusBar :: StatusBarConfig
myStatusBar = statusBarProp "xmobar" (do
numWindows <- getNumberOfWindowsInWorkpace
return $ xmobarPP {
ppCurrent = if numWindows > 0
then xmobarBorder "Top" "#bbbbbb" 4 . xmobarColor "#bbbbbb" "#005577" . wrap " " " "
else xmobarColor "#bbbbbb" "#005577" . wrap " " " "
, ppTitle = id
, ppSep = " | "
, ppLayout = (\_ -> "")
, ppHidden = (\s -> clickableWrap ((read s::Int) - 1) (createDwmBox "#bbbbbb" (" " ++ s ++ " "))) --better way to clickablewrap .
, ppHiddenNoWindows = (\s -> clickableWrap ((read s::Int) - 1) (" " ++ s ++ " "))
}
)
where
getNumberOfWindowsInWorkpace = withWindowSet (pure . length . W.index)
createDwmBox color prefix = "<box type=HBoth offset=L19 color="++color++"><box type=Top mt=3 color="++color++"><box type=Top color="++color++">" ++ prefix ++ "</box></box></box>"
main :: IO ()
main = do
xmonad . withSB myStatusBar . docks . ewmhFullscreen . ewmh
$ def {
focusFollowsMouse = myFocusFollowsMouse,
borderWidth = myBorderWidth,
workspaces = myWorkspaces,
normalBorderColor = myNormalBorderColor,
focusedBorderColor = myFocusedBorderColor,
mouseBindings = myMouseBindings,
layoutHook = myLayout,
handleEventHook = myServerModeEventHook,
manageHook = myManageHook
}