ruby-dbus 0.16.0 → 0.25.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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +288 -3
  3. data/README.md +3 -5
  4. data/Rakefile +27 -12
  5. data/VERSION +1 -1
  6. data/doc/Reference.md +109 -10
  7. data/examples/doc/_extract_examples +7 -0
  8. data/examples/no-bus/pulseaudio.rb +50 -0
  9. data/examples/no-introspect/nm-test.rb +2 -0
  10. data/examples/no-introspect/tracker-test.rb +3 -1
  11. data/examples/rhythmbox/playpause.rb +2 -1
  12. data/examples/service/call_service.rb +2 -1
  13. data/examples/service/complex-property.rb +21 -0
  14. data/examples/service/service_newapi.rb +3 -3
  15. data/examples/simple/call_introspect.rb +1 -0
  16. data/examples/simple/get_id.rb +6 -3
  17. data/examples/simple/properties.rb +2 -0
  18. data/examples/utils/listnames.rb +10 -1
  19. data/examples/utils/notify.rb +1 -0
  20. data/lib/dbus/api_options.rb +9 -0
  21. data/lib/dbus/auth.rb +307 -217
  22. data/lib/dbus/bus.rb +155 -588
  23. data/lib/dbus/bus_name.rb +12 -8
  24. data/lib/dbus/connection.rb +363 -0
  25. data/lib/dbus/core_ext/class/attribute.rb +1 -1
  26. data/lib/dbus/data.rb +821 -0
  27. data/lib/dbus/emits_changed_signal.rb +83 -0
  28. data/lib/dbus/error.rb +4 -2
  29. data/lib/dbus/introspect.rb +133 -31
  30. data/lib/dbus/logger.rb +6 -3
  31. data/lib/dbus/main.rb +66 -0
  32. data/lib/dbus/marshall.rb +247 -296
  33. data/lib/dbus/matchrule.rb +16 -16
  34. data/lib/dbus/message.rb +66 -57
  35. data/lib/dbus/message_queue.rb +40 -25
  36. data/lib/dbus/node_tree.rb +105 -0
  37. data/lib/dbus/object.rb +442 -33
  38. data/lib/dbus/object_manager.rb +61 -0
  39. data/lib/dbus/object_path.rb +11 -6
  40. data/lib/dbus/object_server.rb +149 -0
  41. data/lib/dbus/org.freedesktop.DBus.xml +97 -0
  42. data/lib/dbus/platform.rb +26 -0
  43. data/lib/dbus/proxy_object.rb +30 -11
  44. data/lib/dbus/proxy_object_factory.rb +13 -7
  45. data/lib/dbus/proxy_object_interface.rb +63 -30
  46. data/lib/dbus/proxy_service.rb +107 -0
  47. data/lib/dbus/raw_message.rb +91 -0
  48. data/lib/dbus/type.rb +319 -86
  49. data/lib/dbus/xml.rb +40 -19
  50. data/lib/dbus.rb +28 -17
  51. data/ruby-dbus.gemspec +13 -6
  52. data/spec/async_spec.rb +2 -0
  53. data/spec/auth_spec.rb +225 -0
  54. data/spec/binding_spec.rb +2 -0
  55. data/spec/bus_and_xml_backend_spec.rb +5 -1
  56. data/spec/bus_connection_spec.rb +93 -0
  57. data/spec/bus_name_spec.rb +3 -1
  58. data/spec/bus_spec.rb +2 -0
  59. data/spec/byte_array_spec.rb +2 -0
  60. data/spec/client_robustness_spec.rb +4 -2
  61. data/spec/connection_spec.rb +37 -0
  62. data/spec/coverage_helper.rb +39 -0
  63. data/spec/data/marshall.yaml +1667 -0
  64. data/spec/data_spec.rb +673 -0
  65. data/spec/dbus_spec.rb +22 -0
  66. data/spec/emits_changed_signal_spec.rb +58 -0
  67. data/spec/err_msg_spec.rb +2 -0
  68. data/spec/introspect_xml_parser_spec.rb +2 -0
  69. data/spec/introspection_spec.rb +2 -0
  70. data/spec/main_loop_spec.rb +17 -1
  71. data/spec/message_spec.rb +21 -0
  72. data/spec/mock-service/cockpit-dbustests.rb +29 -0
  73. data/spec/mock-service/com.redhat.Cockpit.DBusTests.xml +180 -0
  74. data/spec/mock-service/org.ruby.service.service +4 -0
  75. data/spec/mock-service/org.rubygems.ruby_dbus.DBusTests.service +4 -0
  76. data/spec/{service_newapi.rb → mock-service/spaghetti-monster.rb} +122 -75
  77. data/spec/node_spec.rb +65 -0
  78. data/spec/object_manager_spec.rb +33 -0
  79. data/spec/object_path_spec.rb +3 -0
  80. data/spec/object_server_spec.rb +138 -0
  81. data/spec/object_spec.rb +194 -0
  82. data/spec/packet_marshaller_spec.rb +41 -0
  83. data/spec/packet_unmarshaller_spec.rb +248 -0
  84. data/spec/platform_spec.rb +14 -0
  85. data/spec/property_spec.rb +214 -12
  86. data/spec/proxy_object_interface_spec.rb +35 -0
  87. data/spec/proxy_object_spec.rb +2 -0
  88. data/spec/{bus_driver_spec.rb → proxy_service_spec.rb} +15 -8
  89. data/spec/raw_message_spec.rb +32 -0
  90. data/spec/server_robustness_spec.rb +20 -6
  91. data/spec/session_bus_spec.rb +36 -1
  92. data/spec/session_bus_spec_manual.rb +2 -0
  93. data/spec/signal_spec.rb +16 -3
  94. data/spec/spec_helper.rb +25 -33
  95. data/spec/thread_safety_spec.rb +55 -12
  96. data/spec/tools/dbus-launch-simple +9 -6
  97. data/spec/tools/dbus-limited-session.conf +32 -0
  98. data/spec/tools/test_env +26 -6
  99. data/spec/type_spec.rb +214 -6
  100. data/spec/value_spec.rb +16 -1
  101. data/spec/variant_spec.rb +4 -2
  102. data/spec/zzz_quit_spec.rb +16 -0
  103. metadata +92 -20
  104. data/examples/gdbus/gdbus +0 -257
  105. data/examples/gdbus/gdbus.glade +0 -98
  106. data/examples/gdbus/launch.sh +0 -4
  107. data/spec/server_spec.rb +0 -53
  108. data/spec/tools/test_server +0 -39
