ruby-dbus 0.22.1 → 0.23.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +38 -0
  3. data/VERSION +1 -1
  4. data/doc/Reference.md +5 -5
  5. data/examples/no-bus/pulseaudio.rb +50 -0
  6. data/examples/service/complex-property.rb +2 -2
  7. data/examples/service/service_newapi.rb +2 -2
  8. data/lib/dbus/bus.rb +48 -694
  9. data/lib/dbus/connection.rb +350 -0
  10. data/lib/dbus/logger.rb +3 -2
  11. data/lib/dbus/main.rb +66 -0
  12. data/lib/dbus/message.rb +6 -8
  13. data/lib/dbus/node_tree.rb +105 -0
  14. data/lib/dbus/object.rb +28 -9
  15. data/lib/dbus/object_manager.rb +6 -3
  16. data/lib/dbus/object_server.rb +149 -0
  17. data/lib/dbus/org.freedesktop.DBus.xml +97 -0
  18. data/lib/dbus/proxy_object.rb +4 -4
  19. data/lib/dbus/proxy_service.rb +107 -0
  20. data/lib/dbus.rb +10 -8
  21. data/ruby-dbus.gemspec +1 -1
  22. data/spec/bus_connection_spec.rb +80 -0
  23. data/spec/connection_spec.rb +37 -0
  24. data/spec/coverage_helper.rb +39 -0
  25. data/spec/dbus_spec.rb +22 -0
  26. data/spec/main_loop_spec.rb +14 -0
  27. data/spec/message_spec.rb +21 -0
  28. data/spec/mock-service/cockpit-dbustests.rb +29 -0
  29. data/spec/mock-service/com.redhat.Cockpit.DBusTests.xml +180 -0
  30. data/spec/mock-service/org.ruby.service.service +4 -0
  31. data/spec/mock-service/org.rubygems.ruby_dbus.DBusTests.service +4 -0
  32. data/spec/{service_newapi.rb → mock-service/spaghetti-monster.rb} +21 -10
  33. data/spec/node_spec.rb +1 -5
  34. data/spec/object_server_spec.rb +138 -0
  35. data/spec/object_spec.rb +46 -0
  36. data/spec/{bus_driver_spec.rb → proxy_service_spec.rb} +13 -8
  37. data/spec/spec_helper.rb +9 -45
  38. data/spec/thread_safety_spec.rb +9 -11
  39. data/spec/tools/dbus-launch-simple +4 -1
  40. data/spec/tools/dbus-limited-session.conf +3 -0
  41. data/spec/tools/test_env +26 -6
  42. metadata +24 -10
  43. data/spec/server_spec.rb +0 -55
  44. data/spec/service_spec.rb +0 -18
  45. data/spec/tools/test_server +0 -39
data/lib/dbus/bus.rb CHANGED
@@ -5,7 +5,7 @@
5
5
  # This file is part of the ruby-dbus project
6
6
  # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
7
7
  #
8
- # This library is free software; you caan redistribute it and/or
8
+ # This library is free software; you can redistribute it and/or
9
9
  # modify it under the terms of the GNU Lesser General Public
10
10
  # License, version 2.1 as published by the Free Software Foundation.
11
11
  # See the file "COPYING" for the exact licensing terms.
@@ -13,505 +13,26 @@
13
13
  require "socket"
14
14
  require "singleton"
15
15
 
16
+ require_relative "connection"
17
+
16
18
  # = D-Bus main module
17
19
  #
18
20
  # Module containing all the D-Bus modules and classes.
19
21
  module DBus
