------------------------------------------------------------------------------ -- | -- Module : Util -- Copyright : (c) Mads N Noe 2009 -- Maintainer : mntnoe (@) gmail.com -- License : as-is -- -- Utility functions for XMonad. -- ------------------------------------------------------------------------------ module Util where -- Haskell modules import Control.Monad (unless, when) import Control.Monad.Trans (lift) import Data.List import qualified Data.Map as M import Data.Monoid (Endo(Endo)) import System.Posix.Unistd(getSystemID, nodeName) import System.IO.Error (isDoesNotExistError) -- XMonad modules import XMonad import XMonad.Actions.CycleWS import XMonad.Actions.WindowGo import qualified XMonad.StackSet as W import XMonad.Util.WorkspaceCompare (getSortByTag) -- | Perform k x if x return a 'Just' value. (?+) :: (Monad m) => m (Maybe a) -> (a -> m ()) -> m () x ?+ k = x >>= maybe (return ()) k infixr 1 ?+ -- | Helper function for use with monads. if_ :: t -> t -> Bool -> t if_ t f c = if c then t else f -- | Return a string that launches xterm with the given 'title', 'appName' and -- command to execute. xterm :: String -> String -> String xterm a e = concat ["xterm -wf -title '", e, "' -name '", a, "' -e '", e, "'"] -- | Shift a window to a workspace and switch to that workspace in one -- operation. shiftView :: WorkspaceId -> WindowSet -> WindowSet shiftView ws w = W.greedyView ws $ W.shift ws w -- | Perform a workspace transformation on the next workspace in 'WSDirection' -- of type 'WSType'. doWithWS :: (String -> (WindowSet -> WindowSet)) -> WSDirection -> WSType -> X () doWithWS f dir wstype = do i <- findWorkspace getSortByTag dir wstype 1 windows $ f i -- | Is the current workspace empty? isCurrentWsEmpty :: X Bool isCurrentWsEmpty = withWindowSet $ \s -> do let l = W.integrate' $ W.stack $ W.workspace $ W.current s return $ null l -- | Modify the 'WindowSet' with a non-pure function. Counterpart to 'doF'. doX :: (Window -> X (WindowSet -> WindowSet)) -> ManageHook doX f = ask >>= Query . lift . fmap Endo . f -- | Ensure that a window always starts on an empty workspace. If a window -- satisfying the query exists, focus it. Otherwise run the specified -- command, swithing to an empty workspace if the current one is not empty. reqEmptyWS :: Query Bool -> X () -> X () reqEmptyWS q f = do raiseNextMaybe (reqEmptyWS' >> f) q where reqEmptyWS' = do empty <- isCurrentWsEmpty i <- findWorkspace getSortByTag Next EmptyWS 1 unless empty $ windows $ W.greedyView i -- | Kill the focused window. If the window satisfies the query, return to the -- previously displayed workspace. killAndReturn q = withFocused $ \w -> do qr <- runQuery q w kill when qr toggleWS -- | Perform a 'WindowSet' transformation on the workspace with the given -- index. withNthWorkspace :: (String -> WindowSet -> WindowSet) -> Int -> X () withNthWorkspace job wnum = nthWorkspaceTag wnum ?+ windows . job where nthWorkspaceTag :: Int -> X (Maybe String) nthWorkspaceTag wnum = do sort <- getSortByTag ws <- gets (map W.tag . sort . W.workspaces . windowset) case drop wnum ws of (w:_) -> return $ Just w [] -> return Nothing -- | Is the focused window the \"master window\" of the current workspace? isMaster :: Query Bool isMaster = ask >>= (\w -> liftX $ withWindowSet $ \ws -> return $ Just w == master ws) where master :: WindowSet -> Maybe Window master ws = case W.integrate' $ W.stack $ W.workspace $ W.current ws of [] -> Nothing (x:xs) -> Just x -- | Is the focused window a floating window? isFloat :: Query Bool isFloat = ask >>= (\w -> liftX $ withWindowSet $ \ws -> return $ M.member w $ W.floating ws) -- | Swap the focused window with the last window in the stack. swapBottom :: W.StackSet i l a s sd -> W.StackSet i l a s sd swapBottom = W.modify' $ \c -> case c of W.Stack _ _ [] -> c -- already bottom. W.Stack t ls rs -> W.Stack t (xs ++ x : ls) [] where (x:xs) = reverse rs -- | Swap the focused window with the following window, or if the window is -- floating, lower it to the bottom. swapOrLower :: X () swapOrLower = withFocused $ \w -> runQuery isFloat w >>= if_ (windows swapBottom) (windows W.swapDown) -- | Swap the focused window with the preceding window, or if the window is -- floating, raise it to the top. swapOrRaise :: X () swapOrRaise = withFocused $ \w -> runQuery isFloat w >>= if_ (windows W.swapMaster) (windows W.swapUp) -- | Determine the host. getHost = do host <- getSystemID case nodeName host of "mntnoe-desktop" -> return Desktop "mntnoe-laptop" -> return Laptop _ -> return Desktop -- | For use in cross host configutions. data Host = Desktop | Laptop deriving Eq