packages feed

dbus-core 0.5 → 0.6

raw patch · 17 files changed

+4045/−3111 lines, 17 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- DBus.Message: errorFlags :: Error -> Set Flag
- DBus.Message: methodReturnFlags :: MethodReturn -> Set Flag
- DBus.Message: signalFlags :: Signal -> Set Flag
+ DBus.Connection: TextContainsNUL :: Text -> MarshalError
+ DBus.Connection: connectFirst :: [Address] -> IO Connection
+ DBus.MatchRule: Error :: MessageType
+ DBus.MatchRule: MatchRule :: Maybe MessageType -> Maybe BusName -> Maybe InterfaceName -> Maybe MemberName -> Maybe ObjectPath -> Maybe BusName -> [ParameterValue] -> MatchRule
+ DBus.MatchRule: MethodCall :: MessageType
+ DBus.MatchRule: MethodReturn :: MessageType
+ DBus.MatchRule: PathValue :: Word8 -> ObjectPath -> ParameterValue
+ DBus.MatchRule: Signal :: MessageType
+ DBus.MatchRule: StringValue :: Word8 -> Text -> ParameterValue
+ DBus.MatchRule: addMatch :: MatchRule -> MethodCall
+ DBus.MatchRule: data MatchRule
+ DBus.MatchRule: data MessageType
+ DBus.MatchRule: data ParameterValue
+ DBus.MatchRule: formatRule :: MatchRule -> Text
+ DBus.MatchRule: instance Eq MessageType
+ DBus.MatchRule: instance Eq ParameterValue
+ DBus.MatchRule: instance Show MessageType
+ DBus.MatchRule: instance Show ParameterValue
+ DBus.MatchRule: matchAll :: MatchRule
+ DBus.MatchRule: matchDestination :: MatchRule -> Maybe BusName
+ DBus.MatchRule: matchInterface :: MatchRule -> Maybe InterfaceName
+ DBus.MatchRule: matchMember :: MatchRule -> Maybe MemberName
+ DBus.MatchRule: matchParameters :: MatchRule -> [ParameterValue]
+ DBus.MatchRule: matchPath :: MatchRule -> Maybe ObjectPath
+ DBus.MatchRule: matchSender :: MatchRule -> Maybe BusName
+ DBus.MatchRule: matchType :: MatchRule -> Maybe MessageType
+ DBus.MatchRule: matches :: MatchRule -> ReceivedMessage -> Bool
+ DBus.Message: errorMessage :: Error -> Text
+ DBus.Message: receivedBody :: ReceivedMessage -> [Variant]
+ DBus.NameReservation: AllowReplacement :: RequestNameFlag
+ DBus.NameReservation: AlreadyOwner :: RequestNameReply
+ DBus.NameReservation: DoNotQueue :: RequestNameFlag
+ DBus.NameReservation: Exists :: RequestNameReply
+ DBus.NameReservation: InQueue :: RequestNameReply
+ DBus.NameReservation: NonExistent :: ReleaseNameReply
+ DBus.NameReservation: NotOwner :: ReleaseNameReply
+ DBus.NameReservation: PrimaryOwner :: RequestNameReply
+ DBus.NameReservation: Released :: ReleaseNameReply
+ DBus.NameReservation: ReplaceExisting :: RequestNameFlag
+ DBus.NameReservation: data ReleaseNameReply
+ DBus.NameReservation: data RequestNameFlag
+ DBus.NameReservation: data RequestNameReply
+ DBus.NameReservation: instance Show ReleaseNameReply
+ DBus.NameReservation: instance Show RequestNameFlag
+ DBus.NameReservation: instance Show RequestNameReply
+ DBus.NameReservation: mkReleaseNameReply :: MethodReturn -> Maybe ReleaseNameReply
+ DBus.NameReservation: mkRequestNameReply :: MethodReturn -> Maybe RequestNameReply
+ DBus.NameReservation: releaseName :: BusName -> MethodCall
+ DBus.NameReservation: requestName :: BusName -> [RequestNameFlag] -> MethodCall
+ DBus.Types: arrayFromBytes :: ByteString -> Array
+ DBus.Types: arrayToBytes :: Array -> Maybe ByteString
+ DBus.Types: instance Variable ByteString
- DBus.Message: Error :: ErrorName -> Serial -> Maybe BusName -> Set Flag -> [Variant] -> Error
+ DBus.Message: Error :: ErrorName -> Serial -> Maybe BusName -> [Variant] -> Error
- DBus.Message: MethodReturn :: Serial -> Maybe BusName -> Set Flag -> [Variant] -> MethodReturn
+ DBus.Message: MethodReturn :: Serial -> Maybe BusName -> [Variant] -> MethodReturn
- DBus.Message: Signal :: ObjectPath -> MemberName -> InterfaceName -> Maybe BusName -> Set Flag -> [Variant] -> Signal
+ DBus.Message: Signal :: ObjectPath -> MemberName -> InterfaceName -> Maybe BusName -> [Variant] -> Signal

Files

