packages feed

taffybar-7.4.0: src/System/Taffybar/Information/Notifications.hs

-- | Notification storage with replacement-aware expiration.
module System.Taffybar.Information.Notifications
  ( Notification (..),
    NotificationQueue,
    newNotificationQueue,
    notificationUpdates,
    readNotifications,
    enqueueNotification,
    removeNotification,
    nextNotification,
  )
where

import Control.Concurrent (forkIO)
import Control.Concurrent.STM
import Control.Exception (mask_)
import Control.Monad (forM_, void, when)
import Data.Foldable (toList)
import Data.Int (Int32)
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq
import Data.Text (Text)
import Data.Unique (Unique, newUnique)
import Data.Word (Word32)

data Notification = Notification
  { noteAppName :: Text,
    noteReplaceId :: Word32,
    noteSummary :: Text,
    noteBody :: Text,
    noteExpireTimeout :: Maybe Int32,
    noteId :: Word32
  }
  deriving (Show, Eq)

data NotificationQueue = NotificationQueue
  { queuedNotifications :: TVar (Seq (Unique, Notification)),
    notificationUpdates :: TChan ()
  }

newNotificationQueue :: IO NotificationQueue
newNotificationQueue =
  NotificationQueue <$> newTVarIO Seq.empty <*> newBroadcastTChanIO

readNotifications :: NotificationQueue -> IO [Notification]
readNotifications queue = map snd . toList <$> readTVarIO (queuedNotifications queue)

enqueueNotification :: NotificationQueue -> Notification -> IO ()
enqueueNotification queue notification = mask_ $ do
  generation <- newUnique
  atomically $ do
    current <- readTVar $ queuedNotifications queue
    let entry = (generation, notification)
        updated = case Seq.findIndexL ((== noteId notification) . noteId . snd) current of
          Nothing -> current Seq.|> entry
          Just index -> Seq.update index entry current
    writeTVar (queuedNotifications queue) updated
    writeTChan (notificationUpdates queue) ()
  forM_ (noteExpireTimeout notification) $ \milliseconds -> void $ forkIO $ do
    elapsed <- registerDelay (max 0 (fromIntegral milliseconds) * 1000)
    atomically $ do
      current <- readTVar $ queuedNotifications queue
      when (any ((== generation) . fst) current) $ do
        readTVar elapsed >>= check
        writeTVar (queuedNotifications queue) $ Seq.filter ((/= generation) . fst) current
        writeTChan (notificationUpdates queue) ()

removeNotification :: NotificationQueue -> Word32 -> IO ()
removeNotification queue identifier = atomically $ do
  modifyTVar' (queuedNotifications queue) $ Seq.filter ((/= identifier) . noteId . snd)
  writeTChan (notificationUpdates queue) ()

nextNotification :: NotificationQueue -> IO ()
nextNotification queue = atomically $ do
  modifyTVar' (queuedNotifications queue) (Seq.drop 1)
  writeTChan (notificationUpdates queue) ()