20
- # This represents a remote service. It should not be instantiated directly
21
- # Use {Connection#service}
22
- class Service
23
- # The service name.
24
- attr_reader :name
25
- # The bus the service is running on.
26
- attr_reader :bus
27
- # The service root (FIXME).
28
- attr_reader :root
29
-
30
- # Create a new service with a given _name_ on a given _bus_.
31
- def initialize(name, bus)
32
- @name = BusName.new(name)
33
- @bus = bus
34
- @root = Node.new("/")
35
- end
36
-
37
- # Determine whether the service name already exists.
38
- def exists?
39
- bus.proxy.ListNames[0].member?(@name)
40
- end
41
-
42
- # Perform an introspection on all the objects on the service
43
- # (starting recursively from the root).
44
- def introspect
45
- raise NotImplementedError if block_given?
46
-
47
- rec_introspect(@root, "/")
48
- self
49
- end
50
-
51
- # Retrieves an object at the given _path_.
52
- # @param path [ObjectPath]
53
- # @return [ProxyObject]
54
- def [](path)
55
- object(path, api: ApiOptions::A1)
56
- end
57
-
58
- # Retrieves an object at the given _path_
59
- # whose methods always return an array.
60
- # @param path [ObjectPath]
61
- # @param api [ApiOptions]
62
- # @return [ProxyObject]
63
- def object(path, api: ApiOptions::A0)
64
- node = get_node(path, create: true)
65
- if node.object.nil? || node.object.api != api
66
- node.object = ProxyObject.new(
67
- @bus, @name, path,
68
- api: api
69
- )
70
- end
71
- node.object
72
- end
73
-
74
- # Export an object
75
- # @param obj [DBus::Object]
76
- def export(obj)
77
- obj.service = self
78
- get_node(obj.path, create: true).object = obj
79
- object_manager_for(obj)&.object_added(obj)
80
- end
81
-
82
- # Undo exporting an object _obj_.
83
- # Raises ArgumentError if it is not a DBus::Object.
84
- # Returns the object, or false if _obj_ was not exported.
85
- # @param obj [DBus::Object]
86
- def unexport(obj)
87
- raise ArgumentError, "DBus::Service#unexport() expects a DBus::Object argument" unless obj.is_a?(DBus::Object)
88
- return false unless obj.path
89
-
90
- last_path_separator_idx = obj.path.rindex("/")
91
- parent_path = obj.path[1..last_path_separator_idx - 1]
92
- node_name = obj.path[last_path_separator_idx + 1..-1]
93
-
94
- parent_node = get_node(parent_path, create: false)
95
- return false unless parent_node
96
-
97
- object_manager_for(obj)&.object_removed(obj)
98
- obj.service = nil
99
- parent_node.delete(node_name).object
100
- end
101
-
102
- # Get the object node corresponding to the given *path*.
103
- # @param path [ObjectPath]
104
- # @param create [Boolean] if true, the the {Node}s in the path are created
105
- # if they do not already exist.
106
- # @return [Node,nil]
107
- def get_node(path, create: false)
108
- n = @root
109
- path.sub(%r{^/}, "").split("/").each do |elem|
110
- if !(n[elem])
111
- return nil if !create
112
-
113
- n[elem] = Node.new(elem)
114
- end
115
- n = n[elem]
116
- end
117
- n
118
- end
119
-
120
- # Find the (closest) parent of *object*
121
- # implementing the ObjectManager interface, or nil
122
- # @return [DBus::Object,nil]
123
- def object_manager_for(object)
124
- path = object.path
125
- node_chain = get_node_chain(path)
126
- om_node = node_chain.reverse_each.find do |node|
127
- node.object&.is_a? DBus::ObjectManager
128
- end
129
- om_node&.object
130
- end
131
-
132
- # All objects (not paths) under this path (except itself).
133
- # @param path [ObjectPath]
134
- # @return [Array<DBus::Object>]
135
- # @raise ArgumentError if the *path* does not exist
136
- def descendants_for(path)
137
- node = get_node(path, create: false)
138
- raise ArgumentError, "Object path #{path} doesn't exist" if node.nil?
139
-
140
- node.descendant_objects
141
- end
142
-
143
- #########
144
-
145
- private
146
-
147
- #########
148
-
149
- # @raise ArgumentError if the *path* does not exist
150
- def get_node_chain(path)
151
- n = @root
152
- result = [n]
153
- path.sub(%r{^/}, "").split("/").each do |elem|
154
- n = n[elem]
155
- raise ArgumentError, "Object path #{path} doesn't exist" if n.nil?
156
-
157
- result.push(n)
158
- end
159
- result
160
- end
161
-
162
- # Perform a recursive retrospection on the given current _node_
163
- # on the given _path_.
164
- def rec_introspect(node, path)
165
- xml = bus.introspect_data(@name, path)
166
- intfs, subnodes = IntrospectXMLParser.new(xml).parse
167
- subnodes.each do |nodename|
168
- subnode = node[nodename] = Node.new(nodename)
169
- subpath = if path == "/"
170
- "/#{nodename}"
171
- else
172
- "#{path}/#{nodename}"
173
- end
174
- rec_introspect(subnode, subpath)
175
- end
176
- return if intfs.empty?
177
-
178
- node.object = ProxyObjectFactory.new(xml, @bus, @name, path).build
179
- end
180
- end
181
-
182
- # = Object path node class
183
- #
184
- # Class representing a node on an object path.
185
- class Node < Hash
186
- # @return [DBus::Object,DBus::ProxyObject,nil]
187
- # The D-Bus object contained by the node.
188
- attr_accessor :object
189
-
190
- # The name of the node.
191
- # @return [String] the last component of its object path, or "/"
192
- attr_reader :name
193
-
194
- # Create a new node with a given _name_.
195
- def initialize(name)
196
- super()
197
- @name = name
198
- @object = nil
199
- end
200
-
201
- # Return an XML string representation of the node.
202
- # It is shallow, not recursing into subnodes
203
- # @param node_opath [String]
204
- def to_xml(node_opath)
205
- xml = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
206
- "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
207
- '
208
- xml += "<node name=\"#{node_opath}\">\n"
209
- each_key do |k|
210
- xml += " <node name=\"#{k}\" />\n"
211
- end
212
- @object&.intfs&.each_value do |v|
213
- xml += v.to_xml
214
- end
215
- xml += "</node>"
216
- xml
217
- end
218
-
219
- # Return inspect information of the node.
220
- def inspect
221
- # Need something here
222
- "<DBus::Node #{sub_inspect}>"
223
- end
224
-
225
- # Return instance inspect information, used by Node#inspect.
226
- def sub_inspect
227
- s = ""
228
- if !@object.nil?
229
- s += format("%x ", @object.object_id)
230
- end
231
- contents_sub_inspect = keys
232
- .map { |k| "#{k} => #{self[k].sub_inspect}" }
233
- .join(",")
234
- "#{s}{#{contents_sub_inspect}}"
235
- end
236
-
237
- # All objects (not paths) under this path (except itself).
238
- # @return [Array<DBus::Object>]
239
- def descendant_objects
240
- children_objects = values.map(&:object).compact
241
- descendants = values.map(&:descendant_objects)
242
- flat_descendants = descendants.reduce([], &:+)
243
- children_objects + flat_descendants
244
- end
245
- end
246
-
247
- # FIXME: rename Connection to Bus?
248
-
249
- # D-Bus main connection class
250
- #
251
- # Main class that maintains a connection to a bus and can handle incoming
252
- # and outgoing messages.
253
- class Connection
22
+ # A regular Bus {Connection}.
23
+ # As opposed to a peer connection to a single counterparty with no daemon in between.
24
+ class BusConnection < Connection
254
25
  # The unique name (by specification) of the message.
