{- Copyright 2015 Markus Ongyerth, Stephan Guenther This file is part of Monky. 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 Monky. If not, see . -} {-# LANGUAGE ForeignFunctionInterface #-} {-# LANGUAGE EmptyDataDecls #-} {-# LANGUAGE TemplateHaskell #-} {-| Module : Monky.Alsa Description : Allows acces to information about the alsa sound system Maintainer : ongy Stability : testing Portability : Linux This module provides access to basic audio information provided by the alsa audio system. This MAY work with pulse, but will report useless/inaccurate values. -} module Monky.Alsa ( VOLHandle , destroyVOLHandle , getMute , getVolumeRaw , getVolumePercent , updateVOLH , getVOLHandle , isLoaded , getPollFDs ) where import Control.Monad.Trans import Control.Monad.Trans.Except import Data.IORef import Foreign.C.String (CString, withCString) import Foreign.C.Types (CInt(..), CShort, CLong) import Foreign.Marshal.Alloc import Foreign.Marshal.Array import Foreign.Ptr import Foreign.Storable import Monky.Template import System.Posix.Types #if MIN_VERSION_base(4,8,0) #else import Control.Applicative ((<$>)) #endif #include data PollFD = POLLFD CInt CShort CShort instance Storable PollFD where sizeOf _ = #{size struct pollfd} alignment _ = alignment (undefined :: CLong) peek p = do fd <- #{peek struct pollfd, fd} p events <- #{peek struct pollfd, events} p revents <- #{peek struct pollfd, revents} p return (POLLFD fd events revents) poke p (POLLFD fd events revents) = do #{poke struct pollfd, fd} p fd #{poke struct pollfd, events} p events #{poke struct pollfd, revents} p revents type PollFDPtr = Ptr PollFD liftExceptT :: ((a -> m (Either e b)) -> m (Either e b)) -> (a -> ExceptT e m b) -> ExceptT e m b liftExceptT g f = ExceptT (g (runExceptT . f)) data RegOpt data MClass data Mixer type MixerHandle = Ptr Mixer type MixerHandleAlloc = Ptr MixerHandle data Sid type SidHandle = Ptr Sid type SidHandleAlloc = Ptr SidHandle data Elem type ElemHandle = Ptr Elem importLib "LibAlsa" "libasound.so.2" [ ("mixer_open", "snd_mixer_open", "MixerHandleAlloc -> Int -> IO CInt") , ("mixer_attach", "snd_mixer_attach", "MixerHandle -> CString -> IO CInt") , ("mixer_register", "snd_mixer_selem_register", "MixerHandle -> Ptr RegOpt -> Ptr MClass -> IO CInt") , ("mixer_load", "snd_mixer_load", "MixerHandle -> IO CInt") , ("sid_sindex", "snd_mixer_selem_id_set_index", "SidHandle -> CInt -> IO ()") , ("sid_sname", "snd_mixer_selem_id_set_name", "SidHandle -> CString -> IO ()") , ("sid_alloc", "snd_mixer_selem_id_malloc", "SidHandleAlloc -> IO CInt") , ("sid_free", "snd_mixer_selem_id_free", "SidHandle -> IO ()") , ("elem_gvrange", "snd_mixer_selem_get_playback_volume_range", "ElemHandle -> Ptr CInt -> Ptr CInt -> IO CInt") , ("elem_gvol", "snd_mixer_selem_get_playback_volume", "ElemHandle -> CInt -> Ptr CInt -> IO CInt") , ("elem_gmute", "snd_mixer_selem_get_playback_switch", "ElemHandle -> CInt -> Ptr CInt -> IO CInt") , ("elem_find", "snd_mixer_find_selem", "MixerHandle -> SidHandle -> IO ElemHandle") , ("mixer_handle_events", "snd_mixer_handle_events", "MixerHandle -> IO ()") , ("get_pdescs", "snd_mixer_poll_descriptors", "MixerHandle -> PollFDPtr -> CInt -> IO CInt") , ("get_pdescc", "snd_mixer_poll_descriptors_count", "MixerHandle -> IO CInt") , ("mixer_close", "snd_mixer_close", "MixerHandle -> IO Int") ] getPollDescs :: MixerHandle -> LibAlsa -> IO [CInt] getPollDescs h l = do count <- get_pdescc l h allocaArray (fromIntegral count) $ \ptr -> do c2 <- get_pdescs l h ptr count if count == c2 then map (\(POLLFD fd _ _) -> fd) <$> (peekArray (fromIntegral c2) ptr) else error "libalsa returned more (or less) fds than it adveritses!" openMixer :: LibAlsa -> ExceptT Int IO MixerHandle openMixer l = liftExceptT alloca $ \ptr -> do rval <- liftIO (mixer_open l ptr 0) if rval < 0 then throwE $ fromIntegral rval else liftIO (peek ptr) mixerAttach :: MixerHandle -> String -> LibAlsa -> ExceptT Int IO () mixerAttach handle card l = do rval <- liftIO (withCString card $ mixer_attach l handle) if rval < 0 then throwE $ fromIntegral rval else liftIO (return ()) mixerRegister :: MixerHandle -> LibAlsa -> ExceptT Int IO () mixerRegister handle l = do rval <- liftIO (mixer_register l handle nullPtr nullPtr) if rval < 0 then throwE $ fromIntegral rval else liftIO (return ()) mixerLoad :: MixerHandle -> LibAlsa -> ExceptT Int IO () mixerLoad handle l = do rval <- liftIO (mixer_load l handle) if rval < 0 then throwE $ fromIntegral rval else liftIO (return ()) withSid :: LibAlsa -> (SidHandle -> IO a) -> IO a withSid l fun = alloca $ \ptr -> do rval <- sid_alloc l ptr if rval < 0 then error "Failed to allocate sid" else do handle <- peek ptr comp <- fun handle sid_free l handle return comp sidSet :: SidHandle -> Int -> String -> LibAlsa -> IO () sidSet handle index name l = do withCString name $ sid_sname l handle sid_sindex l handle $ fromIntegral index getElem :: MixerHandle -> String -> Int -> LibAlsa -> IO ElemHandle getElem handle name index l = withSid l $ \sid -> do sidSet sid index name l elem_find l handle sid isMute :: ElemHandle -> LibAlsa -> IO Bool isMute handle l = alloca $ \ptr -> do _ <- elem_gmute l handle 0 ptr val <- peek ptr return $ val == 0 getVolumeRange :: ElemHandle -> LibAlsa -> IO (Int, Int) getVolumeRange handle l = alloca $ \lower -> alloca $ \upper -> do _ <- elem_gvrange l handle lower upper lowerv <- peek lower upperv <- peek upper return (fromIntegral lowerv, fromIntegral upperv) getVolume :: ElemHandle -> LibAlsa -> IO Int getVolume handle l = alloca $ \ptr -> do _ <- elem_gvol l handle 0 ptr val <- peek ptr return $ fromIntegral val getMixerHandle :: String -> LibAlsa -> ExceptT Int IO MixerHandle getMixerHandle card l = do handle <- openMixer l mixerAttach handle card l mixerRegister handle l mixerLoad handle l return handle percentize :: Int -> Int -> Int -> Int percentize val lower upper = 100 * (val - lower) `div` (upper-lower) -- |The handle exported by this module data VOLHandle = VOLH LibAlsa MixerHandle ElemHandle (IORef Int) (IORef Bool) Int Int | Err {- |Update the volume handle. This function has to be called to update the handle internally. Calling this will get the current state into the handle, which can then by queried by the other functions. Until this is called again, the results of other functions will not update to the current state of the system. -} updateVOLH :: VOLHandle -> IO () updateVOLH (VOLH l handle ehandle valr muter _ _) = do mixer_handle_events l handle val <- getVolume ehandle l mute <- isMute ehandle l writeIORef valr val writeIORef muter mute updateVOLH Err = return () -- |Get the raw volume value from alsa getVolumeRaw :: VOLHandle -> IO Int getVolumeRaw (VOLH _ _ _ valr _ _ _) = readIORef valr getVolumeRaw Err = return 0 {- |Get the volume in percent (100% = loudest 0%=lowest) 0% does not equal a muted device. -} getVolumePercent :: VOLHandle -> IO Int getVolumePercent (VOLH _ _ _ valr _ lower upper) = do val <- readIORef valr return $ percentize val lower upper getVolumePercent Err = return 0 -- |return 'True' if the device is muted getMute :: VOLHandle -> IO Bool getMute (VOLH _ _ _ _ muter _ _) = readIORef muter getMute Err = return True getVOLHandleInt :: Either Int MixerHandle -> LibAlsa -> IO VOLHandle getVOLHandleInt (Right handle) l = do ehandle <- getElem handle "Master" 0 l if ehandle == nullPtr then return Err else do (lower, upper) <- getVolumeRange ehandle l val <- getVolume ehandle l mute <- isMute ehandle l volref <- newIORef val muteref <- newIORef mute return (VOLH l handle ehandle volref muteref lower upper) getVOLHandleInt _ _ = return Err -- |Check if there was an error creating the handle isLoaded :: VOLHandle -> Bool isLoaded Err = False isLoaded _ = True -- |Get PollFds for polling interface getPollFDs :: VOLHandle -> IO [Fd] getPollFDs (VOLH l h _ _ _ _ _) = map Fd <$> getPollDescs h l getPollFDs Err = return [] -- | Close the mixer handle and unload alsa library destroyVOLHandle :: VOLHandle -> IO () destroyVOLHandle (VOLH a m _ _ _ _ _) = mixer_close a m >> destroyLibAlsa a destroyVOLHandle Err = return () {- |Create an 'VOLHandle' This function returns a type save error value if any alsa function fails -} getVOLHandle :: String -- ^The audio-card to use -> IO VOLHandle getVOLHandle card = do l <- getLibAlsa handle <- runExceptT (getMixerHandle card l) getVOLHandleInt handle l