data/lib/dbus/bus.rb CHANGED
@@ -1,421 +1,38 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # dbus.rb - Module containing the low-level D-Bus implementation
2
4
  #
3
5
  # This file is part of the ruby-dbus project
4
6
  # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
5
7
  #
6
- # This library is free software; you caan redistribute it and/or
8
+ # This library is free software; you can redistribute it and/or
7
9
  # modify it under the terms of the GNU Lesser General Public
8
10
  # License, version 2.1 as published by the Free Software Foundation.
9
11
  # See the file "COPYING" for the exact licensing terms.
10
12
 
11
13
  require "socket"
12
- require "thread"
13
14
  require "singleton"
14
15
 
16
+ require_relative "connection"
17
+
15
18
  # = D-Bus main module
16
19
  #
17
20
  # Module containing all the D-Bus modules and classes.
18
21
  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 = BusName.new(name)
32
- @bus = bus
33
- @root = Node.new("/")
34
- end
35
-
36
- # Determine whether the service name already exists.
37
- def exists?
38
- bus.proxy.ListNames[0].member?(@name)
39
- end
40
-
41
- # Perform an introspection on all the objects on the service
42
- # (starting recursively from the root).
43
- def introspect
44
- raise NotImplementedError if block_given?
45
-
46
- rec_introspect(@root, "/")
47
- self
48
- end
49
-
50
- # Retrieves an object at the given _path_.
51
- # @return [ProxyObject]
52
- def [](path)
53
- object(path, api: ApiOptions::A1)
54
- end
55
-
56
- # Retrieves an object at the given _path_
57
- # whose methods always return an array.
58
- # @return [ProxyObject]
59
- def object(path, api: ApiOptions::A0)
60
- node = get_node(path, _create = true)
61
- if node.object.nil? || node.object.api != api
62
- node.object = ProxyObject.new(
63
- @bus, @name, path,
64
- api: api
65
- )
66
- end
67
- node.object
68
- end
69
-
70
- # Export an object _obj_ (an DBus::Object subclass instance).
71
- def export(obj)
72
- obj.service = self
73
- get_node(obj.path, true).object = obj
74
- end
75
-
76
- # Undo exporting an object _obj_.
77
- # Raises ArgumentError if it is not a DBus::Object.
78
- # Returns the object, or false if _obj_ was not exported.
79
- def unexport(obj)
80
- raise ArgumentError, "DBus::Service#unexport() expects a DBus::Object argument" unless obj.is_a?(DBus::Object)
81
- return false unless obj.path
82
- last_path_separator_idx = obj.path.rindex("/")
83
- parent_path = obj.path[1..last_path_separator_idx - 1]
84
- node_name = obj.path[last_path_separator_idx + 1..-1]
85
-
86
- parent_node = get_node(parent_path, false)
87
- return false unless parent_node
88
- obj.service = nil
89
- parent_node.delete(node_name).object
90
- end
91
-
92
- # Get the object node corresponding to the given _path_. if _create_ is
93
- # true, the the nodes in the path are created if they do not already exist.
94
- def get_node(path, create = false)
95
- n = @root
96
- path.sub(%r{^/}, "").split("/").each do |elem|
97
- if !(n[elem])
98
- return nil if !create
99
- n[elem] = Node.new(elem)
100
- end
101
- n = n[elem]
102
- end
103
- if n.nil?
104
- DBus.logger.debug "Warning, unknown object #{path}"
105
- end
106
- n
107
- end
108
-
109
- #########
110
-
111
- private
112
-
113
- #########
114
-
115
- # Perform a recursive retrospection on the given current _node_
116
- # on the given _path_.
117
- def rec_introspect(node, path)
118
- xml = bus.introspect_data(@name, path)
119
- intfs, subnodes = IntrospectXMLParser.new(xml).parse
120
- subnodes.each do |nodename|
121
- subnode = node[nodename] = Node.new(nodename)
122
- subpath = if path == "/"
123
- "/" + nodename
124
- else
125
- path + "/" + nodename
126
- end
127
- rec_introspect(subnode, subpath)
128
- end
129
- return if intfs.empty?
130
- node.object = ProxyObjectFactory.new(xml, @bus, @name, path).build
131
- end
132
- end
133
-
134
- # = Object path node class
135
- #
136
- # Class representing a node on an object path.
137
- class Node < Hash
138
- # The D-Bus object contained by the node.
139
- attr_accessor :object
140
- # The name of the node.
141
- attr_reader :name
142
-
143
- # Create a new node with a given _name_.
144
- def initialize(name)
145
- @name = name
146
- @object = nil
147
- end
148
-
149
- # Return an XML string representation of the node.
150
- # It is shallow, not recursing into subnodes
151
- def to_xml
152
- xml = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
153
- "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
154
- <node>
155
- '
156
- each_pair do |k, _v|
157
- xml += "<node name=\"#{k}\" />"
158
- end
159
- if @object
160
- @object.intfs.each_pair do |_k, v|
161
- xml += %(<interface name="#{v.name}">\n)
162
- v.methods.each_value { |m| xml += m.to_xml }
163
- v.signals.each_value { |m| xml += m.to_xml }
164
- xml += "</interface>\n"
165
- end
166
- end
167
- xml += "</node>"
168
- xml
169
- end
170
-
171
- # Return inspect information of the node.
172
- def inspect
173
- # Need something here
174
- "<DBus::Node #{sub_inspect}>"
175
- end
176
-
177
- # Return instance inspect information, used by Node#inspect.
178
- def sub_inspect
179
- s = ""
180
- if !@object.nil?
181
- s += format("%x ", @object.object_id)
182
- end
183
- s + "{" + keys.collect { |k| "#{k} => #{self[k].sub_inspect}" }.join(",") + "}"
184
- end
185
- end # class Inspect
186
-
187
- # FIXME: rename Connection to Bus?
188
-
189
- # D-Bus main connection class
190
- #
191
- # Main class that maintains a connection to a bus and can handle incoming
192
- # and outgoing messages.
193
- 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
194
25
  # The unique name (by specification) of the message.
