From d75b2d5b186e2155ef6c41b5565f994f1b99e11a Mon Sep 17 00:00:00 2001
From: Jens Nolte <jens@nightmarestudio.de>
Date: Mon, 9 Mar 2020 23:15:03 +0100
Subject: [PATCH] Implement SignalBlock

Renames PullBlock to PollBlock and moves it to the SignalBlock in
QBar.BlockHelper.
The script block is now in linewise mode by default and can be
configured into polling mode with '--poll'.
---
 src/QBar/BlockHelper.hs     | 215 ++++++++++++++++++++++++++++++++++++
 src/QBar/Blocks.hs          |   2 +-
 src/QBar/Blocks/Battery.hs  |  11 +-
 src/QBar/Blocks/CpuUsage.hs |   8 +-
 src/QBar/Blocks/Date.hs     |   5 +-
 src/QBar/Blocks/Script.hs   |  12 +-
 src/QBar/Cli.hs             |   5 +-
 src/QBar/Core.hs            |  87 +++------------
 src/QBar/DefaultConfig.hs   |  12 +-
 src/QBar/Host.hs            |   4 +-
 src/QBar/Server.hs          |   2 +-
 11 files changed, 260 insertions(+), 103 deletions(-)
 create mode 100644 src/QBar/BlockHelper.hs

