em-ruby-dbus 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +504 -0
  3. data/NEWS +253 -0
  4. data/README.md +93 -0
  5. data/Rakefile +58 -0
  6. data/VERSION +1 -0
  7. data/doc/Reference.md +207 -0
  8. data/doc/Tutorial.md +480 -0
  9. data/doc/ex-calling-methods.body.rb +8 -0
  10. data/doc/ex-calling-methods.rb +3 -0
  11. data/doc/ex-properties.body.rb +9 -0
  12. data/doc/ex-properties.rb +3 -0
  13. data/doc/ex-setup.rb +7 -0
  14. data/doc/ex-signal.body.rb +20 -0
  15. data/doc/ex-signal.rb +3 -0
  16. data/doc/example-helper.rb +6 -0
  17. data/em-ruby-dbus.gemspec +20 -0
  18. data/examples/gdbus/gdbus +255 -0
  19. data/examples/gdbus/gdbus.glade +184 -0
  20. data/examples/gdbus/launch.sh +4 -0
  21. data/examples/no-introspect/nm-test.rb +21 -0
  22. data/examples/no-introspect/tracker-test.rb +16 -0
  23. data/examples/rhythmbox/playpause.rb +25 -0
  24. data/examples/service/call_service.rb +25 -0
  25. data/examples/service/service_newapi.rb +51 -0
  26. data/examples/simple/call_introspect.rb +34 -0
  27. data/examples/simple/properties.rb +19 -0
  28. data/examples/utils/listnames.rb +11 -0
  29. data/examples/utils/notify.rb +19 -0
  30. data/lib/dbus.rb +82 -0
  31. data/lib/dbus/auth.rb +269 -0
  32. data/lib/dbus/bus.rb +739 -0
  33. data/lib/dbus/core_ext/array/extract_options.rb +31 -0
  34. data/lib/dbus/core_ext/class/attribute.rb +129 -0
  35. data/lib/dbus/core_ext/kernel/singleton_class.rb +8 -0
  36. data/lib/dbus/core_ext/module/remove_method.rb +14 -0
  37. data/lib/dbus/error.rb +46 -0
  38. data/lib/dbus/export.rb +128 -0
  39. data/lib/dbus/introspect.rb +219 -0
  40. data/lib/dbus/logger.rb +31 -0
  41. data/lib/dbus/loop-em.rb +19 -0
  42. data/lib/dbus/marshall.rb +434 -0
  43. data/lib/dbus/matchrule.rb +101 -0
  44. data/lib/dbus/message.rb +276 -0
  45. data/lib/dbus/message_queue.rb +166 -0
  46. data/lib/dbus/proxy_object.rb +149 -0
  47. data/lib/dbus/proxy_object_factory.rb +41 -0
  48. data/lib/dbus/proxy_object_interface.rb +128 -0
  49. data/lib/dbus/type.rb +193 -0
  50. data/lib/dbus/xml.rb +161 -0
  51. data/test/async_spec.rb +47 -0
  52. data/test/binding_spec.rb +74 -0
  53. data/test/bus_and_xml_backend_spec.rb +39 -0
  54. data/test/bus_driver_spec.rb +20 -0
  55. data/test/bus_spec.rb +20 -0
  56. data/test/byte_array_spec.rb +38 -0
  57. data/test/err_msg_spec.rb +42 -0
  58. data/test/introspect_xml_parser_spec.rb +26 -0
  59. data/test/introspection_spec.rb +32 -0
  60. data/test/main_loop_spec.rb +82 -0
  61. data/test/property_spec.rb +53 -0
  62. data/test/server_robustness_spec.rb +66 -0
  63. data/test/server_spec.rb +53 -0
  64. data/test/service_newapi.rb +217 -0
  65. data/test/session_bus_spec_manual.rb +15 -0
  66. data/test/signal_spec.rb +90 -0
  67. data/test/spec_helper.rb +33 -0
  68. data/test/thread_safety_spec.rb +31 -0
  69. data/test/tools/dbus-launch-simple +35 -0
  70. data/test/tools/dbus-limited-session.conf +28 -0
  71. data/test/tools/test_env +13 -0
  72. data/test/tools/test_server +39 -0
  73. data/test/type_spec.rb +19 -0
  74. data/test/value_spec.rb +81 -0
  75. data/test/variant_spec.rb +66 -0
  76. metadata +145 -0