195
26
  attr_reader :unique_name
196
- # pop and push messages here
197
- attr_reader :message_queue
198
-
199
- # Create a new connection to the bus for a given connect _path_. _path_
200
- # format is described in the D-Bus specification:
201
- # http://dbus.freedesktop.org/doc/dbus-specification.html#addresses
202
- # and is something like:
203
- # "transport1:key1=value1,key2=value2;transport2:key1=value1,key2=value2"
204
- # e.g. "unix:path=/tmp/dbus-test" or "tcp:host=localhost,port=2687"
205
- def initialize(path)
206
- @message_queue = MessageQueue.new(path)
27
+
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
207
33
  @unique_name = nil
208
- @method_call_replies = {}
209
- @method_call_msgs = {}
210
- @signal_matchrules = {}
211
34
  @proxy = nil
212
- @object_root = Node.new("/")
213
- end
214
-
215
- # Dispatch all messages that are available in the queue,
216
- # but do not block on the queue.
217
- # Called by a main loop when something is available in the queue
218
- def dispatch_message_queue
219
- while (msg = @message_queue.pop(:non_block)) # FIXME: EOFError
220
- process(msg)
221
- end
222
- end
223
-
224
- # Tell a bus to register itself on the glib main loop
225
- def glibize
226
- require "glib2"
227
- # Circumvent a ruby-glib bug
228
- @channels ||= []
229
-
230
- gio = GLib::IOChannel.new(@message_queue.socket.fileno)
231
- @channels << gio
232
- gio.add_watch(GLib::IOChannel::IN) do |_c, _ch|
233
- dispatch_message_queue
234
- true
235
- end
236
- end
237
-
238
- # FIXME: describe the following names, flags and constants.
239
- # See DBus spec for definition
240
- NAME_FLAG_ALLOW_REPLACEMENT = 0x1
241
- NAME_FLAG_REPLACE_EXISTING = 0x2
242
- NAME_FLAG_DO_NOT_QUEUE = 0x4
243
-
244
- REQUEST_NAME_REPLY_PRIMARY_OWNER = 0x1
245
- REQUEST_NAME_REPLY_IN_QUEUE = 0x2
246
- REQUEST_NAME_REPLY_EXISTS = 0x3
247
- REQUEST_NAME_REPLY_ALREADY_OWNER = 0x4
248
-
249
- DBUSXMLINTRO = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
250
- "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
251
- <node>
252
- <interface name="org.freedesktop.DBus.Introspectable">
253
- <method name="Introspect">
254
- <arg name="data" direction="out" type="s"/>
255
- </method>
256
- </interface>
257
- <interface name="org.freedesktop.DBus">
258
- <method name="RequestName">
259
- <arg direction="in" type="s"/>
260
- <arg direction="in" type="u"/>
261
- <arg direction="out" type="u"/>
262
- </method>
263
- <method name="ReleaseName">
264
- <arg direction="in" type="s"/>
265
- <arg direction="out" type="u"/>
266
- </method>
267
- <method name="StartServiceByName">
268
- <arg direction="in" type="s"/>
269
- <arg direction="in" type="u"/>
270
- <arg direction="out" type="u"/>
271
- </method>
272
- <method name="Hello">
273
- <arg direction="out" type="s"/>
274
- </method>
275
- <method name="NameHasOwner">
276
- <arg direction="in" type="s"/>
277
- <arg direction="out" type="b"/>
278
- </method>
279
- <method name="ListNames">
280
- <arg direction="out" type="as"/>
281
- </method>
282
- <method name="ListActivatableNames">
283
- <arg direction="out" type="as"/>
284
- </method>
285
- <method name="AddMatch">
286
- <arg direction="in" type="s"/>
287
- </method>
288
- <method name="RemoveMatch">
289
- <arg direction="in" type="s"/>
290
- </method>
291
- <method name="GetNameOwner">
292
- <arg direction="in" type="s"/>
293
- <arg direction="out" type="s"/>
294
- </method>
295
- <method name="ListQueuedOwners">
296
- <arg direction="in" type="s"/>
297
- <arg direction="out" type="as"/>
298
- </method>
299
- <method name="GetConnectionUnixUser">
300
- <arg direction="in" type="s"/>
301
- <arg direction="out" type="u"/>
302
- </method>
303
- <method name="GetConnectionUnixProcessID">
304
- <arg direction="in" type="s"/>
305
- <arg direction="out" type="u"/>
306
- </method>
307
- <method name="GetConnectionSELinuxSecurityContext">
308
- <arg direction="in" type="s"/>
309
- <arg direction="out" type="ay"/>
310
- </method>
311
- <method name="ReloadConfig">
312
- </method>
313
- <signal name="NameOwnerChanged">
314
- <arg type="s"/>
315
- <arg type="s"/>
316
- <arg type="s"/>
317
- </signal>
318
- <signal name="NameLost">
319
- <arg type="s"/>
320
- </signal>
321
- <signal name="NameAcquired">
322
- <arg type="s"/>
323
- </signal>
324
- </interface>
325
- </node>
326
- '.freeze
327
- # This apostroph is for syntax highlighting editors confused by above xml: "
328
-
329
- # @api private
330
- # Send a _message_.
331
- # If _reply_handler_ is not given, wait for the reply
332
- # and return the reply, or raise the error.
333
- # If _reply_handler_ is given, it will be called when the reply
334
- # eventually arrives, with the reply message as the 1st param
335
- # and its params following
336
- def send_sync_or_async(message, &reply_handler)
337
- ret = nil
338
- if reply_handler.nil?
339
- send_sync(message) do |rmsg|
340
- raise rmsg if rmsg.is_a?(Error)
341
- ret = rmsg.params
342
- end
343
- else
344
- on_return(message) do |rmsg|
345
- if rmsg.is_a?(Error)
346
- reply_handler.call(rmsg)
347
- else
348
- reply_handler.call(rmsg, * rmsg.params)
349
- end
350
- end
351
- @message_queue.push(message)
352
- end
353
- ret
354
- end
355
-
356
- # @api private
357
- def introspect_data(dest, path, &reply_handler)
358
- m = DBus::Message.new(DBus::Message::METHOD_CALL)
359
- m.path = path
360
- m.interface = "org.freedesktop.DBus.Introspectable"
361
- m.destination = dest
362
- m.member = "Introspect"
363
- m.sender = unique_name
364
- if reply_handler.nil?
365
- send_sync_or_async(m).first
366
- else
367
- send_sync_or_async(m) do |*args|
368
- # TODO: test async introspection, is it used at all?
369
- args.shift # forget the message, pass only the text
370
- reply_handler.call(*args)
371
- nil
372
- end
373
- end
374
- end
375
-
376
- # @api private
377
- # Issues a call to the org.freedesktop.DBus.Introspectable.Introspect method
378
- # _dest_ is the service and _path_ the object path you want to introspect
379
- # If a code block is given, the introspect call in asynchronous. If not
380
- # data is returned
381
- #
382
- # FIXME: link to ProxyObject data definition
383
- # The returned object is a ProxyObject that has methods you can call to
384
- # issue somme METHOD_CALL messages, and to setup to receive METHOD_RETURN
385
- def introspect(dest, path)
386
- if !block_given?
387
- # introspect in synchronous !
388
- data = introspect_data(dest, path)
389
- pof = DBus::ProxyObjectFactory.new(data, self, dest, path)
390
- pof.build
391
- else
392
- introspect_data(dest, path) do |async_data|
393
- yield(DBus::ProxyObjectFactory.new(async_data, self, dest, path).build)
394
- end
395
- end
396
- end
397
-
398
- # Exception raised when a service name is requested that is not available.
399
- class NameRequestError < Exception
400
- end
401
-
402
- # Attempt to request a service _name_.
403
- #
404
- # FIXME, NameRequestError cannot really be rescued as it will be raised
405
- # when dispatching a later call. Rework the API to better match the spec.
406
- # @return [Service]
407
- def request_service(name)
408
- # Use RequestName, but asynchronously!
409
- # A synchronous call would not work with service activation, where
410
- # method calls to be serviced arrive before the reply for RequestName
411
- # (Ticket#29).
412
- proxy.RequestName(name, NAME_FLAG_REPLACE_EXISTING) do |rmsg, r|
413
- # check and report errors first
414
- raise rmsg if rmsg.is_a?(Error)
415
- raise NameRequestError unless r == REQUEST_NAME_REPLY_PRIMARY_OWNER
416
- end
417
- @service = Service.new(name, self)
418
- @service
35
+ send_hello
419
36
  end
