How do I use Emacs's DBUS interface?

后端 未结 4 1532
Happy的楠姐
Happy的楠姐 2021-02-07 20:17

I looked up the dbus package and it seems like all of the functions are built-in to the C source code and there\'s no documentation for them.

How do I use the dbus

4条回答
  •  独厮守ぢ
    2021-02-07 21:00

    I just had the same problem and found the emacs-fu article that comes up when googling a little too basic for my needs.

    In particular I wanted to export my own elisp methods via dbus, and had problems making sense of the dbus terminology and how it applies to the emacs dbus interface.

    First thing to check out, the emacs documentation, C-h f dbus-register-method

    dbus-register-method is a built-in function in `C source code'.
    
    (dbus-register-method BUS SERVICE PATH INTERFACE METHOD HANDLER)
    
    Register for method METHOD on the D-Bus BUS.
    
    BUS is either the symbol `:system' or the symbol `:session'.
    
    SERVICE is the D-Bus service name of the D-Bus object METHOD is
    registered for.  It must be a known name.
    
    PATH is the D-Bus object path SERVICE is registered.  INTERFACE is the
    interface offered by SERVICE.  It must provide METHOD.  HANDLER is a
    Lisp function to be called when a method call is received.  It must
    accept the input arguments of METHOD.  The return value of HANDLER is
    used for composing the returning D-Bus message.
    

    BUS is just going to be :session or :system (where you probably almost always want to use :session like a desktop application I suppose).

    SERVICE is a unique name for the application on the bus, like an address or domain name. Dbus.el defines dbus-service-emacs as "org.gnu.Emacs".

    PATH is to different types of application functionality what SERVICE is to different applications itself. For example a certain emacs module might expose functionality in the /ModuleName PATH under the org.gnu.Emacs SERVICE.

    INTERFACE is just like an interface in programming. It is a specification that tells other dbus clients how to communicate with the object(s) your application exposes. It contains for example type signatures for your methods. So you might have an interface that says something like: under the service org.gnu.Emacs, in the path /ModuleName, you will find a method named helloworld that will take zero arguments and return a string.

    The difficult thing to figure out for me was: how do I define an interface for my method?

    Poking around dbus.el you'll find that there is dbus-interface-introspectable (among others) defined, that just contains a string "org.freedesktop.DBus.Introspectable", which names a standard interface that just exposes one method:

    org.freedesktop.DBus.Introspectable.Introspect (out STRING xml_data)
    

    (link to the spec http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-introspectable)

    And that is the method which is called by clients to find out about what applications expose on the dbus. So we can use that method to look at how other applications advertise their stuff on dbus, and then we can implement our own Introspect method just mimicking what the others are doing and everything will be fine.

    Note however that the spec says that applications may implement the Introspectable interface, they don't have to. In fact you can call dbus-register-method just fine with an empty string as interface (anything will do it seems). You will be able to call your method. However I got always NoReply errors and problems with applications hanging waiting for a response from dbus which went away when I figured out how to make my stuff introspectable. So I assume that Introspect() is expected quite often.

    So lets do this:

    (defun say-world ()
      ;; you need to map between dbus and emacs datatypes, that's what :string is for
      ;; if you're returning just one value that should work automatically, otherwise
      ;; you're expected to put your return values in a list like I am doing here
      (list :string "world"))
    
    (dbus-register-method
      :session
      "org.test.emacs"
      "/helloworld"
      "org.test.emacs"
      "hello"
      'say-world)
    

    That is what we want to implement and therefore want to define an interface for (named "org.test.emacs"). You can use it just like that and try to call the hello method with qdbus org.test.emacs /helloworld org.test.emacs.hello. It should work, for me it works only after 20 seconds of waiting (making the application hang), but it works.

    Now lets make it introspectable:

    (defun dbus-test-slash-introspect ()
      "
      
      
      
      
      
      
      
      ")
    
    (dbus-register-method
      :session
      "org.test.emacs"
      "/"
      dbus-interface-introspectable
      "Introspect"
      'dbus-test-slash-introspect)
    
    (defun dbus-test-slash-helloworld-introspect ()
      "
      
      
      
      
      
      
      
      
      
      
      ")
    
    (dbus-register-method
      :session
      "org.test.emacs"
      "/helloworld"
      dbus-interface-introspectable
      "Introspect"
      'dbus-test-slash-helloworld-introspect)
    

    There we go. We just define two Introspect methods (one for each level of our path hierachy) and return some hand written xml telling other applications about the /helloworld path and the hello method within it. Note that dbus-test-slash-helloworld-introspect contains ... that has a type signature for our method, that is, as far as I am concerned, the definition of the interface we used when we registered our method with dbus.

    Evaluate all that and poke around with qdbus:

    ~> qdbus org.test.emacs
    /
    /helloworld
    
    ~> qdbus org.test.emacs /
    method QString org.freedesktop.DBus.Introspectable.Introspect()
    
    ~> qdbus org.test.emacs /helloworld
    method QString org.freedesktop.DBus.Introspectable.Introspect()
    method QString org.test.emacs.helloworld()
    
    ~> qdbus org.test.emacs /helloworld org.test.emacs.hello
    world
    

    Hooray, works as expected, no hanging or NoReply errors.

    One last thing, you might try to test your method like so:

    (dbus-call-method :session "org.test.emacs" "/helloworld" "org.test.emacs" "hello" :timeout 1000)
    

    and find that it just timeouts and wonder why. Thats because if you register and call a method from within the same emacs instance then emacs will wait for itself to answer. There is no fancy threading going on, you will always get a NoReply answer in that situation.

    If you have to call and register a method within the same emacs instance you can use dbus-call-method-asynchronously like so:

    (defun handle-hello (hello)
      (print hello))
    
    (dbus-call-method-asynchronously :session "org.test.emacs" "/helloworld" "org.test.emacs" "hello" 'handle-hello)
    

提交回复
热议问题