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/lib/dbus/bus.rb ADDED
@@ -0,0 +1,739 @@
1
+ # dbus.rb - Module containing the low-level D-Bus implementation
2
+ #
3
+ # This file is part of the ruby-dbus project
4
+ # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
5
+ #
6
+ # This library is free software; you caan redistribute it and/or
7
+ # modify it under the terms of the GNU Lesser General Public
8
+ # License, version 2.1 as published by the Free Software Foundation.
9
+ # See the file "COPYING" for the exact licensing terms.
10
+
11
+ require 'socket'
12
+ require 'thread'
13
+ require 'singleton'
14
+
15
+ # = D-Bus main module
16
+ #
17
+ # Module containing all the D-Bus modules and classes.
18
+ module DBus
19
+ # This represents a remote service. It should not be instantiated directly
20
+ # Use Bus::service()
21
+ class Service
22
+ # The service name.
23
+ attr_reader :name
24
+ # The bus the service is running on.
25
+ attr_reader :bus
26
+ # The service root (FIXME).
27
+ attr_reader :root
28
+
29
+ # Create a new service with a given _name_ on a given _bus_.
30
+ def initialize(name, bus)
31
+ @name, @bus = name, bus
32
+ @root = Node.new("/")
33
+ end
34
+
35
+ # Determine whether the service name already exists.
36
+ def exists?
37
+ bus.proxy.ListNames[0].member?(@name)
38
+ end
39
+
40
+ # Perform an introspection on all the objects on the service
41
+ # (starting recursively from the root).
42
+ def introspect
43
+ if block_given?
44
+ raise NotImplementedError
45
+ else
46
+ rec_introspect(@root, "/")
47
+ end
48
+ self
49
+ end
50
+
51
+ # Retrieves an object at the given _path_.
52
+ # @return [ProxyObject]
53
+ def object(path)
54
+ node = get_node(path, true)
55
+ if node.object.nil?
56
+ node.object = ProxyObject.new(@bus, @name, path)
57
+ end
58
+ node.object
59
+ end
60
+
61
+ # Export an object _obj_ (an DBus::Object subclass instance).
62
+ def export(obj)
63
+ obj.service = self
64
+ get_node(obj.path, true).object = obj
65
+ end
66
+
67
+ # Undo exporting an object _obj_.
68
+ # Raises ArgumentError if it is not a DBus::Object.
69
+ # Returns the object, or false if _obj_ was not exported.
70
+ def unexport(obj)
71
+ raise ArgumentError.new("DBus::Service#unexport() expects a DBus::Object argument") unless obj.kind_of?(DBus::Object)
72
+ return false unless obj.path
73
+ pathSep = obj.path.rindex("/") #last path seperator
74
+ parent_path = obj.path[1..pathSep-1]
75
+ node_name = obj.path[pathSep+1..-1]
76
+
77
+ parent_node = get_node(parent_path, false)
78
+ return false unless parent_node
79
+ obj.service = nil
80
+ parent_node.delete(node_name)
81
+ end
82
+
83
+ # Get the object node corresponding to the given _path_. if _create_ is
84
+ # true, the the nodes in the path are created if they do not already exist.
85
+ def get_node(path, create = false)
86
+ n = @root
87
+ path.sub(/^\//, "").split("/").each do |elem|
88
+ if not n[elem]
89
+ if not create
90
+ return nil
91
+ else
92
+ n[elem] = Node.new(elem)
93
+ end
94
+ end
95
+ n = n[elem]
96
+ end
97
+ if n.nil?
98
+ DBus.logger.debug "Warning, unknown object #{path}"
99
+ end
100
+ n
101
+ end
102
+
103
+ #########
104
+ private
105
+ #########
106
+
107
+ # Perform a recursive retrospection on the given current _node_
108
+ # on the given _path_.
109
+ def rec_introspect(node, path)
110
+ xml = bus.introspect_data(@name, path)
111
+ intfs, subnodes = IntrospectXMLParser.new(xml).parse
112
+ subnodes.each do |nodename|
113
+ subnode = node[nodename] = Node.new(nodename)
114
+ if path == "/"
115
+ subpath = "/" + nodename
116
+ else
117
+ subpath = path + "/" + nodename
118
+ end
119
+ rec_introspect(subnode, subpath)
120
+ end
121
+ if intfs.size > 0
122
+ node.object = ProxyObjectFactory.new(xml, @bus, @name, path).build
123
+ end
124
+ end
125
+ end
126
+
127
+ # = Object path node class
128
+ #
129
+ # Class representing a node on an object path.
130
+ class Node < Hash
131
+ # The D-Bus object contained by the node.
132
+ attr_accessor :object
133
+ # The name of the node.
134
+ attr_reader :name
135
+
136
+ # Create a new node with a given _name_.
137
+ def initialize(name)
138
+ @name = name
139
+ @object = nil
140
+ end
141
+
142
+ # Return an XML string representation of the node.
143
+ # It is shallow, not recursing into subnodes
144
+ def to_xml
145
+ xml = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
146
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
147
+ <node>
148
+ '
149
+ self.each_pair do |k, v|
150
+ xml += "<node name=\"#{k}\" />"
151
+ end
152
+ if @object
153
+ @object.intfs.each_pair do |k, v|
154
+ xml += %{<interface name="#{v.name}">\n}
155
+ v.methods.each_value { |m| xml += m.to_xml }
156
+ v.signals.each_value { |m| xml += m.to_xml }
157
+ xml +="</interface>\n"
158
+ end
159
+ end
160
+ xml += '</node>'
161
+ xml
162
+ end
163
+
164
+ # Return inspect information of the node.
165
+ def inspect
166
+ # Need something here
167
+ "<DBus::Node #{sub_inspect}>"
168
+ end
169
+
170
+ # Return instance inspect information, used by Node#inspect.
171
+ def sub_inspect
172
+ s = ""
173
+ if not @object.nil?
174
+ s += "%x " % @object.object_id
175
+ end
176
+ s + "{" + keys.collect { |k| "#{k} => #{self[k].sub_inspect}" }.join(",") + "}"
177
+ end
178
+ end # class Inspect
179
+
180
+ # FIXME: rename Connection to Bus?
181
+
182
+ # D-Bus main connection class
183
+ #
184
+ # Main class that maintains a connection to a bus and can handle incoming
185
+ # and outgoing messages.
186
+ class Connection
187
+ # The unique name (by specification) of the message.
188
+ attr_reader :unique_name
189
+ # pop and push messages here
190
+ attr_reader :message_queue
191
+
192
+ # Create a new connection to the bus for a given connect _path_. _path_
193
+ # format is described in the D-Bus specification:
194
+ # http://dbus.freedesktop.org/doc/dbus-specification.html#addresses
195
+ # and is something like:
196
+ # "transport1:key1=value1,key2=value2;transport2:key1=value1,key2=value2"
197
+ # e.g. "unix:path=/tmp/dbus-test" or "tcp:host=localhost,port=2687"
198
+ def initialize(path)
199
+ @message_queue = MessageQueue.new(path)
200
+ @unique_name = nil
201
+ @method_call_replies = Hash.new
202
+ @method_call_msgs = Hash.new
203
+ @signal_matchrules = Hash.new
204
+ @proxy = nil
205
+ @object_root = Node.new("/")
206
+ end
207
+
208
+ # Dispatch all messages that are available in the queue,
209
+ # but do not block on the queue.
210
+ # Called by a main loop when something is available in the queue
211
+ def dispatch_message_queue
212
+ while (msg = @message_queue.pop(:non_block)) # FIXME EOFError
213
+ process(msg)
214
+ end
215
+ end
216
+
217
+ # Tell a bus to register itself on the glib main loop
218
+ def glibize
219
+ require 'glib2'
220
+ # Circumvent a ruby-glib bug
221
+ @channels ||= Array.new
222
+
223
+ gio = GLib::IOChannel.new(@message_queue.socket.fileno)
224
+ @channels << gio
225
+ gio.add_watch(GLib::IOChannel::IN) do |c, ch|
226
+ dispatch_message_queue
227
+ true
228
+ end
229
+ end
230
+
231
+
232
+ def eventmachinize
233
+ require File.join(File.dirname(File.expand_path(__FILE__)), "loop-em")
234
+
235
+ conn = ::EventMachine.watch(@message_queue.socket, Loop::EventMachine::Reader, self)
236
+ conn.notify_readable = true
237
+ end
238
+
239
+ # FIXME: describe the following names, flags and constants.
240
+ # See DBus spec for definition
241
+ NAME_FLAG_ALLOW_REPLACEMENT = 0x1
242
+ NAME_FLAG_REPLACE_EXISTING = 0x2
243
+ NAME_FLAG_DO_NOT_QUEUE = 0x4
244
+
245
+ REQUEST_NAME_REPLY_PRIMARY_OWNER = 0x1
246
+ REQUEST_NAME_REPLY_IN_QUEUE = 0x2
247
+ REQUEST_NAME_REPLY_EXISTS = 0x3
248
+ REQUEST_NAME_REPLY_ALREADY_OWNER = 0x4
249
+
250
+ DBUSXMLINTRO = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
251
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
252
+ <node>
253
+ <interface name="org.freedesktop.DBus.Introspectable">
254
+ <method name="Introspect">
255
+ <arg name="data" direction="out" type="s"/>
256
+ </method>
257
+ </interface>
258
+ <interface name="org.freedesktop.DBus">
259
+ <method name="RequestName">
260
+ <arg direction="in" type="s"/>
261
+ <arg direction="in" type="u"/>
262
+ <arg direction="out" type="u"/>
263
+ </method>
264
+ <method name="ReleaseName">
265
+ <arg direction="in" type="s"/>
266
+ <arg direction="out" type="u"/>
267
+ </method>
268
+ <method name="StartServiceByName">
269
+ <arg direction="in" type="s"/>
270
+ <arg direction="in" type="u"/>
271
+ <arg direction="out" type="u"/>
272
+ </method>
273
+ <method name="Hello">
274
+ <arg direction="out" type="s"/>
275
+ </method>
276
+ <method name="NameHasOwner">
277
+ <arg direction="in" type="s"/>
278
+ <arg direction="out" type="b"/>
279
+ </method>
280
+ <method name="ListNames">
281
+ <arg direction="out" type="as"/>
282
+ </method>
283
+ <method name="ListActivatableNames">
284
+ <arg direction="out" type="as"/>
285
+ </method>
286
+ <method name="AddMatch">
287
+ <arg direction="in" type="s"/>
288
+ </method>
289
+ <method name="RemoveMatch">
290
+ <arg direction="in" type="s"/>
291
+ </method>
292
+ <method name="GetNameOwner">
293
+ <arg direction="in" type="s"/>
294
+ <arg direction="out" type="s"/>
295
+ </method>
296
+ <method name="ListQueuedOwners">
297
+ <arg direction="in" type="s"/>
298
+ <arg direction="out" type="as"/>
299
+ </method>
300
+ <method name="GetConnectionUnixUser">
301
+ <arg direction="in" type="s"/>
302
+ <arg direction="out" type="u"/>
303
+ </method>
304
+ <method name="GetConnectionUnixProcessID">
305
+ <arg direction="in" type="s"/>
306
+ <arg direction="out" type="u"/>
307
+ </method>
308
+ <method name="GetConnectionSELinuxSecurityContext">
309
+ <arg direction="in" type="s"/>
310
+ <arg direction="out" type="ay"/>
311
+ </method>
312
+ <method name="ReloadConfig">
313
+ </method>
314
+ <signal name="NameOwnerChanged">
315
+ <arg type="s"/>
316
+ <arg type="s"/>
317
+ <arg type="s"/>
318
+ </signal>
319
+ <signal name="NameLost">
320
+ <arg type="s"/>
321
+ </signal>
322
+ <signal name="NameAcquired">
323
+ <arg type="s"/>
324
+ </signal>
325
+ </interface>
326
+ </node>
327
+ '
328
+ # This apostroph is for syntax highlighting editors confused by above xml: "
329
+
330
+ # @api private
331
+ # Send a _message_.
332
+ # If _reply_handler_ is not given, wait for the reply
333
+ # and return the reply, or raise the error.
334
+ # If _reply_handler_ is given, it will be called when the reply
335
+ # eventually arrives, with the reply message as the 1st param
336
+ # and its params following
337
+ def send_sync_or_async(message, &reply_handler)
338
+ ret = nil
339
+ if reply_handler.nil?
340
+ send_sync(message) do |rmsg|
341
+ if rmsg.is_a?(Error)
342
+ raise rmsg
343
+ else
344
+ ret = rmsg.params
345
+ end
346
+ end
347
+ else
348
+ on_return(message) do |rmsg|
349
+ if rmsg.is_a?(Error)
350
+ reply_handler.call(rmsg)
351
+ else
352
+ reply_handler.call(rmsg, * rmsg.params)
353
+ end
354
+ end
355
+ @message_queue.push(message)
356
+ end
357
+ ret
358
+ end
359
+
360
+ # @api private
361
+ def introspect_data(dest, path, &reply_handler)
362
+ m = DBus::Message.new(DBus::Message::METHOD_CALL)
363
+ m.path = path
364
+ m.interface = "org.freedesktop.DBus.Introspectable"
365
+ m.destination = dest
366
+ m.member = "Introspect"
367
+ m.sender = unique_name
368
+ if reply_handler.nil?
369
+ send_sync_or_async(m).first
370
+ else
371
+ send_sync_or_async(m) do |*args|
372
+ # TODO test async introspection, is it used at all?
373
+ args.shift # forget the message, pass only the text
374
+ reply_handler.call(*args)
375
+ nil
376
+ end
377
+ end
378
+ end
379
+
380
+ # @api private
381
+ # Issues a call to the org.freedesktop.DBus.Introspectable.Introspect method
382
+ # _dest_ is the service and _path_ the object path you want to introspect
383
+ # If a code block is given, the introspect call in asynchronous. If not
384
+ # data is returned
385
+ #
386
+ # FIXME: link to ProxyObject data definition
387
+ # The returned object is a ProxyObject that has methods you can call to
388
+ # issue somme METHOD_CALL messages, and to setup to receive METHOD_RETURN
389
+ def introspect(dest, path)
390
+ if not block_given?
391
+ # introspect in synchronous !
392
+ data = introspect_data(dest, path)
393
+ pof = DBus::ProxyObjectFactory.new(data, self, dest, path)
394
+ return pof.build
395
+ else
396
+ introspect_data(dest, path) do |async_data|
397
+ yield(DBus::ProxyObjectFactory.new(async_data, self, dest, path).build)
398
+ end
399
+ end
400
+ end
401
+
402
+ # Exception raised when a service name is requested that is not available.
403
+ class NameRequestError < Exception
404
+ end
405
+
406
+ # Attempt to request a service _name_.
407
+ #
408
+ # FIXME, NameRequestError cannot really be rescued as it will be raised
409
+ # when dispatching a later call. Rework the API to better match the spec.
410
+ # @return [Service]
411
+ def request_service(name)
412
+ # Use RequestName, but asynchronously!
413
+ # A synchronous call would not work with service activation, where
414
+ # method calls to be serviced arrive before the reply for RequestName
415
+ # (Ticket#29).
416
+ proxy.RequestName(name, NAME_FLAG_REPLACE_EXISTING) do |rmsg, r|
417
+ if rmsg.is_a?(Error) # check and report errors first
418
+ raise rmsg
419
+ elsif r != REQUEST_NAME_REPLY_PRIMARY_OWNER
420
+ raise NameRequestError
421
+ end
422
+ end
423
+ @service = Service.new(name, self)
424
+ @service
425
+ end
426
+
427
+ # Set up a ProxyObject for the bus itself, since the bus is introspectable.
428
+ # Returns the object.
429
+ def proxy
430
+ if @proxy == nil
431
+ path = "/org/freedesktop/DBus"
432
+ dest = "org.freedesktop.DBus"
433
+ pof = DBus::ProxyObjectFactory.new(DBUSXMLINTRO, self, dest, path)
434
+ @proxy = pof.build["org.freedesktop.DBus"]
435
+ end
436
+ @proxy
437
+ end
438
+
439
+ # @api private
440
+ # Wait for a message to arrive. Return it once it is available.
441
+ def wait_for_message
442
+ @message_queue.pop # FIXME EOFError
443
+ end
444
+
445
+ # @api private
446
+ # Send a message _m_ on to the bus. This is done synchronously, thus
447
+ # the call will block until a reply message arrives.
448
+ def send_sync(m, &retc) # :yields: reply/return message
449
+ return if m.nil? #check if somethings wrong
450
+ @message_queue.push(m)
451
+ @method_call_msgs[m.serial] = m
452
+ @method_call_replies[m.serial] = retc
453
+
454
+ retm = wait_for_message
455
+ return if retm.nil? #check if somethings wrong
456
+
457
+ process(retm)
458
+ while @method_call_replies.has_key? m.serial
459
+ retm = wait_for_message
460
+ process(retm)
461
+ end
462
+ end
463
+
464
+ # @api private
465
+ # Specify a code block that has to be executed when a reply for
466
+ # message _m_ is received.
467
+ def on_return(m, &retc)
468
+ # Have a better exception here
469
+ if m.message_type != Message::METHOD_CALL
470
+ raise "on_return should only get method_calls"
471
+ end
472
+ @method_call_msgs[m.serial] = m
473
+ @method_call_replies[m.serial] = retc
474
+ end
475
+
476
+ # Asks bus to send us messages matching mr, and execute slot when
477
+ # received
478
+ def add_match(mr, &slot)
479
+ # check this is a signal.
480
+ mrs = mr.to_s
481
+ DBus.logger.debug "#{@signal_matchrules.size} rules, adding #{mrs.inspect}"
482
+ # don't ask for the same match if we override it
483
+ unless @signal_matchrules.key?(mrs)
484
+ DBus.logger.debug "Asked for a new match"
485
+ proxy.AddMatch(mrs)
486
+ end
487
+ @signal_matchrules[mrs] = slot
488
+ end
489
+
490
+ def remove_match(mr)
491
+ mrs = mr.to_s
492
+ unless @signal_matchrules.delete(mrs).nil?
493
+ # don't remove nonexisting matches.
494
+ # FIXME if we do try, the Error.MatchRuleNotFound is *not* raised
495
+ # and instead is reported as "no return code for nil"
496
+ proxy.RemoveMatch(mrs)
497
+ end
498
+ end
499
+
500
+ # @api private
501
+ # Process a message _m_ based on its type.
502
+ def process(m)
503
+ return if m.nil? #check if somethings wrong
504
+ case m.message_type
505
+ when Message::ERROR, Message::METHOD_RETURN
506
+ raise InvalidPacketException if m.reply_serial == nil
507
+ mcs = @method_call_replies[m.reply_serial]
508
+ if not mcs
509
+ DBus.logger.debug "no return code for mcs: #{mcs.inspect} m: #{m.inspect}"
510
+ else
511
+ if m.message_type == Message::ERROR
512
+ mcs.call(Error.new(m))
513
+ else
514
+ mcs.call(m)
515
+ end
516
+ @method_call_replies.delete(m.reply_serial)
517
+ @method_call_msgs.delete(m.reply_serial)
518
+ end
519
+ when DBus::Message::METHOD_CALL
520
+ if m.path == "/org/freedesktop/DBus"
521
+ DBus.logger.debug "Got method call on /org/freedesktop/DBus"
522
+ end
523
+ node = @service.get_node(m.path)
524
+ if not node
525
+ reply = Message.error(m, "org.freedesktop.DBus.Error.UnknownObject",
526
+ "Object #{m.path} doesn't exist")
527
+ @message_queue.push(reply)
528
+ # handle introspectable as an exception:
529
+ elsif m.interface == "org.freedesktop.DBus.Introspectable" and
530
+ m.member == "Introspect"
531
+ reply = Message.new(Message::METHOD_RETURN).reply_to(m)
532
+ reply.sender = @unique_name
533
+ reply.add_param(Type::STRING, node.to_xml)
534
+ @message_queue.push(reply)
535
+ else
536
+ obj = node.object
537
+ return if obj.nil? # FIXME, pushes no reply
538
+ obj.dispatch(m) if obj
539
+ end
540
+ when DBus::Message::SIGNAL
541
+ # the signal can match multiple different rules
542
+ @signal_matchrules.each do |mrs, slot|
543
+ if DBus::MatchRule.new.from_s(mrs).match(m)
544
+ slot.call(m)
545
+ end
546
+ end
547
+ else
548
+ DBus.logger.debug "Unknown message type: #{m.message_type}"
549
+ end
550
+ rescue Exception => ex
551
+ raise m.annotate_exception(ex)
552
+ end
553
+
554
+ # Retrieves the Service with the given _name_.
555
+ # @return [Service]
556
+ def service(name)
557
+ # The service might not exist at this time so we cannot really check
558
+ # anything
559
+ Service.new(name, self)
560
+ end
561
+ alias :[] :service
562
+
563
+ # @api private
564
+ # Emit a signal event for the given _service_, object _obj_, interface
565
+ # _intf_ and signal _sig_ with arguments _args_.
566
+ def emit(service, obj, intf, sig, *args)
567
+ m = Message.new(DBus::Message::SIGNAL)
568
+ m.path = obj.path
569
+ m.interface = intf.name
570
+ m.member = sig.name
571
+ m.sender = service.name
572
+ i = 0
573
+ sig.params.each do |par|
574
+ m.add_param(par.type, args[i])
575
+ i += 1
576
+ end
577
+ @message_queue.push(m)
578
+ end
579
+
580
+ ###########################################################################
581
+ private
582
+
583
+ # Send a hello messages to the bus to let it know we are here.
584
+ def send_hello
585
+ m = Message.new(DBus::Message::METHOD_CALL)
586
+ m.path = "/org/freedesktop/DBus"
587
+ m.destination = "org.freedesktop.DBus"
588
+ m.interface = "org.freedesktop.DBus"
589
+ m.member = "Hello"
590
+ send_sync(m) do |rmsg|
591
+ @unique_name = rmsg.destination
592
+ DBus.logger.debug "Got hello reply. Our unique_name is #{@unique_name}"
593
+ end
594
+ @service = Service.new(@unique_name, self)
595
+ end
596
+ end # class Connection
597
+
598
+
599
+ # = D-Bus session bus class
600
+ #
601
+ # The session bus is a session specific bus (mostly for desktop use).
602
+ #
603
+ # Use SessionBus, the non-singleton ASessionBus is
604
+ # for the test suite.
605
+ class ASessionBus < Connection
606
+ # Get the the default session bus.
607
+ def initialize
608
+ super(ENV["DBUS_SESSION_BUS_ADDRESS"] || address_from_file || "launchd:env=DBUS_LAUNCHD_SESSION_BUS_SOCKET")
609
+ send_hello
610
+ end
611
+
612
+ def address_from_file
613
+ # systemd uses /etc/machine-id
614
+ # traditional dbus uses /var/lib/dbus/machine-id
615
+ machine_id_path = Dir['{/etc,/var/lib/dbus}/machine-id'].first
616
+ return nil unless machine_id_path
617
+ machine_id = File.read(machine_id_path).chomp
618
+
619
+ display = ENV["DISPLAY"][/:(\d+)\.?/, 1]
620
+
621
+ bus_file_path = File.join(ENV["HOME"], "/.dbus/session-bus/#{machine_id}-#{display}")
622
+ return nil unless File.exists?(bus_file_path)
623
+
624
+ File.open(bus_file_path).each_line do |line|
625
+ if line =~ /^DBUS_SESSION_BUS_ADDRESS=(.*)/
626
+ return $1
627
+ end
628
+ end
629
+ end
630
+ end
631
+
632
+ # See ASessionBus
633
+ class SessionBus < ASessionBus
634
+ include Singleton
635
+ end
636
+
637
+
638
+ # = D-Bus system bus class
639
+ #
640
+ # The system bus is a system-wide bus mostly used for global or
641
+ # system usages.
642
+ #
643
+ # Use SystemBus, the non-singleton ASystemBus is
644
+ # for the test suite.
645
+ class ASystemBus < Connection
646
+ # Get the default system bus.
647
+ def initialize
648
+ super(SystemSocketName)
649
+ send_hello
650
+ end
651
+ end
652
+
653
+ # = D-Bus remote (TCP) bus class
654
+ #
655
+ # This class may be used when connecting to remote (listening on a TCP socket)
656
+ # busses. You can also use it to connect to other non-standard path busses.
657
+ #
658
+ # The specified socket_name should look like this:
659
+ # (for TCP) tcp:host=127.0.0.1,port=2687
660
+ # (for Unix-socket) unix:path=/tmp/my_funky_bus_socket
661
+ #
662
+ # you'll need to take care about authentification then, more info here:
663
+ # http://github.com/pangdudu/ruby-dbus/blob/master/README.rdoc
664
+ class RemoteBus < Connection
665
+
666
+ # Get the remote bus.
667
+ def initialize socket_name
668
+ super(socket_name)
669
+ send_hello
670
+ end
671
+ end
672
+
673
+ # See ASystemBus
674
+ class SystemBus < ASystemBus
675
+ include Singleton
676
+ end
677
+
678
+ # Shortcut for the {SystemBus} instance
679
+ # @return [Connection]
680
+ def DBus.system_bus
681
+ SystemBus.instance
682
+ end
683
+
684
+ # Shortcut for the {SessionBus} instance
685
+ # @return [Connection]
686
+ def DBus.session_bus
687
+ SessionBus.instance
688
+ end
689
+
690
+ # = Main event loop class.
691
+ #
692
+ # Class that takes care of handling message and signal events
693
+ # asynchronously. *Note:* This is a native implement and therefore does
694
+ # not integrate with a graphical widget set main loop.
695
+ class Main
696
+ # Create a new main event loop.
697
+ def initialize
698
+ @buses = Hash.new
699
+ @quitting = false
700
+ end
701
+
702
+ # Add a _bus_ to the list of buses to watch for events.
703
+ def <<(bus)
704
+ @buses[bus.message_queue.socket] = bus
705
+ end
706
+
707
+ # Quit a running main loop, to be used eg. from a signal handler
708
+ def quit
709
+ @quitting = true
710
+ end
711
+
712
+ # Run the main loop. This is a blocking call!
713
+ def run
714
+ # before blocking, empty the buffers
715
+ # https://bugzilla.novell.com/show_bug.cgi?id=537401
716
+ @buses.each_value do |b|
717
+ while m = b.message_queue.message_from_buffer_nonblock
718
+ b.process(m)
719
+ end
720
+ end
721
+ while not @quitting and not @buses.empty?
722
+ ready = IO.select(@buses.keys, [], [], 5) # timeout 5 seconds
723
+ next unless ready # timeout exceeds so continue unless quitting
724
+ ready.first.each do |socket|
725
+ b = @buses[socket]
726
+ begin
727
+ b.message_queue.buffer_from_socket_nonblock
728
+ rescue EOFError, SystemCallError
729
+ @buses.delete socket # this bus died
730
+ next
731
+ end
732
+ while m = b.message_queue.message_from_buffer_nonblock
733
+ b.process(m)
734
+ end
735
+ end
736
+ end
737
+ end
738
+ end # class Main
739
+ end # module DBus