420
37
 
421
38
  # Set up a ProxyObject for the bus itself, since the bus is introspectable.
@@ -424,10 +41,13 @@ module DBus
424
41
  # Returns the object.
425
42
  def proxy
426
43
  if @proxy.nil?
44
+ xml_filename = File.expand_path("org.freedesktop.DBus.xml", __dir__)
45
+ xml = File.read(xml_filename)
46
+
427
47
  path = "/org/freedesktop/DBus"
428
48
  dest = "org.freedesktop.DBus"
429
49
  pof = DBus::ProxyObjectFactory.new(
430
- DBUSXMLINTRO, self, dest, path,
50
+ xml, self, dest, path,
431
51
  api: ApiOptions::A0
432
52
  )
433
53
  @proxy = pof.build["org.freedesktop.DBus"]
@@ -435,151 +55,145 @@ module DBus
435
55
  @proxy
436
56
  end
437
57
 
438
- # @api private
439
- # Wait for a message to arrive. Return it once it is available.
440
- def wait_for_message
441
- @message_queue.pop # FIXME: EOFError
58
+ # Request a well-known name so that clients can find us.
59
+ # @note Parameters other than *name* are advanced, you probably don't need them.
60
+ #
61
+ # With no boolean flags, running a second instance of a program that calls `request_name`
62
+ # will result in the second one failing, which this library translates to an exception.
63
+ # If you want the second instance to take over, you need both
64
+ # `allow_replacement: true` and `replace_existing: true.`
65
+ #
66
+ # @param name [BusName] the requested name
67
+ # @param replace_existing [Boolean]
68
+ # Replace an existing owner of the name, if that owner set *allow_replacement*.
69
+ # @param allow_replacement [Boolean]
70
+ # Other connections that specify *replace_existing* will be able to take
71
+ # the name from us. We will get {#on_name_lost NameLost}. If we specified *queue*
72
+ # we may get the name again, with {#on_name_acquired NameAcquired}.
73
+ # @param queue [Boolean]
74
+ # Affects the behavior when the bus denies the name (sooner or later).
75
+ # - If `false` (default), it is recommended to let the `NameRequestError` fall through and end your program.
76
+ # - If `true`, you should `rescue` the `NameRequestError` and set up
77
+ # {#on_name_acquired NameAcquired} and {#on_name_lost NameLost} handlers.
78
+ # Meanwhile, the bus will put us in a queue waiting for *name* (this is the "sooner" case).
79
+ # Also, if we had `allow_replacement: true`, another connection can cause us
80
+ # to lose the name. We will be moved back to the queue, waiting for when the other owners give up
81
+ # (the "later" case).
82
+ # @param flags [Integer,nil]
83
+ # If specified, overrides the boolean parameters.
84
+ # Use a bitwise sum `|` of:
85
+ # - NAME_FLAG_ALLOW_REPLACEMENT
86
+ # - NAME_FLAG_REPLACE_EXISTING
87
+ # - NAME_FLAG_DO_NOT_QUEUE
88
+ # Note that `0` implies `queue: true`.
89
+ #
90
+ # @return [REQUEST_NAME_REPLY_PRIMARY_OWNER,REQUEST_NAME_REPLY_ALREADY_OWNER] on success
91
+ # @raise [NameRequestError] with #error_code REQUEST_NAME_REPLY_EXISTS or REQUEST_NAME_REPLY_IN_QUEUE, on failure
92
+ # @raise DBus::Error another way to fail is being prohibited to own the name
93
+ # which is the default on the system bus
94
+ #
95
+ # @see https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-request-name
96
+ #
97
+ # @example Simple usage
98
+ # bus = DBus.session_bus
99
+ # bus.object_server.export(DBus::Object.new("/org/example/Test"))
100
+ # bus.request_name("org.example.Test")
101
+ # # main loop
102
+ #
103
+ # @example Second instance taking over
104
+ # bus = DBus.session_bus
105
+ # bus.object_server.export(DBus::Object.new("/org/example/Test"))
106
+ # bus.on_name_lost { exit }
107
+ # bus.request_name("org.example.Test", allow_replacement: true, replace_existing: true)
108
+ # # main loop
109
+ #
110
+ # @example Second instance waiting for its turn
111
+ # bus = DBus.session_bus
112
+ # bus.object_server.export(DBus::Object.new("/org/example/Test"))
113
+ # bus.on_name_acquired { @owner = true }
114
+ # begin
115
+ # bus.request_name("org.example.Test", queue: true)
116
+ # rescue DBus::Connection::NameRequestError => e
117
+ # @owner = false
118
+ # end
119
+ # # main loop
120
+ def request_name(name,
121
+ allow_replacement: false,
122
+ replace_existing: false,
123
+ queue: false,
124
+ flags: nil)
125
+ if flags.nil?
126
+ flags = (allow_replacement ? NAME_FLAG_ALLOW_REPLACEMENT : 0) |
127
+ (replace_existing ? NAME_FLAG_REPLACE_EXISTING : 0) |
128
+ (queue ? 0 : NAME_FLAG_DO_NOT_QUEUE)
129
+ end
130
+ name = BusName.new(name)
131
+ r = proxy.RequestName(name, flags).first
132
+ handle_return_of_request_name(r, name)
442
133
  end