255
26
  attr_reader :unique_name
256
- # pop and push messages here
257
- attr_reader :message_queue
258
27
 
259
- # Create a new connection to the bus for a given connect _path_. _path_
260
- # format is described in the D-Bus specification:
261
- # http://dbus.freedesktop.org/doc/dbus-specification.html#addresses
262
- # and is something like:
263
- # "transport1:key1=value1,key2=value2;transport2:key1=value1,key2=value2"
264
- # e.g. "unix:path=/tmp/dbus-test" or "tcp:host=localhost,port=2687"
265
- def initialize(path)
266
- @message_queue = MessageQueue.new(path)
28
+ # Connect, authenticate, and send Hello.
29
+ # @param addresses [String]
30
+ # @see https://dbus.freedesktop.org/doc/dbus-specification.html#addresses
31
+ def initialize(addresses)
32
+ super
267
33
  @unique_name = nil
268
-
269
- # @return [Hash{Integer => Proc}]
270
- # key: message serial
271
- # value: block to be run when the reply to that message is received
272
- @method_call_replies = {}
273
-
274
- # @return [Hash{Integer => Message}]
275
- # for debugging only: messages for which a reply was not received yet;
276
- # key == value.serial
277
- @method_call_msgs = {}
278
- @signal_matchrules = {}
279
34
  @proxy = nil
