{-# LANGUAGE PatternGuards, Rank2Types #-}
-----------------------------------------------------------------------------
-- |
-- Module : XMonad.Prompt.MPD
-- Copyright : Daniel Schoepe <daniel.schoepe@gmail.com>
-- License : BSD3-style (see LICENSE)
--
-- Maintainer : Daniel Schoepe <daniel.schoepe@gmail.com>
-- 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 ()