443
134
 
444
- # @api private
445
- # Send a message _m_ on to the bus. This is done synchronously, thus
446
- # the call will block until a reply message arrives.
447
- def send_sync(m, &retc) # :yields: reply/return message
448
- return if m.nil? # check if somethings wrong
449
- @message_queue.push(m)
450
- @method_call_msgs[m.serial] = m
451
- @method_call_replies[m.serial] = retc
452
-
453
- retm = wait_for_message
454
- return if retm.nil? # check if somethings wrong
455
-
456
- process(retm)
457
- while @method_call_replies.key? m.serial
458
- retm = wait_for_message
459
- process(retm)
460
- end
461
- rescue EOFError
462
- new_err = DBus::Error.new("Connection dropped after we sent #{m.inspect}")
463
- raise new_err
135
+ # The caller has released his claim on the given name.
136
+ # Either the caller was the primary owner of the name, and the name is now unused
137
+ # or taken by somebody waiting in the queue for the name,
138
+ # or the caller was waiting in the queue for the name and has now been removed from the queue.
139
+ RELEASE_NAME_REPLY_RELEASED = 1
140
+ # The given name does not exist on this bus.
141
+ RELEASE_NAME_REPLY_NON_EXISTENT = 2
142
+ # The caller was not the primary owner of this name, and was also not waiting in the queue to own this name.
143
+ RELEASE_NAME_REPLY_NOT_OWNER = 3
144
+
145
+ # @param name [BusName] the name to release
146
+ def release_name(name)
147
+ name = BusName.new(name)
148
+ proxy.ReleaseName(name).first
464
149
  end