280
- end
281
-
282
- # Dispatch all messages that are available in the queue,
283
- # but do not block on the queue.
284
- # Called by a main loop when something is available in the queue
285
- def dispatch_message_queue
286
- while (msg = @message_queue.pop(blocking: false)) # FIXME: EOFError
287
- process(msg)
288
- end
289
- end
290
-
291
- # Tell a bus to register itself on the glib main loop
292
- def glibize
293
- require "glib2"
294
- # Circumvent a ruby-glib bug
295
- @channels ||= []
296
-
297
- gio = GLib::IOChannel.new(@message_queue.socket.fileno)
298
- @channels << gio
299
- gio.add_watch(GLib::IOChannel::IN) do |_c, _ch|
300
- dispatch_message_queue
301
- true
302
- end
303
- end
304
-
305
- # FIXME: describe the following names, flags and constants.
306
- # See DBus spec for definition
307
- NAME_FLAG_ALLOW_REPLACEMENT = 0x1
308
- NAME_FLAG_REPLACE_EXISTING = 0x2
309
- NAME_FLAG_DO_NOT_QUEUE = 0x4
310
-
311
- REQUEST_NAME_REPLY_PRIMARY_OWNER = 0x1
312
- REQUEST_NAME_REPLY_IN_QUEUE = 0x2
313
- REQUEST_NAME_REPLY_EXISTS = 0x3
314
- REQUEST_NAME_REPLY_ALREADY_OWNER = 0x4
315
-
316
- DBUSXMLINTRO = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
317
- "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
318
- <node>
319
- <interface name="org.freedesktop.DBus.Introspectable">
320
- <method name="Introspect">
321
- <arg direction="out" type="s"/>
322
- </method>
323
- </interface>
324
- <interface name="org.freedesktop.DBus">
325
- <method name="Hello">
326
- <arg direction="out" type="s"/>
327
- </method>
328
- <method name="RequestName">
329
- <arg direction="in" type="s"/>
330
- <arg direction="in" type="u"/>
331
- <arg direction="out" type="u"/>
332
- </method>
333
- <method name="ReleaseName">
334
- <arg direction="in" type="s"/>
335
- <arg direction="out" type="u"/>
336
- </method>
337
- <method name="StartServiceByName">
338
- <arg direction="in" type="s"/>
339
- <arg direction="in" type="u"/>
340
- <arg direction="out" type="u"/>
341
- </method>
342
- <method name="UpdateActivationEnvironment">
343
- <arg direction="in" type="a{ss}"/>
344
- </method>
345
- <method name="NameHasOwner">
346
- <arg direction="in" type="s"/>
347
- <arg direction="out" type="b"/>
348
- </method>
349
- <method name="ListNames">
350
- <arg direction="out" type="as"/>
351
- </method>
352
- <method name="ListActivatableNames">
353
- <arg direction="out" type="as"/>
354
- </method>
355
- <method name="AddMatch">
356
- <arg direction="in" type="s"/>
357
- </method>
358
- <method name="RemoveMatch">
359
- <arg direction="in" type="s"/>
360
- </method>
361
- <method name="GetNameOwner">
362
- <arg direction="in" type="s"/>
363
- <arg direction="out" type="s"/>
364
- </method>
365
- <method name="ListQueuedOwners">
366
- <arg direction="in" type="s"/>
367
- <arg direction="out" type="as"/>
368
- </method>
369
- <method name="GetConnectionUnixUser">
370
- <arg direction="in" type="s"/>
371
- <arg direction="out" type="u"/>
372
- </method>
373
- <method name="GetConnectionUnixProcessID">
374
- <arg direction="in" type="s"/>
375
- <arg direction="out" type="u"/>
376
- </method>
377
- <method name="GetAdtAuditSessionData">
378
- <arg direction="in" type="s"/>
379
- <arg direction="out" type="ay"/>
380
- </method>
381
- <method name="GetConnectionSELinuxSecurityContext">
382
- <arg direction="in" type="s"/>
383
- <arg direction="out" type="ay"/>
384
- </method>
385
- <method name="ReloadConfig">
386
- </method>
387
- <method name="GetId">
388
- <arg direction="out" type="s"/>
389
- </method>
390
- <method name="GetConnectionCredentials">
391
- <arg direction="in" type="s"/>
392
- <arg direction="out" type="a{sv}"/>
393
- </method>
394
- <property name="Features" type="as" access="read">
395
- <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
396
- </property>
397
- <property name="Interfaces" type="as" access="read">
398
- <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
399
- </property>
400
- <signal name="NameOwnerChanged">
401
- <arg type="s"/>
402
- <arg type="s"/>
403
- <arg type="s"/>
404
- </signal>
405
- <signal name="NameLost">
406
- <arg type="s"/>
407
- </signal>
408
- <signal name="NameAcquired">
409
- <arg type="s"/>
410
- </signal>
411
- </interface>
412
- </node>
413
- '
414
- # This apostroph is for syntax highlighting editors confused by above xml: "
415
-
416
- # @api private
417
- # Send a _message_.
418
- # If _reply_handler_ is not given, wait for the reply
419
- # and return the reply, or raise the error.
420
- # If _reply_handler_ is given, it will be called when the reply
421
- # eventually arrives, with the reply message as the 1st param
422
- # and its params following
423
- def send_sync_or_async(message, &reply_handler)
424
- ret = nil
425
- if reply_handler.nil?
426
- send_sync(message) do |rmsg|
427
- raise rmsg if rmsg.is_a?(Error)
428
-
429
- ret = rmsg.params
430
- end
431
- else
432
- on_return(message) do |rmsg|
433
- if rmsg.is_a?(Error)
434
- reply_handler.call(rmsg)
435
- else
436
- reply_handler.call(rmsg, * rmsg.params)
437
- end
438
- end
439
- @message_queue.push(message)
440
- end
441
- ret
442
- end
443
-
444
- # @api private
445
- def introspect_data(dest, path, &reply_handler)
446
- m = DBus::Message.new(DBus::Message::METHOD_CALL)
447
- m.path = path
448
- m.interface = "org.freedesktop.DBus.Introspectable"
449
- m.destination = dest
450
- m.member = "Introspect"
451
- m.sender = unique_name
452
- if reply_handler.nil?
453
- send_sync_or_async(m).first
454
- else
455
- send_sync_or_async(m) do |*args|
456
- # TODO: test async introspection, is it used at all?
457
- args.shift # forget the message, pass only the text
458
- reply_handler.call(*args)
459
- nil
460
- end
461
- end
462
- end
463
-
464
- # @api private
465
- # Issues a call to the org.freedesktop.DBus.Introspectable.Introspect method
466
- # _dest_ is the service and _path_ the object path you want to introspect
467
- # If a code block is given, the introspect call in asynchronous. If not
468
- # data is returned
469
- #
470
- # FIXME: link to ProxyObject data definition
471
- # The returned object is a ProxyObject that has methods you can call to
472
- # issue somme METHOD_CALL messages, and to setup to receive METHOD_RETURN
473
- def introspect(dest, path)
474
- if !block_given?
475
- # introspect in synchronous !
476
- data = introspect_data(dest, path)
477
- pof = DBus::ProxyObjectFactory.new(data, self, dest, path)
478
- pof.build
479
- else
480
- introspect_data(dest, path) do |async_data|
481
- yield(DBus::ProxyObjectFactory.new(async_data, self, dest, path).build)
482
- end
483
- end
484
- end
485
-
486
- # Exception raised when a service name is requested that is not available.
487
- class NameRequestError < Exception
488
- end
489
-
490
- # Attempt to request a service _name_.
491
- #
492
- # FIXME, NameRequestError cannot really be rescued as it will be raised
493
- # when dispatching a later call. Rework the API to better match the spec.
494
- # @return [Service]
495
- def request_service(name)
496
- # Use RequestName, but asynchronously!
497
- # A synchronous call would not work with service activation, where
498
- # method calls to be serviced arrive before the reply for RequestName
499
- # (Ticket#29).
500
- proxy.RequestName(name, NAME_FLAG_REPLACE_EXISTING) do |rmsg, r|
501
- # check and report errors first
502
- raise rmsg if rmsg.is_a?(Error)
503
-
504
- details = if r == REQUEST_NAME_REPLY_IN_QUEUE
505
- other = proxy.GetNameOwner(name).first
506
- other_creds = proxy.GetConnectionCredentials(other).first
507
- "already owned by #{other}, #{other_creds.inspect}"
508
- else
509
- "error code #{r}"
510
- end
511
- raise NameRequestError, "Could not request #{name}, #{details}" unless r == REQUEST_NAME_REPLY_PRIMARY_OWNER
512
- end
513
- @service = Service.new(name, self)
514
- @service
35
+ send_hello
515
36
  end