diff --git a/src/QBar/BlockHelper.hs b/src/QBar/BlockHelper.hs
new file mode 100644
index 0000000..0fda979
--- /dev/null
+++ b/src/QBar/BlockHelper.hs
@@ -0,0 +1,215 @@
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE TupleSections #-}
+
+module QBar.BlockHelper where
+
+import QBar.BlockOutput
+import QBar.Core
+import QBar.Time
+
+import Control.Concurrent.Async
+import qualified Control.Concurrent.Event as Event
+import Control.Concurrent.STM.TChan
+import Data.Either (isRight)
+import Pipes
+import Pipes.Core
+import Pipes.Concurrent
+
+
+data Signal a = RegularSignal | UserSignal a | EventSignal BlockEvent
+  deriving (Show, Eq)
+
+type SignalBlock a = (Signal a -> Server (Signal a) BlockUpdate BarIO ExitBlock)
+
+-- |Block that 'respond's with an update whenever it receives a 'PollSignal'.
+type PollBlock' = Server PollSignal BlockUpdate BarIO
+type PollBlock = Server PollSignal BlockUpdate BarIO ExitBlock
+data PollSignal = PollSignal
+
+respondBlockUpdate :: BlockOutput -> Server' (Signal s) BlockUpdate BarIO (Signal s)
+respondBlockUpdate blockOutput = respond (Just (blockOutput, Nothing), DefaultUpdate)
+
+respondBlockUpdate' :: BlockEventHandler -> BlockOutput -> Server' (Signal s) BlockUpdate BarIO (Signal s)
+respondBlockUpdate' blockEventHandler blockOutput = respond (Just (blockOutput, Just blockEventHandler), PollUpdate)
+
+-- |Update a block by removing the current output
+respondEmptyBlockUpdate :: Server' (Signal s) BlockUpdate BarIO (Signal s)
+respondEmptyBlockUpdate = respond (Nothing, PollUpdate)
+
+
+yieldBlockUpdate :: BlockOutput -> Server' PollSignal BlockUpdate BarIO ()
+yieldBlockUpdate blockOutput = void . respond $ (Just (blockOutput, Nothing), PollUpdate)
+
+yieldBlockUpdate' :: BlockEventHandler -> BlockOutput -> Server' PollSignal BlockUpdate BarIO ()
+yieldBlockUpdate' blockEventHandler blockOutput = void . respond $ (Just (blockOutput, Just blockEventHandler), PollUpdate)
+
+-- |Update a block by removing the current output
+yieldEmptyBlockUpdate :: Server' PollSignal BlockUpdate BarIO ()
+yieldEmptyBlockUpdate = void . respond $ (Nothing, PollUpdate)
+
+
+runSignalBlock :: forall a. Maybe Interval -> Maybe ((a -> IO ()) -> BarIO ()) -> SignalBlock a -> Block
+runSignalBlock maybeInterval maybeSignalSourceThread signalBlock' = runSignalBlockConfiguration $ SignalBlockConfiguration {
+  initialize = const $ return (),
+  signalThread = const <$> maybeSignalSourceThread,
+  signalBlock = const signalBlock',
+  interval = maybeInterval,
+  finalize = return
+}
+
+
+runSignalBlockFn :: forall a. Maybe Interval -> ((a -> IO ()) -> BarIO ()) -> ((a, Maybe BlockEvent) -> BarIO BlockState) -> Block
+runSignalBlockFn maybeInterval signalSourceThread renderFn = runSignalBlock maybeInterval (Just signalSourceThread) signalBlock
+  where
+    signalBlock :: (Signal a -> Server (Signal a) BlockUpdate BarIO ExitBlock)
+    signalBlock (UserSignal value) = signalBlock' value (UserSignal value)
+    signalBlock _ = signalBlock =<< respondEmptyBlockUpdate
+    signalBlock' :: a -> (Signal a -> Server (Signal a) BlockUpdate BarIO ExitBlock)
+    signalBlock' state RegularSignal = signalBlock' state =<< respond =<< (, PollUpdate) <$> lift (renderFn (state, Nothing))
+    signalBlock' _ (UserSignal value) = signalBlock' value =<< respond =<< (, DefaultUpdate) <$> lift (renderFn (value, Nothing))
+    signalBlock' state (EventSignal event) = signalBlock' state =<< respond =<< (, DefaultUpdate) <$> lift (renderFn (state, Just event))
+
+runSignalBlockFn' :: Maybe Interval -> (Maybe BlockEvent -> BarIO BlockState) -> Block
+runSignalBlockFn' maybeInterval renderFn = runSignalBlockConfiguration $ SignalBlockConfiguration {
+  initialize = const $ return (),
+  signalThread = Nothing,
+  signalBlock = const eventBlock,
+  interval = maybeInterval,
+  finalize = return
+}
+  where
+    eventBlock :: (Signal a -> Server (Signal a) BlockUpdate BarIO ExitBlock)
+    eventBlock (EventSignal event) = eventBlock =<< respond =<< (, DefaultUpdate) <$> lift (renderFn (Just event))
+    eventBlock _ = eventBlock =<< respond =<< (, PollUpdate) <$> lift (renderFn Nothing)
+
+
+
+data SignalBlockConfiguration c p = SignalBlockConfiguration {
+  initialize :: (p -> IO ()) -> BarIO c,
+  signalThread :: Maybe (c -> (p -> IO ()) -> BarIO ()),
+  signalBlock :: c -> SignalBlock p,
+  interval :: Maybe Interval,
+  finalize :: c -> IO ()
+}
+
+runSignalBlockConfiguration :: forall c p. SignalBlockConfiguration c p -> Block
+runSignalBlockConfiguration SignalBlockConfiguration{initialize, signalThread, signalBlock, interval, finalize} = do
+  -- Initialize
+  signalChan <- liftIO newTChanIO
+  signalEvent <- liftIO Event.new
+
+  runSignalBlockWithThreadInternal signalChan signalEvent
+  where
+    runSignalBlockWithThreadInternal :: TChan (Signal p) -> Event.Event -> Block
+    runSignalBlockWithThreadInternal signalChan signalEvent = do
+      context <- lift $ initialize userSignalAction
+      -- Start signalSource thread
+      userTask <- liftBarIO $ barAsync $
+        case signalThread of
+          Just signalThread' -> signalThread' context userSignalAction
+          Nothing -> return ()
+      intervalTask <- liftBarIO $ barAsync intervalTimer
+
+      -- Run block
+      void (signalBlock context +>> signalPipe >-> attachEventHandlerP)
+
+      -- Cancel threads when the block terminates
+      -- TODO: use bracketP?
+      liftIO $ do
+        cancel userTask
+        cancel intervalTask
+        finalize context
+
+      exitBlock
+
+      where
+        userSignalAction :: p -> IO ()
+        userSignalAction value = do
+          liftIO . atomically $ writeTChan signalChan $ UserSignal value
+          Event.set signalEvent
+
+        signalPipe :: Proxy (Signal p) BlockUpdate () BlockUpdate BarIO ExitBlock
+        signalPipe = forever $ do
+          -- Handle all queued events
+          eventHandled <- sendQueuedEvents
+
+          -- If there was no queued event signal a regular event
+          unless eventHandled $ yield =<< request RegularSignal
+
+          -- Wait for next event
+          liftIO $ Event.wait signalEvent
+          liftIO $ Event.clear signalEvent
+
+          where
+            sendQueuedEvents :: Proxy (Signal p) BlockUpdate () BlockUpdate BarIO Bool
+            sendQueuedEvents = do
+              maybeSignal <- liftIO . atomically $ tryReadTChan signalChan
+              case maybeSignal of
+                Just signal -> (yield =<< request signal) >> sendQueuedEvents >> return True
+                Nothing -> return False
+
+
+        intervalTimer :: BarIO ()
+        intervalTimer = do
+          scheduler <- askSleepScheduler
+          case interval of
+            Just interval' -> forever $ do
+              sleepUntilInterval' scheduler interval'
+              liftIO $ Event.set signalEvent
+            Nothing -> return ()
+
+        attachEventHandlerP :: Pipe BlockUpdate BlockUpdate BarIO ExitBlock
+        attachEventHandlerP = forever $ do
+          (state, reason) <- await
+          let state' = if hasEventHandler state
+              -- If state already has an event handler, we do not attach another one
+              then state
+              -- Attach a click handler that will trigger a block update
+              else updateEventHandler signalEventHandler state
+          yield (state', reason)
+          where
+            signalEventHandler :: BlockEventHandler
+            signalEventHandler event = do
+              liftIO . atomically $ writeTChan signalChan $ EventSignal event
+              liftIO $ Event.set signalEvent
+
+
+
+
+-- |Converts a 'PollBlock' to a 'Block' by running it whenever the 'defaultInterval' is triggered.
+runPollBlock :: PollBlock -> Block
+runPollBlock = runPollBlock' defaultInterval
+
+-- |Converts a 'PollBlock' to a 'Block' by running it whenever the provided 'Interval' is triggered.
+runPollBlock' :: Interval -> PollBlock -> Block
+runPollBlock' interval pb = pb >>~ addPollSignal >-> sleepToNextInterval
+  where
+    addPollSignal :: BlockUpdate -> Proxy PollSignal BlockUpdate () BlockUpdate BarIO ExitBlock
+    addPollSignal = respond >=> const (request PollSignal) >=> addPollSignal
+
+    sleepToNextInterval :: Pipe BlockUpdate BlockUpdate BarIO ExitBlock
+    sleepToNextInterval = do
+      event <- liftIO Event.new
+      forever $ do
+        (state, reason) <- await
+        if hasEventHandler state
+          then do
+            -- If state already has an event handler, we do not attach another one
+            yield (state, reason)
+            sleepUntilInterval interval
+          else do
+            -- Attach a click handler that will trigger a block update
+            yield (updateEventHandler (triggerOnClick event) state, reason)
+
+            scheduler <- askSleepScheduler
+            result <- liftIO $ do
+              timerTask <- async $ sleepUntilInterval' scheduler defaultInterval
+              eventTask <- async $ Event.wait event
+              waitEitherCancel timerTask eventTask
+
+            when (isRight result) $ do
+              liftIO $ Event.clear event
+              yield (invalidateBlockState state, UserUpdate)
+
+    triggerOnClick :: Event.Event -> BlockEvent -> BarIO ()
+    triggerOnClick event _ = liftIO $ Event.set event
diff --git a/src/QBar/Blocks.hs b/src/QBar/Blocks.hs
index 21d0cbd..8df8ca3 100644
--- a/src/QBar/Blocks.hs
+++ b/src/QBar/Blocks.hs
@@ -3,7 +3,7 @@ module QBar.Blocks
     QBar.Blocks.CpuUsage.cpuUsageBlock,
     QBar.Blocks.Date.dateBlock,
     QBar.Blocks.Script.scriptBlock,
-    QBar.Blocks.Script.persistentScriptBlock,
+    QBar.Blocks.Script.pollScriptBlock,
   )
 where
 
