packages feed

blucontrol-0.7.0.0: src/Blucontrol/Monad/ApplyValue/X.hs

{-# LANGUAGE UndecidableInstances #-}

module Blucontrol.Monad.ApplyValue.X (
  ApplyValueXT
, runApplyValueXTIO
, ApplicableValueX
, ConfigX (..)
, XError (..)
) where

import Control.DeepSeq
import Control.Exception.Lifted (SomeException (..), bracket, catch)
import Control.Monad.Base
import Control.Monad.Trans.Control
import Control.Monad.Trans.Control.Default
import Control.Monad.Reader
import Control.Monad.Except
import Data.Default
import Data.Maybe (fromMaybe)
import qualified Data.Text as T
import Data.Word
import GHC.Generics

import Graphics.X11.Xlib.Display (closeDisplay, defaultScreen, openDisplay, rootWindow)
import Graphics.X11.Xlib.Types (Display)

import Blucontrol.Monad.ApplyValue
import Blucontrol.Monad.ApplyValue.X.Internal
import Blucontrol.Value
import Blucontrol.Value.RGB

newtype ApplyValueXT m a = ApplyValueXT { unApplyValueXT :: ExceptT XError (ReaderT Display m) a }
  deriving (Applicative, Functor, Monad, MonadBase b, MonadBaseControl b, MonadError XError)
  deriving (MonadTrans, MonadTransControl) via Stack2T (ExceptT XError) (ReaderT Display)

instance MonadBaseControl IO m => MonadApplyValue (ApplyValueXT m) where
  type ApplicableValue (ApplyValueXT m) = ApplicableValueX
  applyValue rgb = do
    display <- ApplyValueXT ask
    root <- liftXIO XErrorRead $
      rootWindow display $ defaultScreen display

    liftXIO XErrorSetGamma $ xrrSetGamma (unApplicableValueX rgb) display root

runApplyValueXT :: Display -> ApplyValueXT m a -> m (Either XError a)
runApplyValueXT display tma = runReaderT (runExceptT (unApplyValueXT tma)) display

data ConfigX = ConfigX { hostName :: Maybe T.Text
                       , displayServer :: Int
                       , screen :: Maybe Int
                       }
  deriving (Eq, Generic, Ord, Read, Show)

instance NFData ConfigX

instance Default ConfigX where
  def = ConfigX { hostName = Nothing
                , displayServer = 0
                , screen = Nothing
                }

data XError = XErrorCloseDisplay
            | XErrorOpenDisplay
            | XErrorRead
            | XErrorSetGamma
  deriving (Bounded, Enum, Eq, Generic, Ord, Read, Show)

instance NFData XError

liftXIO :: (MonadBaseControl IO m, MonadError XError m) => XError -> IO a -> m a
liftXIO xError = flip catch throwXError . liftBase
  where throwXError (SomeException _) = throwError xError

runApplyValueXTIO :: MonadBaseControl IO m => ConfigX -> ApplyValueXT m a -> m (Either XError a)
runApplyValueXTIO !conf tma = runExceptT $ bracket open close run
  where open = liftXIO XErrorOpenDisplay $ openDisplay $ showDisplay conf
        close display = liftXIO XErrorCloseDisplay $ closeDisplay display
        run display = restoreT $ runApplyValueXT display tma

showDisplay :: ConfigX -> String
showDisplay ConfigX { hostName, displayServer, screen } = T.unpack . T.concat $
  [ fromMaybe "" hostName
  , ":" <> T.pack (show displayServer)
  , maybe "" (("." <>) . T.pack . show) screen
  ]

newtype ApplicableValueX = ApplicableValueX { unApplicableValueX :: XRRGamma }
  deriving (Eq, Generic, Ord, Read, Show)

instance NFData ApplicableValueX

instance CompatibleValues (RGB Word8) ApplicableValueX where
  convertValue RGB { red, green, blue } = ApplicableValueX XRRGamma { xrr_gamma_red, xrr_gamma_green, xrr_gamma_blue }
    where xrr_gamma_red = word8ToFloat red
          xrr_gamma_green = word8ToFloat green
          xrr_gamma_blue = word8ToFloat blue
          word8ToFloat = (/ fromIntegral (maxBound @Word8)) . fromIntegral