module XMonad.Actions.Volume (
toggleMute,
raiseVolume,
lowerVolume,
getVolume,
getMute,
getVolumeMute,
setVolume,
setMute,
setVolumeMute,
modifyVolume,
modifyMute,
modifyVolumeMute,
defaultChannels,
toggleMuteChannels,
raiseVolumeChannels,
lowerVolumeChannels,
getVolumeChannels,
getMuteChannels,
getVolumeMuteChannels,
setVolumeChannels,
setMuteChannels,
setVolumeMuteChannels,
modifyVolumeChannels,
modifyMuteChannels,
modifyVolumeMuteChannels,
defaultOSDOpts,
osdCat
) where
import Control.Monad
import Control.Monad.Trans
import Data.List.Split (splitOn)
import Data.Maybe
import System.IO
import System.Process
import Text.ParserCombinators.Parsec
import XMonad.Core
infixl 1 <*
(<*) :: Monad m => m a -> m b -> m a
pa <* pb = pa >>= \a -> pb >> return a
toggleMute :: MonadIO m => m Bool
raiseVolume :: MonadIO m => Double -> m Double
lowerVolume :: MonadIO m => Double -> m Double
getVolume :: MonadIO m => m Double
getMute :: MonadIO m => m Bool
getVolumeMute :: MonadIO m => m (Double, Bool)
setVolume :: MonadIO m => Double -> m ()
setMute :: MonadIO m => Bool -> m ()
setVolumeMute :: MonadIO m => Double -> Bool -> m ()
modifyVolume :: MonadIO m => (Double -> Double ) -> m Double
modifyMute :: MonadIO m => (Bool -> Bool ) -> m Bool
modifyVolumeMute :: MonadIO m => (Double -> Bool -> (Double, Bool)) -> m (Double, Bool)
toggleMute = toggleMuteChannels defaultChannels
raiseVolume = raiseVolumeChannels defaultChannels
lowerVolume = lowerVolumeChannels defaultChannels
getVolume = getVolumeChannels defaultChannels
getMute = getMuteChannels defaultChannels
getVolumeMute = getVolumeMuteChannels defaultChannels
setVolume = setVolumeChannels defaultChannels
setMute = setMuteChannels defaultChannels
setVolumeMute = setVolumeMuteChannels defaultChannels
modifyVolume = modifyVolumeChannels defaultChannels
modifyMute = modifyMuteChannels defaultChannels
modifyVolumeMute = modifyVolumeMuteChannels defaultChannels
defaultChannels :: [String]
defaultChannels = ["Master", "Wave", "PCM"]
toggleMuteChannels :: MonadIO m => [String] -> m Bool
raiseVolumeChannels :: MonadIO m => [String] -> Double -> m Double
lowerVolumeChannels :: MonadIO m => [String] -> Double -> m Double
getVolumeChannels :: MonadIO m => [String] -> m Double
getMuteChannels :: MonadIO m => [String] -> m Bool
getVolumeMuteChannels :: MonadIO m => [String] -> m (Double, Bool)
setVolumeChannels :: MonadIO m => [String] -> Double -> m ()
setMuteChannels :: MonadIO m => [String] -> Bool -> m ()
setVolumeMuteChannels :: MonadIO m => [String] -> Double -> Bool -> m ()
modifyVolumeChannels :: MonadIO m => [String] -> (Double -> Double ) -> m Double
modifyMuteChannels :: MonadIO m => [String] -> (Bool -> Bool ) -> m Bool
modifyVolumeMuteChannels :: MonadIO m => [String] -> (Double -> Bool -> (Double, Bool)) -> m (Double, Bool)
toggleMuteChannels cs = modifyMuteChannels cs not
raiseVolumeChannels cs = modifyVolumeChannels cs . (+)
lowerVolumeChannels cs = modifyVolumeChannels cs . (subtract)
getVolumeChannels = liftIO . fmap fst . amixerGetAll
getMuteChannels = liftIO . fmap snd . amixerGetAll
getVolumeMuteChannels = liftIO . amixerGetAll
setVolumeChannels cs v = liftIO (amixerSetVolumeOnlyAll v cs)
setMuteChannels cs m = liftIO (amixerSetMuteOnlyAll m cs)
setVolumeMuteChannels cs v m = liftIO (amixerSetAll v m cs)
modifyVolumeChannels = modify getVolumeChannels setVolumeChannels
modifyMuteChannels = modify getMuteChannels setMuteChannels
modifyVolumeMuteChannels cs = modify getVolumeMuteChannels (\cs' -> uncurry (setVolumeMuteChannels cs')) cs . uncurry
geomMean :: Floating a => [a] -> a
geomMean xs = product xs ** (recip . fromIntegral . length $ xs)
clip :: (Num t, Ord t) => t -> t
clip = min 100 . max 0
modify :: Monad m => (arg -> m value) -> (arg -> value -> m ()) -> arg -> (value -> value) -> m value
modify get set cs f = do
v <- liftM f $ get cs
set cs v
return v
outputOf :: String -> IO String
outputOf s = do
uninstallSignalHandlers
(hIn, hOut, hErr, p) <- runInteractiveCommand s
mapM_ hClose [hIn, hErr]
hGetContents hOut <* waitForProcess p <* installSignalHandlers
amixerSetAll :: Double -> Bool -> [String] -> IO ()
amixerSet :: Double -> Bool -> String -> IO String
amixerGetAll :: [String] -> IO (Double, Bool)
amixerGet :: String -> IO String
amixerSetAll = (mapM_ .) . amixerSet
amixerSet v m s = outputOf $ "amixer set '" ++ s ++ "' " ++ show (clip v) ++ "% " ++ (if m then "" else "un") ++ "mute"
amixerGetAll = fmap parseAmixerGetAll . mapM amixerGet
amixerGet s = outputOf $ "amixer get \'" ++ s ++ "\'"
amixerSetVolumeOnlyAll :: Double -> [String] -> IO ()
amixerSetVolumeOnly :: Double -> String -> IO String
amixerSetVolumeOnlyAll = mapM_ . amixerSetVolumeOnly
amixerSetVolumeOnly v s = outputOf $ "amixer set '" ++ s ++ "' " ++ show (clip v) ++ "% "
amixerSetMuteOnlyAll :: Bool -> [String] -> IO ()
amixerSetMuteOnly :: Bool -> String -> IO String
amixerSetMuteOnlyAll = mapM_ . amixerSetMuteOnly
amixerSetMuteOnly m s = outputOf $ "amixer set '" ++ s ++ "' " ++ (if m then "" else "un") ++ "mute"
parseAmixerGetAll :: [String] -> (Double, Bool)
parseAmixerGetAll ss = (geomMean vols, mute) where
(vols, mutings) = unzip [v | Right p <- map (parse amixerGetParser "") ss, v <- p]
mute = or . catMaybes $ mutings
amixerGetParser :: Parser [(Double, Maybe Bool)]
amixerGetParser = headerLine >> playbackChannels >>= volumes <* eof
headerLine :: Parser String
playbackChannels :: Parser [String]
volumes :: [String] -> Parser [(Double, Maybe Bool)]
headerLine = string "Simple mixer control " >> upTo '\n'
playbackChannels = keyValueLine >>= \kv -> case kv of
("Playback channels", v) -> return (splitOn " - " v)
_ -> playbackChannels
volumes channels = fmap concat . many1 $ keyValueLine >>= \kv -> return $ case kv of
(k, v) | k `elem` channels -> parseChannel v
| otherwise -> []
upTo :: Char -> Parser String
keyValueLine :: Parser (String, String)
upTo c = many (satisfy (/= c)) <* char c
keyValueLine = do
string " "
key <- upTo ':'
value <- upTo '\n'
return (key, drop 1 value)
parseChannel :: String -> [(Double, Maybe Bool)]
channelParser :: Parser [(Double, Maybe Bool)]
parseChannel = either (const []) id . parse channelParser ""
channelParser = fmap catMaybes (many1 playbackOrCapture) <* eof
playbackOrCapture :: Parser (Maybe (Double, Maybe Bool))
playbackOrCapture = do
f <- (string "Playback " >> return Just) <|>
(string "Capture " >> return (const Nothing))
many1 digit
char ' '
es <- extras
case filter ('%' `elem`) es of
[volume] -> return . f . (,) (read (init volume) :: Double) $ case ("off" `elem` es, "on" `elem` es) of
(False, False) -> Nothing
(mute, _) -> Just mute
_ -> fail "no percentage-volume found in playback section"
extras :: Parser [String]
extras = sepBy' (char '[' >> upTo ']') (char ' ')
sepBy' :: Parser a -> Parser b -> Parser [a]
sepBy' p sep = liftM2 (:) p loop where
loop = (sep >> (liftM2 (:) p loop <|> return [])) <|> return []
osdCat :: MonadIO m => Double -> (Bool -> String) -> m ()
osdCat vol opts = do
m <- getMute
spawn $ "osd_cat -b percentage -P " ++ show (truncate vol :: Integer) ++ opts m
defaultOSDOpts :: Bool -> String
defaultOSDOpts mute = "--align=center --pos=top --delay=1 --text=\"Volume" ++
(if mute then "[muted]\" " else "\" ") ++
"--font='-bitstream-bitstream vera sans-bold-r-*-*-10-*-*-*-*-*-*-*' " ++
"--outline=1"