content: -----------------------------------------------------------------------------
  -- |
  -- Module      :  XMonad.Util.Dzen
  -- Copyright   :  (c) glasser@mit.edu
  -- License     :  BSD
  --
  -- Maintainer  :  glasser@mit.edu
  -- Stability   :  stable
  -- Portability :  unportable
  --
  -- Handy wrapper for dzen. Requires dzen >= 0.2.4.
  --
  -----------------------------------------------------------------------------
  
  module XMonad.Util.Dzen (
      -- * Flexible interface
      dzenConfig, DzenConfig,
      timeout,
      font,
      xScreen,
      vCenter,
      hCenter,
      center,
      onCurr,
      x,
      y,
      addArgs,
  
      -- * Legacy interface
      dzen,
      dzenScreen,
      dzenWithArgs,
  
      -- * Miscellaneous
      seconds,
      chomp,
      (>=>),
    ) where
  
  import Control.Monad
  import XMonad
  import XMonad.StackSet
  import XMonad.Util.Run (runProcessWithInputAndWait, seconds)
  
  type DzenConfig = (Int, [String]) -> X (Int, [String])
  
  -- | @dzenConfig config s@ will display the string @s@ according to the
  -- configuration @config@.  For example, to display the string @\"foobar\"@ with
  -- all the default settings, you can simply call
  --
  -- > dzenConfig return "foobar"
  --
  -- Or, to set a longer timeout, you could use
  --
  -- > dzenConfig (timeout 10) "foobar"
  --
  -- You can combine configurations with the (>=>) operator.  To display
  -- @\"foobar\"@ for 10 seconds on the first screen, you could use
  --
  -- > dzenConfig (timeout 10 >=> xScreen 0) "foobar"
  --
  -- As a final example, you could adapt the above to display @\"foobar\"@ for
  -- 10 seconds on the current screen with
  --
  -- > dzenConfig (timeout 10 >=> onCurr xScreen) "foobar"
  dzenConfig :: DzenConfig -> String -> X ()
  dzenConfig conf s = do
      (t, args) <- conf (seconds 3, [])
      runProcessWithInputAndWait "dzen2" args (chomp s) t
  
  -- | dzen wants exactly one newline at the end of its input, so this can be
  -- used for your own invocations of dzen.  However, all functions in this
  -- module will call this for you.
  chomp :: String -> String
  chomp = (++"\n") . reverse . dropWhile ('\n' ==) . reverse
  
  -- | Set the timeout, in seconds.  This defaults to 3 seconds if not
  -- specified.
  timeout :: Rational -> DzenConfig
  timeout = timeoutMicro . seconds
  
  -- | Set the timeout, in microseconds.  Mostly here for the legacy
  -- interface.
  timeoutMicro :: Int -> DzenConfig
  timeoutMicro n (_, ss) = return (n, ss)
  
  -- | Add raw command-line arguments to the configuration.  These will be
  -- passed on verbatim to dzen2.  The default includes no arguments.
  addArgs :: [String] -> DzenConfig
  addArgs ss (n, ss') = return (n, ss ++ ss')
  
  -- | Start dzen2 on a particular screen.  Only works with versions of dzen
  -- that support the "-xs" argument.
  xScreen :: ScreenId -> DzenConfig
  xScreen sc = addArgs ["-xs", show (fromIntegral sc + 1 :: Int)]
  
  -- | Take a screen-specific configuration and supply it with the screen ID
  -- of the currently focused screen, according to xmonad.  For example, show
  -- a 100-pixel wide bar centered within the current screen, you could use
  --
  -- > dzenConfig (onCurr (hCenter 100)) "foobar"
  --
  -- Of course, you can still combine these with (>=>); for example, to center
  -- the string @\"foobar\"@ both horizontally and vertically in a 100x14 box
  -- using the lovely Terminus font, you could use
  --
  -- > terminus = "-*-terminus-*-*-*-*-12-*-*-*-*-*-*-*"
  -- > dzenConfig (onCurr (center 100 14) >=> font terminus) "foobar"
  onCurr :: (ScreenId -> DzenConfig) -> DzenConfig
  onCurr f conf = gets (screen . current . windowset) >>= flip f conf
  
  -- | Put the top of the dzen bar at a particular pixel.
  x :: Int -> DzenConfig
  x n = addArgs ["-x", show n]
  -- | Put the left of the dzen bar at a particular pixel.
  y :: Int -> DzenConfig
  y n = addArgs ["-y", show n]
  
  -- | Specify the font.  Check out xfontsel to get the format of the String
  -- right; if your dzen supports xft, then you can supply that here, too.
  font :: String -> DzenConfig
  font fn = addArgs ["-fn", fn]
  
  -- | @vCenter height sc@ sets the configuration to have the dzen bar appear
  -- on screen @sc@ with height @height@, vertically centered with respect to
  -- the actual size of that screen.
  vCenter :: Int -> ScreenId -> DzenConfig
  vCenter = center' rect_height "-h" "-y"
  
  -- | @hCenter width sc@ sets the configuration to have the dzen bar appear
  -- on screen @sc@ with width @width@, horizontally centered with respect to
  -- the actual size of that screen.
  hCenter :: Int -> ScreenId -> DzenConfig
  hCenter = center' rect_width  "-w" "-x"
  
  -- | @center width height sc@ sets the configuration to have the dzen bar
  -- appear on screen @sc@ with width @width@ and height @height@, centered
  -- both horizontally and vertically with respect to the actual size of that
  -- screen.
  center :: Int -> Int -> ScreenId -> DzenConfig
  center width height sc = hCenter width sc >=> vCenter height sc
  
  -- Center things along a single dimension on a particular screen.
  center' :: (Rectangle -> Dimension) -> String -> String -> Int -> ScreenId -> DzenConfig
  center' selector extentName positionName extent sc conf = do
      rect <- gets (detailFromScreenId sc . windowset)
      case rect of
          Nothing -> return conf
          Just r  -> addArgs
              [extentName  , show extent,
               positionName, show ((fromIntegral (selector r) - extent) `div` 2),
               "-xs"       , show (fromIntegral sc + 1 :: Int)
              ] conf
  
  -- Get the rectangle outlining a particular screen.
  detailFromScreenId :: ScreenId -> WindowSet -> Maybe Rectangle
  detailFromScreenId sc ws = fmap screenRect maybeSD where
      c       = current ws
      v       = visible ws
      mapping = map (\s -> (screen s, screenDetail s)) (c:v)
      maybeSD = lookup sc mapping
  
  -- | @dzen str timeout@ pipes @str@ to dzen2 for @timeout@ microseconds.
  -- Example usage:
  --
  -- > dzen "Hi, mom!" (5 `seconds`)
  dzen :: String -> Int -> X ()
  dzen = flip (dzenConfig . timeoutMicro)
  
  -- | @dzen str args timeout@ pipes @str@ to dzen2 for @timeout@ seconds, passing @args@ to dzen.
  -- Example usage:
  --
  -- > dzenWithArgs "Hi, dons!" ["-ta", "r"] (5 `seconds`)
  dzenWithArgs :: String -> [String] -> Int -> X ()
  dzenWithArgs str args t = dzenConfig (timeoutMicro t >=> addArgs args) str
  
  -- | @dzenScreen sc str timeout@ pipes @str@ to dzen2 for @timeout@ microseconds, and on screen @sc@.
  -- Requires dzen to be compiled with Xinerama support.
  dzenScreen :: ScreenId -> String -> Int -> X ()
  dzenScreen sc str t = dzenConfig (timeoutMicro t >=> xScreen sc) str
  