Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • jens/qbar
  • jktr/qbar
  • snowball/qbar
3 results
Show changes
module QBar.Theme (
Theme(..),
ThemedBlockOutput(..),
ThemedBlockText(..),
ThemedBlockTextSegment(..),
defaultTheme,
findTheme,
isAnimated,
mkTheme,
mkThemedBlockOutput,
themeNames,
themes,
whiteThemedBlockOutput,
) where
import QBar.BlockOutput
import QBar.Color
import QBar.Prelude
import Control.Applicative ((<|>))
import Control.Lens ((^.))
import Control.Monad.State.Lazy (State, evalState, get, put)
import Data.Colour.RGBSpace
import Data.Colour.RGBSpace.HSV (hsv)
import Data.HashMap.Lazy qualified as HM
import Data.Maybe (fromMaybe)
import Data.Text.Lazy qualified as T
import Data.Time.Clock.POSIX (getPOSIXTime)
import Pipes
data ThemedBlockOutput = ThemedBlockOutput {
_fullText :: ThemedBlockText,
_shortText :: Maybe ThemedBlockText,
_blockName :: Maybe T.Text
}
deriving (Eq, Show)
newtype ThemedBlockText = ThemedBlockText [ThemedBlockTextSegment]
deriving (Eq, Show)
instance Semigroup ThemedBlockText where
(ThemedBlockText a) <> (ThemedBlockText b) = ThemedBlockText (a <> b)
instance Monoid ThemedBlockText where
mempty = ThemedBlockText []
data ThemedBlockTextSegment = ThemedBlockTextSegment {
themedSegmentText :: T.Text,
color :: Color,
backgroundColor :: Maybe Color
}
deriving (Eq, Show)
data Theme = StaticTheme StaticTheme | AnimatedTheme AnimatedTheme
type StaticTheme = [BlockOutput] -> [ThemedBlockOutput]
type SimplifiedTheme = Bool -> Importance -> (Color, Maybe Color)
type AnimatedTheme = forall r. Pipe [BlockOutput] [ThemedBlockOutput] IO r
isAnimated :: Theme -> Bool
isAnimated (AnimatedTheme _) = True
isAnimated _ = False
themesList :: [(Text, Theme)]
themesList = [
("default", defaultTheme),
("rainbow", rainbowTheme)
]
themeNames :: [Text]
themeNames = map fst themesList
themes :: HM.HashMap Text Theme
themes = HM.fromList themesList
findTheme :: Text -> Either Text Theme
findTheme themeName = maybe invalidThemeName Right $ HM.lookup themeName themes
where
invalidThemeName :: Either Text Theme
invalidThemeName = Left $ "Invalid theme: " <> themeName
mkTheme :: SimplifiedTheme -> Theme
mkTheme theming' = StaticTheme $ map themeBlock
where
themeBlock :: BlockOutput -> ThemedBlockOutput
themeBlock block@BlockOutput{_blockName} = ThemedBlockOutput{_fullText = fullText', _shortText = shortText', _blockName}
where
theming :: SimplifiedTheme
theming
| block ^. invalid = invalidSimplifiedTheme
| otherwise = theming'
fullText' :: ThemedBlockText
fullText' = themeBlockText theming $ block ^. fullText
shortText' :: Maybe ThemedBlockText
shortText' = themeBlockText theming <$> block ^. shortText
themeBlockText :: SimplifiedTheme -> BlockText -> ThemedBlockText
themeBlockText theming (BlockText b) = ThemedBlockText $ themeSegment theming <$> b
themeSegment :: SimplifiedTheme -> BlockTextSegment -> ThemedBlockTextSegment
themeSegment theming BlockTextSegment {active, importance, segmentText} = mkThemedSegment (theming active importance) segmentText
themeSegment theming StyledBlockTextSegment {color, backgroundColor, segmentText} = mkThemedSegment (themedColor, themedBackgroundColor) segmentText
where
themedColor :: Color
themedColor = fromMaybe normalThemedColor color
themedBackgroundColor :: Maybe Color
themedBackgroundColor = backgroundColor <|> normalThemedBackground
(normalThemedColor, normalThemedBackground) = theming False normalImportant
mkThemedBlockOutput :: (Color, Maybe Color) -> Text -> ThemedBlockOutput
mkThemedBlockOutput color text = ThemedBlockOutput {
_fullText = mkThemedText color text,
_shortText = Nothing,
_blockName = Nothing
}
mkThemedText :: (Color, Maybe Color) -> Text -> ThemedBlockText
mkThemedText color text = ThemedBlockText [mkThemedSegment color text]
mkThemedSegment :: (Color, Maybe Color) -> Text -> ThemedBlockTextSegment
mkThemedSegment (color, backgroundColor) text = ThemedBlockTextSegment{themedSegmentText=text, color, backgroundColor}
whiteThemedBlockOutput :: Text -> ThemedBlockOutput
whiteThemedBlockOutput = mkThemedBlockOutput (ColorRGB (RGB 1 1 1), Nothing)
invalidColor :: Color
invalidColor = ColorRGBA (RGB (0x96 / 255) (0x98 / 255) (0x96 / 255)) (0x77 / 255)
invalidSimplifiedTheme :: SimplifiedTheme
invalidSimplifiedTheme _ _ = (invalidColor, Nothing)
defaultTheme :: Theme
defaultTheme = mkTheme defaultTheme'
where
defaultTheme' :: SimplifiedTheme
defaultTheme' True (CriticalImportant _) = (ColorRGB (RGB 0 0 0), Just $ ColorRGB (RGB 1 0 0))
defaultTheme' False (CriticalImportant _) = (ColorRGB (RGB 0.8 0.15 0.15), Nothing)
defaultTheme' True (ErrorImportant _) = (ColorRGB (RGB 1 0.3 0), Nothing)
defaultTheme' False (ErrorImportant _) = (ColorRGB (RGB 0.7 0.35 0.2), Nothing)
defaultTheme' True (WarnImportant _) = (ColorRGB (RGB 1 0.9 0), Nothing)
defaultTheme' False (WarnImportant _) = (ColorRGB (RGB 0.6 0.6 0), Nothing)
defaultTheme' True (NormalImportant _) = (ColorRGB (RGB 1 1 1), Nothing)
defaultTheme' False (NormalImportant _) = (ColorRGB (RGB (0x96 / 255) (0x98 / 255) (0x96 / 255)), Nothing)
rainbowTheme :: Theme
rainbowTheme = AnimatedTheme rainbowThemePipe
where
rainbowThemePipe :: AnimatedTheme
rainbowThemePipe = do
time <- liftIO $ fromRational . toRational <$> getPOSIXTime
yield . rainbowThemePipe' time =<< await
rainbowThemePipe
rainbowThemePipe' :: Double -> StaticTheme
rainbowThemePipe' time blocks = reverse $ evalState (mapM rainbowBlock $ reverse blocks) 0
where
rainbowBlock :: BlockOutput -> State Integer ThemedBlockOutput
rainbowBlock block@BlockOutput{_blockName} = do
let text = rawText $ block ^. fullText
let chars = reverse . splitToChars $ text
coloredChars <- mapM rainbowChar chars
let rainbowText = reverse coloredChars
return $ ThemedBlockOutput {
_blockName,
_fullText = ThemedBlockText rainbowText,
_shortText = Nothing
}
rainbowChar :: T.Text -> State Integer ThemedBlockTextSegment
rainbowChar char = do
color <- nextRainbowColor
return $ mkThemedSegment (color, Nothing) $ char
nextRainbowColor :: State Integer Color
-- nextRainbowColor = state $ \index -> (rainbowColor (fromInteger index), index + 1)
nextRainbowColor = do
index <- get
put $ index + 1
return $ rainbowColor (fromInteger index + time * 10)
rainbowColor :: Double -> Color
rainbowColor position =
let hue' = position * 3
color = hsv hue' 0.8 1.0
in ColorRGB color
splitToChars :: T.Text -> [T.Text]
splitToChars = splitStringToChars . T.unpack
splitStringToChars :: String -> [T.Text]
splitStringToChars [] = []
splitStringToChars ('&':xs) = splitStringToCharsAmp "&" xs
splitStringToChars (x:xs) = T.singleton x : splitStringToChars xs
splitStringToCharsAmp :: String -> String -> [T.Text]
splitStringToCharsAmp _ [] = []
splitStringToCharsAmp acc (';':xs) = T.pack (acc <> ";") : splitStringToChars xs
splitStringToCharsAmp acc (x:xs) = splitStringToCharsAmp (acc <> [x]) xs
{-# LANGUAGE OverloadedLists #-}
module QBar.Time (
HasSleepScheduler(..),
Interval(..),
SleepScheduler,
createSleepScheduler,
everyMinute,
everyNSeconds,
humanReadableInterval,
nextIntervalTime,
sleepUntil',
sleepUntil,
sleepUntilInterval',
sleepUntilInterval,
) where
import QBar.Prelude
import Control.Concurrent.Async
import Control.Concurrent.Event qualified as Event
import Control.Concurrent.MVar
import Data.Ord (comparing)
import Data.SortedList (SortedList, toSortedList, fromSortedList, singleton, partition, insert)
import Data.Time.Clock (UTCTime, getCurrentTime, diffUTCTime, utctDayTime, addUTCTime)
newtype Interval = IntervalSeconds Integer
deriving (Read, Show)
-- |Describes an interval that is run every "n" seconds after midnight.
everyNSeconds :: Integer -> Interval
everyNSeconds = IntervalSeconds
-- |Describes an interval that is run every minute.
everyMinute :: Interval
everyMinute = IntervalSeconds 60
nextIntervalTime :: MonadIO m => Interval -> m UTCTime
nextIntervalTime (IntervalSeconds intervalSeconds) = liftIO $ do
now <- getCurrentTime
let dayTime = utctDayTime now
let daySeconds :: Integer = floor dayTime
let intervalId = daySeconds `div` intervalSeconds
return now {
utctDayTime = fromInteger $ (intervalId + 1) * intervalSeconds
}
humanReadableInterval :: Interval -> String
humanReadableInterval (IntervalSeconds i) = show i <> "s"
data SleepScheduler = SleepScheduler (MVar (SortedList ScheduledEvent, [ScheduledEvent])) Event.Event
data ScheduledEvent = ScheduledEvent {
time :: UTCTime,
event :: Event.Event,
fireOnNegativeTimeJump :: Bool
} deriving Eq
instance Ord ScheduledEvent where
compare = comparing time
class HasSleepScheduler m where
askSleepScheduler :: m SleepScheduler
createSleepScheduler :: MonadIO m => m SleepScheduler
createSleepScheduler = liftIO $ do
scheduler <- SleepScheduler <$> newMVar ([], []) <*> Event.new
link =<< async (schedulerThread scheduler)
return scheduler
where
schedulerThread :: SleepScheduler -> IO ()
schedulerThread (SleepScheduler eventsMVar trigger) = schedulerThread' =<< getCurrentTime
where
schedulerThread' :: UTCTime -> IO ()
schedulerThread' lastTime = do
start <- getCurrentTime
-- Check for a negative time step (threshold is between 5 and 65 seconds, depending on loop activity)
when (start < addUTCTime (fromInteger (-5)) lastTime) $ fireEvents fireOnNegativeTimeJump
(sortedEvents, _) <- readMVar eventsMVar
waitResult <- case fromSortedList sortedEvents of
[] -> True <$ Event.wait trigger
(ScheduledEvent{time} : _) -> waitForEvent time
when waitResult $ do
now <- getCurrentTime
fireEvents (checkEvent now)
schedulerThread' start
-- Waits for the next event, with a timeout. A return value of 'False' indicates a timeout occured.
waitForEvent :: UTCTime -> IO Bool
waitForEvent eventTime = do
now <- getCurrentTime
let timeUntil = diffUTCTime eventTime now
if
| timeUntil <= 0 -> return True
| timeUntil < 60 -> True <$ Event.waitTimeout trigger (ceiling $ toRational timeUntil * 1000000)
-- False indicates a timeout, in which case no events need to be fired
| otherwise -> Event.waitTimeout trigger (60 * 1000000)
fireEvents :: (ScheduledEvent -> Bool) -> IO ()
fireEvents predicate =
modifyMVar_ eventsMVar $ \(hots, colds) -> do
let allEvents = hots <> toSortedList colds
let (activeEvents, futureEvents) = partition predicate allEvents
mapM_ (Event.set . event) activeEvents
-- Sleep scheduler thread 'Event' is cleared during 'modifyMVar_' to prevent race conditions.
Event.clear trigger
return (futureEvents, [])
-- Predicate to check if an event should be fired.
checkEvent :: UTCTime -> ScheduledEvent -> Bool
checkEvent now ScheduledEvent{time} = now >= time
queueScheduledEvent :: MonadIO m => SleepScheduler -> ScheduledEvent -> m ()
queueScheduledEvent (SleepScheduler eventsMVar trigger) event@ScheduledEvent{time=eventTime} = liftIO $
modifyMVar_ eventsMVar $ \(sorted, unsorted) ->
-- Sleep scheduler thread 'Event' is set during 'modifyMVar_' to prevent race conditions.
case fromSortedList sorted of
[] -> (singleton event, unsorted) <$ Event.set trigger
(first : _) ->
if eventTime < time first
-- Event happens before the first event, so it is inserted at the front of the sorted list and the scheduler thread is notified
then (insert event sorted, unsorted) <$ Event.set trigger
-- Otherwise it is added to the unsorted pool and will be handled later.
else return (sorted, event:unsorted)
-- |Suspends the thread until the given time is reached.
sleepUntil :: (HasSleepScheduler m, MonadIO m) => UTCTime -> m ()
sleepUntil time = do
scheduler <- askSleepScheduler
sleepUntil' scheduler time
sleepUntil' :: MonadIO m => SleepScheduler -> UTCTime -> m ()
sleepUntil' scheduler time = liftIO $ do
event <- Event.new
queueScheduledEvent scheduler (ScheduledEvent {time, event, fireOnNegativeTimeJump=False})
Event.wait event
-- |Suspends the thread until the next time boundary described by 'Interval' is reached. Also returns when the system time jumps backwards.
sleepUntilInterval :: (HasSleepScheduler m, MonadIO m) => Interval -> m ()
sleepUntilInterval interval = do
scheduler <- askSleepScheduler
sleepUntilInterval' scheduler interval
sleepUntilInterval' :: MonadIO m => SleepScheduler -> Interval -> m ()
sleepUntilInterval' scheduler interval = liftIO $ do
event <- Event.new
time <- nextIntervalTime interval
queueScheduledEvent scheduler (ScheduledEvent {time, event, fireOnNegativeTimeJump=True})
Event.wait event
module QBar.Utils (
mkBroadcastCacheP,
mkBroadcastP,
randomIdentifier,
signalEventPipe,
) where
import QBar.Prelude
import Control.Concurrent.Event as Event
import Control.Concurrent.STM (atomically)
import Control.Concurrent.STM.TChan
import Control.Concurrent.STM.TVar
import Control.Monad (replicateM)
import Data.Text.Lazy qualified as T
import Pipes
import System.Random
-- Pipe that signals an 'Event' after every value that passes through
signalEventPipe :: MonadIO m => Event.Event -> Pipe a a m r
signalEventPipe event = forever $ (yield =<< await) >> liftIO (Event.signal event)
randomIdentifier :: MonadIO m => m Text
randomIdentifier = liftIO $ T.pack <$> replicateM 8 randomCharacter
where
randomCharacter :: IO Char
randomCharacter = do
index <- randomRIO (0, T.length alphabet - 1)
return $ T.index alphabet index
alphabet :: T.Text
alphabet = T.pack $ ['A'..'Z'] ++ ['a'..'z'] ++ ['0'..'9']
-- |Creates a pair of consumer and producer. Both can be used multiple times in parallel.
-- |All values send to a consumer will be sent to all currently running producers.
mkBroadcastP :: forall a. IO (Consumer a IO (), Producer a IO ())
mkBroadcastP = do
chan <- newBroadcastTChanIO
return (sendToStore chan, recvFromStore chan)
where
sendToStore :: TChan a -> Consumer a IO ()
sendToStore chan = forever $ do
value <- await
liftIO . atomically $ writeTChan chan value
-- Monad will be forked when new outputs connect
recvFromStore :: TChan a -> Producer a IO ()
recvFromStore chan = do
outputChan <- liftIO . atomically $ dupTChan chan
forever $ yield =<< (liftIO . atomically $ readTChan outputChan)
-- |Creates a pair of consumer and producer. Both can be used multiple times in parallel.
-- |All values send to a consumer will be sent to all currently running producers.
-- |When running a new producer it will immediateley receive the latest value that was sent to a consumer.
mkBroadcastCacheP :: forall a. a -> IO (Consumer a IO (), Producer a IO ())
mkBroadcastCacheP initialValue = do
store <- (,) <$> newTVarIO initialValue <*> newBroadcastTChanIO
return (sendToStore store, recvFromStore store)
where
sendToStore :: (TVar a, TChan a) -> Consumer a IO ()
sendToStore (var, chan) = forever $ do
value <- await
liftIO . atomically $ do
writeTVar var value
writeTChan chan value
-- Monad will be forked when new outputs connect
recvFromStore :: (TVar a, TChan a) -> Producer a IO ()
recvFromStore (var, chan) = do
(outputChan, value) <- liftIO . atomically $ do
value <- readTVar var
outputChan <- dupTChan chan
return (outputChan, value)
yield value
forever $ yield =<< (liftIO . atomically $ readTChan outputChan)
import Prelude
main :: IO ()
main = putStrLn "Test suite not yet implemented"
{-# LANGUAGE TemplateHaskell #-}
module QBar.BlockOutput where
import QBar.BlockText
import Control.Lens
import Data.Aeson.TH
import qualified Data.Text.Lazy as T
data BlockOutput = BlockOutput
{ _fullText :: BlockText
, _shortText :: Maybe BlockText
, _blockName :: Maybe T.Text
, _invalid :: Bool
}
deriving (Eq, Show)
$(deriveJSON defaultOptions ''BlockOutput)
makeLenses ''BlockOutput
mkBlockOutput :: BlockText -> BlockOutput
mkBlockOutput text = BlockOutput
{ _fullText = text
, _shortText = Nothing
, _blockName = Nothing
, _invalid = False
}
mkErrorOutput :: T.Text -> BlockOutput
mkErrorOutput = mkBlockOutput . importantText criticalImportant
emptyBlock :: BlockOutput
emptyBlock = mkBlockOutput mempty
addIcon :: T.Text -> BlockOutput -> BlockOutput
addIcon icon = over fullText $ (<>) . normalText $ icon <> " "
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
module QBar.BlockText where
import Data.Aeson.TH
import qualified Data.Text.Lazy as T
import Data.Int (Int64)
import QBar.Pango
newtype BlockText = BlockText [BlockTextSegment]
deriving (Eq, Show)
instance Semigroup BlockText where
(BlockText a) <> (BlockText b) = BlockText (a <> b)
instance Monoid BlockText where
mempty = BlockText []
intercalate :: Monoid a => a -> [a] -> a
intercalate _ [] = mempty
intercalate _ [x] = x
intercalate inter (x:xs) = x <> inter <> intercalate inter xs
data BlockTextSegment = BlockTextSegment {
active :: Bool,
importance :: Importance,
text :: T.Text
}
| PangoTextSegment T.Text
deriving (Eq, Show)
type Importance = Float
$(deriveJSON defaultOptions ''BlockTextSegment)
$(deriveJSON defaultOptions ''BlockText)
normalImportant :: Importance
normalImportant = 1
warnImportant :: Importance
warnImportant = 2
errorImportant :: Importance
errorImportant = 3
criticalImportant :: Importance
criticalImportant = 4
isCritical :: Importance -> Bool
isCritical i
| i >= criticalImportant = True
| otherwise = False
isError :: Importance -> Bool
isError i
| isCritical i = False
| i >= errorImportant = True
| otherwise = False
isWarning :: Importance -> Bool
isWarning i
| isCritical i = False
| isError i = False
| i >= warnImportant = True
| otherwise = False
isNormal :: Importance -> Bool
isNormal i
| isCritical i = False
| isError i = False
| isWarning i = False
| otherwise = True
toImportance :: Real a => (a, a, a, a, a, a) -> a -> Importance
toImportance (tMax, tCrit, tErr, tWarn, tNorm, tMin) =
toImportance' (Just tMax, tCrit, tErr, tWarn, tNorm, Just tMin)
toImportance' :: forall a. Real a => (Maybe a, a, a, a, a, Maybe a) -> a -> Importance
toImportance' (tMax, tCrit, tErr, tWarn, tNorm, tMin) val
| tCrit <= val = 4 + valueCrit tMax tCrit val
| tErr <= val = 3 + linearMatch tCrit tErr val
| tWarn <= val = 2 + linearMatch tErr tWarn val
| tNorm <= val = 1 + linearMatch tWarn tNorm val
| otherwise = 0 + valueOtherwise tNorm tMin val
where
e :: Importance
e = exp 1
linearMatch :: a -> a -> a -> Importance
linearMatch u l v = frac (v - l) (u - l)
logarithmicMatch :: a -> a -> Importance
logarithmicMatch l u = 1 - 1 / log (e + realToFrac (u - l))
frac :: a -> a -> Importance
frac a b = realToFrac a / realToFrac b
valueCrit :: Maybe a -> a -> a -> Importance
valueCrit (Just tMax') tCrit' v
| tMax' > v = linearMatch tMax' tCrit' v
| otherwise = 1
valueCrit Nothing tCrit' v = logarithmicMatch tCrit' v
valueOtherwise :: a -> Maybe a -> a -> Importance
valueOtherwise tNorm' (Just tMin') v
| tMin' < v = linearMatch tNorm' tMin' v
| otherwise = 0
valueOtherwise tNorm' Nothing v = 1 - logarithmicMatch v tNorm'
removePango :: BlockText -> T.Text
removePango (BlockText b) = foldr ((<>) . removePangoFromSegment) "" b
where
removePangoFromSegment :: BlockTextSegment -> T.Text
removePangoFromSegment BlockTextSegment { active=_active, importance=_importance, text } = text
removePangoFromSegment (PangoTextSegment text) =
case parsePango text of
Left _ -> text
Right parsed -> removeFormatting parsed
printedLength :: BlockText -> Int64
printedLength (BlockText b) = foldr ((+) . printedLength') 0 b
where
printedLength' :: BlockTextSegment -> Int64
printedLength' BlockTextSegment { text, active=_, importance=_ } = T.length text
printedLength' (PangoTextSegment _) = 0
mkText :: Bool -> Importance -> T.Text -> BlockText
mkText active importance text = BlockText [BlockTextSegment { text = pangoFriendly text, active, importance }]
where
pangoFriendly :: T.Text -> T.Text
pangoFriendly = T.replace "<" "&lt;" . T.replace ">" "&gt;" . T.replace "&" "&amp;"
activeImportantText :: Importance -> T.Text -> BlockText
activeImportantText = mkText True
importantText :: Importance -> T.Text -> BlockText
importantText = mkText False
activeText :: T.Text -> BlockText
activeText = mkText True normalImportant
normalText :: T.Text -> BlockText
normalText = mkText False normalImportant
pangoText :: T.Text -> BlockText
pangoText pango = BlockText [PangoTextSegment pango]
surroundWith :: (T.Text -> BlockText) -> T.Text -> T.Text -> BlockText -> BlockText
surroundWith format left right middle = format left <> middle <> format right
data Color = ColorRGB Float Float Float | ColorRGBA Float Float Float Float
colorToHex :: Color -> T.Text
colorToHex = colorToHex'
where
colorToHex' :: Color -> T.Text
colorToHex' (ColorRGB r g b) = "#" <> (toDualHex . floor) (r * 255) <> (toDualHex . floor) (g * 255) <> (toDualHex . floor) (b * 255)
colorToHex' (ColorRGBA r g b a) = "#" <> (toDualHex . floor) (r * 255) <> (toDualHex . floor) (g * 255) <> (toDualHex . floor) (b * 255) <> (toDualHex . floor) (a * 255)
toHex :: Int -> T.Text
toHex 0 = "0"
toHex 1 = "1"
toHex 2 = "2"
toHex 3 = "3"
toHex 4 = "4"
toHex 5 = "5"
toHex 6 = "6"
toHex 7 = "7"
toHex 8 = "8"
toHex 9 = "9"
toHex 10 = "A"
toHex 11 = "B"
toHex 12 = "C"
toHex 13 = "D"
toHex 14 = "E"
toHex 15 = "F"
toHex x = toHex $ mod x 16
toDualHex :: Int -> T.Text
toDualHex x = toHex (div x 16) <> toHex x
module QBar.Blocks (
module QBar.Blocks.Battery,
module QBar.Blocks.Date
) where
import QBar.Blocks.Battery
import QBar.Blocks.Date
{-# LANGUAGE ApplicativeDo #-}
module QBar.Cli where
import qualified Data.Text as T
import Options.Applicative
data BarCommand = BarServer | NoFilter | RainbowFilter
barCommandParser :: Parser BarCommand
barCommandParser = hsubparser
( command "server" (info (pure BarServer) (progDesc "Start a new qbar server. Should be called by swaybar, i3bar or or another i3bar-protocol compatible host process.")) <>
command "default" (info (pure NoFilter) (progDesc "Send a message to a running qbar server.")) <>
command "rainbow" (info (pure RainbowFilter) (progDesc "Send a message to a running qbar server."))
)
data MainOptions = MainOptions {
verbose :: Bool,
indicator :: Bool,
socketLocation :: Maybe T.Text,
barCommand :: BarCommand
}
mainOptionsParser :: Parser MainOptions
mainOptionsParser = do
verbose <- switch $ long "verbose" <> short 'v' <> help "Print more diagnostic output to stderr (including a copy of every bar update)."
indicator <- switch $ long "indicator" <> short 'i' <> help "Show render indicator."
socketLocation <- optional $ strOption $ long "socket" <> short 's' <> metavar "SOCKET" <> help "Control socket location. By default determined by WAYLAND_SOCKET location."
barCommand <- barCommandParser
return MainOptions {verbose, indicator, socketLocation, barCommand}
parser :: ParserInfo MainOptions
parser = info (mainOptionsParser <**> helper)
(fullDesc <> header "qbar - queezles {i3,sway}bar infrastructure")
parserPrefs :: ParserPrefs
parserPrefs = prefs showHelpOnEmpty
parseOptions :: IO MainOptions
parseOptions = customExecParser parserPrefs parser
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
module QBar.DefaultConfig where
import QBar.Blocks
import QBar.BlockOutput
import QBar.Core
import Pipes
import Control.Lens
blockLocation :: String -> FilePath
blockLocation name = "~/.config/qbar/blocks/" <> name
generateDefaultBarConfig :: BarIO ()
generateDefaultBarConfig = do
systemInfoInterval <- sharedInterval 10
let todo = systemInfoInterval (blockScript $ blockLocation "todo")
let wifi = systemInfoInterval (blockScript $ blockLocation "wifi2") >-> modify (addIcon "📡\xFE0E")
let networkEnvironment = systemInfoInterval (blockScript $ blockLocation "network-environment")
let cpu = systemInfoInterval (blockScript $ blockLocation "cpu_usage") >-> modify ((blockName ?~ "cpu") . addIcon "💻\xFE0E") >-> autoPadding
let ram = systemInfoInterval (blockScript $ blockLocation "memory") >-> modify (addIcon "🐏\xFE0E") >-> autoPadding
let temperature = systemInfoInterval (blockScript $ blockLocation "temperature") >-> autoPadding
let volumeBlock = startPersistentBlockScript $ blockLocation "volume-pulseaudio -S -F3"
let battery = systemInfoInterval $ batteryBlock >-> modify (blockName ?~ "battery")
addBlock dateBlock
addBlock battery
addBlock volumeBlock
addBlock temperature
addBlock ram
addBlock cpu
addBlock networkEnvironment
addBlock wifi
addBlock todo
{-# LANGUAGE TemplateHaskell #-}
module QBar.Filter where
import QBar.BlockOutput
import QBar.BlockText
import Control.Monad.State.Lazy (State, evalState, get, put)
import Data.Aeson.TH
import Data.Colour.RGBSpace.HSV (hsv)
import qualified Data.Text.Lazy as T
import Control.Lens hiding (index)
import Numeric (showHex)
import Data.Colour.RGBSpace
data Filter = StaticFilter StaticFilter
| AnimatedFilter AnimatedFilter
deriving Show
data StaticFilter = None
deriving Show
data AnimatedFilter = Rainbow
deriving Show
$(deriveJSON defaultOptions ''Filter)
$(deriveJSON defaultOptions ''StaticFilter)
$(deriveJSON defaultOptions ''AnimatedFilter)
isAnimatedFilter :: Filter -> Bool
isAnimatedFilter (AnimatedFilter _) = True
isAnimatedFilter _ = False
applyFilter :: Filter -> Double -> [BlockOutput] -> [BlockOutput]
applyFilter (StaticFilter None) = static id
applyFilter (AnimatedFilter Rainbow) = rainbow
static :: a -> Double -> a
static fn _ = fn
coloredText :: T.Text -> T.Text -> T.Text
coloredText color text = "<span color='" <> color <> "'>" <> text <> "</span>"
pangoColor :: RGB Double -> T.Text
pangoColor (RGB r g b) =
let r' = hexColorComponent r
g' = hexColorComponent g
b' = hexColorComponent b
in "#" <> r' <> g' <> b'
where
hexColorComponent :: Double -> T.Text
hexColorComponent val = paddedHexComponent $ T.pack $ showHex (max 0 $ min 255 (truncate (val * 255) :: Int)) ""
paddedHexComponent hex =
let len = 2 - T.length hex
padding = if len == 1 then "0" else ""
in padding <> hex
rainbow :: Double -> [BlockOutput] -> [BlockOutput]
rainbow time blocks = reverse $ evalState (mapM rainbowBlock $ reverse blocks) 0
where
rainbowBlock :: BlockOutput -> State Integer BlockOutput
rainbowBlock block = do
let text = removePango $ block^.fullText
let chars = T.unpack . T.reverse $ text
coloredChars <- mapM rainbowChar chars
let rainbowText = T.concat . reverse $ coloredChars
return $ fullText .~ pangoText rainbowText $ block
rainbowChar :: Char -> State Integer T.Text
rainbowChar char = do
color <- nextRainbowColor
return $ coloredText color $ T.singleton char
nextRainbowColor :: State Integer T.Text
-- nextRainbowColor = state $ \index -> (rainbowColor (fromInteger index), index + 1)
nextRainbowColor = do
index <- get
put $ index + 1
return $ rainbowColor (fromInteger index + time * 10)
rainbowColor :: Double -> T.Text
rainbowColor position =
let hue' = position * 3
color = hsv hue' 0.8 1.0
in pangoColor color
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.