summaryrefslogtreecommitdiffhomepage
path: root/src/Xmobar/App/TextEventLoop.hs
blob: 3b754acda9e88e20866941e464381b8054b25af4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
------------------------------------------------------------------------------
-- |
-- Module: Xmobar.App.TextEventLoop
-- Copyright: (c) 2022 Jose Antonio Ortega Ruiz
-- License: BSD3-style (see LICENSE)
--
-- Maintainer: jao@gnu.org
-- Stability: unstable
-- Portability: unportable
-- Created: Fri Jan 28, 2022 01:21
--
--
-- Text-only event loop
--
------------------------------------------------------------------------------

module Xmobar.App.TextEventLoop (textLoop) where

import Prelude hiding (lookup)
import Text.Printf
import System.IO
import Data.List (intercalate)

import Control.Monad.Reader

import Control.Concurrent.Async (Async)
import Control.Concurrent.STM

import Xmobar.System.Signal
import Xmobar.Config.Types (Config(textOutputColors), TextColorFormat(..))
import Xmobar.X11.Parsers (Segment, Widget(..), parseString, tColorsString, colorComponents)
import Xmobar.App.CommandThreads (initLoop, loop)

-- | Starts the main event loop and threads
textLoop :: Config -> IO ()
textLoop conf = loop conf (startTextLoop' conf)

startTextLoop' :: Config
               -> TMVar SignalType
               -> TMVar ()
               -> [[([Async ()], TVar String)]]
               -> IO ()
startTextLoop' cfg sig pauser vs = do
    hSetBuffering stdin LineBuffering
    hSetBuffering stdout LineBuffering
    tv <- initLoop sig pauser vs
    eventLoop cfg tv sig

-- | Continuously wait for a signal from a thread or a interrupt handler
eventLoop :: Config -> TVar [String] -> TMVar SignalType -> IO ()
eventLoop cfg tv signal = do
  typ <- atomically $ takeTMVar signal
  case typ of
    Wakeup -> updateString cfg tv >>= putStrLn >> eventLoop cfg tv signal
    _ -> eventLoop cfg tv signal

updateString :: Config -> TVar [String] -> IO String
updateString conf v = do
  s <- readTVarIO v
  let l:c:r:_ = s ++ repeat ""
  liftIO $ concat `fmap` mapM (parseStringAsText conf) [l, c, r]

asInt :: String -> String
asInt x = case (reads $ "0x" ++ x)  :: [(Integer, String)] of
  [(v, "") ] -> show v
  _ -> ""

namedColor :: String -> String
namedColor c =
  case c of
    "black" -> "0"; "red" -> "1"; "green" -> "2"; "yellow" -> "3"; "blue" -> "4";
    "magenta" -> "5"; "cyan" -> "6"; "white" -> "7"; _ -> ""

ansiCode :: String -> String
ansiCode ('#':r:g:[b]) = ansiCode ['#', '0', r, '0', g, '0', b]
ansiCode ('#':r0:r1:g0:g1:b0:[b1]) =
  "2;" ++ intercalate ";" (map asInt [[r0,r1], [g0,g1], [b0,b1]])
ansiCode ('#':n) = ansiCode n
ansiCode c = "5;" ++ if null i then namedColor c else i where i = asInt c

withAnsiColor :: (String, String) -> String -> String
withAnsiColor (fg, bg) s = wrap "38;" fg (wrap "48;" bg s)
  where wrap cd cl w =
          if null cl
          then w
          else "\x1b[" ++ cd ++ ansiCode cl ++ "m" ++ w ++ "\x1b[0m"

replaceAll :: (Eq a) => a -> [a] -> [a] -> [a]
replaceAll c s = concatMap (\x -> if x == c then s else [x])

xmlEscape :: String -> String
xmlEscape s = replaceAll '"' "&quot;" $
              replaceAll '\'' "&apos;" $
              replaceAll '<' "&lt;" $
              replaceAll '>' "&gt;" $
              replaceAll '&' "&amp;" s

withPangoColor :: (String, String) -> String -> String
withPangoColor (fg, bg) s =
  printf fmt (xmlEscape fg) (xmlEscape bg) (xmlEscape s)
  where fmt = "<span foreground=\"%s\" background=\"%s\">%s</span>"

withColor :: TextColorFormat -> (String, String) -> String -> String
withColor format color = case format of
                           NoColors -> id
                           Ansi -> withAnsiColor color
                           Pango -> withPangoColor color
                                

asText :: Config -> Segment -> String
asText conf (Text s, info, _, _) =
  withColor (textOutputColors conf) components s
  where components = colorComponents conf color
        color = tColorsString info
asText colors (Hspace n, i, x, y) =
  asText colors (Text $ replicate (fromIntegral n) ' ', i, x, y)
asText _ _ = ""

parseStringAsText :: Config -> String -> IO String
parseStringAsText c s = do
  segments <- parseString c s
  let txts = map (asText c) segments
  return (concat txts)