516
37
 
517
38
  # Set up a ProxyObject for the bus itself, since the bus is introspectable.
@@ -520,10 +41,13 @@ module DBus
520
41
  # Returns the object.
521
42
  def proxy
522
43
  if @proxy.nil?
44
+ xml_filename = File.expand_path("org.freedesktop.DBus.xml", __dir__)
45
+ xml = File.read(xml_filename)
46
+
523
47
  path = "/org/freedesktop/DBus"
524
48
  dest = "org.freedesktop.DBus"
525
49
  pof = DBus::ProxyObjectFactory.new(
526
- DBUSXMLINTRO, self, dest, path,
50
+ xml, self, dest, path,
527
51
  api: ApiOptions::A0
528
52
  )
529
53
  @proxy = pof.build["org.freedesktop.DBus"]
@@ -531,72 +55,39 @@ module DBus
531
55
  @proxy
532
56
  end
533
57
 
534
- # @api private
535
- # Wait for a message to arrive. Return it once it is available.
536
- def wait_for_message
537
- @message_queue.pop # FIXME: EOFError
538
- end
539
-
540
- # @api private
541
- # Send a message _msg_ on to the bus. This is done synchronously, thus
542
- # the call will block until a reply message arrives.
543
- # @param msg [Message]
544
- # @param retc [Proc] the reply handler
545
- # @yieldparam rmsg [MethodReturnMessage] the reply
546
- # @yieldreturn [Array<Object>] the reply (out) parameters
547
- def send_sync(msg, &retc) # :yields: reply/return message
548
- return if msg.nil? # check if somethings wrong
549
-
550
- @message_queue.push(msg)
551
- @method_call_msgs[msg.serial] = msg
552
- @method_call_replies[msg.serial] = retc
553
-
554
- retm = wait_for_message
555
- return if retm.nil? # check if somethings wrong
556
-
557
- process(retm)
558
- while @method_call_replies.key? msg.serial
559
- retm = wait_for_message
560
- process(retm)
561
- end
562
- rescue EOFError
563
- new_err = DBus::Error.new("Connection dropped after we sent #{msg.inspect}")
564
- raise new_err
565
- end
566
-
567
- # @api private
568
- # Specify a code block that has to be executed when a reply for
569
- # message _msg_ is received.
570
- # @param msg [Message]
571
- def on_return(msg, &retc)
572
- # Have a better exception here
573
- if msg.message_type != Message::METHOD_CALL
574
- raise "on_return should only get method_calls"
575
- end
576
-
577
- @method_call_msgs[msg.serial] = msg
578
- @method_call_replies[msg.serial] = retc
58
+ # @param name [BusName] the requested name
59
+ # @param flags [Integer] TODO: explain and add a better non-numeric API for this
60
+ # @raise NameRequestError if we could not get the name
61
+ # @example Usage
62
+ # bus = DBus.session_bus
63
+ # bus.object_server.export(DBus::Object.new("/org/example/Test"))
64
+ # bus.request_name("org.example.Test")
65
+ # @see https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-request-name
66
+ def request_name(name, flags: 0)
67
+ name = BusName.new(name)
68
+ r = proxy.RequestName(name, flags).first
69
+ handle_return_of_request_name(r, name)
579
70
  end
