packages feed

taffybar-0.1.0: src/System/Taffybar.hs

-- | This is a system status bar meant for use with window manager
-- like XMonad.  It is similar to xmobar, but with more visual flare
-- and a different widget set.  Contributed widgets are more than
-- welcome.  The bar is drawn using gtk and cairo.  It is actually the
-- simplest possible thing that could plausibly work: you give
-- Taffybar a list of GTK widgets and it will render them in a
-- horizontal bar for you (taking care of ugly details like reserving
-- strut space so that window managers don't put windows over it).
--
-- This is the real main module.  The default bar should be
-- customized to taste in the config file
-- (~/.config/taffybar/taffybar.hs).  Typically, this means adding
-- widgets to the default config.  A default configuration file is
-- included in the distribution, but the essentials are covered here.
--
-- The config file is just a Haskell source file that is compiled at
-- startup (if it has changed) to produce a custom executable with the
-- desired set of widgets.  You will want to import this module along
-- with the modules of any widgets you want to add to the bar.  Note,
-- you can define any widgets that you want in your config file or
-- other libraries.  Taffybar only cares that you give it some GTK
-- widgets to display.
--
-- Below is a fairly typical example:
--
-- > import System.Taffybar
-- > import System.Taffybar.Systray
-- > import System.Taffybar.XMonadLog
-- > import System.Taffybar.SimpleClock
-- > import System.Taffybar.Widgets.PollingGraph
-- > import System.Information.CPU
-- >
-- > cpuCallback = do
-- >   (_, systemLoad, totalLoad) <- cpuLoad
-- >   return [ totalLoad, systemLoad ]
-- >
-- > main = do
-- >   let cpuCfg = defaultGraphConfig { graphDataColors = [ (0, 1, 0, 1), (1, 0, 1, 0.5)]
-- >                                   , graphLabel = Just "cpu"
-- >                                   }
-- >       clock = textClockNew Nothing "<span fgcolor='orange'>%a %b %_d %H:%M</span>" 1
-- >       log = xmonadLogNew
-- >       tray = systrayNew
-- >       cpu = pollingGraphNew cpuCfg 0.5 cpuCallback
-- >   defaultTaffybar defaultTaffybarConfig { startWidgets = [ log ]
-- >                                         , endWidgets = [ tray, clock, cpu ]
-- >                                         }
--
-- This configuration creates a bar with four widgets.  On the left is
-- the XMonad log.  The rightmost widget is the system tray, with a
-- clock and then a CPU graph.  The clock is formatted using standard
-- strftime-style format strings (see the clock module).  Note that
-- the clock is colored using Pango markup (again, see the clock
-- module).
--
-- The CPU widget plots two graphs on the same widget: total CPU use
-- in green and then system CPU use in a kind of semi-transparent
-- purple on top of the green.
--
-- It is important to note that the widget lists are *not* [Widget].
-- They are actually [IO Widget] since the bar needs to construct them
-- after performing some GTK initialization.
--
-- The XMonadLog widget differs from its counterpart in xmobar: it
-- listens for updates over DBus instead of reading from stdin.  This
-- makes it easy to restart Taffybar independently of XMonad.  XMonad
-- does not come with a DBus logger, so here is an example of how to
-- make it work.  Note: this requires the dbus-core (>0.9) package,
-- which is installed as a dependency of Taffybar.
--
-- > import XMonad.Hooks.DynamicLog
-- > import DBus.Client.Simple
-- > import System.Taffybar.XMonadLog ( dbusLog )
-- >
-- > main = do
-- >   client <- connectSession
-- >   let pp = defaultPP
-- >   xmonad defaultConfig { logHook = dbusLog client pp }
--
-- The complexity is handled in the System.Tafftbar.XMonadLog module.
module System.Taffybar (
  TaffybarConfig(..),
  defaultTaffybar,
  defaultTaffybarConfig
  ) where

import qualified Config.Dyre as Dyre

import Graphics.UI.Gtk
import Text.Printf

import System.Taffybar.StrutProperties

data TaffybarConfig =
  TaffybarConfig { screenNumber :: Int -- ^ The screen number to run the bar on (default is almost always fine)
                 , monitorNumber :: Int -- ^ The xinerama/xrandr monitor number to put the bar on (default: 0)
                 , barHeight :: Int -- ^ Number of pixels to reserve for the bar (default: 25 pixels)
                 , errorMsg :: Maybe String -- ^ Used by the application
                 , startWidgets :: [IO Widget] -- ^ Widgets that are packed in order at the left end of the bar
                 , endWidgets :: [IO Widget] -- ^ Widgets that are packed from right-to-left in the bar
                 }

-- | The default configuration gives an empty bar 25 pixels high on monitor 0.
defaultTaffybarConfig :: TaffybarConfig
defaultTaffybarConfig =
  TaffybarConfig { screenNumber = 0
                 , monitorNumber = 0
                 , barHeight = 25
                 , errorMsg = Nothing
                 , startWidgets = []
                 , endWidgets = []
                 }

showError :: TaffybarConfig -> String -> TaffybarConfig
showError cfg msg = cfg { errorMsg = Just msg }

-- | The default parameters need to tell GHC to compile using
-- -threaded so that the GTK event loops doesn't block all of the
-- widgets
defaultParams :: Dyre.Params TaffybarConfig
defaultParams = Dyre.defaultParams { Dyre.projectName = "taffybar"
                                   , Dyre.realMain = realMain
                                   , Dyre.showError = showError
                                   , Dyre.ghcOpts = ["-threaded"]
                                   }

-- | The entry point of the application.  Feed it a custom config.
defaultTaffybar :: TaffybarConfig -> IO ()
defaultTaffybar = Dyre.wrapMain defaultParams

realMain :: TaffybarConfig -> IO ()
realMain cfg = do
  case errorMsg cfg of
    Nothing -> taffybarMain cfg
    Just err -> error ("Error: " ++ err)

taffybarMain :: TaffybarConfig -> IO ()
taffybarMain cfg = do
  _ <- initGUI
  Just disp <- displayGetDefault
  nscreens <- displayGetNScreens disp
  screen <- case screenNumber cfg < nscreens of
    False -> error $ printf "Screen %d is not available in the default display" (screenNumber cfg)
    True -> displayGetScreen disp (screenNumber cfg)
  nmonitors <- screenGetNMonitors screen
  monitorSize <- case monitorNumber cfg < nmonitors of
    False -> error $ printf "Monitor %d is not available in the selected screen" (monitorNumber cfg)
    True -> screenGetMonitorGeometry screen (monitorNumber cfg)

  window <- windowNew
  let Rectangle x y w _ = monitorSize
  windowSetTypeHint window WindowTypeHintDock
  windowSetScreen window screen
  windowSetDefaultSize window w (barHeight cfg)
  windowMove window x y
  widgetModifyBg window StateNormal (Color 0 0 0)
  _ <- onRealize window $ setStrutProperties window (0, 0, barHeight cfg, 0,
                             0, 0,
                             0, 0,
                             x, x + w - 10,
                             0, 0)
  box <- hBoxNew False 10
  containerAdd window box

  mapM_ (\io -> do
            wid <- io
            widgetSetSizeRequest wid (-1) (barHeight cfg)
            boxPackStart box wid PackNatural 0) (startWidgets cfg)
  mapM_ (\io -> do
            wid <- io
            widgetSetSizeRequest wid (-1) (barHeight cfg)
            boxPackEnd box wid PackNatural 0) (endWidgets cfg)
  widgetShow window
  widgetShow box
  mainGUI
  return ()