diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/app/Configuration.hs | 59 | ||||
| -rw-r--r-- | src/app/Main.hs | 111 | ||||
| -rw-r--r-- | src/lib/Xmobar.hs | 389 | ||||
| -rw-r--r-- | src/lib/Xmobar/Draw.hs | 123 | ||||
| -rw-r--r-- | src/lib/Xmobar/EventLoop.hs | 250 | ||||
| -rw-r--r-- | src/lib/Xmobar/Plugins.hs | 1 | ||||
| -rw-r--r-- | src/lib/Xmobar/Types.hs | 47 | ||||
| -rw-r--r-- | src/lib/Xmobar/XUtil.hs | 10 | 
8 files changed, 536 insertions, 454 deletions
| diff --git a/src/app/Configuration.hs b/src/app/Configuration.hs index db5c109..e1e3c24 100644 --- a/src/app/Configuration.hs +++ b/src/app/Configuration.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleContexts, CPP #-}  ------------------------------------------------------------------------------  -- | @@ -17,14 +17,33 @@  ------------------------------------------------------------------------------ -module Configuration (parseConfig) where +module Configuration (readConfig, readDefaultConfig) where  import Text.ParserCombinators.Parsec  import Text.ParserCombinators.Parsec.Number (int)  import Text.ParserCombinators.Parsec.Perm ((<|?>), (<$?>), permute) +import Control.Monad.IO.Class (liftIO) + +import System.Posix.Files +import System.FilePath ((</>)) +import System.Environment +import System.Directory (getHomeDirectory) + +import Xmobar.Config  import qualified Xmobar.Config as C +#if defined XFT || defined UTF8 +import qualified System.IO as S (readFile,hGetLine) +#endif + +readFileSafe :: FilePath -> IO String +#if defined XFT || defined UTF8 +readFileSafe = S.readFile +#else +readFileSafe = readFile +#endif +  stripComments :: String -> String  stripComments =    unlines . map (drop 5 . strip False . (replicate 5 ' '++)) . lines @@ -156,3 +175,39 @@ commandsErr = "commands: this usually means that a command could not" ++                "\nbe parsed." ++                "\nThe error could be located at the begining of the command" ++                "\nwhich follows the offending one." + +-- | Reads the configuration files or quits with an error +readConfig :: FilePath -> String -> IO (Config,[String]) +readConfig f usage = do +  file <- liftIO $ fileExist f +  s <- liftIO $ if file then readFileSafe f else error $ +                  f ++ ": file not found!\n" ++ usage +  either (\err -> error $ f ++ +                    ": configuration file contains errors at:\n" ++ show err) +         return $ parseConfig s + +xdgConfigDir :: IO String +xdgConfigDir = do env <- getEnvironment +                  case lookup "XDG_CONFIG_HOME" env of +                       Just val -> return val +                       Nothing  -> fmap (</> ".config") getHomeDirectory + +xmobarConfigDir :: IO FilePath +xmobarConfigDir = fmap (</> "xmobar") xdgConfigDir + +getXdgConfigFile :: IO FilePath +getXdgConfigFile = fmap (</> "xmobarrc") xmobarConfigDir + +-- | Read default configuration file or load the default config +readDefaultConfig :: String -> IO (Config,[String]) +readDefaultConfig usage = do +  xdgConfigFile <- getXdgConfigFile +  xdgConfigFileExists <- liftIO $ fileExist xdgConfigFile +  home <- liftIO $ getEnv "HOME" +  let defaultConfigFile = home ++ "/.xmobarrc" +  defaultConfigFileExists <- liftIO $ fileExist defaultConfigFile +  if xdgConfigFileExists +    then readConfig xdgConfigFile usage +    else if defaultConfigFileExists +         then readConfig defaultConfigFile usage +         else return (defaultConfig,[]) diff --git a/src/app/Main.hs b/src/app/Main.hs index 22834b1..0760d16 100644 --- a/src/app/Main.hs +++ b/src/app/Main.hs @@ -13,126 +13,35 @@  --  ----------------------------------------------------------------------------- -module Main ( -- * Main Stuff -              -- $main -              main -            , readConfig -            , readDefaultConfig -            ) where +module Main (main) where -import Xmobar -import Xmobar.Parsers -import Xmobar.Config -import Xmobar.XUtil - -import Data.Foldable (for_)  import Data.List (intercalate) -import qualified Data.Map as Map  import Data.Version (showVersion) -import Graphics.X11.Xlib  import System.Console.GetOpt -import System.Directory (getHomeDirectory)  import System.Exit -import System.Environment -import System.FilePath ((</>)) -import System.Posix.Files -import Control.Concurrent.Async (Async, cancel) -import Control.Exception (bracket) +import System.Environment (getArgs)  import Control.Monad (unless) -import Control.Monad.IO.Class (liftIO)  import Text.Read (readMaybe) -import Xmobar.Signal (setupSignalHandler, withDeferSignals) +import Xmobar (xmobar) +import Xmobar.Config  import Paths_xmobar (version) -import Configuration +import Configuration (readConfig, readDefaultConfig)  -- $main  -- | The main entry point  main :: IO () -main = withDeferSignals $ do -  initThreads -  d <- openDisplay "" -  args <- getArgs -  (o,file) <- getOpts args +main = do +  (o,file) <- getArgs >>= getOpts    (c,defaultings) <- case file of -                       [cfgfile] -> readConfig cfgfile -                       _ -> readDefaultConfig - +                       [cfgfile] -> readConfig cfgfile usage +                       _ -> readDefaultConfig usage    unless (null defaultings) $ putStrLn $      "Fields missing from config defaulted: " ++ intercalate "," defaultings - -  conf  <- doOpts c o -  fs    <- initFont d (font conf) -  fl    <- mapM (initFont d) (additionalFonts conf) -  cls   <- mapM (parseTemplate conf) (splitTemplate conf) -  sig   <- setupSignalHandler -  bracket (mapM (mapM $ startCommand sig) cls) -          cleanupThreads -          $ \vars -> do -    (r,w) <- createWin d fs conf -    let ic = Map.empty -        to = textOffset conf -        ts = textOffsets conf ++ replicate (length fl) (-1) -    startLoop (XConf d r w (fs:fl) (to:ts) ic conf) sig vars - -cleanupThreads :: [[([Async ()], a)]] -> IO () -cleanupThreads vars = -  -- putStrLn "In cleanupThreads" -  for_ (concat vars) $ \(asyncs, _) -> -    for_ asyncs cancel - --- | Splits the template in its parts -splitTemplate :: Config -> [String] -splitTemplate conf = -  case break (==l) t of -    (le,_:re) -> case break (==r) re of -                   (ce,_:ri) -> [le, ce, ri] -                   _         -> def -    _         -> def -  where [l, r] = alignSep -                   (if length (alignSep conf) == 2 then conf else defaultConfig) -        t = template conf -        def = [t, "", ""] - - --- | Reads the configuration files or quits with an error -readConfig :: FilePath -> IO (Config,[String]) -readConfig f = do -  file <- liftIO $ fileExist f -  s <- liftIO $ if file then readFileSafe f else error $ -                  f ++ ": file not found!\n" ++ usage -  either (\err -> error $ f ++ -                    ": configuration file contains errors at:\n" ++ show err) -         return $ parseConfig s - -xdgConfigDir :: IO String -xdgConfigDir = do env <- getEnvironment -                  case lookup "XDG_CONFIG_HOME" env of -                       Just val -> return val -                       Nothing  -> fmap (</> ".config") getHomeDirectory - -xmobarConfigDir :: IO FilePath -xmobarConfigDir = fmap (</> "xmobar") xdgConfigDir - -getXdgConfigFile :: IO FilePath -getXdgConfigFile = fmap (</> "xmobarrc") xmobarConfigDir - --- | Read default configuration file or load the default config -readDefaultConfig :: IO (Config,[String]) -readDefaultConfig = do -  xdgConfigFile <- getXdgConfigFile -  xdgConfigFileExists <- liftIO $ fileExist xdgConfigFile -  home <- liftIO $ getEnv "HOME" -  let defaultConfigFile = home ++ "/.xmobarrc" -  defaultConfigFileExists <- liftIO $ fileExist defaultConfigFile -  if xdgConfigFileExists -    then readConfig xdgConfigFile -    else if defaultConfigFileExists -         then readConfig defaultConfigFile -         else return (defaultConfig,[]) +  doOpts c o >>= xmobar  data Opts = Help            | Version diff --git a/src/lib/Xmobar.hs b/src/lib/Xmobar.hs index 4172780..722ee72 100644 --- a/src/lib/Xmobar.hs +++ b/src/lib/Xmobar.hs @@ -1,4 +1,5 @@  {-# LANGUAGE CPP #-} +  -----------------------------------------------------------------------------  -- |  -- Module      :  Xmobar @@ -14,349 +15,55 @@  --  ----------------------------------------------------------------------------- -module Xmobar -    ( -- * Main Stuff -      -- $main -      X , XConf (..), runX -    , startLoop -    -- * Program Execution -    -- $command -    , startCommand -    -- * Window Management -    -- $window -    , createWin -    -- * Printing -    -- $print -    , drawInWin, printStrings -    ) where - -import Prelude hiding (lookup) -import Graphics.X11.Xlib hiding (textExtents, textWidth) -import Graphics.X11.Xlib.Extras -import Graphics.X11.Xinerama -import Graphics.X11.Xrandr - -import Control.Arrow ((&&&)) -import Control.Applicative ((<$>)) -import Control.Monad.Reader -import Control.Concurrent -import Control.Concurrent.Async (Async, async) -import Control.Concurrent.STM -import Control.Exception (handle, SomeException(..)) -import Data.Bits -import Data.Map hiding (foldr, map, filter) -import Data.Maybe (fromJust, isJust) +module Xmobar (xmobar) where -import Xmobar.Bitmap as Bitmap +import Xmobar.EventLoop (startLoop, startCommand)  import Xmobar.Config -import Xmobar.Parsers -import Xmobar.Commands -import Xmobar.Actions -import Xmobar.Runnable -import Xmobar.Signal -import Xmobar.Window -import Xmobar.XUtil -import Xmobar.ColorCache - -#ifdef XFT -import Graphics.X11.Xft -import Xmobar.MinXft (drawBackground) -#endif - -#ifdef DBUS -import Xmobar.IPC.DBus -#endif - --- $main --- --- The Xmobar data type and basic loops and functions. - --- | The X type is a ReaderT -type X = ReaderT XConf IO - --- | The ReaderT inner component -data XConf = -    XConf { display   :: Display -          , rect      :: Rectangle -          , window    :: Window -          , fontListS :: [XFont] -          , verticalOffsets :: [Int] -          , iconS     :: Map FilePath Bitmap -          , config    :: Config -          } - --- | Runs the ReaderT -runX :: XConf -> X () -> IO () -runX xc f = runReaderT f xc - --- | Starts the main event loop and threads -startLoop :: XConf -> TMVar SignalType -> [[([Async ()], TVar String)]] -             -> IO () -startLoop xcfg@(XConf _ _ w _ _ _ _) sig vs = do -#ifdef XFT -    xftInitFtLibrary -#endif -    tv <- atomically $ newTVar [] -    _ <- forkIO (handle (handler "checker") (checker tv [] vs sig)) -#ifdef THREADED_RUNTIME -    _ <- forkOS (handle (handler "eventer") (eventer sig)) -#else -    _ <- forkIO (handle (handler "eventer") (eventer sig)) -#endif -#ifdef DBUS -    runIPC sig -#endif -    eventLoop tv xcfg [] sig -  where -    handler thing (SomeException e) = -      void $ putStrLn ("Thread " ++ thing ++ " failed: " ++ show e) -    -- Reacts on events from X -    eventer signal = -      allocaXEvent $ \e -> do -        dpy <- openDisplay "" -        xrrSelectInput dpy (defaultRootWindow dpy) rrScreenChangeNotifyMask -        selectInput dpy w (exposureMask .|. structureNotifyMask .|. buttonPressMask) - -        forever $ do -#ifdef THREADED_RUNTIME -          nextEvent dpy e -#else -          nextEvent' dpy e -#endif -          ev <- getEvent e -          case ev of -            ConfigureEvent {} -> atomically $ putTMVar signal Reposition -            ExposeEvent {} -> atomically $ putTMVar signal Wakeup -            RRScreenChangeNotifyEvent {} -> atomically $ putTMVar signal Reposition -            ButtonEvent {} -> atomically $ putTMVar signal (Action (ev_button ev) (fi $ ev_x ev)) -            _ -> return () - --- | Send signal to eventLoop every time a var is updated -checker :: TVar [String] -           -> [String] -           -> [[([Async ()], TVar String)]] -           -> TMVar SignalType -           -> IO () -checker tvar ov vs signal = do -      nval <- atomically $ do -              nv <- mapM concatV vs -              guard (nv /= ov) -              writeTVar tvar nv -              return nv -      atomically $ putTMVar signal Wakeup -      checker tvar nval vs signal -    where -      concatV = fmap concat . mapM (readTVar . snd) - - --- | Continuously wait for a signal from a thread or a interrupt handler -eventLoop :: TVar [String] -             -> XConf -             -> [([Action], Position, Position)] -             -> TMVar SignalType -             -> IO () -eventLoop tv xc@(XConf d r w fs vos is cfg) as signal = do -      typ <- atomically $ takeTMVar signal -      case typ of -         Wakeup -> do -            str <- updateString cfg tv -            xc' <- updateCache d w is (iconRoot cfg) str >>= -                     \c -> return xc { iconS = c } -            as' <- updateActions xc r str -            runX xc' $ drawInWin r str -            eventLoop tv xc' as' signal - -         Reposition -> -            reposWindow cfg - -         ChangeScreen -> do -            ncfg <- updateConfigPosition cfg -            reposWindow ncfg -         Hide   t -> hide   (t*100*1000) -         Reveal t -> reveal (t*100*1000) -         Toggle t -> toggle t +import Data.Foldable (for_) +import qualified Data.Map as Map -         TogglePersistent -> eventLoop -            tv xc { config = cfg { persistent = not $ persistent cfg } } as signal +import Graphics.X11.Xlib +import Control.Concurrent.Async (Async, cancel) +import Control.Exception (bracket) -         Action but x -> action but x - -    where -        isPersistent = not $ persistent cfg - -        hide t -            | t == 0 = -                when isPersistent (hideWindow d w) >> eventLoop tv xc as signal -            | otherwise = do -                void $ forkIO -                     $ threadDelay t >> atomically (putTMVar signal $ Hide 0) -                eventLoop tv xc as signal - -        reveal t -            | t == 0 = do -                when isPersistent (showWindow r cfg d w) -                eventLoop tv xc as signal -            | otherwise = do -                void $ forkIO -                     $ threadDelay t >> atomically (putTMVar signal $ Reveal 0) -                eventLoop tv xc as signal - -        toggle t = do -            ismapped <- isMapped d w -            atomically (putTMVar signal $ if ismapped then Hide t else Reveal t) -            eventLoop tv xc as signal - -        reposWindow rcfg = do -          r' <- repositionWin d w (head fs) rcfg -          eventLoop tv (XConf d r' w fs vos is rcfg) as signal - -        updateConfigPosition ocfg = -          case position ocfg of -            OnScreen n o -> do -              srs <- getScreenInfo d -              return (if n == length srs -                       then -                        (ocfg {position = OnScreen 1 o}) -                       else -                        (ocfg {position = OnScreen (n+1) o})) -            o -> return (ocfg {position = OnScreen 1 o}) - -        action button x = do -          mapM_ runAction $ -            filter (\(Spawn b _) -> button `elem` b) $ -            concatMap (\(a,_,_) -> a) $ -            filter (\(_, from, to) -> x >= from && x <= to) as -          eventLoop tv xc as signal - --- $command - --- | Runs a command as an independent thread and returns its Async handles --- and the TVar the command will be writing to. -startCommand :: TMVar SignalType -             -> (Runnable,String,String) -             -> IO ([Async ()], TVar String) -startCommand sig (com,s,ss) -    | alias com == "" = do var <- atomically $ newTVar is -                           atomically $ writeTVar var (s ++ ss) -                           return ([], var) -    | otherwise = do var <- atomically $ newTVar is -                     let cb str = atomically $ writeTVar var (s ++ str ++ ss) -                     a1 <- async $ start com cb -                     a2 <- async $ trigger com $ maybe (return ()) -                                                 (atomically . putTMVar sig) -                     return ([a1, a2], var) -    where is = s ++ "Updating..." ++ ss - -updateString :: Config -> TVar [String] -                -> IO [[(Widget, String, Int, Maybe [Action])]] -updateString conf v = do -  s <- readTVarIO v -  let l:c:r:_ = s ++ repeat "" -  liftIO $ mapM (parseString conf) [l, c, r] - -updateActions :: XConf -> Rectangle -> [[(Widget, String, Int, Maybe [Action])]] -                 -> IO [([Action], Position, Position)] -updateActions conf (Rectangle _ _ wid _) ~[left,center,right] = do -  let (d,fs) = (display &&& fontListS) conf -      strLn :: [(Widget, String, Int, Maybe [Action])] -> IO [(Maybe [Action], Position, Position)] -      strLn  = liftIO . mapM getCoords -      iconW i = maybe 0 Bitmap.width (lookup i $ iconS conf) -      getCoords (Text s,_,i,a) = textWidth d (fs!!i) s >>= \tw -> return (a, 0, fi tw) -      getCoords (Icon s,_,_,a) = return (a, 0, fi $ iconW s) -      partCoord off xs = map (\(a, x, x') -> (fromJust a, x, x')) $ -                         filter (\(a, _,_) -> isJust a) $ -                         scanl (\(_,_,x') (a,_,w') -> (a, x', x' + w')) -                               (Nothing, 0, off) -                               xs -      totSLen = foldr (\(_,_,len) -> (+) len) 0 -      remWidth xs = fi wid - totSLen xs -      offs = 1 -      offset a xs = case a of -                     C -> (remWidth xs + offs) `div` 2 -                     R -> remWidth xs -                     L -> offs -  fmap concat $ mapM (\(a,xs) -> -                       (\xs' -> partCoord (offset a xs') xs') <$> strLn xs) $ -                     zip [L,C,R] [left,center,right] - --- $print - --- | Draws in and updates the window -drawInWin :: Rectangle -> [[(Widget, String, Int, Maybe [Action])]] -> X () -drawInWin wr@(Rectangle _ _ wid ht) ~[left,center,right] = do -  r <- ask -  let (c,d) = (config &&& display) r -      (w,(fs,vs)) = (window &&& fontListS &&& verticalOffsets) r -      strLn = liftIO . mapM getWidth -      iconW i = maybe 0 Bitmap.width (lookup i $ iconS r) -      getWidth (Text s,cl,i,_) = -        textWidth d (fs!!i) s >>= \tw -> return (Text s,cl,i,fi tw) -      getWidth (Icon s,cl,i,_) = return (Icon s,cl,i,fi $ iconW s) - -  p <- liftIO $ createPixmap d w wid ht -                         (defaultDepthOfScreen (defaultScreenOfDisplay d)) -#if XFT -  when (alpha c /= 255) (liftIO $ drawBackground d p (bgColor c) (alpha c) wr) -#endif -  withColors d [bgColor c, borderColor c] $ \[bgcolor, bdcolor] -> do -    gc <- liftIO $ createGC  d w -#if XFT -    when (alpha c == 255) $ do -#else -    do -#endif -      liftIO $ setForeground d gc bgcolor -      liftIO $ fillRectangle d p gc 0 0 wid ht -    -- write to the pixmap the new string -    printStrings p gc fs vs 1 L =<< strLn left -    printStrings p gc fs vs 1 R =<< strLn right -    printStrings p gc fs vs 1 C =<< strLn center -    -- draw border if requested -    liftIO $ drawBorder (border c) (borderWidth c) d p gc bdcolor wid ht -    -- copy the pixmap with the new string to the window -    liftIO $ copyArea d p w gc 0 0 wid ht 0 0 -    -- free up everything (we do not want to leak memory!) -    liftIO $ freeGC d gc -    liftIO $ freePixmap d p -    -- resync -    liftIO $ sync d True - -verticalOffset :: (Integral b, Integral a, MonadIO m) => -                  a -> Widget -> XFont -> Int -> Config -> m b -verticalOffset ht (Text t) fontst voffs _ -  | voffs > -1 = return $ fi voffs -  | otherwise = do -     (as,ds) <- liftIO $ textExtents fontst t -     let margin = (fi ht - fi ds - fi as) `div` 2 -     return $ fi as + margin - 1 -verticalOffset ht (Icon _) _ _ conf -  | iconOffset conf > -1 = return $ fi (iconOffset conf) -  | otherwise = return $ fi (ht `div` 2) - 1 - --- | An easy way to print the stuff we need to print -printStrings :: Drawable -> GC -> [XFont] -> [Int] -> Position -             -> Align -> [(Widget, String, Int, Position)] -> X () -printStrings _ _ _ _ _ _ [] = return () -printStrings dr gc fontlist voffs offs a sl@((s,c,i,l):xs) = do -  r <- ask -  let (conf,d) = (config &&& display) r -      alph = alpha conf -      Rectangle _ _ wid ht = rect r -      totSLen = foldr (\(_,_,_,len) -> (+) len) 0 sl -      remWidth = fi wid - fi totSLen -      fontst = fontlist !! i -      offset = case a of -                 C -> (remWidth + offs) `div` 2 -                 R -> remWidth -                 L -> offs -      (fc,bc) = case break (==',') c of -                 (f,',':b) -> (f, b           ) -                 (f,    _) -> (f, bgColor conf) -  valign <- verticalOffset ht s (head fontlist) (voffs !! i) conf -  case s of -    (Text t) -> liftIO $ printString d dr fontst gc fc bc offset valign t alph -    (Icon p) -> liftIO $ maybe (return ()) -                           (drawBitmap d dr gc fc bc offset valign) -                           (lookup p (iconS r)) -  printStrings dr gc fontlist voffs (offs + l) a xs +import Xmobar.Parsers +import Xmobar.XUtil +import Xmobar.Config() +import Xmobar.Signal (setupSignalHandler, withDeferSignals) +import Xmobar.Window +import Xmobar.Types + +splitTemplate :: Config -> [String] +splitTemplate conf = +  case break (==l) t of +    (le,_:re) -> case break (==r) re of +                   (ce,_:ri) -> [le, ce, ri] +                   _         -> def +    _         -> def +  where [l, r] = alignSep +                   (if length (alignSep conf) == 2 then conf else defaultConfig) +        t = template conf +        def = [t, "", ""] + +xmobar :: Config -> IO () +xmobar conf = withDeferSignals $ do +  initThreads +  d <- openDisplay "" +  fs    <- initFont d (font conf) +  fl    <- mapM (initFont d) (additionalFonts conf) +  cls   <- mapM (parseTemplate conf) (splitTemplate conf) +  sig   <- setupSignalHandler +  bracket (mapM (mapM $ startCommand sig) cls) +          cleanupThreads +          $ \vars -> do +    (r,w) <- createWin d fs conf +    let ic = Map.empty +        to = textOffset conf +        ts = textOffsets conf ++ replicate (length fl) (-1) +    startLoop (XConf d r w (fs:fl) (to:ts) ic conf) sig vars + +cleanupThreads :: [[([Async ()], a)]] -> IO () +cleanupThreads vars = +  for_ (concat vars) $ \(asyncs, _) -> +    for_ asyncs cancel diff --git a/src/lib/Xmobar/Draw.hs b/src/lib/Xmobar/Draw.hs new file mode 100644 index 0000000..e63cd27 --- /dev/null +++ b/src/lib/Xmobar/Draw.hs @@ -0,0 +1,123 @@ +{-# LANGUAGE CPP #-} + +------------------------------------------------------------------------------ +-- | +-- Module: Xmobar.Draw +-- Copyright: (c) 2018 Jose Antonio Ortega Ruiz +-- License: BSD3-style (see LICENSE) +-- +-- Maintainer: jao@gnu.org +-- Stability: unstable +-- Portability: portable +-- Created: Sat Nov 24, 2018 18:49 +-- +-- +-- Drawing the xmobar contents +-- +------------------------------------------------------------------------------ + + +module Xmobar.Draw (drawInWin) where + +import Prelude hiding (lookup) +import Control.Monad.IO.Class +import Control.Monad.Reader +import Control.Arrow ((&&&)) +import Data.Map hiding (foldr, map, filter) + +import Graphics.X11.Xlib hiding (textExtents, textWidth) + +import Xmobar.Parsers (Widget(..)) +import Xmobar.Actions (Action(..)) +import qualified Xmobar.Bitmap as B +import Xmobar.Types +import Xmobar.XUtil +import Xmobar.Config +import Xmobar.ColorCache +import Xmobar.Window (drawBorder) + +#ifdef XFT +import Xmobar.MinXft (drawBackground) +#endif + +fi :: (Integral a, Num b) => a -> b +fi = fromIntegral + +-- | Draws in and updates the window +drawInWin :: Rectangle -> [[(Widget, String, Int, Maybe [Action])]] -> X () +drawInWin wr@(Rectangle _ _ wid ht) ~[left,center,right] = do +  r <- ask +  let (c,d) = (config &&& display) r +      (w,(fs,vs)) = (window &&& fontListS &&& verticalOffsets) r +      strLn = liftIO . mapM getWidth +      iconW i = maybe 0 B.width (lookup i $ iconS r) +      getWidth (Text s,cl,i,_) = +        textWidth d (fs!!i) s >>= \tw -> return (Text s,cl,i,fi tw) +      getWidth (Icon s,cl,i,_) = return (Icon s,cl,i,fi $ iconW s) + +  p <- liftIO $ createPixmap d w wid ht +                         (defaultDepthOfScreen (defaultScreenOfDisplay d)) +#if XFT +  when (alpha c /= 255) (liftIO $ drawBackground d p (bgColor c) (alpha c) wr) +#endif +  withColors d [bgColor c, borderColor c] $ \[bgcolor, bdcolor] -> do +    gc <- liftIO $ createGC  d w +#if XFT +    when (alpha c == 255) $ do +#else +    do +#endif +      liftIO $ setForeground d gc bgcolor +      liftIO $ fillRectangle d p gc 0 0 wid ht +    -- write to the pixmap the new string +    printStrings p gc fs vs 1 L =<< strLn left +    printStrings p gc fs vs 1 R =<< strLn right +    printStrings p gc fs vs 1 C =<< strLn center +    -- draw border if requested +    liftIO $ drawBorder (border c) (borderWidth c) d p gc bdcolor wid ht +    -- copy the pixmap with the new string to the window +    liftIO $ copyArea d p w gc 0 0 wid ht 0 0 +    -- free up everything (we do not want to leak memory!) +    liftIO $ freeGC d gc +    liftIO $ freePixmap d p +    -- resync +    liftIO $ sync d True + +verticalOffset :: (Integral b, Integral a, MonadIO m) => +                  a -> Widget -> XFont -> Int -> Config -> m b +verticalOffset ht (Text t) fontst voffs _ +  | voffs > -1 = return $ fi voffs +  | otherwise = do +     (as,ds) <- liftIO $ textExtents fontst t +     let margin = (fi ht - fi ds - fi as) `div` 2 +     return $ fi as + margin - 1 +verticalOffset ht (Icon _) _ _ conf +  | iconOffset conf > -1 = return $ fi (iconOffset conf) +  | otherwise = return $ fi (ht `div` 2) - 1 + +-- | An easy way to print the stuff we need to print +printStrings :: Drawable -> GC -> [XFont] -> [Int] -> Position +             -> Align -> [(Widget, String, Int, Position)] -> X () +printStrings _ _ _ _ _ _ [] = return () +printStrings dr gc fontlist voffs offs a sl@((s,c,i,l):xs) = do +  r <- ask +  let (conf,d) = (config &&& display) r +      alph = alpha conf +      Rectangle _ _ wid ht = rect r +      totSLen = foldr (\(_,_,_,len) -> (+) len) 0 sl +      remWidth = fi wid - fi totSLen +      fontst = fontlist !! i +      offset = case a of +                 C -> (remWidth + offs) `div` 2 +                 R -> remWidth +                 L -> offs +      (fc,bc) = case break (==',') c of +                 (f,',':b) -> (f, b           ) +                 (f,    _) -> (f, bgColor conf) +  valign <- verticalOffset ht s (head fontlist) (voffs !! i) conf +  case s of +    (Text t) -> liftIO $ printString d dr fontst gc fc bc offset valign t alph +    (Icon p) -> liftIO $ maybe (return ()) +                           (B.drawBitmap d dr gc fc bc offset valign) +                           (lookup p (iconS r)) +  printStrings dr gc fontlist voffs (offs + l) a xs diff --git a/src/lib/Xmobar/EventLoop.hs b/src/lib/Xmobar/EventLoop.hs new file mode 100644 index 0000000..27fcf6d --- /dev/null +++ b/src/lib/Xmobar/EventLoop.hs @@ -0,0 +1,250 @@ +{-# LANGUAGE CPP #-} + +------------------------------------------------------------------------------ +-- | +-- Module: Xmobar.EventLoop +-- Copyright: (c) 2018 Jose Antonio Ortega Ruiz +-- License: BSD3-style (see LICENSE) +-- +-- Maintainer: jao@gnu.org +-- Stability: unstable +-- Portability: portable +-- Created: Sat Nov 24, 2018 19:40 +-- +-- +-- Event loop +-- +------------------------------------------------------------------------------ + + +module Xmobar.EventLoop (startLoop, startCommand) where + +import Prelude hiding (lookup) +import Graphics.X11.Xlib hiding (textExtents, textWidth) +import Graphics.X11.Xlib.Extras +import Graphics.X11.Xinerama +import Graphics.X11.Xrandr + +import Control.Arrow ((&&&)) +import Control.Applicative ((<$>)) +import Control.Monad.Reader +import Control.Concurrent +import Control.Concurrent.Async (Async, async) +import Control.Concurrent.STM +import Control.Exception (handle, SomeException(..)) +import Data.Bits +import Data.Map hiding (foldr, map, filter) +import Data.Maybe (fromJust, isJust) + +import Xmobar.Bitmap as Bitmap +import Xmobar.Config +import Xmobar.Parsers +import Xmobar.Commands +import Xmobar.Actions +import Xmobar.Runnable +import Xmobar.Signal +import Xmobar.Window +import Xmobar.XUtil +import Xmobar.Draw +import Xmobar.Types + +#ifdef XFT +import Graphics.X11.Xft +#endif + +#ifdef DBUS +import Xmobar.IPC.DBus +#endif + +-- $main + +-- | Starts the main event loop and threads +startLoop :: XConf -> TMVar SignalType -> [[([Async ()], TVar String)]] +             -> IO () +startLoop xcfg@(XConf _ _ w _ _ _ _) sig vs = do +#ifdef XFT +    xftInitFtLibrary +#endif +    tv <- atomically $ newTVar [] +    _ <- forkIO (handle (handler "checker") (checker tv [] vs sig)) +#ifdef THREADED_RUNTIME +    _ <- forkOS (handle (handler "eventer") (eventer sig)) +#else +    _ <- forkIO (handle (handler "eventer") (eventer sig)) +#endif +#ifdef DBUS +    runIPC sig +#endif +    eventLoop tv xcfg [] sig +  where +    handler thing (SomeException e) = +      void $ putStrLn ("Thread " ++ thing ++ " failed: " ++ show e) +    -- Reacts on events from X +    eventer signal = +      allocaXEvent $ \e -> do +        dpy <- openDisplay "" +        xrrSelectInput dpy (defaultRootWindow dpy) rrScreenChangeNotifyMask +        selectInput dpy w (exposureMask .|. structureNotifyMask .|. buttonPressMask) + +        forever $ do +#ifdef THREADED_RUNTIME +          nextEvent dpy e +#else +          nextEvent' dpy e +#endif +          ev <- getEvent e +          case ev of +            ConfigureEvent {} -> atomically $ putTMVar signal Reposition +            ExposeEvent {} -> atomically $ putTMVar signal Wakeup +            RRScreenChangeNotifyEvent {} -> atomically $ putTMVar signal Reposition +            ButtonEvent {} -> atomically $ putTMVar signal (Action (ev_button ev) (fi $ ev_x ev)) +            _ -> return () + +-- | Send signal to eventLoop every time a var is updated +checker :: TVar [String] +           -> [String] +           -> [[([Async ()], TVar String)]] +           -> TMVar SignalType +           -> IO () +checker tvar ov vs signal = do +      nval <- atomically $ do +              nv <- mapM concatV vs +              guard (nv /= ov) +              writeTVar tvar nv +              return nv +      atomically $ putTMVar signal Wakeup +      checker tvar nval vs signal +    where +      concatV = fmap concat . mapM (readTVar . snd) + + +-- | Continuously wait for a signal from a thread or a interrupt handler +eventLoop :: TVar [String] +             -> XConf +             -> [([Action], Position, Position)] +             -> TMVar SignalType +             -> IO () +eventLoop tv xc@(XConf d r w fs vos is cfg) as signal = do +      typ <- atomically $ takeTMVar signal +      case typ of +         Wakeup -> do +            str <- updateString cfg tv +            xc' <- updateCache d w is (iconRoot cfg) str >>= +                     \c -> return xc { iconS = c } +            as' <- updateActions xc r str +            runX xc' $ drawInWin r str +            eventLoop tv xc' as' signal + +         Reposition -> +            reposWindow cfg + +         ChangeScreen -> do +            ncfg <- updateConfigPosition cfg +            reposWindow ncfg + +         Hide   t -> hide   (t*100*1000) +         Reveal t -> reveal (t*100*1000) +         Toggle t -> toggle t + +         TogglePersistent -> eventLoop +            tv xc { config = cfg { persistent = not $ persistent cfg } } as signal + +         Action but x -> action but x + +    where +        isPersistent = not $ persistent cfg + +        hide t +            | t == 0 = +                when isPersistent (hideWindow d w) >> eventLoop tv xc as signal +            | otherwise = do +                void $ forkIO +                     $ threadDelay t >> atomically (putTMVar signal $ Hide 0) +                eventLoop tv xc as signal + +        reveal t +            | t == 0 = do +                when isPersistent (showWindow r cfg d w) +                eventLoop tv xc as signal +            | otherwise = do +                void $ forkIO +                     $ threadDelay t >> atomically (putTMVar signal $ Reveal 0) +                eventLoop tv xc as signal + +        toggle t = do +            ismapped <- isMapped d w +            atomically (putTMVar signal $ if ismapped then Hide t else Reveal t) +            eventLoop tv xc as signal + +        reposWindow rcfg = do +          r' <- repositionWin d w (head fs) rcfg +          eventLoop tv (XConf d r' w fs vos is rcfg) as signal + +        updateConfigPosition ocfg = +          case position ocfg of +            OnScreen n o -> do +              srs <- getScreenInfo d +              return (if n == length srs +                       then +                        (ocfg {position = OnScreen 1 o}) +                       else +                        (ocfg {position = OnScreen (n+1) o})) +            o -> return (ocfg {position = OnScreen 1 o}) + +        action button x = do +          mapM_ runAction $ +            filter (\(Spawn b _) -> button `elem` b) $ +            concatMap (\(a,_,_) -> a) $ +            filter (\(_, from, to) -> x >= from && x <= to) as +          eventLoop tv xc as signal + +-- $command + +-- | Runs a command as an independent thread and returns its Async handles +-- and the TVar the command will be writing to. +startCommand :: TMVar SignalType +             -> (Runnable,String,String) +             -> IO ([Async ()], TVar String) +startCommand sig (com,s,ss) +    | alias com == "" = do var <- atomically $ newTVar is +                           atomically $ writeTVar var (s ++ ss) +                           return ([], var) +    | otherwise = do var <- atomically $ newTVar is +                     let cb str = atomically $ writeTVar var (s ++ str ++ ss) +                     a1 <- async $ start com cb +                     a2 <- async $ trigger com $ maybe (return ()) +                                                 (atomically . putTMVar sig) +                     return ([a1, a2], var) +    where is = s ++ "Updating..." ++ ss + +updateString :: Config -> TVar [String] +                -> IO [[(Widget, String, Int, Maybe [Action])]] +updateString conf v = do +  s <- readTVarIO v +  let l:c:r:_ = s ++ repeat "" +  liftIO $ mapM (parseString conf) [l, c, r] + +updateActions :: XConf -> Rectangle -> [[(Widget, String, Int, Maybe [Action])]] +                 -> IO [([Action], Position, Position)] +updateActions conf (Rectangle _ _ wid _) ~[left,center,right] = do +  let (d,fs) = (display &&& fontListS) conf +      strLn :: [(Widget, String, Int, Maybe [Action])] -> IO [(Maybe [Action], Position, Position)] +      strLn  = liftIO . mapM getCoords +      iconW i = maybe 0 Bitmap.width (lookup i $ iconS conf) +      getCoords (Text s,_,i,a) = textWidth d (fs!!i) s >>= \tw -> return (a, 0, fi tw) +      getCoords (Icon s,_,_,a) = return (a, 0, fi $ iconW s) +      partCoord off xs = map (\(a, x, x') -> (fromJust a, x, x')) $ +                         filter (\(a, _,_) -> isJust a) $ +                         scanl (\(_,_,x') (a,_,w') -> (a, x', x' + w')) +                               (Nothing, 0, off) +                               xs +      totSLen = foldr (\(_,_,len) -> (+) len) 0 +      remWidth xs = fi wid - totSLen xs +      offs = 1 +      offset a xs = case a of +                     C -> (remWidth xs + offs) `div` 2 +                     R -> remWidth xs +                     L -> offs +  fmap concat $ mapM (\(a,xs) -> +                       (\xs' -> partCoord (offset a xs') xs') <$> strLn xs) $ +                     zip [L,C,R] [left,center,right] diff --git a/src/lib/Xmobar/Plugins.hs b/src/lib/Xmobar/Plugins.hs index 75ee306..e8c6403 100644 --- a/src/lib/Xmobar/Plugins.hs +++ b/src/lib/Xmobar/Plugins.hs @@ -17,7 +17,6 @@  module Xmobar.Plugins      ( Exec (..)      , tenthSeconds -    , readFileSafe      , hGetLineSafe      ) where diff --git a/src/lib/Xmobar/Types.hs b/src/lib/Xmobar/Types.hs new file mode 100644 index 0000000..f7c4fdf --- /dev/null +++ b/src/lib/Xmobar/Types.hs @@ -0,0 +1,47 @@ +------------------------------------------------------------------------------ +-- | +-- Module: Xmobar.Types +-- Copyright: (c) 2018 Jose Antonio Ortega Ruiz +-- License: BSD3-style (see LICENSE) +-- +-- Maintainer: jao@gnu.org +-- Stability: unstable +-- Portability: portable +-- Created: Sat Nov 24, 2018 19:02 +-- +-- +-- The Xmobar basic type +-- +------------------------------------------------------------------------------ + + +module Xmobar.Types (X , XConf (..), runX) where + +import Graphics.X11.Xlib +import Control.Monad.Reader +import Data.Map + +import Xmobar.Config +import Xmobar.Bitmap +import Xmobar.XUtil + + +-- The Xmobar data type and basic loops and functions. + +-- | The X type is a ReaderT +type X = ReaderT XConf IO + +-- | The ReaderT inner component +data XConf = +    XConf { display   :: Display +          , rect      :: Rectangle +          , window    :: Window +          , fontListS :: [XFont] +          , verticalOffsets :: [Int] +          , iconS     :: Map FilePath Bitmap +          , config    :: Config +          } + +-- | Runs the ReaderT +runX :: XConf -> X () -> IO () +runX xc f = runReaderT f xc diff --git a/src/lib/Xmobar/XUtil.hs b/src/lib/Xmobar/XUtil.hs index 5093e59..536a2fb 100644 --- a/src/lib/Xmobar/XUtil.hs +++ b/src/lib/Xmobar/XUtil.hs @@ -22,7 +22,6 @@ module Xmobar.XUtil      , textWidth      , printString      , nextEvent' -    , readFileSafe      , hGetLineSafe      ) where @@ -40,7 +39,7 @@ import System.Posix.Types (Fd(..))  import System.IO  #if defined XFT || defined UTF8 -import qualified System.IO as S (readFile,hGetLine) +import qualified System.IO as S (hGetLine)  #endif  #if defined XFT @@ -50,13 +49,6 @@ import Graphics.X11.Xrender  import Xmobar.ColorCache -readFileSafe :: FilePath -> IO String -#if defined XFT || defined UTF8 -readFileSafe = S.readFile -#else -readFileSafe = readFile -#endif -  hGetLineSafe :: Handle -> IO String  #if defined XFT || defined UTF8  hGetLineSafe = S.hGetLine | 
