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/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