Reputation: 28490
In the doc for DBus
there's this example,
ping :: MethodCall -> IO Reply
ping _ = ReplyReturn []
sayHello :: String -> IO String
sayHello name = return ("Hello " ++ name ++ "!")
export client "/hello_world"
defaultInterface { interfaceName = "com.example.HelloWorld"
, interfaceMethods =
[ method "com.example.HelloWorld" "Ping" ping
, autoMethod "com.example.HelloWorld" "Hello" sayHello
]
}
which should help me understand how to implement a dbus-based server.
However, the example seems a bit outdated in several respects:
method
in that documentation (there's related Method
, makeMethod
, autoMethod
);ping
has an outdate signature, and maybe it should return DBusR Reply
instead of IO Reply
;autoMethod
is given 3 arguments but it should be given 2, and just removing "Hello"
makes it typecheck, but... I've read AutoMethod
's doc, but I don't really understand that.But most importantly the example is incomplete. How do I make use of it?
The doc for export
reads
Export the given
Interface
at the givenObjectPath
makes me think all I have to do is to put the last piece of the snippet (the call to export
) in a main :: IO ()
function, and then keep it alive with _ <- getChar
or something before return ()
, but I still have no clue how to... have those functions sayHello
and ping
be called. Looking at this answer I kinda feel that I should use dbus-send
, but with whatever Haskell nonsense I've managed to compile based on the above code snippet, if I run
dbus-send --session --print-reply --dest="com.example.HelloWorld" /hello_world com.example.HelloWorld.Ping
I get
Error org.freedesktop.DBus.Error.ServiceUnknown: The name is not activatable
which, given my knowledge at the present time, can be because of some unrelated-to-Haskell set-up I supposed to do, as well as because I wrote Haskell nonsense.
Anyway, here's some code I managed to compile, but that doesn't actually do anything:
{-# LANGUAGE OverloadedStrings #-}
import DBus
import DBus.Client
ping :: MethodCall -> IO Reply
ping _ = return (ReplyReturn [])
sayHello :: String -> IO String
sayHello name = return ("Hello " ++ name ++ "!")
main :: IO ()
main = do
client <- connectSession
export client "/hello_world"
defaultInterface { interfaceName = "com.example.HelloWorld"
, interfaceMethods =
[
autoMethod "com.example.HelloWorld" sayHello
]
}
_ <- getLine
return ()
Upvotes: 1
Views: 505
Reputation: 51109
That example is so outdated, it looks like it predates dbus-0.10
, the earliest version of the package that's actually available on Hackage.
A self-contained rewrite that works in dbus-1.3.2 is as follows:
{-# LANGUAGE OverloadedStrings #-}
module Main (main) where
import DBus
import DBus.Client
import Control.Concurrent
import Control.Monad.IO.Class
import Control.Monad
ping :: MethodCall -> IO Reply
ping _ = pure $ ReplyReturn []
sayHello :: String -> IO String
sayHello name = return ("Hello " ++ name ++ "!")
main :: IO ()
main = do
client <- connectSession
requestResult <- requestName client "com.example.HelloWorld" []
when (requestResult /= NamePrimaryOwner) $ do
error "Service already in use"
export client "/hello_world"
defaultInterface { interfaceName = "com.example.HelloWorld"
, interfaceMethods =
[ makeMethod "Ping"
(signature_ [TypeString])
(signature_ [TypeString])
(liftIO . ping)
, autoMethod "Hello" sayHello
]
}
-- wait forever for calls
forever (threadDelay 1000000)
It will respond to requests as long as its running.
$ dbus-send --session --print-reply --dest="com.example.HelloWorld" \
> /hello_world com.example.HelloWorld.Ping string:'example.com'
method return time=1708979166.466824 sender=:1.118 -> destination=:1.119 serial=3 reply_serial=2
$ dbus-send --session --print-reply --dest="com.example.HelloWorld" \
> /hello_world com.example.HelloWorld.Hello string:'world'
method return time=1708979185.572916 sender=:1.118 -> destination=:1.120 serial=4 reply_serial=2
string "Hello world!"
Be warned that if you try to test this in GHCi, even after main
has exited or been interrupted with Ctrl-C, the child thread servicing requests will continue to run in the background, so if you rerun the server from the same GHCi session, you'll probably get an error:
*** Exception: Service already in use
and need to restart GHCi.
Upvotes: 3
Reputation: 16562
Error org.freedesktop.DBus.Error.ServiceUnknown: The name is not activatable
The error message is talking about bus names, which are basically the "addresses" of each service (or even each client) on the bus. Most importantly, it means that the requested --dest=
name is currently not present on the bus.
(Alongside that, the error message also means dbus-daemon doesn't know how to start your service on-demand, but you can safely ignore that – "activation" is just the auto-start mechanism and is not relevant when you're manually starting your service.)
Bus names are not set through configuration (that's only for auto-start); they're "claimed" by the service using a special bus call, every time it starts up and connects to the bus. Your process needs to call requestName
(docs) to make that happen.
Examples (I don't know enough Haskell to make much sense of them):
(The interface name is not that – interfaces are just something your own code uses to group or distinguish methods, whereas the service name (aka bus name, aka unique name) is the actual "D-Bus address" of your whole process. For a simple 1-object, 1-interface service they're very likely to be identical, but if you browse through d-spy
you'll see many examples where a service makes many objects available, and which have multiple interfaces, all at a single bus name.)
Ideally, the bus name should be claimed after the initial objects have been set up and exported, to prevent cases where calls to your service fail with "no such object" or similar because it just happened to arrive a moment too early.
Upvotes: 2