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
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE Rank2Types #-}
module QBar.Theme where
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 qualified Data.HashMap.Lazy as HM
import qualified Data.Text.Lazy as T
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 Color = ColorRGB (RGB Double) | ColorRGBA (RGB Double) Double
deriving (Eq, Show)
data ThemedBlockOutput = ThemedBlockOutput {
_fullText :: ThemedBlockText,
_shortText :: Maybe ThemedBlockText,
......@@ -39,7 +50,6 @@ data ThemedBlockTextSegment = ThemedBlockTextSegment {
}
deriving (Eq, Show)
data Theme = StaticTheme StaticTheme | AnimatedTheme AnimatedTheme
type StaticTheme = [BlockOutput] -> [ThemedBlockOutput]
......@@ -67,6 +77,7 @@ 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
......@@ -87,6 +98,13 @@ mkTheme theming' = StaticTheme $ map themeBlock
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 {
......@@ -104,7 +122,6 @@ mkThemedSegment (color, backgroundColor) text = ThemedBlockTextSegment{themedSeg
whiteThemedBlockOutput :: Text -> ThemedBlockOutput
whiteThemedBlockOutput = mkThemedBlockOutput (ColorRGB (RGB 1 1 1), Nothing)
invalidColor :: Color
invalidColor = ColorRGBA (RGB (0x96 / 255) (0x98 / 255) (0x96 / 255)) (0x77 / 255)
......@@ -115,16 +132,14 @@ defaultTheme :: Theme
defaultTheme = mkTheme defaultTheme'
where
defaultTheme' :: SimplifiedTheme
defaultTheme' active importance
| isCritical importance, active = (ColorRGB (RGB 0 0 0), Just $ ColorRGB (RGB 1 0 0))
| isCritical importance = (ColorRGB (RGB 0.8 0.15 0.15), Nothing)
| isError importance, active = (ColorRGB (RGB 1 0.3 0), Nothing)
| isError importance = (ColorRGB (RGB 0.7 0.35 0.2), Nothing)
| isWarning importance,active = (ColorRGB (RGB 1 0.9 0), Nothing)
| isWarning importance = (ColorRGB (RGB 0.6 0.6 0), Nothing)
| otherwise, active = (ColorRGB (RGB 1 1 1), Nothing)
| otherwise = (ColorRGB (RGB (0x96 / 255) (0x98 / 255) (0x96 / 255)), Nothing)
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
......@@ -132,7 +147,7 @@ rainbowTheme = AnimatedTheme rainbowThemePipe
rainbowThemePipe :: AnimatedTheme
rainbowThemePipe = do
time <- liftIO $ fromRational . toRational <$> getPOSIXTime
yield =<< rainbowThemePipe' time <$> await
yield . rainbowThemePipe' time =<< await
rainbowThemePipe
rainbowThemePipe' :: Double -> StaticTheme
rainbowThemePipe' time blocks = reverse $ evalState (mapM rainbowBlock $ reverse blocks) 0
......@@ -140,18 +155,18 @@ rainbowTheme = AnimatedTheme rainbowThemePipe
rainbowBlock :: BlockOutput -> State Integer ThemedBlockOutput
rainbowBlock block@BlockOutput{_blockName} = do
let text = rawText $ block ^. fullText
let chars = T.unpack . T.reverse $ text
let chars = reverse . splitToChars $ text
coloredChars <- mapM rainbowChar chars
let rainbowText = reverse $ coloredChars
let rainbowText = reverse coloredChars
return $ ThemedBlockOutput {
_blockName,
_fullText = ThemedBlockText rainbowText,
_shortText = Nothing
}
rainbowChar :: Char -> State Integer ThemedBlockTextSegment
rainbowChar :: T.Text -> State Integer ThemedBlockTextSegment
rainbowChar char = do
color <- nextRainbowColor
return $ mkThemedSegment (color, Nothing) $ T.singleton char
return $ mkThemedSegment (color, Nothing) $ char
nextRainbowColor :: State Integer Color
-- nextRainbowColor = state $ \index -> (rainbowColor (fromInteger index), index + 1)
nextRainbowColor = do
......@@ -163,3 +178,13 @@ rainbowTheme = AnimatedTheme rainbowThemePipe
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"
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = with pkgs; [
stack
zsh
jq
];
}
{-# LANGUAGE PackageImports #-}
{-# LANGUAGE NoImplicitPrelude #-}
module BasePrelude
( module Prelude,
)
where
import "base" Prelude
module QBar.Blocks
( QBar.Blocks.Battery.batteryBlock,
QBar.Blocks.CpuUsage.cpuUsageBlock,
QBar.Blocks.Date.dateBlock,
)
where
import qualified QBar.Blocks.Battery
import qualified QBar.Blocks.CpuUsage
import qualified QBar.Blocks.Date
{-# LANGUAGE ApplicativeDo #-}
module QBar.Cli where
import QBar.Theme
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import Options.Applicative
data BarCommand = BarServerCommand | SetThemeCommand Text
barCommandParser :: Parser BarCommand
barCommandParser = hsubparser (
command "server" (info (pure BarServerCommand) (progDesc "Start a new qbar server. Should be called by swaybar, i3bar or or another i3bar-protocol compatible host process.")) <>
command "theme" (info themeCommandParser (progDesc "Change the theme of the running qbar server.")) <>
command "default" (info (pure $ SetThemeCommand "default") (progDesc "Shortcut for 'qbar theme default'.")) <>
command "rainbow" (info (pure $ SetThemeCommand "rainbow") (progDesc "Shortcut for 'qbar theme rainbow'."))
)
themeCommandParser :: Parser BarCommand
themeCommandParser = SetThemeCommand <$> strArgument (metavar "THEME" <> completeWith (map TL.unpack themeNames))
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
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module QBar.ControlSocket where
import QBar.Cli (MainOptions(..))
import QBar.Core
import QBar.BlockOutput
import Control.Exception (handle)
import Control.Monad (forever, void, when)
import Control.Concurrent (forkFinally)
import Control.Concurrent.Async
import Data.Aeson.TH
import Data.ByteString (ByteString)
import System.FilePath ((</>))
import System.IO
import Data.Either (either)
import Data.Maybe (maybe)
import Data.Text.Lazy (Text, pack)
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import Network.Socket
import Pipes
import Pipes.Parse
import Pipes.Aeson (decode, DecodingError)
import Pipes.Aeson.Unchecked (encode)
import Pipes.Network.TCP (fromSocket, toSocket)
import System.Directory (removeFile, doesFileExist)
import System.Environment (getEnv)
type CommandHandler = Command -> IO CommandResult
data Request = Command Command | ConnectBarHost
deriving Show
data Command = SetTheme TL.Text
deriving Show
data CommandResult = Success | Error Text
deriving Show
$(deriveJSON defaultOptions ''Request)
$(deriveJSON defaultOptions ''Command)
$(deriveJSON defaultOptions ''CommandResult)
ipcSocketAddress :: MainOptions -> IO FilePath
ipcSocketAddress MainOptions{socketLocation} = maybe defaultSocketPath (return . T.unpack) socketLocation
where
defaultSocketPath :: IO FilePath
defaultSocketPath = do
waylandSocketPath' <- waylandSocketPath
maybe (maybe headlessSocketPath return =<< i3SocketPath) return waylandSocketPath'
where
waylandSocketPath :: IO (Maybe FilePath)
waylandSocketPath = handleEnvError $ do
xdgRuntimeDir <- getEnv "XDG_RUNTIME_DIR"
waylandDisplay <- getEnv "WAYLAND_DISPLAY"
return $ xdgRuntimeDir </> waylandDisplay <> "-qbar"
i3SocketPath :: IO (Maybe FilePath)
i3SocketPath = handleEnvError $ do
i3SocketPath' <- getEnv "I3_SOCKET_PATH"
return $ i3SocketPath' <> "-qbar"
headlessSocketPath :: IO FilePath
headlessSocketPath = do
xdgRuntimeDir <- getEnv "XDG_RUNTIME_DIR"
return $ xdgRuntimeDir </> "qbar"
handleEnvError :: IO FilePath -> IO (Maybe FilePath)
handleEnvError = handle (const $ return Nothing :: IOError -> IO (Maybe FilePath)) . fmap Just
sendIpc :: MainOptions -> Command -> IO ()
sendIpc options@MainOptions{verbose} command = do
let request = Command command
socketPath <- ipcSocketAddress options
sock <- socket AF_UNIX Stream defaultProtocol
connect sock $ SockAddrUnix socketPath
runEffect $ encode request >-> toSocket sock
decodeResult <- evalStateT decode $ fromSocket sock 4096
maybe exitEmptyStream (either exitInvalidResult showResponse) decodeResult
where
exitEmptyStream :: IO ()
exitEmptyStream = hPutStrLn stderr "Empty stream"
exitInvalidResult :: DecodingError -> IO ()
exitInvalidResult = hPrint stderr
showResponse :: CommandResult -> IO ()
showResponse Success = when verbose $ hPutStrLn stderr "Success"
showResponse (Error message) = hPrint stderr message
listenUnixSocketAsync :: MainOptions -> CommandHandler -> IO (Async ())
listenUnixSocketAsync options commandHandler = async $ listenUnixSocket options commandHandler
listenUnixSocket :: MainOptions -> CommandHandler -> IO ()
listenUnixSocket options commandHandler = do
socketPath <- ipcSocketAddress options
hPutStrLn stderr $ "Creating control socket at " <> socketPath
socketExists <- doesFileExist socketPath
when socketExists $ removeFile socketPath
sock <- socket AF_UNIX Stream defaultProtocol
setCloseOnExecIfNeeded $ fdSocket sock
bind sock (SockAddrUnix socketPath)
listen sock 5
forever $ do
(conn, _) <- accept sock
void $ forkFinally (socketHandler conn) (\_ -> close conn)
where
socketHandler :: Socket -> IO ()
socketHandler sock = streamHandler (fromSocket sock 4096) (toSocket sock)
streamHandler :: Producer ByteString IO () -> Consumer ByteString IO () -> IO ()
streamHandler producer responseConsumer = do
(maybeDecodeResult, leftovers) <- runStateT decode producer
-- Handle empty result
case maybeDecodeResult of
Nothing -> reply $ errorResponse "Empty stream"
Just decodeResult -> case decodeResult of
Left err -> reply $ handleError err
Right request -> handleRequest leftovers responseConsumer request
where
reply :: Producer ByteString IO () -> IO ()
reply response = runEffect (response >-> responseConsumer)
handleRequest :: Producer ByteString IO () -> Consumer ByteString IO () -> Request -> IO ()
handleRequest _leftovers responseConsumer (Command command) = runEffect (handleCommand command >-> responseConsumer)
--handleRequest leftovers Block = addBlock $ handleBlockStream leftovers
handleRequest _leftovers _responseConsumer ConnectBarHost = error "TODO"
handleCommand :: Command -> Producer ByteString IO ()
handleCommand command = do
result <- liftIO $ commandHandler command
encode result
handleError :: DecodingError -> Producer ByteString IO ()
handleError = encode . Error . pack . show
errorResponse :: Text -> Producer ByteString IO ()
errorResponse message = encode $ Error message
handleBlockStream :: Producer ByteString IO () -> PushBlock
handleBlockStream producer = do
(decodeResult, leftovers) <- liftIO $ runStateT decode producer
maybe exitBlock (either (const exitBlock) (handleParsedBlock leftovers)) decodeResult
where
handleParsedBlock :: Producer ByteString IO () -> String -> PushBlock
handleParsedBlock leftovers update = do
updateBlock $ mkBlockOutput . normalText $ TL.pack update
handleBlockStream leftovers
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 ram = systemInfoInterval $ (blockScript $ blockLocation "memory") >-> modify (addIcon "🐏\xFE0E") >-> autoPadding
let temperature = systemInfoInterval $ (blockScript $ blockLocation "temperature") >-> autoPadding
let volumeBlock = persistentBlockScript $ blockLocation "volume-pulseaudio -S -F3"
let battery = systemInfoInterval $ batteryBlock >-> modify (blockName ?~ "battery")
let cpuUsage = systemInfoInterval $ cpuUsageBlock 1 >-> modify ((blockName ?~ "cpuUsage") . addIcon "💻\xFE0E")
addBlock dateBlock
addBlock battery
addBlock volumeBlock
addBlock temperature
addBlock ram
addBlock cpuUsage
addBlock networkEnvironment
addBlock wifi
addBlock todo
module QBar.Time (sleepUntil, nextMinute) where
import Control.Concurrent (threadDelay)
import Control.Monad (when)
import Data.Time.Clock (UTCTime, getCurrentTime, diffUTCTime, utctDayTime)
sleepUntil :: UTCTime -> IO ()
sleepUntil time = do
now <- getCurrentTime
let timeUntil = diffUTCTime time now
when (timeUntil > 0) $
if timeUntil > 1
then threadDelay 1000000 >> sleepUntil time
else threadDelay $ ceiling $ toRational timeUntil * 1000000
nextMinute :: IO UTCTime
nextMinute = do
now <- getCurrentTime
let dayTime = utctDayTime now
let daySeconds = floor dayTime
let dayMinute = daySeconds `div` 60
return now {
utctDayTime = fromInteger $ (dayMinute + 1) * 60
}
\ No newline at end of file
module QBar.Util where
import Control.Concurrent.Event as Event
import Pipes
-- Pipe that signals an 'Event' after every value that passes through
signalPipe :: MonadIO m => Event.Event -> Pipe a a m r
signalPipe event = signalPipe'
where
signalPipe' :: MonadIO m => Pipe a a m r
signalPipe' = do
value <- await
yield value
liftIO $ Event.signal event
signalPipe'
# This file was automatically generated by 'stack init'
#
# Some commonly used options have been documented as comments in this file.
# For advanced use and comprehensive documentation of the format, please see:
# https://docs.haskellstack.org/en/stable/yaml_configuration/
# Resolver to choose a 'specific' stackage snapshot or a compiler version.
# A snapshot resolver dictates the compiler version and the set of packages
# to be used for project dependencies. For example:
#
# resolver: lts-3.5
# resolver: nightly-2015-09-21
# resolver: ghc-7.10.2
#
# The location of a snapshot can be provided as a file or url. Stack assumes
# a snapshot provided as a file might change, whereas a url resource does not.
#
# resolver: ./custom-snapshot.yaml
# resolver: https://example.com/snapshots/2018-01-01.yaml
resolver: lts-14.20
# User packages to be built.
# Various formats can be used as shown in the example below.
#
# packages:
# - some-directory
# - https://example.com/foo/bar/baz-0.0.2.tar.gz
# - location:
# git: https://github.com/commercialhaskell/stack.git
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a
# subdirs:
# - auto-update
# - wai
packages:
- .
# Dependency packages to be pulled from upstream that are not in the resolver
# using the same syntax as the packages field.
# (e.g., acme-missiles-0.3)
# extra-deps: []
# Disable pure nix-shell environment on NixOS, because access to XDG_RUNTIME_DIR is needed for the control socket
nix:
pure: false
# Override default flag values for local packages and extra-deps
# flags: {}
# Extra package databases containing global packages
# extra-package-dbs: []
# Control whether we use the GHC we find on the path
# system-ghc: true
#
# Require a specific version of stack, using version ranges
# require-stack-version: -any # Default
# require-stack-version: ">=1.9"
#
# Override the architecture used by stack, especially useful on Windows
# arch: i386
# arch: x86_64
#
# Extra directories used by stack for building
# extra-include-dirs: [/path/to/dir]
# extra-lib-dirs: [/path/to/dir]
#
# Allow a newer minor version of GHC than the snapshot specifies
# compiler-check: newer-minor