开发者

How do I use Emacs's DBUS interface?

开发者 https://www.devze.com 2023-01-31 17:09 出处:网络
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 th开发者_如何学JAVAem.

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 th开发者_如何学JAVAem.

How do I use the dbus-call-method function?


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 ()
  "<node name='/'>
  <interface name='org.freedesktop.DBus.Introspectable'>
  <method name='Introspect'>
  <arg name='xml_data' type='s' direction='out'/>
  </method>
  </interface>
  <node name='helloworld'>
  </node>
  </node>")

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

(defun dbus-test-slash-helloworld-introspect ()
  "<node name='/helloworld'>
  <interface name='org.freedesktop.DBus.Introspectable'>
  <method name='Introspect'>
  <arg name='xml_data' type='s' direction='out'/>
  </method>
  </interface>
  <interface name='org.test.emacs'>
  <method name='hello'>
  <arg name='' direction='out' type='s' />
  </method>
  </interface>
  </node>")

(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 <interface name="org.test.emacs">...</interface> 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)


Google to the rescue... Follow the link for the example, it's not my code so I won't put it here.

http://emacs-fu.blogspot.com/2009/01/using-d-bus-example.html


Here is a safe way to test for dbus capabilities:

(defun dbus-capable ()
  "Check if dbus is available"
  (unwind-protect
      (let (retval)
        (condition-case ex
            (setq retval (dbus-ping :session "org.freedesktop.Notifications"))
          ('error
           (message (format "Error: %s - No dbus" ex))))
        retval)))

And here is a way to send a dbus notification:

(defun mbug-desktop-notification (summary body timeout icon)
  "call notification-daemon method METHOD with ARGS over dbus"
  (if (dbus-capable)
      (dbus-call-method
       :session                                 ; Session (not system) bus
       "org.freedesktop.Notifications"          ; Service name
       "/org/freedesktop/Notifications"         ; Service path
       "org.freedesktop.Notifications" "Notify" ; Method
       "emacs"
       0
       icon
       summary
       body
       '(:array)
       '(:array :signature "{sv}")
       ':int32 timeout)
    (message "Oh well, you're still notified")))


Or, just evaluate the following within Emacs:

(info "(dbus)")
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号