macos-corelibs-0.0.1.0: src/MacSdk/Framework/Accessibility/Observer.hs
module MacSdk.Framework.Accessibility.Observer
( Observer
, ObserverCallback
, observerCreate
, observerRelease
, observerStart
, observerStart'
, observerStop
, observerGetRunLoopSource
-- * Notifications
, addNotification
, removeNotification
) where
import MacSdk.Framework.Types (PID)
import MacSdk.Framework.Accessibility.Error
import MacSdk.Framework.Accessibility.UIElement
import MacSdk.Framework.Accessibility.UINotification
import MacSdk.Framework.Accessibility.Types
import MacSdk.Framework.CoreFoundation
import Control.Monad.Managed
import Control.Monad ((>=>))
import Foreign hiding (with)
import Foreign.C.Types (CInt(..))
import Prelude hiding (String)
import Data.IORef
data AXObserver
type AXObserverRef = Ptr AXObserver
data Observer = Observer
{ _obsObj :: Object AXObserver
, _obsRef :: Ref
, _obsCallb :: FunPtr AXObserverCallback
, _obsElems :: IORef [(UINotification, Ref)]
}
instance CFClass AXObserver
foreign import ccall "wrapper"
wrap_observer_callback :: AXObserverCallback -> IO (FunPtr AXObserverCallback)
foreign import ccall "Carbon/Carbon.h AXObserverCreate"
ax_observer_create
:: PID -> FunPtr AXObserverCallback -> Ptr AXObserverRef -> IO ForeignAXError
type AXObserverCallback =
AXObserverRef -> AXUIElementRef -> CFStringRef -> StablePtr () -> IO ()
type ObserverCallback =
-- Observer ->
UIElement -> UINotification -> StablePtr () -> IO ()
toAXCallback :: ObserverCallback -> AXObserverCallback
toAXCallback f _ el str ptr = do
el' <- retainManageCFObj el
mNotif <- toNotif str
maybe (pure ()) (\notif -> f el' notif ptr) mNotif
-- | Creates an observer for the application with specified PID.
--
-- Note that this does not start the observer. Refer to 'observerStart' for
-- this.
--
-- Observers that are not longer useful must be released with 'observerRelease'
-- to avoid memory leaks.
observerCreate
:: (MonadIO m, MonadError e m, AsAXError e)
=> PID -> ObserverCallback -> m Observer
observerCreate pid f = do
fptr <- liftIO (wrap_observer_callback (toAXCallback f))
obs <- axObserverCreate pid fptr >>= liftIO . manageCFObj
liftIO $ do
oref <- objRetain obs
elems <- newIORef []
pure (Observer obs oref fptr elems)
-- | Starts an observer by adding it to the given event loop, according to the
-- specified 'RunLoopMode'.
--
-- See 'observerStart'' for a version of this using default parameters.
observerStart :: MonadIO m => RunLoop -> RunLoopMode -> Observer -> m ()
observerStart rl rlm obs = liftIO $ do
rls <- observerGetRunLoopSource obs
b <- runLoopContainsSource rl rls rlm
if b then pure () else runLoopAddSource rl rls rlm
-- | Starts an observer by adding it to the main run loop, using the default
-- mode.
observerStart' :: MonadIO m => Observer -> m ()
observerStart' o = liftIO mainRunLoop >>= \x -> observerStart x DefaultMode o
-- | Stops an observer, preventing it from receiving further notifications.
observerStop :: Observer -> IO ()
observerStop obs = observerGetRunLoopSource obs >>= runLoopSourceInvalidate
-- | Releases all resources associated to an observer.
observerRelease :: MonadIO m => Observer -> m ()
observerRelease (Observer _ ref fptr elems) = liftIO $ do
refRelease ref
freeHaskellFunPtr fptr
readIORef elems >>= mapM_ refRelease . fmap snd
axObserverCreate
:: (MonadIO m, MonadError e m, AsAXError e)
=> PID -> FunPtr AXObserverCallback -> m AXObserverRef
axObserverCreate pid callb = except . liftIO $ alloca $ \p -> do
err <- ax_observer_create pid callb p
o <- peek p
pure (either Left (const (Right o)) (toAXResult err))
foreign import ccall unsafe "Carbon/Carbon.h AXObserverGetRunLoopSource"
axObserverGetRunLoopSource :: AXObserverRef -> IO CFRunLoopSourceRef
observerGetRunLoopSource :: Observer -> IO RunLoopSource
observerGetRunLoopSource obs = withCFPtr (_obsObj obs) $
axObserverGetRunLoopSource >=> retainManageCFObj
foreign import ccall unsafe "Carbon/Carbon.h AXObserverAddNotification"
ax_observer_add_notification
:: AXObserverRef -> AXUIElementRef -> CFStringRef -> StablePtr a
-> IO ForeignAXError
-- | Registers the specified observer to receive notifications from the
-- specified accessibility object.
addNotification
:: (MonadIO m, MonadError e m, AsAXError e)
=> Observer -> UIElement -> UINotification -> StablePtr a -> m ()
addNotification obs el notif ptr = except . liftIO $ flip with pure $ do
obs' <- managed $ withCFPtr (_obsObj obs)
el' <- managed $ withCFPtr el
liftIO $ do
str <- notifString notif
ref <- objRetain el
fmap toAXResult (ax_observer_add_notification obs' el' str ptr)
<* atomicModifyIORef' (_obsElems obs) ((,()) . ((notif, ref) :))
foreign import ccall "Carbon/Carbon.h AXObserverRemoveNotification"
ax_observer_remove_notification
:: AXObserverRef -> AXUIElementRef -> CFStringRef -> IO ForeignAXError
lookupAndDelete :: Eq a => a -> [(a, b)] -> ([(a, b)], Maybe b)
lookupAndDelete x xs =
maybe (xs, Nothing) ((filter ((/= x) . fst) xs,) . Just) (lookup x xs)
-- | Removes the specified notification from the list of notifications the
-- observer wants to receive from the accessibility object.
removeNotification
:: (MonadIO m, MonadError e m, AsAXError e)
=> Observer -> UIElement -> UINotification -> m ()
removeNotification obs el notif = except . liftIO . flip with pure $ do
obs' <- managed $ withCFPtr (_obsObj obs)
el' <- managed $ withCFPtr el
liftIO $ do
str <- notifString notif
fmap toAXResult (ax_observer_remove_notification obs' el' str)
<* (atomicModifyIORef' (_obsElems obs) (lookupAndDelete notif)
>>= maybe (pure ()) refRelease)