------------------------------------------------------------------------------
-- |
-- 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