ruby-dbus 0.22.1 → 0.23.0.beta2

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