465
150
 
466
- # @api private
467
- # Specify a code block that has to be executed when a reply for
468
- # message _m_ is received.
469
- def on_return(m, &retc)
470
- # Have a better exception here
471
- if m.message_type != Message::METHOD_CALL
472
- raise "on_return should only get method_calls"
473
- end
474
- @method_call_msgs[m.serial] = m
475
- @method_call_replies[m.serial] = retc
151
+ def on_name_acquired(&handler)
152
+ proxy.on_signal("NameAcquired", &handler)
153
+ end
154
+
155
+ def on_name_lost(&handler)
156
+ proxy.on_signal("NameLost", &handler)
476
157
  end
477
158
 
478
159
  # Asks bus to send us messages matching mr, and execute slot when
479
160
  # received
480
- def add_match(mr, &slot)
481
- # check this is a signal.
482
- mrs = mr.to_s
483
- DBus.logger.debug "#{@signal_matchrules.size} rules, adding #{mrs.inspect}"
161
+ # @param match_rule [MatchRule,#to_s]
162
+ # @return [void]
163
+ def add_match(match_rule, &slot)
164
+ mrs = match_rule.to_s
165
+ rule_existed = super(mrs, &slot)
484
166
  # don't ask for the same match if we override it
485
- unless @signal_matchrules.key?(mrs)
486
- DBus.logger.debug "Asked for a new match"
487
- proxy.AddMatch(mrs)
488
- end
489
- @signal_matchrules[mrs] = slot
167
+ return if rule_existed
168
+
169
+ DBus.logger.debug "Asked for a new match"
170
+ proxy.AddMatch(mrs)
490
171
  end
491
172
 
