2021-11-27 01:02:22 -05:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- | Common internal DBus functions
|
|
|
|
|
2022-07-09 17:44:14 -04:00
|
|
|
module Data.Internal.DBus
|
|
|
|
( SafeClient(..)
|
|
|
|
, SysClient(..)
|
|
|
|
, SesClient(..)
|
|
|
|
, addMatchCallback
|
2021-11-27 01:02:22 -05:00
|
|
|
, matchProperty
|
2021-11-27 13:24:13 -05:00
|
|
|
, matchPropertyFull
|
2021-11-27 01:02:22 -05:00
|
|
|
, matchPropertyChanged
|
|
|
|
, SignalMatch(..)
|
|
|
|
, SignalCallback
|
|
|
|
, MethodBody
|
|
|
|
, withSignalMatch
|
|
|
|
, callPropertyGet
|
|
|
|
, callMethod
|
|
|
|
, callMethod'
|
2021-11-27 13:24:13 -05:00
|
|
|
, methodCallBus
|
2021-11-27 01:02:22 -05:00
|
|
|
, callGetManagedObjects
|
|
|
|
, ObjectTree
|
|
|
|
, getManagedObjects
|
|
|
|
, omInterface
|
|
|
|
, addInterfaceAddedListener
|
|
|
|
, addInterfaceRemovedListener
|
2021-11-27 13:24:13 -05:00
|
|
|
, fromSingletonVariant
|
|
|
|
, bodyToMaybe
|
2021-11-27 01:02:22 -05:00
|
|
|
) where
|
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
import RIO
|
|
|
|
import qualified RIO.Map as M
|
|
|
|
import qualified RIO.Text as T
|
2022-12-26 14:45:49 -05:00
|
|
|
|
2021-11-27 01:02:22 -05:00
|
|
|
import DBus
|
|
|
|
import DBus.Client
|
|
|
|
|
2022-07-09 17:44:14 -04:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- | Type-safe client
|
|
|
|
|
|
|
|
class SafeClient c where
|
|
|
|
toClient :: c -> Client
|
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
getDBusClient
|
|
|
|
:: (MonadUnliftIO m, MonadReader env m, HasLogFunc env)
|
|
|
|
=> m (Maybe c)
|
|
|
|
|
|
|
|
disconnectDBusClient
|
|
|
|
:: (MonadUnliftIO m)
|
|
|
|
=> c
|
|
|
|
-> m ()
|
|
|
|
disconnectDBusClient = liftIO . disconnect . toClient
|
|
|
|
|
|
|
|
withDBusClient
|
|
|
|
:: (MonadUnliftIO m, MonadReader env m, HasLogFunc env)
|
|
|
|
=> (c -> m a)
|
|
|
|
-> m (Maybe a)
|
|
|
|
-- TODO bracket
|
2022-07-09 17:44:14 -04:00
|
|
|
withDBusClient f = do
|
|
|
|
client <- getDBusClient
|
|
|
|
forM client $ \c -> do
|
|
|
|
r <- f c
|
2022-12-30 10:38:21 -05:00
|
|
|
liftIO $ disconnect (toClient c)
|
2022-07-09 17:44:14 -04:00
|
|
|
return r
|
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
withDBusClient_
|
|
|
|
:: (MonadUnliftIO m, MonadReader env m, HasLogFunc env)
|
|
|
|
=> (c -> m ())
|
|
|
|
-> m ()
|
2022-07-09 17:44:14 -04:00
|
|
|
withDBusClient_ = void . withDBusClient
|
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
fromDBusClient
|
|
|
|
:: (MonadUnliftIO m, MonadReader env m, HasLogFunc env)
|
|
|
|
=> (c -> a)
|
|
|
|
-> m (Maybe a)
|
2022-07-09 17:44:14 -04:00
|
|
|
fromDBusClient f = withDBusClient (return . f)
|
|
|
|
|
|
|
|
newtype SysClient = SysClient Client
|
|
|
|
|
|
|
|
instance SafeClient SysClient where
|
|
|
|
toClient (SysClient cl) = cl
|
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
getDBusClient = fmap SysClient <$> getDBusClient_ True
|
2022-07-09 17:44:14 -04:00
|
|
|
|
|
|
|
newtype SesClient = SesClient Client
|
|
|
|
|
|
|
|
instance SafeClient SesClient where
|
|
|
|
toClient (SesClient cl) = cl
|
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
getDBusClient = fmap SesClient <$> getDBusClient_ False
|
2022-07-09 17:44:14 -04:00
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
getDBusClient_
|
|
|
|
:: (MonadUnliftIO m, MonadReader env m, HasLogFunc env)
|
|
|
|
=> Bool
|
|
|
|
-> m (Maybe Client)
|
|
|
|
getDBusClient_ sys = do
|
|
|
|
res <- try $ liftIO $ if sys then connectSystem else connectSession
|
2022-07-09 17:44:14 -04:00
|
|
|
case res of
|
2022-12-30 10:38:21 -05:00
|
|
|
Left e -> do
|
|
|
|
logError $ Utf8Builder $ encodeUtf8Builder $ T.pack $ clientErrorMessage e
|
|
|
|
return Nothing
|
2022-07-09 17:44:14 -04:00
|
|
|
Right c -> return $ Just c
|
|
|
|
|
2021-11-27 01:02:22 -05:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- | Methods
|
|
|
|
|
2022-12-26 14:45:49 -05:00
|
|
|
type MethodBody = Either T.Text [Variant]
|
2021-11-27 01:02:22 -05:00
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
callMethod' :: (MonadIO m, SafeClient c) => c -> MethodCall -> m MethodBody
|
|
|
|
callMethod' cl =
|
|
|
|
liftIO
|
|
|
|
. fmap (bimap (T.pack . methodErrorMessage) methodReturnBody)
|
2022-07-09 17:44:14 -04:00
|
|
|
. call (toClient cl)
|
2021-11-27 01:02:22 -05:00
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
callMethod :: (MonadIO m, SafeClient c) => c -> BusName -> ObjectPath -> InterfaceName
|
|
|
|
-> MemberName -> m MethodBody
|
2021-11-27 13:24:13 -05:00
|
|
|
callMethod client bus path iface = callMethod' client . methodCallBus bus path iface
|
|
|
|
|
|
|
|
methodCallBus :: BusName -> ObjectPath -> InterfaceName -> MemberName -> MethodCall
|
|
|
|
methodCallBus b p i m = (methodCall p i m)
|
|
|
|
{ methodCallDestination = Just b }
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- | Bus names
|
|
|
|
|
|
|
|
dbusInterface :: InterfaceName
|
|
|
|
dbusInterface = interfaceName_ "org.freedesktop.DBus"
|
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
callGetNameOwner :: (MonadIO m, SafeClient c) => c -> BusName -> m (Maybe BusName)
|
2022-07-09 17:44:14 -04:00
|
|
|
callGetNameOwner cl name = bodyToMaybe <$> callMethod' cl mc
|
2021-11-27 13:24:13 -05:00
|
|
|
where
|
|
|
|
mc = (methodCallBus dbusName dbusPath dbusInterface mem)
|
|
|
|
{ methodCallBody = [toVariant name] }
|
|
|
|
mem = memberName_ "GetNameOwner"
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- | Variant parsing
|
|
|
|
|
|
|
|
fromSingletonVariant :: IsVariant a => [Variant] -> Maybe a
|
|
|
|
fromSingletonVariant = fromVariant <=< listToMaybe
|
|
|
|
|
|
|
|
bodyToMaybe :: IsVariant a => MethodBody -> Maybe a
|
|
|
|
bodyToMaybe = either (const Nothing) fromSingletonVariant
|
2021-11-27 01:02:22 -05:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- | Signals
|
|
|
|
|
|
|
|
type SignalCallback = [Variant] -> IO ()
|
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
addMatchCallback
|
|
|
|
:: (MonadIO m, SafeClient c)
|
|
|
|
=> MatchRule
|
|
|
|
-> SignalCallback
|
|
|
|
-> c
|
|
|
|
-> m SignalHandler
|
|
|
|
addMatchCallback rule cb cl =
|
|
|
|
liftIO $ addMatch (toClient cl) rule $ cb . signalBody
|
2021-11-27 01:02:22 -05:00
|
|
|
|
2021-11-27 13:24:13 -05:00
|
|
|
matchSignal :: Maybe BusName -> Maybe ObjectPath -> Maybe InterfaceName
|
|
|
|
-> Maybe MemberName -> MatchRule
|
|
|
|
matchSignal b p i m = matchAny
|
|
|
|
{ matchPath = p
|
|
|
|
, matchSender = b
|
|
|
|
, matchInterface = i
|
|
|
|
, matchMember = m
|
|
|
|
}
|
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
matchSignalFull
|
|
|
|
:: (MonadIO m, SafeClient c)
|
|
|
|
=> c
|
|
|
|
-> BusName
|
|
|
|
-> Maybe ObjectPath
|
|
|
|
-> Maybe InterfaceName
|
|
|
|
-> Maybe MemberName
|
|
|
|
-> m (Maybe MatchRule)
|
2021-11-27 13:24:13 -05:00
|
|
|
matchSignalFull client b p i m =
|
|
|
|
fmap (\o -> matchSignal (Just o) p i m) <$> callGetNameOwner client b
|
|
|
|
|
2021-11-27 01:02:22 -05:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- | Properties
|
|
|
|
|
|
|
|
propertyInterface :: InterfaceName
|
|
|
|
propertyInterface = interfaceName_ "org.freedesktop.DBus.Properties"
|
|
|
|
|
|
|
|
propertySignal :: MemberName
|
|
|
|
propertySignal = memberName_ "PropertiesChanged"
|
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
callPropertyGet :: (MonadIO m, SafeClient c) => BusName -> ObjectPath -> InterfaceName
|
|
|
|
-> MemberName -> c -> m [Variant]
|
|
|
|
callPropertyGet bus path iface property cl =
|
|
|
|
liftIO
|
|
|
|
$ fmap (either (const []) (:[]))
|
2022-07-09 17:44:14 -04:00
|
|
|
$ getProperty (toClient cl) $ methodCallBus bus path iface property
|
2021-11-27 01:02:22 -05:00
|
|
|
|
2021-11-27 13:24:13 -05:00
|
|
|
matchProperty :: Maybe BusName -> Maybe ObjectPath -> MatchRule
|
|
|
|
matchProperty b p =
|
|
|
|
matchSignal b p (Just propertyInterface) (Just propertySignal)
|
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
matchPropertyFull
|
|
|
|
:: (MonadIO m, SafeClient c)
|
|
|
|
=> c
|
|
|
|
-> BusName
|
|
|
|
-> Maybe ObjectPath
|
|
|
|
-> m (Maybe MatchRule)
|
2022-07-09 17:44:14 -04:00
|
|
|
matchPropertyFull cl b p =
|
|
|
|
matchSignalFull cl b p (Just propertyInterface) (Just propertySignal)
|
2021-11-27 01:02:22 -05:00
|
|
|
|
|
|
|
data SignalMatch a = Match a | NoMatch | Failure deriving (Eq, Show)
|
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
withSignalMatch :: Monad m => (Maybe a -> m ()) -> SignalMatch a -> m ()
|
2021-11-27 01:02:22 -05:00
|
|
|
withSignalMatch f (Match x) = f (Just x)
|
|
|
|
withSignalMatch f Failure = f Nothing
|
|
|
|
withSignalMatch _ NoMatch = return ()
|
|
|
|
|
2022-12-26 14:45:49 -05:00
|
|
|
matchPropertyChanged :: IsVariant a => InterfaceName -> T.Text -> [Variant]
|
2021-11-27 01:02:22 -05:00
|
|
|
-> SignalMatch a
|
|
|
|
matchPropertyChanged iface property [i, body, _] =
|
2022-12-26 14:45:49 -05:00
|
|
|
let i' = (fromVariant i :: Maybe T.Text)
|
2021-11-27 01:02:22 -05:00
|
|
|
b = toMap body in
|
|
|
|
case (i', b) of
|
2022-12-26 14:45:49 -05:00
|
|
|
(Just i'', Just b') -> if i'' == T.pack (formatInterfaceName iface) then
|
2021-11-27 01:02:22 -05:00
|
|
|
maybe NoMatch Match $ fromVariant =<< M.lookup property b'
|
|
|
|
else NoMatch
|
|
|
|
_ -> Failure
|
|
|
|
where
|
2022-12-26 14:45:49 -05:00
|
|
|
toMap v = fromVariant v :: Maybe (M.Map T.Text Variant)
|
2021-11-27 01:02:22 -05:00
|
|
|
matchPropertyChanged _ _ _ = Failure
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- | Object Manager
|
|
|
|
|
2022-12-26 14:45:49 -05:00
|
|
|
type ObjectTree = M.Map ObjectPath (M.Map T.Text (M.Map T.Text Variant))
|
2021-11-27 01:02:22 -05:00
|
|
|
|
|
|
|
omInterface :: InterfaceName
|
|
|
|
omInterface = interfaceName_ "org.freedesktop.DBus.ObjectManager"
|
|
|
|
|
|
|
|
getManagedObjects :: MemberName
|
|
|
|
getManagedObjects = memberName_ "GetManagedObjects"
|
|
|
|
|
|
|
|
omInterfacesAdded :: MemberName
|
|
|
|
omInterfacesAdded = memberName_ "InterfacesAdded"
|
|
|
|
|
|
|
|
omInterfacesRemoved :: MemberName
|
|
|
|
omInterfacesRemoved = memberName_ "InterfacesRemoved"
|
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
callGetManagedObjects
|
|
|
|
:: (MonadIO m, SafeClient c)
|
|
|
|
=> c
|
|
|
|
-> BusName
|
|
|
|
-> ObjectPath
|
|
|
|
-> m ObjectTree
|
2022-07-09 17:44:14 -04:00
|
|
|
callGetManagedObjects cl bus path =
|
2021-11-27 13:24:13 -05:00
|
|
|
either (const M.empty) (fromMaybe M.empty . fromSingletonVariant)
|
2022-07-09 17:44:14 -04:00
|
|
|
<$> callMethod cl bus path omInterface getManagedObjects
|
2021-11-27 01:02:22 -05:00
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
addInterfaceChangedListener
|
|
|
|
:: (MonadIO m, SafeClient c)
|
|
|
|
=> BusName
|
|
|
|
-> MemberName
|
|
|
|
-> ObjectPath
|
|
|
|
-> SignalCallback
|
|
|
|
-> c
|
|
|
|
-> m (Maybe SignalHandler)
|
2022-07-09 17:44:14 -04:00
|
|
|
addInterfaceChangedListener bus prop path sc cl = do
|
|
|
|
rule <- matchSignalFull cl bus (Just path) (Just omInterface) (Just prop)
|
|
|
|
forM rule $ \r -> addMatchCallback r sc cl
|
2021-11-27 13:24:13 -05:00
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
addInterfaceAddedListener
|
|
|
|
:: (MonadIO m, SafeClient c)
|
|
|
|
=> BusName
|
|
|
|
-> ObjectPath
|
|
|
|
-> SignalCallback
|
|
|
|
-> c
|
|
|
|
-> m (Maybe SignalHandler)
|
2021-11-27 13:24:13 -05:00
|
|
|
addInterfaceAddedListener bus =
|
|
|
|
addInterfaceChangedListener bus omInterfacesAdded
|
|
|
|
|
2022-12-30 10:38:21 -05:00
|
|
|
addInterfaceRemovedListener
|
|
|
|
:: (MonadIO m, SafeClient c)
|
|
|
|
=> BusName
|
|
|
|
-> ObjectPath
|
|
|
|
-> SignalCallback
|
|
|
|
-> c
|
|
|
|
-> m (Maybe SignalHandler)
|
2021-11-27 13:24:13 -05:00
|
|
|
addInterfaceRemovedListener bus =
|
|
|
|
addInterfaceChangedListener bus omInterfacesRemoved
|