data/doc/Tutorial.md ADDED
@@ -0,0 +1,480 @@
1
+ <style>
2
+ code { background-color: #F0E7E7; }
3
+ pre code { background-color: #F0DDDD; }
4
+ pre {
5
+ font-size: 90%;
6
+ overflow: hidden;
7
+ padding-left: 10pt;
8
+ border: thin solid #F0B4B4;
9
+ background-color: #F0DDDD;
10
+ }
11
+ </style>
12
+
13
+ Welcome
14
+ =======
15
+
16
+ This is the Ruby D-Bus tutorial. It aims to show you the features of Ruby
17
+ D-Bus and as you read through the tutorial also how to use them.
18
+
19
+ &copy; Arnaud Cornet and Paul van Tilburg; this tutorial is part of
20
+ free software; you can redistribute it and/or modify it under the
21
+ terms of the [GNU Lesser General Public License,
22
+ version 2.1](http://www.gnu.org/licenses/lgpl.html) as published by the
23
+ [Free Software Foundation](http://www.fsf.org/).
24
+
25
+ Introduction
26
+ ============
27
+
28
+ This is a tutorial for Ruby D-Bus, a library to access D-Bus facilities of your
29
+ system.
30
+
31
+ What is D-Bus?
32
+ --------------
33
+
34
+ D-Bus is an RPC(Remote Procedure Call) protocol. A common setup can have
35
+ multiple D-Bus daemons running that route procedure calls and signals in
36
+ the form of messages. Each of these daemons supports a bus. A bus that
37
+ is often used by modern desktop environments, and is available per session, is
38
+ called the _session bus_. Another bus that can be available, but in a
39
+ system-wide manner, is called the _system bus_. It is used for example by
40
+ the [Hardware Abstraction Layer](http://hal.freedesktop.org/) daemon. Note
41
+ that theoretically the D-Bus RPC protocol can be used without a system or
42
+ session bus. I never came across any actual use of this though.
43
+
44
+ At the desktop level, D-Bus allows some components to interact. Typically
45
+ if you are writing an application or a personal script that wants to
46
+ interact with your web browser, your music player, or that simply wants to
47
+ pop-up a desktop notification, D-Bus comes into play.
48
+
49
+ At the system level, the Hardware Abstraction Layer is a privileged daemon
50
+ that notifies other software of hardware activities. Typically, if you
51
+ want to be notified if a CD-ROM has been loaded in, of if you want to
52
+ explore hardware, the system daemon comes into play.
53
+
54
+ The D-Bus RPC system is as we will see _object oriented_.
55
+
56
+ Buses provide access to _services_ provided in turn by running or ready to
57
+ run processes. Let me introduce some D-Bus terminology before we discuss
58
+ the API of Ruby D-Bus.
59
+
60
+ Client
61
+ ------
62
+
63
+ A D-Bus client is a process that connects to a D-Bus. They issue method
64
+ calls and register to the bus for signals and events.
65
+
66
+ Service
67
+ -------
68
+
69
+ A connected client can export some of its objects and let other clients
70
+ call some of its methods. Such clients typically register a special name
71
+ like `org.freedesktop.Notifications`, the service name.
72
+
73
+ There is slightly different type of service. They are provided by
74
+ processes that can be launched by a D-Bus daemon on demand. Once they are
75
+ started by D-Bus they register a service name and behave like another
76
+ client.
77
+
78
+ Note that the buses themselves provide the `org.freedesktop.DBus` service,
79
+ and provide some features through it.
80
+
81
+ Object path
82
+ -----------
83
+
84
+ An object path is the D-Bus way to specify an object _instance_ address. A
85
+ service can provide different object instances to the outside world, so
86
+ that external processes can call methods on each of them. An object path
87
+ is an address of an instance in a very similar way that the path is an
88
+ address of a file on a file system. For example:
89
+ `/org/freedesktop/Notification` is an object path of an object provided by
90
+ the `org.freedesktop.Notification` service
91
+
92
+ **Beware**: service names and object paths can, but do _not_ have to be
93
+ related! You'll probably encounter a lot of cases though, where the
94
+ object path is a slashed version of the dotted service name.
95
+
96
+ Interface
97
+ ---------
98
+
99
+ Classically in an object model, classes can implement interfaces. That is,
100
+ some method definitions grouped in an interface. This is exactly what a
101
+ D-Bus interface is as well. In D-Bus interfaces have names. These names must be
102
+ specified on method calls.
103
+
104
+ The `org.freedesktop.Notification` service provides an object instance
105
+ called `/org/freedesktop/Notification`. This instance object implements an
106
+ interface called `org.freedesktop.Notifications`. It also provides two
107
+ special D-Bus specific interfaces: `org.freedesktop.DBus.Introspect` and
108
+ `org.freedesktop.DBus.Properties`. Again, object paths, service names,
109
+ and interface names can be related but do not have to be.
110
+
111
+ Basically the `org.freedesktop.DBus.Introspect` has an `Introspect` method,
112
+ that returns XML data describing the `/org/freedesktop/Notification` object
113
+ interfaces. This is used heavily internally by Ruby D-Bus.
114
+
115
+ Method
116
+ ------
117
+
118
+ A method is, well, a method in the classical meaning. It's a function that
119
+ is called in the context of an object instance. Methods have typed
120
+ parameters and return typed return values.
121
+
122
+ Signal
123
+ ------
124
+
125
+ Signals are simplified method calls that do not have a return value. They
126
+ do have typed parameters though.
127
+
128
+ Message
129
+ -------
130
+
131
+ Method calls, method returns, signals, errors: all are encoded as D-Bus
132
+ messages sent over a bus. They are made of a packet header with source and
133
+ destination address, a type (method call, method reply, signal) and the
134
+ body containing the parameters (for signals and method calls) or the return
135
+ values (for a method return message).
136
+
137
+ Signature
138
+ ---------
139
+
140
+ Because D-Bus is typed and dynamic, each message comes with a signature that
141
+ describes the types of the data that is contained within the message. The
142
+ signature is a string with an extremely basic language that only describes
143
+ a data type. You will need to have some knowledge of what a signature
144
+ looks like if you are setting up a service. If you are just programming a
145
+ D-Bus client, you can live without knowing about them.
146
+
147
+ Client Usage
148
+ ============
149
+
150
+ This chapter discusses basic client usage
151
+ and has the following topics:
152
+
153
+ Using the library
154
+ -----------------
155
+
156
+ If you want to use the library, you have to make Ruby load it by issuing:
157
+
158
+ require 'dbus'
159
+
160
+ That's all! Now we can move on to really using it...
161
+
162
+ Connecting to a bus
163
+ -------------------
164
+
165
+ On a typical system, two buses are running, the system bus and the session
166
+ bus. The system bus can be accessed by:
167
+
168
+ bus = DBus::SystemBus.instance
169
+
170
+ Probably you already have guessed how to access the session bus. This
171
+ can be done by:
172
+
173
+ bus = DBus::SessionBus.instance
174
+
175
+ Performing method calls
176
+ -----------------------
177
+
178
+ Let me continue this example using the session bus. Let's say that I want
179
+ to access an object of some client on the session bus. This particular
180
+ D-Bus client provides a service called `org.gnome.Rhythmbox`. Let me
181
+ access this service:
182
+
183
+ rb_service = bus.service("org.gnome.Rhythmbox")
184
+
185
+ In this example I access the `org.gnome.Rhythmbox` service, which is
186
+ provided by the application
187
+ [Rhythmbox](http://www.gnome.org/projects/rhythmbox/).
188
+ OK, I have a service handle now, and I know that it exports the object
189
+ "/org/gnome/Rhythmbox/Player". I will trivially access this remote object
190
+ using:
191
+
192
+ rb_player = rb_service.object("/org/gnome/Rhythmbox/Player")
193
+
194
+ Introspection
195
+ -------------
196
+
197
+ Well, that was easy. Let's say that I know that this particular object is
198
+ introspectable. In real life most of them are. The `rb_object` object we
199
+ have here is just a handle of a remote object, in general they are called
200
+ _proxy objects_, because they are the local handle of a remote object. It
201
+ would be nice to be able to make it have methods, and that its methods send
202
+ a D-Bus call to remotely execute the actual method in another process.
203
+ Well, instating these methods for a _introspectable_ object is trivial:
204
+
205
+ rb_player.introspect
206
+
207
+ And there you go. Note that not all services or objects can be
208
+ introspected, therefore you have to do this manually! Let me remind you
209
+ that objects in D-Bus have interfaces and interfaces have methods. Let's
210
+ now access these methods:
211
+
212
+ rb_player_iface = rb_player["org.gnome.Rhythmbox.Player"]
213
+ puts rb_player_iface.getPlayingUri
214
+
215
+ As you can see, when you want to call a method on an instance object, you have
216
+ to get the correct interface. It is a bit tedious, so we have the following
217
+ shortcut that does the same thing as before:
218
+
219
+ rb_player.default_iface = "org.gnome.Rhythmbox.Player"
220
+ puts rb_player.getPlayingUri
221
+
222
+ The `default_iface=` call specifies the default interface that should be
223
+ used when non existing methods are called directly on a proxy object, and
224
+ not on one of its interfaces.
225
+
226
+ Note that the bus itself has a corresponding introspectable object. You can
227
+ access it with `bus.proxy` method. For example, you can retrieve an array of
228
+ exported service names of a bus like this:
229
+
230
+ bus.proxy.ListNames[0]
231
+
232
+ Properties
233
+ ----------
234
+
235
+ Some D-Bus objects provide access to properties. They are accessed by
236
+ treating a proxy interface as a hash:
237
+
238
+ nm_iface = network_manager_object["org.freedesktop.NetworkManager"]
239
+ enabled = nm_iface["WirelessEnabled"]
240
+ puts "Wireless is " + (enabled ? "enabled":"disabled")
241
+ puts "Toggling wireless"
242
+ nm_iface["WirelessEnabled"] = ! enabled
243
+
244
+
245
+ Calling a method asynchronously
246
+ -------------------------------
247
+
248
+ D-Bus is _asynchronous_. This means that you do not have to wait for a
249
+ reply when you send a message. When you call a remote method that takes a
250
+ lot of time to process remotely, you don't want your application to hang,
251
+ right? Well the asychronousness exists for this reason. What if you dont'
252
+ want to wait for the return value of a method, but still you want to take
253
+ some action when you receive it?
254
+
255
+ There is a classical method to program this event-driven mechanism. You do
256
+ some computation, perform some method call, and at the same time you setup
257
+ a callback that will be triggered once you receive a reply. Then you run a
258
+ main loop that is responsible to call the callbacks properly. Here is how
259
+ you do it:
260
+
261
+ rb_player.getPlayingUri do |resp|
262
+ puts "The playing URI is #{resp}"
263
+ end
264
+ puts "See, I'm not waiting!"
265
+ loop = DBus::Main.new
266
+ loop << bus
267
+ loop.run
268
+
269
+ This code will print the following:
270
+
271
+ See, I'm not waiting!
272
+ The playing URI is file:///music/papapingoin.mp3
273
+
274
+ Waiting for a signal
275
+ --------------------
276
+
277
+ Signals are calls from the remote object to your program. As a client, you
278
+ set yourself up to receive a signal and handle it with a callback. Then running
279
+ the main loop triggers the callback. You can register a callback handler
280
+ as allows:
281
+
282
+ rb_player.on_signal("elapsedChanged") do |u|
283
+ puts u
284
+ end
285
+
286
+ More about introspection
287
+ ------------------------
288
+
289
+ There are various ways to inspect a remote service. You can simply call
290
+ `Introspect()` and read the XML output. However, in this tutorial I assume
291
+ that you want to do it using the Ruby D-Bus API.
292
+
293
+ Notice that you can introspect a service, and not only objects:
294
+
295
+ rb_service = bus.service("org.gnome.Rhythmbox")
296
+ rb_service.introspect
297
+ p rb_service.root
298
+
299
+ This dumps a tree-like structure that represents multiple object paths. In
300
+ this particular case the output is:
301
+
302
+ </: {org => {gnome => {Rhythmbox => {Player => ..fdbe625de {},Shell => ..fdbe6852e {},PlaylistManager => ..fdbe4e340 {}}>
303
+
304
+ Read this left to right: the root node is "/", it has one child node "org",
305
+ "org" has one child node "gnome", and "gnome" has one child node "Rhythmbox".
306
+ Rhythmbox has Tree child nodes "Player", "Shell" and "PlaylistManager".
307
+ These three last child nodes have a weird digit that means it has an object
308
+ instance. Such object instances are already introspected.
309
+
310
+ If the prose wasn't clear, maybe the following ASCII art will help you:
311
+
312
+ /
313
+ org
314
+ gnome
315
+ Rhythmbox
316
+ Shell (with object)
317
+ Player (with object)
318
+ PlaylistManager (with object)
319
+
320
+ ### Walking the object tree
321
+
322
+ You can have an object on any node, i.e. it is not limited to leaves.
323
+ You can access a specific node like this:
324
+
325
+ rb_player = rb_service.root["org"]["gnome"]["Rhythmbox"]["Player"]
326
+ rb_player = rb_service.object("/org/gnome/Rhythmbox/Player")
327
+
328
+ The difference between the two is that for the first one, `rb_service`
329
+ needs to have been introspected. Also the obtained `rb_player` is already
330
+ introspected whereas the second `rb_player` isn't yet.
331
+
332
+ Errors
333
+ ------
334
+
335
+ D-Bus calls can reply with an error instead of a return value. An error is
336
+ translated to a Ruby exception.
337
+
338
+ begin
339
+ network_manager.sleep
340
+ rescue DBus::Error => e
341
+ puts e unless e.name == "org.freedesktop.NetworkManager.AlreadyAsleepOrAwake"
342
+ end
343
+
344
+ Creating a Service
345
+ ==================
346
+
347
+ This chapter deals with the opposite side of the basic client usage, namely
348
+ the creation of a D-Bus service.
349
+
350
+ Registering a service
351
+ ---------------------
352
+
353
+ Now that you know how to perform D-Bus calls, and how to wait for and
354
+ handle signals, you might want to learn how to publish some object and
355
+ interface to provide them to the D-Bus world. Here is how you do that.
356
+
357
+ As you should already know, D-Bus clients that provide some object to be
358
+ called remotely are services. Here is how to allocate a name on a bus:
359
+
360
+ bus = DBus.session_bus
361
+ service = bus.request_service("org.ruby.service")
362
+
363
+ Now this client is know to the outside world as `org.ruby.service`.
364
+ Note that this is a request and it _can_ be denied! When it
365
+ is denied, an exception (`DBus::NameRequestError`) is thrown.
366
+
367
+ Exporting an object
368
+ -------------------
369
+
370
+ Now, let's define a class that we want to export:
371
+
372
+ class Test < DBus::Object
373
+ # Create an interface.
374
+ dbus_interface "org.ruby.SampleInterface" do
375
+ # Create a hello method in that interface.
376
+ dbus_method :hello, "in name:s, in name2:s" do |name, name2|
377
+ puts "hello(#{name}, #{name2})"
378
+ end
379
+ end
380
+ end
381
+
382
+ As you can see, we define a `Test` class in which we define a
383
+ `org.ruby.SampleInterface` interface. In this interface, we define a
384
+ method. The given code block is the method's implementation. This will be
385
+ executed when remote programs performs a D-Bus call. Now the annoying part:
386
+ the actual method definition. As you can guess the call
387
+
388
+ dbus_method :hello, "in name:s, in name2:s" do ...
389
+
390
+ creates a `hello` method that takes two parameters both of type string.
391
+ The _:s_ means "of type string". Let's have a look at some other common
392
+ parameter types:
393
+
394
+ - *u* means unsigned integer
395
+ - *i* means integer
396
+ - *y* means byte
397
+ - *(ui)* means a structure having a unsigned integer and a signed one.
398
+ - *a* means array, so that "ai" means array of integers
399
+ - *as* means array of string
400
+ - *a(is)* means array of structures, each having an integer and a string.
401
+
402
+ For a full description of the available D-Bus types, please refer to the
403
+ [D-Bus specification](http://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-signatures).
404
+
405
+ Now that the class has been defined, we can instantiate an object
406
+ and export it as follows:
407
+
408
+ exported_obj = Test.new("/org/ruby/MyInstance")
409
+ service.export(exported_obj)
410
+
411
+ This piece of code above instantiates a `Test` object with a D-Bus object
412
+ path. This object is reachable from the outside world after
413
+ `service.export(exported_obj)` is called.
414
+
415
+ We also need a loop which will read and process the calls coming over the bus:
416
+
417
+ loop = DBus::Main.new
418
+ loop << bus
419
+ loop.run
420
+
421
+ ### Using the exported object
422
+
423
+ Now, let's consider another program that will access our newly created service:
424
+
425
+ ruby_service = bus.service("org.ruby.service")
426
+ obj = ruby_service.object("/org/ruby/MyInstance")
427
+ obj.introspect
428
+ obj.default_iface = "org.ruby.SampleInterface"
429
+ obj.hello("giligiligiligili", "haaaaaaa")
430
+
431
+ As you can see, the object we defined earlier is automatically introspectable.
432
+ See also "Basic Client Usage".
433
+
434
+ Emitting a signal
435
+ -----------------
436
+
437
+ Let's add some example method so you can see how to return a value to the
438
+ caller and let's also define another example interface that has a signal.
439
+
440
+ class Test2 < DBus::Object
441
+ # Create an interface
442
+ dbus_interface "org.ruby.SampleInterface" do
443
+ # Create a hello method in the interface:
444
+ dbus_method :hello, "in name:s, in name2:s" do |name, name2|
445
+ puts "hello(#{name}, #{name2})"
446
+ end
447
+ # Define a signal in the interface:
448
+ dbus_signal :SomethingJustHappened, "toto:s, tutu:u"
449
+ end
450
+
451
+ dbus_interface "org.ruby.AnotherInterface" do
452
+ dbus_method :ThatsALongMethodNameIThink, "in name:s, out ret:s" do |name|
453
+ ["So your name is #{name}"]
454
+ end
455
+ end
456
+ end
457
+
458
+ Triggering the signal is a easy as calling a method, but then this time on
459
+ a local (exported) object and not on a remote/proxy object:
460
+
461
+ exported_obj.SomethingJustHappened("blah", 1)
462
+
463
+ Note that the `ThatsALongMethodNameIThink` method is returning a single
464
+ value to the caller. Notice that you always have to return an array. If
465
+ you want to return multiple values, just have an array with multiple
466
+ values.
467
+
468
+ Replying with an error
469
+ ----------------------
470
+
471
+ To reply to a dbus_method with a D-Bus error, raise a `DBus::Error`,
472
+ as constructed by the `error` convenience function:
473
+
474
+ raise DBus.error("org.example.Error.SeatOccupied"), "Seat #{seat} is occupied"
475
+
476
+ If the error name is not specified, the generic
477
+ `org.freedesktop.DBus.Error.Failed` is used.
478
+
479
+ raise DBus.error, "Seat #{seat} is occupied"
480
+ raise DBus.error