summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorjao <jao@gnu.org>2018-11-24 20:36:10 +0000
committerjao <jao@gnu.org>2018-11-24 20:36:10 +0000
commit4af5117eb47028b234f4779c9c62cf344320e151 (patch)
treee981eb8934e52cb63a5bed1f7ab6240edc543eca
parentc84a2e586563cce90f4324eb38bfb2e2207eb7fc (diff)
downloadxmobar-4af5117eb47028b234f4779c9c62cf344320e151.tar.gz
xmobar-4af5117eb47028b234f4779c9c62cf344320e151.tar.bz2
Refactoring: cleaner separation app/lib and more cleanups
-rw-r--r--src/app/Configuration.hs59
-rw-r--r--src/app/Main.hs111
-rw-r--r--src/lib/Xmobar.hs389
-rw-r--r--src/lib/Xmobar/Draw.hs123
-rw-r--r--src/lib/Xmobar/EventLoop.hs250
-rw-r--r--src/lib/Xmobar/Plugins.hs1
-rw-r--r--src/lib/Xmobar/Types.hs47
-rw-r--r--src/lib/Xmobar/XUtil.hs10
-rw-r--r--xmobar.cabal24
9 files changed, 551 insertions, 463 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
diff --git a/xmobar.cabal b/xmobar.cabal
index 4a22a8b..9c6fcf7 100644
--- a/xmobar.cabal
+++ b/xmobar.cabal
@@ -95,19 +95,12 @@ library
hs-source-dirs: src/lib
exposed-modules: Xmobar,
+ Xmobar.Types,
+ Xmobar.Config,
Xmobar.Actions,
Xmobar.Bitmap,
- Xmobar.Config,
- Xmobar.Parsers,
Xmobar.Commands,
- Xmobar.Localize,
- Xmobar.XUtil,
- Xmobar.StatFS,
Xmobar.Runnable,
- Xmobar.ColorCache,
- Xmobar.Window,
- Xmobar.Signal,
- Xmobar.Environment,
Xmobar.Plugins,
Xmobar.Plugins.BufferedPipeReader,
Xmobar.Plugins.CommandReader,
@@ -139,6 +132,17 @@ library
Xmobar.Plugins.Monitors.Bright,
Xmobar.Plugins.Monitors.CatInt
+ other-modules: Xmobar.Localize,
+ Xmobar.Parsers,
+ Xmobar.XUtil,
+ Xmobar.StatFS,
+ Xmobar.ColorCache,
+ Xmobar.Window,
+ Xmobar.Draw,
+ Xmobar.Signal,
+ Xmobar.Environment,
+ Xmobar.EventLoop
+
extra-libraries: Xrandr Xrender
ghc-options: -funbox-strict-fields -Wall -fno-warn-unused-do-bind
@@ -297,6 +301,8 @@ test-suite XmobarTest
other-modules: Plugins.Monitors.CommonSpec,
Xmobar.Commands,
Xmobar.Signal,
+ Xmobar.XUtil,
+ Xmobar.ColorCache,
Xmobar.Plugins,
Xmobar.Plugins.Utils,
Xmobar.Plugins.Monitors.Common