packages feed

macos-corelibs-0.0.1.0: src/MacSdk/Framework/CoreGraphics/Display.hs

{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}

module MacSdk.Framework.CoreGraphics.Display
  ( DisplayID
  , DisplayChange(..)
  , DisplayReconfigurationCallback
  , DisplayCallbackToken
  , displayUUID
  , uuidString'
  , displayBounds
  , displayCount
  , activeDisplays
  , activeDisplay
  , setDisplayCallback
  , removeDisplayCallback
  , displayForWindow'
  ) where

import MacSdk.Framework.CoreFoundation
import MacSdk.Framework.CoreGraphics.Error
import MacSdk.Framework.CoreGraphics.Rect
import MacSdk.Framework.CoreGraphics.Window.Types
-- import MacSdk.Framework.Types (WindowID)

import Data.Word
import Data.Bits
import Control.Monad
import Prelude hiding (String)
import Control.Monad.Managed
import Foreign.C.Types (CUInt(..), CInt(..))
import Foreign.Ptr
import Foreign.Marshal.Array
import Foreign hiding (with)

type DisplayID = Word32

foreign import ccall unsafe "CGDisplayCreateUUIDFromDisplayID"
  cgCreateUUIDFromDisplayID :: DisplayID -> IO CFUUIDRef

displayUUID :: DisplayID -> IO UUID
displayUUID = cgCreateUUIDFromDisplayID >=> manageCFObj

foreign import ccall unsafe "CFUUIDCreateString"
  cfUUIDCreateString :: CFAllocatorRef -> CFUUIDRef -> IO CFStringRef

uuidString :: Allocator -> UUID -> IO CFString
uuidString al uuid = flip with pure $ do
  al' <- managed $ withCFPtr al
  uuid' <- managed $ withCFPtr uuid
  liftIO $ cfUUIDCreateString al' uuid' >>= manageCFObj

uuidString' :: UUID -> IO CFString
uuidString' uuid = nullAllocator >>= flip uuidString uuid

foreign import ccall unsafe cgDisplayBounds_ :: CUInt -> Ptr Rect -> IO ()

displayBounds :: DisplayID -> IO Rect
displayBounds dId = alloca $ \p -> do
  cgDisplayBounds_ (fromIntegral dId) p
  peek p

foreign import ccall unsafe "CGGetActiveDisplayList"
  cgGetActiveDisplayList
  :: CUInt -> Ptr DisplayID -> Ptr CUInt -> IO ForeignCGError

displayCount :: (MonadIO m, MonadError e m, AsCGError e) => m Int
displayCount = except . liftIO . alloca $ \p -> do
  err <- cgGetActiveDisplayList 0 nullPtr p
  let res = intToCGError err
  either (pure . Left) (const (Right <$> (fmap fromIntegral $ peek p))) res

activeDisplays :: (MonadIO m, MonadError e m, AsCGError e) => Int -> m [DisplayID]
activeDisplays maxIds = except . liftIO . allocaArray maxIds $ \p -> alloca $ \q -> do
  err <- cgGetActiveDisplayList (fromIntegral maxIds) p q
  let res = intToCGError err
  either (pure . Left)
    (const (fromIntegral <$> peek q >>= \count ->
               Right <$> peekArray count p)) res

--------------------------------------------------------------------------------

type DisplayChangeSummaryFlags = Word32

beginConfigFlag, movedFlag, setMainFlag, setModeFlag, addFlag, removeFlag,
  enabledFlag, disabledFlag, mirrorFlag, unmirrorFlag,
    desktopShapeChangedFlag :: DisplayChangeSummaryFlags
beginConfigFlag = 1
movedFlag = 1 `shiftL` 1
setMainFlag = 1 `shiftL` 2
setModeFlag = 1 `shiftL` 3
addFlag = 1 `shiftL` 4
removeFlag = 1 `shiftL` 5
enabledFlag = 1 `shiftL` 8
disabledFlag = 1 `shiftL` 9
mirrorFlag = 1 `shiftL` 10
unmirrorFlag = 1 `shiftL` 11
desktopShapeChangedFlag = 1 `shiftL` 12

