{-# LANGUAGE PatternGuards, Rank2Types #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Prompt.MPD -- Copyright : Daniel Schoepe -- License : BSD3-style (see LICENSE) -- -- Maintainer : Daniel Schoepe -- Stability : unstable -- Portability : unportable -- -- This module lets you select songs and add/play songs with MPD by filtering -- them by user-supplied criteria(E.g. ask for an artist, then for the album..) -- ----------------------------------------------------------------------------- -- TODO: Add other backends besides mpd(e.g. amarok via dcop). module XMonad.Prompt.MPD (-- * Usage -- $usage findMatching ,addMatching ,addAndPlay ,RunMPD ,findOrAdd ) where import Control.Monad import Control.Applicative import Data.Char import Network.MPD import System.IO import XMonad import XMonad.Prompt import XMonad.Prompt.Input import Data.List (nub,isPrefixOf,findIndex) -- $usage -- -- To use this, import the following modules: -- -- > import XMonad.Prompt.MPD -- > import qualified Network.MPD as MPD -- -- You can then use this in a keybinding, to filter first by artist, then by album and add -- the matching songs: -- -- > addMatching MPD.withMPD defaultXPConfig [MPD.sgArtist, MPD.sgAlbum] >> return () -- -- If you need a password to connect to your MPD or need a different host/port, you can pass a -- partially applied withMPDEx to the function: -- -- > addMatching (MPD.withMPDEx "your.host" 666 (return $ Just $ "password")) .. -- -- | Allows the user to supply a custom way to connect to MPD (e.g. partially applied withMPDEx). type RunMPD = forall a . MPD a -> IO (Response a) -- | Creates a case-insensitive completion function from a list. mkComplLst :: [String] -> String -> IO [String] mkComplLst lst s = return . filter (\s' -> map toLower s `isPrefixOf` map toLower s') $ lst findMatching' :: XPConfig -> [Song] -> (Song -> String) -> X [Song] findMatching' xp s m = do Just input <- inputPromptWithCompl xp "MPD" (mkComplLst . nub . map m $ s) return $ filter ((==input) . m) s -- | Lets the user filter out non-matching with a prompt by supplied criteria. findMatching :: RunMPD -> XPConfig -> [Song -> String] -> X [Song] findMatching runMPD xp ms = do -- rs <- io (runMPD $ rights `fmap` listAllInfo "") -- workaround due to a bug in libmpd(reported and fixed in darcs). -- should be replaced by the line above when a new version is released. -- (last version with this bug: 0.3.1) rs <- io . runMPD . fmap concat $ mapM findArtist =<< listArtists case rs of Left e -> io (hPutStrLn stderr $ "MPD error: " ++ show e) >> return [] Right s -> foldM (findMatching' xp) s ms -- | Determine playlist position of the song and add it, if it isn't present. findOrAdd :: Song -> MPD PLIndex findOrAdd s = playlistInfo Nothing >>= \pl -> do case findIndex ((== sgFilePath s) . sgFilePath) pl of Just i -> return . Pos . fromIntegral $ i Nothing -> ID <$> (addId . sgFilePath $ s) -- | Add all selected songs to the playlist if they are not on it. addMatching :: RunMPD -> XPConfig -> [Song -> String] -> X [PLIndex] addMatching r xp ms = findMatching r xp ms >>= fmap (either (const []) id) . io . r . mapM findOrAdd -- | Add matching songs and play the first one. addAndPlay :: RunMPD -> XPConfig -> [Song -> String] -> X () addAndPlay r xp ms = addMatching r xp ms >>= io . r . play . Just . head >> return ()