packages feed

pulseaudio-0.0.1.0: src/Sound/Pulse/Mainloop.hsc

{-
    Copyright 2016 Markus Ongyerth

    This file is part of pulseaudio-hs.

    Monky is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Monky is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with pulseaudio-hs.  If not, see <http://www.gnu.org/licenses/>.
-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE RecordWildCards #-}
{-|
Module      : Sound.Pulse.Mainloop
Description : Provides a class and wrapper type for Mainloop abstraction
Maintianer  : ongy
Stability   : experimental

This module provides Haskell abstractions over the pulse mainloop-api <https://freedesktop.org/software/pulseaudio/doxygen/mainloop-api_8h.html>.

There is also a simple implementation for this in 'Sound.Pulse.Mainloop.Simple'.
-}
module Sound.Pulse.Mainloop
    ( PAMainloop(..)
    , PAIOEventFlags(..)
    , PAMainloopApi
    , getMainloopApi

    )
where

import Sound.Pulse.Userdata

#if __GLASGOW_HASKELL__ < 800
#let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__)
#endif
#include <pulse/mainloop-api.h>

import Control.Applicative ((<$>), (<*>))
import Foreign.Marshal.Utils (with)
import Foreign.Storable
import Foreign.StablePtr
import Foreign.Ptr
import Foreign.C.Types
import System.Posix.Types (Fd(..))

import Data.Bits ((.&.), (.|.))

import Control.Concurrent.MVar

import Data.Time
import Data.Time.Internal

-- |Events that 'PAIOEvent's may wait for.
-- At my current level of testing, it looks like libpulse only uses Input (and
-- I guess output when writing to the server)
data PAIOEventFlags
    = PAIOEventNull -- ^No Event (this is for compatibility, should never show up)
    | PAIOEventInput -- ^Fd went ready for reading
    | PAIOEventOutput -- ^Fd went ready for writing
    | PAIOEventHangup -- ^Fd got a hangup (see poll documentation)
    | PAIOEventError -- ^Fd got an error (see poll documentation)
    deriving (Eq, Show)

-- |Internal function for conversion
ioEventToCInt :: PAIOEventFlags -> CInt
ioEventToCInt PAIOEventNull   = #{const PA_IO_EVENT_NULL}
ioEventToCInt PAIOEventInput  = #{const PA_IO_EVENT_INPUT}
ioEventToCInt PAIOEventOutput = #{const PA_IO_EVENT_OUTPUT}
ioEventToCInt PAIOEventHangup = #{const PA_IO_EVENT_HANGUP}
ioEventToCInt PAIOEventError  = #{const PA_IO_EVENT_ERROR}

-- |Internal function for conversion
ioEventsToCInt :: [PAIOEventFlags] -> CInt
ioEventsToCInt xs = foldr (\x y -> ioEventToCInt x .|. y) 0 xs

-- |Internal function for conversion
ioEventsFromCInt :: CInt -> [PAIOEventFlags]
ioEventsFromCInt val =
    let enull  = potAdd PAIOEventNull   []
        input  = potAdd PAIOEventInput  enull
        output = potAdd PAIOEventOutput input
        hangup = potAdd PAIOEventHangup output
        err    = potAdd PAIOEventError  hangup
    in err
    where potAdd :: PAIOEventFlags -> ([PAIOEventFlags] -> [PAIOEventFlags])
          potAdd con = if (val .&. ioEventToCInt con) /= 0 then (con:) else id

{- | The main class for a pulseaudio mainloop abstraction. -}
class PAMainloop a where
    -- |Pulseaudio IO event. Will have to wait for an Fd to become ready
    -- (w.r.t. to flags)
    data PAIOEvent a :: *
    -- |Pulseaudio Time event. Should fire at a specified time.
    data PATimeEvent a :: *
    -- |Pulseaudio Defer event. Should be handled before other events. May
    -- be disabled.
    data PADeferEvent a :: *

    -- |Create a new 'PAIOEvent' and set it to listen for specified events.
    ioNew    :: a -> Fd -> [PAIOEventFlags] -> ([PAIOEventFlags] -> IO ()) -> IO (PAIOEvent a)
    -- |Modify the events an IOEvent is waiting on.
    ioEnable :: (PAIOEvent a) -> [PAIOEventFlags] -> IO ()
    -- |Delete an IOEvent.
    ioFree   :: (PAIOEvent a) -> IO ()
    -- |Set the destroy handler of an IOEvent. The handler should be called,
    -- when the event is deleted.
    ioSetDestroy :: (PAIOEvent a) -> IO () -> IO ()

    -- |Create a new 'PATimeEvent'. This should fire at the given time.
    timeNew :: a -> PATime -> (PATime -> IO ()) -> IO (PATimeEvent a)
    -- |Set a new time when the 'PATimeEvent' should fire. This may require
    -- requeing.
    timeRestart :: PATimeEvent a -> PATime -> IO ()
    -- |Delete a 'PATimeEvent'.
    timeFree :: PATimeEvent a -> IO ()
    -- |Set the destroy handler.
    timeSetDestroy :: PATimeEvent a -> IO () -> IO ()

    -- |Create a new 'PADefereEvent'. This should be fired before any other
    -- events.
    deferNew :: a -> IO () -> IO (PADeferEvent a)
    -- |Enable or disable a 'PADefereEvent'.
    deferEnable :: PADeferEvent a -> Bool -> IO ()
    -- |Delete the event.
    deferFree :: PADeferEvent a -> IO ()
    -- |Set the destroy handler for the event.
    deferSetDestroy :: PADeferEvent a -> IO () -> IO ()

    -- |Quit the mainloop and return the given int.
    quitLoop :: a -> Int -> IO ()

-- |Internal wrapper type for PAIOEvent
data PAIOEventW a = PAIOEventW
    {- ioRealHandle  :: -} (PAIOEvent a)
    {- ioWrappedData :: -} (Ptr Userdata)
    {- ioWrappedApi  :: -} (Ptr (PAMainloopApi a))

-- |"Record-name"
ioRealHandle :: PAIOEventW a -> PAIOEvent a
ioRealHandle (PAIOEventW x _ _) = x

-- |Internal wrapper type for PATimeEvent
data PATimeEventW a = PATimeEventW
    {- timeRealHandle  :: -} (PATimeEvent a)
    {- timeWrappedData :: -} (Ptr Userdata)
    {- timeWrappedApi  :: -} (Ptr (PAMainloopApi a))

-- |"Record-name"
timeRealHandle :: PATimeEventW a -> PATimeEvent a
timeRealHandle (PATimeEventW x _ _) = x

-- |Internal wrapper type for PADeferEvent
data PADeferEventW a = PADeferEventW
    {- deferRealHandle  :: -} (PADeferEvent a)
    {- deferWrappedData :: -} (Ptr Userdata)
    {- deferWrappedApi  :: -} (Ptr (PAMainloopApi a))

-- |"Record-name"
deferRealHandle :: PADeferEventW a -> PADeferEvent a
deferRealHandle (PADeferEventW x _ _) = x

{- IO Events -}
type IOEvCB a = Ptr (PAMainloopApi a) -> StablePtr (PAIOEventW a) -> CInt -> CInt -> Ptr Userdata -> IO ()
foreign import ccall "dynamic" mkIOEvCB :: FunPtr (IOEvCB a) -> (IOEvCB a)

type IONew a = Ptr (PAMainloopApi a) -> CInt -> CInt -> FunPtr (IOEvCB a) -> Ptr Userdata -> IO (StablePtr (PAIOEventW a))
foreign import ccall "wrapper" mkIONew :: IONew a -> IO (FunPtr (IONew a))

type IOEnable a = StablePtr (PAIOEventW a) -> CInt -> IO ()
foreign import ccall "wrapper" mkIOEnable :: IOEnable a -> IO (FunPtr (IOEnable a))

type IOFree a = StablePtr (PAIOEventW a) -> IO ()
foreign import ccall "wrapper" mkIOFree :: IOFree a -> IO (FunPtr (IOFree a))

type IODestroy a = Ptr (PAMainloopApi a) -> StablePtr (PAIOEventW a) -> Ptr Userdata -> IO ()
foreign import ccall "dynamic" mkIODestroy :: FunPtr (IODestroy a) -> (IODestroy a)

type IOSetDestroy a = StablePtr (PAIOEventW a) -> FunPtr (IODestroy a) -> IO ()
foreign import ccall "wrapper" mkIOSetDestroy :: IOSetDestroy a -> IO (FunPtr (IOSetDestroy a))

{- Time Events -}
type TimeEvCB a = Ptr (PAMainloopApi a) -> StablePtr (PATimeEventW a) -> Ptr PAITime -> Ptr Userdata -> IO ()
foreign import ccall "dynamic" mkTimeEvCB :: FunPtr (TimeEvCB a) -> (TimeEvCB a)

type TimeNew a = Ptr (PAMainloopApi a) -> Ptr PAITime -> FunPtr (TimeEvCB a) -> Ptr Userdata -> IO (StablePtr (PATimeEventW a))
foreign import ccall "wrapper" mkTimeNew :: TimeNew a -> IO (FunPtr (TimeNew a))

type TimeRestart a = StablePtr (PATimeEventW a) -> Ptr PAITime -> IO ()
foreign import ccall "wrapper" mkTimeRestart :: TimeRestart a -> IO (FunPtr (TimeRestart a))

type TimeFree a = StablePtr (PATimeEventW a) -> IO ()
foreign import ccall "wrapper" mkTimeFree :: TimeFree a -> IO (FunPtr (TimeFree a))

type TimeDestroy a = Ptr (PAMainloopApi a) -> StablePtr (PATimeEventW a) -> Ptr Userdata -> IO ()
foreign import ccall "dynamic" mkTimeDestroy :: FunPtr (TimeDestroy a) -> (TimeDestroy a)

type TimeSetDestroy a = StablePtr (PATimeEventW a) -> FunPtr (TimeDestroy a) -> IO ()
foreign import ccall "wrapper" mkTimeSetDestroy :: TimeSetDestroy a -> IO (FunPtr (TimeSetDestroy a))

{- Defer Events -}
type DeferEvCB a = Ptr (PAMainloopApi a) -> StablePtr (PADeferEventW a) -> Ptr Userdata -> IO ()
foreign import ccall "dynamic" mkDeferEvCB :: FunPtr (DeferEvCB a) -> DeferEvCB a

type DeferNew a = Ptr (PAMainloopApi a) -> FunPtr (DeferEvCB a) -> Ptr Userdata -> IO (StablePtr (PADeferEventW a))
foreign import ccall "wrapper" mkDeferNew :: DeferNew a -> IO (FunPtr (DeferNew a))

type DeferEnable a = StablePtr (PADeferEventW a) -> CInt -> IO ()
foreign import ccall "wrapper" mkDeferEnable :: DeferEnable a -> IO (FunPtr (DeferEnable a))

type DeferFree a = StablePtr (PADeferEventW a) -> IO ()
foreign import ccall "wrapper" mkDeferFree :: DeferFree a -> IO (FunPtr (DeferFree a))

type DeferDestroy a = Ptr (PAMainloopApi a) -> StablePtr (PADeferEventW a) -> Ptr Userdata -> IO ()
foreign import ccall "dynamic" mkDeferDestroy :: FunPtr (DeferDestroy a) -> (DeferDestroy a)

type DeferSetDestroy a = StablePtr (PADeferEventW a) -> FunPtr (DeferDestroy a) -> IO ()
foreign import ccall "wrapper" mkDeferSetDestroy :: DeferSetDestroy a -> IO (FunPtr (DeferSetDestroy a))

type Quit a = Ptr (PAMainloopApi a) -> CInt -> IO ()
foreign import ccall "wrapper" mkQuit :: Quit a -> IO (FunPtr (Quit a))

-- |The type used to encapsule a 'PAMainloop' in a C compatible struct of
-- 'FunPtr's.
data PAMainloopApi a = PAMainloopApi
    { userdata          :: StablePtr a

    , io_new            :: FunPtr (IONew a)
    , io_enable         :: FunPtr (IOEnable a)
    , io_free           :: FunPtr (IOFree a)
    , io_set_destroy    :: FunPtr (IOSetDestroy a)

    , time_new          :: FunPtr (TimeNew a)
    , time_restart      :: FunPtr (TimeRestart a)
    , time_free         :: FunPtr (TimeFree a)
    , time_set_destroy  :: FunPtr (TimeSetDestroy a)

    , defer_new         :: FunPtr (DeferNew a)
    , defer_enable      :: FunPtr (DeferEnable a)
    , defer_free        :: FunPtr (DeferFree a)
    , defer_set_destroy :: FunPtr (DeferSetDestroy a)

    , quit              :: FunPtr (Quit a)
    }

instance Storable (PAMainloopApi a) where
    sizeOf _ = #{size pa_mainloop_api}
    alignment _ = #{alignment pa_mainloop_api}
    peek p = PAMainloopApi
        <$> #{peek pa_mainloop_api, userdata} p
        <*> #{peek pa_mainloop_api, io_new} p
        <*> #{peek pa_mainloop_api, io_enable} p
        <*> #{peek pa_mainloop_api, io_free} p
        <*> #{peek pa_mainloop_api, io_set_destroy} p
        <*> #{peek pa_mainloop_api, time_new} p
        <*> #{peek pa_mainloop_api, time_restart} p
        <*> #{peek pa_mainloop_api, time_free} p
        <*> #{peek pa_mainloop_api, time_set_destroy} p
        <*> #{peek pa_mainloop_api, defer_new} p
        <*> #{peek pa_mainloop_api, defer_enable} p
        <*> #{peek pa_mainloop_api, defer_free} p
        <*> #{peek pa_mainloop_api, defer_set_destroy} p
        <*> #{peek pa_mainloop_api, quit} p
    poke p (PAMainloopApi {..}) = do
        #{poke pa_mainloop_api, userdata}          p userdata
        #{poke pa_mainloop_api, io_new}            p io_new
        #{poke pa_mainloop_api, io_enable}         p io_enable
        #{poke pa_mainloop_api, io_free}           p io_free
        #{poke pa_mainloop_api, io_set_destroy}    p io_set_destroy
        #{poke pa_mainloop_api, time_new}          p time_new
        #{poke pa_mainloop_api, time_restart}      p time_restart
        #{poke pa_mainloop_api, time_free}         p time_free
        #{poke pa_mainloop_api, time_set_destroy}  p time_set_destroy
        #{poke pa_mainloop_api, defer_new}         p defer_new
        #{poke pa_mainloop_api, defer_enable}      p defer_enable
        #{poke pa_mainloop_api, defer_free}        p defer_free
        #{poke pa_mainloop_api, defer_set_destroy} p defer_set_destroy
        #{poke pa_mainloop_api, quit}              p quit

-- |Get the Mainloop back from a 'PAMainloopApi' without marshalling all of it.
getMainloopImpl :: Ptr (PAMainloopApi a) -> IO a
getMainloopImpl p =
    deRefStablePtr =<< (peek . #{ptr pa_mainloop_api, userdata} $ p)

-- |Warning! This leaks a bit of memory when it's Garbage collected, because
-- the FunPtrs created for the PulseApi cannot be collected at that point.
-- Currently there is no way to properly free them, but this should only be
-- called once per application, so this will be a known Bug for now.
getMainloopApi :: PAMainloop a => a -> IO (PAMainloopApi a)
getMainloopApi api = do
    io_enable <- mkIOEnable $ \ptr flags -> do
        evt <- deRefStablePtr ptr
        ioEnable (ioRealHandle evt) (ioEventsFromCInt flags)
    io_new <- mkIONew $ \a fd flags fptr usr -> do
        -- This is stupid :(
        let fun = mkIOEvCB fptr
        impl <- getMainloopImpl a
        mvar <- newEmptyMVar
        ret <- ioNew impl (Fd fd) (ioEventsFromCInt flags) $ \iflags -> do
            evtp <- readMVar mvar
            fun a evtp fd (ioEventsToCInt iflags) usr
        rpt <- newStablePtr $ PAIOEventW ret usr a
        putMVar mvar rpt
        return rpt
    io_free <- mkIOFree ((=<<) (ioFree . ioRealHandle) . deRefStablePtr)
    io_set_destroy <- mkIOSetDestroy $ \ptr fptr -> do
        let fun = mkIODestroy fptr
        (PAIOEventW evt usr a) <- deRefStablePtr ptr
        ioSetDestroy evt $ fun a ptr usr

    time_new <- mkTimeNew $ \a timep fptr usr -> do
        let fun = mkTimeEvCB fptr
        impl <- getMainloopImpl a
        mvar <- newEmptyMVar
        time <- peek timep
        ret <- timeNew impl (fromPAI time) $ \itime -> do
            evt <- readMVar mvar
            with (toPAI itime) $ flip (fun a evt) usr
        rpt <- newStablePtr $ PATimeEventW ret usr a
        putMVar mvar rpt
        return rpt
    time_restart <- mkTimeRestart $ \ptr timep -> do
        time <- peek timep
        flip timeRestart (fromPAI time) . timeRealHandle =<< deRefStablePtr ptr
    time_free <- mkTimeFree ((=<<) (timeFree . timeRealHandle) . deRefStablePtr)
    time_set_destroy <- mkTimeSetDestroy $ \ptr fptr -> do
        let fun = mkTimeDestroy fptr
        (PATimeEventW evt usr a) <- deRefStablePtr ptr
        timeSetDestroy evt $ fun a ptr usr

    defer_new <- mkDeferNew $ \a fptr usr -> do
        let fun = mkDeferEvCB fptr
        impl <- getMainloopImpl a
        mvar <- newEmptyMVar
        ret <- deferNew impl $ (flip (fun a) usr =<< readMVar mvar)
        rpt <- newStablePtr $ PADeferEventW ret usr a
        putMVar mvar rpt
        return rpt
    defer_enable <- mkDeferEnable $ \ptr switch -> do
        flip deferEnable (switch /= 0) . deferRealHandle =<< deRefStablePtr ptr
    defer_free <- mkDeferFree ((=<<) (deferFree . deferRealHandle) . deRefStablePtr)
    defer_set_destroy <- mkDeferSetDestroy $ \ptr fptr -> do
        let fun = mkDeferDestroy fptr
        (PADeferEventW evt usr a) <- deRefStablePtr ptr
        deferSetDestroy evt $ fun a ptr usr

    quit <- mkQuit $ \ptr val -> do
        impl <- getMainloopImpl ptr
        quitLoop impl (fromIntegral val)
    userdata <- newStablePtr api
    return $ PAMainloopApi {..}