{-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE EmptyDataDecls #-} {-# LANGUAGE UndecidableInstances #-} -- | Routines for connecting `GObject`s to signals. module Data.GI.Base.Signals ( on , after , SignalProxy(..) , SignalConnectMode(..) , connectSignalFunPtr , SignalHandlerId , SignalInfo(..) , GObjectNotifySignalInfo ) where import Control.Monad.IO.Class (MonadIO, liftIO) import Data.Proxy (Proxy(..)) import Foreign import Foreign.C import GHC.TypeLits import Data.GI.Base.Attributes (AttrLabelProxy, AttrInfo(AttrLabel)) import Data.GI.Base.BasicTypes import Data.GI.Base.GParamSpec (newGParamSpecFromPtr) import Data.GI.Base.ManagedPtr (withManagedPtr) import Data.GI.Base.Overloading (ResolveSignal, IsLabelProxy(..), ResolveAttribute) import Data.GI.Base.Utils (safeFreeFunPtrPtr) #if MIN_VERSION_base(4,9,0) import GHC.OverloadedLabels (IsLabel(..)) #else import Data.GI.Base.Overloading (HasSignal) #endif -- | Type of a `GObject` signal handler id. type SignalHandlerId = CULong -- | A class that provides a constraint satisfied by every type. class NoConstraint a instance NoConstraint a -- | Support for overloaded signal connectors. data SignalProxy (object :: *) (info :: *) where SignalProxy :: SignalProxy o info PropertyNotify :: (info ~ ResolveAttribute propName o, AttrInfo info, pl ~ AttrLabel info) => AttrLabelProxy propName -> SignalProxy o (GObjectNotifySignalInfo pl) -- | Support for overloaded labels. instance #if !MIN_VERSION_base(4,9,0) -- This gives better error reporting in ghc < 8.0. (HasSignal slot object, info ~ ResolveSignal slot object) #else info ~ ResolveSignal slot object #endif => IsLabelProxy slot (SignalProxy object info) where fromLabelProxy _ = SignalProxy #if MIN_VERSION_base(4,10,0) instance info ~ ResolveSignal slot object => IsLabel slot (SignalProxy object info) where fromLabel = SignalProxy #elif MIN_VERSION_base(4,9,0) instance info ~ ResolveSignal slot object => IsLabel slot (SignalProxy object info) where fromLabel _ = SignalProxy #endif -- | Information about an overloaded signal. class SignalInfo (info :: *) where type HaskellCallbackType info -- | Connect a Haskell function to a signal of the given `GObject`, -- specifying whether the handler will be called before or after -- the default handler. connectSignal :: GObject o => SignalProxy o info -> o -> HaskellCallbackType info -> SignalConnectMode -> IO SignalHandlerId -- | Whether to connect a handler to a signal with `connectSignal` so -- that it runs before/after the default handler for the given signal. data SignalConnectMode = SignalConnectBefore -- ^ Run before the default handler. | SignalConnectAfter -- ^ Run after the default handler. -- | Same as `connectSignal`, specifying from the beginning that the -- handler is to be run before the default handler. -- -- > on object signal handler = liftIO $ connectSignal signal object handler SignalConnectBefore on :: forall object info m. (GObject object, MonadIO m, SignalInfo info) => object -> SignalProxy object info -> HaskellCallbackType info -> m SignalHandlerId on o p c = liftIO $ connectSignal p o c SignalConnectBefore -- | Connect a signal to a handler, running the handler after the default one. -- -- > after object signal handler = liftIO $ connectSignal signal object handler SignalConnectAfter after :: forall object info m. (GObject object, MonadIO m, SignalInfo info) => object -> SignalProxy object info -> HaskellCallbackType info -> m SignalHandlerId after o p c = liftIO $ connectSignal p o c SignalConnectAfter -- Connecting GObjects to signals foreign import ccall "g_signal_connect_data" g_signal_connect_data :: Ptr a -> -- instance CString -> -- detailed_signal FunPtr b -> -- c_handler Ptr () -> -- data FunPtr c -> -- destroy_data CUInt -> -- connect_flags IO SignalHandlerId -- | Connect a signal to a handler, given as a `FunPtr`. connectSignalFunPtr :: GObject o => o -> String -> FunPtr a -> SignalConnectMode -> IO SignalHandlerId connectSignalFunPtr object signal fn mode = do let flags = case mode of SignalConnectAfter -> 1 SignalConnectBefore -> 0 withCString signal $ \csignal -> withManagedPtr object $ \objPtr -> g_signal_connect_data objPtr csignal fn (castFunPtrToPtr fn) safeFreeFunPtrPtr flags -- | Connection information for a "notify" signal indicating that a -- specific property changed (see `PropertyNotify` for the relevant -- constructor). data GObjectNotifySignalInfo (propName :: Symbol) instance KnownSymbol propName => SignalInfo (GObjectNotifySignalInfo propName) where type HaskellCallbackType (GObjectNotifySignalInfo propName) = GObjectNotifyCallback connectSignal = connectGObjectNotify (symbolVal (Proxy :: Proxy propName)) -- | Type for a `GObject` `notify` callback. type GObjectNotifyCallback = GParamSpec -> IO () gobjectNotifyCallbackWrapper :: GObjectNotifyCallback -> Ptr () -> Ptr GParamSpec -> Ptr () -> IO () gobjectNotifyCallbackWrapper _cb _ pspec _ = do pspec' <- newGParamSpecFromPtr pspec _cb pspec' type GObjectNotifyCallbackC = Ptr () -> Ptr GParamSpec -> Ptr () -> IO () foreign import ccall "wrapper" mkGObjectNotifyCallback :: GObjectNotifyCallbackC -> IO (FunPtr GObjectNotifyCallbackC) -- | Connect the given notify callback for a GObject. connectGObjectNotify :: forall o i. GObject o => String -> SignalProxy o (i :: *) -> o -> GObjectNotifyCallback -> SignalConnectMode -> IO SignalHandlerId connectGObjectNotify propName _ obj cb mode = do cb' <- mkGObjectNotifyCallback (gobjectNotifyCallbackWrapper cb) let signalName = "notify::" ++ propName connectSignalFunPtr obj signalName cb' mode