1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
{-# LANGUAGE DeriveGeneric, OverloadedLists, OverloadedStrings, ViewPatterns, ExistentialQuantification, MultiWayIf #-}
module XMonad.Mpv
( MpvCommand(..), MpvResponse(..), MpvException(..)
, mpv
, mpvDir
, mpvAll, mpvOne
, mpvResponse
) where
import Data.Aeson
import Data.Monoid
import Network.Socket hiding (recv)
import Network.Socket.ByteString
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as CBS
import qualified Data.ByteString.Lazy as LBS
import GHC.Generics (Generic)
import Data.Typeable (Typeable)
import Data.String (IsString(..))
import Control.Exception
import System.IO.Temp (getCanonicalTemporaryDirectory)
import Control.Monad
import Control.Exception (bracket)
import Control.Monad.IO.Class (MonadIO(..))
import System.FilePath
import System.Directory (getDirectoryContents)
import Data.List
import Data.Either
import Data.Maybe
import Debug.Trace
data MpvCommand
= forall a. ToJSON a => MpvSetProperty String a
| MpvGetProperty String
data MpvResponse
= MpvError String
| MpvSuccess (Maybe Value)
deriving (Read, Show, Generic, Eq)
data MpvException = MpvException String
| MpvNoValue
| MpvNoParse String
deriving (Generic, Typeable, Read, Show)
instance Exception MpvException
instance ToJSON MpvCommand where
toJSON (MpvSetProperty name val) = Array ["set_property", fromString name, toJSON val]
toJSON (MpvGetProperty name) = Array ["get_property", fromString name]
instance FromJSON MpvResponse where
parseJSON = withObject "response object" $ \obj -> do
mval <- obj .:? "data"
err <- obj .: "error"
let ret
| err == "success" = MpvSuccess mval
| otherwise = MpvError err
return ret
mpvSocket :: FilePath -> (Socket -> IO a) -> IO a
mpvSocket sockPath = withSocketsDo . bracket mkSock close
where
mkSock = do
sock <- socket AF_UNIX Stream defaultProtocol
connect sock $ SockAddrUnix (traceId sockPath)
return sock
mpvResponse :: FromJSON v => MpvResponse -> IO v
mpvResponse (MpvError str) = throwIO $ MpvException str
mpvResponse (MpvSuccess Nothing) = throwIO MpvNoValue
mpvResponse (MpvSuccess (Just v)) = case fromJSON v of
Success v' -> return v'
Error str -> throwIO $ MpvNoParse str
mpv :: FilePath -> MpvCommand -> IO MpvResponse
mpv sockPath cmd = mpvSocket sockPath $ \sock -> do
let message = (`BS.append` "\n") . LBS.toStrict . encode $ Object [("command", toJSON cmd)]
traceIO $ show message
sendAll sock message
let recvAll = do
prefix <- recv sock 4096
if
| (prefix', rest) <- CBS.break (== '\n') prefix
, not (BS.null rest) -> return prefix'
| BS.null prefix -> return prefix
| otherwise -> BS.append prefix <$> recvAll
response <- recvAll
traceIO $ show response
either (ioError . userError) return . traceShowId $ eitherDecodeStrict' response
mpvDir :: Exception e => FilePath -> (FilePath -> [(FilePath, Either e MpvResponse)] -> Maybe MpvCommand) -> IO [(FilePath, Either e MpvResponse)]
mpvDir dir step = do
socks <- filter (".sock" `isSuffixOf`) <$> getDirectoryContents dir
go [] socks
where
go acc [] = return acc
go acc (sock:socks)
| Just cmd <- step sock acc = do
res <- try $ mpv (dir </> sock) cmd
go ((sock, res) : acc) socks
| otherwise =
go acc socks
mpvAll :: FilePath -> MpvCommand -> IO [MpvResponse]
mpvAll dir cmd = do
results <- map snd <$> (mpvDir dir (\_ _ -> Just cmd) :: IO [(FilePath, Either SomeException MpvResponse)])
mapM (either throwIO return) results
mpvOne :: FilePath -> MpvCommand -> IO (Maybe MpvResponse)
mpvOne dir cmd = listToMaybe . snd . partitionEithers . map snd <$> (mpvDir dir step :: IO [(FilePath, Either SomeException MpvResponse)])
where
step _ results
| any (isRight . snd) results = Nothing
| otherwise = Just cmd
|