data DisplayChange
  = DisplayBeginConfig
  | DisplayMoved
  | DisplaySetMain
  | DisplaySetMode
  | DisplayAdd
  | DisplayRemove
  | DisplayEnabled
  | DisplayDisabled
  | DisplayMirror
  | DisplayUnmirror
  | DisplayDesktopShapeChanged
  deriving (Eq)

toDisplayChange :: DisplayChangeSummaryFlags -> Maybe DisplayChange
toDisplayChange fl
  | Foreign.toBool (fl .&. beginConfigFlag) = Just DisplayBeginConfig
  | Foreign.toBool (fl .&. movedFlag) = Just DisplayMoved
  | Foreign.toBool (fl .&. setMainFlag) = Just DisplaySetMain
  | Foreign.toBool (fl .&. setModeFlag) = Just DisplaySetMode
  | Foreign.toBool (fl .&. addFlag) = Just DisplayAdd
  | Foreign.toBool (fl .&. removeFlag) = Just DisplayRemove
  | Foreign.toBool (fl .&. enabledFlag) = Just DisplayEnabled
  | Foreign.toBool (fl .&. disabledFlag) = Just DisplayDisabled
  | Foreign.toBool (fl .&. mirrorFlag) = Just DisplayMirror
  | Foreign.toBool (fl .&. unmirrorFlag) = Just DisplayUnmirror
  | Foreign.toBool (fl .&. desktopShapeChangedFlag) = Just DisplayDesktopShapeChanged
  | otherwise = Nothing

--------------------------------------------------------------------------------

type CGDisplayReconfigurationCallback =
  DisplayID -> DisplayChangeSummaryFlags -> Ptr () -> IO ()

foreign import ccall unsafe "wrapper" wrap_display_callback
  :: CGDisplayReconfigurationCallback -> IO (FunPtr CGDisplayReconfigurationCallback)

type DisplayReconfigurationCallback = DisplayID -> DisplayChange -> IO ()

toCGCallback :: DisplayReconfigurationCallback -> CGDisplayReconfigurationCallback
toCGCallback cb did flags _ = maybe (pure ()) (cb did) (toDisplayChange flags)

foreign import ccall "CGDisplayRegisterReconfigurationCallback"
  cgDisplayRegisterReconfigurationCallback_
  :: FunPtr CGDisplayReconfigurationCallback -> Ptr () -> IO ForeignCGError

foreign import ccall "CGDisplayRemoveReconfigurationCallback"
  cgDisplayRemoveReconfigurationCallback
  :: FunPtr CGDisplayReconfigurationCallback -> Ptr () -> IO ForeignCGError

newtype DisplayCallbackToken =
  DisplayCallbackToken (FunPtr CGDisplayReconfigurationCallback)

setDisplayCallback
  :: (MonadIO m, MonadError e m, AsCGError e) => DisplayReconfigurationCallback
  -> m DisplayCallbackToken
setDisplayCallback f = do
  fp <- liftIO $ wrap_display_callback (toCGCallback f)
  err <- liftIO $
    intToCGError <$> cgDisplayRegisterReconfigurationCallback_ fp nullPtr
  except (pure (err >> pure (DisplayCallbackToken fp)))

removeDisplayCallback
  :: (MonadIO m, MonadError e m, AsCGError e) => DisplayCallbackToken -> m ()
removeDisplayCallback (DisplayCallbackToken fp) =
  except . liftIO . fmap intToCGError $
    cgDisplayRemoveReconfigurationCallback fp nullPtr

-- foreign import ccall unsafe "CGSCopyManagedDisplayForWindow"
--   cgSCopyManagedDisplayForWindow_
--   :: ConnectionID -> CUInt -> IO CFStringRef

-- displayForWindow :: ConnectionID -> WindowID -> IO String
-- displayForWindow cid wid =
--   cgSCopyManagedDisplayForWindow_ cid (fromIntegral wid) >>= manageCFObj

--------------------------------------------------------------------------------

foreign import ccall unsafe display_for_window :: WindowID -> IO CFStringRef

displayForWindow' :: WindowID -> IO CFString
displayForWindow' = display_for_window >=> manageCFObj

foreign import ccall unsafe activeDisplay :: IO DisplayID