packages feed

dbus-core 0.7 → 0.8

raw patch · 18 files changed

+1017/−338 lines, 18 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- DBus.Types: mkBusName' :: Text -> BusName
- DBus.Types: mkErrorName' :: Text -> ErrorName
- DBus.Types: mkInterfaceName' :: Text -> InterfaceName
- DBus.Types: mkMemberName' :: Text -> MemberName
- DBus.Types: mkObjectPath' :: Text -> ObjectPath
- DBus.Types: mkSignature' :: Text -> Signature
+ DBus.Authentication: AuthenticationError :: Text -> AuthenticationError
+ DBus.Authentication: Mechanism :: ((Command -> IO Command) -> IO UUID) -> Mechanism
+ DBus.Authentication: authenticate :: Mechanism -> (ByteString -> IO ()) -> IO Word8 -> IO UUID
+ DBus.Authentication: data AuthenticationError
+ DBus.Authentication: instance Exception AuthenticationError
+ DBus.Authentication: instance Show AuthenticationError
+ DBus.Authentication: instance Typeable AuthenticationError
+ DBus.Authentication: mechanismRun :: Mechanism -> (Command -> IO Command) -> IO UUID
+ DBus.Authentication: newtype Mechanism
+ DBus.Authentication: realUserID :: Mechanism
+ DBus.Authentication: type Command = Text
+ DBus.Connection: connectionAddress :: Connection -> Address
+ DBus.Connection: connectionUUID :: Connection -> UUID
+ DBus.MatchRule: instance Show MatchRule
+ DBus.Message: firstSerial :: Serial
+ DBus.Message: nextSerial :: Serial -> Serial
+ DBus.Types: instance IsString BusName
+ DBus.Types: instance IsString ErrorName
+ DBus.Types: instance IsString InterfaceName
+ DBus.Types: instance IsString MemberName
+ DBus.Types: instance IsString ObjectPath
+ DBus.Types: instance IsString Signature
+ DBus.Types: mkBusName_ :: Text -> BusName
+ DBus.Types: mkErrorName_ :: Text -> ErrorName
+ DBus.Types: mkInterfaceName_ :: Text -> InterfaceName
+ DBus.Types: mkMemberName_ :: Text -> MemberName
+ DBus.Types: mkObjectPath_ :: Text -> ObjectPath
+ DBus.Types: mkSignature_ :: Text -> Signature
+ DBus.UUID: data UUID
+ DBus.UUID: fromHex :: Text -> Maybe UUID
+ DBus.UUID: instance Eq UUID
+ DBus.UUID: instance Show UUID
+ DBus.UUID: toHex :: UUID -> Text
- DBus.Bus: getBus :: Address -> IO (Connection, BusName)
+ DBus.Bus: getBus :: Mechanism -> Address -> IO (Connection, BusName)
- DBus.Bus: getFirstBus :: [Address] -> IO (Connection, BusName)
+ DBus.Bus: getFirstBus :: [(Mechanism, Address)] -> IO (Connection, BusName)
- DBus.Connection: connect :: Address -> IO Connection
+ DBus.Connection: connect :: Mechanism -> Address -> IO Connection
- DBus.Connection: connectFirst :: [Address] -> IO Connection
+ DBus.Connection: connectFirst :: [(Mechanism, Address)] -> IO Connection

Files

