TUTORIAL.txt

Path: TUTORIAL.txt
Last Update: Thu Mar 29 17:34:10 CEST 2007

TUTORIAL

Kristoffer Lundén <kristoffer.lunden@gmail.com>

This is a work in progress. Please also see the examples for more info on how to use this library.

Definitions

D-Bus
is the protocol, see dbus.freedesktop.org
R-Bus
is a native implementation of the D-Bus protocol, written in Ruby.

Connecting to a Bus

To communicate via D-Bus, you need to connect to a bus daemon, that will forward the messages between connected application. You do this in R-Bus by creating an instance of RBus::Bus, which represents this connection.

On most systems, you will have a Session Bus, that is per login, and a System Bus, that is global for the machine. For special needs and testing, you may also want to connect to a non-standard bus.

The Session and System Bus objects are Singleton objects, meaning that you always get the same object and connection even if you get it several times. Apart from this, you can communicate with as many buses as you wish in the same application.

There are three convenience methods available for making these connections:

Connecting to the Session Bus

  require 'rbus'
  bus = RBus.session_bus

This connects to the Session Bus, or returns the already active connection.

Connecting to the System Bus

  require 'rbus'
  bus = RBus.system_bus

This connects to the System Bus, or returns the already active connection.

Connecting to a specified address

  require 'rbus'
  tcp_bus = RBus.get_bus('tcp:host=192.168.0.1,port=6666')
  unix_bus = RBus.get_bus('unix:path=/tmp/dbus-test')
  abstract_bus = RBus.get_bus('unix:abstract=/tmp/dbus-lafi7lJhll,guid=a190e4459a37da0c62586ac4de5fa400')

This connects to the given address, which is given in the same form as reported by the Bus Daemon.

Remote objects

D-Bus applications can export objects that other applications can call methods on, and receive signals from. A remote object has a well-known name, which represents the exporting application, and a object path, which represents the object itself.

Examples of well-known names are org.freedesktop.NetworkManager and org.gnome.Rhythmbox. Examples of object paths are /org/freedesktop/NetworkManager/Devices/eth0 and /org/gnome/Rhythmbox/Player.

Later in this document we will see how to export our own objects, but first we will look at interacting with other objects.

Proxy objects

To communicate with the remote object, we need a local representation of it, a Proxy object, that we can call methods on and receive signals from. We can obtain a Proxy from the Bus via the method get_object, which takes a well-known name and an object path as describe above.

  # get the Player object from Rhythmbox:
  rb_player = session_bus.get_object('org.gnome.Rhythmbox', '/org/gnome/Rhythmbox/Player')

Method calls

To make method calls on the remote object, we simply call the exact same method on the Proxy object. Any method not defined in the Proxy class will be forwarded to the other side of the wire. To avoid any collisions with built-in methods, the Proxy class is almost completely emptied. Also, to avoid almost any possible clash, the few methods that operate on Proxy objects end in ’!’, such as method! and interface!. It may not be not 100% idiomatic Ruby, so better suggestions are welcome. :)

Some examples of method calls:

  # get the uri of the playing song
  uri = rb_player.getPlayingUri()

  # get information on a HAL device
  info = device.GetProperty('info.product')

  # pop up a notification bubble
  notify.Notify('R-Bus',0,'info','R-Bus Notification',
                'A test example to see that everything works.',[],{},-1)

Signatures, introspection and data types

D-Bus defines a number of standard data types that can be used to send and receive data between applications. Each method and signal has a signature that shows how many arguments and of what types it expects and/or returns. A few examples of signatures:

s
a String
ui
Uint32, Int32
as
Array of Strings
a{sas}
Dict, or Hash in Ruby, with String keys and Arrays of Strings as values.

R-Bus is able to use Rubys standard types without any conversion for the most part, as long as there is a known signature available for the conversion. This signature can be obtained in a number of ways, and the library tries really hard to find one:

When a new Proxy object is created, R-Bus will ask the remote application for a list of methods and signatures via the standard method Introspect. If the application provides such a list (and most do), everything should just work. :)

