em-ruby-dbus 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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