{-# LANGUAGE OverloadedStrings, RecordWildCards #-} module Network.Avahi.Browse (browse, dispatch ) where import Control.Monad import Control.Concurrent import Data.Text (Text) import Data.Word import Data.Int import Data.Char import DBus.Client as C import DBus.Internal.Types import DBus.Internal.Message import Network.Avahi.Common import Data.ByteString (ByteString) import Data.Text.Encoding (decodeUtf8) import qualified Data.Text as Text listenAvahi :: Maybe BusName -> Maybe MemberName -> C.MatchRule listenAvahi name member = matchAny { matchSender = name, matchMember = member } -- | Browse for specified service browse :: BrowseQuery -> IO () browse (BrowseQuery {..}) = do client <- connectSystem -- We have to set up callback for ItemNew signal before we actually create a browser. -- Otherwise, the signal can arrive sooner then we managed to set up a callback for it. -- See also https://github.com/cocagne/txdbus/issues/8, https://github.com/lathiat/avahi/issues/9 addMatch client (listenAvahi Nothing Nothing) (handler client lookupCallback) [sb] <- call' client "/" serverInterface "ServiceBrowserNew" [iface_unspec, proto2variant lookupProtocol, toVariant lookupServiceName, toVariant lookupDomain, flags_empty ] -- print sb addMatch client (listenAvahi (Just serviceResolver) (Just "Found")) (handler client lookupCallback) return () -- | Dispatch signal and call corresponding function. dispatch :: [(String, Signal -> IO b)] -> Signal -> IO () dispatch pairs signal = do let signame = signalMember signal -- putStrLn $ "signame: " ++ show signame ++ ", sender: " ++ show (signalSender signal) let good = [callback | (name, callback) <- pairs, memberName_ name == signame] forM_ good $ \callback -> callback signal handler :: Client -> (Service -> IO ()) -> Signal -> IO () handler client callback signal = do dispatch [("ItemNew", on_new_item client), ("Found", on_service_found callback) ] signal on_new_item :: Client -> Signal -> IO () on_new_item client signal = do let body = signalBody signal [iface,proto,name,stype,domain,flags] = body call' client "/" serverInterface "ServiceResolverNew" [iface, proto, name, stype, domain, proto2variant PROTO_UNSPEC, flags_empty ] return () on_service_found :: (Service -> IO ()) -> Signal -> IO () on_service_found callback signal = do let body = signalBody signal [iface, proto, name, stype, domain, host, aproto, addr, port, text, flags] = body service = Service { serviceProtocol = variant2proto proto, serviceName = fromVariant_ "service name" name, serviceType = fromVariant_ "service type" stype, serviceDomain = fromVariant_ "domain" domain, serviceHost = fromVariant_ "service host" host, serviceAddress = fromVariant addr, servicePort = fromVariant_ "service port" port, serviceText = maybe "" toString (fromVariant text :: Maybe [ByteString]) } callback service toString :: [ByteString] -> String toString = Text.unpack . Text.concat . fmap (decodeUtf8)