492
- def remove_match(mr)
493
- mrs = mr.to_s
494
- rule_existed = @signal_matchrules.delete(mrs).nil?
173
+ # @param match_rule [MatchRule,#to_s]
174
+ # @return [void]
175
+ def remove_match(match_rule)
176
+ mrs = match_rule.to_s
177
+ rule_existed = super(mrs)
495
178
  # don't remove nonexisting matches.
496
179
  return if rule_existed
180
+
497
181
  # FIXME: if we do try, the Error.MatchRuleNotFound is *not* raised
498
182
  # and instead is reported as "no return code for nil"
499
183
  proxy.RemoveMatch(mrs)
500
184
  end
501
185
 
502
- # @api private
503
- # Process a message _m_ based on its type.
504
- def process(m)
505
- return if m.nil? # check if somethings wrong
506
- case m.message_type
507
- when Message::ERROR, Message::METHOD_RETURN
508
- raise InvalidPacketException if m.reply_serial.nil?
509
- mcs = @method_call_replies[m.reply_serial]
510
- if !mcs
511
- DBus.logger.debug "no return code for mcs: #{mcs.inspect} m: #{m.inspect}"
512
- else
513
- if m.message_type == Message::ERROR
514
- mcs.call(Error.new(m))
515
- else
516
- mcs.call(m)
517
- end
518
- @method_call_replies.delete(m.reply_serial)
519
- @method_call_msgs.delete(m.reply_serial)
520
- end
521
- when DBus::Message::METHOD_CALL
522
- if m.path == "/org/freedesktop/DBus"
523
- DBus.logger.debug "Got method call on /org/freedesktop/DBus"
524
- end
525
- node = @service.get_node(m.path)
526
- if !node
527
- reply = Message.error(m, "org.freedesktop.DBus.Error.UnknownObject",
528
- "Object #{m.path} doesn't exist")
529
- @message_queue.push(reply)
530
- # handle introspectable as an exception:
531
- elsif m.interface == "org.freedesktop.DBus.Introspectable" &&
532
- m.member == "Introspect"
533
- reply = Message.new(Message::METHOD_RETURN).reply_to(m)
534
- reply.sender = @unique_name
535
- reply.add_param(Type::STRING, node.to_xml)
536
- @message_queue.push(reply)
537
- else
538
- obj = node.object
539
- return if obj.nil? # FIXME, pushes no reply
540
- obj.dispatch(m) if obj
541
- end
542
- when DBus::Message::SIGNAL
543
- # the signal can match multiple different rules
544
- # clone to allow new signale handlers to be registered
545
- @signal_matchrules.dup.each do |mrs, slot|
546
- if DBus::MatchRule.new.from_s(mrs).match(m)
547
- slot.call(m)
548
- end
549
- end
550
- else
551
- DBus.logger.debug "Unknown message type: #{m.message_type}"
552
- end
553
- rescue Exception => ex
554
- raise m.annotate_exception(ex)
555
- end
556
-
557
- # Retrieves the Service with the given _name_.
558
- # @return [Service]
186
+ # Makes a {ProxyService} with the given *name*.
187
+ # Note that this succeeds even if the name does not exist and cannot be
188
+ # activated. It will only fail when calling a method.
189
+ # @return [ProxyService]
559
190
  def service(name)
560
191
  # The service might not exist at this time so we cannot really check
561
192
  # anything
562
- Service.new(name, self)
193
+ ProxyService.new(name, self)
563
194
  end
564
195
  alias [] service
565
196
 
566
- # @api private
567
- # Emit a signal event for the given _service_, object _obj_, interface
568
- # _intf_ and signal _sig_ with arguments _args_.
569
- def emit(service, obj, intf, sig, *args)
570
- m = Message.new(DBus::Message::SIGNAL)
571
- m.path = obj.path
572
- m.interface = intf.name
573
- m.member = sig.name
574
- m.sender = service.name
575
- i = 0
576
- sig.params.each do |par|
577
- m.add_param(par.type, args[i])
578
- i += 1
579
- end
580
- @message_queue.push(m)
581
- end
582
-
583
197
  ###########################################################################
584
198
  private
585
199
 
@@ -594,9 +208,8 @@ module DBus
594
208
  @unique_name = rmsg.destination
595
209
  DBus.logger.debug "Got hello reply. Our unique_name is #{@unique_name}"
596
210
  end
597
- @service = Service.new(@unique_name, self)
598
211
  end
599
- end # class Connection
212
+ end
600
213
 
601
214
  # = D-Bus session bus class
602
215
  #
@@ -604,17 +217,17 @@ module DBus
604
217
  #
605
218
  # Use SessionBus, the non-singleton ASessionBus is
606
219
  # for the test suite.
607
- class ASessionBus < Connection
220
+ class ASessionBus < BusConnection
608
221
  # Get the the default session bus.
609
222
  def initialize
610
223
  super(self.class.session_bus_address)
611
- send_hello
612
224
  end
613
225
 
614
226
  def self.session_bus_address
615
227
  ENV["DBUS_SESSION_BUS_ADDRESS"] ||