Examples/dbus-monitor.hs view
@@ -85,8 +85,9 @@ 	, "type='error'" 	] -onMessage :: ReceivedMessage -> IO ()-onMessage msg = putStrLn $ (TL.unpack $ formatMessage msg) ++ "\n"+onMessage :: Either UnmarshalError ReceivedMessage -> IO ()+onMessage (Right msg) = putStrLn $ (TL.unpack $ formatMessage msg) ++ "\n"+onMessage (Left err) = error $ show err  main :: IO () main = do
Makefile view
@@ -4,8 +4,10 @@ 	hs/DBus/Connection.hs \ 	hs/DBus/Constants.hs \ 	hs/DBus/Introspection.hs \+	hs/DBus/MatchRule.hs \ 	hs/DBus/Message.hs \ 	hs/DBus/Message/Internal.hs \+	hs/DBus/NameReservation.hs \ 	hs/DBus/Types.hs \ 	hs/DBus/Util.hs \ 	hs/DBus/Wire.hs \
Tests.nw view
@@ -463,18 +463,16 @@ 	arbitrary = do 		serial <- arbitrary 		dest   <- arbitrary-		flags  <- fmap Set.fromList arbitrary 		Structure body <- arbitrary-		return $ MethodReturn serial dest flags body+		return $ MethodReturn serial dest body  instance Arbitrary Error where 	arbitrary = do 		name   <- arbitrary 		serial <- arbitrary 		dest   <- arbitrary-		flags  <- fmap Set.fromList arbitrary 		Structure body <- arbitrary-		return $ Error name serial dest flags body+		return $ Error name serial dest body  instance Arbitrary Signal where 	arbitrary = do@@ -482,9 +480,8 @@ 		member <- arbitrary 		iface  <- arbitrary 		dest   <- arbitrary-		flags  <- fmap Set.fromList arbitrary 		Structure body <- arbitrary-		return $ Signal path member iface dest flags body+		return $ Signal path member iface dest body  @ \subsection{Wire format} 
dbus-core.cabal view
@@ -1,5 +1,5 @@ name: dbus-core-version: 0.5+version: 0.6 synopsis: Low-level D-Bus protocol implementation license: GPL license-file: License.txt@@ -51,7 +51,9 @@     DBus.Connection     DBus.Constants     DBus.Introspection+    DBus.MatchRule     DBus.Message+    DBus.NameReservation     DBus.Types    other-modules:
dbus-core.nw view
@@ -98,2988 +98,3494 @@ <<text imports>> <<type imports>> -@ DBus types are divided into two categories, ``atomic'' and ``container''-types. Atoms are actual values -- strings, numbers, etc. Containers store-atoms and other containers. The most interesting difference between the two-is that atoms may be used as the keys in associative mappings-(``dictionaries'').--Internally, types are represented using an enumeration.--<<DBus/Types.hs>>=-data Type-	= DBusBoolean-	| DBusByte-	| DBusInt16-	| DBusInt32-	| DBusInt64-	| DBusWord16-	| DBusWord32-	| DBusWord64-	| DBusDouble-	| DBusString-	| DBusSignature-	| DBusObjectPath-	| DBusVariant-	| DBusArray Type-	| DBusDictionary Type Type-	| DBusStructure [Type]-	deriving (Show, Eq)--<<DBus/Types.hs>>=-isAtomicType :: Type -> Bool-isAtomicType DBusBoolean    = True-isAtomicType DBusByte       = True-isAtomicType DBusInt16      = True-isAtomicType DBusInt32      = True-isAtomicType DBusInt64      = True-isAtomicType DBusWord16     = True-isAtomicType DBusWord32     = True-isAtomicType DBusWord64     = True-isAtomicType DBusDouble     = True-isAtomicType DBusString     = True-isAtomicType DBusSignature  = True-isAtomicType DBusObjectPath = True-isAtomicType _              = False--@ Each type can be converted to a textual representation, used in ``type-signatures'' or for debugging.--<<DBus/Types.hs>>=-typeCode :: Type -> Text--<<type exports>>=-  -- * Available types-  Type (..)-, typeCode--@ Certain Haskell types are considered ``built-in'' D-Bus types; that is,-they are directly represented in the D-Bus protocol.--<<DBus/Types.hs>>=-class (Show a, Eq a) => Builtin a where-	builtinDBusType :: a -> Type--@ \subsection{Variants}--A wrapper type is needed for safely storing generic D-Bus values in Haskell.-The D-Bus ``variant'' type is perfect for this, because variants may store-any D-Bus value.--To cleanly store any D-Bus type, without exposing the internal storage-mechanism, requires existential quantification and some run-time casting.--<<type extensions>>=-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE ExistentialQuantification #-}--<<type imports>>=-import Data.Typeable (Typeable, cast)--@ Any type which is an instance of {\tt Variable} is considered a valid-D-Bus value, because it can be used to construct {\tt Variant}s. However,-outside of this module, {\tt Variant}s can only be constructed from-pre-defined types.--<<DBus/Types.hs>>=-data Variant = forall a. (Variable a, Builtin a, Typeable a) => Variant a-	deriving (Typeable)--class Variable a where-	toVariant :: a -> Variant-	fromVariant :: Variant -> Maybe a--<<type exports>>=-  -- * Variants-, Variant-, Variable (..)--@ Variants can be printed, for debugging purposes -- this instance shouldn't-be parsed or inspected or anything like that, since the output format might-change drastically.--<<DBus/Types.hs>>=-instance Show Variant where-	showsPrec d (Variant x) = showParen (d > 10) $-		s "Variant " . shows code . s " " . showsPrec 11 x-		where code = typeCode . builtinDBusType $ x-		      s    = showString--@ In the test suite, it's useful to test that two variants have the same-value.--<<DBus/Types.hs>>=-instance Eq Variant where-	(Variant x) == (Variant y) = cast x == Just y--@ Since many operations on D-Bus values depend on having the correct type,-{\tt variantType} is used to retrieve which type is actually stored within-a {\tt Variant}.--<<DBus/Types.hs>>=-variantType :: Variant -> Type-variantType (Variant x) = builtinDBusType x--<<type exports>>=-, variantType--@ These helper macros will be used for defining instances of Haskell types.--<<DBus/Types.hs>>=-#define INSTANCE_BUILTIN(HASKELL, DBUS) \-	instance Builtin HASKELL where \-		{ builtinDBusType _ = DBUS };--#define INSTANCE_VARIABLE(HASKELL) \-	instance Variable HASKELL where \-		{ toVariant = Variant \-		; fromVariant (Variant x) = cast x };--#define BUILTIN_VARIABLE(HASKELL, DBUS) \-	INSTANCE_BUILTIN(HASKELL, DBUS) \-	INSTANCE_VARIABLE(HASKELL)--@ Since {\tt Variant}s are D-Bus values themselves, they have a type.--<<DBus/Types.hs>>=-BUILTIN_VARIABLE(Variant, DBusVariant)--@ \subsection{Numerics}--D-Bus supports most common numeric types:--\begin{table}[h]-\caption{D-Bus Numeric types}-\begin{center}-\begin{tabular}{ll}-\toprule-Type        & Description \\-\midrule-Boolean     & Either {\tt True} or {\tt False} \\-Byte        & 8-bit unsigned integer \\-Int16       & 16-bit signed integer \\-Int32       & 32-bit signed integer \\-Int64       & 64-bit signed integer \\-Word16      & 16-bit unsigned integer \\-Word32      & 32-bit unsigned integer \\-Word64      & 64-bit unsigned integer \\-Double      & 64-bit IEEE754 floating-point \\-\bottomrule-\end{tabular}-\end{center}-\end{table}--All D-Bus numeric types are fixed-length, so the {\tt Int} and {\tt Integer}-types can't be used. Instead, instances for the fixed-length integer types-are defined and any others will have to be converted.--<<type imports>>=-import Data.Word (Word8, Word16, Word32, Word64)-import Data.Int (Int16, Int32, Int64)--<<DBus/Types.hs>>=-BUILTIN_VARIABLE(Bool,   DBusBoolean)-BUILTIN_VARIABLE(Word8,  DBusByte)-BUILTIN_VARIABLE(Int16,  DBusInt16)-BUILTIN_VARIABLE(Int32,  DBusInt32)-BUILTIN_VARIABLE(Int64,  DBusInt64)-BUILTIN_VARIABLE(Word16, DBusWord16)-BUILTIN_VARIABLE(Word32, DBusWord32)-BUILTIN_VARIABLE(Word64, DBusWord64)-BUILTIN_VARIABLE(Double, DBusDouble)--@ \subsection{Strings}--Strings are a weird case; the built-in type, {\tt String}, is horribly-inefficent. To provide better performance for large strings, packed Unicode-strings defined in {\tt Data.Text} are used internally.--<<DBus/Types.hs>>=-BUILTIN_VARIABLE(TL.Text, DBusString)--@ There's two different {\tt Text} types, strict and lazy. It'd be a pain-to store both and have to convert later, so instead, all strict {\tt Text}-values are converted to lazy values.--<<type imports>>=-import qualified Data.Text as T--<<DBus/Types.hs>>=-instance Variable T.Text where-	toVariant = toVariant . TL.fromChunks . (:[])-	fromVariant = fmap (T.concat . TL.toChunks) . fromVariant--@ Built-in {\tt String}s can still be stored, of course, but it requires-a language extension.--<<type extensions>>=-{-# LANGUAGE TypeSynonymInstances #-}--<<DBus/Types.hs>>=-instance Variable String where-	toVariant = toVariant . TL.pack-	fromVariant = fmap TL.unpack . fromVariant--@ \subsection{Signatures}--@ Valid DBus types must obey certain rules, such as ``dict keys must be-atomic'', which are difficult to express in the Haskell type system.  A-{\tt Signature} is guaranteed to be valid according to these rules. Creating-one requires using the {\tt mkSignature} function, which will convert a valid-DBus signature string into a {\tt Signature}.--<<DBus/Types.hs>>=-BUILTIN_VARIABLE(Signature, DBusSignature)-data Signature = Signature { signatureTypes :: [Type] }-	deriving (Eq, Typeable)--instance Show Signature where-	showsPrec d x = showParen (d > 10) $-		showString "Signature " . shows (strSignature x)--@ Signatures can also be converted back into text, by concatenating the-type codes of their contained types.--<<DBus/Types.hs>>=-strSignature :: Signature -> Text-strSignature (Signature ts) = TL.concat $ map typeCode ts--@ It doesn't make much sense to sort signatures, but since they can be used-as dictionary keys, it's useful to have them as an instance of {\tt Ord}.--<<DBus/Types.hs>>=-instance Ord Signature where-	compare x y = compare (strSignature x) (strSignature y)--<<type exports>>=-  -- * Signatures-, Signature-, signatureTypes-, strSignature--@ \subsubsection{Type codes}--@ For atomic types, the type code is a single letter. Arrays, structures,-and dictionary types are multiple characters.--<<DBus/Types.hs>>=-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"--@ 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>>=-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>>=-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>>=-typeCode (DBusStructure ts) = TL.concat $-	["("] ++ map typeCode ts ++ [")"]--@ \subsubsection{Parsing}--When parsing, additional restrictions apply which are not inherent to the-D-Bus type system:--\begin{itemize}-\item Signatures may be at most 255 characters long.-\end{itemize}--Parsec is used to parse signatures.--<<type imports>>=-import Text.Parsec ((<|>))-import qualified Text.Parsec as P-import DBus.Util (checkLength, parseMaybe)--<<DBus/Types.hs>>=-mkSignature :: Text -> Maybe Signature-mkSignature = (parseMaybe sigParser =<<) . checkLength 255 . TL.unpack where-	sigParser = do-		types <- P.many parseType-		P.eof-		return $ Signature types-	parseType = parseAtom <|> parseContainer-	parseContainer =-		    parseArray-		<|> parseStruct-		<|> (P.char 'v' >> return DBusVariant)-	parseAtom =-		    (P.char 'b' >> return DBusBoolean)-		<|> (P.char 'y' >> return DBusByte)-		<|> (P.char 'n' >> return DBusInt16)-		<|> (P.char 'i' >> return DBusInt32)-		<|> (P.char 'x' >> return DBusInt64)-		<|> (P.char 'q' >> return DBusWord16)-		<|> (P.char 'u' >> return DBusWord32)-		<|> (P.char 't' >> return DBusWord64)-		<|> (P.char 'd' >> return DBusDouble)-		<|> (P.char 's' >> return DBusString)-		<|> (P.char 'g' >> return DBusSignature)-		<|> (P.char 'o' >> return DBusObjectPath)-	parseArray = do-		P.char 'a'-		parseDict <|> do-		t <- parseType-		return $ DBusArray t-	parseDict = do-		P.char '{'-		keyType <- parseAtom-		valueType <- parseType-		P.char '}'-		return $ DBusDictionary keyType valueType-	parseStruct = do-		P.char '('-		types <- P.many parseType-		P.char ')'-		return $ DBusStructure types--@ Since many signatures are defined as string literals, it's useful to-have a helper function to construct a signature directly from a string.-If the input string is invalid, {\tt error} will be called.--<<type imports>>=-import DBus.Util (mkUnsafe)--<<DBus/Types.hs>>=-mkSignature' :: Text -> Signature-mkSignature' = mkUnsafe "signature" mkSignature--@ 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'--@ \subsection{Object paths}--<<DBus/Types.hs>>=-BUILTIN_VARIABLE(ObjectPath, DBusObjectPath)-newtype ObjectPath = ObjectPath-	{ strObjectPath :: Text-	}-	deriving (Show, Eq, Ord, Typeable)--@ An object path may be one of--\begin{itemize}-\item The root path, {\tt "/"}.-\item {\tt '/'}, followed by one or more element names. Each element name-      contains characters in the set {\tt [a-zA-Z0-9\_]}, and must have at-      least one character.-\end{itemize}--Element names are separated by {\tt '/'}, and the path may not end in-{\tt '/'} unless it is the root path.--<<DBus/Types.hs>>=-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--<<type exports>>=-  -- * Object paths-, ObjectPath-, strObjectPath-, mkObjectPath-, mkObjectPath'--@ \subsection{Arrays}--Arrays are homogenous sequences of any valid DBus type. Arrays might be-empty, so the type they contain is stored instead of being calculated from-their contents (as in {\tt variantType}).--TODO: There ought to be a specialised constructor for arrays of bytes, based-on the {\tt ByteString} type. Storing bytes individually in a list of-{\tt Variant}s is inefficient.--<<DBus/Types.hs>>=-INSTANCE_VARIABLE(Array)-data Array = Array-	{ arrayType  :: Type-	, arrayItems :: [Variant]-	}-	deriving (Eq, Typeable)--instance Builtin Array where-	builtinDBusType = DBusArray . arrayType--<<type exports>>=-  -- * Arrays-, Array-, arrayType-, arrayItems--@ Like {\tt Variant}, deriving {\tt Show} for {\tt Array} is mostly-just useful for debugging.--<<DBus/Types.hs>>=-instance Show Array where-	showsPrec d (Array t vs) = showParen (d > 10) $-		s "Array " . showSig . s " [" . s valueString . s "]" where-			s = showString-			showSig = shows $ typeCode t-			vs' = [show x | (Variant x) <- vs]-			valueString = intercalate ", " vs'--@ Clients constructing an array must provide the expected item type, which-will be checked for validity. Every item in the array will be checked against-the item type, to ensure the array is homogenous.--<<DBus/Types.hs>>=-arrayFromItems :: Type -> [Variant] -> Maybe Array-arrayFromItems t vs = do-	mkSignature (typeCode t)-	if all (\x -> variantType x == t) vs-		then Just $ Array t vs-		else Nothing--@ Additionally, for ease of use, an {\tt Array} can be converted directly-to/from lists of {\tt Variable} values.--<<DBus/Types.hs>>=-toArray :: Variable a => Type -> [a] -> Maybe Array-toArray t = arrayFromItems t . map toVariant--fromArray :: Variable a => Array -> Maybe [a]-fromArray = mapM fromVariant . arrayItems--<<type exports>>=-, toArray-, fromArray-, arrayFromItems--@ \subsection{Dictionaries}--Dictionaries are a homogenous (key $\rightarrow$ value) mapping, where the-key type must be atomic. Values may be of any valid DBus type. Like-{\tt Array}, {\tt Dictionary} stores its contained types.--<<DBus/Types.hs>>=-INSTANCE_VARIABLE(Dictionary)-data Dictionary = Dictionary-	{ dictionaryKeyType   :: Type-	, dictionaryValueType :: Type-	, dictionaryItems     :: [(Variant, Variant)]-	}-	deriving (Eq, Typeable)--instance Builtin Dictionary where-	builtinDBusType (Dictionary kt vt _) = DBusDictionary kt vt--<<type exports>>=-  -- * Dictionaries-, Dictionary-, dictionaryItems-, dictionaryKeyType-, dictionaryValueType--@ {\tt show}ing a {\tt Dictionary} displays the mapping in a more readable-format than a list of pairs.--<<type imports>>=-import Data.List (intercalate)--<<DBus/Types.hs>>=-instance Show Dictionary where-	showsPrec d (Dictionary kt vt pairs) = showParen (d > 10) $-		s "Dictionary " . showSig . s " {" . s valueString . s "}" where-			s = showString-			showSig = shows $ TL.append (typeCode kt) (typeCode vt)-			valueString = intercalate ", " $ map showPair pairs-			showPair ((Variant k), (Variant v)) =-				show k ++ " -> " ++ show v--@ Constructing a {\tt Dictionary} works like constructing an {\tt Array},-except that there are two types to check, and the key type must be atomic.--<<type imports>>=-import Control.Monad (unless)--<<DBus/Types.hs>>=-dictionaryFromItems :: Type -> Type -> [(Variant, Variant)] -> Maybe Dictionary-dictionaryFromItems kt vt pairs = do-	unless (isAtomicType kt) Nothing-	mkSignature (typeCode kt)-	mkSignature (typeCode vt)-	-	let sameType (k, v) = variantType k == kt &&-	                      variantType v == vt-	if all sameType pairs-		then Just $ Dictionary kt vt pairs-		else Nothing--@ The closest match for dictionary semantics in Haskell is the-{\tt Data.Map.Map} type. Therefore, the utility conversion functions work-with {\tt Map}s instead of pair lists.--<<type imports>>=-import Control.Arrow ((***))-import qualified Data.Map as Map--<<DBus/Types.hs>>=-toDictionary :: (Variable a, Variable b) => Type -> Type -> Map.Map a b-             -> Maybe Dictionary-toDictionary kt vt = dictionaryFromItems kt vt . pairs where-	pairs = map (toVariant *** toVariant) . Map.toList--<<type imports>>=-import Control.Monad (forM)--<<DBus/Types.hs>>=-fromDictionary :: (Variable a, Ord a, Variable b) => Dictionary-               -> Maybe (Map.Map a b)-fromDictionary (Dictionary _ _ vs) = do-	pairs <- forM vs $ \(k, v) -> do-		k' <- fromVariant k-		v' <- fromVariant v-		return (k', v')-	return $ Map.fromList pairs--<<type exports>>=-, toDictionary-, fromDictionary-, dictionaryFromItems--@ \subsubsection{Converting between {\tt Array} and {\tt Dictionary}}--Converting between {\tt Array} and {\tt Dictionary} is useful when-(un)marshaling -- dictionaries can be thought of as arrays of two-element-structures, much as a {\tt Map} is a list of pairs.--<<DBus/Types.hs>>=-dictionaryToArray :: Dictionary -> Array-dictionaryToArray (Dictionary kt vt items) = array where-	Just array = toArray itemType structs-	itemType = DBusStructure [kt, vt]-	structs = [Structure [k, v] | (k, v) <- items]--<<DBus/Types.hs>>=-arrayToDictionary :: Array -> Maybe Dictionary-arrayToDictionary (Array t items) = do-	let toPair x = do-		struct <- fromVariant x-		case struct of-			Structure [k, v] -> Just (k, v)-			_                -> Nothing-	(kt, vt) <- case t of-		DBusStructure [kt, vt] -> Just (kt, vt)-		_                      -> Nothing-	pairs <- mapM toPair items-	dictionaryFromItems kt vt pairs--<<type exports>>=-, dictionaryToArray-, arrayToDictionary--@ \subsection{Structures}--A heterogeneous, fixed-length container; equivalent in purpose to a Haskell-tuple.--<<DBus/Types.hs>>=-INSTANCE_VARIABLE(Structure)-data Structure = Structure [Variant]-	deriving (Show, Eq, Typeable)--instance Builtin Structure where-	builtinDBusType (Structure vs) = DBusStructure $ map variantType vs--<<type exports>>=-  -- * Structures-, Structure (..)--@ \subsection{Names}--Various aspects of DBus require the use of specially-formatted strings,-called ``names''. All names are limited to 255 characters, and use subsets-of ASCII.--Since all names have basically the same structure (a {\tt newtype}-declaration and some helper functions), I define a macro to automate-the definitions.--<<DBus/Types.hs>>=-#define NAME_TYPE(TYPE, NAME) \-	newtype TYPE = TYPE {str##TYPE :: Text} \-		deriving (Show, Eq, Ord); \-		                          \-	instance Variable TYPE where \-		{ toVariant = toVariant . str##TYPE \-		; fromVariant = (mk##TYPE =<<) . fromVariant }; \-		                                                \-	mk##TYPE##' :: Text -> TYPE; \-	mk##TYPE##' = mkUnsafe NAME mk##TYPE--<<type exports>>=--- * Names--@ \subsubsection{Bus names}--There are two forms of bus names, ``unique'' and ``well-known''.--Unique names begin with {\tt `:'} and contain two or more elements, separated-by {\tt `.'}. Each element consists of characters from the set-{\tt [a-zA-Z0-9\_-]}.--Well-known names contain two or more elements, separated by {\tt `.'}. Each-element consists of characters from the set {\tt [a-zA-Z0-9\_-]}, and must-not start with a digit.--<<DBus/Types.hs>>=-NAME_TYPE(BusName, "bus name")--mkBusName :: Text -> Maybe BusName-mkBusName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where-	c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"-	c' = c ++ ['0'..'9']-	parser = (unique <|> wellKnown) >> P.eof >> return (BusName s)-	unique = P.char ':' >> elems c'-	wellKnown = elems c-	elems start = elem' start >> P.many1 (P.char '.' >> elem' start)-	elem' start = P.oneOf start >> P.many (P.oneOf c')--<<type exports>>=-  -- ** Bus names-, BusName-, strBusName-, mkBusName-, mkBusName'--@ \subsubsection{Interface names}--An interface name consists of two or more {\tt '.'}-separated elements. Each-element constists of characters from the set {\tt [a-zA-Z0-9\_]}, may not-start with a digit, and must have at least one character.--<<DBus/Types.hs>>=-NAME_TYPE(InterfaceName, "interface name")--mkInterfaceName :: Text -> Maybe InterfaceName-mkInterfaceName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where-	c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"-	c' = c ++ ['0'..'9']-	element = P.oneOf c >> P.many (P.oneOf c')-	name = element >> P.many1 (P.char '.' >> element)-	parser = name >> P.eof >> return (InterfaceName s)--<<type exports>>=-  -- ** Interface names-, InterfaceName-, strInterfaceName-, mkInterfaceName-, mkInterfaceName'--@ \subsubsection{Error names}--Error names have the same format as interface names, so the parser logic-can just be re-purposed.--<<DBus/Types.hs>>=-NAME_TYPE(ErrorName, "error name")--mkErrorName :: Text -> Maybe ErrorName-mkErrorName = fmap (ErrorName . strInterfaceName) . mkInterfaceName--<<type exports>>=-  -- ** Error names-, ErrorName-, strErrorName-, mkErrorName-, mkErrorName'--@ \subsubsection{Member names}--Member names must contain only characters from the set {\tt [a-zA-Z0-9\_]},-may not begin with a digit, and must be at least one character long.--<<DBus/Types.hs>>=-NAME_TYPE(MemberName, "member name")--mkMemberName :: Text -> Maybe MemberName-mkMemberName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where-	c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"-	c' = c ++ ['0'..'9']-	name = P.oneOf c >> P.many (P.oneOf c')-	parser = name >> P.eof >> return (MemberName s)--<<type exports>>=-  -- ** Member names-, MemberName-, strMemberName-, mkMemberName-, mkMemberName'--@-\section{Messages}--@ To prevent internal details of messages from leaking out to clients,-declarations are contained in an internal module and then re-exported-in the public module.--<<DBus/Message.hs>>=-<<copyright>>-module DBus.Message-	(<<message exports>>-	) where-import DBus.Message.Internal--<<DBus/Message/Internal.hs>>=-<<copyright>>-module DBus.Message.Internal where-import qualified Data.Set as S-import Data.Word (Word8, Word32)-import qualified DBus.Types as T--<<DBus/Message/Internal.hs>>=-class Message a where-	messageTypeCode     :: a -> Word8-	messageHeaderFields :: a -> [HeaderField]-	messageFlags        :: a -> S.Set Flag-	messageBody         :: a -> [T.Variant]--<<message exports>>=-Message ( messageFlags-        , messageBody-        )--@ \subsection{Flags}--The instance of {\tt Ord} only exists for storing flags in a set. Flags have-no inherent ordering.--<<DBus/Message/Internal.hs>>=-data Flag-	= NoReplyExpected-	| NoAutoStart-	deriving (Show, Eq, Ord)--<<message exports>>=-, Flag (..)--@ \subsection{Header fields}--<<DBus/Message/Internal.hs>>=-data HeaderField-	= Path        T.ObjectPath-	| Interface   T.InterfaceName-	| Member      T.MemberName-	| ErrorName   T.ErrorName-	| ReplySerial Serial-	| Destination T.BusName-	| Sender      T.BusName-	| Signature   T.Signature-	deriving (Show, Eq)--@ \subsection{Serials}--{\tt Serial} is just a wrapper around {\tt Word32}, to provide a bit of-added type-safety.--<<DBus/Message/Internal.hs>>=-newtype Serial = Serial { serialValue :: Word32 }-	deriving (Eq, Ord)--instance Show Serial where-	show (Serial x) = show x--instance T.Variable Serial where-	toVariant (Serial x) = T.toVariant x-	fromVariant = fmap Serial . T.fromVariant--@ Additionally, some useful functions exist for incrementing serials.--<<DBus/Message/Internal.hs>>=-firstSerial :: Serial-firstSerial = Serial 1--nextSerial :: Serial -> Serial-nextSerial (Serial x) = Serial (x + 1)--@ The {\tt Serial} constructor isn't useful to clients, because building-arbitrary serials doesn't make any sense.--<<message exports>>=-, Serial-, serialValue--@ \subsection{Message types}--<<DBus/Message/Internal.hs>>=-maybe' :: (a -> b) -> Maybe a -> [b]-maybe' f = maybe [] (\x' -> [f x'])--@ \subsubsection{Method calls}--<<DBus/Message/Internal.hs>>=-data MethodCall = MethodCall-	{ methodCallPath        :: T.ObjectPath-	, methodCallMember      :: T.MemberName-	, methodCallInterface   :: Maybe T.InterfaceName-	, methodCallDestination :: Maybe T.BusName-	, methodCallFlags       :: S.Set Flag-	, methodCallBody        :: [T.Variant]-	}-	deriving (Show, Eq)--instance Message MethodCall where-	messageTypeCode _ = 1-	messageFlags      = methodCallFlags-	messageBody       = methodCallBody-	messageHeaderFields m = concat-		[ [ Path    $ methodCallPath m-		  ,  Member $ methodCallMember m-		  ]-		, maybe' Interface . methodCallInterface $ m-		, maybe' Destination . methodCallDestination $ m-		]--<<message exports>>=-, MethodCall (..)--@ \subsubsection{Method returns}--<<DBus/Message/Internal.hs>>=-data MethodReturn = MethodReturn-	{ methodReturnSerial      :: Serial-	, methodReturnDestination :: Maybe T.BusName-	, methodReturnFlags       :: S.Set Flag-	, methodReturnBody        :: [T.Variant]-	}-	deriving (Show, Eq)--instance Message MethodReturn where-	messageTypeCode _ = 2-	messageFlags      = methodReturnFlags-	messageBody       = methodReturnBody-	messageHeaderFields m = concat-		[ [ ReplySerial $ methodReturnSerial m-		  ]-		, maybe' Destination . methodReturnDestination $ m-		]--<<message exports>>=-, MethodReturn (..)--@ \subsubsection{Errors}--<<DBus/Message/Internal.hs>>=-data Error = Error-	{ errorName        :: T.ErrorName-	, errorSerial      :: Serial-	, errorDestination :: Maybe T.BusName-	, errorFlags       :: S.Set Flag-	, errorBody        :: [T.Variant]-	}-	deriving (Show, Eq)--instance Message Error where-	messageTypeCode _ = 3-	messageFlags      = errorFlags-	messageBody       = errorBody-	messageHeaderFields m = concat-		[ [ ErrorName   $ errorName m-		  , ReplySerial $ errorSerial m-		  ]-		, maybe' Destination . errorDestination $ m-		]--<<message exports>>=-, Error (..)--@ \subsubsection{Signals}--<<DBus/Message/Internal.hs>>=-data Signal = Signal-	{ signalPath        :: T.ObjectPath-	, signalMember      :: T.MemberName-	, signalInterface   :: T.InterfaceName-	, signalDestination :: Maybe T.BusName-	, signalFlags       :: S.Set Flag-	, signalBody        :: [T.Variant]-	}-	deriving (Show, Eq)--instance Message Signal where-	messageTypeCode _ = 4-	messageFlags      = signalFlags-	messageBody       = signalBody-	messageHeaderFields m = concat-		[ [ Path      $ signalPath m-		  , Member    $ signalMember m-		  , Interface $ signalInterface m-		  ]-		, maybe' Destination . signalDestination $ m-		]--<<message exports>>=-, Signal (..)--@ \subsubsection{Unknown messages}--Unknown messages are used for storing information about messages without-a recognized type code. They are not instances of {\tt Message}, because-if they were, then clients could accidentally send invalid messages over-the bus.--<<DBus/Message/Internal.hs>>=-data Unknown = Unknown-	{ unknownType    :: Word8-	, unknownFlags   :: S.Set Flag-	, unknownBody    :: [T.Variant]-	}-	deriving (Show, Eq)--<<message exports>>=-, Unknown (..)--@ \subsection{Received messages}--Messages received from a bus have additional fields which do not make sense-when sending.--If a message has an unknown type, its serial and origin are still useful-for sending an error reply.--<<DBus/Message/Internal.hs>>=-data ReceivedMessage-	= ReceivedMethodCall   Serial (Maybe T.BusName) MethodCall-	| ReceivedMethodReturn Serial (Maybe T.BusName) MethodReturn-	| ReceivedError        Serial (Maybe T.BusName) Error-	| ReceivedSignal       Serial (Maybe T.BusName) Signal-	| ReceivedUnknown      Serial (Maybe T.BusName) Unknown-	deriving (Show, Eq)--<<DBus/Message/Internal.hs>>=-receivedSerial :: ReceivedMessage -> Serial-receivedSerial (ReceivedMethodCall   s _ _) = s-receivedSerial (ReceivedMethodReturn s _ _) = s-receivedSerial (ReceivedError        s _ _) = s-receivedSerial (ReceivedSignal       s _ _) = s-receivedSerial (ReceivedUnknown      s _ _) = s--<<DBus/Message/Internal.hs>>=-receivedSender :: ReceivedMessage -> Maybe T.BusName-receivedSender (ReceivedMethodCall   _ s _) = s-receivedSender (ReceivedMethodReturn _ s _) = s-receivedSender (ReceivedError        _ s _) = s-receivedSender (ReceivedSignal       _ s _) = s-receivedSender (ReceivedUnknown      _ s _) = s--<<message exports>>=-, ReceivedMessage (..)-, receivedSerial-, receivedSender--@-\section{Wire format}--<<DBus/Wire.hs>>=-<<copyright>>-<<text extensions>>-<<wire extensions>>-{-# LANGUAGE DeriveDataTypeable #-}-module DBus.Wire (<<wire exports>>) where-<<text imports>>-<<wire imports>>-import Control.Monad (when, unless)-import Data.Maybe (fromJust, listToMaybe, fromMaybe)-import Data.Word (Word8, Word32, Word64)-import Data.Int (Int16, Int32, Int64)-import Data.Typeable (Typeable)--import qualified DBus.Types as T--@ \subsection{Endianness}--<<DBus/Wire.hs>>=-data Endianness = LittleEndian | BigEndian-	deriving (Show, Eq)--encodeEndianness :: Endianness -> Word8-encodeEndianness LittleEndian = 108-encodeEndianness BigEndian    = 66--decodeEndianness :: Word8 -> Maybe Endianness-decodeEndianness 108 = Just LittleEndian-decodeEndianness 66  = Just BigEndian-decodeEndianness _   = Nothing--<<wire exports>>=-  Endianness (..)--@ \subsection{Alignment}--Every built-in type has an associated alignment. If a value of the given-type is marshaled, it must have {\sc nul} bytes inserted until it starts-on a byte index divisible by its alignment.--<<DBus/Wire.hs>>=-alignment :: T.Type -> Word8-<<alignments>>--padding :: Word64 -> Word8 -> Word64-padding current count = required where-	count' = fromIntegral count-	missing = mod current count'-	required = if missing > 0-		then count' - missing-		else 0--<<wire exports>>=-, alignment--@ \subsection{Marshaling}--Marshaling is implemented using an error transformer over an internal-state.--<<wire imports>>=-import qualified Control.Monad.State as ST-import qualified Control.Monad.Error as E-import qualified Data.ByteString.Lazy as L--<<wire extensions>>=-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--<<DBus/Wire.hs>>=-data MarshalState = MarshalState Endianness L.ByteString-newtype MarshalM a = MarshalM (E.ErrorT MarshalError (ST.State MarshalState) a)-	deriving (Monad, E.MonadError MarshalError, ST.MonadState MarshalState)-type Marshal = MarshalM ()--@ Clients can perform marshaling via {\tt marshal} and {\tt runMarshal},-which will generate a {\tt ByteString} with the fully marshaled data.--<<wire exports>>=-, MarshalM-, Marshal-, marshal-, runMarshal--<<DBus/Wire.hs>>=-runMarshal :: Marshal -> Endianness -> Either MarshalError L.ByteString-runMarshal (MarshalM m) e = case ST.runState (E.runErrorT m) initialState of-	(Right _, MarshalState _ bytes) -> Right bytes-	(Left  x, _) -> Left x-	where initialState = MarshalState e L.empty--<<DBus/Wire.hs>>=-marshal :: T.Variant -> Marshal-marshal v = marshalType (T.variantType v) where-	x :: T.Variable a => a-	x = fromJust . T.fromVariant $ v-	marshalType :: T.Type -> Marshal-	<<marshalers>>--@ TODO: describe these functions--<<DBus/Wire.hs>>=-append :: L.ByteString -> Marshal-append bs = do-	(MarshalState e bs') <- ST.get-	ST.put $ MarshalState e (L.append bs' bs)--<<DBus/Wire.hs>>=-pad :: Word8 -> Marshal-pad count = do-	(MarshalState _ bytes) <- ST.get-	let padding' = padding (fromIntegral . L.length $ bytes) count-	append $ L.replicate (fromIntegral padding') 0--@ Most numeric values already have marshalers implemented in the-{\tt Data.Binary.Put} module; this function lets them be re-used easily.--<<wire imports>>=-import qualified Data.Binary.Put as P--<<DBus/Wire.hs>>=-marshalPut :: (a -> P.Put) -> a -> Marshal-marshalPut put x = do-	let bytes = P.runPut $ put x-	(MarshalState e _) <- ST.get-	pad . fromIntegral . L.length $ bytes-	append $ case e of-		BigEndian -> bytes-		LittleEndian -> L.reverse bytes--@ \subsubsection{Errors}--Marshaling can fail for four reasons:--\begin{itemize}-\item The message exceeds the maximum message size of $2^{27}$ bytes.-\item An array in the message exceeds the maximum array size of $2^{26}$ bytes.-\item The body's signature is not valid (for example, more than 255 fields).-\item A variant's signature is not valid -- same causes as an invalid body-      signature.-\end{itemize}--<<DBus/Wire.hs>>=-data MarshalError-	= MessageTooLong Word64-	| ArrayTooLong Word64-	| InvalidBodySignature Text-	| InvalidVariantSignature Text-	deriving (Eq, Typeable)--instance Show MarshalError where-	show (MessageTooLong x) = concat-		["Message too long (", show x, " bytes)."]-	show (ArrayTooLong x) = concat-		["Array too long (", show x, " bytes)."]-	show (InvalidBodySignature x) = concat-		["Invalid body signature: ", show x]-	show (InvalidVariantSignature x) = concat-		["Invalid variant signature: ", show x]--instance E.Error MarshalError--<<wire exports>>=-, MarshalError (..)--@ \subsection{Unmarshaling}--Unmarshaling also uses an error transformer and internal state.--<<wire exports>>=-, Unmarshal-, unmarshal-, runUnmarshal--<<DBus/Wire.hs>>=-data UnmarshalState = UnmarshalState Endianness L.ByteString Word64-newtype Unmarshal a = Unmarshal (E.ErrorT UnmarshalError (ST.State UnmarshalState) a)-	deriving (Monad, Functor, E.MonadError UnmarshalError, ST.MonadState UnmarshalState)--<<DBus/Wire.hs>>=-runUnmarshal :: Unmarshal a -> Endianness -> L.ByteString -> Either UnmarshalError a-runUnmarshal (Unmarshal m) e bytes = ST.evalState (E.runErrorT m) state where-	state = UnmarshalState e bytes 0--<<DBus/Wire.hs>>=-unmarshal :: T.Signature -> Unmarshal [T.Variant]-unmarshal = mapM unmarshalType . T.signatureTypes--unmarshalType :: T.Type -> Unmarshal T.Variant-<<unmarshalers>>--@ TODO: describe these functions--<<DBus/Wire.hs>>=-consume :: Word64 -> Unmarshal L.ByteString-consume count = do-	(UnmarshalState e bytes offset) <- ST.get-	let bytes' = L.drop (fromIntegral offset) bytes-	let x = L.take (fromIntegral count) bytes'-	unless (L.length x == fromIntegral count) $-		E.throwError $ UnexpectedEOF offset-	-	ST.put $ UnmarshalState e bytes (offset + count)-	return x--<<DBus/Wire.hs>>=-skipPadding :: Word8 -> Unmarshal ()-skipPadding count = do-	(UnmarshalState _ _ offset) <- ST.get-	bytes <- consume $ padding offset count-	unless (L.all (== 0) bytes) $-		E.throwError $ InvalidPadding offset--<<DBus/Wire.hs>>=-skipTerminator :: Unmarshal ()-skipTerminator = do-	(UnmarshalState _ _ offset) <- ST.get-	bytes <- consume 1-	unless (L.all (== 0) bytes) $-		E.throwError $ MissingTerminator offset--<<DBus/Wire.hs>>=-fromMaybeU :: Show a => Text -> (a -> Maybe b) -> a -> Unmarshal b-fromMaybeU label f x = case f x of-	Just x' -> return x'-	Nothing -> E.throwError . Invalid label . TL.pack . show $ x--fromMaybeU' :: (Show a, T.Variable b) => Text -> (a -> Maybe b) -> a-           -> Unmarshal T.Variant-fromMaybeU' label f x = do-	x' <- fromMaybeU label f x-	return $ T.toVariant x'--<<wire imports>>=-import qualified Data.Binary.Get as G--<<DBus/Wire.hs>>=-unmarshalGet :: Word8 -> G.Get a -> G.Get a -> Unmarshal a-unmarshalGet count be le = do-	skipPadding count-	(UnmarshalState e _ _) <- ST.get-	bs <- consume . fromIntegral $ count-	let get' = case e of-		BigEndian -> be-		LittleEndian -> le-	return $ G.runGet get' bs--unmarshalGet' :: T.Variable a => Word8 -> G.Get a -> G.Get a-              -> Unmarshal T.Variant-unmarshalGet' count be le = fmap T.toVariant $ unmarshalGet count be le--<<DBus/Wire.hs>>=-untilM :: Monad m => m Bool -> m a -> m [a]-untilM test comp = do-	done <- test-	if done-		then return []-		else do-			x <- comp-			xs <- untilM test comp-			return $ x:xs--@ \subsubsection{Errors}--Unmarshaling can fail for four reasons:--\begin{itemize}-\item The message's declared protocol version is unsupported.-\item Unexpected {\sc eof}, when there are less bytes remaining than are-      required.-\item An invalid byte sequence for a given value type.-\item Missing required header fields for the declared message type.-\item Non-zero bytes were found where padding was expected.-\item A string, signature, or object path was not {\sc null}-terminated.-\item An array's size didn't match the number of elements-\end{itemize}--<<DBus/Wire.hs>>=-data UnmarshalError-	= UnsupportedProtocolVersion Word8-	| UnexpectedEOF Word64-	| Invalid Text Text-	| MissingHeaderField Text-	| InvalidHeaderField Text T.Variant-	| InvalidPadding Word64-	| MissingTerminator Word64-	| ArraySizeMismatch-	deriving (Eq, Typeable)--instance Show UnmarshalError where-	show (UnsupportedProtocolVersion x) = concat-		["Unsupported protocol version: ", show x]-	show (UnexpectedEOF pos) = concat-		["Unexpected EOF at position ", show pos]-	show (Invalid label x) = TL.unpack $ TL.concat-		["Invalid ", label, ": ", x]-	show (MissingHeaderField x) = concat-		["Required field " , show x , " is missing."]-	show (InvalidHeaderField x got) = concat-		[ "Invalid header field ", show x, ": ", show got]-	show (InvalidPadding pos) = concat-		["Invalid padding at position ", show pos]-	show (MissingTerminator pos) = concat-		["Missing NUL terminator at position ", show pos]-	show ArraySizeMismatch = "Array size mismatch"--instance E.Error UnmarshalError--<<wire exports>>=-, UnmarshalError (..)--@ \subsection{Numerics}--Numeric values are fixed-length, and aligned ``naturally'' -- ie, a 4-byte-integer will have a 4-byte alignment.--<<alignments>>=-alignment T.DBusByte    = 1-alignment T.DBusWord16  = 2-alignment T.DBusWord32  = 4-alignment T.DBusWord64  = 8-alignment T.DBusInt16   = 2-alignment T.DBusInt32   = 4-alignment T.DBusInt64   = 8-alignment T.DBusDouble  = 8--@ Because {\tt Word32}s are often used for other types, there's-separate functions for handling them.--<<DBus/Wire.hs>>=-marshalWord32 :: Word32 -> Marshal-marshalWord32 = marshalPut P.putWord32be--unmarshalWord32 :: Unmarshal Word32-unmarshalWord32 = unmarshalGet 4 G.getWord32be G.getWord32le--<<marshalers>>=-marshalType T.DBusByte   = append $ L.singleton x-marshalType T.DBusWord16 = marshalPut P.putWord16be x-marshalType T.DBusWord32 = marshalPut P.putWord32be x-marshalType T.DBusWord64 = marshalPut P.putWord64be x-marshalType T.DBusInt16  = marshalPut P.putWord16be $ fromIntegral (x :: Int16)-marshalType T.DBusInt32  = marshalPut P.putWord32be $ fromIntegral (x :: Int32)-marshalType T.DBusInt64  = marshalPut P.putWord64be $ fromIntegral (x :: Int64)--<<unmarshalers>>=-unmarshalType T.DBusByte = fmap (T.toVariant . L.head) $ consume 1-unmarshalType T.DBusWord16 = unmarshalGet' 2 G.getWord16be G.getWord16le-unmarshalType T.DBusWord32 = unmarshalGet' 4 G.getWord32be G.getWord32le-unmarshalType T.DBusWord64 = unmarshalGet' 8 G.getWord64be G.getWord64le--unmarshalType T.DBusInt16  = do-	x <- unmarshalGet 2 G.getWord16be G.getWord16le-	return . T.toVariant $ (fromIntegral x :: Int16)--unmarshalType T.DBusInt32  = do-	x <- unmarshalGet 4 G.getWord32be G.getWord32le-	return . T.toVariant $ (fromIntegral x :: Int32)--unmarshalType T.DBusInt64  = do-	x <- unmarshalGet 8 G.getWord64be G.getWord64le-	return . T.toVariant $ (fromIntegral x :: Int64)--@ {\tt Double}s are marshaled as in-bit IEEE-754 floating-point format.--<<wire imports>>=-import qualified Data.Binary.IEEE754 as IEEE--<<marshalers>>=-marshalType T.DBusDouble = marshalPut IEEE.putFloat64be x--<<unmarshalers>>=-unmarshalType T.DBusDouble = unmarshalGet' 8 IEEE.getFloat64be IEEE.getFloat64le--@ \subsection{Booleans}--Booleans are marshaled as 4-byte unsigned integers containing either of-the values 0 or 1. Yes, really.--<<alignments>>=-alignment T.DBusBoolean = 4--<<marshalers>>=-marshalType T.DBusBoolean = marshalWord32 $ if x then 1 else 0--<<unmarshalers>>=-unmarshalType T.DBusBoolean = unmarshalWord32 >>=-	fromMaybeU' "boolean" (\x -> case x of-		0 -> Just False-		1 -> Just True-		_ -> Nothing)--@ \subsection{Strings and object paths}--Strings are encoded in {\sc utf-8}, terminated with {\tt NUL}, and prefixed-with their length as an unsigned 32-bit integer. Their alignment is that of-their length. Object paths are marshaled just like strings, though additional-checks are required when unmarshaling.--TODO: If the input is invalid, these {\sc utf8} functions will raise an-exception, rather than return an error value. Eventually they should be-wrapped with {\tt unsafePerformIO} and be tweaked to return a proper-{\tt Maybe} or {\tt Either}.--<<wire imports>>=-import Data.Text.Lazy.Encoding (encodeUtf8, decodeUtf8)--<<DBus/Wire.hs>>=-marshalText :: Text -> Marshal-marshalText x = do-	let bytes = encodeUtf8 x-	marshalWord32 . fromIntegral . L.length $ bytes-	append bytes-	append (L.singleton 0)--<<DBus/Wire.hs>>=-unmarshalText :: Unmarshal Text-unmarshalText = do-	byteCount <- unmarshalWord32-	bytes <- consume . fromIntegral $ byteCount-	skipTerminator-	return . decodeUtf8 $ bytes--<<alignments>>=-alignment T.DBusString     = 4-alignment T.DBusObjectPath = 4--<<marshalers>>=-marshalType T.DBusString = marshalText x-marshalType T.DBusObjectPath = marshalText . T.strObjectPath $ x--<<unmarshalers>>=-unmarshalType T.DBusString = fmap T.toVariant unmarshalText--unmarshalType T.DBusObjectPath = unmarshalText >>=-	fromMaybeU' "object path" T.mkObjectPath--@ \subsection{Signatures}--Signatures are similar to strings, except their length is limited to 255-characters and is therefore stored as a single byte.--<<DBus/Wire.hs>>=-marshalSignature :: T.Signature -> Marshal-marshalSignature x = do-	let bytes = encodeUtf8 . T.strSignature $ x-	let size = fromIntegral . L.length $ bytes-	append (L.singleton size)-	append bytes-	append (L.singleton 0)--<<DBus/Wire.hs>>=-unmarshalSignature :: Unmarshal T.Signature-unmarshalSignature = do-	byteCount <- fmap L.head $ consume 1-	sigText <- fmap decodeUtf8 $ consume . fromIntegral $ byteCount-	skipTerminator-	fromMaybeU "signature" T.mkSignature sigText--<<alignments>>=-alignment T.DBusSignature  = 1--<<marshalers>>=-marshalType T.DBusSignature = marshalSignature x--<<unmarshalers>>=-unmarshalType T.DBusSignature = fmap T.toVariant unmarshalSignature--@ \subsection{Containers}--@ \subsubsection{Arrays}--<<alignments>>=-alignment (T.DBusArray _) = 4--<<marshalers>>=-marshalType (T.DBusArray _) = marshalArray x--<<unmarshalers>>=-unmarshalType (T.DBusArray t) = fmap T.toVariant $ unmarshalArray t--@ Marshaling arrays is complicated, because the array body must be marshaled-\emph{first} to calculate the array length. This requires building a-temporary marshaler, to get the padding right.--<<wire imports>>=-import qualified DBus.Constants as C--<<DBus/Wire.hs>>=-marshalArray :: T.Array -> Marshal-marshalArray x = do-	(arrayPadding, arrayBytes) <- getArrayBytes x-	let arrayLen = L.length arrayBytes-	when (arrayLen > fromIntegral C.arrayMaximumLength)-		(E.throwError $ ArrayTooLong $ fromIntegral arrayLen)-	marshalWord32 $ fromIntegral arrayLen-	append arrayPadding-	append arrayBytes--<<DBus/Wire.hs>>=-getArrayBytes :: T.Array -> MarshalM (L.ByteString, L.ByteString)-getArrayBytes x = do-	let vs = T.arrayItems x-	let itemType = T.arrayType x-	s <- ST.get-	(MarshalState _ afterLength) <- marshalWord32 0 >> ST.get-	(MarshalState _ afterPadding) <- pad (alignment itemType) >> ST.get-	(MarshalState _ afterItems) <- mapM_ marshal vs >> ST.get-	-	let paddingBytes = L.drop (L.length afterLength) afterPadding-	let itemBytes = L.drop (L.length afterPadding) afterItems-	-	ST.put s-	return (paddingBytes, itemBytes)--@ Unmarshaling is much easier--<<DBus/Wire.hs>>=-unmarshalArray :: T.Type -> Unmarshal T.Array-unmarshalArray itemType = do-	let getOffset = do-		(UnmarshalState _ _ o) <- ST.get-		return o-	byteCount <- unmarshalWord32-	skipPadding (alignment itemType)-	start <- getOffset-	let end = start + fromIntegral byteCount-	vs <- untilM (fmap (>= end) getOffset) (unmarshalType itemType)-	end' <- getOffset-	when (end' > end) $-		E.throwError ArraySizeMismatch-	fromMaybeU "array" (T.arrayFromItems itemType) vs--@ \subsubsection{Dictionaries}--<<alignments>>=-alignment (T.DBusDictionary _ _) = 4--<<marshalers>>=-marshalType (T.DBusDictionary _ _) = marshalArray (T.dictionaryToArray x)--<<unmarshalers>>=-unmarshalType (T.DBusDictionary kt vt) = do-	let pairType = T.DBusStructure [kt, vt]-	array <- unmarshalArray pairType-	fromMaybeU' "dictionary" T.arrayToDictionary array--@ \subsubsection{Structures}--<<alignments>>=-alignment (T.DBusStructure _) = 8--<<marshalers>>=-marshalType (T.DBusStructure _) = do-	let T.Structure vs = x-	pad 8-	mapM_ marshal vs--<<unmarshalers>>=-unmarshalType (T.DBusStructure ts) = do-	skipPadding 8-	fmap (T.toVariant . T.Structure) $ mapM unmarshalType ts--@ \subsubsection{Variants}--<<alignments>>=-alignment T.DBusVariant = 1--<<marshalers>>=-marshalType T.DBusVariant = do-	let rawSig = T.typeCode . T.variantType $ x-	sig <- case T.mkSignature rawSig of-		Just x' -> return x'-		Nothing -> E.throwError $ InvalidVariantSignature rawSig-	marshalSignature sig-	marshal x--<<unmarshalers>>=-unmarshalType T.DBusVariant = do-	let getType sig = case T.signatureTypes sig of-		[t] -> Just t-		_   -> Nothing-	-	t <- fromMaybeU "variant signature" getType =<< unmarshalSignature-	fmap T.toVariant $ unmarshalType t--@ \subsection{Messages}--<<wire imports>>=-import qualified DBus.Message.Internal as M--@ \subsubsection{Flags}--<<wire imports>>=-import Data.Bits ((.|.), (.&.))-import qualified Data.Set as Set--<<DBus/Wire.hs>>=-encodeFlags :: Set.Set M.Flag -> Word8-encodeFlags flags = foldr (.|.) 0 $ map flagValue $ Set.toList flags where-	flagValue M.NoReplyExpected = 0x1-	flagValue M.NoAutoStart     = 0x2--decodeFlags :: Word8 -> Set.Set M.Flag-decodeFlags word = Set.fromList flags where-	flagSet = [ (0x1, M.NoReplyExpected)-	          , (0x2, M.NoAutoStart)-	          ]-	flags = flagSet >>= \(x, y) -> [y | word .&. x > 0]--@ \subsubsection{Header fields}--<<DBus/Wire.hs>>=-encodeField :: M.HeaderField -> T.Structure-encodeField (M.Path x)        = encodeField' 1 x-encodeField (M.Interface x)   = encodeField' 2 x-encodeField (M.Member x)      = encodeField' 3 x-encodeField (M.ErrorName x)   = encodeField' 4 x-encodeField (M.ReplySerial x) = encodeField' 5 x-encodeField (M.Destination x) = encodeField' 6 x-encodeField (M.Sender x)      = encodeField' 7 x-encodeField (M.Signature x)   = encodeField' 8 x--encodeField' :: T.Variable a => Word8 -> a -> T.Structure-encodeField' code x = T.Structure-	[ T.toVariant code-	, T.toVariant $ T.toVariant x-	]--<<DBus/Wire.hs>>=-decodeField :: Monad m => T.Structure-            -> E.ErrorT UnmarshalError m [M.HeaderField]-decodeField struct = case unpackField struct of-	(1, x) -> decodeField' x M.Path "path"-	(2, x) -> decodeField' x M.Interface "interface"-	(3, x) -> decodeField' x M.Member "member"-	(4, x) -> decodeField' x M.ErrorName "error name"-	(5, x) -> decodeField' x M.ReplySerial "reply serial"-	(6, x) -> decodeField' x M.Destination "destination"-	(7, x) -> decodeField' x M.Sender "sender"-	(8, x) -> decodeField' x M.Signature "signature"-	_      -> return []--decodeField' :: (Monad m, T.Variable a) => T.Variant -> (a -> b) -> Text-             -> E.ErrorT UnmarshalError m [b]-decodeField' x f label = case T.fromVariant x of-	Just x' -> return [f x']-	Nothing -> E.throwError $ InvalidHeaderField label x--<<DBus/Wire.hs>>=-unpackField :: T.Structure -> (Word8, T.Variant)-unpackField struct = (c', v') where-	T.Structure [c, v] = struct-	c' = fromJust . T.fromVariant $ c-	v' = fromJust . T.fromVariant $ v--@ \subsubsection{Header layout}--TODO: describe header layout here--@ \subsubsection{Marshaling}--<<wire exports>>=-, marshalMessage--<<DBus/Wire.hs>>=-marshalMessage :: M.Message a => Endianness -> M.Serial -> a-               -> Either MarshalError L.ByteString-marshalMessage e serial msg = runMarshal marshaler e where-	body = M.messageBody msg-	marshaler = do-		sig <- checkBodySig body-		empty <- ST.get-		mapM_ marshal body-		(MarshalState _ bodyBytes) <- ST.get-		ST.put empty-		marshalEndianness e-		marshalHeader msg serial sig-			$ fromIntegral . L.length $ bodyBytes-		pad 8-		append bodyBytes-		checkMaximumSize--<<DBus/Wire.hs>>=-checkBodySig :: [T.Variant] -> MarshalM T.Signature-checkBodySig vs = let-	sigStr = TL.concat . map (T.typeCode . T.variantType) $ vs-	invalid = E.throwError $ InvalidBodySignature sigStr-	in case T.mkSignature sigStr of-		Just x -> return x-		Nothing -> invalid--<<DBus/Wire.hs>>=-marshalHeader :: M.Message a => a -> M.Serial -> T.Signature -> Word32-              -> Marshal-marshalHeader msg serial bodySig bodyLength = do-	let fields = M.Signature bodySig : M.messageHeaderFields msg-	marshal . T.toVariant . M.messageTypeCode $ msg-	marshal . T.toVariant . encodeFlags . M.messageFlags $ msg-	marshal . T.toVariant $ C.protocolVersion-	marshalWord32 bodyLength-	marshal . T.toVariant $ serial-	let fieldType = T.DBusStructure [T.DBusByte, T.DBusVariant]-	marshal . T.toVariant . fromJust . T.toArray fieldType-	        $ map encodeField fields--<<DBus/Wire.hs>>=-marshalEndianness :: Endianness -> Marshal-marshalEndianness = marshal . T.toVariant . encodeEndianness--<<DBus/Wire.hs>>=-checkMaximumSize :: Marshal-checkMaximumSize = do-	(MarshalState _ messageBytes) <- ST.get-	let messageLength = L.length messageBytes-	when (messageLength > fromIntegral C.messageMaximumLength)-		(E.throwError $ MessageTooLong $ fromIntegral messageLength)--@ \subsubsection{Unmarshaling}--<<wire exports>>=-, unmarshalMessage--<<DBus/Wire.hs>>=-unmarshalMessage :: Monad m => (Word32 -> m L.ByteString)-                 -> m (Either UnmarshalError M.ReceivedMessage)-unmarshalMessage getBytes' = E.runErrorT $ do-	let getBytes = E.lift . getBytes'-	-	<<read fixed-length header>>-	<<read full header>>-	<<read body>>-	<<build message>>--@ The first part of the header has a fixed size of 16 bytes, so it can be-retrieved without any size calculations.--<<read fixed-length header>>=-let fixedSig = T.mkSignature' "yyyyuuu"-fixedBytes <- getBytes 16--@ The first field of interest is the protocol version; if the incoming-message's version is different from this library, the message cannot be-parsed.--<<read fixed-length header>>=-let messageVersion = L.index fixedBytes 3-when (messageVersion /= C.protocolVersion) $-	E.throwError $ UnsupportedProtocolVersion messageVersion--@ Next is the endianness, used for parsing pretty much every other field.--<<read fixed-length header>>=-let eByte = L.index fixedBytes 0-endianness <- case decodeEndianness eByte of-	Just x' -> return x'-	Nothing -> E.throwError . Invalid "endianness" . TL.pack . show $ eByte--@ With the endianness out of the way, the rest of the fixed header-can be decoded--<<read fixed-length header>>=-let unmarshal' x bytes = case runUnmarshal (unmarshal x) endianness bytes of-	Right x' -> return x'-	Left  e  -> E.throwError e-fixed <- unmarshal' fixedSig fixedBytes-let typeCode = fromJust . T.fromVariant $ fixed !! 1-let flags = decodeFlags . fromJust . T.fromVariant $ fixed !! 2-let bodyLength = fromJust . T.fromVariant $ fixed !! 4-let serial = fromJust . T.fromVariant $ fixed !! 5--@ The last field of the fixed header is actually part of the field array,-but is treated as a single {\tt Word32} so it'll be known how many bytes-to retrieve.--<<read fixed-length header>>=-let fieldByteCount = fromJust . T.fromVariant $ fixed !! 6--@ With the field byte count, the remainder of the header bytes can be-pulled out of the monad.--<<read full header>>=-let headerSig  = T.mkSignature' "yyyyuua(yv)"-fieldBytes <- getBytes fieldByteCount-let headerBytes = L.append fixedBytes fieldBytes-header <- unmarshal' headerSig headerBytes--@ And the header fields can be parsed.--<<read full header>>=-let fieldArray = fromJust . T.fromVariant $ header !! 6-let fieldStructures = fromJust . T.fromArray $ fieldArray-fields <- fmap concat $ mapM decodeField fieldStructures--@ The body is always aligned to 8 bytes, so pull out the padding before-unmarshaling it.--<<read body>>=-let bodyPadding = padding (fromIntegral fieldByteCount + 16) 8-getBytes . fromIntegral $ bodyPadding--<<DBus/Wire.hs>>=-findBodySignature :: [M.HeaderField] -> T.Signature-findBodySignature fields = fromMaybe empty signature where-	empty = T.mkSignature' ""-	signature = listToMaybe [x | M.Signature x <- fields]--<<read body>>=-let bodySig = findBodySignature fields--@ Then pull the body bytes, and unmarshal it.--<<read body>>=-bodyBytes <- getBytes bodyLength-body <- unmarshal' bodySig bodyBytes--@ Even if the received message was structurally valid, building the-{\tt ReceivedMessage} can still fail due to missing header fields.--<<build message>>=-y <- case buildReceivedMessage typeCode fields of-	Right x -> return x-	Left x -> E.throwError $ MissingHeaderField x--<<build message>>=-return $ y serial flags body--@ This really belongs in the Message section...--<<DBus/Wire.hs>>=-buildReceivedMessage :: Word8 -> [M.HeaderField] -> Either Text -                        (M.Serial -> (Set.Set M.Flag) -> [T.Variant]-                         -> M.ReceivedMessage)--@ Method calls--<<DBus/Wire.hs>>=-buildReceivedMessage 1 fields = do-	path <- require "path" [x | M.Path x <- fields]-	member <- require "member name" [x | M.Member x <- fields]-	return $ \serial flags body -> let-		iface = listToMaybe [x | M.Interface x <- fields]-		dest = listToMaybe [x | M.Destination x <- fields]-		sender = listToMaybe [x | M.Sender x <- fields]-		msg = M.MethodCall path member iface dest flags body-		in M.ReceivedMethodCall serial sender msg--@ Method returns--<<DBus/Wire.hs>>=-buildReceivedMessage 2 fields = do-	replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]-	return $ \serial flags body -> let-		dest = listToMaybe [x | M.Destination x <- fields]-		sender = listToMaybe [x | M.Sender x <- fields]-		msg = M.MethodReturn replySerial dest flags body-		in M.ReceivedMethodReturn serial sender msg--@ Errors--<<DBus/Wire.hs>>=-buildReceivedMessage 3 fields = do-	name <- require "error name" [x | M.ErrorName x <- fields]-	replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]-	return $ \serial flags body -> let-		dest = listToMaybe [x | M.Destination x <- fields]-		sender = listToMaybe [x | M.Sender x <- fields]-		msg = M.Error name replySerial dest flags body-		in M.ReceivedError serial sender msg--@ Signals--<<DBus/Wire.hs>>=-buildReceivedMessage 4 fields = do-	path <- require "path" [x | M.Path x <- fields]-	member <- require "member name" [x | M.Member x <- fields]-	iface <- require "interface" [x | M.Interface x <- fields]-	return $ \serial flags body -> let-		dest = listToMaybe [x | M.Destination x <- fields]-		sender = listToMaybe [x | M.Sender x <- fields]-		msg = M.Signal path member iface dest flags body-		in M.ReceivedSignal serial sender msg--@ Unknown--<<DBus/Wire.hs>>=-buildReceivedMessage typeCode fields = return $ \serial flags body -> let-	sender = listToMaybe [x | M.Sender x <- fields]-	msg = M.Unknown typeCode flags body-	in M.ReceivedUnknown serial sender msg--<<DBus/Wire.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.hs>>=-instance E.Error Text where-	strMsg = TL.pack--@-\section{Connections}--<<DBus/Connection.hs>>=-<<copyright>>-<<text extensions>>-{-# LANGUAGE DeriveDataTypeable #-}-module DBus.Connection-	( <<connection exports>>-	) where-<<text imports>>-<<connection imports>>--@ A {\tt Connection} is an opaque handle to an open DBus channel, with-an internal state for maintaining the current message serial.--<<connection imports>>=-import qualified Control.Concurrent as C-import qualified DBus.Address as A-import qualified DBus.Message.Internal as M--<<DBus/Connection.hs>>=-data Connection = Connection A.Address Transport (C.MVar M.Serial)--<<connection exports>>=-  Connection--@ 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--@ \subsection{Transports}--A transport is anything which can send and receive bytestrings, typically-over a socket.--<<connection imports>>=-import qualified Data.ByteString.Lazy as L-import Data.Word (Word32)--<<DBus/Connection.hs>>=-data Transport = Transport-	{ transportSend :: L.ByteString -> IO ()-	, transportRecv :: Word32 -> IO L.ByteString-	}--<<DBus/Connection.hs>>=-connectTransport :: A.Address -> IO Transport-connectTransport a = transport' (A.addressMethod a) a where-	transport' "unix" = unix-	transport' _      = unknownTransport--@ \subsubsection{UNIX}--The {\sc unix} transport accepts two parameters: {\tt path}, which is a-simple filesystem path, and {\tt abstract}, which is a path in the-Linux-specific abstract domain. One, and only one, of these parameters must-be specified.--<<connection imports>>=-import qualified Network as N-import qualified Data.Map as Map--<<DBus/Connection.hs>>=-unix :: A.Address -> IO Transport-unix a = handleTransport . N.connectTo "localhost" =<< port where-	params = A.addressParameters a-	path = Map.lookup "path" params-	abstract = Map.lookup "abstract" params-	-	tooMany = "Only one of `path' or `abstract' may be specified for the\-	          \ `unix' method."-	tooFew = "One of `path' or `abstract' must be specified for the\-	         \ `unix' transport."-	-	port = fmap N.UnixSocket path'-	path' = case (path, abstract) of-		(Just _, Just _) -> E.throwIO $ BadParameters a tooMany-		(Nothing, Nothing) -> E.throwIO $ BadParameters a tooFew-		(Just x, Nothing) -> return $ TL.unpack x-		(Nothing, Just x) -> return $ '\x00' : TL.unpack x--@ \subsubsection{TCP}--known parameters:--\begin{itemize}-\item {\tt host} (optional, default "{\tt localhost}")-\item {\tt port}-\item {\tt family} (optional, choices are "{\tt ipv4}" or "{\tt ipv6}"-\end{itemize}--TCP support is TODO--<<TODO>>=-tcp :: A.Address -> IO Transport-tcp a@(A.Address _ params) = handleTransport a connect' where-	host = lookup "host" params-	port = parsePort =<< lookup "post" params-	family = parseFamily =<< lookup "family" params-	connect' = do-		-- check host-		-- check port-		-- check family-		-- return handle-parsePort :: String -> Maybe PortNumber--parseFamily :: String -> Maybe Family--@ \subsubsection{Generic handle-based transport}--Both UNIX and TCP are backed by standard handles, and can therefore use-a shared handle-based transport backend.--<<connection imports>>=-import qualified System.IO as I--<<DBus/Connection.hs>>=-handleTransport :: IO I.Handle -> IO Transport-handleTransport io = do-	h <- io-	I.hSetBuffering h I.NoBuffering-	I.hSetBinaryMode h True-	return $ Transport (L.hPut h) (L.hGet h . fromIntegral)--@ \subsubsection{Unknown transports}--If a method has no known transport, attempting to connect using it will-just result in an exception.--<<DBus/Connection.hs>>=-unknownTransport :: A.Address -> IO Transport-unknownTransport = E.throwIO . UnknownMethod--@ \subsection{Errors}--If connecting to DBus fails, a {\tt ConnectionError} will be thrown.-The constructor describes which exception occurred.--<<connection imports>>=-import qualified Control.Exception as E-import Data.Typeable (Typeable)--<<DBus/Connection.hs>>=-data ConnectionError-	= InvalidAddress Text-	| BadParameters A.Address Text-	| UnknownMethod A.Address-	| NoWorkingAddress [A.Address]-	deriving (Show, Typeable)--instance E.Exception ConnectionError--<<connection exports>>=-, ConnectionError (..)-, W.MarshalError (..)-, W.UnmarshalError (..)--@ \subsection{Establishing a connection}--A connection can be opened to any valid address, though actually connecting-might fail due to external factors.--<<connection imports>>=-import Data.Text.Lazy.Encoding (decodeUtf8, encodeUtf8)--<<DBus/Connection.hs>>=-connect :: A.Address -> IO Connection-connect a = do-	t <- connectTransport a-	let putS = transportSend t . encodeUtf8 . TL.pack-	let getS = fmap (TL.unpack . decodeUtf8) . transportRecv t-	authenticate putS getS-	serialMVar <- C.newMVar M.firstSerial-	return $ Connection a t serialMVar--<<connection exports>>=-, connect--@ \subsection{Authentication}--<<DBus/Connection.hs>>=-authenticate :: (String -> IO ()) -> (Word32 -> IO String)-                -> IO ()-authenticate put get = do-	put "\x00"--@ {\sc external} authentication is performed using the process's real user-ID, converted to a string, and then hex-encoded.--<<connection imports>>=-import System.Posix.User (getRealUserID)-import Data.Char (ord)-import Text.Printf (printf)--<<DBus/Connection.hs>>=-	uid <- getRealUserID-	let authToken = concatMap (printf "%02X" . ord) (show uid)-	put $ "AUTH EXTERNAL " ++ authToken ++ "\r\n"--@ 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)--<<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."--<<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--@ \subsection{Sending and receiving messages}--Sending a message will increment the connection's internal serial state.-The second parameter is present to allow registration of a callback before-the message has actually been sent, which avoids race conditions in-multi-threaded clients.--<<connection imports>>=-import qualified DBus.Wire as W--<<DBus/Connection.hs>>=-send :: M.Message a => Connection -> (M.Serial -> IO b) -> a-     -> IO (Either W.MarshalError b)-send (Connection _ t mvar) io msg = withSerial mvar $ \serial ->-	case W.marshalMessage W.LittleEndian serial msg of-		Right bytes -> do-			x <- io serial-			transportSend t bytes-			return $ Right x-		Left  err   -> return $ Left err--<<connection exports>>=-, send--<<DBus/Connection.hs>>=-withSerial :: C.MVar M.Serial -> (M.Serial -> IO a) -> IO a-withSerial m io = E.block $ do-	s <- C.takeMVar m-	let s' = M.nextSerial s-	x <- E.unblock (io s) `E.onException` C.putMVar m s'-	C.putMVar m s'-	return x--@ Messages are received wrapped in a {\tt ReceivedMessage} value. If an-error is encountered while unmarshaling, an exception will be thrown.--<<DBus/Connection.hs>>=-receive :: Connection -> IO (Either W.UnmarshalError M.ReceivedMessage)-receive (Connection _ t _) = W.unmarshalMessage $ transportRecv t--<<connection exports>>=-, receive--@-\section{The central bus}--<<DBus/Bus.hs>>=-<<copyright>>-<<text extensions>>-module DBus.Bus-	( getSystemBus-	, getSessionBus-	, getStarterBus-	, getFirstBus-	, getBus-	) where-<<text imports>>--import qualified Control.Exception as E-import Control.Monad (when)-import Data.Maybe (fromJust, isNothing)-import qualified Data.Set as Set-import System.Environment (getEnv)--import qualified DBus.Address as A-import qualified DBus.Connection as C-import DBus.Constants (dbusName, dbusPath, dbusInterface)-import qualified DBus.Message as M-import qualified DBus.Types as T-import DBus.Util (fromRight)--@ Connecting to a message bus is a bit more involved than just connecting-over an app-to-app connection: the bus must be notified of the new client,-using a "hello message", before it will begin forwarding messages.--<<DBus/Bus.hs>>=-getBus :: A.Address -> IO (C.Connection, T.BusName)-getBus addr = do-	c <- C.connect addr-	name <- sendHello c-	return (c, name)--@ Optionally, multiple addresses may be provided. The first successfully-connected bus will be returned.--<<DBus/Bus.hs>>=-getFirstBus :: [A.Address] -> IO (C.Connection, T.BusName)-getFirstBus as = getFirstBus' as as--getFirstBus' :: [A.Address] -> [A.Address] -> IO (C.Connection, T.BusName)-getFirstBus' orig     [] = E.throwIO $ C.NoWorkingAddress orig-getFirstBus' orig (a:as) = E.catch (getBus a) onError where-	onError :: E.SomeException -> IO (C.Connection, T.BusName)-	onError _ = getFirstBus' orig as--@ \subsection{Default connections}--Two default buses are defined, the ``system'' and ``session'' buses. The system-bus is global for the OS, while the session bus runs only for the duration-of the user's session.--<<DBus/Bus.hs>>=-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--<<DBus/Bus.hs>>=-getSessionBus :: IO (C.Connection, T.BusName)-getSessionBus = getBus' $ getEnv "DBUS_SESSION_BUS_ADDRESS"--<<DBus/Bus.hs>>=-getStarterBus :: IO (C.Connection, T.BusName)-getStarterBus = getBus' $ getEnv "DBUS_STARTER_ADDRESS"--<<DBus/Bus.hs>>=-getBus' :: IO String -> IO (C.Connection, T.BusName)-getBus' io = do-	addr <- fmap TL.pack io-	case A.mkAddresses addr of-		Just [x] -> getBus x-		Just  x  -> getFirstBus x-		_        -> E.throwIO $ C.InvalidAddress addr--@ \subsection{Sending the ``hello'' message}--<<DBus/Bus.hs>>=-hello :: M.MethodCall-hello = M.MethodCall dbusPath-	(T.mkMemberName' "Hello")-	(Just dbusInterface)-	(Just dbusName)-	Set.empty-	[]--<<DBus/Bus.hs>>=-sendHello :: C.Connection -> IO T.BusName-sendHello c = do-	serial <- fromRight `fmap` C.send c return hello-	reply <- waitForReply c serial-	let name = case M.methodReturnBody reply of-		(x:_) -> T.fromVariant x-		_     -> Nothing-	-	when (isNothing name) $-		E.throwIO $ E.AssertionFailed "Invalid response to Hello()"-	-	return . fromJust $ name--<<DBus/Bus.hs>>=-waitForReply :: C.Connection -> M.Serial -> IO M.MethodReturn-waitForReply c serial = do-	received <- C.receive c-	msg <- case received of-		Right x -> return x-		Left err -> E.throwIO $ E.AssertionFailed "Invalid response to Hello()"-	case msg of-		(M.ReceivedMethodReturn _ _ reply) ->-			if M.methodReturnSerial reply == serial-				then return reply-				else waitForReply c serial-		_ -> waitForReply c serial--@-\section{Addresses}--<<DBus/Address.hs>>=-<<copyright>>-<<text extensions>>-module DBus.Address-	( Address-	, addressMethod-	, addressParameters-	, mkAddresses-	, strAddress-	) where-<<text imports>>--import Data.Char (ord, chr)-import qualified Data.Map as M-import Text.Printf (printf)-import qualified Text.Parsec as P-import Text.Parsec ((<|>))-import DBus.Util (hexToInt, eitherToMaybe)--@ \subsection{Address syntax}--A bus address is in the format {\tt $method$:$key$=$value$,$key$=$value$...}-where the method may be empty and parameters are optional. An address's-parameter list, if present, may end with a comma. Addresses in environment-variables are separated by semicolons, and the full address list may end-in a semicolon. Multiple parameters may have the same key; in this case,-only the first parameter for each key will be stored.--The bytes allowed in each component of the address are given by the following-chart, where each character is understood to be its ASCII value:--\begin{table}[h]-\begin{center}-\begin{tabular}{ll}-\toprule-Component   & Allowed Characters \\-\midrule-Method      & Any except {\tt `;'} and {\tt `:'} \\-Param key   & Any except {\tt `;'}, {\tt `,'}, and {\tt `='} \\-Param value & {\tt `0'} to {\tt `9'} \\-            & {\tt `a'} to {\tt `z'} \\-            & {\tt `A'} to {\tt `Z'} \\-            & Any of: {\tt - \textunderscore{} / \textbackslash{} * . \%} \\-\bottomrule-\end{tabular}-\end{center}-\end{table}--In parameter values, any byte may be encoded by prepending the \% character-to its value in hexadecimal. \% is not allowed to appear unless it is-followed by two hexadecimal digits. Every other allowed byte is termed-an ``optionally encoded'' byte, and may appear unescaped in parameter-values.--<<DBus/Address.hs>>=-optionallyEncoded :: [Char]-optionallyEncoded = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."--@-The address simply stores its method and parameter map, with a custom-{\tt Show} instance to provide easier debugging.--<<DBus/Address.hs>>=-data Address = Address-	{ addressMethod     :: Text-	, addressParameters :: M.Map Text Text-	} deriving (Eq)--instance Show Address where-	showsPrec d x = showParen (d> 10) $-		showString "Address " . shows (strAddress x)--@-Parsing is straightforward; the input string is divided into addresses by-semicolons, then further by colons and commas. Parsing will fail if any-of the addresses in the input failed to parse.--<<DBus/Address.hs>>=-mkAddresses :: Text -> Maybe [Address]-mkAddresses s = eitherToMaybe . P.parse parser "" . TL.unpack $ s where-	address = do-		method <- P.many (P.noneOf ":;")-		P.char ':'-		params <- P.sepEndBy param (P.char ',')-		return $ Address (TL.pack method) (M.fromList params)-	-	param = do-		key <- P.many1 (P.noneOf "=;,")-		P.char '='-		value <- P.many1 (encodedValue <|> unencodedValue)-		return (TL.pack key, TL.pack value)-	-	parser = do-		as <- P.sepEndBy1 address (P.char ';')-		P.eof-		return as-	-	unencodedValue = P.oneOf optionallyEncoded-	encodedValue = do-		P.char '%'-		hex <- P.count 2 P.hexDigit-		return . chr . hexToInt $ hex--@-Converting an {\tt Address} back to a {\tt String} is just the reverse-operation. Note that because the original parameter order is not preserved,-the string produced might differ from the original input.--<<DBus/Address.hs>>=-strAddress :: Address -> Text-strAddress (Address t ps) = TL.concat [t, ":", ps'] where-	ps' = TL.intercalate "," $ do-		(k, v) <- M.toList ps-		return $ TL.concat [k, "=", TL.concatMap encode v]-	encode c | elem c optionallyEncoded = TL.singleton c-	         | otherwise       = TL.pack $ printf "%%%02X" (ord c)---@-\section{Introspection}--@ DBus objects may be ``introspected'' to determine which methods, signals,-etc they support. Intospection data is sent over the bus in {\sc xml}, in-a mostly standardised but undocumented format.--An XML introspection document looks like this:--\begin{verbatim}-<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"-         "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">-<node name="/org/example/example">-	<interface name="org.example.ExampleInterface">-		<method name="Echo">-			<arg name="text" type="s" direction="in"/>-			<arg type="s" direction="out"/>-		</method>-		<signal name="Echoed">-			<arg type="s"/>-		</signal>-		<property name="EchoCount" type="u" access="read"/>-	</interface>-	<node name="child_a"/>-	<node name="child/b"/>-</node>-\end{verbatim}--<<DBus/Introspection.hs>>=-<<copyright>>-<<text extensions>>-module DBus.Introspection-	( Object (..)-	, Interface (..)-	, Method (..)-	, Signal (..)-	, Parameter (..)-	, Property (..)-	, PropertyAccess (..)-	, toXML-	, fromXML-	) where-<<text imports>>-<<introspection imports>>-import qualified DBus.Types as T--@ HaXml is used to do the heavy lifting of XML parsing because HXT cannot-be combined with Parsec 3.--<<introspection imports>>=-import qualified Text.XML.HaXml as H--@ \subsection{Data types}--<<DBus/Introspection.hs>>=-data Object = Object T.ObjectPath [Interface] [Object]-	deriving (Show, Eq)--data Interface = Interface T.InterfaceName [Method] [Signal] [Property]-	deriving (Show, Eq)--data Method = Method T.MemberName [Parameter] [Parameter]-	deriving (Show, Eq)--data Signal = Signal T.MemberName [Parameter]-	deriving (Show, Eq)--data Parameter = Parameter Text T.Signature-	deriving (Show, Eq)--data Property = Property Text T.Signature [PropertyAccess]-	deriving (Show, Eq)--data PropertyAccess = Read | Write-	deriving (Show, Eq)--@ \subsection{Parsing XML}--The root {\tt node} is special, in that it's the only {\tt node} which is-not required to have a {\tt name} attribute. If the root has no {\tt name},-its path will default to the path of the introspected object.--If parsing fails, {\tt fromXML} will return {\tt Nothing}. Aside from the-elements directly accessed by the parser, no effort is made to check the-document's validity because there is no DTD as of yet.--<<introspection imports>>=-import Text.XML.HaXml.Parse (xmlParse')-import DBus.Util (eitherToMaybe)--<<DBus/Introspection.hs>>=-fromXML :: T.ObjectPath -> Text -> Maybe Object-fromXML path text = do-	doc <- eitherToMaybe . xmlParse' "" . TL.unpack $ text-	let (H.Document _ _ root _) = doc-	parseRoot path root--@ Even though the root object's {\tt name} is optional, if present, it must-still be a valid object path.--<<DBus/Introspection.hs>>=-parseRoot :: T.ObjectPath -> H.Element a -> Maybe Object-parseRoot defaultPath e = do-	path <- case getAttr "name" e of-		Nothing -> Just defaultPath-		Just x  -> T.mkObjectPath x-	parseObject' path e--@ Child {\tt nodes} have ``relative'' paths -- that is, their {\tt name}-attribute is not a valid object path, but should be valid when appended to-the root object's path.--<<DBus/Introspection.hs>>=-parseChild :: T.ObjectPath -> H.Element a -> Maybe Object-parseChild parentPath e = do-	let parentPath' = case T.strObjectPath parentPath of-		"/" -> "/"-		x   -> TL.append x "/"-	pathSegment <- getAttr "name" e-	path <- T.mkObjectPath $ TL.append parentPath' pathSegment-	parseObject' path e--@ Other than the name, both root and non-root {\tt nodes} have identical-contents.  They may contain interface definitions, and child {\tt node}s.--<<DBus/Introspection.hs>>=-parseObject' :: T.ObjectPath -> H.Element a -> Maybe Object-parseObject' path e@(H.Elem "node" _ _)  = do-	interfaces <- children parseInterface (H.tag "interface") e-	children' <- children (parseChild path) (H.tag "node") e-	return $ Object path interfaces children'-parseObject' _ _ = Nothing--@ Interfaces may contain methods, signals, and properties.--<<DBus/Introspection.hs>>=-parseInterface :: H.Element a -> Maybe Interface-parseInterface e = do-	name <- T.mkInterfaceName =<< getAttr "name" e-	methods <- children parseMethod (H.tag "method") e-	signals <- children parseSignal (H.tag "signal") e-	properties <- children parseProperty (H.tag "property") e-	return $ Interface name methods signals properties--@ Methods contain a list of parameters, which default to ``in'' parameters-if no direction is specified.--<<DBus/Introspection.hs>>=-parseMethod :: H.Element a -> Maybe Method-parseMethod e = do-	name <- T.mkMemberName =<< getAttr "name" e-	paramsIn <- children parseParameter (isParam ["in", ""]) e-	paramsOut <- children parseParameter (isParam ["out"]) e-	return $ Method name paramsIn paramsOut--@ Signals are similar to methods, except they have no ``in'' parameters.--<<DBus/Introspection.hs>>=-parseSignal :: H.Element a -> Maybe Signal-parseSignal e = do-	name <- T.mkMemberName =<< getAttr "name" e-	params <- children parseParameter (isParam ["out", ""]) e-	return $ Signal name params--@ A parameter has a free-form name, and a single valid type.--<<DBus/Introspection.hs>>=-parseParameter :: H.Element a -> Maybe Parameter-parseParameter e = do-	let name = getAttr' "name" e-	sig <- parseType e-	return $ Parameter name sig--<<DBus/Introspection.hs>>=-parseType :: H.Element a -> Maybe T.Signature-parseType e = do-	sig <- T.mkSignature =<< getAttr "type" e-	case T.signatureTypes sig of-		[_] -> Just sig-		_   -> Nothing--@ Properties are used by the {\tt org.freedesktop.DBus.Properties} interface.-Each property may be read, written, or both, and has an associated type.--<<DBus/Introspection.hs>>=-parseProperty :: H.Element a -> Maybe Property-parseProperty e = do-	let name = getAttr' "name" e-	sig <- parseType e-	access <- case getAttr' "access" e of-		""          -> Just []-		"read"      -> Just [Read]-		"write"     -> Just [Write]-		"readwrite" -> Just [Read, Write]-		_           -> Nothing-	return $ Property name sig access--@ HaXml doesn't seem to have any way to retrieve the ``real'' value of an-attribute, so {\tt attrValue} implements this.--<<introspection imports>>=-import Data.Char (chr)--<<DBus/Introspection.hs>>=-attrValue :: H.AttValue -> Maybe Text-attrValue attr = fmap (TL.pack . concat) $ mapM unescape parts where-	(H.AttValue parts) = attr-	-	unescape (Left x) = Just x-	unescape (Right (H.RefEntity x)) = lookup x namedRefs-	unescape (Right (H.RefChar x)) = Just [chr x]-	-	namedRefs =-		[ ("lt", "<")-		, ("gt", ">")-		, ("amp", "&")-		, ("apos", "'")-		, ("quot", "\"")-		]--@ Some helper functions for dealing with HaXml filters--<<introspection imports>>=-import Data.Maybe (fromMaybe)--<<DBus/Introspection.hs>>=-getAttr :: String -> H.Element a -> Maybe Text-getAttr name (H.Elem _ attrs _) = lookup name attrs >>= attrValue--getAttr' :: String -> H.Element a -> Text-getAttr' = (fromMaybe "" .) . getAttr--<<DBus/Introspection.hs>>=-isParam :: [Text] -> H.CFilter a-isParam dirs content = do-	arg@(H.CElem e _) <- H.tag "arg" content-	let direction = getAttr' "direction" e-	[arg | direction `elem` dirs]--<<DBus/Introspection.hs>>=-children :: Monad m => (H.Element a -> m b) -> H.CFilter a -> H.Element a -> m [b]-children f filt (H.Elem _ _ contents) = do-	mapM f [x | (H.CElem x _) <- concatMap filt contents]--@ \subsection{Generating XML}--<<DBus/Introspection.hs>>=-dtdPublicID, dtdSystemID :: String-dtdPublicID = "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"-dtdSystemID = "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"--@ HaXml punts to the {\tt pretty} package for serialising XML.--<<introspection imports>>=-import Text.XML.HaXml.Pretty (document)-import Text.PrettyPrint.HughesPJ (render)--@ Generating XML can fail; if a child object's path is not a sub-path of the-parent, {\tt toXML} will return {\tt Nothing}.--<<DBus/Introspection.hs>>=-toXML :: Object -> Maybe Text-toXML obj = fmap (TL.pack . render . document) doc where-	prolog = H.Prolog Nothing [] (Just doctype) []-	doctype = H.DTD "node" (Just (H.PUBLIC-		(H.PubidLiteral dtdPublicID)-		(H.SystemLiteral dtdSystemID))) []-	doc = do-		root <- xmlRoot obj-		return $ H.Document prolog H.emptyST root []--@ When writing objects to {\tt node}s, the root object must have an absolute-path, and children must have paths relative to their parent.--<<DBus/Introspection.hs>>=-xmlRoot :: Object -> Maybe (H.Element a)-xmlRoot obj@(Object path _ _) = do-	(H.CElem root _) <- xmlObject' (T.strObjectPath path) obj-	return $ root--<<DBus/Introspection.hs>>=-xmlObject :: T.ObjectPath -> Object -> Maybe (H.Content a)-xmlObject parentPath obj@(Object path _ _) = do-	let path' = T.strObjectPath path-	    parent' = T.strObjectPath parentPath-	relpath <- if TL.isPrefixOf parent' path'-		then Just $ if parent' == "/"-			then TL.drop 1 path'-			else TL.drop (TL.length parent' + 1) path'-		else Nothing-	xmlObject' relpath obj--<<DBus/Introspection.hs>>=-xmlObject' :: Text -> Object -> Maybe (H.Content a)-xmlObject' path (Object fullPath interfaces children') = do-	children'' <- mapM (xmlObject fullPath) children'-	return $ mkElement "node"-		[mkAttr "name" $ TL.unpack path]-		$ concat-			[ map xmlInterface interfaces-			, children''-			]--<<DBus/Introspection.hs>>=-xmlInterface :: Interface -> H.Content a-xmlInterface (Interface name methods signals properties) =-	mkElement "interface"-		[mkAttr "name" . TL.unpack . T.strInterfaceName $ name]-		$ concat-			[ map xmlMethod methods-			, map xmlSignal signals-			, map xmlProperty properties-			]--<<DBus/Introspection.hs>>=-xmlMethod :: Method -> H.Content a-xmlMethod (Method name inParams outParams) = mkElement "method"-	[mkAttr "name" . TL.unpack . T.strMemberName $ name]-	$ concat-		[ map (xmlParameter "in") inParams-		, map (xmlParameter "out") outParams-		]--<<DBus/Introspection.hs>>=-xmlSignal :: Signal -> H.Content a-xmlSignal (Signal name params) = mkElement "signal"-	[mkAttr "name" . TL.unpack . T.strMemberName $ name]-	$ map (xmlParameter "out") params--<<DBus/Introspection.hs>>=-xmlParameter :: String -> Parameter -> H.Content a-xmlParameter direction (Parameter name sig) = mkElement "arg"-	[ mkAttr "name" . TL.unpack $ name-	, mkAttr "type" . TL.unpack . T.strSignature $ sig-	, mkAttr "direction" direction-	] []--<<DBus/Introspection.hs>>=-xmlProperty :: Property -> H.Content a-xmlProperty (Property name sig access) = mkElement "property"-	[ mkAttr "name" . TL.unpack $ name-	, mkAttr "type" . TL.unpack . T.strSignature $ sig-	, mkAttr "access" $ xmlAccess access-	] []--<<DBus/Introspection.hs>>=-xmlAccess :: [PropertyAccess] -> String-xmlAccess access = read ++ write where-	read = if elem Read access then "read" else ""-	write = if elem Write access then "write" else ""--<<DBus/Introspection.hs>>=-mkElement :: String -> [H.Attribute] -> [H.Content a] -> H.Content a-mkElement name attrs contents = H.CElem (H.Elem name attrs contents) undefined--<<DBus/Introspection.hs>>=-mkAttr :: String -> String -> H.Attribute-mkAttr name value = (name, H.AttValue [Left escaped]) where-	raw = H.CString True value ()-	escaped = H.verbatim $ H.xmlEscapeContent H.stdXmlEscaper [raw]--@-\section{Constants}--<<DBus/Constants.hs>>=-<<copyright>>-{-# LANGUAGE OverloadedStrings #-}-module DBus.Constants where-import qualified DBus.Types as T-import Data.Word (Word8, Word32)--<<DBus/Constants.hs>>=-protocolVersion :: Word8-protocolVersion = 1--messageMaximumLength :: Word32-messageMaximumLength = 134217728--arrayMaximumLength :: Word32-arrayMaximumLength = 67108864--@ \subsection{The message bus}--<<DBus/Constants.hs>>=-dbusName :: T.BusName-dbusName = T.mkBusName' "org.freedesktop.DBus"--dbusPath :: T.ObjectPath-dbusPath = T.mkObjectPath' "/org/freedesktop/DBus"--dbusInterface :: T.InterfaceName-dbusInterface = T.mkInterfaceName' "org.freedesktop.DBus"--@ \subsection{Pre-defined interfaces}--<<DBus/Constants.hs>>=-interfaceIntrospectable :: T.InterfaceName-interfaceIntrospectable = T.mkInterfaceName' "org.freedesktop.DBus.Introspectable"--interfaceProperties :: T.InterfaceName-interfaceProperties = T.mkInterfaceName' "org.freedesktop.DBus.Properties"--interfacePeer :: T.InterfaceName-interfacePeer = T.mkInterfaceName' "org.freedesktop.DBus.Peer"--@ \subsection{Pre-defined error names}--<<DBus/Constants.hs>>=-errorFailed :: T.ErrorName-errorFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Failed"--errorNoMemory :: T.ErrorName-errorNoMemory = T.mkErrorName' "org.freedesktop.DBus.Error.NoMemory"--errorServiceUnknown :: T.ErrorName-errorServiceUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.ServiceUnknown"--errorNameHasNoOwner :: T.ErrorName-errorNameHasNoOwner = T.mkErrorName' "org.freedesktop.DBus.Error.NameHasNoOwner"--errorNoReply :: T.ErrorName-errorNoReply = T.mkErrorName' "org.freedesktop.DBus.Error.NoReply"--errorIOError :: T.ErrorName-errorIOError = T.mkErrorName' "org.freedesktop.DBus.Error.IOError"--errorBadAddress :: T.ErrorName-errorBadAddress = T.mkErrorName' "org.freedesktop.DBus.Error.BadAddress"--errorNotSupported :: T.ErrorName-errorNotSupported = T.mkErrorName' "org.freedesktop.DBus.Error.NotSupported"--errorLimitsExceeded :: T.ErrorName-errorLimitsExceeded = T.mkErrorName' "org.freedesktop.DBus.Error.LimitsExceeded"--errorAccessDenied :: T.ErrorName-errorAccessDenied = T.mkErrorName' "org.freedesktop.DBus.Error.AccessDenied"--errorAuthFailed :: T.ErrorName-errorAuthFailed = T.mkErrorName' "org.freedesktop.DBus.Error.AuthFailed"--errorNoServer :: T.ErrorName-errorNoServer = T.mkErrorName' "org.freedesktop.DBus.Error.NoServer"--errorTimeout :: T.ErrorName-errorTimeout = T.mkErrorName' "org.freedesktop.DBus.Error.Timeout"--errorNoNetwork :: T.ErrorName-errorNoNetwork = T.mkErrorName' "org.freedesktop.DBus.Error.NoNetwork"--errorAddressInUse :: T.ErrorName-errorAddressInUse = T.mkErrorName' "org.freedesktop.DBus.Error.AddressInUse"--errorDisconnected :: T.ErrorName-errorDisconnected = T.mkErrorName' "org.freedesktop.DBus.Error.Disconnected"--errorInvalidArgs :: T.ErrorName-errorInvalidArgs = T.mkErrorName' "org.freedesktop.DBus.Error.InvalidArgs"--errorFileNotFound :: T.ErrorName-errorFileNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.FileNotFound"--errorFileExists :: T.ErrorName-errorFileExists = T.mkErrorName' "org.freedesktop.DBus.Error.FileExists"--errorUnknownMethod :: T.ErrorName-errorUnknownMethod = T.mkErrorName' "org.freedesktop.DBus.Error.UnknownMethod"--errorTimedOut :: T.ErrorName-errorTimedOut = T.mkErrorName' "org.freedesktop.DBus.Error.TimedOut"--errorMatchRuleNotFound :: T.ErrorName-errorMatchRuleNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.MatchRuleNotFound"--errorMatchRuleInvalid :: T.ErrorName-errorMatchRuleInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.MatchRuleInvalid"--errorSpawnExecFailed :: T.ErrorName-errorSpawnExecFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ExecFailed"--errorSpawnForkFailed :: T.ErrorName-errorSpawnForkFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ForkFailed"--errorSpawnChildExited :: T.ErrorName-errorSpawnChildExited = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ChildExited"--errorSpawnChildSignaled :: T.ErrorName-errorSpawnChildSignaled = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ChildSignaled"--errorSpawnFailed :: T.ErrorName-errorSpawnFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.Failed"--errorSpawnFailedToSetup :: T.ErrorName-errorSpawnFailedToSetup = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.FailedToSetup"--errorSpawnConfigInvalid :: T.ErrorName-errorSpawnConfigInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ConfigInvalid"--errorSpawnServiceNotValid :: T.ErrorName-errorSpawnServiceNotValid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ServiceNotValid"--errorSpawnServiceNotFound :: T.ErrorName-errorSpawnServiceNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ServiceNotFound"--errorSpawnPermissionsInvalid :: T.ErrorName-errorSpawnPermissionsInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid"--errorSpawnFileInvalid :: T.ErrorName-errorSpawnFileInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.FileInvalid"--errorSpawnNoMemory :: T.ErrorName-errorSpawnNoMemory = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.NoMemory"--errorUnixProcessIdUnknown :: T.ErrorName-errorUnixProcessIdUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.UnixProcessIdUnknown"--errorInvalidFileContent :: T.ErrorName-errorInvalidFileContent = T.mkErrorName' "org.freedesktop.DBus.Error.InvalidFileContent"--errorSELinuxSecurityContextUnknown :: T.ErrorName-errorSELinuxSecurityContextUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"--errorAdtAuditDataUnknown :: T.ErrorName-errorAdtAuditDataUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.AdtAuditDataUnknown"--errorObjectPathInUse :: T.ErrorName-errorObjectPathInUse = T.mkErrorName' "org.freedesktop.DBus.Error.ObjectPathInUse"--errorInconsistentMessage :: T.ErrorName-errorInconsistentMessage = T.mkErrorName' "org.freedesktop.DBus.Error.InconsistentMessage"--@-\section{Misc. utility functions}--<<DBus/Util.hs>>=-<<copyright>>-module DBus.Util where-import Text.Parsec (Parsec, parse)-import Data.Char (digitToInt)--checkLength :: Int -> String -> Maybe String-checkLength length' s | length s <= length' = Just s-checkLength _ _ = Nothing--parseMaybe :: Parsec String () a -> String -> Maybe a-parseMaybe p = either (const Nothing) Just . parse p ""--mkUnsafe :: Show a => String -> (a -> Maybe b) -> a -> b-mkUnsafe label f x = case f x of-	Just x' -> x'-	Nothing -> error $ "Invalid " ++ label ++ ": " ++ show x--hexToInt :: String -> Int-hexToInt = foldl ((+) . (16 *)) 0 . map digitToInt--eitherToMaybe :: Either a b -> Maybe b-eitherToMaybe (Left  _) = Nothing-eitherToMaybe (Right x) = Just x--fromRight :: Either a b -> b-fromRight (Right x) = x-fromRight _ = error "DBus.Util.fromRight: Left"+@ D-Bus types are divided into two categories, ``atomic'' and ``container''+types. Atoms are actual values -- strings, numbers, etc. Containers store+atoms and other containers. The most interesting difference between the two+is that atoms may be used as the keys in associative mappings+(``dictionaries'').++Internally, types are represented using an enumeration.++<<DBus/Types.hs>>=+data Type+	= DBusBoolean+	| DBusByte+	| DBusInt16+	| DBusInt32+	| DBusInt64+	| DBusWord16+	| DBusWord32+	| DBusWord64+	| DBusDouble+	| DBusString+	| DBusSignature+	| DBusObjectPath+	| DBusVariant+	| DBusArray Type+	| DBusDictionary Type Type+	| DBusStructure [Type]+	deriving (Show, Eq)++<<DBus/Types.hs>>=+isAtomicType :: Type -> Bool+isAtomicType DBusBoolean    = True+isAtomicType DBusByte       = True+isAtomicType DBusInt16      = True+isAtomicType DBusInt32      = True+isAtomicType DBusInt64      = True+isAtomicType DBusWord16     = True+isAtomicType DBusWord32     = True+isAtomicType DBusWord64     = True+isAtomicType DBusDouble     = True+isAtomicType DBusString     = True+isAtomicType DBusSignature  = True+isAtomicType DBusObjectPath = True+isAtomicType _              = False++@ Each type can be converted to a textual representation, used in ``type+signatures'' or for debugging.++<<DBus/Types.hs>>=+typeCode :: Type -> Text++<<type exports>>=+  -- * Available types+  Type (..)+, typeCode++@ Certain Haskell types are considered ``built-in'' D-Bus types; that is,+they are directly represented in the D-Bus protocol.++<<DBus/Types.hs>>=+class (Show a, Eq a) => Builtin a where+	builtinDBusType :: a -> Type++@ \subsection{Variants}++A wrapper type is needed for safely storing generic D-Bus values in Haskell.+The D-Bus ``variant'' type is perfect for this, because variants may store+any D-Bus value.++To cleanly store any D-Bus type, without exposing the internal storage+mechanism, requires existential quantification and some run-time casting.++<<type extensions>>=+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE ExistentialQuantification #-}++<<type imports>>=+import Data.Typeable (Typeable, cast)++@ Any type which is an instance of {\tt Variable} is considered a valid+D-Bus value, because it can be used to construct {\tt Variant}s. However,+outside of this module, {\tt Variant}s can only be constructed from+pre-defined types.++<<DBus/Types.hs>>=+data Variant = forall a. (Variable a, Builtin a, Typeable a) => Variant a+	deriving (Typeable)++class Variable a where+	toVariant :: a -> Variant+	fromVariant :: Variant -> Maybe a++<<type exports>>=+  -- * Variants+, Variant+, Variable (..)++@ Variants can be printed, for debugging purposes -- this instance shouldn't+be parsed or inspected or anything like that, since the output format might+change drastically.++<<DBus/Types.hs>>=+instance Show Variant where+	showsPrec d (Variant x) = showParen (d > 10) $+		s "Variant " . shows code . s " " . showsPrec 11 x+		where code = typeCode . builtinDBusType $ x+		      s    = showString++@ In the test suite, it's useful to test that two variants have the same+value.++<<DBus/Types.hs>>=+instance Eq Variant where+	(Variant x) == (Variant y) = cast x == Just y++@ Since many operations on D-Bus values depend on having the correct type,+{\tt variantType} is used to retrieve which type is actually stored within+a {\tt Variant}.++<<DBus/Types.hs>>=+variantType :: Variant -> Type+variantType (Variant x) = builtinDBusType x++<<type exports>>=+, variantType++@ These helper macros will be used for defining instances of Haskell types.++<<DBus/Types.hs>>=+#define INSTANCE_BUILTIN(HASKELL, DBUS) \+	instance Builtin HASKELL where \+		{ builtinDBusType _ = DBUS };++#define INSTANCE_VARIABLE(HASKELL) \+	instance Variable HASKELL where \+		{ toVariant = Variant \+		; fromVariant (Variant x) = cast x };++#define BUILTIN_VARIABLE(HASKELL, DBUS) \+	INSTANCE_BUILTIN(HASKELL, DBUS) \+	INSTANCE_VARIABLE(HASKELL)++@ Since {\tt Variant}s are D-Bus values themselves, they have a type.++<<DBus/Types.hs>>=+BUILTIN_VARIABLE(Variant, DBusVariant)++@ \subsection{Numerics}++D-Bus supports most common numeric types:++\begin{table}[h]+\caption{D-Bus Numeric types}+\begin{center}+\begin{tabular}{ll}+\toprule+Type        & Description \\+\midrule+Boolean     & Either {\tt True} or {\tt False} \\+Byte        & 8-bit unsigned integer \\+Int16       & 16-bit signed integer \\+Int32       & 32-bit signed integer \\+Int64       & 64-bit signed integer \\+Word16      & 16-bit unsigned integer \\+Word32      & 32-bit unsigned integer \\+Word64      & 64-bit unsigned integer \\+Double      & 64-bit IEEE754 floating-point \\+\bottomrule+\end{tabular}+\end{center}+\end{table}++All D-Bus numeric types are fixed-length, so the {\tt Int} and {\tt Integer}+types can't be used. Instead, instances for the fixed-length integer types+are defined and any others will have to be converted.++<<type imports>>=+import Data.Word (Word8, Word16, Word32, Word64)+import Data.Int (Int16, Int32, Int64)++<<DBus/Types.hs>>=+BUILTIN_VARIABLE(Bool,   DBusBoolean)+BUILTIN_VARIABLE(Word8,  DBusByte)+BUILTIN_VARIABLE(Int16,  DBusInt16)+BUILTIN_VARIABLE(Int32,  DBusInt32)+BUILTIN_VARIABLE(Int64,  DBusInt64)+BUILTIN_VARIABLE(Word16, DBusWord16)+BUILTIN_VARIABLE(Word32, DBusWord32)+BUILTIN_VARIABLE(Word64, DBusWord64)+BUILTIN_VARIABLE(Double, DBusDouble)++@ \subsection{Strings}++Strings are a weird case; the built-in type, {\tt String}, is horribly+inefficent. To provide better performance for large strings, packed Unicode+strings defined in {\tt Data.Text} are used internally.++<<DBus/Types.hs>>=+BUILTIN_VARIABLE(TL.Text, DBusString)++@ There's two different {\tt Text} types, strict and lazy. It'd be a pain+to store both and have to convert later, so instead, all strict {\tt Text}+values are converted to lazy values.++<<type imports>>=+import qualified Data.Text as T++<<DBus/Types.hs>>=+instance Variable T.Text where+	toVariant = toVariant . TL.fromChunks . (:[])+	fromVariant = fmap (T.concat . TL.toChunks) . fromVariant++@ Built-in {\tt String}s can still be stored, of course, but it requires+a language extension.++<<type extensions>>=+{-# LANGUAGE TypeSynonymInstances #-}++<<DBus/Types.hs>>=+instance Variable String where+	toVariant = toVariant . TL.pack+	fromVariant = fmap TL.unpack . fromVariant++@ \subsection{Signatures}++@ Valid D-Bus types must obey certain rules, such as ``dict keys must be+atomic'', which are difficult to express in the Haskell type system.  A+{\tt Signature} is guaranteed to be valid according to these rules. Creating+one requires using the {\tt mkSignature} function, which will convert a valid+D-Bus signature string into a {\tt Signature}.++<<DBus/Types.hs>>=+BUILTIN_VARIABLE(Signature, DBusSignature)+data Signature = Signature { signatureTypes :: [Type] }+	deriving (Eq, Typeable)++instance Show Signature where+	showsPrec d x = showParen (d > 10) $+		showString "Signature " . shows (strSignature x)++@ Signatures can also be converted back into text, by concatenating the+type codes of their contained types.++<<DBus/Types.hs>>=+strSignature :: Signature -> Text+strSignature (Signature ts) = TL.concat $ map typeCode ts++@ It doesn't make much sense to sort signatures, but since they can be used+as dictionary keys, it's useful to have them as an instance of {\tt Ord}.++<<DBus/Types.hs>>=+instance Ord Signature where+	compare x y = compare (strSignature x) (strSignature y)++<<type exports>>=+  -- * Signatures+, Signature+, signatureTypes+, strSignature++@ \subsubsection{Type codes}++@ For atomic types, the type code is a single letter. Arrays, structures,+and dictionary types are multiple characters.++<<DBus/Types.hs>>=+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"++@ 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>>=+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>>=+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>>=+typeCode (DBusStructure ts) = TL.concat $+	["("] ++ map typeCode ts ++ [")"]++@ \subsubsection{Parsing}++When parsing, additional restrictions apply which are not inherent to the+D-Bus type system:++\begin{itemize}+\item Signatures may be at most 255 characters long.+\end{itemize}++Parsec is used to parse signatures.++<<type imports>>=+import Text.Parsec ((<|>))+import qualified Text.Parsec as P+import DBus.Util (checkLength, parseMaybe)++<<DBus/Types.hs>>=+mkSignature :: Text -> Maybe Signature+mkSignature = (parseMaybe sigParser =<<) . checkLength 255 . TL.unpack where+	sigParser = do+		types <- P.many parseType+		P.eof+		return $ Signature types+	parseType = parseAtom <|> parseContainer+	parseContainer =+		    parseArray+		<|> parseStruct+		<|> (P.char 'v' >> return DBusVariant)+	parseAtom =+		    (P.char 'b' >> return DBusBoolean)+		<|> (P.char 'y' >> return DBusByte)+		<|> (P.char 'n' >> return DBusInt16)+		<|> (P.char 'i' >> return DBusInt32)+		<|> (P.char 'x' >> return DBusInt64)+		<|> (P.char 'q' >> return DBusWord16)+		<|> (P.char 'u' >> return DBusWord32)+		<|> (P.char 't' >> return DBusWord64)+		<|> (P.char 'd' >> return DBusDouble)+		<|> (P.char 's' >> return DBusString)+		<|> (P.char 'g' >> return DBusSignature)+		<|> (P.char 'o' >> return DBusObjectPath)+	parseArray = do+		P.char 'a'+		parseDict <|> do+		t <- parseType+		return $ DBusArray t+	parseDict = do+		P.char '{'+		keyType <- parseAtom+		valueType <- parseType+		P.char '}'+		return $ DBusDictionary keyType valueType+	parseStruct = do+		P.char '('+		types <- P.many parseType+		P.char ')'+		return $ DBusStructure types++@ Since many signatures are defined as string literals, it's useful to+have a helper function to construct a signature directly from a string.+If the input string is invalid, {\tt error} will be called.++<<type imports>>=+import DBus.Util (mkUnsafe)++<<DBus/Types.hs>>=+mkSignature' :: Text -> Signature+mkSignature' = mkUnsafe "signature" mkSignature++@ 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'++@ \subsection{Object paths}++<<DBus/Types.hs>>=+BUILTIN_VARIABLE(ObjectPath, DBusObjectPath)+newtype ObjectPath = ObjectPath+	{ strObjectPath :: Text+	}+	deriving (Show, Eq, Ord, Typeable)++@ An object path may be one of++\begin{itemize}+\item The root path, {\tt "/"}.+\item {\tt '/'}, followed by one or more element names. Each element name+      contains characters in the set {\tt [a-zA-Z0-9\_]}, and must have at+      least one character.+\end{itemize}++Element names are separated by {\tt '/'}, and the path may not end in+{\tt '/'} unless it is the root path.++<<DBus/Types.hs>>=+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++<<type exports>>=+  -- * Object paths+, ObjectPath+, strObjectPath+, mkObjectPath+, mkObjectPath'++@ \subsection{Arrays}++Arrays are homogenous sequences of any valid D-Bus type. Arrays might be+empty, so the type they contain is stored instead of being calculated from+their contents (as in {\tt variantType}).++Many D-Bus APIs represent binary data using an array of bytes; therefore,+there is a special constructor for {\tt ByteString}-based arrays.++<<type imports>>=+import Data.ByteString.Lazy (ByteString)+import qualified Data.ByteString.Lazy as ByteString++<<DBus/Types.hs>>=+INSTANCE_VARIABLE(Array)+data Array+	= VariantArray Type [Variant]+	| ByteArray ByteString+	deriving (Eq, Typeable)++instance Builtin Array where+	builtinDBusType = DBusArray . arrayType++arrayType :: Array -> Type+arrayType (VariantArray t _) = t+arrayType (ByteArray _) = DBusByte++arrayItems :: Array -> [Variant]+arrayItems (VariantArray _ xs) = xs+arrayItems (ByteArray xs) = map toVariant $ ByteString.unpack xs++<<type exports>>=+  -- * Arrays+, Array+, arrayType+, arrayItems++@ Like {\tt Variant}, deriving {\tt Show} for {\tt Array} is mostly+just useful for debugging.++<<DBus/Types.hs>>=+instance Show Array where+	showsPrec d array = showParen (d > 10) $+		s "Array " . showSig . s " [" . s valueString . s "]" where+			s = showString+			showSig = shows . typeCode . arrayType $ array+			vs = [show x | (Variant x) <- arrayItems array]+			valueString = intercalate ", " vs++@ Clients constructing an array must provide the expected item type, which+will be checked for validity. Every item in the array will be checked against+the item type, to ensure the array is homogenous.++<<DBus/Types.hs>>=+arrayFromItems :: Type -> [Variant] -> Maybe Array+arrayFromItems DBusByte vs = do+	bytes <- mapM fromVariant vs+	Just . ByteArray . ByteString.pack $ bytes++arrayFromItems t vs = do+	mkSignature (typeCode t)+	if all (\x -> variantType x == t) vs+		then Just $ VariantArray t vs+		else Nothing++@ Additionally, for ease of use, an {\tt Array} can be converted directly+to/from lists of {\tt Variable} values.++<<DBus/Types.hs>>=+toArray :: Variable a => Type -> [a] -> Maybe Array+toArray t = arrayFromItems t . map toVariant++fromArray :: Variable a => Array -> Maybe [a]+fromArray = mapM fromVariant . arrayItems++<<type exports>>=+, toArray+, fromArray+, arrayFromItems++@ To provide a more efficient interface for byte array literals, these+functions bypass the conversions in {\tt toArray} and {\tt fromArray}++<<DBus/Types.hs>>=+arrayToBytes :: Array -> Maybe ByteString+arrayToBytes (ByteArray x) = Just x+arrayToBytes _             = Nothing++arrayFromBytes :: ByteString -> Array+arrayFromBytes = ByteArray++<<type exports>>=+, arrayToBytes+, arrayFromBytes++@ And to simplify inclusion of {\tt ByteString}s in message, instances of+{\tt Variable} exist for both strict and lazy {\tt ByteString}.++<<DBus/Types.hs>>=+instance Variable ByteString where+	toVariant = toVariant . arrayFromBytes+	fromVariant x = fromVariant x >>= arrayToBytes++<<type imports>>=+import qualified Data.ByteString as StrictByteString++<<DBus/Types.hs>>=+instance Variable StrictByteString.ByteString where+	toVariant x = toVariant . arrayFromBytes $ ByteString.fromChunks [x]+	fromVariant x = do+		chunks <- ByteString.toChunks `fmap` fromVariant x+		return $ StrictByteString.concat chunks++@ \subsection{Dictionaries}++Dictionaries are a homogenous (key $\rightarrow$ value) mapping, where the+key type must be atomic. Values may be of any valid D-Bus type. Like+{\tt Array}, {\tt Dictionary} stores its contained types.++<<DBus/Types.hs>>=+INSTANCE_VARIABLE(Dictionary)+data Dictionary = Dictionary+	{ dictionaryKeyType   :: Type+	, dictionaryValueType :: Type+	, dictionaryItems     :: [(Variant, Variant)]+	}+	deriving (Eq, Typeable)++instance Builtin Dictionary where+	builtinDBusType (Dictionary kt vt _) = DBusDictionary kt vt++<<type exports>>=+  -- * Dictionaries+, Dictionary+, dictionaryItems+, dictionaryKeyType+, dictionaryValueType++@ {\tt show}ing a {\tt Dictionary} displays the mapping in a more readable+format than a list of pairs.++<<type imports>>=+import Data.List (intercalate)++<<DBus/Types.hs>>=+instance Show Dictionary where+	showsPrec d (Dictionary kt vt pairs) = showParen (d > 10) $+		s "Dictionary " . showSig . s " {" . s valueString . s "}" where+			s = showString+			showSig = shows $ TL.append (typeCode kt) (typeCode vt)+			valueString = intercalate ", " $ map showPair pairs+			showPair ((Variant k), (Variant v)) =+				show k ++ " -> " ++ show v++@ Constructing a {\tt Dictionary} works like constructing an {\tt Array},+except that there are two types to check, and the key type must be atomic.++<<type imports>>=+import Control.Monad (unless)++<<DBus/Types.hs>>=+dictionaryFromItems :: Type -> Type -> [(Variant, Variant)] -> Maybe Dictionary+dictionaryFromItems kt vt pairs = do+	unless (isAtomicType kt) Nothing+	mkSignature (typeCode kt)+	mkSignature (typeCode vt)+	+	let sameType (k, v) = variantType k == kt &&+	                      variantType v == vt+	if all sameType pairs+		then Just $ Dictionary kt vt pairs+		else Nothing++@ The closest match for dictionary semantics in Haskell is the+{\tt Data.Map.Map} type. Therefore, the utility conversion functions work+with {\tt Map}s instead of pair lists.++<<type imports>>=+import Control.Arrow ((***))+import qualified Data.Map as Map++<<DBus/Types.hs>>=+toDictionary :: (Variable a, Variable b) => Type -> Type -> Map.Map a b+             -> Maybe Dictionary+toDictionary kt vt = dictionaryFromItems kt vt . pairs where+	pairs = map (toVariant *** toVariant) . Map.toList++<<type imports>>=+import Control.Monad (forM)++<<DBus/Types.hs>>=+fromDictionary :: (Variable a, Ord a, Variable b) => Dictionary+               -> Maybe (Map.Map a b)+fromDictionary (Dictionary _ _ vs) = do+	pairs <- forM vs $ \(k, v) -> do+		k' <- fromVariant k+		v' <- fromVariant v+		return (k', v')+	return $ Map.fromList pairs++<<type exports>>=+, toDictionary+, fromDictionary+, dictionaryFromItems++@ \subsubsection{Converting between {\tt Array} and {\tt Dictionary}}++Converting between {\tt Array} and {\tt Dictionary} is useful when+(un)marshaling -- dictionaries can be thought of as arrays of two-element+structures, much as a {\tt Map} is a list of pairs.++<<DBus/Types.hs>>=+dictionaryToArray :: Dictionary -> Array+dictionaryToArray (Dictionary kt vt items) = array where+	Just array = toArray itemType structs+	itemType = DBusStructure [kt, vt]+	structs = [Structure [k, v] | (k, v) <- items]++<<DBus/Types.hs>>=+arrayToDictionary :: Array -> Maybe Dictionary+arrayToDictionary array = do+	let toPair x = do+		struct <- fromVariant x+		case struct of+			Structure [k, v] -> Just (k, v)+			_                -> Nothing+	(kt, vt) <- case arrayType array of+		DBusStructure [kt, vt] -> Just (kt, vt)+		_                      -> Nothing+	pairs <- mapM toPair $ arrayItems array+	dictionaryFromItems kt vt pairs++<<type exports>>=+, dictionaryToArray+, arrayToDictionary++@ \subsection{Structures}++A heterogeneous, fixed-length container; equivalent in purpose to a Haskell+tuple.++<<DBus/Types.hs>>=+INSTANCE_VARIABLE(Structure)+data Structure = Structure [Variant]+	deriving (Show, Eq, Typeable)++instance Builtin Structure where+	builtinDBusType (Structure vs) = DBusStructure $ map variantType vs++<<type exports>>=+  -- * Structures+, Structure (..)++@ \subsection{Names}++Various aspects of D-Bus require the use of specially-formatted strings,+called ``names''. All names are limited to 255 characters, and use subsets+of ASCII.++Since all names have basically the same structure (a {\tt newtype}+declaration and some helper functions), I define a macro to automate+the definitions.++<<DBus/Types.hs>>=+#define NAME_TYPE(TYPE, NAME) \+	newtype TYPE = TYPE {str##TYPE :: Text} \+		deriving (Show, Eq, Ord); \+		                          \+	instance Variable TYPE where \+		{ toVariant = toVariant . str##TYPE \+		; fromVariant = (mk##TYPE =<<) . fromVariant }; \+		                                                \+	mk##TYPE##' :: Text -> TYPE; \+	mk##TYPE##' = mkUnsafe NAME mk##TYPE++<<type exports>>=+-- * Names++@ \subsubsection{Bus names}++There are two forms of bus names, ``unique'' and ``well-known''.++Unique names begin with {\tt `:'} and contain two or more elements, separated+by {\tt `.'}. Each element consists of characters from the set+{\tt [a-zA-Z0-9\_-]}.++Well-known names contain two or more elements, separated by {\tt `.'}. Each+element consists of characters from the set {\tt [a-zA-Z0-9\_-]}, and must+not start with a digit.++<<DBus/Types.hs>>=+NAME_TYPE(BusName, "bus name")++mkBusName :: Text -> Maybe BusName+mkBusName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where+	c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"+	c' = c ++ ['0'..'9']+	parser = (unique <|> wellKnown) >> P.eof >> return (BusName s)+	unique = P.char ':' >> elems c'+	wellKnown = elems c+	elems start = elem' start >> P.many1 (P.char '.' >> elem' start)+	elem' start = P.oneOf start >> P.many (P.oneOf c')++<<type exports>>=+  -- ** Bus names+, BusName+, strBusName+, mkBusName+, mkBusName'++@ \subsubsection{Interface names}++An interface name consists of two or more {\tt '.'}-separated elements. Each+element constists of characters from the set {\tt [a-zA-Z0-9\_]}, may not+start with a digit, and must have at least one character.++<<DBus/Types.hs>>=+NAME_TYPE(InterfaceName, "interface name")++mkInterfaceName :: Text -> Maybe InterfaceName+mkInterfaceName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where+	c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+	c' = c ++ ['0'..'9']+	element = P.oneOf c >> P.many (P.oneOf c')+	name = element >> P.many1 (P.char '.' >> element)+	parser = name >> P.eof >> return (InterfaceName s)++<<type exports>>=+  -- ** Interface names+, InterfaceName+, strInterfaceName+, mkInterfaceName+, mkInterfaceName'++@ \subsubsection{Error names}++Error names have the same format as interface names, so the parser logic+can just be re-purposed.++<<DBus/Types.hs>>=+NAME_TYPE(ErrorName, "error name")++mkErrorName :: Text -> Maybe ErrorName+mkErrorName = fmap (ErrorName . strInterfaceName) . mkInterfaceName++<<type exports>>=+  -- ** Error names+, ErrorName+, strErrorName+, mkErrorName+, mkErrorName'++@ \subsubsection{Member names}++Member names must contain only characters from the set {\tt [a-zA-Z0-9\_]},+may not begin with a digit, and must be at least one character long.++<<DBus/Types.hs>>=+NAME_TYPE(MemberName, "member name")++mkMemberName :: Text -> Maybe MemberName+mkMemberName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where+	c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+	c' = c ++ ['0'..'9']+	name = P.oneOf c >> P.many (P.oneOf c')+	parser = name >> P.eof >> return (MemberName s)++<<type exports>>=+  -- ** Member names+, MemberName+, strMemberName+, mkMemberName+, mkMemberName'++@+\section{Messages}++@ To prevent internal details of messages from leaking out to clients,+declarations are contained in an internal module and then re-exported+in the public module.++<<DBus/Message.hs>>=+<<copyright>>+module DBus.Message+	(<<message exports>>+	) where+import DBus.Message.Internal++<<DBus/Message/Internal.hs>>=+<<copyright>>+<<text extensions>>+module DBus.Message.Internal where+<<text imports>>+import qualified Data.Set as S+import Data.Word (Word8, Word32)+import Data.Maybe (fromMaybe)+import qualified DBus.Types as T+import DBus.Util (maybeIndex)++<<DBus/Message/Internal.hs>>=+class Message a where+	messageTypeCode     :: a -> Word8+	messageHeaderFields :: a -> [HeaderField]+	messageFlags        :: a -> S.Set Flag+	messageBody         :: a -> [T.Variant]++<<message exports>>=+Message ( messageFlags+        , messageBody+        )++@ \subsection{Flags}++The instance of {\tt Ord} only exists for storing flags in a set. Flags have+no inherent ordering.++<<DBus/Message/Internal.hs>>=+data Flag+	= NoReplyExpected+	| NoAutoStart+	deriving (Show, Eq, Ord)++<<message exports>>=+, Flag (..)++@ \subsection{Header fields}++<<DBus/Message/Internal.hs>>=+data HeaderField+	= Path        T.ObjectPath+	| Interface   T.InterfaceName+	| Member      T.MemberName+	| ErrorName   T.ErrorName+	| ReplySerial Serial+	| Destination T.BusName+	| Sender      T.BusName+	| Signature   T.Signature+	deriving (Show, Eq)++@ \subsection{Serials}++{\tt Serial} is just a wrapper around {\tt Word32}, to provide a bit of+added type-safety.++<<DBus/Message/Internal.hs>>=+newtype Serial = Serial { serialValue :: Word32 }+	deriving (Eq, Ord)++instance Show Serial where+	show (Serial x) = show x++instance T.Variable Serial where+	toVariant (Serial x) = T.toVariant x+	fromVariant = fmap Serial . T.fromVariant++@ Additionally, some useful functions exist for incrementing serials.++<<DBus/Message/Internal.hs>>=+firstSerial :: Serial+firstSerial = Serial 1++nextSerial :: Serial -> Serial+nextSerial (Serial x) = Serial (x + 1)++@ The {\tt Serial} constructor isn't useful to clients, because building+arbitrary serials doesn't make any sense.++<<message exports>>=+, Serial+, serialValue++@ \subsection{Message types}++<<DBus/Message/Internal.hs>>=+maybe' :: (a -> b) -> Maybe a -> [b]+maybe' f = maybe [] (\x' -> [f x'])++@ \subsubsection{Method calls}++<<DBus/Message/Internal.hs>>=+data MethodCall = MethodCall+	{ methodCallPath        :: T.ObjectPath+	, methodCallMember      :: T.MemberName+	, methodCallInterface   :: Maybe T.InterfaceName+	, methodCallDestination :: Maybe T.BusName+	, methodCallFlags       :: S.Set Flag+	, methodCallBody        :: [T.Variant]+	}+	deriving (Show, Eq)++instance Message MethodCall where+	messageTypeCode _ = 1+	messageFlags      = methodCallFlags+	messageBody       = methodCallBody+	messageHeaderFields m = concat+		[ [ Path    $ methodCallPath m+		  ,  Member $ methodCallMember m+		  ]+		, maybe' Interface . methodCallInterface $ m+		, maybe' Destination . methodCallDestination $ m+		]++<<message exports>>=+, MethodCall (..)++@ \subsubsection{Method returns}++<<DBus/Message/Internal.hs>>=+data MethodReturn = MethodReturn+	{ methodReturnSerial      :: Serial+	, methodReturnDestination :: Maybe T.BusName+	, methodReturnBody        :: [T.Variant]+	}+	deriving (Show, Eq)++instance Message MethodReturn where+	messageTypeCode _ = 2+	messageFlags    _ = S.fromList [NoReplyExpected, NoAutoStart]+	messageBody       = methodReturnBody+	messageHeaderFields m = concat+		[ [ ReplySerial $ methodReturnSerial m+		  ]+		, maybe' Destination . methodReturnDestination $ m+		]++<<message exports>>=+, MethodReturn (..)++@ \subsubsection{Errors}++<<DBus/Message/Internal.hs>>=+data Error = Error+	{ errorName        :: T.ErrorName+	, errorSerial      :: Serial+	, errorDestination :: Maybe T.BusName+	, errorBody        :: [T.Variant]+	}+	deriving (Show, Eq)++instance Message Error where+	messageTypeCode _ = 3+	messageFlags    _ = S.fromList [NoReplyExpected, NoAutoStart]+	messageBody       = errorBody+	messageHeaderFields m = concat+		[ [ ErrorName   $ errorName m+		  , ReplySerial $ errorSerial m+		  ]+		, maybe' Destination . errorDestination $ m+		]++@ Errors usually contain a human-readable message in their first body field.+This function lets it be retrieved easily, with a fallback if no valid+message was found.++<<DBus/Message/Internal.hs>>=+errorMessage :: Error -> Text+errorMessage msg = fromMaybe "(no error message)" $ do+	field <- maybeIndex (errorBody msg) 0+	text <- T.fromVariant field+	if TL.null text+		then Nothing+		else return text++<<message exports>>=+, Error (..)+, errorMessage++@ \subsubsection{Signals}++<<DBus/Message/Internal.hs>>=+data Signal = Signal+	{ signalPath        :: T.ObjectPath+	, signalMember      :: T.MemberName+	, signalInterface   :: T.InterfaceName+	, signalDestination :: Maybe T.BusName+	, signalBody        :: [T.Variant]+	}+	deriving (Show, Eq)++instance Message Signal where+	messageTypeCode _ = 4+	messageFlags    _ = S.fromList [NoReplyExpected, NoAutoStart]+	messageBody       = signalBody+	messageHeaderFields m = concat+		[ [ Path      $ signalPath m+		  , Member    $ signalMember m+		  , Interface $ signalInterface m+		  ]+		, maybe' Destination . signalDestination $ m+		]++<<message exports>>=+, Signal (..)++@ \subsubsection{Unknown messages}++Unknown messages are used for storing information about messages without+a recognised type code. They are not instances of {\tt Message}, because+if they were, then clients could accidentally send invalid messages over+the bus.++<<DBus/Message/Internal.hs>>=+data Unknown = Unknown+	{ unknownType    :: Word8+	, unknownFlags   :: S.Set Flag+	, unknownBody    :: [T.Variant]+	}+	deriving (Show, Eq)++<<message exports>>=+, Unknown (..)++@ \subsection{Received messages}++Messages received from a bus have additional fields which do not make sense+when sending.++If a message has an unknown type, its serial and origin are still useful+for sending an error reply.++<<DBus/Message/Internal.hs>>=+data ReceivedMessage+	= ReceivedMethodCall   Serial (Maybe T.BusName) MethodCall+	| ReceivedMethodReturn Serial (Maybe T.BusName) MethodReturn+	| ReceivedError        Serial (Maybe T.BusName) Error+	| ReceivedSignal       Serial (Maybe T.BusName) Signal+	| ReceivedUnknown      Serial (Maybe T.BusName) Unknown+	deriving (Show, Eq)++<<DBus/Message/Internal.hs>>=+receivedSerial :: ReceivedMessage -> Serial+receivedSerial (ReceivedMethodCall   s _ _) = s+receivedSerial (ReceivedMethodReturn s _ _) = s+receivedSerial (ReceivedError        s _ _) = s+receivedSerial (ReceivedSignal       s _ _) = s+receivedSerial (ReceivedUnknown      s _ _) = s++<<DBus/Message/Internal.hs>>=+receivedSender :: ReceivedMessage -> Maybe T.BusName+receivedSender (ReceivedMethodCall   _ s _) = s+receivedSender (ReceivedMethodReturn _ s _) = s+receivedSender (ReceivedError        _ s _) = s+receivedSender (ReceivedSignal       _ s _) = s+receivedSender (ReceivedUnknown      _ s _) = s++<<DBus/Message/Internal.hs>>=+receivedBody :: ReceivedMessage -> [T.Variant]+receivedBody (ReceivedMethodCall   _ _ x) = messageBody x+receivedBody (ReceivedMethodReturn _ _ x) = messageBody x+receivedBody (ReceivedError        _ _ x) = messageBody x+receivedBody (ReceivedSignal       _ _ x) = messageBody x+receivedBody _ = []++<<message exports>>=+, ReceivedMessage (..)+, receivedSerial+, receivedSender+, receivedBody++@+\section{Wire format}++<<DBus/Wire.hs>>=+<<copyright>>+<<text extensions>>+<<wire extensions>>+module DBus.Wire (<<wire exports>>) where+<<text imports>>+<<wire imports>>+import Control.Monad (when, unless)+import Data.Maybe (fromJust, listToMaybe, fromMaybe)+import Data.Word (Word8, Word32, Word64)+import Data.Int (Int16, Int32, Int64)++import qualified DBus.Types as T++@ \subsection{Endianness}++<<DBus/Wire.hs>>=+data Endianness = LittleEndian | BigEndian+	deriving (Show, Eq)++encodeEndianness :: Endianness -> Word8+encodeEndianness LittleEndian = 108+encodeEndianness BigEndian    = 66++decodeEndianness :: Word8 -> Maybe Endianness+decodeEndianness 108 = Just LittleEndian+decodeEndianness 66  = Just BigEndian+decodeEndianness _   = Nothing++<<wire exports>>=+  Endianness (..)++@ \subsection{Alignment}++Every built-in type has an associated alignment. If a value of the given+type is marshaled, it must have {\sc nul} bytes inserted until it starts+on a byte index divisible by its alignment.++<<DBus/Wire.hs>>=+alignment :: T.Type -> Word8+<<alignments>>++padding :: Word64 -> Word8 -> Word64+padding current count = required where+	count' = fromIntegral count+	missing = mod current count'+	required = if missing > 0+		then count' - missing+		else 0++<<wire exports>>=+, alignment++@ \subsection{Marshaling}++Marshaling is implemented using an error transformer over an internal+state. The {\tt Builder} type is used for efficient construction of lazy+byte strings, but it doesn't provide any way to retrieve the length of its+internal buffer, so the byte count is tracked separately.++<<wire imports>>=+import qualified Control.Monad.State as ST+import qualified Control.Monad.Error as E+import qualified Data.ByteString.Lazy as L+import qualified Data.Binary.Builder as B++<<wire extensions>>=+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE BangPatterns #-}++<<DBus/Wire.hs>>=+data MarshalState = MarshalState Endianness B.Builder !Word64+newtype MarshalM a = MarshalM (E.ErrorT MarshalError (ST.State MarshalState) a)+	deriving (Monad, E.MonadError MarshalError, ST.MonadState MarshalState)+type Marshal = MarshalM ()++@ Clients can perform marshaling via {\tt marshal} and {\tt runMarshal},+which will generate a {\tt ByteString} with the fully marshaled data.++<<wire exports>>=+, MarshalM+, Marshal+, marshal+, runMarshal++<<DBus/Wire.hs>>=+runMarshal :: Marshal -> Endianness -> Either MarshalError L.ByteString+runMarshal (MarshalM m) e = case ST.runState (E.runErrorT m) initialState of+	(Right _, MarshalState _ builder _) -> Right (B.toLazyByteString builder)+	(Left  x, _) -> Left x+	where initialState = MarshalState e B.empty 0++<<DBus/Wire.hs>>=+marshal :: T.Variant -> Marshal+marshal v = marshalType (T.variantType v) where+	x :: T.Variable a => a+	x = fromJust . T.fromVariant $ v+	marshalType :: T.Type -> Marshal+	<<marshalers>>++@ TODO: describe these functions++<<DBus/Wire.hs>>=+append :: L.ByteString -> Marshal+append bytes = do+	(MarshalState e builder count) <- ST.get+	let builder' = B.append builder $ B.fromLazyByteString bytes+	    count' = count + (fromIntegral $ L.length bytes)+	ST.put $ MarshalState e builder' count'++<<DBus/Wire.hs>>=+pad :: Word8 -> Marshal+pad count = do+	(MarshalState _ _ existing) <- ST.get+	let padding' = fromIntegral $ padding existing count+	append $ L.replicate padding' 0++@ Most numeric values already have marshalers implemented in the+{\tt Data.Binary.Builder} module; this function lets them be re-used easily.++<<DBus/Wire.hs>>=+marshalBuilder :: Word8 -> (a -> B.Builder) -> (a -> B.Builder) -> a -> Marshal+marshalBuilder size be le x = do+	pad size+	(MarshalState e builder count) <- ST.get+	let builder' = B.append builder $ case e of+		BigEndian -> be x+		LittleEndian -> le x+	let count' = count + (fromIntegral size)+	ST.put $ MarshalState e builder' count'++@ \subsubsection{Errors}++Marshaling can fail for four reasons:++\begin{itemize}+\item The message exceeds the maximum message size of $2^{27}$ bytes.+\item An array in the message exceeds the maximum array size of $2^{26}$ bytes.+\item The body's signature is not valid (for example, more than 255 fields).+\item A variant's signature is not valid -- same causes as an invalid body+      signature.+\item Some text contains {\sc nul} ({\tt '\textbackslash{}0'}).+\end{itemize}++<<DBus/Wire.hs>>=+data MarshalError+	= MessageTooLong Word64+	| ArrayTooLong Word64+	| InvalidBodySignature Text+	| InvalidVariantSignature Text+	| TextContainsNUL Text+	deriving (Eq)++instance Show MarshalError where+	show (MessageTooLong x) = concat+		["Message too long (", show x, " bytes)."]+	show (ArrayTooLong x) = concat+		["Array too long (", show x, " bytes)."]+	show (InvalidBodySignature x) = concat+		["Invalid body signature: ", show x]+	show (InvalidVariantSignature x) = concat+		["Invalid variant signature: ", show x]+	show (TextContainsNUL x) = concat+		["Text contains NUL: ", show x]++instance E.Error MarshalError++<<wire exports>>=+, MarshalError (..)++@ \subsection{Unmarshaling}++Unmarshaling also uses an error transformer and internal state.++<<wire exports>>=+, Unmarshal+, unmarshal+, runUnmarshal++<<DBus/Wire.hs>>=+data UnmarshalState = UnmarshalState Endianness L.ByteString !Word64+newtype Unmarshal a = Unmarshal (E.ErrorT UnmarshalError (ST.State UnmarshalState) a)+	deriving (Monad, Functor, E.MonadError UnmarshalError, ST.MonadState UnmarshalState)++<<DBus/Wire.hs>>=+runUnmarshal :: Unmarshal a -> Endianness -> L.ByteString -> Either UnmarshalError a+runUnmarshal (Unmarshal m) e bytes = ST.evalState (E.runErrorT m) state where+	state = UnmarshalState e bytes 0++<<DBus/Wire.hs>>=+unmarshal :: T.Signature -> Unmarshal [T.Variant]+unmarshal = mapM unmarshalType . T.signatureTypes++unmarshalType :: T.Type -> Unmarshal T.Variant+<<unmarshalers>>++@ TODO: describe these functions++<<DBus/Wire.hs>>=+consume :: Word64 -> Unmarshal L.ByteString+consume count = do+	(UnmarshalState e bytes offset) <- ST.get+	let (x, bytes') = L.splitAt (fromIntegral count) bytes+	unless (L.length x == fromIntegral count) $+		E.throwError $ UnexpectedEOF offset+	+	ST.put $ UnmarshalState e bytes' (offset + count)+	return x++<<DBus/Wire.hs>>=+skipPadding :: Word8 -> Unmarshal ()+skipPadding count = do+	(UnmarshalState _ _ offset) <- ST.get+	bytes <- consume $ padding offset count+	unless (L.all (== 0) bytes) $+		E.throwError $ InvalidPadding offset++<<DBus/Wire.hs>>=+skipTerminator :: Unmarshal ()+skipTerminator = do+	(UnmarshalState _ _ offset) <- ST.get+	bytes <- consume 1+	unless (L.all (== 0) bytes) $+		E.throwError $ MissingTerminator offset++<<DBus/Wire.hs>>=+fromMaybeU :: Show a => Text -> (a -> Maybe b) -> a -> Unmarshal b+fromMaybeU label f x = case f x of+	Just x' -> return x'+	Nothing -> E.throwError . Invalid label . TL.pack . show $ x++fromMaybeU' :: (Show a, T.Variable b) => Text -> (a -> Maybe b) -> a+           -> Unmarshal T.Variant+fromMaybeU' label f x = do+	x' <- fromMaybeU label f x+	return $ T.toVariant x'++<<wire imports>>=+import qualified Data.Binary.Get as G++<<DBus/Wire.hs>>=+unmarshalGet :: Word8 -> G.Get a -> G.Get a -> Unmarshal a+unmarshalGet count be le = do+	skipPadding count+	(UnmarshalState e _ _) <- ST.get+	bs <- consume . fromIntegral $ count+	let get' = case e of+		BigEndian -> be+		LittleEndian -> le+	return $ G.runGet get' bs++unmarshalGet' :: T.Variable a => Word8 -> G.Get a -> G.Get a+              -> Unmarshal T.Variant+unmarshalGet' count be le = T.toVariant `fmap` unmarshalGet count be le++<<DBus/Wire.hs>>=+untilM :: Monad m => m Bool -> m a -> m [a]+untilM test comp = do+	done <- test+	if done+		then return []+		else do+			x <- comp+			xs <- untilM test comp+			return $ x:xs++@ \subsubsection{Errors}++Unmarshaling can fail for four reasons:++\begin{itemize}+\item The message's declared protocol version is unsupported.+\item Unexpected {\sc eof}, when there are less bytes remaining than are+      required.+\item An invalid byte sequence for a given value type.+\item Missing required header fields for the declared message type.+\item Non-zero bytes were found where padding was expected.+\item A string, signature, or object path was not {\sc null}-terminated.+\item An array's size didn't match the number of elements+\end{itemize}++<<DBus/Wire.hs>>=+data UnmarshalError+	= UnsupportedProtocolVersion Word8+	| UnexpectedEOF Word64+	| Invalid Text Text+	| MissingHeaderField Text+	| InvalidHeaderField Text T.Variant+	| InvalidPadding Word64+	| MissingTerminator Word64+	| ArraySizeMismatch+	deriving (Eq)++instance Show UnmarshalError where+	show (UnsupportedProtocolVersion x) = concat+		["Unsupported protocol version: ", show x]+	show (UnexpectedEOF pos) = concat+		["Unexpected EOF at position ", show pos]+	show (Invalid label x) = TL.unpack $ TL.concat+		["Invalid ", label, ": ", x]+	show (MissingHeaderField x) = concat+		["Required field " , show x , " is missing."]+	show (InvalidHeaderField x got) = concat+		[ "Invalid header field ", show x, ": ", show got]+	show (InvalidPadding pos) = concat+		["Invalid padding at position ", show pos]+	show (MissingTerminator pos) = concat+		["Missing NUL terminator at position ", show pos]+	show ArraySizeMismatch = "Array size mismatch"++instance E.Error UnmarshalError++<<wire exports>>=+, UnmarshalError (..)++@ \subsection{Numerics}++Numeric values are fixed-length, and aligned ``naturally'' -- ie, a 4-byte+integer will have a 4-byte alignment.++<<alignments>>=+alignment T.DBusByte    = 1+alignment T.DBusWord16  = 2+alignment T.DBusWord32  = 4+alignment T.DBusWord64  = 8+alignment T.DBusInt16   = 2+alignment T.DBusInt32   = 4+alignment T.DBusInt64   = 8+alignment T.DBusDouble  = 8++@ Because {\tt Word32}s are often used for other types, there's+separate functions for handling them.++<<DBus/Wire.hs>>=+marshalWord32 :: Word32 -> Marshal+marshalWord32 = marshalBuilder 4 B.putWord32be B.putWord32le++unmarshalWord32 :: Unmarshal Word32+unmarshalWord32 = unmarshalGet 4 G.getWord32be G.getWord32le++<<marshalers>>=+marshalType T.DBusByte   = append $ L.singleton x+marshalType T.DBusWord16 = marshalBuilder 2 B.putWord16be B.putWord16le x+marshalType T.DBusWord32 = marshalBuilder 4 B.putWord32be B.putWord32le x+marshalType T.DBusWord64 = marshalBuilder 8 B.putWord64be B.putWord64le x+marshalType T.DBusInt16  = marshalBuilder 2 B.putWord16be B.putWord16le $ fromIntegral (x :: Int16)+marshalType T.DBusInt32  = marshalBuilder 4 B.putWord32be B.putWord32le $ fromIntegral (x :: Int32)+marshalType T.DBusInt64  = marshalBuilder 8 B.putWord64be B.putWord64le $ fromIntegral (x :: Int64)++<<unmarshalers>>=+unmarshalType T.DBusByte = fmap (T.toVariant . L.head) $ consume 1+unmarshalType T.DBusWord16 = unmarshalGet' 2 G.getWord16be G.getWord16le+unmarshalType T.DBusWord32 = unmarshalGet' 4 G.getWord32be G.getWord32le+unmarshalType T.DBusWord64 = unmarshalGet' 8 G.getWord64be G.getWord64le++unmarshalType T.DBusInt16  = do+	x <- unmarshalGet 2 G.getWord16be G.getWord16le+	return . T.toVariant $ (fromIntegral x :: Int16)++unmarshalType T.DBusInt32  = do+	x <- unmarshalGet 4 G.getWord32be G.getWord32le+	return . T.toVariant $ (fromIntegral x :: Int32)++unmarshalType T.DBusInt64  = do+	x <- unmarshalGet 8 G.getWord64be G.getWord64le+	return . T.toVariant $ (fromIntegral x :: Int64)++@ {\tt Double}s are marshaled as in-bit IEEE-754 floating-point format.++<<wire imports>>=+import Data.Binary.Put (runPut)+import qualified Data.Binary.IEEE754 as IEEE++<<marshalers>>=+marshalType T.DBusDouble = do+	pad 8+	(MarshalState e _ _) <- ST.get+	let put = case e of+		BigEndian -> IEEE.putFloat64be+		LittleEndian -> IEEE.putFloat64le+	let bytes = runPut $ put x+	append bytes++<<unmarshalers>>=+unmarshalType T.DBusDouble = unmarshalGet' 8 IEEE.getFloat64be IEEE.getFloat64le++@ \subsection{Booleans}++Booleans are marshaled as 4-byte unsigned integers containing either of+the values 0 or 1. Yes, really.++<<alignments>>=+alignment T.DBusBoolean = 4++<<marshalers>>=+marshalType T.DBusBoolean = marshalWord32 $ if x then 1 else 0++<<unmarshalers>>=+unmarshalType T.DBusBoolean = unmarshalWord32 >>=+	fromMaybeU' "boolean" (\x -> case x of+		0 -> Just False+		1 -> Just True+		_ -> Nothing)++@ \subsection{Strings and object paths}++Strings are encoded in {\sc utf-8}, terminated with {\tt NUL}, and prefixed+with their length as an unsigned 32-bit integer. Their alignment is that of+their length. Object paths are marshaled just like strings, though additional+checks are required when unmarshaling.++TODO: If the input is invalid, these {\sc utf8} functions will raise an+exception, rather than return an error value. Eventually they should be+wrapped with {\tt unsafePerformIO} and be tweaked to return a proper+{\tt Maybe} or {\tt Either}.++<<wire imports>>=+import Data.Text.Lazy.Encoding (encodeUtf8, decodeUtf8)++<<DBus/Wire.hs>>=+marshalText :: Text -> Marshal+marshalText x = do+	let bytes = encodeUtf8 x+	when (L.any (== 0) bytes) $+		E.throwError $ TextContainsNUL x+	marshalWord32 . fromIntegral . L.length $ bytes+	append bytes+	append (L.singleton 0)++<<DBus/Wire.hs>>=+unmarshalText :: Unmarshal Text+unmarshalText = do+	byteCount <- unmarshalWord32+	bytes <- consume . fromIntegral $ byteCount+	skipTerminator+	return . decodeUtf8 $ bytes++<<alignments>>=+alignment T.DBusString     = 4+alignment T.DBusObjectPath = 4++<<marshalers>>=+marshalType T.DBusString = marshalText x+marshalType T.DBusObjectPath = marshalText . T.strObjectPath $ x++<<unmarshalers>>=+unmarshalType T.DBusString = fmap T.toVariant unmarshalText++unmarshalType T.DBusObjectPath = unmarshalText >>=+	fromMaybeU' "object path" T.mkObjectPath++@ \subsection{Signatures}++Signatures are similar to strings, except their length is limited to 255+characters and is therefore stored as a single byte.++<<DBus/Wire.hs>>=+marshalSignature :: T.Signature -> Marshal+marshalSignature x = do+	let bytes = encodeUtf8 . T.strSignature $ x+	let size = fromIntegral . L.length $ bytes+	append (L.singleton size)+	append bytes+	append (L.singleton 0)++<<DBus/Wire.hs>>=+unmarshalSignature :: Unmarshal T.Signature+unmarshalSignature = do+	byteCount <- L.head `fmap` consume 1+	sigText <- fmap decodeUtf8 $ consume . fromIntegral $ byteCount+	skipTerminator+	fromMaybeU "signature" T.mkSignature sigText++<<alignments>>=+alignment T.DBusSignature  = 1++<<marshalers>>=+marshalType T.DBusSignature = marshalSignature x++<<unmarshalers>>=+unmarshalType T.DBusSignature = fmap T.toVariant unmarshalSignature++@ \subsection{Containers}++@ \subsubsection{Arrays}++<<alignments>>=+alignment (T.DBusArray _) = 4++<<marshalers>>=+marshalType (T.DBusArray _) = marshalArray x++<<unmarshalers>>=+unmarshalType (T.DBusArray t) = T.toVariant `fmap` unmarshalArray t++@ Marshaling arrays is complicated, because the array body must be marshaled+\emph{first} to calculate the array length. This requires building a+temporary marshaler, to get the padding right.++<<wire imports>>=+import qualified DBus.Constants as C++<<DBus/Wire.hs>>=+marshalArray :: T.Array -> Marshal+marshalArray x = do+	(arrayPadding, arrayBytes) <- getArrayBytes (T.arrayType x) x+	let arrayLen = L.length arrayBytes+	when (arrayLen > fromIntegral C.arrayMaximumLength)+		(E.throwError $ ArrayTooLong $ fromIntegral arrayLen)+	marshalWord32 $ fromIntegral arrayLen+	append $ L.replicate arrayPadding 0+	append arrayBytes++<<DBus/Wire.hs>>=+getArrayBytes :: T.Type -> T.Array -> MarshalM (Int64, L.ByteString)+getArrayBytes T.DBusByte x = return (0, bytes) where+	Just bytes = T.arrayToBytes x++<<DBus/Wire.hs>>=+getArrayBytes itemType x = do+	let vs = T.arrayItems x+	s <- ST.get+	(MarshalState _ _ afterLength) <- marshalWord32 0 >> ST.get+	(MarshalState e _ afterPadding) <- pad (alignment itemType) >> ST.get+	+	ST.put $ MarshalState e B.empty afterPadding+	(MarshalState _ itemBuilder _) <- mapM_ marshal vs >> ST.get+	+	let itemBytes = B.toLazyByteString itemBuilder+	    paddingSize = fromIntegral $ afterPadding - afterLength+	+	ST.put s+	return (paddingSize, itemBytes)++@ Unmarshaling is much easier, especially if it's a byte array.++<<DBus/Wire.hs>>=+unmarshalArray :: T.Type -> Unmarshal T.Array+unmarshalArray T.DBusByte = do+	byteCount <- unmarshalWord32+	T.arrayFromBytes `fmap` consume (fromIntegral byteCount)++<<DBus/Wire.hs>>=+unmarshalArray itemType = do+	let getOffset = do+		(UnmarshalState _ _ o) <- ST.get+		return o+	byteCount <- unmarshalWord32+	skipPadding (alignment itemType)+	start <- getOffset+	let end = start + fromIntegral byteCount+	vs <- untilM (fmap (>= end) getOffset) (unmarshalType itemType)+	end' <- getOffset+	when (end' > end) $+		E.throwError ArraySizeMismatch+	fromMaybeU "array" (T.arrayFromItems itemType) vs++@ \subsubsection{Dictionaries}++<<alignments>>=+alignment (T.DBusDictionary _ _) = 4++<<marshalers>>=+marshalType (T.DBusDictionary _ _) = marshalArray (T.dictionaryToArray x)++<<unmarshalers>>=+unmarshalType (T.DBusDictionary kt vt) = do+	let pairType = T.DBusStructure [kt, vt]+	array <- unmarshalArray pairType+	fromMaybeU' "dictionary" T.arrayToDictionary array++@ \subsubsection{Structures}++<<alignments>>=+alignment (T.DBusStructure _) = 8++<<marshalers>>=+marshalType (T.DBusStructure _) = do+	let T.Structure vs = x+	pad 8+	mapM_ marshal vs++<<unmarshalers>>=+unmarshalType (T.DBusStructure ts) = do+	skipPadding 8+	fmap (T.toVariant . T.Structure) $ mapM unmarshalType ts++@ \subsubsection{Variants}++<<alignments>>=+alignment T.DBusVariant = 1++<<marshalers>>=+marshalType T.DBusVariant = do+	let rawSig = T.typeCode . T.variantType $ x+	sig <- case T.mkSignature rawSig of+		Just x' -> return x'+		Nothing -> E.throwError $ InvalidVariantSignature rawSig+	marshalSignature sig+	marshal x++<<unmarshalers>>=+unmarshalType T.DBusVariant = do+	let getType sig = case T.signatureTypes sig of+		[t] -> Just t+		_   -> Nothing+	+	t <- fromMaybeU "variant signature" getType =<< unmarshalSignature+	T.toVariant `fmap` unmarshalType t++@ \subsection{Messages}++<<wire imports>>=+import qualified DBus.Message.Internal as M++@ \subsubsection{Flags}++<<wire imports>>=+import Data.Bits ((.|.), (.&.))+import qualified Data.Set as Set++<<DBus/Wire.hs>>=+encodeFlags :: Set.Set M.Flag -> Word8+encodeFlags flags = foldr (.|.) 0 $ map flagValue $ Set.toList flags where+	flagValue M.NoReplyExpected = 0x1+	flagValue M.NoAutoStart     = 0x2++decodeFlags :: Word8 -> Set.Set M.Flag+decodeFlags word = Set.fromList flags where+	flagSet = [ (0x1, M.NoReplyExpected)+	          , (0x2, M.NoAutoStart)+	          ]+	flags = flagSet >>= \(x, y) -> [y | word .&. x > 0]++@ \subsubsection{Header fields}++<<DBus/Wire.hs>>=+encodeField :: M.HeaderField -> T.Structure+encodeField (M.Path x)        = encodeField' 1 x+encodeField (M.Interface x)   = encodeField' 2 x+encodeField (M.Member x)      = encodeField' 3 x+encodeField (M.ErrorName x)   = encodeField' 4 x+encodeField (M.ReplySerial x) = encodeField' 5 x+encodeField (M.Destination x) = encodeField' 6 x+encodeField (M.Sender x)      = encodeField' 7 x+encodeField (M.Signature x)   = encodeField' 8 x++encodeField' :: T.Variable a => Word8 -> a -> T.Structure+encodeField' code x = T.Structure+	[ T.toVariant code+	, T.toVariant $ T.toVariant x+	]++<<DBus/Wire.hs>>=+decodeField :: Monad m => T.Structure+            -> E.ErrorT UnmarshalError m [M.HeaderField]+decodeField struct = case unpackField struct of+	(1, x) -> decodeField' x M.Path "path"+	(2, x) -> decodeField' x M.Interface "interface"+	(3, x) -> decodeField' x M.Member "member"+	(4, x) -> decodeField' x M.ErrorName "error name"+	(5, x) -> decodeField' x M.ReplySerial "reply serial"+	(6, x) -> decodeField' x M.Destination "destination"+	(7, x) -> decodeField' x M.Sender "sender"+	(8, x) -> decodeField' x M.Signature "signature"+	_      -> return []++decodeField' :: (Monad m, T.Variable a) => T.Variant -> (a -> b) -> Text+             -> E.ErrorT UnmarshalError m [b]+decodeField' x f label = case T.fromVariant x of+	Just x' -> return [f x']+	Nothing -> E.throwError $ InvalidHeaderField label x++<<DBus/Wire.hs>>=+unpackField :: T.Structure -> (Word8, T.Variant)+unpackField struct = (c', v') where+	T.Structure [c, v] = struct+	c' = fromJust . T.fromVariant $ c+	v' = fromJust . T.fromVariant $ v++@ \subsubsection{Header layout}++TODO: describe header layout here++@ \subsubsection{Marshaling}++<<wire exports>>=+, marshalMessage++<<DBus/Wire.hs>>=+marshalMessage :: M.Message a => Endianness -> M.Serial -> a+               -> Either MarshalError L.ByteString+marshalMessage e serial msg = runMarshal marshaler e where+	body = M.messageBody msg+	marshaler = do+		sig <- checkBodySig body+		empty <- ST.get+		mapM_ marshal body+		(MarshalState _ bodyBytesB _) <- ST.get+		ST.put empty+		marshalEndianness e+		let bodyBytes = B.toLazyByteString bodyBytesB+		marshalHeader msg serial sig+			$ fromIntegral . L.length $ bodyBytes+		pad 8+		append bodyBytes+		checkMaximumSize++<<DBus/Wire.hs>>=+checkBodySig :: [T.Variant] -> MarshalM T.Signature+checkBodySig vs = let+	sigStr = TL.concat . map (T.typeCode . T.variantType) $ vs+	invalid = E.throwError $ InvalidBodySignature sigStr+	in case T.mkSignature sigStr of+		Just x -> return x+		Nothing -> invalid++<<DBus/Wire.hs>>=+marshalHeader :: M.Message a => a -> M.Serial -> T.Signature -> Word32+              -> Marshal+marshalHeader msg serial bodySig bodyLength = do+	let fields = M.Signature bodySig : M.messageHeaderFields msg+	marshal . T.toVariant . M.messageTypeCode $ msg+	marshal . T.toVariant . encodeFlags . M.messageFlags $ msg+	marshal . T.toVariant $ C.protocolVersion+	marshalWord32 bodyLength+	marshal . T.toVariant $ serial+	let fieldType = T.DBusStructure [T.DBusByte, T.DBusVariant]+	marshal . T.toVariant . fromJust . T.toArray fieldType+	        $ map encodeField fields++<<DBus/Wire.hs>>=+marshalEndianness :: Endianness -> Marshal+marshalEndianness = marshal . T.toVariant . encodeEndianness++<<DBus/Wire.hs>>=+checkMaximumSize :: Marshal+checkMaximumSize = do+	(MarshalState _ _ messageLength) <- ST.get+	when (messageLength > fromIntegral C.messageMaximumLength)+		(E.throwError $ MessageTooLong $ fromIntegral messageLength)++@ \subsubsection{Unmarshaling}++<<wire exports>>=+, unmarshalMessage++<<DBus/Wire.hs>>=+unmarshalMessage :: Monad m => (Word32 -> m L.ByteString)+                 -> m (Either UnmarshalError M.ReceivedMessage)+unmarshalMessage getBytes' = E.runErrorT $ do+	let getBytes = E.lift . getBytes'+	+	<<read fixed-length header>>+	<<read full header>>+	<<read body>>+	<<build message>>++@ The first part of the header has a fixed size of 16 bytes, so it can be+retrieved without any size calculations.++<<read fixed-length header>>=+let fixedSig = T.mkSignature' "yyyyuuu"+fixedBytes <- getBytes 16++@ The first field of interest is the protocol version; if the incoming+message's version is different from this library, the message cannot be+parsed.++<<read fixed-length header>>=+let messageVersion = L.index fixedBytes 3+when (messageVersion /= C.protocolVersion) $+	E.throwError $ UnsupportedProtocolVersion messageVersion++@ Next is the endianness, used for parsing pretty much every other field.++<<read fixed-length header>>=+let eByte = L.index fixedBytes 0+endianness <- case decodeEndianness eByte of+	Just x' -> return x'+	Nothing -> E.throwError . Invalid "endianness" . TL.pack . show $ eByte++@ With the endianness out of the way, the rest of the fixed header+can be decoded++<<read fixed-length header>>=+let unmarshal' x bytes = case runUnmarshal (unmarshal x) endianness bytes of+	Right x' -> return x'+	Left  e  -> E.throwError e+fixed <- unmarshal' fixedSig fixedBytes+let typeCode = fromJust . T.fromVariant $ fixed !! 1+let flags = decodeFlags . fromJust . T.fromVariant $ fixed !! 2+let bodyLength = fromJust . T.fromVariant $ fixed !! 4+let serial = fromJust . T.fromVariant $ fixed !! 5++@ The last field of the fixed header is actually part of the field array,+but is treated as a single {\tt Word32} so it'll be known how many bytes+to retrieve.++<<read fixed-length header>>=+let fieldByteCount = fromJust . T.fromVariant $ fixed !! 6++@ With the field byte count, the remainder of the header bytes can be+pulled out of the monad.++<<read full header>>=+let headerSig  = T.mkSignature' "yyyyuua(yv)"+fieldBytes <- getBytes fieldByteCount+let headerBytes = L.append fixedBytes fieldBytes+header <- unmarshal' headerSig headerBytes++@ And the header fields can be parsed.++<<read full header>>=+let fieldArray = fromJust . T.fromVariant $ header !! 6+let fieldStructures = fromJust . T.fromArray $ fieldArray+fields <- concat `fmap` mapM decodeField fieldStructures++@ The body is always aligned to 8 bytes, so pull out the padding before+unmarshaling it.++<<read body>>=+let bodyPadding = padding (fromIntegral fieldByteCount + 16) 8+getBytes . fromIntegral $ bodyPadding++<<DBus/Wire.hs>>=+findBodySignature :: [M.HeaderField] -> T.Signature+findBodySignature fields = fromMaybe empty signature where+	empty = T.mkSignature' ""+	signature = listToMaybe [x | M.Signature x <- fields]++<<read body>>=+let bodySig = findBodySignature fields++@ Then pull the body bytes, and unmarshal it.++<<read body>>=+bodyBytes <- getBytes bodyLength+body <- unmarshal' bodySig bodyBytes++@ Even if the received message was structurally valid, building the+{\tt ReceivedMessage} can still fail due to missing header fields.++<<build message>>=+y <- case buildReceivedMessage typeCode fields of+	Right x -> return x+	Left x -> E.throwError $ MissingHeaderField x++<<build message>>=+return $ y serial flags body++@ This really belongs in the Message section...++<<DBus/Wire.hs>>=+buildReceivedMessage :: Word8 -> [M.HeaderField] -> Either Text +                        (M.Serial -> (Set.Set M.Flag) -> [T.Variant]+                         -> M.ReceivedMessage)++@ Method calls++<<DBus/Wire.hs>>=+buildReceivedMessage 1 fields = do+	path <- require "path" [x | M.Path x <- fields]+	member <- require "member name" [x | M.Member x <- fields]+	return $ \serial flags body -> let+		iface = listToMaybe [x | M.Interface x <- fields]+		dest = listToMaybe [x | M.Destination x <- fields]+		sender = listToMaybe [x | M.Sender x <- fields]+		msg = M.MethodCall path member iface dest flags body+		in M.ReceivedMethodCall serial sender msg++@ Method returns++<<DBus/Wire.hs>>=+buildReceivedMessage 2 fields = do+	replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]+	return $ \serial _ body -> let+		dest = listToMaybe [x | M.Destination x <- fields]+		sender = listToMaybe [x | M.Sender x <- fields]+		msg = M.MethodReturn replySerial dest body+		in M.ReceivedMethodReturn serial sender msg++@ Errors++<<DBus/Wire.hs>>=+buildReceivedMessage 3 fields = do+	name <- require "error name" [x | M.ErrorName x <- fields]+	replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]+	return $ \serial _ body -> let+		dest = listToMaybe [x | M.Destination x <- fields]+		sender = listToMaybe [x | M.Sender x <- fields]+		msg = M.Error name replySerial dest body+		in M.ReceivedError serial sender msg++@ Signals++<<DBus/Wire.hs>>=+buildReceivedMessage 4 fields = do+	path <- require "path" [x | M.Path x <- fields]+	member <- require "member name" [x | M.Member x <- fields]+	iface <- require "interface" [x | M.Interface x <- fields]+	return $ \serial _ body -> let+		dest = listToMaybe [x | M.Destination x <- fields]+		sender = listToMaybe [x | M.Sender x <- fields]+		msg = M.Signal path member iface dest body+		in M.ReceivedSignal serial sender msg++@ Unknown++<<DBus/Wire.hs>>=+buildReceivedMessage typeCode fields = return $ \serial flags body -> let+	sender = listToMaybe [x | M.Sender x <- fields]+	msg = M.Unknown typeCode flags body+	in M.ReceivedUnknown serial sender msg++<<DBus/Wire.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.hs>>=+instance E.Error Text where+	strMsg = TL.pack++@+\section{Addresses}++<<DBus/Address.hs>>=+<<copyright>>+<<text extensions>>+module DBus.Address+	( Address+	, addressMethod+	, addressParameters+	, mkAddresses+	, strAddress+	) where+<<text imports>>++import Data.Char (ord, chr)+import qualified Data.Map as M+import Text.Printf (printf)+import qualified Text.Parsec as P+import Text.Parsec ((<|>))+import DBus.Util (hexToInt, eitherToMaybe)++@ \subsection{Address syntax}++A bus address is in the format {\tt $method$:$key$=$value$,$key$=$value$...}+where the method may be empty and parameters are optional. An address's+parameter list, if present, may end with a comma. Addresses in environment+variables are separated by semicolons, and the full address list may end+in a semicolon. Multiple parameters may have the same key; in this case,+only the first parameter for each key will be stored.++The bytes allowed in each component of the address are given by the following+chart, where each character is understood to be its ASCII value:++\begin{table}[h]+\begin{center}+\begin{tabular}{ll}+\toprule+Component   & Allowed Characters \\+\midrule+Method      & Any except {\tt `;'} and {\tt `:'} \\+Param key   & Any except {\tt `;'}, {\tt `,'}, and {\tt `='} \\+Param value & {\tt `0'} to {\tt `9'} \\+            & {\tt `a'} to {\tt `z'} \\+            & {\tt `A'} to {\tt `Z'} \\+            & Any of: {\tt - \textunderscore{} / \textbackslash{} * . \%} \\+\bottomrule+\end{tabular}+\end{center}+\end{table}++In parameter values, any byte may be encoded by prepending the \% character+to its value in hexadecimal. \% is not allowed to appear unless it is+followed by two hexadecimal digits. Every other allowed byte is termed+an ``optionally encoded'' byte, and may appear unescaped in parameter+values.++<<DBus/Address.hs>>=+optionallyEncoded :: [Char]+optionallyEncoded = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."++@+The address simply stores its method and parameter map, with a custom+{\tt Show} instance to provide easier debugging.++<<DBus/Address.hs>>=+data Address = Address+	{ addressMethod     :: Text+	, addressParameters :: M.Map Text Text+	} deriving (Eq)++instance Show Address where+	showsPrec d x = showParen (d> 10) $+		showString "Address " . shows (strAddress x)++@+Parsing is straightforward; the input string is divided into addresses by+semicolons, then further by colons and commas. Parsing will fail if any+of the addresses in the input failed to parse.++<<DBus/Address.hs>>=+mkAddresses :: Text -> Maybe [Address]+mkAddresses s = eitherToMaybe . P.parse parser "" . TL.unpack $ s where+	address = do+		method <- P.many (P.noneOf ":;")+		P.char ':'+		params <- P.sepEndBy param (P.char ',')+		return $ Address (TL.pack method) (M.fromList params)+	+	param = do+		key <- P.many1 (P.noneOf "=;,")+		P.char '='+		value <- P.many1 (encodedValue <|> unencodedValue)+		return (TL.pack key, TL.pack value)+	+	parser = do+		as <- P.sepEndBy1 address (P.char ';')+		P.eof+		return as+	+	unencodedValue = P.oneOf optionallyEncoded+	encodedValue = do+		P.char '%'+		hex <- P.count 2 P.hexDigit+		return . chr . hexToInt $ hex++@+Converting an {\tt Address} back to a {\tt String} is just the reverse+operation. Note that because the original parameter order is not preserved,+the string produced might differ from the original input.++<<DBus/Address.hs>>=+strAddress :: Address -> Text+strAddress (Address t ps) = TL.concat [t, ":", ps'] where+	ps' = TL.intercalate "," $ do+		(k, v) <- M.toList ps+		return $ TL.concat [k, "=", TL.concatMap encode v]+	encode c | elem c optionallyEncoded = TL.singleton c+	         | otherwise       = TL.pack $ printf "%%%02X" (ord c)++@+\section{Connections}++<<DBus/Connection.hs>>=+<<copyright>>+<<text extensions>>+{-# LANGUAGE DeriveDataTypeable #-}+module DBus.Connection+	( <<connection exports>>+	) where+<<text imports>>+<<connection imports>>++@ A {\tt Connection} is an opaque handle to an open D-Bus channel, with+an internal state for maintaining the current message serial.++The second {\tt MVar} doesn't really store a value, it's just used to+prevent two separate threads from reading from the transport at once.++<<connection imports>>=+import qualified Control.Concurrent as C+import qualified DBus.Address as A+import qualified DBus.Message.Internal as M++<<DBus/Connection.hs>>=+data Connection = Connection A.Address Transport (C.MVar M.Serial) (C.MVar ())++<<connection exports>>=+  Connection++@ 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++@ \subsection{Transports}++A transport is anything which can send and receive bytestrings, typically+over a socket.++<<connection imports>>=+import qualified Data.ByteString.Lazy as L+import Data.Word (Word32)++<<DBus/Connection.hs>>=+data Transport = Transport+	{ transportSend :: L.ByteString -> IO ()+	, transportRecv :: Word32 -> IO L.ByteString+	}++@ If a method has no known transport, attempting to connect using it will+just result in an exception.++<<DBus/Connection.hs>>=+connectTransport :: A.Address -> IO Transport+connectTransport a = transport' (A.addressMethod a) a where+	transport' "unix" = unix+	transport' "tcp"  = tcp+	transport' _      = E.throwIO . UnknownMethod++@ \subsubsection{UNIX}++The {\sc unix} transport accepts two parameters: {\tt path}, which is a+simple filesystem path, and {\tt abstract}, which is a path in the+Linux-specific abstract domain. One, and only one, of these parameters must+be specified.++<<connection imports>>=+import qualified Network as N+import qualified Data.Map as Map++<<DBus/Connection.hs>>=+unix :: A.Address -> IO Transport+unix a = port >>= N.connectTo "localhost" >>= handleTransport where+	params = A.addressParameters a+	path = Map.lookup "path" params+	abstract = Map.lookup "abstract" params+	+	tooMany = "Only one of `path' or `abstract' may be specified for the\+	          \ `unix' transport."+	tooFew = "One of `path' or `abstract' must be specified for the\+	         \ `unix' transport."+	+	port = fmap N.UnixSocket path'+	path' = case (path, abstract) of+		(Just _, Just _) -> E.throwIO $ BadParameters a tooMany+		(Nothing, Nothing) -> E.throwIO $ BadParameters a tooFew+		(Just x, Nothing) -> return $ TL.unpack x+		(Nothing, Just x) -> return $ '\x00' : TL.unpack x++@ \subsubsection{TCP}++The {\sc tcp} transport has three parameters:++\begin{itemize}+\item {\tt host} -- optional, defaults to {\tt "localhost"}+\item {\tt port} -- unsigned 16-bit integer+\item {\tt family} -- optional, defaults to {\sc unspec}, choices are+      {\tt "ipv4"} or {\tt "ipv6"}+\end{itemize}++The high-level {\tt Network} module doesn't provide enough control over+socket construction for this transport, so {\tt Network.Socket} must be+imported.++<<connection imports>>=+import qualified Network.Socket as NS++<<DBus/Connection.hs>>=+tcp :: A.Address -> IO Transport+tcp a = openHandle >>= handleTransport where+	params = A.addressParameters a+	openHandle = do+		port <- getPort+		family <- getFamily+		addresses <- getAddresses family+		socket <- openSocket port addresses+		NS.socketToHandle socket I.ReadWriteMode++@ Parameter parsing...++<<DBus/Connection.hs>>=+	hostname = maybe "localhost" TL.unpack $ Map.lookup "host" params++<<DBus/Connection.hs>>=+	unknownFamily x = TL.concat ["Unknown socket family for TCP transport: ", x]+	getFamily = case Map.lookup "family" params of+		Just "ipv4" -> return NS.AF_INET+		Just "ipv6" -> return NS.AF_INET6+		Nothing     -> return NS.AF_UNSPEC+		Just x      -> E.throwIO $ BadParameters a $ unknownFamily x++<<DBus/Connection.hs>>=+	missingPort = "TCP transport requires the ``port'' parameter."+	badPort x = TL.concat ["Invalid socket port for TCP transport: ", x]+	getPort = case Map.lookup "port" params of+		Nothing -> E.throwIO $ BadParameters a missingPort+		Just x -> case P.parse parseWord16 "" (TL.unpack x) of+			Right x' -> return $ NS.PortNum x'+			Left  _  -> E.throwIO $ BadParameters a $ badPort x++@ Parsing the port is a bit complicated; assuming every character is an ASCII+digit, the port is converted to an {\tt Integer} and confirmed valid.+{\tt PortNumber} is expected to be in big-endian byte order, so the parsed+value must be converted from host order using {\tt Data.Binary}.++<<connection imports>>=+import qualified Text.Parsec as P+import Control.Monad (unless)+import Data.Binary.Get (runGet, getWord16host)+import Data.Binary.Put (runPut, putWord16be)++<<DBus/Connection.hs>>=+	parseWord16 = do+		chars <- P.many1 P.digit+		P.eof+		let value = read chars :: Integer+		unless (value > 0 && value <= 65535) $+			P.parserFail "bad port" >> return ()+		let word = fromIntegral value+		return $ runGet getWord16host (runPut (putWord16be word))++<<DBus/Connection.hs>>=+	getAddresses family = do+		let hints = NS.defaultHints+			{ NS.addrFlags = [NS.AI_ADDRCONFIG]+			, NS.addrFamily = family+			, NS.addrSocketType = NS.Stream+			}+		NS.getAddrInfo (Just hints) (Just hostname) Nothing++@ The {\tt SockAddr} values returned from {\tt getAddrInfo} don't have any+port set, so it must be manually changed to whatever was in the {\tt port}+option.++<<DBus/Connection.hs>>=+	setPort port (NS.SockAddrInet  _ x)     = NS.SockAddrInet port x+	setPort port (NS.SockAddrInet6 _ x y z) = NS.SockAddrInet6 port x y z+	setPort _    addr                       = addr++@ {\tt getAddrInfo} returns multiple addresses; each one is tried in turn,+until a valid address is found. If none are found, or are usable, an+exception will be thrown.++<<DBus/Connection.hs>>=+	openSocket _ [] = E.throwIO $ NoWorkingAddress [a]+	openSocket port (addr:addrs) = E.catch (openSocket' port addr) $+		\(E.SomeException _) -> openSocket port addrs+	openSocket' port addr = do+		sock <- NS.socket (NS.addrFamily addr)+		                  (NS.addrSocketType addr)+		                  (NS.addrProtocol addr)+		NS.connect sock . setPort port . NS.addrAddress $ addr+		return sock++@ \subsubsection{Generic handle-based transport}++Both UNIX and TCP are backed by standard handles, and can therefore use+a shared handle-based transport backend.++<<connection imports>>=+import qualified System.IO as I++<<DBus/Connection.hs>>=+handleTransport :: I.Handle -> IO Transport+handleTransport h = do+	I.hSetBuffering h I.NoBuffering+	I.hSetBinaryMode h True+	return $ Transport (L.hPut h) (L.hGet h . fromIntegral)++@ \subsection{Errors}++If connecting to D-Bus fails, a {\tt ConnectionError} will be thrown.+The constructor describes which exception occurred.++<<connection imports>>=+import qualified Control.Exception as E+import Data.Typeable (Typeable)++<<DBus/Connection.hs>>=+data ConnectionError+	= InvalidAddress Text+	| BadParameters A.Address Text+	| UnknownMethod A.Address+	| NoWorkingAddress [A.Address]+	deriving (Show, Typeable)++instance E.Exception ConnectionError++<<connection exports>>=+, ConnectionError (..)+, W.MarshalError (..)+, W.UnmarshalError (..)++@ \subsection{Establishing a connection}++A connection can be opened to any valid address, though actually connecting+might fail due to external factors.++<<connection imports>>=+import Data.Text.Lazy.Encoding (decodeUtf8, encodeUtf8)++<<DBus/Connection.hs>>=+connect :: A.Address -> IO Connection+connect a = do+	t <- connectTransport a+	let putS = transportSend t . encodeUtf8 . TL.pack+	let getS = fmap (TL.unpack . decodeUtf8) . transportRecv t+	authenticate putS getS+	readLock <- C.newMVar ()+	serialMVar <- C.newMVar M.firstSerial+	return $ Connection a t serialMVar readLock++@ 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+connectFirst orig = connectFirst' orig where+	connectFirst'     [] = E.throwIO $ NoWorkingAddress orig+	connectFirst' (a:as) = E.catch (connect a) $+		\(E.SomeException _) -> connectFirst' as++<<connection exports>>=+, connect+, connectFirst++@ \subsection{Authentication}++<<DBus/Connection.hs>>=+authenticate :: (String -> IO ()) -> (Word32 -> IO String)+                -> IO ()+authenticate put get = do+	put "\x00"++@ {\sc external} authentication is performed using the process's real user+ID, converted to a string, and then hex-encoded.++<<connection imports>>=+import System.Posix.User (getRealUserID)+import Data.Char (ord)+import Text.Printf (printf)++<<DBus/Connection.hs>>=+	uid <- getRealUserID+	let authToken = concatMap (printf "%02X" . ord) (show uid)+	put $ "AUTH EXTERNAL " ++ authToken ++ "\r\n"++@ 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)++<<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."++<<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++@ \subsection{Sending and receiving messages}++Sending a message will increment the connection's internal serial state.+The second parameter is present to allow registration of a callback before+the message has actually been sent, which avoids race conditions in+multi-threaded clients.++<<connection imports>>=+import qualified DBus.Wire as W++<<DBus/Connection.hs>>=+send :: M.Message a => Connection -> (M.Serial -> IO b) -> a+     -> IO (Either W.MarshalError b)+send (Connection _ t mvar _) io msg = withSerial mvar $ \serial ->+	case W.marshalMessage W.LittleEndian serial msg of+		Right bytes -> do+			x <- io serial+			transportSend t bytes+			return $ Right x+		Left  err   -> return $ Left err++<<connection exports>>=+, send++<<DBus/Connection.hs>>=+withSerial :: C.MVar M.Serial -> (M.Serial -> IO a) -> IO a+withSerial m io = E.block $ do+	s <- C.takeMVar m+	let s' = M.nextSerial s+	x <- E.unblock (io s) `E.onException` C.putMVar m s'+	C.putMVar m s'+	return x++@ Messages are received wrapped in a {\tt ReceivedMessage} value. If an+error is encountered while unmarshaling, an exception will be thrown.++<<DBus/Connection.hs>>=+receive :: Connection -> IO (Either W.UnmarshalError M.ReceivedMessage)+receive (Connection _ t _ lock) = C.withMVar lock $ \_ ->+	W.unmarshalMessage $ transportRecv t++<<connection exports>>=+, receive++@+\section{The central bus}++<<DBus/Bus.hs>>=+<<copyright>>+<<text extensions>>+module DBus.Bus+	( getBus+	, getFirstBus+	, getSystemBus+	, getSessionBus+	, getStarterBus+	) where+<<text imports>>++import qualified Control.Exception as E+import Control.Monad (when)+import Data.Maybe (fromJust, isNothing)+import qualified Data.Set as Set+import System.Environment (getEnv)++import qualified DBus.Address as A+import qualified DBus.Connection as C+import DBus.Constants (dbusName, dbusPath, dbusInterface)+import qualified DBus.Message as M+import qualified DBus.Types as T+import DBus.Util (fromRight)++@ Connecting to a message bus is a bit more involved than just connecting+over an app-to-app connection: the bus must be notified of the new client,+using a "hello message", before it will begin forwarding messages.++<<DBus/Bus.hs>>=+busForConnection :: C.Connection -> IO (C.Connection, T.BusName)+busForConnection c = sendHello c >>= return . (,) c++<<DBus/Bus.hs>>=+getBus :: 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)+getFirstBus = (busForConnection =<<) . C.connectFirst++@ \subsection{Default connections}++Two default buses are defined, the ``system'' and ``session'' buses. The system+bus is global for the OS, while the session bus runs only for the duration+of the user's session.++<<DBus/Bus.hs>>=+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++<<DBus/Bus.hs>>=+getSessionBus :: IO (C.Connection, T.BusName)+getSessionBus = getBus' $ getEnv "DBUS_SESSION_BUS_ADDRESS"++<<DBus/Bus.hs>>=+getStarterBus :: IO (C.Connection, T.BusName)+getStarterBus = getBus' $ getEnv "DBUS_STARTER_ADDRESS"++<<DBus/Bus.hs>>=+getBus' :: IO String -> IO (C.Connection, T.BusName)+getBus' io = do+	addr <- fmap TL.pack io+	case A.mkAddresses addr of+		Just [x] -> getBus x+		Just  x  -> getFirstBus x+		_        -> E.throwIO $ C.InvalidAddress addr++@ \subsection{Sending the ``hello'' message}++<<DBus/Bus.hs>>=+hello :: M.MethodCall+hello = M.MethodCall dbusPath+	(T.mkMemberName' "Hello")+	(Just dbusInterface)+	(Just dbusName)+	Set.empty+	[]++<<DBus/Bus.hs>>=+sendHello :: C.Connection -> IO T.BusName+sendHello c = do+	serial <- fromRight `fmap` C.send c return hello+	reply <- waitForReply c serial+	let name = case M.methodReturnBody reply of+		(x:_) -> T.fromVariant x+		_     -> Nothing+	+	when (isNothing name) $+		E.throwIO $ E.AssertionFailed "Invalid response to Hello()"+	+	return . fromJust $ name++<<DBus/Bus.hs>>=+waitForReply :: C.Connection -> M.Serial -> IO M.MethodReturn+waitForReply c serial = do+	received <- C.receive c+	msg <- case received of+		Right x -> return x+		Left _  -> E.throwIO $ E.AssertionFailed "Invalid response to Hello()"+	case msg of+		(M.ReceivedMethodReturn _ _ reply) ->+			if M.methodReturnSerial reply == serial+				then return reply+				else waitForReply c serial+		_ -> waitForReply c serial++@+\section{Introspection}++@ D-Bus objects may be ``introspected'' to determine which methods, signals,+etc they support. Intospection data is sent over the bus in {\sc xml}, in+a mostly standardised but undocumented format.++An XML introspection document looks like this:++\begin{verbatim}+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"+         "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">+<node name="/org/example/example">+	<interface name="org.example.ExampleInterface">+		<method name="Echo">+			<arg name="text" type="s" direction="in"/>+			<arg type="s" direction="out"/>+		</method>+		<signal name="Echoed">+			<arg type="s"/>+		</signal>+		<property name="EchoCount" type="u" access="read"/>+	</interface>+	<node name="child_a"/>+	<node name="child/b"/>+</node>+\end{verbatim}++<<DBus/Introspection.hs>>=+<<copyright>>+<<text extensions>>+module DBus.Introspection+	( Object (..)+	, Interface (..)+	, Method (..)+	, Signal (..)+	, Parameter (..)+	, Property (..)+	, PropertyAccess (..)+	, toXML+	, fromXML+	) where+<<text imports>>+<<introspection imports>>+import qualified DBus.Types as T++@ HaXml is used to do the heavy lifting of XML parsing because HXT cannot+be combined with Parsec 3.++<<introspection imports>>=+import qualified Text.XML.HaXml as H++@ \subsection{Data types}++<<DBus/Introspection.hs>>=+data Object = Object T.ObjectPath [Interface] [Object]+	deriving (Show, Eq)++data Interface = Interface T.InterfaceName [Method] [Signal] [Property]+	deriving (Show, Eq)++data Method = Method T.MemberName [Parameter] [Parameter]+	deriving (Show, Eq)++data Signal = Signal T.MemberName [Parameter]+	deriving (Show, Eq)++data Parameter = Parameter Text T.Signature+	deriving (Show, Eq)++data Property = Property Text T.Signature [PropertyAccess]+	deriving (Show, Eq)++data PropertyAccess = Read | Write+	deriving (Show, Eq)++@ \subsection{Parsing XML}++The root {\tt node} is special, in that it's the only {\tt node} which is+not required to have a {\tt name} attribute. If the root has no {\tt name},+its path will default to the path of the introspected object.++If parsing fails, {\tt fromXML} will return {\tt Nothing}. Aside from the+elements directly accessed by the parser, no effort is made to check the+document's validity because there is no DTD as of yet.++<<introspection imports>>=+import Text.XML.HaXml.Parse (xmlParse')+import DBus.Util (eitherToMaybe)++<<DBus/Introspection.hs>>=+fromXML :: T.ObjectPath -> Text -> Maybe Object+fromXML path text = do+	doc <- eitherToMaybe . xmlParse' "" . TL.unpack $ text+	let (H.Document _ _ root _) = doc+	parseRoot path root++@ Even though the root object's {\tt name} is optional, if present, it must+still be a valid object path.++<<DBus/Introspection.hs>>=+parseRoot :: T.ObjectPath -> H.Element a -> Maybe Object+parseRoot defaultPath e = do+	path <- case getAttr "name" e of+		Nothing -> Just defaultPath+		Just x  -> T.mkObjectPath x+	parseObject' path e++@ Child {\tt nodes} have ``relative'' paths -- that is, their {\tt name}+attribute is not a valid object path, but should be valid when appended to+the root object's path.++<<DBus/Introspection.hs>>=+parseChild :: T.ObjectPath -> H.Element a -> Maybe Object+parseChild parentPath e = do+	let parentPath' = case T.strObjectPath parentPath of+		"/" -> "/"+		x   -> TL.append x "/"+	pathSegment <- getAttr "name" e+	path <- T.mkObjectPath $ TL.append parentPath' pathSegment+	parseObject' path e++@ Other than the name, both root and non-root {\tt nodes} have identical+contents.  They may contain interface definitions, and child {\tt node}s.++<<DBus/Introspection.hs>>=+parseObject' :: T.ObjectPath -> H.Element a -> Maybe Object+parseObject' path e@(H.Elem "node" _ _)  = do+	interfaces <- children parseInterface (H.tag "interface") e+	children' <- children (parseChild path) (H.tag "node") e+	return $ Object path interfaces children'+parseObject' _ _ = Nothing++@ Interfaces may contain methods, signals, and properties.++<<DBus/Introspection.hs>>=+parseInterface :: H.Element a -> Maybe Interface+parseInterface e = do+	name <- T.mkInterfaceName =<< getAttr "name" e+	methods <- children parseMethod (H.tag "method") e+	signals <- children parseSignal (H.tag "signal") e+	properties <- children parseProperty (H.tag "property") e+	return $ Interface name methods signals properties++@ Methods contain a list of parameters, which default to ``in'' parameters+if no direction is specified.++<<DBus/Introspection.hs>>=+parseMethod :: H.Element a -> Maybe Method+parseMethod e = do+	name <- T.mkMemberName =<< getAttr "name" e+	paramsIn <- children parseParameter (isParam ["in", ""]) e+	paramsOut <- children parseParameter (isParam ["out"]) e+	return $ Method name paramsIn paramsOut++@ Signals are similar to methods, except they have no ``in'' parameters.++<<DBus/Introspection.hs>>=+parseSignal :: H.Element a -> Maybe Signal+parseSignal e = do+	name <- T.mkMemberName =<< getAttr "name" e+	params <- children parseParameter (isParam ["out", ""]) e+	return $ Signal name params++@ A parameter has a free-form name, and a single valid type.++<<DBus/Introspection.hs>>=+parseParameter :: H.Element a -> Maybe Parameter+parseParameter e = do+	let name = getAttr' "name" e+	sig <- parseType e+	return $ Parameter name sig++<<DBus/Introspection.hs>>=+parseType :: H.Element a -> Maybe T.Signature+parseType e = do+	sig <- T.mkSignature =<< getAttr "type" e+	case T.signatureTypes sig of+		[_] -> Just sig+		_   -> Nothing++@ Properties are used by the {\tt org.freedesktop.DBus.Properties} interface.+Each property may be read, written, or both, and has an associated type.++<<DBus/Introspection.hs>>=+parseProperty :: H.Element a -> Maybe Property+parseProperty e = do+	let name = getAttr' "name" e+	sig <- parseType e+	access <- case getAttr' "access" e of+		""          -> Just []+		"read"      -> Just [Read]+		"write"     -> Just [Write]+		"readwrite" -> Just [Read, Write]+		_           -> Nothing+	return $ Property name sig access++@ HaXml doesn't seem to have any way to retrieve the ``real'' value of an+attribute, so {\tt attrValue} implements this.++<<introspection imports>>=+import Data.Char (chr)++<<DBus/Introspection.hs>>=+attrValue :: H.AttValue -> Maybe Text+attrValue attr = fmap (TL.pack . concat) $ mapM unescape parts where+	(H.AttValue parts) = attr+	+	unescape (Left x) = Just x+	unescape (Right (H.RefEntity x)) = lookup x namedRefs+	unescape (Right (H.RefChar x)) = Just [chr x]+	+	namedRefs =+		[ ("lt", "<")+		, ("gt", ">")+		, ("amp", "&")+		, ("apos", "'")+		, ("quot", "\"")+		]++@ Some helper functions for dealing with HaXml filters++<<introspection imports>>=+import Data.Maybe (fromMaybe)++<<DBus/Introspection.hs>>=+getAttr :: String -> H.Element a -> Maybe Text+getAttr name (H.Elem _ attrs _) = lookup name attrs >>= attrValue++getAttr' :: String -> H.Element a -> Text+getAttr' = (fromMaybe "" .) . getAttr++<<DBus/Introspection.hs>>=+isParam :: [Text] -> H.CFilter a+isParam dirs content = do+	arg@(H.CElem e _) <- H.tag "arg" content+	let direction = getAttr' "direction" e+	[arg | direction `elem` dirs]++<<DBus/Introspection.hs>>=+children :: Monad m => (H.Element a -> m b) -> H.CFilter a -> H.Element a -> m [b]+children f filt (H.Elem _ _ contents) =+	mapM f [x | (H.CElem x _) <- concatMap filt contents]++@ \subsection{Generating XML}++<<DBus/Introspection.hs>>=+dtdPublicID, dtdSystemID :: String+dtdPublicID = "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"+dtdSystemID = "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"++@ HaXml punts to the {\tt pretty} package for serialising XML.++<<introspection imports>>=+import Text.XML.HaXml.Pretty (document)+import Text.PrettyPrint.HughesPJ (render)++@ Generating XML can fail; if a child object's path is not a sub-path of the+parent, {\tt toXML} will return {\tt Nothing}.++<<DBus/Introspection.hs>>=+toXML :: Object -> Maybe Text+toXML obj = fmap (TL.pack . render . document) doc where+	prolog = H.Prolog Nothing [] (Just doctype) []+	doctype = H.DTD "node" (Just (H.PUBLIC+		(H.PubidLiteral dtdPublicID)+		(H.SystemLiteral dtdSystemID))) []+	doc = do+		root <- xmlRoot obj+		return $ H.Document prolog H.emptyST root []++@ When writing objects to {\tt node}s, the root object must have an absolute+path, and children must have paths relative to their parent.++<<DBus/Introspection.hs>>=+xmlRoot :: Object -> Maybe (H.Element a)+xmlRoot obj@(Object path _ _) = do+	(H.CElem root _) <- xmlObject' (T.strObjectPath path) obj+	return root++<<DBus/Introspection.hs>>=+xmlObject :: T.ObjectPath -> Object -> Maybe (H.Content a)+xmlObject parentPath obj@(Object path _ _) = do+	let path' = T.strObjectPath path+	    parent' = T.strObjectPath parentPath+	relpath <- if TL.isPrefixOf parent' path'+		then Just $ if parent' == "/"+			then TL.drop 1 path'+			else TL.drop (TL.length parent' + 1) path'+		else Nothing+	xmlObject' relpath obj++<<DBus/Introspection.hs>>=+xmlObject' :: Text -> Object -> Maybe (H.Content a)+xmlObject' path (Object fullPath interfaces children') = do+	children'' <- mapM (xmlObject fullPath) children'+	return $ mkElement "node"+		[mkAttr "name" $ TL.unpack path]+		$ concat+			[ map xmlInterface interfaces+			, children''+			]++<<DBus/Introspection.hs>>=+xmlInterface :: Interface -> H.Content a+xmlInterface (Interface name methods signals properties) =+	mkElement "interface"+		[mkAttr "name" . TL.unpack . T.strInterfaceName $ name]+		$ concat+			[ map xmlMethod methods+			, map xmlSignal signals+			, map xmlProperty properties+			]++<<DBus/Introspection.hs>>=+xmlMethod :: Method -> H.Content a+xmlMethod (Method name inParams outParams) = mkElement "method"+	[mkAttr "name" . TL.unpack . T.strMemberName $ name]+	$ concat+		[ map (xmlParameter "in") inParams+		, map (xmlParameter "out") outParams+		]++<<DBus/Introspection.hs>>=+xmlSignal :: Signal -> H.Content a+xmlSignal (Signal name params) = mkElement "signal"+	[mkAttr "name" . TL.unpack . T.strMemberName $ name]+	$ map (xmlParameter "out") params++<<DBus/Introspection.hs>>=+xmlParameter :: String -> Parameter -> H.Content a+xmlParameter direction (Parameter name sig) = mkElement "arg"+	[ mkAttr "name" . TL.unpack $ name+	, mkAttr "type" . TL.unpack . T.strSignature $ sig+	, mkAttr "direction" direction+	] []++<<DBus/Introspection.hs>>=+xmlProperty :: Property -> H.Content a+xmlProperty (Property name sig access) = mkElement "property"+	[ mkAttr "name" . TL.unpack $ name+	, mkAttr "type" . TL.unpack . T.strSignature $ sig+	, mkAttr "access" $ xmlAccess access+	] []++<<DBus/Introspection.hs>>=+xmlAccess :: [PropertyAccess] -> String+xmlAccess access = readS ++ writeS where+	readS = if elem Read access then "read" else ""+	writeS = if elem Write access then "write" else ""++<<DBus/Introspection.hs>>=+mkElement :: String -> [H.Attribute] -> [H.Content a] -> H.Content a+mkElement name attrs contents = H.CElem (H.Elem name attrs contents) undefined++<<DBus/Introspection.hs>>=+mkAttr :: String -> String -> H.Attribute+mkAttr name value = (name, H.AttValue [Left escaped]) where+	raw = H.CString True value ()+	escaped = H.verbatim $ H.xmlEscapeContent H.stdXmlEscaper [raw]++@+\section{Match rules}++Match rules are used 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.++<<DBus/MatchRule.hs>>=+<<copyright>>+<<text extensions>>+module DBus.MatchRule (+	  MatchRule (..)+	, MessageType (..)+	, ParameterValue (..)+	, formatRule+	, addMatch+	, matchAll+	, matches+	) where+<<text imports>>+import Data.Word (Word8)+import Data.Maybe (catMaybes, fromMaybe)+import qualified Data.Set as Set+import qualified DBus.Types as T+import qualified DBus.Message as M+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>>=+data MatchRule = MatchRule+	{ matchType        :: Maybe MessageType+	, matchSender      :: Maybe T.BusName+	, matchInterface   :: Maybe T.InterfaceName+	, matchMember      :: Maybe T.MemberName+	, matchPath        :: Maybe T.ObjectPath+	, 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.++<<DBus/MatchRule.hs>>=+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>>=+data MessageType+	= MethodCall+	| MethodReturn+	| Signal+	| Error+	deriving (Show, Eq)++@ There's currently only one operation to perform on match rules, and that's+to format them.++<<DBus/MatchRule.hs>>=+formatRule :: MatchRule -> Text+formatRule rule = TL.intercalate "," filters where+	filters = structureFilters ++ parameterFilters+	parameterFilters = map formatParameter $ matchParameters rule+	structureFilters = catMaybes $ map unpack+		[ ("type", fmap formatType . matchType)+		, ("sender", fmap T.strBusName . matchSender)+		, ("interface", fmap T.strInterfaceName . matchInterface)+		, ("member", fmap T.strMemberName . matchMember)+		, ("path", fmap T.strObjectPath . matchPath)+		, ("destination", fmap T.strBusName . matchDestination)+		]+	unpack (key, mkValue) = formatFilter' key `fmap` mkValue rule++<<DBus/MatchRule.hs>>=+formatParameter :: ParameterValue -> Text+formatParameter (StringValue index x) = formatFilter' key x where+	key = "arg" `TL.append` TL.pack (show index)+formatParameter (PathValue index x) = formatFilter' key value where+	key = "arg" `TL.append` TL.pack (show index) `TL.append` "path"+	value = T.strObjectPath x++@ FIXME: what are the escaping rules for match rules? Other bindings don't+seem to perform any escaping at all.++<<DBus/MatchRule.hs>>=+formatFilter' :: Text -> Text -> Text+formatFilter' key value = TL.concat [key, "='", value, "'"]++<<DBus/MatchRule.hs>>=+formatType :: MessageType -> Text+formatType MethodCall   = "method_call"+formatType MethodReturn = "method_return"+formatType Signal       = "signal"+formatType Error        = "error"++@ And since the only real reason for formatting a match rule is to send it,+it's useful to have a message-building function pre-defined.++<<DBus/MatchRule.hs>>=+addMatch :: MatchRule -> M.MethodCall+addMatch rule = M.MethodCall+	C.dbusPath+	(T.mkMemberName' "AddMatch")+	(Just C.dbusInterface)+	(Just C.dbusName)+	Set.empty+	[T.toVariant $ formatRule rule]++@ Most match rules will have only one or two fields filled in, so defining+an empty rule allows clients to set only the fields they care about.++<<DBus/MatchRule.hs>>=+matchAll :: MatchRule+matchAll = MatchRule+	{ matchType        = Nothing+	, matchSender      = Nothing+	, matchInterface   = Nothing+	, matchMember      = Nothing+	, matchPath        = Nothing+	, matchDestination = Nothing+	, matchParameters  = []+	}++@ It's useful to match against a rule client-side, eg when listening for+signals.++<<DBus/MatchRule.hs>>=+matches :: MatchRule -> M.ReceivedMessage -> Bool+matches rule msg = and . catMaybes . map ($ rule) $+	[ fmap      (typeMatches msg) . matchType+	, fmap    (senderMatches msg) . matchSender+	, fmap     (ifaceMatches msg) . matchInterface+	, fmap    (memberMatches msg) . matchMember+	, fmap      (pathMatches msg) . matchPath+	, fmap      (destMatches msg) . matchDestination+	, Just . parametersMatch msg  . matchParameters+	]++<<DBus/MatchRule.hs>>=+typeMatches :: M.ReceivedMessage -> MessageType -> Bool+typeMatches (M.ReceivedMethodCall   _ _ _) MethodCall   = True+typeMatches (M.ReceivedMethodReturn _ _ _) MethodReturn = True+typeMatches (M.ReceivedSignal       _ _ _) Signal       = True+typeMatches (M.ReceivedError        _ _ _) Error        = True+typeMatches _ _ = False+++<<DBus/MatchRule.hs>>=+senderMatches :: M.ReceivedMessage -> T.BusName -> Bool+senderMatches msg name = M.receivedSender msg == Just name++<<DBus/MatchRule.hs>>=+ifaceMatches :: M.ReceivedMessage -> T.InterfaceName -> Bool+ifaceMatches (M.ReceivedMethodCall _ _ msg) name =+	Just name == M.methodCallInterface msg+ifaceMatches (M.ReceivedSignal _ _ msg) name =+	name == M.signalInterface msg+ifaceMatches _ _ = False++<<DBus/MatchRule.hs>>=+memberMatches :: M.ReceivedMessage -> T.MemberName -> Bool+memberMatches (M.ReceivedMethodCall _ _ msg) name =+	name == M.methodCallMember msg+memberMatches (M.ReceivedSignal _ _ msg) name =+	name == M.signalMember msg+memberMatches _ _ = False++<<DBus/MatchRule.hs>>=+pathMatches :: M.ReceivedMessage -> T.ObjectPath -> Bool+pathMatches (M.ReceivedMethodCall _ _ msg) path =+	path == M.methodCallPath msg+pathMatches (M.ReceivedSignal _ _ msg) path =+	path == M.signalPath msg+pathMatches _ _ = False++<<DBus/MatchRule.hs>>=+destMatches :: M.ReceivedMessage -> T.BusName -> Bool+destMatches (M.ReceivedMethodCall _ _ msg) name =+	Just name == M.methodCallDestination msg+destMatches (M.ReceivedMethodReturn _ _ msg) name =+	Just name == M.methodReturnDestination msg+destMatches (M.ReceivedError _ _ msg) name =+	Just name == M.errorDestination msg+destMatches (M.ReceivedSignal _ _ msg) name =+	Just name == M.signalDestination msg+destMatches _ _ = False++<<DBus/MatchRule.hs>>=+parametersMatch :: M.ReceivedMessage -> [ParameterValue] -> Bool+parametersMatch _ [] = True+parametersMatch msg values = all validParam values where+	body = M.receivedBody msg+	validParam (StringValue idx x) = validParam' idx x+	validParam (PathValue   idx x) = validParam' idx x+	validParam' idx x = fromMaybe False $ do+		var <- maybeIndex body $ fromIntegral idx+		fmap (== x) $ T.fromVariant var++@+\section{Name reservation}++The central bus allows clients to register a well-known bus name, which+enables other clients to connect with or start a particular application.++<<DBus/NameReservation.hs>>=+<<copyright>>+{-# LANGUAGE OverloadedStrings #-}+module DBus.NameReservation+	( RequestNameFlag (..)+	, RequestNameReply (..)+	, ReleaseNameReply (..)+	, requestName+	, releaseName+	, mkRequestNameReply+	, mkReleaseNameReply+	) where+import Data.Word (Word32)+import Data.Bits ((.|.))+import qualified Data.Set as Set+import qualified DBus.Types as T+import qualified DBus.Message as M+import qualified DBus.Constants as C+import DBus.Util (maybeIndex)++<<DBus/NameReservation.hs>>=+data RequestNameFlag+	= AllowReplacement+	| ReplaceExisting+	| DoNotQueue+	deriving (Show)++<<DBus/NameReservation.hs>>=+encodeFlags :: [RequestNameFlag] -> Word32+encodeFlags = foldr (.|.) 0 . map flagValue where+	flagValue AllowReplacement = 0x1+	flagValue ReplaceExisting  = 0x2+	flagValue DoNotQueue       = 0x4++@ There are only two methods of interest here, {\tt RequestName} and+{\tt ReleaseName}.++<<DBus/NameReservation.hs>>=+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.methodCallBody =+		[ T.toVariant name+		, T.toVariant . encodeFlags $ flags]+	}++<<DBus/NameReservation.hs>>=+data RequestNameReply+	= PrimaryOwner+	| InQueue+	| Exists+	| AlreadyOwner+	deriving (Show)++<<DBus/NameReservation.hs>>=+mkRequestNameReply :: M.MethodReturn -> Maybe RequestNameReply+mkRequestNameReply msg =+	maybeIndex (M.messageBody msg) 0 >>=+	T.fromVariant >>=+	decodeRequestReply++<<DBus/NameReservation.hs>>=+decodeRequestReply :: Word32 -> Maybe RequestNameReply+decodeRequestReply 1 = Just PrimaryOwner+decodeRequestReply 2 = Just InQueue+decodeRequestReply 3 = Just Exists+decodeRequestReply 4 = Just AlreadyOwner+decodeRequestReply _ = Nothing++<<DBus/NameReservation.hs>>=+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.methodCallBody = [T.toVariant name]+	}++<<DBus/NameReservation.hs>>=+data ReleaseNameReply+	= Released+	| NonExistent+	| NotOwner+	deriving (Show)++<<DBus/NameReservation.hs>>=+mkReleaseNameReply :: M.MethodReturn -> Maybe ReleaseNameReply+mkReleaseNameReply msg =+	maybeIndex (M.messageBody msg) 0 >>=+	T.fromVariant >>=+	decodeReleaseReply++<<DBus/NameReservation.hs>>=+decodeReleaseReply :: Word32 -> Maybe ReleaseNameReply+decodeReleaseReply 1 = Just Released+decodeReleaseReply 2 = Just NonExistent+decodeReleaseReply 3 = Just NotOwner+decodeReleaseReply _ = Nothing++@+\section{Constants}++<<DBus/Constants.hs>>=+<<copyright>>+{-# LANGUAGE OverloadedStrings #-}+module DBus.Constants where+import qualified DBus.Types as T+import Data.Word (Word8, Word32)++<<DBus/Constants.hs>>=+protocolVersion :: Word8+protocolVersion = 1++messageMaximumLength :: Word32+messageMaximumLength = 134217728++arrayMaximumLength :: Word32+arrayMaximumLength = 67108864++@ \subsection{The message bus}++<<DBus/Constants.hs>>=+dbusName :: T.BusName+dbusName = T.mkBusName' "org.freedesktop.DBus"++dbusPath :: T.ObjectPath+dbusPath = T.mkObjectPath' "/org/freedesktop/DBus"++dbusInterface :: T.InterfaceName+dbusInterface = T.mkInterfaceName' "org.freedesktop.DBus"++@ \subsection{Pre-defined interfaces}++<<DBus/Constants.hs>>=+interfaceIntrospectable :: T.InterfaceName+interfaceIntrospectable = T.mkInterfaceName' "org.freedesktop.DBus.Introspectable"++interfaceProperties :: T.InterfaceName+interfaceProperties = T.mkInterfaceName' "org.freedesktop.DBus.Properties"++interfacePeer :: T.InterfaceName+interfacePeer = T.mkInterfaceName' "org.freedesktop.DBus.Peer"++@ \subsection{Pre-defined error names}++<<DBus/Constants.hs>>=+errorFailed :: T.ErrorName+errorFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Failed"++errorNoMemory :: T.ErrorName+errorNoMemory = T.mkErrorName' "org.freedesktop.DBus.Error.NoMemory"++errorServiceUnknown :: T.ErrorName+errorServiceUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.ServiceUnknown"++errorNameHasNoOwner :: T.ErrorName+errorNameHasNoOwner = T.mkErrorName' "org.freedesktop.DBus.Error.NameHasNoOwner"++errorNoReply :: T.ErrorName+errorNoReply = T.mkErrorName' "org.freedesktop.DBus.Error.NoReply"++errorIOError :: T.ErrorName+errorIOError = T.mkErrorName' "org.freedesktop.DBus.Error.IOError"++errorBadAddress :: T.ErrorName+errorBadAddress = T.mkErrorName' "org.freedesktop.DBus.Error.BadAddress"++errorNotSupported :: T.ErrorName+errorNotSupported = T.mkErrorName' "org.freedesktop.DBus.Error.NotSupported"++errorLimitsExceeded :: T.ErrorName+errorLimitsExceeded = T.mkErrorName' "org.freedesktop.DBus.Error.LimitsExceeded"++errorAccessDenied :: T.ErrorName+errorAccessDenied = T.mkErrorName' "org.freedesktop.DBus.Error.AccessDenied"++errorAuthFailed :: T.ErrorName+errorAuthFailed = T.mkErrorName' "org.freedesktop.DBus.Error.AuthFailed"++errorNoServer :: T.ErrorName+errorNoServer = T.mkErrorName' "org.freedesktop.DBus.Error.NoServer"++errorTimeout :: T.ErrorName+errorTimeout = T.mkErrorName' "org.freedesktop.DBus.Error.Timeout"++errorNoNetwork :: T.ErrorName+errorNoNetwork = T.mkErrorName' "org.freedesktop.DBus.Error.NoNetwork"++errorAddressInUse :: T.ErrorName+errorAddressInUse = T.mkErrorName' "org.freedesktop.DBus.Error.AddressInUse"++errorDisconnected :: T.ErrorName+errorDisconnected = T.mkErrorName' "org.freedesktop.DBus.Error.Disconnected"++errorInvalidArgs :: T.ErrorName+errorInvalidArgs = T.mkErrorName' "org.freedesktop.DBus.Error.InvalidArgs"++errorFileNotFound :: T.ErrorName+errorFileNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.FileNotFound"++errorFileExists :: T.ErrorName+errorFileExists = T.mkErrorName' "org.freedesktop.DBus.Error.FileExists"++errorUnknownMethod :: T.ErrorName+errorUnknownMethod = T.mkErrorName' "org.freedesktop.DBus.Error.UnknownMethod"++errorTimedOut :: T.ErrorName+errorTimedOut = T.mkErrorName' "org.freedesktop.DBus.Error.TimedOut"++errorMatchRuleNotFound :: T.ErrorName+errorMatchRuleNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.MatchRuleNotFound"++errorMatchRuleInvalid :: T.ErrorName+errorMatchRuleInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.MatchRuleInvalid"++errorSpawnExecFailed :: T.ErrorName+errorSpawnExecFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ExecFailed"++errorSpawnForkFailed :: T.ErrorName+errorSpawnForkFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ForkFailed"++errorSpawnChildExited :: T.ErrorName+errorSpawnChildExited = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ChildExited"++errorSpawnChildSignaled :: T.ErrorName+errorSpawnChildSignaled = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ChildSignaled"++errorSpawnFailed :: T.ErrorName+errorSpawnFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.Failed"++errorSpawnFailedToSetup :: T.ErrorName+errorSpawnFailedToSetup = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.FailedToSetup"++errorSpawnConfigInvalid :: T.ErrorName+errorSpawnConfigInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ConfigInvalid"++errorSpawnServiceNotValid :: T.ErrorName+errorSpawnServiceNotValid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ServiceNotValid"++errorSpawnServiceNotFound :: T.ErrorName+errorSpawnServiceNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ServiceNotFound"++errorSpawnPermissionsInvalid :: T.ErrorName+errorSpawnPermissionsInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid"++errorSpawnFileInvalid :: T.ErrorName+errorSpawnFileInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.FileInvalid"++errorSpawnNoMemory :: T.ErrorName+errorSpawnNoMemory = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.NoMemory"++errorUnixProcessIdUnknown :: T.ErrorName+errorUnixProcessIdUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.UnixProcessIdUnknown"++errorInvalidFileContent :: T.ErrorName+errorInvalidFileContent = T.mkErrorName' "org.freedesktop.DBus.Error.InvalidFileContent"++errorSELinuxSecurityContextUnknown :: T.ErrorName+errorSELinuxSecurityContextUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"++errorAdtAuditDataUnknown :: T.ErrorName+errorAdtAuditDataUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.AdtAuditDataUnknown"++errorObjectPathInUse :: T.ErrorName+errorObjectPathInUse = T.mkErrorName' "org.freedesktop.DBus.Error.ObjectPathInUse"++errorInconsistentMessage :: T.ErrorName+errorInconsistentMessage = T.mkErrorName' "org.freedesktop.DBus.Error.InconsistentMessage"++@+\section{Misc. utility functions}++<<DBus/Util.hs>>=+<<copyright>>+module DBus.Util where+import Text.Parsec (Parsec, parse)+import Data.Char (digitToInt)++checkLength :: Int -> String -> Maybe String+checkLength length' s | length s <= length' = Just s+checkLength _ _ = Nothing++parseMaybe :: Parsec String () a -> String -> Maybe a+parseMaybe p = either (const Nothing) Just . parse p ""++mkUnsafe :: Show a => String -> (a -> Maybe b) -> a -> b+mkUnsafe label f x = case f x of+	Just x' -> x'+	Nothing -> error $ "Invalid " ++ label ++ ": " ++ show x++hexToInt :: String -> Int+hexToInt = foldl ((+) . (16 *)) 0 . map digitToInt++eitherToMaybe :: Either a b -> Maybe b+eitherToMaybe (Left  _) = Nothing+eitherToMaybe (Right x) = Just x++fromRight :: Either a b -> b+fromRight (Right x) = x+fromRight _ = error "DBus.Util.fromRight: Left"++maybeIndex :: [a] -> Int -> Maybe a+maybeIndex (x:_ ) 0         = Just x+maybeIndex (_:xs) n | n > 0 = maybeIndex xs (n - 1)+maybeIndex _ _ = Nothing  @ \end{document}
hs/DBus/Address.hs view
@@ -80,4 +80,3 @@         encode c | elem c optionallyEncoded = TL.singleton c                  | otherwise       = TL.pack $ printf "%%%02X" (ord c) -
hs/DBus/Bus.hs view
@@ -18,11 +18,11 @@ {-# LANGUAGE OverloadedStrings #-}  module DBus.Bus-        ( getSystemBus+        ( getBus+        , getFirstBus+        , getSystemBus         , getSessionBus         , getStarterBus-        , getFirstBus-        , getBus         ) where import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL@@ -41,20 +41,14 @@ import qualified DBus.Types as T import DBus.Util (fromRight) +busForConnection :: C.Connection -> IO (C.Connection, T.BusName)+busForConnection c = sendHello c >>= return . (,) c+ getBus :: A.Address -> IO (C.Connection, T.BusName)-getBus addr = do-        c <- C.connect addr-        name <- sendHello c-        return (c, name)+getBus = (busForConnection =<<) . C.connect  getFirstBus :: [A.Address] -> IO (C.Connection, T.BusName)-getFirstBus as = getFirstBus' as as--getFirstBus' :: [A.Address] -> [A.Address] -> IO (C.Connection, T.BusName)-getFirstBus' orig     [] = E.throwIO $ C.NoWorkingAddress orig-getFirstBus' orig (a:as) = E.catch (getBus a) onError where-        onError :: E.SomeException -> IO (C.Connection, T.BusName)-        onError _ = getFirstBus' orig as+getFirstBus = (busForConnection =<<) . C.connectFirst  getSystemBus :: IO (C.Connection, T.BusName) getSystemBus = getBus' $ fromEnv `E.catch` noEnv where@@ -102,7 +96,7 @@         received <- C.receive c         msg <- case received of                 Right x -> return x-                Left err -> E.throwIO $ E.AssertionFailed "Invalid response to Hello()"+                Left _  -> E.throwIO $ E.AssertionFailed "Invalid response to Hello()"         case msg of                 (M.ReceivedMethodReturn _ _ reply) ->                         if M.methodReturnSerial reply == serial
hs/DBus/Connection.hs view
@@ -26,6 +26,7 @@           , W.UnmarshalError (..)            , connect+          , connectFirst            , send @@ -45,6 +46,13 @@ import qualified Network as N import qualified Data.Map as Map +import qualified Network.Socket as NS++import qualified Text.Parsec as P+import Control.Monad (unless)+import Data.Binary.Get (runGet, getWord16host)+import Data.Binary.Put (runPut, putWord16be)+ import qualified System.IO as I  import qualified Control.Exception as E@@ -61,10 +69,10 @@ import qualified DBus.Wire as W  -data Connection = Connection A.Address Transport (C.MVar M.Serial)+data Connection = Connection A.Address Transport (C.MVar M.Serial) (C.MVar ())  instance Show Connection where-        showsPrec d (Connection a _ _) = showParen (d > 10) $+        showsPrec d (Connection a _ _ _) = showParen (d > 10) $                 showString' ["<connection ", show $ A.strAddress a, ">"] where                 showString' = foldr (.) id . map showString @@ -76,16 +84,17 @@ connectTransport :: A.Address -> IO Transport connectTransport a = transport' (A.addressMethod a) a where         transport' "unix" = unix-        transport' _      = unknownTransport+        transport' "tcp"  = tcp+        transport' _      = E.throwIO . UnknownMethod  unix :: A.Address -> IO Transport-unix a = handleTransport . N.connectTo "localhost" =<< port where+unix a = port >>= N.connectTo "localhost" >>= handleTransport where         params = A.addressParameters a         path = Map.lookup "path" params         abstract = Map.lookup "abstract" params                  tooMany = "Only one of `path' or `abstract' may be specified for the\-                  \ `unix' method."+                  \ `unix' transport."         tooFew = "One of `path' or `abstract' must be specified for the\                  \ `unix' transport."         @@ -96,16 +105,70 @@                 (Just x, Nothing) -> return $ TL.unpack x                 (Nothing, Just x) -> return $ '\x00' : TL.unpack x -handleTransport :: IO I.Handle -> IO Transport-handleTransport io = do-        h <- io+tcp :: A.Address -> IO Transport+tcp a = openHandle >>= handleTransport where+        params = A.addressParameters a+        openHandle = do+                port <- getPort+                family <- getFamily+                addresses <- getAddresses family+                socket <- openSocket port addresses+                NS.socketToHandle socket I.ReadWriteMode++        hostname = maybe "localhost" TL.unpack $ Map.lookup "host" params++        unknownFamily x = TL.concat ["Unknown socket family for TCP transport: ", x]+        getFamily = case Map.lookup "family" params of+                Just "ipv4" -> return NS.AF_INET+                Just "ipv6" -> return NS.AF_INET6+                Nothing     -> return NS.AF_UNSPEC+                Just x      -> E.throwIO $ BadParameters a $ unknownFamily x++        missingPort = "TCP transport requires the ``port'' parameter."+        badPort x = TL.concat ["Invalid socket port for TCP transport: ", x]+        getPort = case Map.lookup "port" params of+                Nothing -> E.throwIO $ BadParameters a missingPort+                Just x -> case P.parse parseWord16 "" (TL.unpack x) of+                        Right x' -> return $ NS.PortNum x'+                        Left  _  -> E.throwIO $ BadParameters a $ badPort x++        parseWord16 = do+                chars <- P.many1 P.digit+                P.eof+                let value = read chars :: Integer+                unless (value > 0 && value <= 65535) $+                        P.parserFail "bad port" >> return ()+                let word = fromIntegral value+                return $ runGet getWord16host (runPut (putWord16be word))++        getAddresses family = do+                let hints = NS.defaultHints+                        { NS.addrFlags = [NS.AI_ADDRCONFIG]+                        , NS.addrFamily = family+                        , NS.addrSocketType = NS.Stream+                        }+                NS.getAddrInfo (Just hints) (Just hostname) Nothing++        setPort port (NS.SockAddrInet  _ x)     = NS.SockAddrInet port x+        setPort port (NS.SockAddrInet6 _ x y z) = NS.SockAddrInet6 port x y z+        setPort _    addr                       = addr++        openSocket _ [] = E.throwIO $ NoWorkingAddress [a]+        openSocket port (addr:addrs) = E.catch (openSocket' port addr) $+                \(E.SomeException _) -> openSocket port addrs+        openSocket' port addr = do+                sock <- NS.socket (NS.addrFamily addr)+                                  (NS.addrSocketType addr)+                                  (NS.addrProtocol addr)+                NS.connect sock . setPort port . NS.addrAddress $ addr+                return sock++handleTransport :: I.Handle -> IO Transport+handleTransport h = do         I.hSetBuffering h I.NoBuffering         I.hSetBinaryMode h True         return $ Transport (L.hPut h) (L.hGet h . fromIntegral) -unknownTransport :: A.Address -> IO Transport-unknownTransport = E.throwIO . UnknownMethod- data ConnectionError         = InvalidAddress Text         | BadParameters A.Address Text@@ -121,9 +184,16 @@         let putS = transportSend t . encodeUtf8 . TL.pack         let getS = fmap (TL.unpack . decodeUtf8) . transportRecv t         authenticate putS getS+        readLock <- C.newMVar ()         serialMVar <- C.newMVar M.firstSerial-        return $ Connection a t serialMVar+        return $ Connection a t serialMVar readLock +connectFirst :: [A.Address] -> IO Connection+connectFirst orig = connectFirst' orig where+        connectFirst'     [] = E.throwIO $ NoWorkingAddress orig+        connectFirst' (a:as) = E.catch (connect a) $+                \(E.SomeException _) -> connectFirst' as+ authenticate :: (String -> IO ()) -> (Word32 -> IO String)                 -> IO () authenticate put get = do@@ -151,7 +221,7 @@  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@@ -168,5 +238,6 @@         return x  receive :: Connection -> IO (Either W.UnmarshalError M.ReceivedMessage)-receive (Connection _ t _) = W.unmarshalMessage $ transportRecv t+receive (Connection _ t _ lock) = C.withMVar lock $ \_ ->+        W.unmarshalMessage $ transportRecv t 
hs/DBus/Introspection.hs view
@@ -170,7 +170,7 @@         [arg | direction `elem` dirs]  children :: Monad m => (H.Element a -> m b) -> H.CFilter a -> H.Element a -> m [b]-children f filt (H.Elem _ _ contents) = do+children f filt (H.Elem _ _ contents) =         mapM f [x | (H.CElem x _) <- concatMap filt contents]  dtdPublicID, dtdSystemID :: String@@ -190,7 +190,7 @@ xmlRoot :: Object -> Maybe (H.Element a) xmlRoot obj@(Object path _ _) = do         (H.CElem root _) <- xmlObject' (T.strObjectPath path) obj-        return $ root+        return root  xmlObject :: T.ObjectPath -> Object -> Maybe (H.Content a) xmlObject parentPath obj@(Object path _ _) = do@@ -251,9 +251,9 @@         ] []  xmlAccess :: [PropertyAccess] -> String-xmlAccess access = read ++ write where-        read = if elem Read access then "read" else ""-        write = if elem Write access then "write" else ""+xmlAccess access = readS ++ writeS where+        readS = if elem Read access then "read" else ""+        writeS = if elem Write access then "write" else ""  mkElement :: String -> [H.Attribute] -> [H.Content a] -> H.Content a mkElement name attrs contents = H.CElem (H.Elem name attrs contents) undefined
+ hs/DBus/MatchRule.hs view
@@ -0,0 +1,175 @@+{-+  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 DBus.MatchRule (+          MatchRule (..)+        , MessageType (..)+        , ParameterValue (..)+        , formatRule+        , addMatch+        , matchAll+        , matches+        ) where+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as TL++import Data.Word (Word8)+import Data.Maybe (catMaybes, fromMaybe)+import qualified Data.Set as Set+import qualified DBus.Types as T+import qualified DBus.Message as M+import qualified DBus.Constants as C+import DBus.Util (maybeIndex)++data MatchRule = MatchRule+        { matchType        :: Maybe MessageType+        , matchSender      :: Maybe T.BusName+        , matchInterface   :: Maybe T.InterfaceName+        , matchMember      :: Maybe T.MemberName+        , matchPath        :: Maybe T.ObjectPath+        , matchDestination :: Maybe T.BusName+        , matchParameters  :: [ParameterValue]+        }++data ParameterValue+        = StringValue Word8 Text+        | PathValue Word8 T.ObjectPath+        deriving (Show, Eq)++data MessageType+        = MethodCall+        | MethodReturn+        | Signal+        | Error+        deriving (Show, Eq)++formatRule :: MatchRule -> Text+formatRule rule = TL.intercalate "," filters where+        filters = structureFilters ++ parameterFilters+        parameterFilters = map formatParameter $ matchParameters rule+        structureFilters = catMaybes $ map unpack+                [ ("type", fmap formatType . matchType)+                , ("sender", fmap T.strBusName . matchSender)+                , ("interface", fmap T.strInterfaceName . matchInterface)+                , ("member", fmap T.strMemberName . matchMember)+                , ("path", fmap T.strObjectPath . matchPath)+                , ("destination", fmap T.strBusName . matchDestination)+                ]+        unpack (key, mkValue) = formatFilter' key `fmap` mkValue rule++formatParameter :: ParameterValue -> Text+formatParameter (StringValue index x) = formatFilter' key x where+        key = "arg" `TL.append` TL.pack (show index)+formatParameter (PathValue index x) = formatFilter' key value where+        key = "arg" `TL.append` TL.pack (show index) `TL.append` "path"+        value = T.strObjectPath x++formatFilter' :: Text -> Text -> Text+formatFilter' key value = TL.concat [key, "='", value, "'"]++formatType :: MessageType -> Text+formatType MethodCall   = "method_call"+formatType MethodReturn = "method_return"+formatType Signal       = "signal"+formatType Error        = "error"++addMatch :: MatchRule -> M.MethodCall+addMatch rule = M.MethodCall+        C.dbusPath+        (T.mkMemberName' "AddMatch")+        (Just C.dbusInterface)+        (Just C.dbusName)+        Set.empty+        [T.toVariant $ formatRule rule]++matchAll :: MatchRule+matchAll = MatchRule+        { matchType        = Nothing+        , matchSender      = Nothing+        , matchInterface   = Nothing+        , matchMember      = Nothing+        , matchPath        = Nothing+        , matchDestination = Nothing+        , matchParameters  = []+        }++matches :: MatchRule -> M.ReceivedMessage -> Bool+matches rule msg = and . catMaybes . map ($ rule) $+        [ fmap      (typeMatches msg) . matchType+        , fmap    (senderMatches msg) . matchSender+        , fmap     (ifaceMatches msg) . matchInterface+        , fmap    (memberMatches msg) . matchMember+        , fmap      (pathMatches msg) . matchPath+        , fmap      (destMatches msg) . matchDestination+        , Just . parametersMatch msg  . matchParameters+        ]++typeMatches :: M.ReceivedMessage -> MessageType -> Bool+typeMatches (M.ReceivedMethodCall   _ _ _) MethodCall   = True+typeMatches (M.ReceivedMethodReturn _ _ _) MethodReturn = True+typeMatches (M.ReceivedSignal       _ _ _) Signal       = True+typeMatches (M.ReceivedError        _ _ _) Error        = True+typeMatches _ _ = False+++senderMatches :: M.ReceivedMessage -> T.BusName -> Bool+senderMatches msg name = M.receivedSender msg == Just name++ifaceMatches :: M.ReceivedMessage -> T.InterfaceName -> Bool+ifaceMatches (M.ReceivedMethodCall _ _ msg) name =+        Just name == M.methodCallInterface msg+ifaceMatches (M.ReceivedSignal _ _ msg) name =+        name == M.signalInterface msg+ifaceMatches _ _ = False++memberMatches :: M.ReceivedMessage -> T.MemberName -> Bool+memberMatches (M.ReceivedMethodCall _ _ msg) name =+        name == M.methodCallMember msg+memberMatches (M.ReceivedSignal _ _ msg) name =+        name == M.signalMember msg+memberMatches _ _ = False++pathMatches :: M.ReceivedMessage -> T.ObjectPath -> Bool+pathMatches (M.ReceivedMethodCall _ _ msg) path =+        path == M.methodCallPath msg+pathMatches (M.ReceivedSignal _ _ msg) path =+        path == M.signalPath msg+pathMatches _ _ = False++destMatches :: M.ReceivedMessage -> T.BusName -> Bool+destMatches (M.ReceivedMethodCall _ _ msg) name =+        Just name == M.methodCallDestination msg+destMatches (M.ReceivedMethodReturn _ _ msg) name =+        Just name == M.methodReturnDestination msg+destMatches (M.ReceivedError _ _ msg) name =+        Just name == M.errorDestination msg+destMatches (M.ReceivedSignal _ _ msg) name =+        Just name == M.signalDestination msg+destMatches _ _ = False++parametersMatch :: M.ReceivedMessage -> [ParameterValue] -> Bool+parametersMatch _ [] = True+parametersMatch msg values = all validParam values where+        body = M.receivedBody msg+        validParam (StringValue idx x) = validParam' idx x+        validParam (PathValue   idx x) = validParam' idx x+        validParam' idx x = fromMaybe False $ do+                var <- maybeIndex body $ fromIntegral idx+                fmap (== x) $ T.fromVariant var+
hs/DBus/Message.hs view
@@ -30,6 +30,7 @@          , MethodReturn (..)           , Error (..)+         , errorMessage           , Signal (..) @@ -38,6 +39,7 @@          , ReceivedMessage (..)          , receivedSerial          , receivedSender+         , receivedBody          ) where import DBus.Message.Internal
hs/DBus/Message/Internal.hs view
@@ -15,10 +15,17 @@   along with this program.  If not, see <http://www.gnu.org/licenses/>. -} +{-# LANGUAGE OverloadedStrings #-}+ module DBus.Message.Internal where+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as TL+ import qualified Data.Set as S import Data.Word (Word8, Word32)+import Data.Maybe (fromMaybe) import qualified DBus.Types as T+import DBus.Util (maybeIndex)  class Message a where         messageTypeCode     :: a -> Word8@@ -86,14 +93,13 @@ data MethodReturn = MethodReturn         { methodReturnSerial      :: Serial         , methodReturnDestination :: Maybe T.BusName-        , methodReturnFlags       :: S.Set Flag         , methodReturnBody        :: [T.Variant]         }         deriving (Show, Eq)  instance Message MethodReturn where         messageTypeCode _ = 2-        messageFlags      = methodReturnFlags+        messageFlags    _ = S.fromList [NoReplyExpected, NoAutoStart]         messageBody       = methodReturnBody         messageHeaderFields m = concat                 [ [ ReplySerial $ methodReturnSerial m@@ -105,14 +111,13 @@         { errorName        :: T.ErrorName         , errorSerial      :: Serial         , errorDestination :: Maybe T.BusName-        , errorFlags       :: S.Set Flag         , errorBody        :: [T.Variant]         }         deriving (Show, Eq)  instance Message Error where         messageTypeCode _ = 3-        messageFlags      = errorFlags+        messageFlags    _ = S.fromList [NoReplyExpected, NoAutoStart]         messageBody       = errorBody         messageHeaderFields m = concat                 [ [ ErrorName   $ errorName m@@ -121,19 +126,26 @@                 , maybe' Destination . errorDestination $ m                 ] +errorMessage :: Error -> Text+errorMessage msg = fromMaybe "(no error message)" $ do+        field <- maybeIndex (errorBody msg) 0+        text <- T.fromVariant field+        if TL.null text+                then Nothing+                else return text+ data Signal = Signal         { signalPath        :: T.ObjectPath         , signalMember      :: T.MemberName         , signalInterface   :: T.InterfaceName         , signalDestination :: Maybe T.BusName-        , signalFlags       :: S.Set Flag         , signalBody        :: [T.Variant]         }         deriving (Show, Eq)  instance Message Signal where         messageTypeCode _ = 4-        messageFlags      = signalFlags+        messageFlags    _ = S.fromList [NoReplyExpected, NoAutoStart]         messageBody       = signalBody         messageHeaderFields m = concat                 [ [ Path      $ signalPath m@@ -171,4 +183,11 @@ receivedSender (ReceivedError        _ s _) = s receivedSender (ReceivedSignal       _ s _) = s receivedSender (ReceivedUnknown      _ s _) = s++receivedBody :: ReceivedMessage -> [T.Variant]+receivedBody (ReceivedMethodCall   _ _ x) = messageBody x+receivedBody (ReceivedMethodReturn _ _ x) = messageBody x+receivedBody (ReceivedError        _ _ x) = messageBody x+receivedBody (ReceivedSignal       _ _ x) = messageBody x+receivedBody _ = [] 
+ hs/DBus/NameReservation.hs view
@@ -0,0 +1,107 @@+{-+  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 DBus.NameReservation+        ( RequestNameFlag (..)+        , RequestNameReply (..)+        , ReleaseNameReply (..)+        , requestName+        , releaseName+        , mkRequestNameReply+        , mkReleaseNameReply+        ) where+import Data.Word (Word32)+import Data.Bits ((.|.))+import qualified Data.Set as Set+import qualified DBus.Types as T+import qualified DBus.Message as M+import qualified DBus.Constants as C+import DBus.Util (maybeIndex)++data RequestNameFlag+        = AllowReplacement+        | ReplaceExisting+        | DoNotQueue+        deriving (Show)++encodeFlags :: [RequestNameFlag] -> Word32+encodeFlags = foldr (.|.) 0 . map flagValue where+        flagValue AllowReplacement = 0x1+        flagValue ReplaceExisting  = 0x2+        flagValue DoNotQueue       = 0x4++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.methodCallBody =+                [ T.toVariant name+                , T.toVariant . encodeFlags $ flags]+        }++data RequestNameReply+        = PrimaryOwner+        | InQueue+        | Exists+        | AlreadyOwner+        deriving (Show)++mkRequestNameReply :: M.MethodReturn -> Maybe RequestNameReply+mkRequestNameReply msg =+        maybeIndex (M.messageBody msg) 0 >>=+        T.fromVariant >>=+        decodeRequestReply++decodeRequestReply :: Word32 -> Maybe RequestNameReply+decodeRequestReply 1 = Just PrimaryOwner+decodeRequestReply 2 = Just InQueue+decodeRequestReply 3 = Just Exists+decodeRequestReply 4 = Just AlreadyOwner+decodeRequestReply _ = Nothing++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.methodCallBody = [T.toVariant name]+        }++data ReleaseNameReply+        = Released+        | NonExistent+        | NotOwner+        deriving (Show)++mkReleaseNameReply :: M.MethodReturn -> Maybe ReleaseNameReply+mkReleaseNameReply msg =+        maybeIndex (M.messageBody msg) 0 >>=+        T.fromVariant >>=+        decodeReleaseReply++decodeReleaseReply :: Word32 -> Maybe ReleaseNameReply+decodeReleaseReply 1 = Just Released+decodeReleaseReply 2 = Just NonExistent+decodeReleaseReply 3 = Just NotOwner+decodeReleaseReply _ = Nothing+
hs/DBus/Types.hs view
@@ -55,6 +55,9 @@                    , fromArray                    , arrayFromItems +                   , arrayToBytes+                   , arrayFromBytes+                      -- * Dictionaries                    , Dictionary                    , dictionaryItems@@ -113,6 +116,11 @@  import DBus.Util (mkUnsafe) +import Data.ByteString.Lazy (ByteString)+import qualified Data.ByteString.Lazy as ByteString++import qualified Data.ByteString as StrictByteString+ import Data.List (intercalate)  import Control.Monad (unless)@@ -311,28 +319,39 @@ mkObjectPath' = mkUnsafe "object path" mkObjectPath  instance Variable Array where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };-data Array = Array-        { arrayType  :: Type-        , arrayItems :: [Variant]-        }+data Array+        = VariantArray Type [Variant]+        | ByteArray ByteString         deriving (Eq, Typeable)  instance Builtin Array where         builtinDBusType = DBusArray . arrayType +arrayType :: Array -> Type+arrayType (VariantArray t _) = t+arrayType (ByteArray _) = DBusByte++arrayItems :: Array -> [Variant]+arrayItems (VariantArray _ xs) = xs+arrayItems (ByteArray xs) = map toVariant $ ByteString.unpack xs+ instance Show Array where-        showsPrec d (Array t vs) = showParen (d > 10) $+        showsPrec d array = showParen (d > 10) $                 s "Array " . showSig . s " [" . s valueString . s "]" where                         s = showString-                        showSig = shows $ typeCode t-                        vs' = [show x | (Variant x) <- vs]-                        valueString = intercalate ", " vs'+                        showSig = shows . typeCode . arrayType $ array+                        vs = [show x | (Variant x) <- arrayItems array]+                        valueString = intercalate ", " vs  arrayFromItems :: Type -> [Variant] -> Maybe Array+arrayFromItems DBusByte vs = do+        bytes <- mapM fromVariant vs+        Just . ByteArray . ByteString.pack $ bytes+ arrayFromItems t vs = do         mkSignature (typeCode t)         if all (\x -> variantType x == t) vs-                then Just $ Array t vs+                then Just $ VariantArray t vs                 else Nothing  toArray :: Variable a => Type -> [a] -> Maybe Array@@ -341,6 +360,23 @@ fromArray :: Variable a => Array -> Maybe [a] fromArray = mapM fromVariant . arrayItems +arrayToBytes :: Array -> Maybe ByteString+arrayToBytes (ByteArray x) = Just x+arrayToBytes _             = Nothing++arrayFromBytes :: ByteString -> Array+arrayFromBytes = ByteArray++instance Variable ByteString where+        toVariant = toVariant . arrayFromBytes+        fromVariant x = fromVariant x >>= arrayToBytes++instance Variable StrictByteString.ByteString where+        toVariant x = toVariant . arrayFromBytes $ ByteString.fromChunks [x]+        fromVariant x = do+                chunks <- ByteString.toChunks `fmap` fromVariant x+                return $ StrictByteString.concat chunks+ instance Variable Dictionary where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x }; data Dictionary = Dictionary         { dictionaryKeyType   :: Type@@ -394,16 +430,16 @@         structs = [Structure [k, v] | (k, v) <- items]  arrayToDictionary :: Array -> Maybe Dictionary-arrayToDictionary (Array t items) = do+arrayToDictionary array = do         let toPair x = do                 struct <- fromVariant x                 case struct of                         Structure [k, v] -> Just (k, v)                         _                -> Nothing-        (kt, vt) <- case t of+        (kt, vt) <- case arrayType array of                 DBusStructure [kt, vt] -> Just (kt, vt)                 _                      -> Nothing-        pairs <- mapM toPair items+        pairs <- mapM toPair $ arrayItems array         dictionaryFromItems kt vt pairs  instance Variable Structure where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };
hs/DBus/Util.hs view
@@ -42,3 +42,8 @@ fromRight (Right x) = x fromRight _ = error "DBus.Util.fromRight: Left" +maybeIndex :: [a] -> Int -> Maybe a+maybeIndex (x:_ ) 0         = Just x+maybeIndex (_:xs) n | n > 0 = maybeIndex xs (n - 1)+maybeIndex _ _ = Nothing+
hs/DBus/Wire.hs view
@@ -18,8 +18,8 @@ {-# LANGUAGE OverloadedStrings #-}  {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE DeriveDataTypeable #-} module DBus.Wire (  Endianness (..)                    , alignment@@ -47,11 +47,11 @@ import qualified Control.Monad.State as ST import qualified Control.Monad.Error as E import qualified Data.ByteString.Lazy as L--import qualified Data.Binary.Put as P+import qualified Data.Binary.Builder as B  import qualified Data.Binary.Get as G +import Data.Binary.Put (runPut) import qualified Data.Binary.IEEE754 as IEEE  import Data.Text.Lazy.Encoding (encodeUtf8, decodeUtf8)@@ -67,7 +67,6 @@ import Data.Maybe (fromJust, listToMaybe, fromMaybe) import Data.Word (Word8, Word32, Word64) import Data.Int (Int16, Int32, Int64)-import Data.Typeable (Typeable)  import qualified DBus.Types as T @@ -117,16 +116,16 @@                 then count' - missing                 else 0 -data MarshalState = MarshalState Endianness L.ByteString+data MarshalState = MarshalState Endianness B.Builder !Word64 newtype MarshalM a = MarshalM (E.ErrorT MarshalError (ST.State MarshalState) a)         deriving (Monad, E.MonadError MarshalError, ST.MonadState MarshalState) type Marshal = MarshalM ()  runMarshal :: Marshal -> Endianness -> Either MarshalError L.ByteString runMarshal (MarshalM m) e = case ST.runState (E.runErrorT m) initialState of-        (Right _, MarshalState _ bytes) -> Right bytes+        (Right _, MarshalState _ builder _) -> Right (B.toLazyByteString builder)         (Left  x, _) -> Left x-        where initialState = MarshalState e L.empty+        where initialState = MarshalState e B.empty 0  marshal :: T.Variant -> Marshal marshal v = marshalType (T.variantType v) where@@ -134,14 +133,21 @@         x = fromJust . T.fromVariant $ v         marshalType :: T.Type -> Marshal         marshalType T.DBusByte   = append $ L.singleton x-        marshalType T.DBusWord16 = marshalPut P.putWord16be x-        marshalType T.DBusWord32 = marshalPut P.putWord32be x-        marshalType T.DBusWord64 = marshalPut P.putWord64be x-        marshalType T.DBusInt16  = marshalPut P.putWord16be $ fromIntegral (x :: Int16)-        marshalType T.DBusInt32  = marshalPut P.putWord32be $ fromIntegral (x :: Int32)-        marshalType T.DBusInt64  = marshalPut P.putWord64be $ fromIntegral (x :: Int64)+        marshalType T.DBusWord16 = marshalBuilder 2 B.putWord16be B.putWord16le x+        marshalType T.DBusWord32 = marshalBuilder 4 B.putWord32be B.putWord32le x+        marshalType T.DBusWord64 = marshalBuilder 8 B.putWord64be B.putWord64le x+        marshalType T.DBusInt16  = marshalBuilder 2 B.putWord16be B.putWord16le $ fromIntegral (x :: Int16)+        marshalType T.DBusInt32  = marshalBuilder 4 B.putWord32be B.putWord32le $ fromIntegral (x :: Int32)+        marshalType T.DBusInt64  = marshalBuilder 8 B.putWord64be B.putWord64le $ fromIntegral (x :: Int64) -        marshalType T.DBusDouble = marshalPut IEEE.putFloat64be x+        marshalType T.DBusDouble = do+                pad 8+                (MarshalState e _ _) <- ST.get+                let put = case e of+                        BigEndian -> IEEE.putFloat64be+                        LittleEndian -> IEEE.putFloat64le+                let bytes = runPut $ put x+                append bytes          marshalType T.DBusBoolean = marshalWord32 $ if x then 1 else 0 @@ -169,31 +175,35 @@   append :: L.ByteString -> Marshal-append bs = do-        (MarshalState e bs') <- ST.get-        ST.put $ MarshalState e (L.append bs' bs)+append bytes = do+        (MarshalState e builder count) <- ST.get+        let builder' = B.append builder $ B.fromLazyByteString bytes+            count' = count + (fromIntegral $ L.length bytes)+        ST.put $ MarshalState e builder' count'  pad :: Word8 -> Marshal pad count = do-        (MarshalState _ bytes) <- ST.get-        let padding' = padding (fromIntegral . L.length $ bytes) count-        append $ L.replicate (fromIntegral padding') 0+        (MarshalState _ _ existing) <- ST.get+        let padding' = fromIntegral $ padding existing count+        append $ L.replicate padding' 0 -marshalPut :: (a -> P.Put) -> a -> Marshal-marshalPut put x = do-        let bytes = P.runPut $ put x-        (MarshalState e _) <- ST.get-        pad . fromIntegral . L.length $ bytes-        append $ case e of-                BigEndian -> bytes-                LittleEndian -> L.reverse bytes+marshalBuilder :: Word8 -> (a -> B.Builder) -> (a -> B.Builder) -> a -> Marshal+marshalBuilder size be le x = do+        pad size+        (MarshalState e builder count) <- ST.get+        let builder' = B.append builder $ case e of+                BigEndian -> be x+                LittleEndian -> le x+        let count' = count + (fromIntegral size)+        ST.put $ MarshalState e builder' count'  data MarshalError         = MessageTooLong Word64         | ArrayTooLong Word64         | InvalidBodySignature Text         | InvalidVariantSignature Text-        deriving (Eq, Typeable)+        | TextContainsNUL Text+        deriving (Eq)  instance Show MarshalError where         show (MessageTooLong x) = concat@@ -204,10 +214,12 @@                 ["Invalid body signature: ", show x]         show (InvalidVariantSignature x) = concat                 ["Invalid variant signature: ", show x]+        show (TextContainsNUL x) = concat+                ["Text contains NUL: ", show x]  instance E.Error MarshalError -data UnmarshalState = UnmarshalState Endianness L.ByteString Word64+data UnmarshalState = UnmarshalState Endianness L.ByteString !Word64 newtype Unmarshal a = Unmarshal (E.ErrorT UnmarshalError (ST.State UnmarshalState) a)         deriving (Monad, Functor, E.MonadError UnmarshalError, ST.MonadState UnmarshalState) @@ -251,7 +263,7 @@  unmarshalType T.DBusSignature = fmap T.toVariant unmarshalSignature -unmarshalType (T.DBusArray t) = fmap T.toVariant $ unmarshalArray t+unmarshalType (T.DBusArray t) = T.toVariant `fmap` unmarshalArray t  unmarshalType (T.DBusDictionary kt vt) = do         let pairType = T.DBusStructure [kt, vt]@@ -268,18 +280,17 @@                 _   -> Nothing                  t <- fromMaybeU "variant signature" getType =<< unmarshalSignature-        fmap T.toVariant $ unmarshalType t+        T.toVariant `fmap` unmarshalType t   consume :: Word64 -> Unmarshal L.ByteString consume count = do         (UnmarshalState e bytes offset) <- ST.get-        let bytes' = L.drop (fromIntegral offset) bytes-        let x = L.take (fromIntegral count) bytes'+        let (x, bytes') = L.splitAt (fromIntegral count) bytes         unless (L.length x == fromIntegral count) $                 E.throwError $ UnexpectedEOF offset         -        ST.put $ UnmarshalState e bytes (offset + count)+        ST.put $ UnmarshalState e bytes' (offset + count)         return x  skipPadding :: Word8 -> Unmarshal ()@@ -319,7 +330,7 @@  unmarshalGet' :: T.Variable a => Word8 -> G.Get a -> G.Get a               -> Unmarshal T.Variant-unmarshalGet' count be le = fmap T.toVariant $ unmarshalGet count be le+unmarshalGet' count be le = T.toVariant `fmap` unmarshalGet count be le  untilM :: Monad m => m Bool -> m a -> m [a] untilM test comp = do@@ -340,7 +351,7 @@         | InvalidPadding Word64         | MissingTerminator Word64         | ArraySizeMismatch-        deriving (Eq, Typeable)+        deriving (Eq)  instance Show UnmarshalError where         show (UnsupportedProtocolVersion x) = concat@@ -362,7 +373,7 @@ instance E.Error UnmarshalError  marshalWord32 :: Word32 -> Marshal-marshalWord32 = marshalPut P.putWord32be+marshalWord32 = marshalBuilder 4 B.putWord32be B.putWord32le  unmarshalWord32 :: Unmarshal Word32 unmarshalWord32 = unmarshalGet 4 G.getWord32be G.getWord32le@@ -370,6 +381,8 @@ marshalText :: Text -> Marshal marshalText x = do         let bytes = encodeUtf8 x+        when (L.any (== 0) bytes) $+                E.throwError $ TextContainsNUL x         marshalWord32 . fromIntegral . L.length $ bytes         append bytes         append (L.singleton 0)@@ -391,37 +404,45 @@  unmarshalSignature :: Unmarshal T.Signature unmarshalSignature = do-        byteCount <- fmap L.head $ consume 1+        byteCount <- L.head `fmap` consume 1         sigText <- fmap decodeUtf8 $ consume . fromIntegral $ byteCount         skipTerminator         fromMaybeU "signature" T.mkSignature sigText  marshalArray :: T.Array -> Marshal marshalArray x = do-        (arrayPadding, arrayBytes) <- getArrayBytes x+        (arrayPadding, arrayBytes) <- getArrayBytes (T.arrayType x) x         let arrayLen = L.length arrayBytes         when (arrayLen > fromIntegral C.arrayMaximumLength)                 (E.throwError $ ArrayTooLong $ fromIntegral arrayLen)         marshalWord32 $ fromIntegral arrayLen-        append arrayPadding+        append $ L.replicate arrayPadding 0         append arrayBytes -getArrayBytes :: T.Array -> MarshalM (L.ByteString, L.ByteString)-getArrayBytes x = do+getArrayBytes :: T.Type -> T.Array -> MarshalM (Int64, L.ByteString)+getArrayBytes T.DBusByte x = return (0, bytes) where+        Just bytes = T.arrayToBytes x++getArrayBytes itemType x = do         let vs = T.arrayItems x-        let itemType = T.arrayType x         s <- ST.get-        (MarshalState _ afterLength) <- marshalWord32 0 >> ST.get-        (MarshalState _ afterPadding) <- pad (alignment itemType) >> ST.get-        (MarshalState _ afterItems) <- mapM_ marshal vs >> ST.get+        (MarshalState _ _ afterLength) <- marshalWord32 0 >> ST.get+        (MarshalState e _ afterPadding) <- pad (alignment itemType) >> ST.get         -        let paddingBytes = L.drop (L.length afterLength) afterPadding-        let itemBytes = L.drop (L.length afterPadding) afterItems+        ST.put $ MarshalState e B.empty afterPadding+        (MarshalState _ itemBuilder _) <- mapM_ marshal vs >> ST.get         +        let itemBytes = B.toLazyByteString itemBuilder+            paddingSize = fromIntegral $ afterPadding - afterLength+                 ST.put s-        return (paddingBytes, itemBytes)+        return (paddingSize, itemBytes)  unmarshalArray :: T.Type -> Unmarshal T.Array+unmarshalArray T.DBusByte = do+        byteCount <- unmarshalWord32+        T.arrayFromBytes `fmap` consume (fromIntegral byteCount)+ unmarshalArray itemType = do         let getOffset = do                 (UnmarshalState _ _ o) <- ST.get@@ -497,9 +518,10 @@                 sig <- checkBodySig body                 empty <- ST.get                 mapM_ marshal body-                (MarshalState _ bodyBytes) <- ST.get+                (MarshalState _ bodyBytesB _) <- ST.get                 ST.put empty                 marshalEndianness e+                let bodyBytes = B.toLazyByteString bodyBytesB                 marshalHeader msg serial sig                         $ fromIntegral . L.length $ bodyBytes                 pad 8@@ -532,8 +554,7 @@  checkMaximumSize :: Marshal checkMaximumSize = do-        (MarshalState _ messageBytes) <- ST.get-        let messageLength = L.length messageBytes+        (MarshalState _ _ messageLength) <- ST.get         when (messageLength > fromIntegral C.messageMaximumLength)                 (E.throwError $ MessageTooLong $ fromIntegral messageLength) @@ -572,7 +593,7 @@          let fieldArray = fromJust . T.fromVariant $ header !! 6         let fieldStructures = fromJust . T.fromArray $ fieldArray-        fields <- fmap concat $ mapM decodeField fieldStructures+        fields <- concat `fmap` mapM decodeField fieldStructures          let bodyPadding = padding (fromIntegral fieldByteCount + 16) 8         getBytes . fromIntegral $ bodyPadding@@ -610,29 +631,29 @@  buildReceivedMessage 2 fields = do         replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]-        return $ \serial flags body -> let+        return $ \serial _ body -> let                 dest = listToMaybe [x | M.Destination x <- fields]                 sender = listToMaybe [x | M.Sender x <- fields]-                msg = M.MethodReturn replySerial dest flags body+                msg = M.MethodReturn replySerial dest body                 in M.ReceivedMethodReturn serial sender msg  buildReceivedMessage 3 fields = do         name <- require "error name" [x | M.ErrorName x <- fields]         replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]-        return $ \serial flags body -> let+        return $ \serial _ body -> let                 dest = listToMaybe [x | M.Destination x <- fields]                 sender = listToMaybe [x | M.Sender x <- fields]-                msg = M.Error name replySerial dest flags body+                msg = M.Error name replySerial dest body                 in M.ReceivedError serial sender msg  buildReceivedMessage 4 fields = do         path <- require "path" [x | M.Path x <- fields]         member <- require "member name" [x | M.Member x <- fields]         iface <- require "interface" [x | M.Interface x <- fields]-        return $ \serial flags body -> let+        return $ \serial _ body -> let                 dest = listToMaybe [x | M.Destination x <- fields]                 sender = listToMaybe [x | M.Sender x <- fields]-                msg = M.Signal path member iface dest flags body+                msg = M.Signal path member iface dest body                 in M.ReceivedSignal serial sender msg  buildReceivedMessage typeCode fields = return $ \serial flags body -> let
hs/Tests.hs view
@@ -426,18 +426,16 @@         arbitrary = do                 serial <- arbitrary                 dest   <- arbitrary-                flags  <- fmap Set.fromList arbitrary                 Structure body <- arbitrary-                return $ MethodReturn serial dest flags body+                return $ MethodReturn serial dest body  instance Arbitrary Error where         arbitrary = do                 name   <- arbitrary                 serial <- arbitrary                 dest   <- arbitrary-                flags  <- fmap Set.fromList arbitrary                 Structure body <- arbitrary-                return $ Error name serial dest flags body+                return $ Error name serial dest body  instance Arbitrary Signal where         arbitrary = do@@ -445,9 +443,8 @@                 member <- arbitrary                 iface  <- arbitrary                 dest   <- arbitrary-                flags  <- fmap Set.fromList arbitrary                 Structure body <- arbitrary-                return $ Signal path member iface dest flags body+                return $ Signal path member iface dest body  isRight :: Either a b -> Bool isRight = either (const False) (const True)