d-bus-0.1.0: src/DBus/Object.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverlappingInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PackageImports #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE PatternGuards #-}
module DBus.Object where
import Control.Applicative ((<$>))
import Control.Concurrent.STM
import qualified Control.Exception as Ex
import Control.Monad
import Control.Monad.Trans
import Data.List (intercalate, find)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe
import Data.Singletons
import Data.Singletons.Prelude.List
import Data.Singletons.TH
import Data.String
import Data.Text (Text)
import qualified Data.Text as Text
import Unsafe.Coerce (unsafeCoerce)
import DBus.Types
import DBus.Representable
-- | IsMethod is a Helper class to create MethodWrappers without having to
-- explicitly unwrap all the function arguments.
class IsMethod f where
type ArgTypes f :: [DBusType]
type ResultType f :: [DBusType]
toMethod :: f -> MethodWrapper (ArgTypes f) (ResultType f)
instance SingI t => IsMethod (IO (DBusArguments t)) where
type ArgTypes (IO (DBusArguments ts)) = '[]
type ResultType (IO (DBusArguments ts)) = ts
toMethod = MReturn. lift
instance SingI t => IsMethod (SignalT IO (DBusArguments t)) where
type ArgTypes (SignalT IO (DBusArguments ts)) = '[]
type ResultType (SignalT IO (DBusArguments ts)) = ts
toMethod = MReturn
instance (IsMethod f, SingI t) => IsMethod (DBusValue t -> f) where
type ArgTypes (DBusValue t -> f) = (t ': ArgTypes f)
type ResultType (DBusValue t -> f) = ResultType f
toMethod f = MAsk $ \x -> toMethod (f x)
class RepMethod f where
type RepMethodArgs f :: [DBusType]
type RepMethodValue f :: [DBusType]
repMethod :: f -> MethodWrapper (RepMethodArgs f) (RepMethodValue f)
type family FlattenRepType r where
FlattenRepType TypeUnit = '[]
FlattenRepType (TypeStruct ts) = ts
FlattenRepType t = '[t]
-- TODO: Figure out how to convice GHC that the flattenRepType function and the
-- FlattenRepType type function coincide
flattenRepS :: Sing a -> Sing (FlattenRepType a)
flattenRepS s = case flattenRepS' s of
SomeSing s' -> unsafeCoerce s'
where
flattenRepS' STypeUnit = SomeSing SNil
flattenRepS' (STypeStruct ts) = SomeSing ts
flattenRepS' t = SomeSing (SCons t SNil)
flattenRep :: ( Representable a ) =>
a
-> DBusArguments (FlattenRepType (RepType a))
flattenRep (x :: t) =
let rts = sing :: Sing (RepType t)
frts :: Sing (FlattenRepType (RepType t))
frts = flattenRepS rts
in case (rts, frts) of
(STypeUnit, SNil) -> ArgsNil
(STypeStruct ts, ts') -> case toRep x of DBVStruct str -> structToArgs str
(t, SCons t' SNil) -> case t %~ t' of
Proved Refl -> ArgsCons (toRep x) ArgsNil
Disproved _ -> error "flattenRep: this shouldn't happen"
instance (Representable t) => RepMethod (IO t) where
type RepMethodArgs (IO t) = '[]
type RepMethodValue (IO t) = FlattenRepType (RepType t)
repMethod (f :: IO t)
= let sng = flattenRepS (sing :: Sing (RepType t))
in withSingI sng $ MReturn $ flattenRep . toRep <$> lift f
instance (Representable t) => RepMethod (SignalT IO t) where
type RepMethodArgs (SignalT IO t) = '[]
type RepMethodValue (SignalT IO t) = FlattenRepType (RepType t)
repMethod (f :: SignalT IO t)
= let sng = flattenRepS (sing :: Sing (RepType t))
in withSingI sng $ MReturn $ flattenRep . toRep <$> f
instance (RepMethod b, Representable a)
=> RepMethod (a -> b) where
type RepMethodArgs (a -> b) = (RepType a ': RepMethodArgs b)
type RepMethodValue (a -> b) = RepMethodValue b
repMethod f = MAsk $ \x -> case fromRep x of
Nothing -> error "marshalling error" -- TODO
Just x -> repMethod $ f x
-- | Create a property from a getter and a setter. It will emit a
-- PropertyChanged signal when the setter is called (not only then). To change
-- this behaviour modify the propertyEmitsChangedSignal field
mkProperty :: Representable a =>
Text
-> Maybe (IO a)
-> Maybe (a -> IO Bool)
-> Property
mkProperty name get set =
let pw = PropertyWrapper { setProperty = doSet <$> set
, getProperty = lift . fmap toRep <$> get
}
in Property { propertyName = name
, propertyAccessors = pw
, propertyEmitsChangedSignal = PECSTrue
}
where
doSet f v = lift $ f =<< fromRepHelper v
fromRepHelper x = case fromRep x of
Nothing -> Ex.throwIO argTypeMismatch
Just r -> return r
-- | Make a property out of a TVar. The property is considered changed on every
-- get, no matter if the updated value is actually different from the old one
mkTVarProperty :: Representable a =>
Text
-> TVar a
-> Property
mkTVarProperty name tv = mkProperty
name
(Just (atomically $ readTVar tv))
(Just (\v -> atomically (writeTVar tv v)
>> return True))
runMethodW :: SingI at =>
MethodWrapper at rt
-> [SomeDBusValue]
-> Maybe (SignalT IO (DBusArguments rt))
runMethodW m args = runMethodW' sing args m
runMethodW' :: Sing at
-> [SomeDBusValue]
-> MethodWrapper at rt
-> Maybe (SignalT IO (DBusArguments rt))
runMethodW' SNil [] (MReturn f) = Just f
runMethodW' (SCons t ts) (arg:args) (MAsk f) = (runMethodW' ts args . f )
=<< dbusValue arg
runMethodW' _ _ _ = Nothing
methodWSignature :: (SingI at, SingI rt) =>
MethodWrapper (at :: [DBusType]) (rt :: [DBusType])
-> ([DBusType], [DBusType])
methodWSignature (_ :: MethodWrapper at rt) =
( fromSing (sing :: Sing at)
, fromSing (sing :: Sing rt)
)
runMethod :: Method -> [SomeDBusValue] -> Maybe (SignalT IO SomeDBusArguments)
runMethod (Method m _ _ _) args = liftM SDBA <$> runMethodW m args
methodSignature :: Method -> ([DBusType], [DBusType])
methodSignature (Method m _ _ _) = methodWSignature m
methodName :: Method -> Text.Text
methodName (Method _ n _ _) = n
argDescToList :: ArgumentDescription ts
-> [Text.Text]
argDescToList (t :-> ts) = t : argDescToList ts
argDescToList Result = []
resultDescToList :: ResultDescription t -> [Text.Text]
resultDescToList ResultDone = []
resultDescToList (t :> ts) = t : resultDescToList ts
argDescriptions args ress = (argDescToList args, resultDescToList ress)
instance Show Method where
show m@(Method _ n argDs resDs) =
let (args, res) = argDescriptions argDs resDs
(argst, rest) = methodSignature m
components = zipWith (\name tp -> (Text.unpack name
++ ":"
++ ppType tp))
(args ++ res)
(argst ++ rest)
in Text.unpack n ++ " :: " ++ intercalate " -> " components
instance Show Interface where
show i = "Interface " ++ show (interfaceName i) ++ " [{"
++ intercalate "}, {" (map show $ interfaceMethods i) ++ "}]"
instance Show Object where
show o = "Object " ++ show (objectPathToText $ objectObjectPath o) ++ " [("
++ intercalate "), (" (map show $ objectInterfaces o) ++ ")]"
findObject :: ObjectPath -> Object -> Maybe Object
findObject path o = case stripObjectPrefix (objectObjectPath o) path of
Nothing -> Nothing
Just suff -> if isEmpty suff then Just o
else listToMaybe . catMaybes $
(findObject suff <$> objectSubObjects o)
errorFailed msg = (MsgError "org.freedesktop.DBus.Error.Failed"
(Just msg)
[])
-- noSuchInterface, noSuchProperty, propertyNotReadable, propertyNotReadable
noSuchInterface = errorFailed "No such interface"
noSuchProperty = errorFailed "No such property"
propertyNotReadable = errorFailed "Property is not readable"
propertyNotWriteable = errorFailed "Property is not writeable"
argTypeMismatch = errorFailed "Argument type missmatch"
findProperty o ifaceName prop
= case find ((== ifaceName) . interfaceName) $ objectInterfaces o of
Nothing -> Left noSuchInterface
Just iface -> case find ((== prop) . propertyName)
$ interfaceProperties iface of
Nothing -> Left noSuchProperty
Just p -> Right p
propertyInterface = "org.freedesktop.DBus.Properties"
handleProperty :: Object
-> ObjectPath
-> MemberName
-> [SomeDBusValue]
-> Either MsgError (SignalT IO SomeDBusArguments)
handleProperty o _ "Get" [mbIface, mbProp]
| Just ifaceName <- fromRep =<< dbusValue mbIface
, Just propName <- fromRep =<< dbusValue mbProp
= findProperty o ifaceName propName
>>= (\Property{ propertyAccessors = accs} ->
case getProperty accs of
Nothing -> Left propertyNotReadable
Just rd -> Right $ SDBA . singletonArg <$> rd)
handleProperty o path "Set" [mbIface , mbProp, mbVal]
| Just ifaceName <- fromRep =<< dbusValue mbIface
, Just propName <- fromRep =<< dbusValue mbProp
= findProperty o ifaceName propName
>>= (\prop@Property{ propertyAccessors = accs} -> do
rd <- maybe (Left propertyNotWriteable) Right (setProperty accs)
v <- maybe (Left argTypeMismatch) Right (dbusValue mbVal)
Right $ do
invalidated <- rd v
when invalidated $ case propertyEmitsChangedSignal prop of
PECSTrue ->
signal
Signal
{ signalPath = path
, signalInterface = propertyInterface
, signalMember = "PropertiesChanged"
, signalBody =
[ DBV $
toRep ( ifaceName
, Map.fromList [(propName , DBVVariant v)]
, [] :: [Text]
)
]
}
PECSInvalidates ->
signal
Signal
{ signalPath = path
, signalInterface = propertyInterface
, signalMember = "PropertiesChanged"
, signalBody =
[ DBV $ toRep
( ifaceName
, Map.empty :: Map.Map Text
(DBusValue (TypeVariant))
, [propName]
)
]
}
PECSFalse -> return ()
return (SDBA ArgsNil))
handleProperty _ _ _ _ = Left argTypeMismatch
callAtPath :: Object
-> ObjectPath
-> Text.Text
-> Text.Text
-> [SomeDBusValue]
-> Either MsgError (SignalT IO SomeDBusArguments)
callAtPath root path interface member args = case findObject path root of
Nothing -> Left (MsgError "org.freedesktop.DBus.Error.Failed"
(Just . Text.pack $ "No such object "
++ show path)
[])
Just o -> case interface of
"org.freedesktop.DBus.Properties" -> handleProperty o path member args
_ -> case find ((== interface) . interfaceName) $ objectInterfaces o of
Nothing -> Left noSuchInterface
Just i -> case find ((== member) . methodName) $ interfaceMethods i of
Nothing -> Left (MsgError "org.freedesktop.DBus.Error.Failed"
(Just "No such member")
[])
Just m -> case runMethod m args of
Nothing ->
Left (MsgError "org.freedesktop.DBus.Error.InvalidArgs"
(Just "Argument type missmatch")
[])
Just ret -> Right ret