Examples/dbus-monitor.hs view
@@ -18,11 +18,13 @@ {-# LANGUAGE OverloadedStrings #-}  import DBus.Address+import DBus.Authentication import DBus.Bus import DBus.Connection import DBus.Constants import DBus.Message import DBus.Types+import DBus.Wire  import Control.Monad import qualified Data.Set as Set@@ -61,18 +63,19 @@ 	BusOption Session -> getSessionBus 	BusOption System  -> getSystemBus 	AddressOption addr -> case mkAddresses (TL.pack addr) of-			Just [x] -> getBus x-			Just  x  -> getFirstBus x+			Just [x] -> getBus realUserID x+			Just  xs -> getFirstBus [(realUserID, addr) | addr <- xs] 			_        -> error $ "Invalid address: " ++ show addr  addMatchMsg :: String -> MethodCall addMatchMsg match = MethodCall-	dbusPath-	(mkMemberName' "AddMatch")-	(Just dbusInterface)-	(Just dbusName)-	Set.empty-	[toVariant match]+	{ methodCallPath = dbusPath+	, methodCallMember = "AddMatch"+	, methodCallInterface = Just dbusInterface+	, methodCallDestination = Just dbusName+	, methodCallFlags = Set.empty+	, methodCallBody = [toVariant match]+	}  addMatch :: Connection -> String -> IO () addMatch c s = send c return (addMatchMsg s) >> return ()
+ Examples/simple.hs view
@@ -0,0 +1,64 @@+-- Copyright (C) 2009 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.++{-# LANGUAGE OverloadedStrings #-}+module Main (main) where++import DBus.Bus+import DBus.Connection+import DBus.Message+import DBus.Types+import qualified Data.Set as Set+import Data.List (sort)++main :: IO ()+main = do+	-- Connect to the bus, and print which name was assigned to this+	-- connection.+	(bus, name) <- getSessionBus+	putStrLn $ "Connected as: " ++ show name+	+	-- Request a list of connected clients from the bus+	Right serial <- send bus return $ MethodCall+		{ methodCallPath = "/org/freedesktop/DBus"+		, methodCallMember = "ListNames"+		, methodCallInterface = Just "org.freedesktop.DBus"+		, methodCallDestination = Just "org.freedesktop.DBus"+		, methodCallFlags = Set.empty+		, methodCallBody = []+		}+	+	-- Wait for the reply+	reply <- waitForReply bus serial+	+	-- Pull out the body, and convert it to [String]+	let Just names = fromArray =<< fromVariant (messageBody reply !! 0)+	+	-- Print each name on a line, sorted so reserved names are below+	-- temporary names.+	mapM_ putStrLn $ sort names++waitForReply :: Connection -> Serial -> IO MethodReturn+waitForReply bus serial = wait where+	wait = do+		received <- receive bus+		case received of+			Right (ReceivedMethodReturn _ _ ret) ->+				if methodReturnSerial ret == serial+					then return ret+					else wait+			Right _ -> wait+			Left err -> error $ show err+
Makefile view
@@ -1,5 +1,6 @@ HS_SOURCES=\ 	hs/DBus/Address.hs \+	hs/DBus/Authentication.hs \ 	hs/DBus/Bus.hs \ 	hs/DBus/Connection.hs \ 	hs/DBus/Constants.hs \@@ -10,6 +11,7 @@ 	hs/DBus/NameReservation.hs \ 	hs/DBus/Types.hs \ 	hs/DBus/Util.hs \+	hs/DBus/UUID.hs \ 	hs/DBus/Wire.hs \ 	hs/DBus/Wire/Internal.hs \ 	hs/Tests.hs
Tests.nw view
@@ -128,7 +128,7 @@ 	arbitrary = oneof [atomicType, containerType]  instance Arbitrary Signature where-	arbitrary = clampedSize 255 genSig mkSignature' where+	arbitrary = clampedSize 255 genSig mkSignature_ where 		genSig = fmap (TL.concat . map typeCode) arbitrary  <<atom tests>>=@@ -141,7 +141,7 @@  <<Tests.hs>>= instance Arbitrary ObjectPath where-	arbitrary = fmap (mkObjectPath' . TL.pack) path' where+	arbitrary = fmap (mkObjectPath_ . TL.pack) path' where 		c = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_" 		path = fmap (intercalate "/" . ([] :)) genElements 		path' = frequency [(1, return "/"), (9, path)]@@ -155,7 +155,7 @@  <<Tests.hs>>= instance Arbitrary BusName where-	arbitrary = clampedSize 255 (oneof [unique, wellKnown]) mkBusName' where+	arbitrary = clampedSize 255 (oneof [unique, wellKnown]) mkBusName_ where 		c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-" 		c' = c ++ ['0'..'9'] 		@@ -180,7 +180,7 @@  <<Tests.hs>>= instance Arbitrary InterfaceName where-	arbitrary = clampedSize 255 genName mkInterfaceName' where+	arbitrary = clampedSize 255 genName mkInterfaceName_ where 		c = ['a'..'z'] ++ ['A'..'Z'] ++ "_" 		c' = c ++ ['0'..'9'] 		@@ -199,7 +199,7 @@  <<Tests.hs>>= instance Arbitrary ErrorName where-	arbitrary = fmap (mkErrorName' . strInterfaceName) arbitrary+	arbitrary = fmap (mkErrorName_ . strInterfaceName) arbitrary  <<atom tests>>= , testGroup "ErrorName" $ commonVariantTests (arbitrary :: Gen ErrorName) ++@@ -209,7 +209,7 @@  <<Tests.hs>>= instance Arbitrary MemberName where-	arbitrary = clampedSize 255 genName mkMemberName' where+	arbitrary = clampedSize 255 genName mkMemberName_ where 		c = ['a'..'z'] ++ ['A'..'Z'] ++ "_" 		c' = c ++ ['0'..'9'] 		@@ -562,7 +562,7 @@ 	let path' = case strObjectPath parentPath of 		"/" -> thisPath 		x   -> TL.append x thisPath-	let path = mkObjectPath' path'+	let path = mkObjectPath_ path' 	ifaces <- arbitrary 	children <- shrinkingGen . listOf . subObject $ path 	return $ I.Object path ifaces children
dbus-core.cabal view
@@ -1,7 +1,7 @@ name: dbus-core-version: 0.7+version: 0.8 synopsis: Low-level D-Bus protocol implementation-license: GPL+license: GPL-3 license-file: License.txt author: John Millikin maintainer: jmillikin@gmail.com@@ -10,6 +10,8 @@ category: Network, Desktop stability: experimental bug-reports: mailto:jmillikin@gmail.com+homepage: http://ianen.org/haskell/dbus/+tested-with: GHC==6.10.4  extra-source-files:   dbus-core.nw@@ -19,7 +21,7 @@  source-repository head   type: darcs-  location: http://patch-tag.com/r/jmillikin/dbus-core/pullrepo+  location: http://ianen.org/haskell/dbus/core/  flag test   default: False@@ -32,21 +34,22 @@   hs-source-dirs: hs    build-depends:-    base >=4 && < 5,-    parsec >= 3.0.0,-    binary,-    bytestring,-    data-binary-ieee754 >= 0.3,-    HaXml >= 1.19.7,-    pretty,-    text,-    mtl,-    containers,-    unix,-    network+      base >=4 && < 5+    , parsec >= 3.0.0+    , binary+    , bytestring+    , data-binary-ieee754 >= 0.3+    , HaXml >= 1.19.7+    , pretty+    , text+    , mtl+    , containers+    , unix+    , network    exposed-modules:     DBus.Address+    DBus.Authentication     DBus.Bus     DBus.Connection     DBus.Constants@@ -55,6 +58,7 @@     DBus.Message     DBus.NameReservation     DBus.Types+    DBus.UUID     DBus.Wire    other-modules:
dbus-core.nw view
@@ -127,6 +127,7 @@ 	deriving (Show, Eq)  <<DBus/Types.hs>>=+<<apidoc isAtomicType>> isAtomicType :: Type -> Bool isAtomicType DBusBoolean    = True isAtomicType DBusByte       = True@@ -146,7 +147,9 @@ signatures'' or for debugging.  <<DBus/Types.hs>>=+<<apidoc typeCode>> typeCode :: Type -> Text+<<type codes>>  <<type exports>>=   -- * Available types@@ -182,6 +185,7 @@ pre-defined types.  <<DBus/Types.hs>>=+<<apidoc Variant>> data Variant = forall a. (Variable a, Builtin a, Typeable a) => Variant a 	deriving (Typeable) @@ -217,6 +221,7 @@ a {\tt Variant}.  <<DBus/Types.hs>>=+<<apidoc variantType>> variantType :: Variant -> Type variantType (Variant x) = builtinDBusType x @@ -362,7 +367,7 @@ @ For atomic types, the type code is a single letter. Arrays, structures, and dictionary types are multiple characters. -<<DBus/Types.hs>>=+<<type codes>>= typeCode DBusBoolean    = "b" typeCode DBusByte       = "y" typeCode DBusInt16      = "n"@@ -380,20 +385,20 @@ @ An array's type code is ``a'' followed by the type it contains. For example, an array of booleans would have the type string ``ab''. -<<DBus/Types.hs>>=+<<type codes>>= typeCode (DBusArray t) = TL.cons 'a' $ typeCode t  @ A dictionary's type code is ``a\{$key\_type$ $value\_type$\}''. For example, a dictionary of bytes to booleans would have the type string ``a\{yb\}''. -<<DBus/Types.hs>>=+<<type codes>>= typeCode (DBusDictionary k v) = TL.concat ["a{", typeCode k, typeCode v, "}"]  @ A structure's type code is the concatenation of its contained types, wrapped by ``('' and ``)''. Structures may be empty, in which case their type code is simply ``()''. -<<DBus/Types.hs>>=+<<type codes>>= typeCode (DBusStructure ts) = TL.concat $ 	["("] ++ map typeCode ts ++ [")"] @@ -463,16 +468,23 @@ import DBus.Util (mkUnsafe)  <<DBus/Types.hs>>=-mkSignature' :: Text -> Signature-mkSignature' = mkUnsafe "signature" mkSignature+mkSignature_ :: Text -> Signature+mkSignature_ = mkUnsafe "signature" mkSignature +<<type imports>>=+import qualified Data.String as String++<<DBus/Types.hs>>=+instance String.IsString Signature where+	fromString = mkSignature_ . TL.pack+ @ Most signature-related functions are exposed to clients, except the {\tt Signature} value constructor. If that were exposed, clients could construct invalid signatures.  <<type exports>>= , mkSignature-, mkSignature'+, mkSignature_  @ \subsection{Object paths} @@ -481,8 +493,15 @@ newtype ObjectPath = ObjectPath 	{ strObjectPath :: Text 	}-	deriving (Show, Eq, Ord, Typeable)+	deriving (Eq, Ord, Typeable) +instance Show ObjectPath where+	showsPrec d (ObjectPath x) = showParen (d > 10) $+		showString "ObjectPath " . shows x++instance String.IsString ObjectPath where+	fromString = mkObjectPath_ . TL.pack+ @ An object path may be one of  \begin{itemize}@@ -502,15 +521,15 @@ 	path = P.char '/' >>= P.optional . P.sepBy (P.many1 c) . P.char 	path' = path >> P.eof >> return (ObjectPath s) -mkObjectPath' :: Text -> ObjectPath-mkObjectPath' = mkUnsafe "object path" mkObjectPath+mkObjectPath_ :: Text -> ObjectPath+mkObjectPath_ = mkUnsafe "object path" mkObjectPath  <<type exports>>=   -- * Object paths , ObjectPath , strObjectPath , mkObjectPath-, mkObjectPath'+, mkObjectPath_  @ \subsection{Arrays} @@ -535,6 +554,7 @@ instance Builtin Array where 	builtinDBusType = DBusArray . arrayType +<<apidoc arrayType>> arrayType :: Array -> Type arrayType (VariantArray t _) = t arrayType (ByteArray _) = DBusByte@@ -778,14 +798,22 @@ <<DBus/Types.hs>>= #define NAME_TYPE(TYPE, NAME) \ 	newtype TYPE = TYPE {str##TYPE :: Text} \-		deriving (Show, Eq, Ord); \-		                          \+		deriving (Eq, Ord); \+		\+	instance Show TYPE where \+		{ showsPrec d (TYPE x) = showParen (d > 10) $ \+			showString "TYPE " . shows x \+		}; \+		\+	instance String.IsString TYPE where \+		{ fromString = mk##TYPE##_ . TL.pack }; \+		\ 	instance Variable TYPE where \ 		{ toVariant = toVariant . str##TYPE \ 		; fromVariant = (mk##TYPE =<<) . fromVariant }; \ 		                                                \-	mk##TYPE##' :: Text -> TYPE; \-	mk##TYPE##' = mkUnsafe NAME mk##TYPE+	mk##TYPE##_ :: Text -> TYPE; \+	mk##TYPE##_ = mkUnsafe NAME mk##TYPE  <<type exports>>= -- * Names@@ -820,7 +848,7 @@ , BusName , strBusName , mkBusName-, mkBusName'+, mkBusName_  @ \subsubsection{Interface names} @@ -844,7 +872,7 @@ , InterfaceName , strInterfaceName , mkInterfaceName-, mkInterfaceName'+, mkInterfaceName_  @ \subsubsection{Error names} @@ -862,7 +890,7 @@ , ErrorName , strErrorName , mkErrorName-, mkErrorName'+, mkErrorName_  @ \subsubsection{Member names} @@ -884,7 +912,7 @@ , MemberName , strMemberName , mkMemberName-, mkMemberName'+, mkMemberName_  @ \section{Messages}@@ -957,6 +985,7 @@ added type-safety.  <<DBus/Message/Internal.hs>>=+<<apidoc Serial>> newtype Serial = Serial { serialValue :: Word32 } 	deriving (Eq, Ord) @@ -982,6 +1011,8 @@ <<message exports>>= , Serial , serialValue+, firstSerial+, nextSerial  @ \subsection{Message types} @@ -1133,6 +1164,7 @@ for sending an error reply.  <<DBus/Message/Internal.hs>>=+<<apidoc ReceivedMessage>> data ReceivedMessage 	= ReceivedMethodCall   Serial (Maybe T.BusName) MethodCall 	| ReceivedMethodReturn Serial (Maybe T.BusName) MethodReturn@@ -1875,6 +1907,7 @@ , marshalMessage  <<DBus/Wire/Internal.hs>>=+<<apidoc marshalMessage>> marshalMessage :: M.Message a => Endianness -> M.Serial -> a                -> Either MarshalError L.ByteString marshalMessage e serial msg = runMarshal marshaler e where@@ -1933,6 +1966,7 @@ , unmarshalMessage  <<DBus/Wire/Internal.hs>>=+<<apidoc unmarshalMessage>> unmarshalMessage :: Monad m => (Word32 -> m L.ByteString)                  -> m (Either UnmarshalError M.ReceivedMessage) unmarshalMessage getBytes' = E.runErrorT $ do@@ -1947,7 +1981,7 @@ retrieved without any size calculations.  <<read fixed-length header>>=-let fixedSig = T.mkSignature' "yyyyuuu"+let fixedSig = "yyyyuuu" fixedBytes <- getBytes 16  @ The first field of interest is the protocol version; if the incoming@@ -1991,7 +2025,7 @@ pulled out of the monad.  <<read full header>>=-let headerSig  = T.mkSignature' "yyyyuua(yv)"+let headerSig  = "yyyyuua(yv)" fieldBytes <- getBytes fieldByteCount let headerBytes = L.append fixedBytes fieldBytes header <- unmarshal' headerSig headerBytes@@ -2012,8 +2046,7 @@  <<DBus/Wire/Internal.hs>>= findBodySignature :: [M.HeaderField] -> T.Signature-findBodySignature fields = fromMaybe empty signature where-	empty = T.mkSignature' ""+findBodySignature fields = fromMaybe "" signature where 	signature = listToMaybe [x | M.Signature x <- fields]  <<read body>>=@@ -2028,10 +2061,21 @@ @ Even if the received message was structurally valid, building the {\tt ReceivedMessage} can still fail due to missing header fields. +Slightly ugly; to avoid orphan instances of either {\tt Text} or+{\tt Either}, a newtype is used to turn {\tt Either} into a monad.++<<DBus/Wire/Internal.hs>>=+newtype EitherM a b = EitherM (Either a b)++instance Monad (EitherM a) where+	return = EitherM . Right+	(EitherM (Left x)) >>= _ = EitherM (Left x)+	(EitherM (Right x)) >>= k = k x+ <<build message>>= y <- case buildReceivedMessage typeCode fields of-	Right x -> return x-	Left x -> E.throwError $ MissingHeaderField x+	EitherM (Right x) -> return x+	EitherM (Left x) -> E.throwError $ MissingHeaderField x  <<build message>>= return $ y serial flags body@@ -2039,7 +2083,7 @@ @ This really belongs in the Message section...  <<DBus/Wire/Internal.hs>>=-buildReceivedMessage :: Word8 -> [M.HeaderField] -> Either Text +buildReceivedMessage :: Word8 -> [M.HeaderField] -> EitherM Text                         (M.Serial -> (Set.Set M.Flag) -> [T.Variant]                          -> M.ReceivedMessage) @@ -2101,15 +2145,9 @@ 	in M.ReceivedUnknown serial sender msg  <<DBus/Wire/Internal.hs>>=-require :: Text -> [a] -> Either Text a-require _     (x:_) = Right x-require label _     = Left label--@ This is just needed for the Monad instance of {\tt Either Text}--<<DBus/Wire/Internal.hs>>=-instance E.Error Text where-	strMsg = TL.pack+require :: Text -> [a] -> EitherM Text a+require _     (x:_) = return x+require label _     = EitherM $ Left label  @ \section{Addresses}@@ -2253,33 +2291,44 @@ <<connection imports>>= import qualified Control.Concurrent as C import qualified DBus.Address as A-import qualified DBus.Message.Internal as M+import qualified DBus.Message as M+import qualified DBus.UUID as UUID  <<DBus/Connection.hs>>=-data Connection = Connection A.Address Transport (C.MVar M.Serial) (C.MVar ())+data Connection = Connection+	{ connectionAddress    :: A.Address+	, connectionTransport  :: Transport+	, connectionSerialMVar :: C.MVar M.Serial+	, connectionReadMVar   :: C.MVar ()+	, connectionUUID       :: UUID.UUID+	}  <<connection exports>>=   Connection+, connectionAddress+, connectionUUID  @ While not particularly useful for other functions, being able to {\tt show} a {\tt Connection} is useful when debugging.  <<DBus/Connection.hs>>= instance Show Connection where-	showsPrec d (Connection a _ _ _) = showParen (d > 10) $-		showString' ["<connection ", show $ A.strAddress a, ">"] where-		showString' = foldr (.) id . map showString+	showsPrec d con = showParen (d > 10) strCon where+		addr = A.strAddress $ connectionAddress con+		strCon = s "<Connection " . shows addr . s ">"+		s = showString  @ \subsection{Transports}  A transport is anything which can send and receive bytestrings, typically-over a socket.+via a socket.  <<connection imports>>= import qualified Data.ByteString.Lazy as L import Data.Word (Word32)  <<DBus/Connection.hs>>=+<<apidoc Transport>> data Transport = Transport 	{ transportSend :: L.ByteString -> IO () 	, transportRecv :: Word32 -> IO L.ByteString@@ -2473,28 +2522,30 @@ might fail due to external factors.  <<connection imports>>=-import Data.Text.Lazy.Encoding (decodeUtf8, encodeUtf8)+import qualified DBus.Authentication as Auth  <<DBus/Connection.hs>>=-connect :: A.Address -> IO Connection-connect a = do+<<apidoc connect>>+connect :: Auth.Mechanism -> A.Address -> IO Connection+connect mechanism a = do 	t <- connectTransport a-	let putS = transportSend t . encodeUtf8 . TL.pack-	let getS = fmap (TL.unpack . decodeUtf8) . transportRecv t-	authenticate putS getS+	let getByte = L.head `fmap` transportRecv t 1+	uuid <- Auth.authenticate mechanism (transportSend t) getByte 	readLock <- C.newMVar () 	serialMVar <- C.newMVar M.firstSerial-	return $ Connection a t serialMVar readLock+	return $ Connection a t serialMVar readLock uuid  @ Since addresses usually come in a list, it's sensible to have a variant of {\tt connect} which tries multiple addresses. The first successfully opened {\tt Connection} is returned.  <<DBus/Connection.hs>>=-connectFirst :: [A.Address] -> IO Connection+<<apidoc connectFirst>>+connectFirst :: [(Auth.Mechanism, A.Address)] -> IO Connection connectFirst orig = connectFirst' orig where-	connectFirst'     [] = E.throwIO $ NoWorkingAddress orig-	connectFirst' (a:as) = E.catch (connect a) $+	allAddrs = [a | (_, a) <- orig]+	connectFirst'     [] = E.throwIO $ NoWorkingAddress allAddrs+	connectFirst' ((mech, a):as) = E.catch (connect mech a) $ 		\(E.SomeException _) -> connectFirst' as  <<connection exports>>=@@ -2503,49 +2554,132 @@  @ \subsection{Authentication} -<<DBus/Connection.hs>>=-authenticate :: (String -> IO ()) -> (Word32 -> IO String)-                -> IO ()-authenticate put get = do-	put "\x00"+Authentication is a bit iffy; currently, there's a one specified+authentication mechanism ({\tt DBUS\_COOKIE\_SHA1}), which I've never seen+in the wild. Everybody seems to use {\tt EXTERNAL}, described below. -@ {\sc external} authentication is performed using the process's real user-ID, converted to a string, and then hex-encoded.+To support multiple modes more easily in the future, and for user-defined+authentication handling (eg, in proxy servers), authentication is handled by+a separate module. -<<connection imports>>=+<<DBus/Authentication.hs>>=+<<copyright>>+<<text extensions>>+{-# LANGUAGE DeriveDataTypeable #-}+module DBus.Authentication+	( <<authentication exports>>+	) where+<<text imports>>+<<authentication imports>>++@ The authentication protocol is based on {\sc ascii} text, initiated by the+client. Commands and mechanisms are represented by a couple data types:++<<authentication imports>>=+import Data.ByteString.Lazy (ByteString)+import qualified Data.ByteString.Lazy as ByteString+import qualified DBus.UUID as UUID++<<DBus/Authentication.hs>>=+type Command = Text+newtype Mechanism = Mechanism+	{ mechanismRun :: (Command -> IO Command) -> IO UUID.UUID+	}++<<authentication exports>>=+  Command+, Mechanism (..)++@ If authentication fails, an exception will be raised.++<<authentication imports>>=+import Data.Typeable (Typeable)+import qualified Control.Exception as E++<<DBus/Authentication.hs>>=+data AuthenticationError+	= AuthenticationError Text+	deriving (Show, Typeable)++instance E.Exception AuthenticationError++<<authentication exports>>=+, AuthenticationError (..)++@ The process begins by the client sending a single {\sc nul} byte, followed+by exchanges of {\sc ascii} commands until authentication is complete. Which+commands are sent depends on the selected mechanism.++<<authentication imports>>=+import Data.Word (Word8)++<<DBus/Authentication.hs>>=+authenticate :: Mechanism -> (ByteString -> IO ()) -> IO Word8+             -> IO UUID.UUID+authenticate mech put getByte = do+	put $ ByteString.singleton 0+	uuid <- mechanismRun mech (putCommand put getByte)+	put "BEGIN\r\n"+	return uuid++<<authentication exports>>=+, authenticate++@ TODO: describe {\tt putCommand} here++<<authentication imports>>=+import Control.Monad (liftM)+import Data.Char (chr)+import Data.Text.Lazy.Encoding (encodeUtf8)+import DBus.Util (readUntil, dropEnd)++<<DBus/Authentication.hs>>=+putCommand :: Monad m => (ByteString -> m ()) -> m Word8 -> Command -> m Command+putCommand put get cmd = do+	let getC = liftM (chr . fromIntegral) get+	put $ encodeUtf8 cmd+	put "\r\n"+	liftM (TL.pack . dropEnd 2) $ readUntil "\r\n" getC++@ \subsubsection{Support authentication mechanisms}++Currently, the only supported authentication mechanism is sending the local+user's ``real user ID''.++<<authentication exports>>=+, realUserID++<<authentication imports>>= import System.Posix.User (getRealUserID) import Data.Char (ord) import Text.Printf (printf) -<<DBus/Connection.hs>>=+<<DBus/Authentication.hs>>=+realUserID :: Mechanism+realUserID = Mechanism $ \sendCmd -> do 	uid <- getRealUserID-	let authToken = concatMap (printf "%02X" . ord) (show uid)-	put $ "AUTH EXTERNAL " ++ authToken ++ "\r\n"+	let token = concatMap (printf "%02X" . ord) (show uid)+	let cmd = "AUTH EXTERNAL " ++ token+	eitherUUID <- checkOK `fmap` sendCmd (TL.pack cmd)+	case eitherUUID of+		Right uuid -> return uuid+		Left err -> E.throwIO $ AuthenticationError err  @ If authentication was successful, the server responds with {\tt OK-<server GUID>}.  The GUID is intended to enable connection sharing, which-is currently unimplemented, so it's ignored.--<<connection imports>>=-import Data.List (isPrefixOf)+<server GUID>}. -<<DBus/Connection.hs>>=-	response <- readUntil '\n' get-	if "OK" `isPrefixOf` response-		then put "BEGIN\r\n"-		else do-			putStrLn $ "response = " ++ show response-			error "Server rejected authentication token."+<<authentication imports>>=+import Data.Maybe (isJust) -<<DBus/Connection.hs>>=-readUntil :: Monad m => Char -> (Word32 -> m String) -> m String-readUntil = readUntil' "" where-	readUntil' xs c f = do-		[x] <- f 1-		let xs' = xs ++ [x]-		if x == c-			then return xs'-			else readUntil' xs' c f+<<DBus/Authentication.hs>>=+checkOK :: Command -> Either Text UUID.UUID+checkOK cmd = if validUUID then Right uuid else Left errorMsg where+	validUUID = TL.isPrefixOf "OK " cmd && isJust maybeUUID+	maybeUUID = UUID.fromHex $ TL.drop 3 cmd+	Just uuid = maybeUUID+	errorMsg = if TL.isPrefixOf "ERROR " cmd+		then TL.drop 6 cmd+		else TL.pack $ "Unexpected response: " ++ show cmd  @ \subsection{Sending and receiving messages} @@ -2558,9 +2692,10 @@ import qualified DBus.Wire as W  <<DBus/Connection.hs>>=+<<apidoc send>> send :: M.Message a => Connection -> (M.Serial -> IO b) -> a      -> IO (Either W.MarshalError b)-send (Connection _ t mvar _) io msg = withSerial mvar $ \serial ->+send (Connection _ t mvar _ _) io msg = withSerial mvar $ \serial -> 	case W.marshalMessage W.LittleEndian serial msg of 		Right bytes -> do 			x <- io serial@@ -2584,8 +2719,9 @@ error is encountered while unmarshaling, an exception will be thrown.  <<DBus/Connection.hs>>=+<<apidoc receive>> receive :: Connection -> IO (Either W.UnmarshalError M.ReceivedMessage)-receive (Connection _ t _ lock) = C.withMVar lock $ \_ ->+receive (Connection _ t _ lock _) = C.withMVar lock $ \_ -> 	W.unmarshalMessage $ transportRecv t  <<connection exports>>=@@ -2613,6 +2749,7 @@ import System.Environment (getEnv)  import qualified DBus.Address as A+import qualified DBus.Authentication as Auth import qualified DBus.Connection as C import DBus.Constants (dbusName, dbusPath, dbusInterface) import qualified DBus.Message as M@@ -2628,14 +2765,16 @@ busForConnection c = sendHello c >>= return . (,) c  <<DBus/Bus.hs>>=-getBus :: A.Address -> IO (C.Connection, T.BusName)-getBus = (busForConnection =<<) . C.connect+<<apidoc getBus>>+getBus :: Auth.Mechanism -> A.Address -> IO (C.Connection, T.BusName)+getBus = ((busForConnection =<<) .) . C.connect  @ Optionally, multiple addresses may be provided. The first successful connection will be used.  <<DBus/Bus.hs>>=-getFirstBus :: [A.Address] -> IO (C.Connection, T.BusName)+<<apidoc getFirstBus>>+getFirstBus :: [(Auth.Mechanism, A.Address)] -> IO (C.Connection, T.BusName) getFirstBus = (busForConnection =<<) . C.connectFirst  @ \subsection{Default connections}@@ -2645,6 +2784,7 @@ of the user's session.  <<DBus/Bus.hs>>=+<<apidoc getSystemBus>> getSystemBus :: IO (C.Connection, T.BusName) getSystemBus = getBus' $ fromEnv `E.catch` noEnv where 	defaultAddr = "unix:path=/var/run/dbus/system_bus_socket"@@ -2652,10 +2792,12 @@ 	noEnv (E.SomeException _) = return defaultAddr  <<DBus/Bus.hs>>=+<<apidoc getSessionBus>> getSessionBus :: IO (C.Connection, T.BusName) getSessionBus = getBus' $ getEnv "DBUS_SESSION_BUS_ADDRESS"  <<DBus/Bus.hs>>=+<<apidoc getStarterBus>> getStarterBus :: IO (C.Connection, T.BusName) getStarterBus = getBus' $ getEnv "DBUS_STARTER_ADDRESS" @@ -2664,8 +2806,8 @@ getBus' io = do 	addr <- fmap TL.pack io 	case A.mkAddresses addr of-		Just [x] -> getBus x-		Just  x  -> getFirstBus x+		Just [x] -> getBus Auth.realUserID x+		Just  xs -> getFirstBus [(Auth.realUserID,x) | x <- xs] 		_        -> E.throwIO $ C.InvalidAddress addr  @ \subsection{Sending the ``hello'' message}@@ -2673,7 +2815,7 @@ <<DBus/Bus.hs>>= hello :: M.MethodCall hello = M.MethodCall dbusPath-	(T.mkMemberName' "Hello")+	"Hello" 	(Just dbusInterface) 	(Just dbusName) 	Set.empty@@ -3071,7 +3213,7 @@ @ \section{Match rules} -Match rules are used indicate that the client is interested in messages+Match rules are used to indicate that the client is interested in messages matching a particular filter. This module provides an interface for building match rule strings. Eventually, it will support parsing them also. @@ -3096,11 +3238,12 @@ import qualified DBus.Constants as C import DBus.Util (maybeIndex) -@ A match rule is a set of filters; most filters may have one possible-value assigned, such as a single message type. The exception are parameter-filters, which are limited in number only by the server implementation.  <<DBus/MatchRule.hs>>=+-- | A match rule is a set of filters; most filters may have one possible+-- value assigned, such as a single message type. The exception are parameter+-- filters, which are limited in number only by the server implementation.+--  data MatchRule = MatchRule 	{ matchType        :: Maybe MessageType 	, matchSender      :: Maybe T.BusName@@ -3110,25 +3253,26 @@ 	, matchDestination :: Maybe T.BusName 	, matchParameters  :: [ParameterValue] 	}--@ Parameters may match against two types, strings and object paths. It's-probably an error to have two values for the same parameter.--The constructor {\tt StringValue 3 "hello"} means that the fourth parameter-in the message body must be the string ``hello''. {\tt PathValue} is the-same, but its value must be an object path.+	deriving (Show)  <<DBus/MatchRule.hs>>=+-- | Parameters may match against two types, strings and object paths. It's+-- probably an error to have two values for the same parameter.+-- +-- The constructor @StringValue 3 \"hello\"@ means that the fourth parameter+-- in the message body must be the string @\"hello\"@. @PathValue@ is the+-- same, but its value must be an object path.+--  data ParameterValue 	= StringValue Word8 Text 	| PathValue Word8 T.ObjectPath 	deriving (Show, Eq) -@ The set of allowed message types to filter on is separate from the set-supported for sending over the wire. This allows the server to support-additional types not yet implemented in the library, or vice-versa.- <<DBus/MatchRule.hs>>=+-- | The set of allowed message types to filter on is separate from the set+-- supported for sending over the wire. This allows the server to support+-- additional types not yet implemented in the library, or vice-versa.+--  data MessageType 	= MethodCall 	| MethodReturn@@ -3140,6 +3284,7 @@ to format them.  <<DBus/MatchRule.hs>>=+<<apidoc formatRule>> formatRule :: MatchRule -> Text formatRule rule = TL.intercalate "," filters where 	filters = structureFilters ++ parameterFilters@@ -3180,10 +3325,11 @@ it's useful to have a message-building function pre-defined.  <<DBus/MatchRule.hs>>=+<<apidoc addMatch>> addMatch :: MatchRule -> M.MethodCall addMatch rule = M.MethodCall 	C.dbusPath-	(T.mkMemberName' "AddMatch")+	"AddMatch" 	(Just C.dbusInterface) 	(Just C.dbusName) 	Set.empty@@ -3193,6 +3339,7 @@ an empty rule allows clients to set only the fields they care about.  <<DBus/MatchRule.hs>>=+<<apidoc matchAll>> matchAll :: MatchRule matchAll = MatchRule 	{ matchType        = Nothing@@ -3208,6 +3355,7 @@ signals.  <<DBus/MatchRule.hs>>=+<<apidoc matches>> matches :: MatchRule -> M.ReceivedMessage -> Bool matches rule msg = and . catMaybes . map ($ rule) $ 	[ fmap      (typeMatches msg) . matchType@@ -3323,13 +3471,14 @@ {\tt ReleaseName}.  <<DBus/NameReservation.hs>>=+<<apidoc requestName>> requestName :: T.BusName -> [RequestNameFlag] -> M.MethodCall requestName name flags = M.MethodCall 	{ M.methodCallPath = C.dbusPath 	, M.methodCallInterface = Just C.dbusInterface 	, M.methodCallDestination = Just C.dbusName 	, M.methodCallFlags = Set.empty-	, M.methodCallMember = T.mkMemberName' "RequestName"+	, M.methodCallMember = "RequestName" 	, M.methodCallBody = 		[ T.toVariant name 		, T.toVariant . encodeFlags $ flags]@@ -3359,13 +3508,14 @@ decodeRequestReply _ = Nothing  <<DBus/NameReservation.hs>>=+<<apidoc releaseName>> releaseName :: T.BusName -> M.MethodCall releaseName name = M.MethodCall 	{ M.methodCallPath = C.dbusPath 	, M.methodCallInterface = Just C.dbusInterface 	, M.methodCallDestination = Just C.dbusName 	, M.methodCallFlags = Set.empty-	, M.methodCallMember = T.mkMemberName' "ReleaseName"+	, M.methodCallMember = "ReleaseName" 	, M.methodCallBody = [T.toVariant name] 	} @@ -3414,153 +3564,192 @@  <<DBus/Constants.hs>>= dbusName :: T.BusName-dbusName = T.mkBusName' "org.freedesktop.DBus"+dbusName = "org.freedesktop.DBus"  dbusPath :: T.ObjectPath-dbusPath = T.mkObjectPath' "/org/freedesktop/DBus"+dbusPath = "/org/freedesktop/DBus"  dbusInterface :: T.InterfaceName-dbusInterface = T.mkInterfaceName' "org.freedesktop.DBus"+dbusInterface = "org.freedesktop.DBus"  @ \subsection{Pre-defined interfaces}  <<DBus/Constants.hs>>= interfaceIntrospectable :: T.InterfaceName-interfaceIntrospectable = T.mkInterfaceName' "org.freedesktop.DBus.Introspectable"+interfaceIntrospectable = "org.freedesktop.DBus.Introspectable"  interfaceProperties :: T.InterfaceName-interfaceProperties = T.mkInterfaceName' "org.freedesktop.DBus.Properties"+interfaceProperties = "org.freedesktop.DBus.Properties"  interfacePeer :: T.InterfaceName-interfacePeer = T.mkInterfaceName' "org.freedesktop.DBus.Peer"+interfacePeer = "org.freedesktop.DBus.Peer"  @ \subsection{Pre-defined error names}  <<DBus/Constants.hs>>= errorFailed :: T.ErrorName-errorFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Failed"+errorFailed = "org.freedesktop.DBus.Error.Failed"  errorNoMemory :: T.ErrorName-errorNoMemory = T.mkErrorName' "org.freedesktop.DBus.Error.NoMemory"+errorNoMemory = "org.freedesktop.DBus.Error.NoMemory"  errorServiceUnknown :: T.ErrorName-errorServiceUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.ServiceUnknown"+errorServiceUnknown = "org.freedesktop.DBus.Error.ServiceUnknown"  errorNameHasNoOwner :: T.ErrorName-errorNameHasNoOwner = T.mkErrorName' "org.freedesktop.DBus.Error.NameHasNoOwner"+errorNameHasNoOwner = "org.freedesktop.DBus.Error.NameHasNoOwner"  errorNoReply :: T.ErrorName-errorNoReply = T.mkErrorName' "org.freedesktop.DBus.Error.NoReply"+errorNoReply = "org.freedesktop.DBus.Error.NoReply"  errorIOError :: T.ErrorName-errorIOError = T.mkErrorName' "org.freedesktop.DBus.Error.IOError"+errorIOError = "org.freedesktop.DBus.Error.IOError"  errorBadAddress :: T.ErrorName-errorBadAddress = T.mkErrorName' "org.freedesktop.DBus.Error.BadAddress"+errorBadAddress = "org.freedesktop.DBus.Error.BadAddress"  errorNotSupported :: T.ErrorName-errorNotSupported = T.mkErrorName' "org.freedesktop.DBus.Error.NotSupported"+errorNotSupported = "org.freedesktop.DBus.Error.NotSupported"  errorLimitsExceeded :: T.ErrorName-errorLimitsExceeded = T.mkErrorName' "org.freedesktop.DBus.Error.LimitsExceeded"+errorLimitsExceeded = "org.freedesktop.DBus.Error.LimitsExceeded"  errorAccessDenied :: T.ErrorName-errorAccessDenied = T.mkErrorName' "org.freedesktop.DBus.Error.AccessDenied"+errorAccessDenied = "org.freedesktop.DBus.Error.AccessDenied"  errorAuthFailed :: T.ErrorName-errorAuthFailed = T.mkErrorName' "org.freedesktop.DBus.Error.AuthFailed"+errorAuthFailed = "org.freedesktop.DBus.Error.AuthFailed"  errorNoServer :: T.ErrorName-errorNoServer = T.mkErrorName' "org.freedesktop.DBus.Error.NoServer"+errorNoServer = "org.freedesktop.DBus.Error.NoServer"  errorTimeout :: T.ErrorName-errorTimeout = T.mkErrorName' "org.freedesktop.DBus.Error.Timeout"+errorTimeout = "org.freedesktop.DBus.Error.Timeout"  errorNoNetwork :: T.ErrorName-errorNoNetwork = T.mkErrorName' "org.freedesktop.DBus.Error.NoNetwork"+errorNoNetwork = "org.freedesktop.DBus.Error.NoNetwork"  errorAddressInUse :: T.ErrorName-errorAddressInUse = T.mkErrorName' "org.freedesktop.DBus.Error.AddressInUse"+errorAddressInUse = "org.freedesktop.DBus.Error.AddressInUse"  errorDisconnected :: T.ErrorName-errorDisconnected = T.mkErrorName' "org.freedesktop.DBus.Error.Disconnected"+errorDisconnected = "org.freedesktop.DBus.Error.Disconnected"  errorInvalidArgs :: T.ErrorName-errorInvalidArgs = T.mkErrorName' "org.freedesktop.DBus.Error.InvalidArgs"+errorInvalidArgs = "org.freedesktop.DBus.Error.InvalidArgs"  errorFileNotFound :: T.ErrorName-errorFileNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.FileNotFound"+errorFileNotFound = "org.freedesktop.DBus.Error.FileNotFound"  errorFileExists :: T.ErrorName-errorFileExists = T.mkErrorName' "org.freedesktop.DBus.Error.FileExists"+errorFileExists = "org.freedesktop.DBus.Error.FileExists"  errorUnknownMethod :: T.ErrorName-errorUnknownMethod = T.mkErrorName' "org.freedesktop.DBus.Error.UnknownMethod"+errorUnknownMethod = "org.freedesktop.DBus.Error.UnknownMethod"  errorTimedOut :: T.ErrorName-errorTimedOut = T.mkErrorName' "org.freedesktop.DBus.Error.TimedOut"+errorTimedOut = "org.freedesktop.DBus.Error.TimedOut"  errorMatchRuleNotFound :: T.ErrorName-errorMatchRuleNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.MatchRuleNotFound"+errorMatchRuleNotFound = "org.freedesktop.DBus.Error.MatchRuleNotFound"  errorMatchRuleInvalid :: T.ErrorName-errorMatchRuleInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.MatchRuleInvalid"+errorMatchRuleInvalid = "org.freedesktop.DBus.Error.MatchRuleInvalid"  errorSpawnExecFailed :: T.ErrorName-errorSpawnExecFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ExecFailed"+errorSpawnExecFailed = "org.freedesktop.DBus.Error.Spawn.ExecFailed"  errorSpawnForkFailed :: T.ErrorName-errorSpawnForkFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ForkFailed"+errorSpawnForkFailed = "org.freedesktop.DBus.Error.Spawn.ForkFailed"  errorSpawnChildExited :: T.ErrorName-errorSpawnChildExited = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ChildExited"+errorSpawnChildExited = "org.freedesktop.DBus.Error.Spawn.ChildExited"  errorSpawnChildSignaled :: T.ErrorName-errorSpawnChildSignaled = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ChildSignaled"+errorSpawnChildSignaled = "org.freedesktop.DBus.Error.Spawn.ChildSignaled"  errorSpawnFailed :: T.ErrorName-errorSpawnFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.Failed"+errorSpawnFailed = "org.freedesktop.DBus.Error.Spawn.Failed"  errorSpawnFailedToSetup :: T.ErrorName-errorSpawnFailedToSetup = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.FailedToSetup"+errorSpawnFailedToSetup = "org.freedesktop.DBus.Error.Spawn.FailedToSetup"  errorSpawnConfigInvalid :: T.ErrorName-errorSpawnConfigInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ConfigInvalid"+errorSpawnConfigInvalid = "org.freedesktop.DBus.Error.Spawn.ConfigInvalid"  errorSpawnServiceNotValid :: T.ErrorName-errorSpawnServiceNotValid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ServiceNotValid"+errorSpawnServiceNotValid = "org.freedesktop.DBus.Error.Spawn.ServiceNotValid"  errorSpawnServiceNotFound :: T.ErrorName-errorSpawnServiceNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ServiceNotFound"+errorSpawnServiceNotFound = "org.freedesktop.DBus.Error.Spawn.ServiceNotFound"  errorSpawnPermissionsInvalid :: T.ErrorName-errorSpawnPermissionsInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid"+errorSpawnPermissionsInvalid = "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid"  errorSpawnFileInvalid :: T.ErrorName-errorSpawnFileInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.FileInvalid"+errorSpawnFileInvalid = "org.freedesktop.DBus.Error.Spawn.FileInvalid"  errorSpawnNoMemory :: T.ErrorName-errorSpawnNoMemory = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.NoMemory"+errorSpawnNoMemory = "org.freedesktop.DBus.Error.Spawn.NoMemory"  errorUnixProcessIdUnknown :: T.ErrorName-errorUnixProcessIdUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.UnixProcessIdUnknown"+errorUnixProcessIdUnknown = "org.freedesktop.DBus.Error.UnixProcessIdUnknown"  errorInvalidFileContent :: T.ErrorName-errorInvalidFileContent = T.mkErrorName' "org.freedesktop.DBus.Error.InvalidFileContent"+errorInvalidFileContent = "org.freedesktop.DBus.Error.InvalidFileContent"  errorSELinuxSecurityContextUnknown :: T.ErrorName-errorSELinuxSecurityContextUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"+errorSELinuxSecurityContextUnknown = "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"  errorAdtAuditDataUnknown :: T.ErrorName-errorAdtAuditDataUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.AdtAuditDataUnknown"+errorAdtAuditDataUnknown = "org.freedesktop.DBus.Error.AdtAuditDataUnknown"  errorObjectPathInUse :: T.ErrorName-errorObjectPathInUse = T.mkErrorName' "org.freedesktop.DBus.Error.ObjectPathInUse"+errorObjectPathInUse = "org.freedesktop.DBus.Error.ObjectPathInUse"  errorInconsistentMessage :: T.ErrorName-errorInconsistentMessage = T.mkErrorName' "org.freedesktop.DBus.Error.InconsistentMessage"+errorInconsistentMessage = "org.freedesktop.DBus.Error.InconsistentMessage"  @+\section{UUIDs}++D-Bus {\sc uuid}s are 128-bit unique identifiers, used for server instances+and machine {\sc id}s. They are not compatible with {\sc rfc4122}.++<<DBus/UUID.hs>>=+<<copyright>>+module DBus.UUID+	( UUID+	, toHex+	, fromHex+	) where+<<text imports>>++newtype UUID = UUID Text -- TODO: (Word64, Word64)?+	deriving (Eq)++instance Show UUID where+	showsPrec d uuid = showParen (d > 10) $+		showString "UUID " . shows (toHex uuid)++toHex :: UUID -> Text+toHex (UUID text) = text++fromHex :: Text -> Maybe UUID+fromHex text = if validUUID text+	then Just $ UUID text+	else Nothing++validUUID :: Text -> Bool+validUUID text = valid where+	valid = and [TL.length text == 32, TL.all validChar text]+	validChar c = or+		[ c >= '0' && c <= '9'+		, c >= 'a' && c <= 'f'+		, c >= 'A' && c <= 'F'+		]++@ \section{Misc. utility functions}  <<DBus/Util.hs>>=@@ -3568,6 +3757,7 @@ module DBus.Util where import Text.Parsec (Parsec, parse) import Data.Char (digitToInt)+import Data.List (isPrefixOf)  checkLength :: Int -> String -> Maybe String checkLength length' s | length s <= length' = Just s@@ -3596,5 +3786,147 @@ maybeIndex (x:_ ) 0         = Just x maybeIndex (_:xs) n | n > 0 = maybeIndex xs (n - 1) maybeIndex _ _ = Nothing++-- | Read values from a monad until a guard value is read; return all+-- values, including the guard.+--+readUntil :: (Monad m, Eq a) => [a] -> m a -> m [a]+readUntil guard getx = readUntil' [] where+	guard' = reverse guard+	step xs | isPrefixOf guard' xs = return . reverse $ xs+	        | otherwise            = readUntil' xs+	readUntil' xs = do+		x <- getx+		step $ x:xs++-- | Drop /n/ items from the end of a list+dropEnd :: Int -> [a] -> [a]+dropEnd n xs = take (length xs - n) xs++@+\section{Haddock API documentation}++The \LaTeX{} documentation is great for somebody reading the source code+or trying to fix an error, but it's not useful for people who just want+to add D-Bus support to their applications. These documentation sections+will be included in Haddock output for public functions.++There's no real order to this section; it exists only because Haddock+can't merge documentation from external files.++<<apidoc isAtomicType>>=+-- | \"Atomic\" types are any which can't contain any other types. Only+-- atomic types may be used as dictionary keys.++<<apidoc typeCode>>=+-- | Every type has an associated type code; a textual representation of+-- the type, useful for debugging.++<<apidoc Variant>>=+-- | 'Variant's may contain any other built-in D-Bus value. Besides+-- representing native @VARIANT@ values, they allow type-safe storage and+-- deconstruction of heterogeneous collections.++<<apidoc variantType>>=+-- | Every variant is strongly-typed; that is, the type of its contained+-- value is known at all times. This function retrieves that type, so that+-- the correct cast can be used to retrieve the value.++<<apidoc arrayType>>=+-- | This is the type contained within the array, not the type of the array+-- itself.++<<apidoc Connection>>=+-- | A 'Connection' is an opaque handle to an open D-Bus channel, with an+-- internal state for maintaining the current message serial.++<<apidoc Transport>>=+-- | A 'Transport' is anything which can send and receive bytestrings,+-- typically via a socket.++<<apidoc connect>>=+-- | Open a connection to some address, using a given authentication+-- mechanism. If the connection fails, a 'ConnectionError' will be thrown.++<<apidoc connectFirst>>=+-- | Try to open a connection to various addresses, returning the first+-- connection which could be successfully opened.++<<apidoc send>>=+-- | Send a single message, with a generated 'M.Serial'. The second parameter+-- exists to prevent race conditions when registering a reply handler; it+-- receives the serial the message /will/ be sent with, before it's actually+-- sent.+--+-- Only one message may be sent at a time; if multiple threads attempt to+-- send messages in parallel, one will block until after the other has+-- finished.++<<apidoc receive>>=+-- | Receive the next message from the connection, blocking until one is+-- available.+--+-- Only one message may be received at a time; if multiple threads attempt+-- to receive messages in parallel, one will block until after the other has+-- finished.++<<apidoc getBus>>=+-- | Similar to 'C.connect', but additionally sends @Hello@ messages to the+-- central bus.++<<apidoc getFirstBus>>=+-- | Similar to 'C.connectFirst', but additionally sends @Hello@ messages to+-- the central bus.++<<apidoc getSystemBus>>=+-- | Connect to the bus specified in the environment variable+-- @DBUS_SYSTEM_BUS_ADDRESS@, or to+-- @unix:path=\/var\/run\/dbus\/system_bus_socket@ if @DBUS_SYSTEM_BUS_ADDRESS@+-- is not set.++<<apidoc getSessionBus>>=+-- | Connect to the bus specified in the environment variable+-- @DBUS_SESSION_BUS_ADDRESS@, which must be set.++<<apidoc getStarterBus>>=+-- | Connect to the bus specified in the environment variable+-- @DBUS_STARTER_ADDRESS@, which must be set.++<<apidoc formatRule>>=+-- | Format a 'MatchRule' as the bus expects to receive in a call to+-- @AddMatch@.++<<apidoc addMatch>>=+-- | Build a 'M.MethodCall' for adding a match rule to the bus.++<<apidoc matchAll>>=+-- | An empty match rule, which matches everything.++<<apidoc matches>>=+-- | Whether a 'M.ReceivedMessage' matches a given rule. This is useful+-- for implementing signal handlers.++<<apidoc requestName>>=+-- | Build a 'M.MethodCall' for requesting a registered bus name.++<<apidoc releaseName>>=+-- | Build a 'M.MethodCall' for releasing a registered bus name.++<<apidoc Serial>>=+-- | A value used to uniquely identify a particular message within a session.+-- 'Serial's are 32-bit unsigned integers, and eventually wrap.++<<apidoc ReceivedMessage>>=+-- | Not an actual message type, but a wrapper around messages received from+-- the bus. Each value contains the message's 'Serial' and possibly the+-- origin's 'T.BusName'++<<apidoc marshalMessage>>=+-- | Convert a 'M.Message' into a 'L.ByteString'. Although unusual, it is+-- possible for marshaling to fail -- if this occurs, an appropriate error+-- will be returned instead.++<<apidoc unmarshalMessage>>=+-- | Read bytes from a monad until a complete message has been received.  @ \end{document}
+ hs/DBus/Authentication.hs view
@@ -0,0 +1,100 @@+{-+  Copyright (C) 2009 John Millikin <jmillikin@gmail.com>+  +  This program is free software: you can redistribute it and/or modify+  it under the terms of the GNU General Public License as published by+  the Free Software Foundation, either version 3 of the License, or+  any later version.+  +  This program is distributed in the hope that it will be useful,+  but WITHOUT ANY WARRANTY; without even the implied warranty of+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+  GNU General Public License for more details.+  +  You should have received a copy of the GNU General Public License+  along with this program.  If not, see <http://www.gnu.org/licenses/>.+-}++{-# LANGUAGE OverloadedStrings #-}++{-# LANGUAGE DeriveDataTypeable #-}+module DBus.Authentication+        (   Command+          , Mechanism (..)++          , AuthenticationError (..)++          , authenticate++          , realUserID++        ) where+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as TL++import Data.ByteString.Lazy (ByteString)+import qualified Data.ByteString.Lazy as ByteString+import qualified DBus.UUID as UUID++import Data.Typeable (Typeable)+import qualified Control.Exception as E++import Data.Word (Word8)++import Control.Monad (liftM)+import Data.Char (chr)+import Data.Text.Lazy.Encoding (encodeUtf8)+import DBus.Util (readUntil, dropEnd)++import System.Posix.User (getRealUserID)+import Data.Char (ord)+import Text.Printf (printf)++import Data.Maybe (isJust)+++type Command = Text+newtype Mechanism = Mechanism+        { mechanismRun :: (Command -> IO Command) -> IO UUID.UUID+        }++data AuthenticationError+        = AuthenticationError Text+        deriving (Show, Typeable)++instance E.Exception AuthenticationError++authenticate :: Mechanism -> (ByteString -> IO ()) -> IO Word8+             -> IO UUID.UUID+authenticate mech put getByte = do+        put $ ByteString.singleton 0+        uuid <- mechanismRun mech (putCommand put getByte)+        put "BEGIN\r\n"+        return uuid++putCommand :: Monad m => (ByteString -> m ()) -> m Word8 -> Command -> m Command+putCommand put get cmd = do+        let getC = liftM (chr . fromIntegral) get+        put $ encodeUtf8 cmd+        put "\r\n"+        liftM (TL.pack . dropEnd 2) $ readUntil "\r\n" getC++realUserID :: Mechanism+realUserID = Mechanism $ \sendCmd -> do+        uid <- getRealUserID+        let token = concatMap (printf "%02X" . ord) (show uid)+        let cmd = "AUTH EXTERNAL " ++ token+        eitherUUID <- checkOK `fmap` sendCmd (TL.pack cmd)+        case eitherUUID of+                Right uuid -> return uuid+                Left err -> E.throwIO $ AuthenticationError err++checkOK :: Command -> Either Text UUID.UUID+checkOK cmd = if validUUID then Right uuid else Left errorMsg where+        validUUID = TL.isPrefixOf "OK " cmd && isJust maybeUUID+        maybeUUID = UUID.fromHex $ TL.drop 3 cmd+        Just uuid = maybeUUID+        errorMsg = if TL.isPrefixOf "ERROR " cmd+                then TL.drop 6 cmd+                else TL.pack $ "Unexpected response: " ++ show cmd+
hs/DBus/Bus.hs view
@@ -35,6 +35,7 @@ import System.Environment (getEnv)  import qualified DBus.Address as A+import qualified DBus.Authentication as Auth import qualified DBus.Connection as C import DBus.Constants (dbusName, dbusPath, dbusInterface) import qualified DBus.Message as M@@ -44,21 +45,38 @@ busForConnection :: C.Connection -> IO (C.Connection, T.BusName) busForConnection c = sendHello c >>= return . (,) c -getBus :: A.Address -> IO (C.Connection, T.BusName)-getBus = (busForConnection =<<) . C.connect+-- | Similar to 'C.connect', but additionally sends @Hello@ messages to the+-- central bus. -getFirstBus :: [A.Address] -> IO (C.Connection, T.BusName)+getBus :: Auth.Mechanism -> A.Address -> IO (C.Connection, T.BusName)+getBus = ((busForConnection =<<) .) . C.connect++-- | Similar to 'C.connectFirst', but additionally sends @Hello@ messages to+-- the central bus.++getFirstBus :: [(Auth.Mechanism, A.Address)] -> IO (C.Connection, T.BusName) getFirstBus = (busForConnection =<<) . C.connectFirst +-- | Connect to the bus specified in the environment variable+-- @DBUS_SYSTEM_BUS_ADDRESS@, or to+-- @unix:path=\/var\/run\/dbus\/system_bus_socket@ if @DBUS_SYSTEM_BUS_ADDRESS@+-- is not set.+ getSystemBus :: IO (C.Connection, T.BusName) getSystemBus = getBus' $ fromEnv `E.catch` noEnv where         defaultAddr = "unix:path=/var/run/dbus/system_bus_socket"         fromEnv = getEnv "DBUS_SYSTEM_BUS_ADDRESS"         noEnv (E.SomeException _) = return defaultAddr +-- | Connect to the bus specified in the environment variable+-- @DBUS_SESSION_BUS_ADDRESS@, which must be set.+ getSessionBus :: IO (C.Connection, T.BusName) getSessionBus = getBus' $ getEnv "DBUS_SESSION_BUS_ADDRESS" +-- | Connect to the bus specified in the environment variable+-- @DBUS_STARTER_ADDRESS@, which must be set.+ getStarterBus :: IO (C.Connection, T.BusName) getStarterBus = getBus' $ getEnv "DBUS_STARTER_ADDRESS" @@ -66,13 +84,13 @@ getBus' io = do         addr <- fmap TL.pack io         case A.mkAddresses addr of-                Just [x] -> getBus x-                Just  x  -> getFirstBus x+                Just [x] -> getBus Auth.realUserID x+                Just  xs -> getFirstBus [(Auth.realUserID,x) | x <- xs]                 _        -> E.throwIO $ C.InvalidAddress addr  hello :: M.MethodCall hello = M.MethodCall dbusPath-        (T.mkMemberName' "Hello")+        "Hello"         (Just dbusInterface)         (Just dbusName)         Set.empty
hs/DBus/Connection.hs view
@@ -20,6 +20,8 @@ {-# LANGUAGE DeriveDataTypeable #-} module DBus.Connection         (   Connection+          , connectionAddress+          , connectionUUID            , ConnectionError (..) @@ -36,7 +38,8 @@  import qualified Control.Concurrent as C import qualified DBus.Address as A-import qualified DBus.Message.Internal as M+import qualified DBus.Message as M+import qualified DBus.UUID as UUID  import qualified Data.ByteString.Lazy as L import Data.Word (Word32)@@ -56,24 +59,28 @@ import qualified Control.Exception as E import Data.Typeable (Typeable) -import Data.Text.Lazy.Encoding (decodeUtf8, encodeUtf8)--import System.Posix.User (getRealUserID)-import Data.Char (ord)-import Text.Printf (printf)--import Data.List (isPrefixOf)+import qualified DBus.Authentication as Auth  import qualified DBus.Wire as W  -data Connection = Connection A.Address Transport (C.MVar M.Serial) (C.MVar ())+data Connection = Connection+        { connectionAddress    :: A.Address+        , connectionTransport  :: Transport+        , connectionSerialMVar :: C.MVar M.Serial+        , connectionReadMVar   :: C.MVar ()+        , connectionUUID       :: UUID.UUID+        }  instance Show Connection where-        showsPrec d (Connection a _ _ _) = showParen (d > 10) $-                showString' ["<connection ", show $ A.strAddress a, ">"] where-                showString' = foldr (.) id . map showString+        showsPrec d con = showParen (d > 10) strCon where+                addr = A.strAddress $ connectionAddress con+                strCon = s "<Connection " . shows addr . s ">"+                s = showString +-- | A 'Transport' is anything which can send and receive bytestrings,+-- typically via a socket.+ data Transport = Transport         { transportSend :: L.ByteString -> IO ()         , transportRecv :: Word32 -> IO L.ByteString@@ -176,50 +183,40 @@  instance E.Exception ConnectionError -connect :: A.Address -> IO Connection-connect a = do+-- | Open a connection to some address, using a given authentication+-- mechanism. If the connection fails, a 'ConnectionError' will be thrown.++connect :: Auth.Mechanism -> A.Address -> IO Connection+connect mechanism a = do         t <- connectTransport a-        let putS = transportSend t . encodeUtf8 . TL.pack-        let getS = fmap (TL.unpack . decodeUtf8) . transportRecv t-        authenticate putS getS+        let getByte = L.head `fmap` transportRecv t 1+        uuid <- Auth.authenticate mechanism (transportSend t) getByte         readLock <- C.newMVar ()         serialMVar <- C.newMVar M.firstSerial-        return $ Connection a t serialMVar readLock+        return $ Connection a t serialMVar readLock uuid -connectFirst :: [A.Address] -> IO Connection+-- | Try to open a connection to various addresses, returning the first+-- connection which could be successfully opened.++connectFirst :: [(Auth.Mechanism, A.Address)] -> IO Connection connectFirst orig = connectFirst' orig where-        connectFirst'     [] = E.throwIO $ NoWorkingAddress orig-        connectFirst' (a:as) = E.catch (connect a) $+        allAddrs = [a | (_, a) <- orig]+        connectFirst'     [] = E.throwIO $ NoWorkingAddress allAddrs+        connectFirst' ((mech, a):as) = E.catch (connect mech a) $                 \(E.SomeException _) -> connectFirst' as -authenticate :: (String -> IO ()) -> (Word32 -> IO String)-                -> IO ()-authenticate put get = do-        put "\x00"--        uid <- getRealUserID-        let authToken = concatMap (printf "%02X" . ord) (show uid)-        put $ "AUTH EXTERNAL " ++ authToken ++ "\r\n"--        response <- readUntil '\n' get-        if "OK" `isPrefixOf` response-                then put "BEGIN\r\n"-                else do-                        putStrLn $ "response = " ++ show response-                        error "Server rejected authentication token."--readUntil :: Monad m => Char -> (Word32 -> m String) -> m String-readUntil = readUntil' "" where-        readUntil' xs c f = do-                [x] <- f 1-                let xs' = xs ++ [x]-                if x == c-                        then return xs'-                        else readUntil' xs' c f+-- | Send a single message, with a generated 'M.Serial'. The second parameter+-- exists to prevent race conditions when registering a reply handler; it+-- receives the serial the message /will/ be sent with, before it's actually+-- sent.+--+-- Only one message may be sent at a time; if multiple threads attempt to+-- send messages in parallel, one will block until after the other has+-- finished.  send :: M.Message a => Connection -> (M.Serial -> IO b) -> a      -> IO (Either W.MarshalError b)-send (Connection _ t mvar _) io msg = withSerial mvar $ \serial ->+send (Connection _ t mvar _ _) io msg = withSerial mvar $ \serial ->         case W.marshalMessage W.LittleEndian serial msg of                 Right bytes -> do                         x <- io serial@@ -235,7 +232,14 @@         C.putMVar m s'         return x +-- | Receive the next message from the connection, blocking until one is+-- available.+--+-- Only one message may be received at a time; if multiple threads attempt+-- to receive messages in parallel, one will block until after the other has+-- finished.+ receive :: Connection -> IO (Either W.UnmarshalError M.ReceivedMessage)-receive (Connection _ t _ lock) = C.withMVar lock $ \_ ->+receive (Connection _ t _ lock _) = C.withMVar lock $ \_ ->         W.unmarshalMessage $ transportRecv t 
hs/DBus/Constants.hs view
@@ -30,143 +30,143 @@ arrayMaximumLength = 67108864  dbusName :: T.BusName-dbusName = T.mkBusName' "org.freedesktop.DBus"+dbusName = "org.freedesktop.DBus"  dbusPath :: T.ObjectPath-dbusPath = T.mkObjectPath' "/org/freedesktop/DBus"+dbusPath = "/org/freedesktop/DBus"  dbusInterface :: T.InterfaceName-dbusInterface = T.mkInterfaceName' "org.freedesktop.DBus"+dbusInterface = "org.freedesktop.DBus"  interfaceIntrospectable :: T.InterfaceName-interfaceIntrospectable = T.mkInterfaceName' "org.freedesktop.DBus.Introspectable"+interfaceIntrospectable = "org.freedesktop.DBus.Introspectable"  interfaceProperties :: T.InterfaceName-interfaceProperties = T.mkInterfaceName' "org.freedesktop.DBus.Properties"+interfaceProperties = "org.freedesktop.DBus.Properties"  interfacePeer :: T.InterfaceName-interfacePeer = T.mkInterfaceName' "org.freedesktop.DBus.Peer"+interfacePeer = "org.freedesktop.DBus.Peer"  errorFailed :: T.ErrorName-errorFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Failed"+errorFailed = "org.freedesktop.DBus.Error.Failed"  errorNoMemory :: T.ErrorName-errorNoMemory = T.mkErrorName' "org.freedesktop.DBus.Error.NoMemory"+errorNoMemory = "org.freedesktop.DBus.Error.NoMemory"  errorServiceUnknown :: T.ErrorName-errorServiceUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.ServiceUnknown"+errorServiceUnknown = "org.freedesktop.DBus.Error.ServiceUnknown"  errorNameHasNoOwner :: T.ErrorName-errorNameHasNoOwner = T.mkErrorName' "org.freedesktop.DBus.Error.NameHasNoOwner"+errorNameHasNoOwner = "org.freedesktop.DBus.Error.NameHasNoOwner"  errorNoReply :: T.ErrorName-errorNoReply = T.mkErrorName' "org.freedesktop.DBus.Error.NoReply"+errorNoReply = "org.freedesktop.DBus.Error.NoReply"  errorIOError :: T.ErrorName-errorIOError = T.mkErrorName' "org.freedesktop.DBus.Error.IOError"+errorIOError = "org.freedesktop.DBus.Error.IOError"  errorBadAddress :: T.ErrorName-errorBadAddress = T.mkErrorName' "org.freedesktop.DBus.Error.BadAddress"+errorBadAddress = "org.freedesktop.DBus.Error.BadAddress"  errorNotSupported :: T.ErrorName-errorNotSupported = T.mkErrorName' "org.freedesktop.DBus.Error.NotSupported"+errorNotSupported = "org.freedesktop.DBus.Error.NotSupported"  errorLimitsExceeded :: T.ErrorName-errorLimitsExceeded = T.mkErrorName' "org.freedesktop.DBus.Error.LimitsExceeded"+errorLimitsExceeded = "org.freedesktop.DBus.Error.LimitsExceeded"  errorAccessDenied :: T.ErrorName-errorAccessDenied = T.mkErrorName' "org.freedesktop.DBus.Error.AccessDenied"+errorAccessDenied = "org.freedesktop.DBus.Error.AccessDenied"  errorAuthFailed :: T.ErrorName-errorAuthFailed = T.mkErrorName' "org.freedesktop.DBus.Error.AuthFailed"+errorAuthFailed = "org.freedesktop.DBus.Error.AuthFailed"  errorNoServer :: T.ErrorName-errorNoServer = T.mkErrorName' "org.freedesktop.DBus.Error.NoServer"+errorNoServer = "org.freedesktop.DBus.Error.NoServer"  errorTimeout :: T.ErrorName-errorTimeout = T.mkErrorName' "org.freedesktop.DBus.Error.Timeout"+errorTimeout = "org.freedesktop.DBus.Error.Timeout"  errorNoNetwork :: T.ErrorName-errorNoNetwork = T.mkErrorName' "org.freedesktop.DBus.Error.NoNetwork"+errorNoNetwork = "org.freedesktop.DBus.Error.NoNetwork"  errorAddressInUse :: T.ErrorName-errorAddressInUse = T.mkErrorName' "org.freedesktop.DBus.Error.AddressInUse"+errorAddressInUse = "org.freedesktop.DBus.Error.AddressInUse"  errorDisconnected :: T.ErrorName-errorDisconnected = T.mkErrorName' "org.freedesktop.DBus.Error.Disconnected"+errorDisconnected = "org.freedesktop.DBus.Error.Disconnected"  errorInvalidArgs :: T.ErrorName-errorInvalidArgs = T.mkErrorName' "org.freedesktop.DBus.Error.InvalidArgs"+errorInvalidArgs = "org.freedesktop.DBus.Error.InvalidArgs"  errorFileNotFound :: T.ErrorName-errorFileNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.FileNotFound"+errorFileNotFound = "org.freedesktop.DBus.Error.FileNotFound"  errorFileExists :: T.ErrorName-errorFileExists = T.mkErrorName' "org.freedesktop.DBus.Error.FileExists"+errorFileExists = "org.freedesktop.DBus.Error.FileExists"  errorUnknownMethod :: T.ErrorName-errorUnknownMethod = T.mkErrorName' "org.freedesktop.DBus.Error.UnknownMethod"+errorUnknownMethod = "org.freedesktop.DBus.Error.UnknownMethod"  errorTimedOut :: T.ErrorName-errorTimedOut = T.mkErrorName' "org.freedesktop.DBus.Error.TimedOut"+errorTimedOut = "org.freedesktop.DBus.Error.TimedOut"  errorMatchRuleNotFound :: T.ErrorName-errorMatchRuleNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.MatchRuleNotFound"+errorMatchRuleNotFound = "org.freedesktop.DBus.Error.MatchRuleNotFound"  errorMatchRuleInvalid :: T.ErrorName-errorMatchRuleInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.MatchRuleInvalid"+errorMatchRuleInvalid = "org.freedesktop.DBus.Error.MatchRuleInvalid"  errorSpawnExecFailed :: T.ErrorName-errorSpawnExecFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ExecFailed"+errorSpawnExecFailed = "org.freedesktop.DBus.Error.Spawn.ExecFailed"  errorSpawnForkFailed :: T.ErrorName-errorSpawnForkFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ForkFailed"+errorSpawnForkFailed = "org.freedesktop.DBus.Error.Spawn.ForkFailed"  errorSpawnChildExited :: T.ErrorName-errorSpawnChildExited = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ChildExited"+errorSpawnChildExited = "org.freedesktop.DBus.Error.Spawn.ChildExited"  errorSpawnChildSignaled :: T.ErrorName-errorSpawnChildSignaled = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ChildSignaled"+errorSpawnChildSignaled = "org.freedesktop.DBus.Error.Spawn.ChildSignaled"  errorSpawnFailed :: T.ErrorName-errorSpawnFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.Failed"+errorSpawnFailed = "org.freedesktop.DBus.Error.Spawn.Failed"  errorSpawnFailedToSetup :: T.ErrorName-errorSpawnFailedToSetup = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.FailedToSetup"+errorSpawnFailedToSetup = "org.freedesktop.DBus.Error.Spawn.FailedToSetup"  errorSpawnConfigInvalid :: T.ErrorName-errorSpawnConfigInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ConfigInvalid"+errorSpawnConfigInvalid = "org.freedesktop.DBus.Error.Spawn.ConfigInvalid"  errorSpawnServiceNotValid :: T.ErrorName-errorSpawnServiceNotValid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ServiceNotValid"+errorSpawnServiceNotValid = "org.freedesktop.DBus.Error.Spawn.ServiceNotValid"  errorSpawnServiceNotFound :: T.ErrorName-errorSpawnServiceNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ServiceNotFound"+errorSpawnServiceNotFound = "org.freedesktop.DBus.Error.Spawn.ServiceNotFound"  errorSpawnPermissionsInvalid :: T.ErrorName-errorSpawnPermissionsInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid"+errorSpawnPermissionsInvalid = "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid"  errorSpawnFileInvalid :: T.ErrorName-errorSpawnFileInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.FileInvalid"+errorSpawnFileInvalid = "org.freedesktop.DBus.Error.Spawn.FileInvalid"  errorSpawnNoMemory :: T.ErrorName-errorSpawnNoMemory = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.NoMemory"+errorSpawnNoMemory = "org.freedesktop.DBus.Error.Spawn.NoMemory"  errorUnixProcessIdUnknown :: T.ErrorName-errorUnixProcessIdUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.UnixProcessIdUnknown"+errorUnixProcessIdUnknown = "org.freedesktop.DBus.Error.UnixProcessIdUnknown"  errorInvalidFileContent :: T.ErrorName-errorInvalidFileContent = T.mkErrorName' "org.freedesktop.DBus.Error.InvalidFileContent"+errorInvalidFileContent = "org.freedesktop.DBus.Error.InvalidFileContent"  errorSELinuxSecurityContextUnknown :: T.ErrorName-errorSELinuxSecurityContextUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"+errorSELinuxSecurityContextUnknown = "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"  errorAdtAuditDataUnknown :: T.ErrorName-errorAdtAuditDataUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.AdtAuditDataUnknown"+errorAdtAuditDataUnknown = "org.freedesktop.DBus.Error.AdtAuditDataUnknown"  errorObjectPathInUse :: T.ErrorName-errorObjectPathInUse = T.mkErrorName' "org.freedesktop.DBus.Error.ObjectPathInUse"+errorObjectPathInUse = "org.freedesktop.DBus.Error.ObjectPathInUse"  errorInconsistentMessage :: T.ErrorName-errorInconsistentMessage = T.mkErrorName' "org.freedesktop.DBus.Error.InconsistentMessage"+errorInconsistentMessage = "org.freedesktop.DBus.Error.InconsistentMessage" 
hs/DBus/MatchRule.hs view
@@ -37,6 +37,11 @@ import qualified DBus.Constants as C import DBus.Util (maybeIndex) ++-- | A match rule is a set of filters; most filters may have one possible+-- value assigned, such as a single message type. The exception are parameter+-- filters, which are limited in number only by the server implementation.+--  data MatchRule = MatchRule         { matchType        :: Maybe MessageType         , matchSender      :: Maybe T.BusName@@ -46,12 +51,24 @@         , matchDestination :: Maybe T.BusName         , matchParameters  :: [ParameterValue]         }+        deriving (Show) +-- | Parameters may match against two types, strings and object paths. It's+-- probably an error to have two values for the same parameter.+-- +-- The constructor @StringValue 3 \"hello\"@ means that the fourth parameter+-- in the message body must be the string @\"hello\"@. @PathValue@ is the+-- same, but its value must be an object path.+--  data ParameterValue         = StringValue Word8 Text         | PathValue Word8 T.ObjectPath         deriving (Show, Eq) +-- | The set of allowed message types to filter on is separate from the set+-- supported for sending over the wire. This allows the server to support+-- additional types not yet implemented in the library, or vice-versa.+--  data MessageType         = MethodCall         | MethodReturn@@ -59,6 +76,9 @@         | Error         deriving (Show, Eq) +-- | Format a 'MatchRule' as the bus expects to receive in a call to+-- @AddMatch@.+ formatRule :: MatchRule -> Text formatRule rule = TL.intercalate "," filters where         filters = structureFilters ++ parameterFilters@@ -89,15 +109,19 @@ formatType Signal       = "signal" formatType Error        = "error" +-- | Build a 'M.MethodCall' for adding a match rule to the bus.+ addMatch :: MatchRule -> M.MethodCall addMatch rule = M.MethodCall         C.dbusPath-        (T.mkMemberName' "AddMatch")+        "AddMatch"         (Just C.dbusInterface)         (Just C.dbusName)         Set.empty         [T.toVariant $ formatRule rule] +-- | An empty match rule, which matches everything.+ matchAll :: MatchRule matchAll = MatchRule         { matchType        = Nothing@@ -108,6 +132,9 @@         , matchDestination = Nothing         , matchParameters  = []         }++-- | Whether a 'M.ReceivedMessage' matches a given rule. This is useful+-- for implementing signal handlers.  matches :: MatchRule -> M.ReceivedMessage -> Bool matches rule msg = and . catMaybes . map ($ rule) $
hs/DBus/Message.hs view
@@ -24,6 +24,8 @@           , Serial          , serialValue+         , firstSerial+         , nextSerial           , MethodCall (..) 
hs/DBus/Message/Internal.hs view
@@ -49,6 +49,9 @@         | Signature   T.Signature         deriving (Show, Eq) +-- | A value used to uniquely identify a particular message within a session.+-- 'Serial's are 32-bit unsigned integers, and eventually wrap.+ newtype Serial = Serial { serialValue :: Word32 }         deriving (Eq, Ord) @@ -161,6 +164,10 @@         , unknownBody    :: [T.Variant]         }         deriving (Show, Eq)++-- | Not an actual message type, but a wrapper around messages received from+-- the bus. Each value contains the message's 'Serial' and possibly the+-- origin's 'T.BusName'  data ReceivedMessage         = ReceivedMethodCall   Serial (Maybe T.BusName) MethodCall
hs/DBus/NameReservation.hs view
@@ -45,13 +45,15 @@         flagValue ReplaceExisting  = 0x2         flagValue DoNotQueue       = 0x4 +-- | Build a 'M.MethodCall' for requesting a registered bus name.+ requestName :: T.BusName -> [RequestNameFlag] -> M.MethodCall requestName name flags = M.MethodCall         { M.methodCallPath = C.dbusPath         , M.methodCallInterface = Just C.dbusInterface         , M.methodCallDestination = Just C.dbusName         , M.methodCallFlags = Set.empty-        , M.methodCallMember = T.mkMemberName' "RequestName"+        , M.methodCallMember = "RequestName"         , M.methodCallBody =                 [ T.toVariant name                 , T.toVariant . encodeFlags $ flags]@@ -77,13 +79,15 @@ decodeRequestReply 4 = Just AlreadyOwner decodeRequestReply _ = Nothing +-- | Build a 'M.MethodCall' for releasing a registered bus name.+ releaseName :: T.BusName -> M.MethodCall releaseName name = M.MethodCall         { M.methodCallPath = C.dbusPath         , M.methodCallInterface = Just C.dbusInterface         , M.methodCallDestination = Just C.dbusName         , M.methodCallFlags = Set.empty-        , M.methodCallMember = T.mkMemberName' "ReleaseName"+        , M.methodCallMember = "ReleaseName"         , M.methodCallBody = [T.toVariant name]         } 
hs/DBus/Types.hs view
@@ -38,13 +38,13 @@                    , strSignature                     , mkSignature-                   , mkSignature'+                   , mkSignature_                       -- * Object paths                    , ObjectPath                    , strObjectPath                    , mkObjectPath-                   , mkObjectPath'+                   , mkObjectPath_                       -- * Arrays                    , Array@@ -80,25 +80,25 @@                    , BusName                    , strBusName                    , mkBusName-                   , mkBusName'+                   , mkBusName_                       -- ** Interface names                    , InterfaceName                    , strInterfaceName                    , mkInterfaceName-                   , mkInterfaceName'+                   , mkInterfaceName_                       -- ** Error names                    , ErrorName                    , strErrorName                    , mkErrorName-                   , mkErrorName'+                   , mkErrorName_                       -- ** Member names                    , MemberName                    , strMemberName                    , mkMemberName-                   , mkMemberName'+                   , mkMemberName_ ) where import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL@@ -116,6 +116,8 @@  import DBus.Util (mkUnsafe) +import qualified Data.String as String+ import Data.ByteString.Lazy (ByteString) import qualified Data.ByteString.Lazy as ByteString @@ -150,6 +152,9 @@         | DBusStructure [Type]         deriving (Show, Eq) +-- | \"Atomic\" types are any which can't contain any other types. Only+-- atomic types may be used as dictionary keys.+ isAtomicType :: Type -> Bool isAtomicType DBusBoolean    = True isAtomicType DBusByte       = True@@ -165,11 +170,39 @@ isAtomicType DBusObjectPath = True isAtomicType _              = False +-- | Every type has an associated type code; a textual representation of+-- the type, useful for debugging.+ typeCode :: Type -> Text+typeCode DBusBoolean    = "b"+typeCode DBusByte       = "y"+typeCode DBusInt16      = "n"+typeCode DBusInt32      = "i"+typeCode DBusInt64      = "x"+typeCode DBusWord16     = "q"+typeCode DBusWord32     = "u"+typeCode DBusWord64     = "t"+typeCode DBusDouble     = "d"+typeCode DBusString     = "s"+typeCode DBusSignature  = "g"+typeCode DBusObjectPath = "o"+typeCode DBusVariant    = "v" +typeCode (DBusArray t) = TL.cons 'a' $ typeCode t++typeCode (DBusDictionary k v) = TL.concat ["a{", typeCode k, typeCode v, "}"]++typeCode (DBusStructure ts) = TL.concat $+        ["("] ++ map typeCode ts ++ [")"]++ class (Show a, Eq a) => Builtin a where         builtinDBusType :: a -> Type +-- | 'Variant's may contain any other built-in D-Bus value. Besides+-- representing native @VARIANT@ values, they allow type-safe storage and+-- deconstruction of heterogeneous collections.+ data Variant = forall a. (Variable a, Builtin a, Typeable a) => Variant a         deriving (Typeable) @@ -186,6 +219,10 @@ instance Eq Variant where         (Variant x) == (Variant y) = cast x == Just y +-- | Every variant is strongly-typed; that is, the type of its contained+-- value is known at all times. This function retrieves that type, so that+-- the correct cast can be used to retrieve the value.+ variantType :: Variant -> Type variantType (Variant x) = builtinDBusType x @@ -238,27 +275,6 @@ instance Ord Signature where         compare x y = compare (strSignature x) (strSignature y) -typeCode DBusBoolean    = "b"-typeCode DBusByte       = "y"-typeCode DBusInt16      = "n"-typeCode DBusInt32      = "i"-typeCode DBusInt64      = "x"-typeCode DBusWord16     = "q"-typeCode DBusWord32     = "u"-typeCode DBusWord64     = "t"-typeCode DBusDouble     = "d"-typeCode DBusString     = "s"-typeCode DBusSignature  = "g"-typeCode DBusObjectPath = "o"-typeCode DBusVariant    = "v"--typeCode (DBusArray t) = TL.cons 'a' $ typeCode t--typeCode (DBusDictionary k v) = TL.concat ["a{", typeCode k, typeCode v, "}"]--typeCode (DBusStructure ts) = TL.concat $-        ["("] ++ map typeCode ts ++ [")"]- mkSignature :: Text -> Maybe Signature mkSignature = (parseMaybe sigParser =<<) . checkLength 255 . TL.unpack where         sigParser = do@@ -300,23 +316,33 @@                 P.char ')'                 return $ DBusStructure types -mkSignature' :: Text -> Signature-mkSignature' = mkUnsafe "signature" mkSignature+mkSignature_ :: Text -> Signature+mkSignature_ = mkUnsafe "signature" mkSignature +instance String.IsString Signature where+        fromString = mkSignature_ . TL.pack+ instance Builtin ObjectPath where                 { builtinDBusType _ = DBusObjectPath };         instance Variable ObjectPath where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x }; newtype ObjectPath = ObjectPath         { strObjectPath :: Text         }-        deriving (Show, Eq, Ord, Typeable)+        deriving (Eq, Ord, Typeable) +instance Show ObjectPath where+        showsPrec d (ObjectPath x) = showParen (d > 10) $+                showString "ObjectPath " . shows x++instance String.IsString ObjectPath where+        fromString = mkObjectPath_ . TL.pack+ mkObjectPath :: Text -> Maybe ObjectPath mkObjectPath s = parseMaybe path' (TL.unpack s) where         c = P.oneOf $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_"         path = P.char '/' >>= P.optional . P.sepBy (P.many1 c) . P.char         path' = path >> P.eof >> return (ObjectPath s) -mkObjectPath' :: Text -> ObjectPath-mkObjectPath' = mkUnsafe "object path" mkObjectPath+mkObjectPath_ :: Text -> ObjectPath+mkObjectPath_ = mkUnsafe "object path" mkObjectPath  instance Variable Array where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x }; data Array@@ -327,6 +353,9 @@ instance Builtin Array where         builtinDBusType = DBusArray . arrayType +-- | This is the type contained within the array, not the type of the array+-- itself.+ arrayType :: Array -> Type arrayType (VariantArray t _) = t arrayType (ByteArray _) = DBusByte@@ -460,8 +489,16 @@   -newtype BusName = BusName {strBusName :: Text}                 deriving (Show, Eq, Ord);                                                   instance Variable BusName where                 { toVariant = toVariant . strBusName                 ; fromVariant = (mkBusName =<<) . fromVariant };                                                                         mkBusName' :: Text -> BusName;         mkBusName' = mkUnsafe "bus name" mkBusName ++++++++newtype BusName = BusName {strBusName :: Text}                 deriving (Eq, Ord);                         instance Show BusName where                 { showsPrec d (BusName x) = showParen (d > 10) $                         showString "BusName " . shows x                 };                         instance String.IsString BusName where                 { fromString = mkBusName_ . TL.pack };                         instance Variable BusName where                 { toVariant = toVariant . strBusName                 ; fromVariant = (mkBusName =<<) . fromVariant };                                                                         mkBusName_ :: Text -> BusName;         mkBusName_ = mkUnsafe "bus name" mkBusName+ mkBusName :: Text -> Maybe BusName mkBusName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where         c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"@@ -472,7 +509,7 @@         elems start = elem' start >> P.many1 (P.char '.' >> elem' start)         elem' start = P.oneOf start >> P.many (P.oneOf c') -newtype InterfaceName = InterfaceName {strInterfaceName :: Text}                 deriving (Show, Eq, Ord);                                                   instance Variable InterfaceName where                 { toVariant = toVariant . strInterfaceName                 ; fromVariant = (mkInterfaceName =<<) . fromVariant };                                                                         mkInterfaceName' :: Text -> InterfaceName;         mkInterfaceName' = mkUnsafe "interface name" mkInterfaceName+newtype InterfaceName = InterfaceName {strInterfaceName :: Text}                 deriving (Eq, Ord);                         instance Show InterfaceName where                 { showsPrec d (InterfaceName x) = showParen (d > 10) $                         showString "InterfaceName " . shows x                 };                         instance String.IsString InterfaceName where                 { fromString = mkInterfaceName_ . TL.pack };                         instance Variable InterfaceName where                 { toVariant = toVariant . strInterfaceName                 ; fromVariant = (mkInterfaceName =<<) . fromVariant };                                                                         mkInterfaceName_ :: Text -> InterfaceName;         mkInterfaceName_ = mkUnsafe "interface name" mkInterfaceName  mkInterfaceName :: Text -> Maybe InterfaceName mkInterfaceName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where@@ -482,12 +519,12 @@         name = element >> P.many1 (P.char '.' >> element)         parser = name >> P.eof >> return (InterfaceName s) -newtype ErrorName = ErrorName {strErrorName :: Text}                 deriving (Show, Eq, Ord);                                                   instance Variable ErrorName where                 { toVariant = toVariant . strErrorName                 ; fromVariant = (mkErrorName =<<) . fromVariant };                                                                         mkErrorName' :: Text -> ErrorName;         mkErrorName' = mkUnsafe "error name" mkErrorName+newtype ErrorName = ErrorName {strErrorName :: Text}                 deriving (Eq, Ord);                         instance Show ErrorName where                 { showsPrec d (ErrorName x) = showParen (d > 10) $                         showString "ErrorName " . shows x                 };                         instance String.IsString ErrorName where                 { fromString = mkErrorName_ . TL.pack };                         instance Variable ErrorName where                 { toVariant = toVariant . strErrorName                 ; fromVariant = (mkErrorName =<<) . fromVariant };                                                                         mkErrorName_ :: Text -> ErrorName;         mkErrorName_ = mkUnsafe "error name" mkErrorName  mkErrorName :: Text -> Maybe ErrorName mkErrorName = fmap (ErrorName . strInterfaceName) . mkInterfaceName -newtype MemberName = MemberName {strMemberName :: Text}                 deriving (Show, Eq, Ord);                                                   instance Variable MemberName where                 { toVariant = toVariant . strMemberName                 ; fromVariant = (mkMemberName =<<) . fromVariant };                                                                         mkMemberName' :: Text -> MemberName;         mkMemberName' = mkUnsafe "member name" mkMemberName+newtype MemberName = MemberName {strMemberName :: Text}                 deriving (Eq, Ord);                         instance Show MemberName where                 { showsPrec d (MemberName x) = showParen (d > 10) $                         showString "MemberName " . shows x                 };                         instance String.IsString MemberName where                 { fromString = mkMemberName_ . TL.pack };                         instance Variable MemberName where                 { toVariant = toVariant . strMemberName                 ; fromVariant = (mkMemberName =<<) . fromVariant };                                                                         mkMemberName_ :: Text -> MemberName;         mkMemberName_ = mkUnsafe "member name" mkMemberName  mkMemberName :: Text -> Maybe MemberName mkMemberName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where
+ hs/DBus/UUID.hs view
@@ -0,0 +1,50 @@+{-+  Copyright (C) 2009 John Millikin <jmillikin@gmail.com>+  +  This program is free software: you can redistribute it and/or modify+  it under the terms of the GNU General Public License as published by+  the Free Software Foundation, either version 3 of the License, or+  any later version.+  +  This program is distributed in the hope that it will be useful,+  but WITHOUT ANY WARRANTY; without even the implied warranty of+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+  GNU General Public License for more details.+  +  You should have received a copy of the GNU General Public License+  along with this program.  If not, see <http://www.gnu.org/licenses/>.+-}++module DBus.UUID+        ( UUID+        , toHex+        , fromHex+        ) where+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as TL+++newtype UUID = UUID Text -- TODO: (Word64, Word64)?+        deriving (Eq)++instance Show UUID where+        showsPrec d uuid = showParen (d > 10) $+                showString "UUID " . shows (toHex uuid)++toHex :: UUID -> Text+toHex (UUID text) = text++fromHex :: Text -> Maybe UUID+fromHex text = if validUUID text+        then Just $ UUID text+        else Nothing++validUUID :: Text -> Bool+validUUID text = valid where+        valid = and [TL.length text == 32, TL.all validChar text]+        validChar c = or+                [ c >= '0' && c <= '9'+                , c >= 'a' && c <= 'f'+                , c >= 'A' && c <= 'F'+                ]+
hs/DBus/Util.hs view
@@ -18,6 +18,7 @@ module DBus.Util where import Text.Parsec (Parsec, parse) import Data.Char (digitToInt)+import Data.List (isPrefixOf)  checkLength :: Int -> String -> Maybe String checkLength length' s | length s <= length' = Just s@@ -46,4 +47,20 @@ maybeIndex (x:_ ) 0         = Just x maybeIndex (_:xs) n | n > 0 = maybeIndex xs (n - 1) maybeIndex _ _ = Nothing++-- | Read values from a monad until a guard value is read; return all+-- values, including the guard.+--+readUntil :: (Monad m, Eq a) => [a] -> m a -> m [a]+readUntil guard getx = readUntil' [] where+        guard' = reverse guard+        step xs | isPrefixOf guard' xs = return . reverse $ xs+                | otherwise            = readUntil' xs+        readUntil' xs = do+                x <- getx+                step $ x:xs++-- | Drop /n/ items from the end of a list+dropEnd :: Int -> [a] -> [a]+dropEnd n xs = take (length xs - n) xs 
hs/DBus/Wire/Internal.hs view
@@ -508,6 +508,10 @@         c' = fromJust . T.fromVariant $ c         v' = fromJust . T.fromVariant $ v +-- | Convert a 'M.Message' into a 'L.ByteString'. Although unusual, it is+-- possible for marshaling to fail -- if this occurs, an appropriate error+-- will be returned instead.+ marshalMessage :: M.Message a => Endianness -> M.Serial -> a                -> Either MarshalError L.ByteString marshalMessage e serial msg = runMarshal marshaler e where@@ -556,12 +560,13 @@         when (messageLength > fromIntegral C.messageMaximumLength)                 (E.throwError $ MessageTooLong $ fromIntegral messageLength) +-- | Read bytes from a monad until a complete message has been received. unmarshalMessage :: Monad m => (Word32 -> m L.ByteString)                  -> m (Either UnmarshalError M.ReceivedMessage) unmarshalMessage getBytes' = E.runErrorT $ do         let getBytes = E.lift . getBytes'         -        let fixedSig = T.mkSignature' "yyyyuuu"+        let fixedSig = "yyyyuuu"         fixedBytes <- getBytes 16          let messageVersion = L.index fixedBytes 3@@ -584,7 +589,7 @@          let fieldByteCount = fromJust . T.fromVariant $ fixed !! 6 -        let headerSig  = T.mkSignature' "yyyyuua(yv)"+        let headerSig  = "yyyyuua(yv)"         fieldBytes <- getBytes fieldByteCount         let headerBytes = L.append fixedBytes fieldBytes         header <- unmarshal' headerSig headerBytes@@ -602,18 +607,24 @@         body <- unmarshal' bodySig bodyBytes          y <- case buildReceivedMessage typeCode fields of-                Right x -> return x-                Left x -> E.throwError $ MissingHeaderField x+                EitherM (Right x) -> return x+                EitherM (Left x) -> E.throwError $ MissingHeaderField x          return $ y serial flags body   findBodySignature :: [M.HeaderField] -> T.Signature-findBodySignature fields = fromMaybe empty signature where-        empty = T.mkSignature' ""+findBodySignature fields = fromMaybe "" signature where         signature = listToMaybe [x | M.Signature x <- fields] -buildReceivedMessage :: Word8 -> [M.HeaderField] -> Either Text +newtype EitherM a b = EitherM (Either a b)++instance Monad (EitherM a) where+        return = EitherM . Right+        (EitherM (Left x)) >>= _ = EitherM (Left x)+        (EitherM (Right x)) >>= k = k x++buildReceivedMessage :: Word8 -> [M.HeaderField] -> EitherM Text                         (M.Serial -> (Set.Set M.Flag) -> [T.Variant]                          -> M.ReceivedMessage) @@ -659,10 +670,7 @@         msg = M.Unknown typeCode flags body         in M.ReceivedUnknown serial sender msg -require :: Text -> [a] -> Either Text a-require _     (x:_) = Right x-require label _     = Left label--instance E.Error Text where-        strMsg = TL.pack+require :: Text -> [a] -> EitherM Text a+require _     (x:_) = return x+require label _     = EitherM $ Left label