{-# LANGUAGE DeriveDataTypeable #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Hooks.DynamicBars -- Copyright : (c) Ben Boeckel 2012 -- License : BSD-style (as xmonad) -- -- Maintainer : mathstuf@gmail.com -- Stability : unstable -- Portability : unportable -- -- Manage per-screen status bars. -- ----------------------------------------------------------------------------- module XMonad.Hooks.DynamicBars ( -- * Usage -- $usage DynamicStatusBar , DynamicStatusBarCleanup , DynamicStatusBarPartialCleanup , dynStatusBarStartup , dynStatusBarStartup' , dynStatusBarEventHook , dynStatusBarEventHook' , multiPP , multiPPFormat ) where import Prelude import Control.Monad import Control.Monad.Trans (lift) import Control.Monad.Writer (WriterT, execWriterT, tell) import Data.List import Data.Maybe import Data.Monoid import Data.Foldable (traverse_) import Graphics.X11.Xinerama import Graphics.X11.Xlib import Graphics.X11.Xlib.Extras import Graphics.X11.Xrandr import System.IO import XMonad import qualified XMonad.StackSet as W import XMonad.Hooks.DynamicLog import qualified XMonad.Util.ExtensibleState as XS -- $usage -- Provides a few helper functions to manage per-screen status bars while -- dynamically responding to screen changes. A startup action, event hook, and -- a way to separate PP styles based on the screen's focus are provided: -- -- * The 'dynStatusBarStartup' hook which initializes the status bars. The -- first argument is an `ScreenId -> IO Handle` which spawns a status bar on the -- given screen and returns the pipe which the string should be written to. -- The second argument is a `IO ()` to shut down all status bars. This should -- be placed in your `startupHook`. -- -- * The 'dynStatusBarEventHook' hook which respawns status bars when the -- number of screens changes. The arguments are the same as for the -- `dynStatusBarStartup` function. This should be placed in your -- `handleEventHook`. -- -- * Each of the above functions have an alternate form -- (`dynStatusBarStartup'` and `dynStatusBarEventHook'`) which use a cleanup -- function which takes an additional `ScreenId` argument which allows for -- more fine-grained control for shutting down a specific screen's status bar. -- -- * The 'multiPP' function which allows for different output based on whether -- the screen for the status bar has focus (the first argument) or not (the -- second argument). This is for use in your `logHook`. -- -- * The 'multiPPFormat' function is the same as the 'multiPP' function, but it -- also takes in a function that can customize the output to status bars. -- -- The hooks take a 'DynamicStatusBar' function which is given the id of the -- screen to start up and returns the 'Handle' to the pipe to write to. The -- 'DynamicStatusBarCleanup' argument should tear down previous instances. It -- is called when the number of screens changes and on startup. -- data DynStatusBarInfo = DynStatusBarInfo { dsbInfo :: [(ScreenId, Handle)] } deriving (Typeable) instance ExtensionClass DynStatusBarInfo where initialValue = DynStatusBarInfo [] type DynamicStatusBar = ScreenId -> IO Handle type DynamicStatusBarCleanup = IO () type DynamicStatusBarPartialCleanup = ScreenId -> IO () dynStatusBarSetup :: X () dynStatusBarSetup = do dpy <- asks display root <- asks theRoot io $ xrrSelectInput dpy root rrScreenChangeNotifyMask dynStatusBarStartup :: DynamicStatusBar -> DynamicStatusBarCleanup -> X () dynStatusBarStartup sb cleanup = do dynStatusBarSetup updateStatusBars sb cleanup dynStatusBarStartup' :: DynamicStatusBar -> DynamicStatusBarPartialCleanup -> X () dynStatusBarStartup' sb cleanup = do dynStatusBarSetup updateStatusBars' sb cleanup dynStatusBarEventHook :: DynamicStatusBar -> DynamicStatusBarCleanup -> Event -> X All dynStatusBarEventHook sb cleanup = dynStatusBarRun (updateStatusBars sb cleanup) dynStatusBarEventHook' :: DynamicStatusBar -> DynamicStatusBarPartialCleanup -> Event -> X All dynStatusBarEventHook' sb cleanup = dynStatusBarRun (updateStatusBars' sb cleanup) dynStatusBarRun :: X () -> Event -> X All dynStatusBarRun action (RRScreenChangeNotifyEvent {}) = action >> return (All True) dynStatusBarRun _ _ = return (All True) updateStatusBars :: DynamicStatusBar -> DynamicStatusBarCleanup -> X () updateStatusBars sb cleanup = do (dsbInfoScreens, dsbInfoHandles) <- XS.get >>= return . unzip . dsbInfo screens <- getScreens when (screens /= dsbInfoScreens) $ do newHandles <- liftIO $ do hClose `mapM_` dsbInfoHandles cleanup mapM sb screens XS.put $ DynStatusBarInfo (zip screens newHandles) updateStatusBars' :: DynamicStatusBar -> DynamicStatusBarPartialCleanup -> X () updateStatusBars' sb cleanup = do (dsbInfoScreens, dsbInfoHandles) <- XS.get >>= return . unzip . dsbInfo screens <- getScreens when (screens /= dsbInfoScreens) $ do let oldInfo = zip dsbInfoScreens dsbInfoHandles let (infoToKeep, infoToClose) = partition (flip elem screens . fst) oldInfo newInfo <- liftIO $ do mapM_ hClose $ map snd infoToClose mapM_ cleanup $ map fst infoToClose let newScreens = screens \\ dsbInfoScreens newHandles <- mapM sb newScreens return $ zip newScreens newHandles XS.put . DynStatusBarInfo $ infoToKeep ++ newInfo ----------------------------------------------------------------------------- -- The following code is from adamvo's xmonad.hs file. -- http://www.haskell.org/haskellwiki/Xmonad/Config_archive/adamvo%27s_xmonad.hs multiPP :: PP -- ^ The PP to use if the screen is focused -> PP -- ^ The PP to use otherwise -> X () multiPP = multiPPFormat dynamicLogString multiPPFormat :: (PP -> X String) -> PP -> PP -> X () multiPPFormat dynlStr focusPP unfocusPP = do (_, dsbInfoHandles) <- XS.get >>= return . unzip . dsbInfo multiPP' dynlStr focusPP unfocusPP dsbInfoHandles multiPP' :: (PP -> X String) -> PP -> PP -> [Handle] -> X () multiPP' dynlStr focusPP unfocusPP handles = do st <- get let pickPP :: WorkspaceId -> WriterT (Last XState) X String pickPP ws = do let isFoc = (ws ==) . W.tag . W.workspace . W.current $ windowset st put st{ windowset = W.view ws $ windowset st } out <- lift $ dynlStr $ if isFoc then focusPP else unfocusPP when isFoc $ get >>= tell . Last . Just return out traverse_ put . getLast =<< execWriterT . (io . zipWithM_ hPutStrLn handles <=< mapM pickPP) . catMaybes =<< mapM screenWorkspace (zipWith const [0 .. ] handles) getScreens :: MonadIO m => m [ScreenId] getScreens = liftIO $ do screens <- do dpy <- openDisplay "" rects <- getScreenInfo dpy closeDisplay dpy return rects let ids = zip [0 .. ] screens return $ map fst ids