diff options
Diffstat (limited to 'src/Xmobar/Config')
-rw-r--r-- | src/Xmobar/Config/Parse.hs | 16 | ||||
-rw-r--r-- | src/Xmobar/Config/Template.hs | 188 | ||||
-rw-r--r-- | src/Xmobar/Config/Types.hs | 141 |
3 files changed, 331 insertions, 14 deletions
diff --git a/src/Xmobar/Config/Parse.hs b/src/Xmobar/Config/Parse.hs index 16af3db..0b41267 100644 --- a/src/Xmobar/Config/Parse.hs +++ b/src/Xmobar/Config/Parse.hs @@ -19,7 +19,8 @@ module Xmobar.Config.Parse(readConfig , parseConfig , indexedFont - , indexedOffset) where + , indexedOffset + , colorComponents) where import Text.ParserCombinators.Parsec import Text.ParserCombinators.Parsec.Number (int) @@ -31,6 +32,14 @@ import Xmobar.Config.Types import qualified System.IO as S (readFile) +-- | Splits a colors string into its two components +colorComponents :: Config -> String -> (String, String) +colorComponents conf c = + case break (==',') c of + (f,',':b) -> (f, b) + (f, _) -> (f, bgColor conf) + + stripComments :: String -> String stripComments = unlines . map (drop 5 . strip False . (replicate 5 ' '++)) . lines @@ -63,7 +72,7 @@ parseConfig defaultConfig = <|?> pAllDesktops <|?> pOverrideRedirect <|?> pPickBroadest <|?> pLowerOnStart <|?> pPersistent <|?> pIconRoot <|?> pCommands <|?> pSepChar <|?> pAlignSep <|?> pTemplate - <|?> pVerbose <|?> pSignal + <|?> pVerbose <|?> pSignal <|?> pDpi fields = [ "font", "additionalFonts", "bgColor", "fgColor" , "wmClass", "wmName", "sepChar" @@ -72,7 +81,7 @@ parseConfig defaultConfig = , "allDesktops", "overrideRedirect", "pickBroadest" , "hideOnStart", "lowerOnStart", "persistent", "iconRoot" , "alpha", "commands", "verbose", "signal", "textOutput" - , "textOutputFormat" + , "textOutputFormat", "dpi" ] pTextOutput = readField textOutput "textOutput" @@ -103,6 +112,7 @@ parseConfig defaultConfig = pIconRoot = readField iconRoot "iconRoot" pAlpha = readField alpha "alpha" pVerbose = readField verbose "verbose" + pDpi = readField dpi "dpi" pSignal = field signal "signal" $ fail "signal is meant for use with Xmobar as a library.\n It is not meant for use in the configuration file." diff --git a/src/Xmobar/Config/Template.hs b/src/Xmobar/Config/Template.hs new file mode 100644 index 0000000..ad30c3d --- /dev/null +++ b/src/Xmobar/Config/Template.hs @@ -0,0 +1,188 @@ +------------------------------------------------------------------------------ +-- | +-- Module: Xmobar.Config.Template +-- Copyright: (c) 2022 jao +-- License: BSD3-style (see LICENSE) +-- +-- Maintainer: mail@jao.io +-- Stability: unstable +-- Portability: portable +-- Created: Fri Sep 30, 2022 06:33 +-- +-- +-- Parsing template strings +-- +------------------------------------------------------------------------------ + + +module Xmobar.Config.Template (parseString) where + +import Data.Maybe (fromMaybe) +import qualified Control.Monad as CM + +import Text.Parsec ((<|>)) +import Text.Read (readMaybe) + +import qualified Text.Parsec as P +import qualified Text.Parsec.Combinator as C + +import Text.ParserCombinators.Parsec (Parser) + +import qualified Xmobar.Config.Types as T + +type Context = (T.TextRenderInfo, T.FontIndex, Maybe [T.Action]) + +retSegment :: Context -> T.Widget -> Parser [T.Segment] +retSegment (i, idx, as) widget = return [(widget, i, idx, as)] + +-- | Run the template string parser for the given config, producing a list of +-- drawable segment specifications. +parseString :: T.Config -> String -> [T.Segment] +parseString c s = + case P.parse (stringParser ci) "" s of + Left _ -> [(T.Text $ "Could not parse string: " ++ s, ti, 0, Nothing)] + Right x -> concat x + where ci = (ti , 0, Nothing) + ti = T.TextRenderInfo (T.fgColor c) 0 0 [] + +-- Top level parser reading the full template string +stringParser :: Context -> Parser [[T.Segment]] +stringParser c = C.manyTill (allParsers c) C.eof + +allParsers :: Context -> Parser [T.Segment] +allParsers c = C.choice (textParser c:map (\f -> P.try (f c)) parsers) + where parsers = [ iconParser, hspaceParser, rawParser, actionParser + , fontParser, boxParser, colorParser ] + +-- Wrapper for notFollowedBy that returns the result of the first parser. +-- Also works around the issue that, at least in Parsec 3.0.0, notFollowedBy +-- accepts only parsers with return type Char. +notFollowedBy' :: Parser a -> Parser b -> Parser a +notFollowedBy' p e = do x <- p + C.notFollowedBy $ P.try (e >> return '*') + return x + +-- Parse a maximal string without markup +textParser :: Context -> Parser [T.Segment] +textParser c = + C.many1 (P.noneOf "<" <|> P.try (notFollowedBy' (P.char '<') suffixes)) + >>= retSegment c . T.Text + where suffixes = C.choice $ map (P.try . P.string) + [ "icon=" , "hspace=", "raw=" + , "action=", "/action>", "fn=", "/fn>" + , "box", "/box>", "fc=", "/fc>" ] + +-- Parse a "raw" tag, which we use to prevent other tags from creeping in. +-- The format here is net-string-esque: a literal "<raw=" followed by a string +-- of digits (base 10) denoting the length of the raw string, a literal ":" as +-- digit-string-terminator, the raw string itself, and then a literal "/>". +rawParser :: Context -> Parser [T.Segment] +rawParser c = do + P.string "<raw=" + lenstr <- C.many1 P.digit + P.char ':' + case reads lenstr of + [(len,[])] -> do + CM.guard ((len :: Integer) <= fromIntegral (maxBound :: Int)) + s <- C.count (fromIntegral len) P.anyChar + P.string "/>" + retSegment c (T.Text s) + _ -> CM.mzero + +iconParser :: Context -> Parser [T.Segment] +iconParser c = do + P.string "<icon=" + i <- C.manyTill (P.noneOf ">") (P.try (P.string "/>")) + retSegment c (T.Icon i) + +hspaceParser :: Context -> Parser [T.Segment] +hspaceParser c = do + P.string "<hspace=" + pVal <- C.manyTill P.digit (P.try (P.string "/>")) + retSegment c (T.Hspace (fromMaybe 0 $ readMaybe pVal)) + +actionParser :: Context -> Parser [T.Segment] +actionParser (ti, fi, act) = do + P.string "<action=" + command <- C.between (P.char '`') (P.char '`') (C.many1 (P.noneOf "`")) + <|> C.many1 (P.noneOf ">") + buttons <- (P.char '>' >> return "1") <|> (P.space >> P.spaces >> + C.between (P.string "button=") (P.string ">") (C.many1 (P.oneOf "12345"))) + let a = T.Spawn (toButtons buttons) command + a' = case act of + Nothing -> Just [a] + Just act' -> Just $ a : act' + s <- C.manyTill (allParsers (ti, fi, a')) (P.try $ P.string "</action>") + return (concat s) + +toButtons :: String -> [T.Button] +toButtons = map (\x -> read [x]) + +colorParser :: Context -> Parser [T.Segment] +colorParser (T.TextRenderInfo _ _ _ bs, fidx, a) = do + c <- C.between (P.string "<fc=") (P.string ">") (C.many1 colorc) + let colorParts = break (==':') c + let (ot,ob) = case break (==',') (drop 1 $ snd colorParts) of + (top,',':btm) -> (top, btm) + (top, _) -> (top, top) + tri = T.TextRenderInfo (fst colorParts) + (fromMaybe (-1) $ readMaybe ot) + (fromMaybe (-1) $ readMaybe ob) + bs + s <- C.manyTill (allParsers (tri, fidx, a)) (P.try $ P.string "</fc>") + return (concat s) + where colorc = P.alphaNum <|> P.oneOf ",:#" + +boxParser :: Context -> Parser [T.Segment] +boxParser (T.TextRenderInfo cs ot ob bs, f, a) = do + c <- C.between (P.string "<box") (P.string ">") + (C.option "" (C.many1 (P.alphaNum <|> P.oneOf "= #,"))) + let b = T.Box T.BBFull (T.BoxOffset T.C 0) 1 cs (T.BoxMargins 0 0 0 0) + let g = boxReader b (words c) + s <- C.manyTill + (allParsers (T.TextRenderInfo cs ot ob (g : bs), f, a)) + (P.try $ P.string "</box>") + return (concat s) + +boxReader :: T.Box -> [String] -> T.Box +boxReader b [] = b +boxReader b (x:xs) = boxReader (boxParamReader b param val) xs + where (param,val) = case break (=='=') x of + (p,'=':v) -> (p, v) + (p, _) -> (p, "") + +boxParamReader :: T.Box -> String -> String -> T.Box +boxParamReader b _ "" = b + +boxParamReader (T.Box bb off lw fc mgs) "type" val = + T.Box (fromMaybe bb $ readMaybe ("BB" ++ val)) off lw fc mgs + +boxParamReader (T.Box bb (T.BoxOffset alg off) lw fc mgs) "offset" (a:o) = + T.Box bb (T.BoxOffset align offset) lw fc mgs + where offset = fromMaybe off $ readMaybe o + align = fromMaybe alg $ readMaybe [a] + +boxParamReader (T.Box bb off lw fc mgs) "width" val = + T.Box bb off (fromMaybe lw $ readMaybe val) fc mgs + +boxParamReader (T.Box bb off lw _ mgs) "color" val = + T.Box bb off lw val mgs + +boxParamReader (T.Box bb off lw fc mgs@(T.BoxMargins mt mr mb ml)) ('m':pos) v = + let mgs' = case pos of + "t" -> T.BoxMargins (maybeVal mt) mr mb ml + "r" -> T.BoxMargins mt (maybeVal mr) mb ml + "b" -> T.BoxMargins mt mr (maybeVal mb) ml + "l" -> T.BoxMargins mt mr mb (maybeVal ml) + _ -> mgs + maybeVal d = fromMaybe d (readMaybe v) + in T.Box bb off lw fc mgs' + +boxParamReader b _ _ = b + +fontParser :: Context -> Parser [T.Segment] +fontParser (i, _, a) = do + f <- C.between (P.string "<fn=") (P.string ">") (C.many1 P.digit) + s <- C.manyTill (allParsers (i, fromMaybe 0 $ readMaybe f, a)) + (P.try $ P.string "</fn>") + return (concat s) diff --git a/src/Xmobar/Config/Types.hs b/src/Xmobar/Config/Types.hs index 4959aa1..785b55b 100644 --- a/src/Xmobar/Config/Types.hs +++ b/src/Xmobar/Config/Types.hs @@ -15,14 +15,28 @@ module Xmobar.Config.Types ( Config (..) , XPosition (..), Align (..), Border (..), TextOutputFormat (..) + , Segment , FontIndex + , Box(..) + , BoxBorder(..) + , BoxOffset(..) + , BoxMargins(..) + , TextRenderInfo(..) + , Widget(..) , SignalChan (..) + , Action (..) + , Button ) where import qualified Control.Concurrent.STM as STM import qualified Xmobar.Run.Runnable as R import qualified Xmobar.System.Signal as S +import Data.Int (Int32) +import Foreign.C.Types (CInt) + +import Xmobar.Run.Actions (Action (..), Button) + -- $config -- Configuration data type @@ -71,20 +85,94 @@ data Config = , template :: String -- ^ The output template , verbose :: Bool -- ^ Emit additional debug messages , signal :: SignalChan -- ^ Channel to send signals to xmobar + , dpi :: Double -- ^ DPI scaling factor for fonts } deriving (Read, Show) -data XPosition = Top - | TopH Int - | TopW Align Int - | TopSize Align Int Int - | TopP Int Int +-- | The position datatype +data XPosition = Top -- ^ Top of the screen, full width, auto height + + | TopH -- ^ Top of the screen, full width with + -- specific height + Int -- ^ Height (in pixels) + + -- | Top of the screen, full width with + -- specific height and margins + | TopHM + Int -- ^ Height (in pixels) + Int -- ^ Left margin (in pixels) + Int -- ^ Right margin (in pixels) + Int -- ^ Top margin (in pixels) + Int -- ^ Bottom margin (in pixels) + + -- | Top of the screen with specific width + -- (as screen percentage) and alignment + | TopW + Align -- ^ Alignement (L|C|R) + Int -- ^ Width as screen percentage (0-100) + + -- | Top of the screen with specific width + -- (as screen percentage), height and + -- alignment + | TopSize + Align -- ^ Alignement (L|C|R) + Int -- ^ Width as screen percentage (0-100) + Int -- ^ Height (in pixels) + + -- | Top of the screen with specific left/right + -- margins + | TopP + Int -- ^ Left margin (in pixels) + Int -- ^ Right margin (in pixels) + + -- | Bottom of the screen, full width, auto height | Bottom - | BottomH Int - | BottomP Int Int - | BottomW Align Int - | BottomSize Align Int Int - | Static {xpos, ypos, width, height :: Int} - | OnScreen Int XPosition + + | BottomH -- ^ Bottom of the screen, full width, with + -- specific height + Int -- ^ Height (in pixels) + + -- | Bottom of the screen with specific height + -- and margins + | BottomHM + Int -- ^ Height (in pixels) + Int -- ^ Left margin (in pixels) + Int -- ^ Right margin (in pixels) + Int -- ^ Top margin (in pixels) + Int -- ^ Bottom margin (in pixels) + + -- | Bottom of the screen with specific + -- left/right margins + | BottomP + Int -- ^ Left margin (in pixels) + Int -- ^ Bottom margin (in pixels) + + -- | Bottom of the screen with specific width + -- (as screen percentage) and alignment + -- and alignment + | BottomW + Align -- ^ Alignement (L|C|R) + Int -- ^ Width as screen percentage (0-100) + + -- | Bottom of the screen with specific width + -- (as screen percentage), height + -- and alignment + | BottomSize + Align -- ^ Alignement (L|C|R) + Int -- ^ Width as screen percentage (0-100) + Int -- ^ Height (in pixels) + + -- | Static position and specific size + | Static { xpos :: Int -- ^ Position X (in pixels) + , ypos :: Int -- ^ Position Y (in pixels) + , width :: Int -- ^ Width (in pixels) + , height :: Int -- ^ Height (in pixels) + } + + -- | Along with the position characteristics + -- specify the screen to display the bar + | OnScreen + Int -- ^ Screen id (primary is 0) + XPosition -- ^ Position deriving ( Read, Show, Eq ) data Align = L | R | C deriving ( Read, Show, Eq ) @@ -110,3 +198,34 @@ instance Read SignalChan where instance Show SignalChan where show (SignalChan (Just _)) = "SignalChan (Just <tmvar>)" show (SignalChan Nothing) = "SignalChan Nothing" + +data Widget = Icon String | Text String | Hspace Int32 deriving Show + +data BoxOffset = BoxOffset Align Int32 deriving (Eq, Show) + +-- margins: Top, Right, Bottom, Left +data BoxMargins = BoxMargins Int32 Int32 Int32 Int32 deriving (Eq, Show) + +data BoxBorder = BBTop + | BBBottom + | BBVBoth + | BBLeft + | BBRight + | BBHBoth + | BBFull + deriving (Read, Eq, Show) + +data Box = Box { bBorder :: BoxBorder + , bOffset :: BoxOffset + , bWidth :: CInt + , bColor :: String + , bMargins :: BoxMargins + } deriving (Eq, Show) + +data TextRenderInfo = TextRenderInfo { tColorsString :: String + , tBgTopOffset :: Int32 + , tBgBottomOffset :: Int32 + , tBoxes :: [Box] + } deriving Show + +type Segment = (Widget, TextRenderInfo, FontIndex, Maybe [Action]) |