File: Tutorial.md

package info (click to toggle)
ruby-dbus 0.16.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 520 kB
  • sloc: ruby: 3,786; sh: 53; makefile: 8
file content (480 lines) | stat: -rw-r--r-- 17,280 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
<style>
code { background-color: #F0E7E7; }
pre code { background-color: #F0DDDD; }
pre {
     font-size: 90%;
     overflow: hidden;
     padding-left: 10pt;
     border: thin solid #F0B4B4;
     background-color: #F0DDDD;
}
</style>

Welcome
=======

This is the Ruby D-Bus tutorial.  It aims to show you the features of Ruby
D-Bus and as you read through the tutorial also how to use them.

&copy; Arnaud Cornet and Paul van Tilburg; this tutorial is part of
free software; you can redistribute it and/or modify it under the
terms of the [GNU Lesser General Public License,
version 2.1](http://www.gnu.org/licenses/lgpl.html) as published by the 
[Free Software Foundation](http://www.fsf.org/).

Introduction
============

This is a tutorial for Ruby D-Bus, a library to access D-Bus facilities of your
system.

What is D-Bus?
--------------

D-Bus is an RPC(Remote Procedure Call) protocol.  A common setup can have
multiple D-Bus daemons running that route procedure calls and signals in
the form of messages.  Each of these daemons supports a bus.  A bus that
is often used by modern desktop environments, and is available per session, is
called the _session bus_.  Another bus that can be available, but in a
system-wide manner, is called the _system bus_.  It is used for example by
the [Hardware Abstraction Layer](http://hal.freedesktop.org/) daemon.  Note
that theoretically the D-Bus RPC protocol can be used without a system or
session bus.  I never came across any actual use of this though.

At the desktop level, D-Bus allows some components to interact.  Typically
if you are writing an application or a personal script that wants to
interact with your web browser, your music player, or that simply wants to
pop-up a desktop notification, D-Bus comes into play.

At the system level, the Hardware Abstraction Layer is a privileged daemon
that notifies other software of hardware activities.  Typically, if you
want to be notified if a CD-ROM has been loaded in, of if you want to
explore hardware, the system daemon comes into play.

The D-Bus RPC system is as we will see _object oriented_.

Buses provide access to _services_ provided in turn by running or ready to
run processes.  Let me introduce some D-Bus terminology before we discuss
the API of Ruby D-Bus.

Client
------

A D-Bus client is a process that connects to a D-Bus. They issue method
calls and register to the bus for signals and events.

Service
-------

A connected client can export some of its objects and let other clients
call some of its methods.  Such clients typically register a special name
like `org.freedesktop.Notifications`, the service name.

There is slightly different type of service.  They are provided by
processes that can be launched by a D-Bus daemon on demand.  Once they are
started by D-Bus they register a service name and behave like another
client.

Note that the buses themselves provide the `org.freedesktop.DBus` service,
and provide some features through it.

Object path
-----------

An object path is the D-Bus way to specify an object _instance_ address.  A
service can provide different object instances to the outside world, so
that external processes can call methods on each of them.  An object path
is an address of an instance in a very similar way that the path is an
address of a file on a file system.  For example: 
`/org/freedesktop/Notification` is an object path of an object provided by
the `org.freedesktop.Notification` service

**Beware**:  service names and object paths can, but do _not_ have to be
related!  You'll probably encounter a lot of cases though, where the
object path is a slashed version of the dotted service name.

Interface
---------

Classically in an object model, classes can implement interfaces. That is,
some method definitions grouped in an interface. This is exactly what a
D-Bus interface is as well. In D-Bus interfaces have names. These names must be
specified on method calls.

The `org.freedesktop.Notification` service provides an object instance
called `/org/freedesktop/Notification`.  This instance object implements an
interface called `org.freedesktop.Notifications`.  It also provides two
special D-Bus specific interfaces:  `org.freedesktop.DBus.Introspect` and
`org.freedesktop.DBus.Properties`. Again, object paths, service names,
and interface names can be related but do not have to be.

Basically the `org.freedesktop.DBus.Introspect` has an `Introspect` method,
that returns XML data describing the `/org/freedesktop/Notification` object
interfaces. This is used heavily internally by Ruby D-Bus.

Method
------

A method is, well, a method in the classical meaning. It's a function that
is called in the context of an object instance. Methods have typed
parameters and return typed return values.

Signal
------

Signals are simplified method calls that do not have a return value. They
do have typed parameters though.

Message
-------

Method calls, method returns, signals, errors: all are encoded as D-Bus
messages sent over a bus. They are made of a packet header with source and
destination address, a type (method call, method reply, signal) and the
body containing the parameters (for signals and method calls) or the return
values (for a method return message).

Signature
---------

Because D-Bus is typed and dynamic, each message comes with a signature that
describes the types of the data that is contained within the message.  The
signature is a string with an extremely basic language that only describes
a data type.  You will need to have some knowledge of what a signature
looks like if you are setting up a service.  If you are just programming a
D-Bus client, you can live without knowing about them.

Client Usage
============

This chapter discusses basic client usage
and has the following topics:

Using the library
-----------------

If you want to use the library, you have to make Ruby load it by issuing:

    require 'dbus'

That's all!  Now we can move on to really using it...

Connecting to a bus
-------------------

On a typical system, two buses are running, the system bus and the session
bus.  The system bus can be accessed by:

    bus = DBus::SystemBus.instance

Probably you already have guessed how to access the session bus. This
can be done by:

    bus = DBus::SessionBus.instance

Performing method calls
-----------------------

Let me continue this example using the session bus.  Let's say that I want
to access an object of some client on the session bus.  This particular
D-Bus client provides a service called `org.gnome.Rhythmbox`.  Let me
access this service:

    rb_service = bus.service("org.gnome.Rhythmbox")

In this example I access the `org.gnome.Rhythmbox` service, which is
provided by the application
[Rhythmbox](http://www.gnome.org/projects/rhythmbox/).
OK, I have a service handle now, and I know that it exports the object
"/org/gnome/Rhythmbox/Player".  I will trivially access this remote object
using:

    rb_player = rb_service.object("/org/gnome/Rhythmbox/Player")

Introspection
-------------

Well, that was easy.  Let's say that I know that this particular object is
introspectable.  In real life most of them are.  The `rb_object` object we
have here is just a handle of a remote object, in general they are called
_proxy objects_, because they are the local handle of a remote object.  It
would be nice to be able to make it have methods, and that its methods send
a D-Bus call to remotely execute the actual method in another process. 
Well, instating these methods for a _introspectable_ object is trivial:

    rb_player.introspect

And there you go.  Note that not all services or objects can be
introspected, therefore you have to do this manually!  Let me remind you
that objects in D-Bus have interfaces and interfaces have methods.  Let's
now access these methods:

    rb_player_iface = rb_player["org.gnome.Rhythmbox.Player"]
    puts rb_player_iface.getPlayingUri

As you can see, when you want to call a method on an instance object, you have
to get the correct interface. It is a bit tedious, so we have the following
shortcut that does the same thing as before:

    rb_player.default_iface = "org.gnome.Rhythmbox.Player"
    puts rb_player.getPlayingUri

The `default_iface=` call specifies the default interface that should be
used when non existing methods are called directly on a proxy object, and
not on one of its interfaces.

Note that the bus itself has a corresponding introspectable object. You can
access it with `bus.proxy` method. For example, you can retrieve an array of
exported service names of a bus like this:

    bus.proxy.ListNames[0]

Properties
----------

Some D-Bus objects provide access to properties. They are accessed by
treating a proxy interface as a hash:

    nm_iface = network_manager_object["org.freedesktop.NetworkManager"]
    enabled = nm_iface["WirelessEnabled"]
    puts "Wireless is " + (enabled ? "enabled":"disabled")
    puts "Toggling wireless"
    nm_iface["WirelessEnabled"] = ! enabled


Calling a method asynchronously
-------------------------------

D-Bus is _asynchronous_.  This means that you do not have to wait for a
reply when you send a message.  When you call a remote method that takes a
lot of time to process remotely, you don't want your application to hang,
right?  Well the asychronousness exists for this reason.  What if you dont'
want to wait for the return value of a method, but still you want to take
some action when you receive it?

There is a classical method to program this event-driven mechanism.  You do
some computation, perform some method call, and at the same time you setup
a callback that will be triggered once you receive a reply.  Then you run a
main loop that is responsible to call the callbacks properly.  Here is how
you do it:

    rb_player.getPlayingUri do |resp|
    	puts "The playing URI is #{resp}"
    end
    puts "See, I'm not waiting!"
    loop = DBus::Main.new
    loop << bus
    loop.run

This code will print the following:

    See, I'm not waiting!
    The playing URI is file:///music/papapingoin.mp3

Waiting for a signal
--------------------

Signals are calls from the remote object to your program.  As a client, you
set yourself up to receive a signal and handle it with a callback.  Then running
the main loop triggers the callback.  You can register a callback handler
as allows:

    rb_player.on_signal("elapsedChanged") do |u|
    	puts u
    end

More about introspection
------------------------

There are various ways to inspect a remote service.  You can simply call
`Introspect()` and read the XML output.  However, in this tutorial I assume
that you want to do it using the Ruby D-Bus API.

Notice that you can introspect a service, and not only objects:

    rb_service = bus.service("org.gnome.Rhythmbox")
    rb_service.introspect
    p rb_service.root

This dumps a tree-like structure that represents multiple object paths.  In
this particular case the output is:

    </: {org => {gnome => {Rhythmbox => {Player => ..fdbe625de {},Shell => ..fdbe6852e {},PlaylistManager => ..fdbe4e340 {}}>

Read this left to right:  the root node is "/", it has one child node "org",
"org" has one child node "gnome", and "gnome" has one child node "Rhythmbox". 
Rhythmbox has Tree child nodes "Player", "Shell" and "PlaylistManager". 
These three last child nodes have a weird digit that means it has an object
instance.  Such object instances are already introspected.

If the prose wasn't clear, maybe the following ASCII art will help you:

    /
    	org
    		gnome
    			Rhythmbox
    				Shell (with object)
    				Player (with object)
    				PlaylistManager (with object)

### Walking the object tree

You can have an object on any node, i.e. it is not limited to leaves.
You can access a specific node like this:

    rb_player = rb_service.root["org"]["gnome"]["Rhythmbox"]["Player"]
    rb_player = rb_service.object("/org/gnome/Rhythmbox/Player")

The difference between the two is that for the first one, `rb_service`
needs to have been introspected.  Also the obtained `rb_player` is already
introspected whereas the second `rb_player` isn't yet.

Errors
------

D-Bus calls can reply with an error instead of a return value. An error is
translated to a Ruby exception.

    begin
        network_manager.sleep
    rescue DBus::Error => e
        puts e unless e.name == "org.freedesktop.NetworkManager.AlreadyAsleepOrAwake"
    end

Creating a Service
==================

This chapter deals with the opposite side of the basic client usage, namely
the creation of a D-Bus service.

Registering a service
---------------------

Now that you know how to perform D-Bus calls, and how to wait for and
handle signals, you might want to learn how to publish some object and
interface to provide them to the D-Bus world.  Here is how you do that.

As you should already know, D-Bus clients that provide some object to be
called remotely are services.  Here is how to allocate a name on a bus:

    bus = DBus.session_bus
    service = bus.request_service("org.ruby.service")

Now this client is know to the outside world as `org.ruby.service`.
Note that this is a request and it _can_ be denied! When it
is denied, an exception (`DBus::NameRequestError`) is thrown.

Exporting an object
-------------------

Now, let's define a class that we want to export:

    class Test < DBus::Object
      # Create an interface.
      dbus_interface "org.ruby.SampleInterface" do
        # Create a hello method in that interface.
        dbus_method :hello, "in name:s, in name2:s" do |name, name2|
          puts "hello(#{name}, #{name2})"
        end
      end
    end

As you can see, we define a `Test` class in which we define a
`org.ruby.SampleInterface` interface.  In this interface, we define a
method.  The given code block is the method's implementation.  This will be
executed when remote programs performs a D-Bus call.  Now the annoying part:
the actual method definition.  As you can guess the call

    dbus_method :hello, "in name:s, in name2:s" do ...

creates a `hello` method that takes two parameters both of type string. 
The _:s_ means "of type string".  Let's have a look at some other common
parameter types:

- *u* means unsigned integer
- *i* means integer
- *y* means byte
- *(ui)* means a structure having a unsigned integer and a signed one.
- *a* means array, so that "ai" means array of integers
    - *as* means array of string
    - *a(is)* means array of structures, each having an integer and a string.

For a full description of the available D-Bus types, please refer to the 
[D-Bus specification](http://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-signatures).

Now that the class has been defined, we can instantiate an object
and export it as follows:

    exported_obj = Test.new("/org/ruby/MyInstance")
    service.export(exported_obj)

This piece of code above instantiates a `Test` object with a D-Bus object
path.  This object is reachable from the outside world after
`service.export(exported_obj)` is called.

We also need a loop which will read and process the calls coming over the bus:

    loop = DBus::Main.new
    loop << bus
    loop.run

### Using the exported object

Now, let's consider another program that will access our newly created service:

    ruby_service = bus.service("org.ruby.service")
    obj = ruby_service.object("/org/ruby/MyInstance")
    obj.introspect
    obj.default_iface = "org.ruby.SampleInterface"
    obj.hello("giligiligiligili", "haaaaaaa")

As you can see, the object we defined earlier is automatically introspectable.
See also "Basic Client Usage".

Emitting a signal
-----------------

Let's add some example method so you can see how to return a value to the
caller and let's also define another example interface that has a signal.

    class Test2 < DBus::Object
      # Create an interface
      dbus_interface "org.ruby.SampleInterface" do
        # Create a hello method in the interface:
        dbus_method :hello, "in name:s, in name2:s" do |name, name2|
          puts "hello(#{name}, #{name2})"
        end
        # Define a signal in the interface:
        dbus_signal :SomethingJustHappened, "toto:s, tutu:u"
      end

      dbus_interface "org.ruby.AnotherInterface" do
        dbus_method :ThatsALongMethodNameIThink, "in name:s, out ret:s" do |name|
          ["So your name is #{name}"] 
        end
      end
    end

Triggering the signal is a easy as calling a method, but then this time on
a local (exported) object and not on a remote/proxy object:

    exported_obj.SomethingJustHappened("blah", 1)

Note that the `ThatsALongMethodNameIThink` method is returning a single
value to the caller.  Notice that you always have to return an array.  If
you want to return multiple values, just have an array with multiple
values.

Replying with an error
----------------------

To reply to a dbus_method with a D-Bus error, raise a `DBus::Error`,
as constructed by the `error` convenience function:

    raise DBus.error("org.example.Error.SeatOccupied"), "Seat #{seat} is occupied"

If the error name is not specified, the generic
`org.freedesktop.DBus.Error.Failed` is used.

    raise DBus.error, "Seat #{seat} is occupied"
    raise DBus.error