packages feed

taffybar-4.1.2: src/System/Taffybar/Hyprland.hs

-----------------------------------------------------------------------------
-- |
-- Module      : System.Taffybar.Hyprland
-- Copyright   : (c) Ivan A. Malison
-- License     : BSD3-style (see LICENSE)
--
-- Maintainer  : Ivan A. Malison
-- Stability   : unstable
-- Portability : unportable
--
-- Context-integrated helpers for the Hyprland client.
--
-- Hyprland resources are stored directly on 'Context' (not in 'contextState')
-- to avoid deadlocks from nested 'getStateDefault' usage during widget
-- initialization.
-----------------------------------------------------------------------------

module System.Taffybar.Hyprland
  ( -- * Shared Client
    getHyprlandClient
  , getHyprlandClientWith

    -- * Hyprland Monad In TaffyIO
  , HyprlandIO
  , runHyprland

    -- * Shared Event Channel
  , getHyprlandEventChan
  , getHyprlandEventChanWith

    -- * Convenience Runners
  , runHyprlandCommandRawT
  , runHyprlandCommandJsonT
  ) where

import qualified Control.Concurrent.MVar as MV
import           Control.Monad.IO.Class (liftIO)
import           Data.Aeson (FromJSON)
import qualified Data.ByteString as BS

import           Control.Monad.Trans.Reader (ReaderT, asks)

import           System.Taffybar.Context (Context(..), TaffyIO)
import           System.Taffybar.Information.Hyprland
  ( HyprlandClient
  , HyprlandClientConfig
  , HyprlandCommand
  , HyprlandError
  , HyprlandEventChan
  , HyprlandT
  , buildHyprlandEventChan
  , defaultHyprlandClientConfig
  , runHyprlandCommandJson
  , runHyprlandCommandRaw
  , runHyprlandT
  )

-- | Get a shared 'HyprlandClient' from the 'Context' state.
--
-- Note: this uses 'getStateDefault', so the first call wins and subsequent
-- calls return the existing client.
getHyprlandClient :: TaffyIO HyprlandClient
getHyprlandClient = getHyprlandClientWith defaultHyprlandClientConfig

-- | Like 'getHyprlandClient', but allows supplying the initial config.
--
-- The config is only used if the client has not already been created and
-- stored in the 'Context' state.
getHyprlandClientWith :: HyprlandClientConfig -> TaffyIO HyprlandClient
getHyprlandClientWith _cfg = asks hyprlandClient

-- | Hyprland actions in the 'TaffyIO' context.
--
-- Note: 'TaffyIO' is a type synonym, and type synonyms cannot be partially
-- applied. We therefore spell out the underlying 'ReaderT Context IO' here.
type HyprlandIO a = HyprlandT (ReaderT Context IO) a

runHyprland :: HyprlandIO a -> TaffyIO a
runHyprland action = getHyprlandClient >>= \client -> runHyprlandT client action

-- | Get a shared Hyprland event channel from the 'Context' state.
--
-- The channel is backed by a single event socket reader thread, and is safe to
-- use concurrently by multiple widgets via 'subscribeHyprlandEvents'.
getHyprlandEventChan :: TaffyIO HyprlandEventChan
getHyprlandEventChan = getHyprlandEventChanWith defaultHyprlandClientConfig

-- | Like 'getHyprlandEventChan', but allows supplying the initial client config.
--
-- The config is only used if the channel has not already been created and
-- stored in the 'Context' state.
getHyprlandEventChanWith :: HyprlandClientConfig -> TaffyIO HyprlandEventChan
getHyprlandEventChanWith cfg = do
  client <- getHyprlandClientWith cfg
  chanVar <- asks hyprlandEventChanVar
  liftIO $ MV.modifyMVar chanVar $ \existing ->
    case existing of
      Just c -> pure (existing, c)
      Nothing -> do
        c <- buildHyprlandEventChan client
        pure (Just c, c)

runHyprlandCommandRawT :: HyprlandCommand -> TaffyIO (Either HyprlandError BS.ByteString)
runHyprlandCommandRawT cmd =
  getHyprlandClient >>= \client -> liftIO $ runHyprlandCommandRaw client cmd

runHyprlandCommandJsonT :: FromJSON a => HyprlandCommand -> TaffyIO (Either HyprlandError a)
runHyprlandCommandJsonT cmd =
  getHyprlandClient >>= \client -> liftIO $ runHyprlandCommandJson client cmd