From 4af5117eb47028b234f4779c9c62cf344320e151 Mon Sep 17 00:00:00 2001 From: jao Date: Sat, 24 Nov 2018 20:36:10 +0000 Subject: Refactoring: cleaner separation app/lib and more cleanups --- src/app/Configuration.hs | 59 ++++++- src/app/Main.hs | 111 ++----------- src/lib/Xmobar.hs | 389 ++++++-------------------------------------- src/lib/Xmobar/Draw.hs | 123 ++++++++++++++ src/lib/Xmobar/EventLoop.hs | 250 ++++++++++++++++++++++++++++ src/lib/Xmobar/Plugins.hs | 1 - src/lib/Xmobar/Types.hs | 47 ++++++ src/lib/Xmobar/XUtil.hs | 10 +- xmobar.cabal | 24 ++- 9 files changed, 551 insertions(+), 463 deletions(-) create mode 100644 src/lib/Xmobar/Draw.hs create mode 100644 src/lib/Xmobar/EventLoop.hs create mode 100644 src/lib/Xmobar/Types.hs 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 -- cgit v1.2.3