If the introspection fails, the library tries to guess the signature from the arguments the method was called with. It will handle most types except integers automatically, as long as the structures aren’t too complex. Currently, these Ruby classes are properly handled:

  • String - D-Bus string
  • Symbol - D-Bus string
  • TrueClass and FalseClass - D-Bus boolean
  • Float - D-Bus double
  • Hash - D-Bus array of dicts
    • can not be empty, needs at least one key/value pair
  • Array
    • D-Bus array if all values have the same signature (symbol and string is considered to be the same)
    • D-Bus struct if there are mixed values
    • can’t handle array of containers

So, a call like:

  object.Method({:key => ['value'], 'key2' => ['value']})

will result in the signature a{sas}. And so on.

Also, you can manually give any arguments in the same form as rbus-send (and dbus-send) accepts. This is the value, as a string, prefixed with the name of the type and a colon:

  string:'some string'
  uint32:42
  int16:-25
  array:string:'foo','bar'
  dict:string:uint32:'foo',32,'bar',64

See the section on rbus-send at the bottom of this document for the specification of the format and some examples.

If all this fails, finally you can add the method manually with method!:

  obj.method!(:MethodName, 'as')

This would define a method MethodName that takes an Array of Strings.

Interfaces

D-Bus uses interfaces to provide namespaces for methods. An interface groups methods and signals under a common name, which is a dot-separated name starting with a reversed domain name. They are in many cases optional, only if an object have multiple methods with the same name, an interface is actually needed.

You can use the interface! method to set an interface on an object.

Some examples:

  rb_player.interface!('org.gnome.Rhythmbox.Player')
  puts rb_player.getPlayingUri()

  eth0.interface!('org.freedesktop.NetworkManager.Devices')
  eth0.getProperties()

interface! also returns the object itself, so commands can be chained:

  rb_player.interface!('org.gnome.Rhythmbox.Player').getPlayingUri()

If you want to set an interface temporarily, you can use a block, which will get a copy of the object with a temporary interface:

  rb_player.interface!('org.gnome.Rhythmbox.SomethingElse')
  rb_player.interface!('org.gnome.Rhythmbox.Player') do |p|
    p.getPlayingUri()
  end
  # ...here the interface is +SomethingElse+ again.

Asynchronous method calls

To make an asynchronous call, simply attach a block to the method call instead of waiting for a reply.

  # from async_rb_loop.rb / glib_async_rb_loop.rb
  10.times do |i|
    rb.getPlayingUri() do |uri|
      sleep(rand(2))
      puts "#{i}: #{uri}"
    end
  end

With the default version of R-Bus, that uses threads, the above code will return a list of uris in random order.

With the GLib version, the mainloop needs to be started (see Event loop) and the calls will be returned in the order they were queued. You can use the threaded version with GLib/Gtk+ applications if you need the extra flexibility though, however, mostly you don’t. :)

Receiving signals

Remote objects can notify listeners of events via signals. To listen to a signal on the remote object, call the connect! method with a block to process the signal:

  # from rhythmbox_signal_print_playing.rb:
  # get notifications from Rhythmbox when the song changes
  rb_player.connect!(:playingUriChanged) { |uri|
    properties = rb_shell.getSongProperties(uri)
    duration = Time.at(properties['duration'].to_i).strftime("%M:%S")
    artist = properties['artist']
    title = properties['title']
    puts "Now playing: #{artist} - #{title} (#{duration})"
    printf "File-size: %.2f MB\n", properties['file-size'].to_f/1024/1024
  }

  RBus.mainloop

The block will receive whatever data the signal sends.

connect! takes the name of the signal, and an optional Hash of extra match rules. See Match rules in the D-Bus specification for a list of rules.

  obj.connect!(:SignalName, {:arg0 => 'Foo', :arg1 => 'Bar'}) do |*args|
    # ...
  end

To receive signals, you need a mainloop. See the next section for a few more details.

Event loop