580
71
 
581
72
  # Asks bus to send us messages matching mr, and execute slot when
582
73
  # received
583
74
  # @param match_rule [MatchRule,#to_s]
75
+ # @return [void]
584
76
  def add_match(match_rule, &slot)
585
- # check this is a signal.
586
77
  mrs = match_rule.to_s
587
- DBus.logger.debug "#{@signal_matchrules.size} rules, adding #{mrs.inspect}"
78
+ rule_existed = super(mrs, &slot)
588
79
  # don't ask for the same match if we override it
589
- unless @signal_matchrules.key?(mrs)
590
- DBus.logger.debug "Asked for a new match"
591
- proxy.AddMatch(mrs)
592
- end
593
- @signal_matchrules[mrs] = slot
80
+ return if rule_existed
81
+
82
+ DBus.logger.debug "Asked for a new match"
83
+ proxy.AddMatch(mrs)
594
84
  end
595
85
 
596
86
  # @param match_rule [MatchRule,#to_s]
87
+ # @return [void]
597
88
  def remove_match(match_rule)
598
89
  mrs = match_rule.to_s
599
- rule_existed = @signal_matchrules.delete(mrs).nil?
90
+ rule_existed = super(mrs)
600
91
  # don't remove nonexisting matches.
601
92
  return if rule_existed