616
228
  address_from_file ||
617
- "launchd:env=DBUS_LAUNCHD_SESSION_BUS_SOCKET"
229
+ ("launchd:env=DBUS_LAUNCHD_SESSION_BUS_SOCKET" if Platform.macos?) ||
230
+ (raise NotImplementedError, "Cannot find session bus; sorry, haven't figured out autolaunch yet")
618
231
  end
619
232
 
620
233
  def self.address_from_file
@@ -622,6 +235,7 @@ module DBus
622
235
  # traditional dbus uses /var/lib/dbus/machine-id
623
236
  machine_id_path = Dir["{/etc,/var/lib/dbus,/var/db/dbus}/machine-id"].first
624
237
  return nil unless machine_id_path
238
+
625
239
  machine_id = File.read(machine_id_path).chomp
626
240
 
627
241
  display = ENV["DISPLAY"][/:(\d+)\.?/, 1]
@@ -643,6 +257,9 @@ module DBus
643
257
  include Singleton
644
258
  end
645
259
 
260
+ # Default socket name for the system bus.
261
+ SYSTEM_BUS_ADDRESS = "unix:path=/var/run/dbus/system_bus_socket"
262
+
646
263
  # = D-Bus system bus class
647
264
  #
648
265
  # The system bus is a system-wide bus mostly used for global or
@@ -650,11 +267,14 @@ module DBus
650
267
  #
651
268
  # Use SystemBus, the non-singleton ASystemBus is
652
269
  # for the test suite.
653
- class ASystemBus < Connection
270
+ class ASystemBus < BusConnection
654
271
  # Get the default system bus.
655
272
  def initialize
656
- super(SystemSocketName)
657
- send_hello
273
+ super(self.class.system_bus_address)
274
+ end
275
+
276
+ def self.system_bus_address
277
+ ENV["DBUS_SYSTEM_BUS_ADDRESS"] || SYSTEM_BUS_ADDRESS
658
278
  end
659
279
  end
660
280
 
@@ -668,13 +288,10 @@ module DBus
668
288
  # (for Unix-socket) unix:path=/tmp/my_funky_bus_socket
669
289
  #
670
290
  # you'll need to take care about authentification then, more info here:
671
- # http://github.com/pangdudu/ruby-dbus/blob/master/README.rdoc
672
- class RemoteBus < Connection
673
- # Get the remote bus.
674
- def initialize(socket_name)
675
- super(socket_name)
676
- send_hello
677
- end
291
+ # https://gitlab.com/pangdudu/ruby-dbus/-/blob/master/README.rdoc
292
+ # TODO: keep the name but update the docs
293
+ # @deprecated just use BusConnection
294
+ class RemoteBus < BusConnection
678
295
  end
679
296
 
680
297
  # See ASystemBus
@@ -683,64 +300,14 @@ module DBus
683
300
  end
684
301
 
685
302
  # Shortcut for the {SystemBus} instance
686
- # @return [Connection]
303
+ # @return [BusConnection]
687
304
  def self.system_bus
688
305
  SystemBus.instance
689
306
  end
690
307
 
691
308
  # Shortcut for the {SessionBus} instance
692
- # @return [Connection]
309
+ # @return [BusConnection]
693
310
  def self.session_bus
694
311
  SessionBus.instance
695
312
  end
696
-
697
- # = Main event loop class.
698
- #
699
- # Class that takes care of handling message and signal events
700
- # asynchronously. *Note:* This is a native implement and therefore does
701
- # not integrate with a graphical widget set main loop.
702
- class Main
703
- # Create a new main event loop.
704
- def initialize
705
- @buses = {}
706
- @quitting = false
707
- end
708
-
709
- # Add a _bus_ to the list of buses to watch for events.
710
- def <<(bus)
711
- @buses[bus.message_queue.socket] = bus
712
- end
713
-
714
- # Quit a running main loop, to be used eg. from a signal handler
715
- def quit
716
- @quitting = true
717
- end
718
-
719
- # Run the main loop. This is a blocking call!
720
- def run
721
- # before blocking, empty the buffers
722
- # https://bugzilla.novell.com/show_bug.cgi?id=537401
723
- @buses.each_value do |b|
724
- while (m = b.message_queue.message_from_buffer_nonblock)
725
- b.process(m)
726
- end
727
- end
728
- while !@quitting && !@buses.empty?
729
- ready = IO.select(@buses.keys, [], [], 5) # timeout 5 seconds
730
- next unless ready # timeout exceeds so continue unless quitting
731
- ready.first.each do |socket|
732
- b = @buses[socket]
733
- begin
734
- b.message_queue.buffer_from_socket_nonblock
735
- rescue EOFError, SystemCallError
736
- @buses.delete socket # this bus died
737
- next
738
- end
739
- while (m = b.message_queue.message_from_buffer_nonblock)
740
- b.process(m)
741
- end
742
- end
743
- end
744
- end
745
- end # class Main
746
- end # module DBus
313
+ end