To be able to receive signals and answers to asynchronous calls, an event loop needs to be running and listening for them. In the dfault version, using require ‘rbus’, such a loop is always started automatically and in most cases nothing more neds to be done. However, a word of caution: as it uses threads you need to be wary of the same kind of concurrency issues as with any Ruby threads.

With the GLib loop, used with require ‘rbus/glib‘, you need to start the loop. You can either use RBus.mainloop or Gtk.main. The former simply calls the latter, but you can use it for compatibility with other R-Bus mainloops.

You can use RBus.mainloop in the threaded version as well to just sit idle and wait for signals and method replies.

Exporting objects

This section has yet to be written, and the functionality is currently missing.

Starting Services

  require 'rbus'
  session_bus = RBus.session_bus()

  # Start Rhythmbox
  session_bus.StartServiceByName('org.gnome.Rhythmbox', 0)

Extra utilities

rbus-send

R-Bus comes with its own variant of dbus-send, called rbus-send, which should be pretty much compatible, except for:

  • There is no —reply-timeout. Trivial to fix in seconds, though milli-seconds may not be. Will be added.
  • Currently dumps the any server answer in YAML instead of the dbus-send format.
  • Does not demand that an interface be specified, meaning you can call, as an example, the method Introspect instead of the qualified org.freedesktop.DBus.Introspectable.Introspect, as long as the method is unique in the object. As the specification clearly allows this for any client, I consider this a feature.

The invokation looks like this:

  rbus-send <destination object path> <message name> [options] [arguments ...]
  --dest=NAME                  Specify the name of the connection to receive the message.
  --print-reply                Block for a reply to the message sent, and print any reply received.
  --system                     Send to the system message bus.
  --session                    Send to the session message bus. (This is the default.)
  --type=TYPE                  Specify "method_call" or "signal" (defaults to "signal").

The object path and the name of the message to send must always be specified. Any following non-options are message arguments. These are given as type-specified values and may include containers (arrays, dicts, and variants) as follows:

  <arguments>  ::= <item> | <container> [ <item> | <container>...]
  <item>       ::= <type>:<value>
  <container>  ::= <array> | <dict> | <variant>
  <array>      ::= array:<type>:<value>[,<value>...]
  <dict>       ::= dict:<type>:<type>:<key>,<value>[,<key>,<value>...]
  <variant>    ::= variant:<type>:<value>
  <type>       ::= string | int16 | uint16 | int32 | uint32 | int64 | uint64 | double | byte | boolean | objpath

Some sample queries:

  # List all HAL properties:
  $ rbus-send --system --print-reply --dest=org.freedesktop.Hal \
      /org/freedesktop/Hal/devices/computer \
      org.freedesktop.Hal.Device.GetAllProperties

  # Print URI of current song in Rhythmbox:
  $ rbus-send --dest='org.gnome.Rhythmbox' --print-reply \
      /org/gnome/Rhythmbox/Player org.gnome.Rhythmbox.Player.getPlayingUri

  # Toggle play/pause in Rhytmbox:
  $ rbus-send --dest='org.gnome.Rhythmbox' /org/gnome/Rhythmbox/Player \
      org.gnome.Rhythmbox.Player.playPause boolean:true

  # Find services possible to start on the Bus:
  $ rbus-send --print-reply --dest=org.freedesktop.DBus \
      /org/freedesktop/DBus org.freedesktop.DBus.ListActivatableNames

  # Start such a service (Rhythmbox in this case):
  $ rbus-send --type=method_call --dest=org.freedesktop.DBus \
      /org/freedesktop/DBus org.freedesktop.DBus.StartServiceByName \
      string:'org.gnome.Rhythmbox' uint32:0

  # Pop up a notification bubble:
  $ rbus-send --dest=org.freedesktop.Notifications \
      /org/freedesktop/Notifications org.freedesktop.Notifications.Notify \
      string:'R-Bus' uint32:0 string:'info' string:'R-Bus Notification' \
      string:'A test example to see that everything works.' \
      array:string: dict:string:variant: int32:-1

If you have not installed the library, you can use ruby -Ilib bin/rbus-send [invocation] from the root of the project to try it out.

[Validate]