602
93
 
@@ -605,97 +96,17 @@ module DBus
605
96
  proxy.RemoveMatch(mrs)
606
97
  end
607
98
 
608
- # @api private
609
- # Process a message _msg_ based on its type.
610
- # @param msg [Message]
611
- def process(msg)
612
- return if msg.nil? # check if somethings wrong
613
-
614
- case msg.message_type
615
- when Message::ERROR, Message::METHOD_RETURN
616
- raise InvalidPacketException if msg.reply_serial.nil?
617
-
618
- mcs = @method_call_replies[msg.reply_serial]
619
- if !mcs
620
- DBus.logger.debug "no return code for mcs: #{mcs.inspect} msg: #{msg.inspect}"
621
- else
622
- if msg.message_type == Message::ERROR
623
- mcs.call(Error.new(msg))
624
- else
625
- mcs.call(msg)
626
- end
627
- @method_call_replies.delete(msg.reply_serial)
628
- @method_call_msgs.delete(msg.reply_serial)
629
- end
630
- when DBus::Message::METHOD_CALL
631
- if msg.path == "/org/freedesktop/DBus"
632
- DBus.logger.debug "Got method call on /org/freedesktop/DBus"
633
- end
634
- node = @service.get_node(msg.path, create: false)
635
- # introspect a known path even if there is no object on it
636
- if node &&
637
- msg.interface == "org.freedesktop.DBus.Introspectable" &&
638
- msg.member == "Introspect"
639
- reply = Message.new(Message::METHOD_RETURN).reply_to(msg)
640
- reply.sender = @unique_name
641
- xml = node.to_xml(msg.path)
642
- reply.add_param(Type::STRING, xml)
643
- @message_queue.push(reply)
644
- # dispatch for an object
645
- elsif node&.object
646
- node.object.dispatch(msg)
647
- else
648
- reply = Message.error(msg, "org.freedesktop.DBus.Error.UnknownObject",
649
- "Object #{msg.path} doesn't exist")
650
- @message_queue.push(reply)
651
- end
652
- when DBus::Message::SIGNAL
653
- # the signal can match multiple different rules
654
- # clone to allow new signale handlers to be registered
655
- @signal_matchrules.dup.each do |mrs, slot|
656
- if DBus::MatchRule.new.from_s(mrs).match(msg)
657
- slot.call(msg)
658
- end
659
- end
660
- else
661
- # spec(Message Format): Unknown types must be ignored.
662
- DBus.logger.debug "Unknown message type: #{msg.message_type}"
663
- end
664
- rescue Exception => e
665
- raise msg.annotate_exception(e)
666
- end
667
-
668
- # Retrieves the Service with the given _name_.
669
- # @return [Service]
99
+ # Makes a {ProxyService} with the given *name*.
100
+ # Note that this succeeds even if the name does not exist and cannot be
101
+ # activated. It will only fail when calling a method.
102
+ # @return [ProxyService]
670
103
  def service(name)
671
104
  # The service might not exist at this time so we cannot really check
672
105
  # anything
673
- Service.new(name, self)
106
+ ProxyService.new(name, self)
674
107
  end
675
108
  alias [] service
676
109
 
677
- # @api private
678
- # Emit a signal event for the given _service_, object _obj_, interface
679
- # _intf_ and signal _sig_ with arguments _args_.
680
- # @param service [Service]
681
- # @param obj [DBus::Object]
682
- # @param intf [Interface]
683
- # @param sig [Signal]
684
- # @param args arguments for the signal
685
- def emit(service, obj, intf, sig, *args)
686
- m = Message.new(DBus::Message::SIGNAL)
687
- m.path = obj.path
688
- m.interface = intf.name
689
- m.member = sig.name
690
- m.sender = service.name
691
- i = 0
692
- sig.params.each do |par|
693
- m.add_param(par.type, args[i])
694
- i += 1
695
- end
696
- @message_queue.push(m)
697
- end
698
-
699
110
  ###########################################################################