diff --git a/src/QBar/Blocks/Battery.hs b/src/QBar/Blocks/Battery.hs
index 5d33ecb..9941954 100644
--- a/src/QBar/Blocks/Battery.hs
+++ b/src/QBar/Blocks/Battery.hs
@@ -1,10 +1,9 @@
 {-# LANGUAGE MultiWayIf #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 
-
 module QBar.Blocks.Battery where
 
-
+import QBar.BlockHelper
 import QBar.Core
 import QBar.Blocks.Utils
 import QBar.BlockOutput
@@ -79,7 +78,7 @@ getBatteryState path = maybe getBatteryStateCharge (return . Just) =<< getBatter
 
 
 batteryBlock :: Block
-batteryBlock = pullBlock $ forever $ do
+batteryBlock = runPollBlock $ forever $ do
   batteryPaths <- liftIO $ map ((apiPath <> "/") <>) . filter (T.isPrefixOf "BAT" . T.pack) <$> getDirectoryContents apiPath
   batteryStates <- liftIO $ mapM getBatteryState batteryPaths
   isPlugged <- liftIO getPluggedState
@@ -101,9 +100,9 @@ batteryBlock = pullBlock $ forever $ do
             _ -> return . return $ False
 
 
-updateBatteryBlock :: Bool -> [BatteryState] -> PullBlock' ()
-updateBatteryBlock _ [] = sendEmptyBlockUpdate
-updateBatteryBlock isPlugged bs = sendBlockUpdate $ (shortText.~shortText') $ mkBlockOutput fullText'
+updateBatteryBlock :: Bool -> [BatteryState] -> PollBlock' ()
+updateBatteryBlock _ [] = yieldEmptyBlockUpdate
+updateBatteryBlock isPlugged bs = yieldBlockUpdate $ (shortText.~shortText') $ mkBlockOutput fullText'
   where
     fullText' :: BlockText
     fullText' = overallPercentage <> optionalEachBattery <> optionalOverallEstimate
diff --git a/src/QBar/Blocks/CpuUsage.hs b/src/QBar/Blocks/CpuUsage.hs
index 66505ad..505ed4e 100644
--- a/src/QBar/Blocks/CpuUsage.hs
+++ b/src/QBar/Blocks/CpuUsage.hs
@@ -2,6 +2,8 @@
 
 module QBar.Blocks.CpuUsage where
 
+import QBar.BlockHelper
+
 import Control.Applicative ((<|>))
 import Control.Lens
 import Control.Monad.State (StateT, evalStateT, lift)
@@ -113,14 +115,14 @@ data CpuBlockState
 makeLenses ''CpuBlockState
 
 cpuUsageBlock :: Int -> Block
-cpuUsageBlock decimalPlaces = pullBlock $ evalStateT cpuUsageBlock' createState
+cpuUsageBlock decimalPlaces = runPollBlock $ evalStateT cpuUsageBlock' createState
   where
-    cpuUsageBlock' :: StateT CpuBlockState PullBlock' ExitBlock
+    cpuUsageBlock' :: StateT CpuBlockState PollBlock' ExitBlock
     cpuUsageBlock' = forever $ do
       updateState
       importance <- cpuUsageImportance
       text <- ("💻\xFE0E " <>) <$> cpuUsageText
-      lift $ sendBlockUpdate $ mkBlockOutput $ importantText importance text
+      lift $ yieldBlockUpdate $ mkBlockOutput $ importantText importance text
     createState :: CpuBlockState
     createState =
       CpuBlockState
diff --git a/src/QBar/Blocks/Date.hs b/src/QBar/Blocks/Date.hs
index adecf74..dbb867f 100644
--- a/src/QBar/Blocks/Date.hs
+++ b/src/QBar/Blocks/Date.hs
@@ -1,5 +1,6 @@
 module QBar.Blocks.Date where
 
+import QBar.BlockHelper
 import QBar.BlockOutput
 import QBar.Core
 import QBar.Time
@@ -10,9 +11,9 @@ import Data.Time.LocalTime
 
 
 dateBlock :: Block
-dateBlock = pullBlock' (everyNSeconds 60) $ forever $ do
+dateBlock = runPollBlock' (everyNSeconds 60) $ forever $ do
   zonedTime <- liftIO getZonedTime
   let date = T.pack (formatTime defaultTimeLocale "%a %F" zonedTime)
   let time = T.pack (formatTime defaultTimeLocale "%R" zonedTime)
   let text = normalText ("📅\xFE0E " <> date <> " ") <> activeText time
-  sendBlockUpdate $ mkBlockOutput text
+  yieldBlockUpdate $ mkBlockOutput text
diff --git a/src/QBar/Blocks/Script.hs b/src/QBar/Blocks/Script.hs
index 5480853..79d20d9 100644
--- a/src/QBar/Blocks/Script.hs
+++ b/src/QBar/Blocks/Script.hs
@@ -1,5 +1,6 @@
 module QBar.Blocks.Script where
 
+import QBar.BlockHelper
 import QBar.BlockOutput
 import QBar.Core
 import QBar.TagParser
@@ -17,8 +18,8 @@ import System.Process.Typed (Process, shell, setStdin, setStdout,
   getStdout, closed, createPipe, readProcessStdout, startProcess, stopProcess)
 
 
-scriptBlock :: FilePath -> Block
-scriptBlock path = pullBlock $ forever $ sendBlockUpdate =<< (lift blockScriptAction)
+pollScriptBlock :: FilePath -> Block
+pollScriptBlock path = runPollBlock $ forever $ yieldBlockUpdate =<< (lift blockScriptAction)
   where
     blockScriptAction :: BarIO BlockOutput
     blockScriptAction = do
@@ -27,17 +28,16 @@ scriptBlock path = pullBlock $ forever $ sendBlockUpdate =<< (lift blockScriptAc
       (exitCode, output) <- liftIO $ readProcessStdout $ shell path
       return $ case exitCode of
         ExitSuccess -> createScriptBlockOutput output
-        (ExitFailure nr) -> case nr of
-          _ -> mkErrorOutput $ "exit code " <> T.pack (show nr) <> ""
+        (ExitFailure nr) -> mkErrorOutput $ "exit code " <> T.pack (show nr) <> ""
     createScriptBlockOutput :: C8.ByteString -> BlockOutput
     createScriptBlockOutput output = case map E.decodeUtf8 (C8.lines output) of
       (text:short:_) -> parseTags'' text short
       (text:_) -> parseTags' text
       [] -> emptyBlock
 
-persistentScriptBlock :: FilePath -> Block
+scriptBlock :: FilePath -> Block
 -- The outer catchP only catches errors that occur during process creation
-persistentScriptBlock path = catchP startScriptProcess handleError
+scriptBlock path = catchP startScriptProcess handleError
   where
     handleError :: IOException -> Block
     handleError e = do
diff --git a/src/QBar/Cli.hs b/src/QBar/Cli.hs
index f1f213c..a547117 100644
--- a/src/QBar/Cli.hs
+++ b/src/QBar/Cli.hs
@@ -78,10 +78,11 @@ blockParser =
     command "date" (info (pure $ addBlock dateBlock) (progDesc "Load the date and time block.")) <>
     command "cpu" (info (pure $ addBlock $ cpuUsageBlock 1) (progDesc "Load the cpu usage block.")) <>
     command "battery" (info (pure $ addBlock $ batteryBlock) (progDesc "Load the battery block.")) <>
+    command "script" (info scriptBlockParser (progDesc "Display the output of an external script as a block."))
   )
 
 scriptBlockParser :: Parser (BarIO ())
 scriptBlockParser = helper <*> do
-  persistent <- switch $ long "persistent" <> short 'p' <> help "Run script in persistent mode (every line of output updates the block)."
+  poll <- switch $ long "poll" <> short 'p' <> help "Run script in poll mode (every line of output updates the block)."
   script <- strArgument (metavar "SCRIPT" <> help "The script that will be executed with a shell.")
-  return $ (if persistent then addBlock . persistentScriptBlock else addBlock . scriptBlock) script
+  return $ (if poll then addBlock . pollScriptBlock else addBlock . scriptBlock) script
diff --git a/src/QBar/Core.hs b/src/QBar/Core.hs
index 14312b2..b8b0f85 100644
--- a/src/QBar/Core.hs
+++ b/src/QBar/Core.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE RankNTypes #-}
 
 module QBar.Core where
 
@@ -8,20 +9,18 @@ import QBar.Time
 import QBar.Util
 
 import Control.Concurrent.Async
-import Control.Concurrent.Event as Event
+import qualified Control.Concurrent.Event as Event
 import Control.Concurrent.MVar
-import Control.Concurrent.STM.TChan (TChan, writeTChan)
+import Control.Concurrent.STM.TChan
 import Control.Lens
 import Control.Monad.Reader (ReaderT, runReaderT, ask)
 import Control.Monad.State (StateT)
 import Control.Monad.Writer (WriterT)
 import Data.Aeson.TH
-import Data.Either (isRight)
 import Data.Int (Int64)
 import Data.Maybe (fromMaybe)
 import qualified Data.Text.Lazy as T
 import Pipes
-import Pipes.Core
 import Pipes.Concurrent
 import Pipes.Safe (SafeT, runSafeT)
 import qualified Pipes.Prelude as PP
@@ -35,7 +34,7 @@ data MainOptions = MainOptions {
 data BlockEvent = Click {
   name :: T.Text,
   button :: Int
-} deriving Show
+} deriving (Eq, Show)
 $(deriveJSON defaultOptions ''BlockEvent)
 
 
@@ -44,18 +43,13 @@ data ExitBlock = ExitBlock
 type BlockEventHandler = BlockEvent -> BarIO ()
 
 type BlockState = Maybe (BlockOutput, Maybe BlockEventHandler)
-data BlockUpdateReason = DefaultUpdate | PullUpdate | UserUpdate
+data BlockUpdateReason = DefaultUpdate | PollUpdate | UserUpdate
 type BlockUpdate = (BlockState, BlockUpdateReason)
 
 -- |Block that 'yield's an update whenever the block should be changed
 type Block' = Producer BlockUpdate BarIO
 type Block = Producer BlockUpdate BarIO ExitBlock
 
--- |Block that 'respond's with an update whenever it receives a 'PullSignal'.
-type PullBlock' = Server PullSignal BlockUpdate BarIO
-type PullBlock = Server PullSignal BlockUpdate BarIO ExitBlock
-data PullSignal = PullSignal
-
 -- |Cache that holds multiple BlockStates. When iterated it always immediately 'yield's the latest update, so it should only be pulled when a bar update has been requested.
 type BlockCache = Producer [BlockState] BarIO ExitBlock
 
@@ -64,17 +58,11 @@ class IsCachable a where
 
 instance IsCachable Block where
   toCachedBlock = cacheBlock
-instance IsCachable PullBlock where
-  toCachedBlock = cacheBlock . pullBlock
 instance IsCachable BlockCache where
   toCachedBlock = id
 
-class IsBlock a where
-  exitBlock :: a
-instance IsBlock Block where
-  exitBlock = return ExitBlock
-instance IsBlock PullBlock where
-  exitBlock = return ExitBlock
+exitBlock :: Functor m => Proxy a' a b' b m ExitBlock
+exitBlock = return ExitBlock
 
 exitCache :: BlockCache
 exitCache = return ExitBlock
@@ -114,27 +102,15 @@ askBar :: MonadBarIO m => m Bar
 askBar = liftBarIO $ lift ask
 
 
-sendBlockUpdate :: BlockOutput -> Proxy a' a PullSignal BlockUpdate BarIO ()
-sendBlockUpdate blockOutput = void . respond $ (Just (blockOutput, Nothing), PullUpdate)
-
-sendBlockUpdate' :: BlockEventHandler -> BlockOutput -> Proxy a' a PullSignal BlockUpdate BarIO ()
-sendBlockUpdate' blockEventHandler blockOutput = void . respond $ (Just (blockOutput, Just blockEventHandler), PullUpdate)
-
--- |Update a block by removing the current output
-sendEmptyBlockUpdate :: Proxy a' a PullSignal BlockUpdate BarIO ()
-sendEmptyBlockUpdate = void . respond $ (Nothing, PullUpdate)
-
-
-pushBlockUpdate :: BlockOutput -> Proxy a' a () BlockUpdate BarIO ()
-pushBlockUpdate blockOutput = void . respond $ (Just (blockOutput, Nothing), DefaultUpdate)
+pushBlockUpdate :: BlockOutput -> Producer' BlockUpdate BarIO ()
+pushBlockUpdate blockOutput = yield (Just (blockOutput, Nothing), DefaultUpdate)
 
-pushBlockUpdate' :: BlockEventHandler -> BlockOutput -> Proxy a' a () BlockUpdate BarIO ()
-pushBlockUpdate' blockEventHandler blockOutput = void . respond $ (Just (blockOutput, Just blockEventHandler), DefaultUpdate)
+pushBlockUpdate' :: BlockEventHandler -> BlockOutput -> Producer' BlockUpdate BarIO ()
+pushBlockUpdate' blockEventHandler blockOutput = yield (Just (blockOutput, Just blockEventHandler), DefaultUpdate)
 
 -- |Update a block by removing the current output
-pushEmptyBlockUpdate :: Proxy a' a () BlockUpdate BarIO ()
-pushEmptyBlockUpdate = void . respond $ (Nothing, DefaultUpdate)
-
+pushEmptyBlockUpdate :: Producer' BlockUpdate BarIO ()
+pushEmptyBlockUpdate = yield (Nothing, DefaultUpdate)
 
 
 mkBlockState :: BlockOutput -> BlockState
@@ -162,43 +138,6 @@ runBarIO bar action = liftIO $ runReaderT (runSafeT action) bar
 defaultInterval :: Interval
 defaultInterval = everyNSeconds 10
 
--- |Converts a 'PullBlock' to a 'Block' by running it whenever the 'defaultInterval' is triggered.
-pullBlock :: PullBlock -> Block
-pullBlock = pullBlock' defaultInterval
-
--- |Converts a 'PullBlock' to a 'Block' by running it whenever the 'defaultInterval' is triggered.
-pullBlock' :: Interval -> PullBlock -> Block
-pullBlock' interval pb = pb >>~ addPullSignal >-> sleepToNextInterval
-  where
-    addPullSignal :: BlockUpdate -> Proxy PullSignal BlockUpdate () BlockUpdate BarIO ExitBlock
-    addPullSignal = respond >=> const (request PullSignal) >=> addPullSignal
-
-    sleepToNextInterval :: Pipe BlockUpdate BlockUpdate BarIO ExitBlock
-    sleepToNextInterval = do
-      event <- liftIO Event.new
-      forever $ do
-        (state, reason) <- await
-        if hasEventHandler state
-          then do
-            -- If state already has an event handler, we do not attach another one
-            yield (state, reason)
-            sleepUntilInterval interval
-          else do
-            -- Attach a click handler that will trigger a block update
-            yield (updateEventHandler (triggerOnClick event) state, reason)
-
-            scheduler <- askSleepScheduler
-            result <- liftIO $ do
-              timerTask <- async $ sleepUntilInterval' scheduler defaultInterval
-              eventTask <- async $ Event.wait event
-              waitEitherCancel timerTask eventTask
-
-            when (isRight result) $ do
-              liftIO $ Event.clear event
-              yield (invalidateBlockState state, UserUpdate)
-
-    triggerOnClick :: Event -> BlockEvent -> BarIO ()
-    triggerOnClick event _ = liftIO $ Event.set event
 
 -- |Creates a new cache from a producer that automatically seals itself when the producer terminates.
 newCache :: Producer [BlockState] BarIO () -> BlockCache
diff --git a/src/QBar/DefaultConfig.hs b/src/QBar/DefaultConfig.hs
index e7ce594..85b02a4 100644
--- a/src/QBar/DefaultConfig.hs
+++ b/src/QBar/DefaultConfig.hs
@@ -19,12 +19,12 @@ defaultBarConfig = do
 
 legacyBarConfig :: BarIO ()
 legacyBarConfig = do
-  let todo = scriptBlock $ blockLocation "todo"
-  let wifi = (scriptBlock $ blockLocation "wifi2") >-> modify (addIcon "📡\xFE0E")
-  let networkEnvironment = scriptBlock $ blockLocation "network-environment"
-  let ram = (scriptBlock $ blockLocation "memory") >-> modify (addIcon "🐏\xFE0E") >-> autoPadding
-  let temperature = (scriptBlock $ blockLocation "temperature") >-> autoPadding
-  let volumeBlock = persistentScriptBlock $ blockLocation "volume-pulseaudio -S -F3"
+  let todo = pollScriptBlock $ blockLocation "todo"
+  let wifi = (pollScriptBlock $ blockLocation "wifi2") >-> modify (addIcon "📡\xFE0E")
+  let networkEnvironment = pollScriptBlock $ blockLocation "network-environment"
+  let ram = (pollScriptBlock $ blockLocation "memory") >-> modify (addIcon "🐏\xFE0E") >-> autoPadding
+  let temperature = (pollScriptBlock $ blockLocation "temperature") >-> autoPadding
+  let volumeBlock = scriptBlock $ blockLocation "volume-pulseaudio -S -F3"
 
   addBlock dateBlock
   addBlock batteryBlock
diff --git a/src/QBar/Host.hs b/src/QBar/Host.hs
index c730da3..f7a04b6 100644
--- a/src/QBar/Host.hs
+++ b/src/QBar/Host.hs
@@ -138,12 +138,12 @@ filterDuplicates = do
 
 followupEventWaitTime :: BlockUpdateReason -> Int
 followupEventWaitTime DefaultUpdate = 10000
-followupEventWaitTime PullUpdate = 50000
+followupEventWaitTime PollUpdate = 50000
 -- 'followupEventWaitTime' for 'UserUpdate' has to be zero, or blocks would be blocked blocked for this time when sending a 'UserUpdate'.
 followupEventWaitTime UserUpdate = 0
 
 followupEventWaitTimeDefault :: Int
-followupEventWaitTimeDefault = followupEventWaitTime PullUpdate
+followupEventWaitTimeDefault = followupEventWaitTime PollUpdate
 
 requestBarUpdateHandler :: HostHandle -> BlockUpdateReason -> IO ()
 requestBarUpdateHandler HostHandle{barUpdateEvent, barUpdatedEvent, followupEventWaitTimeMVar} blockUpdateReason = do
diff --git a/src/QBar/Server.hs b/src/QBar/Server.hs
index 324d2ae..234ccb0 100644
--- a/src/QBar/Server.hs
+++ b/src/QBar/Server.hs
@@ -146,7 +146,7 @@ runBarServer loadBlocks options = runBarHost barServer loadBlocks
       liftIO $ link =<< async (renderLoop renderEvent themedBlockProducerMVar)
 
       -- Return a consumer that accepts BlockOutputs from the bar host, moves them to the mailbox and signals the renderer to update the bar.
-      return (signalPipe renderEvent >-> toOutput output, swayBarInput options)
+      return (signalEventPipe renderEvent >-> toOutput output, swayBarInput options)
 
     renderLoop :: Event.Event -> MVar (Producer [ThemedBlockOutput] IO (), Bool) -> IO ()
     renderLoop renderEvent themedBlockProducerMVar = runEffect $
-- 
GitLab