{-# LANGUAGE OverloadedStrings #-} -- | -- Module : Network.DBus.Actions -- License : BSD-style -- Maintainer : Vincent Hanquez -- Stability : experimental -- Portability : unknown -- module Network.DBus.Actions ( DBusContext , DBusTransport(..) , authenticate , authenticateUID , connectSession , connectSystem , contextNew , contextNewWith , busGetSession , busGetSystem , busGetNextSerial , busClose , messageSend , messageSendWithSerial , messageRecv -- * from Message module , MessageType(..) , MessageFlag(..) , DBusFields(..) , DBusMessage(..) , Serial -- * read a message body , readBody , readBodyWith -- * from Signature module , Type(..) , SignatureElem , Signature , serializeSignature , unserializeSignature -- * from Type module , ObjectPath(..) , PackedString(..) , packedStringToString , DBusValue(..) , DBusTypeable(..) ) where import Numeric (showHex) import Data.Char (ord) import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as BC import Control.Arrow import Control.Applicative ((<$>)) import Control.Concurrent import Control.Monad.State import System.Environment import System.IO hiding (hGetLine) import Network.Socket import Network.DBus.Message import Network.DBus.Type import Network.DBus.Internal import Network.DBus.Signature data DBusTransport = DBusTransport { transportGet :: Int -> IO ByteString , transportPut :: ByteString -> IO () , transportClose :: IO () } data DBusContext = DBusContext { contextTransport :: DBusTransport , contextSerial :: MVar Serial } withTransport :: DBusContext -> (DBusTransport -> IO a) -> IO a withTransport ctx f = f $ contextTransport ctx transportHandle :: Handle -> DBusTransport transportHandle h = DBusTransport { transportGet = BC.hGet h , transportPut = BC.hPut h , transportClose = hClose h } hGet :: DBusContext -> Int -> IO ByteString hGet ctx i = withTransport ctx (\t -> transportGet t i) hPut :: DBusContext -> ByteString -> IO () hPut ctx b = withTransport ctx (\t -> transportPut t b) hPuts :: DBusContext -> [ByteString] -> IO () hPuts ctx bs = withTransport ctx (\t -> mapM_ (transportPut t) bs) hGetLine :: DBusContext -> IO () hGetLine ctx = withTransport ctx getTillEOL where getTillEOL transport = do v <- transportGet transport 1 if BC.singleton '\n' == v then return () else getTillEOL transport -- | authenticate to DBus using a UID. authenticateUID :: DBusContext -> Int -> IO () authenticateUID ctx uid = authenticate ctx hexencoded_uid where hexencoded_uid = BC.pack $ concatMap (hex2 . ord) $ show uid hex2 a | a < 0x10 = '0' : showHex a "" | otherwise = showHex a "" -- | authenticate to DBus using a raw bytestring. authenticate :: DBusContext -> ByteString -> IO () authenticate ctx auth = do hPut ctx $ BC.concat ["\0AUTH EXTERNAL ", auth, "\r\n"] _ <- hGetLine ctx hPut ctx "BEGIN\r\n" close :: DBusTransport -> IO () close = transportClose connectUnix :: ByteString -> IO Handle connectUnix addr = do let sockaddr = SockAddrUnix $ BC.unpack addr sock <- socket AF_UNIX Stream 0 connect sock sockaddr h <- socketToHandle sock ReadWriteMode hSetBuffering h NoBuffering return h connectOver :: ByteString -> [(ByteString, ByteString)] -> IO Handle connectOver "unix" flags = do let abstract = lookup "abstract" flags case abstract of Nothing -> error "no abstract path, use the normal path ..." Just path -> connectUnix $ BC.concat ["\x00", path] connectOver _ _ = error "not implemented yet" connectSessionAt :: ByteString -> IO Handle connectSessionAt addr = do let (domain, flagstr) = second BC.tail $ BC.breakSubstring ":" addr let flags = map (\x -> let (k:v:[]) = BC.split '=' x in (k,v)) $ BC.split ',' flagstr connectOver domain flags -- | connect to the dbus session bus define by the environment variable DBUS_SESSION_BUS_ADDRESS connectSession :: IO Handle connectSession = BC.pack <$> getEnv "DBUS_SESSION_BUS_ADDRESS" >>= connectSessionAt -- | connect to the dbus system bus connectSystem :: IO Handle connectSystem = connectUnix "/var/run/dbus/system_bus_socket" -- | create a new DBus context from an handle contextNew :: Handle -> IO DBusContext contextNew h = contextNewWith (transportHandle h) -- | create a new DBus context from a transport contextNewWith :: DBusTransport -> IO DBusContext contextNewWith transport = liftM (DBusContext transport) (newMVar 1) -- | create a new DBus context on session bus busGetSession :: IO DBusContext busGetSession = connectSession >>= contextNew -- | create a new DBus context on system bus busGetSystem :: IO DBusContext busGetSystem = connectSystem >>= contextNew -- | close this DBus context busClose :: DBusContext -> IO () busClose = transportClose . contextTransport -- | get the next serial usable, and increment the serial state. busGetNextSerial :: DBusContext -> IO Serial busGetNextSerial ctx = modifyMVar (contextSerial ctx) (\v -> return $! (v+1, v)) -- | send one message to the bus with a predefined serial number. messageSendWithSerial :: DBusContext -> Serial -> DBusMessage -> IO () messageSendWithSerial ctx serial msg = do let fieldstr = writeFields (msgFields msg) let fieldlen = BC.length fieldstr let alignfields = alignVal 8 fieldlen - fieldlen let header = (headerFromMessage msg) { headerBodyLength = BC.length $ msgBodyRaw msg , headerFieldsLength = fieldlen , headerSerial = serial } hPuts ctx [ writeHeader header, fieldstr, BC.replicate alignfields '\0', msgBodyRaw msg ] -- | send one message to the bus -- note that the serial of the message sent is allocated here. messageSend :: DBusContext -> DBusMessage -> IO Serial messageSend ctx msg = do serial <- busGetNextSerial ctx messageSendWithSerial ctx serial msg return serial -- | receive one single message from the bus -- it is not necessarily the reply from a previous sent message. messageRecv :: DBusContext -> IO DBusMessage messageRecv ctx = do hdr <- readHeader <$> hGet ctx 16 fields <- readFields (headerEndian hdr) <$> hGet ctx (alignVal 8 $ headerFieldsLength hdr) body <- hGet ctx (headerBodyLength hdr) return $ (messageFromHeader hdr) { msgFields = fields, msgBodyRaw = body } alignVal :: Int -> Int -> Int alignVal n x | x `mod` n == 0 = x | otherwise = x + (n - (x `mod` n))