700
111
  private
701
112
 
@@ -710,7 +121,6 @@ module DBus
710
121
  @unique_name = rmsg.destination
711
122
  DBus.logger.debug "Got hello reply. Our unique_name is #{@unique_name}"
712
123
  end
713
- @service = Service.new(@unique_name, self)
714
124
  end
715
125
  end
716
126
 
@@ -720,11 +130,10 @@ module DBus
720
130
  #
721
131
  # Use SessionBus, the non-singleton ASessionBus is
722
132
  # for the test suite.
723
- class ASessionBus < Connection
133
+ class ASessionBus < BusConnection
724
134
  # Get the the default session bus.
725
135
  def initialize
726
136
  super(self.class.session_bus_address)
727
- send_hello
728
137
  end
729
138
 
730
139
  def self.session_bus_address
@@ -771,11 +180,10 @@ module DBus
771
180
  #
772
181
  # Use SystemBus, the non-singleton ASystemBus is
773
182
  # for the test suite.
774
- class ASystemBus < Connection
183
+ class ASystemBus < BusConnection
775
184
  # Get the default system bus.
776
185
  def initialize
777
186
  super(self.class.system_bus_address)
778
- send_hello
779
187
  end
780
188
 
781
189
  def self.system_bus_address
@@ -794,12 +202,9 @@ module DBus
794
202
  #
795
203
  # you'll need to take care about authentification then, more info here:
796
204
  # https://gitlab.com/pangdudu/ruby-dbus/-/blob/master/README.rdoc
797
- class RemoteBus < Connection
798
- # Get the remote bus.
799
- def initialize(socket_name)
800
- super(socket_name)
801
- send_hello
802
- end
205
+ # TODO: keep the name but update the docs
206
+ # @deprecated just use BusConnection
207
+ class RemoteBus < BusConnection
803
208
  end
804
209
 
805
210
  # See ASystemBus
@@ -808,65 +213,14 @@ module DBus
808
213
  end
809
214
 
810
215
  # Shortcut for the {SystemBus} instance
811
- # @return [Connection]
216
+ # @return [BusConnection]
812
217
  def self.system_bus
813
218
  SystemBus.instance
814
219
  end
815
220
 
816
221
  # Shortcut for the {SessionBus} instance
817
- # @return [Connection]
222
+ # @return [BusConnection]
818
223
  def self.session_bus
819
224
  SessionBus.instance
820
225
  end
821
-
822
- # = Main event loop class.
823
- #
824
- # Class that takes care of handling message and signal events
825
- # asynchronously. *Note:* This is a native implement and therefore does
826
- # not integrate with a graphical widget set main loop.
827
- class Main
828
- # Create a new main event loop.
829
- def initialize
830
- @buses = {}
831
- @quitting = false
832
- end
833
-
834
- # Add a _bus_ to the list of buses to watch for events.
835
- def <<(bus)
836
- @buses[bus.message_queue.socket] = bus
837
- end
838
-
839
- # Quit a running main loop, to be used eg. from a signal handler
840
- def quit
841
- @quitting = true
842
- end
843
-
844
- # Run the main loop. This is a blocking call!
845
- def run
846
- # before blocking, empty the buffers
847
- # https://bugzilla.novell.com/show_bug.cgi?id=537401
848
- @buses.each_value do |b|
849
- while (m = b.message_queue.message_from_buffer_nonblock)
850
- b.process(m)
851
- end
852
- end
853
- while !@quitting && !@buses.empty?
854
- ready = IO.select(@buses.keys, [], [], 5) # timeout 5 seconds
855
- next unless ready # timeout exceeds so continue unless quitting
856
-
857
- ready.first.each do |socket|
858
- b = @buses[socket]
859
- begin
860
- b.message_queue.buffer_from_socket_nonblock
861
- rescue EOFError, SystemCallError
862
- @buses.delete socket # this bus died
863
- next
864
- end
865
- while (m = b.message_queue.message_from_buffer_nonblock)
866
- b.process(m)
867
- end
868
- end
869
- end
870
- end
871
- end
872
226
  end