ruby-dbus 0.23.0.beta1 → 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.
data/lib/dbus/bus.rb CHANGED
@@ -13,286 +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
- # FIXME: rename Connection to Bus?
21
-
22
- # D-Bus main connection class
23
- #
24
- # Main class that maintains a connection to a bus and can handle incoming
25
- # and outgoing messages.
26
- 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
27
25
  # The unique name (by specification) of the message.
28
26
  attr_reader :unique_name
29
- # pop and push messages here
30
- attr_reader :message_queue
31
27
 
32
- # Create a new connection to the bus for a given connect _path_. _path_
33
- # format is described in the D-Bus specification:
34
- # http://dbus.freedesktop.org/doc/dbus-specification.html#addresses
35
- # and is something like:
36
- # "transport1:key1=value1,key2=value2;transport2:key1=value1,key2=value2"
37
- # e.g. "unix:path=/tmp/dbus-test" or "tcp:host=localhost,port=2687"
38
- def initialize(path)
39
- @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
40
33
  @unique_name = nil
41
-
42
- # @return [Hash{Integer => Proc}]
43
- # key: message serial
44
- # value: block to be run when the reply to that message is received
45
- @method_call_replies = {}
46
-
47
- # @return [Hash{Integer => Message}]
48
- # for debugging only: messages for which a reply was not received yet;
49
- # key == value.serial
50
- @method_call_msgs = {}
51
- @signal_matchrules = {}
52
34
  @proxy = nil
53
- end
54
-
55
- def object_server
56
- @object_server ||= ObjectServer.new(self)
57
- end
58
-
59
- # Dispatch all messages that are available in the queue,
60
- # but do not block on the queue.
61
- # Called by a main loop when something is available in the queue
62
- def dispatch_message_queue
63
- while (msg = @message_queue.pop(blocking: false)) # FIXME: EOFError
64
- process(msg)
65
- end
66
- end
67
-
68
- # Tell a bus to register itself on the glib main loop
69
- def glibize
70
- require "glib2"
71
- # Circumvent a ruby-glib bug
72
- @channels ||= []
73
-
74
- gio = GLib::IOChannel.new(@message_queue.socket.fileno)
75
- @channels << gio
76
- gio.add_watch(GLib::IOChannel::IN) do |_c, _ch|
77
- dispatch_message_queue
78
- true
79
- end
80
- end
81
-
82
- # FIXME: describe the following names, flags and constants.
83
- # See DBus spec for definition
84
- NAME_FLAG_ALLOW_REPLACEMENT = 0x1
85
- NAME_FLAG_REPLACE_EXISTING = 0x2
86
- NAME_FLAG_DO_NOT_QUEUE = 0x4
87
-
88
- REQUEST_NAME_REPLY_PRIMARY_OWNER = 0x1
89
- REQUEST_NAME_REPLY_IN_QUEUE = 0x2
90
- REQUEST_NAME_REPLY_EXISTS = 0x3
91
- REQUEST_NAME_REPLY_ALREADY_OWNER = 0x4
92
-
93
- DBUSXMLINTRO = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
94
- "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
95
- <node>
96
- <interface name="org.freedesktop.DBus.Introspectable">
97
- <method name="Introspect">
98
- <arg direction="out" type="s"/>
99
- </method>
100
- </interface>
101
- <interface name="org.freedesktop.DBus">
102
- <method name="Hello">
103
- <arg direction="out" type="s"/>
104
- </method>
105
- <method name="RequestName">
106
- <arg direction="in" type="s"/>
107
- <arg direction="in" type="u"/>
108
- <arg direction="out" type="u"/>
109
- </method>
110
- <method name="ReleaseName">
111
- <arg direction="in" type="s"/>
112
- <arg direction="out" type="u"/>
113
- </method>
114
- <method name="StartServiceByName">
115
- <arg direction="in" type="s"/>
116
- <arg direction="in" type="u"/>
117
- <arg direction="out" type="u"/>
118
- </method>
119
- <method name="UpdateActivationEnvironment">
120
- <arg direction="in" type="a{ss}"/>
121
- </method>
122
- <method name="NameHasOwner">
123
- <arg direction="in" type="s"/>
124
- <arg direction="out" type="b"/>
125
- </method>
126
- <method name="ListNames">
127
- <arg direction="out" type="as"/>
128
- </method>
129
- <method name="ListActivatableNames">
130
- <arg direction="out" type="as"/>
131
- </method>
132
- <method name="AddMatch">
133
- <arg direction="in" type="s"/>
134
- </method>
135
- <method name="RemoveMatch">
136
- <arg direction="in" type="s"/>
137
- </method>
138
- <method name="GetNameOwner">
139
- <arg direction="in" type="s"/>
140
- <arg direction="out" type="s"/>
141
- </method>
142
- <method name="ListQueuedOwners">
143
- <arg direction="in" type="s"/>
144
- <arg direction="out" type="as"/>
145
- </method>
146
- <method name="GetConnectionUnixUser">
147
- <arg direction="in" type="s"/>
148
- <arg direction="out" type="u"/>
149
- </method>
150
- <method name="GetConnectionUnixProcessID">
151
- <arg direction="in" type="s"/>
152
- <arg direction="out" type="u"/>
153
- </method>
154
- <method name="GetAdtAuditSessionData">
155
- <arg direction="in" type="s"/>
156
- <arg direction="out" type="ay"/>
157
- </method>
158
- <method name="GetConnectionSELinuxSecurityContext">
159
- <arg direction="in" type="s"/>
160
- <arg direction="out" type="ay"/>
161
- </method>
162
- <method name="ReloadConfig">
163
- </method>
164
- <method name="GetId">
165
- <arg direction="out" type="s"/>
166
- </method>
167
- <method name="GetConnectionCredentials">
168
- <arg direction="in" type="s"/>
169
- <arg direction="out" type="a{sv}"/>
170
- </method>
171
- <property name="Features" type="as" access="read">
172
- <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
173
- </property>
174
- <property name="Interfaces" type="as" access="read">
175
- <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
176
- </property>
177
- <signal name="NameOwnerChanged">
178
- <arg type="s"/>
179
- <arg type="s"/>
180
- <arg type="s"/>
181
- </signal>
182
- <signal name="NameLost">
183
- <arg type="s"/>
184
- </signal>
185
- <signal name="NameAcquired">
186
- <arg type="s"/>
187
- </signal>
188
- </interface>
189
- </node>
190
- '
191
- # This apostroph is for syntax highlighting editors confused by above xml: "
192
-
193
- # @api private
194
- # Send a _message_.
195
- # If _reply_handler_ is not given, wait for the reply
196
- # and return the reply, or raise the error.
197
- # If _reply_handler_ is given, it will be called when the reply
198
- # eventually arrives, with the reply message as the 1st param
199
- # and its params following
200
- def send_sync_or_async(message, &reply_handler)
201
- ret = nil
202
- if reply_handler.nil?
203
- send_sync(message) do |rmsg|
204
- raise rmsg if rmsg.is_a?(Error)
205
-
206
- ret = rmsg.params
207
- end
208
- else
209
- on_return(message) do |rmsg|
210
- if rmsg.is_a?(Error)
211
- reply_handler.call(rmsg)
212
- else
213
- reply_handler.call(rmsg, * rmsg.params)
214
- end
215
- end
216
- @message_queue.push(message)
217
- end
218
- ret
219
- end
220
-
221
- # @api private
222
- def introspect_data(dest, path, &reply_handler)
223
- m = DBus::Message.new(DBus::Message::METHOD_CALL)
224
- m.path = path
225
- m.interface = "org.freedesktop.DBus.Introspectable"
226
- m.destination = dest
227
- m.member = "Introspect"
228
- m.sender = unique_name
229
- if reply_handler.nil?
230
- send_sync_or_async(m).first
231
- else
232
- send_sync_or_async(m) do |*args|
233
- # TODO: test async introspection, is it used at all?
234
- args.shift # forget the message, pass only the text
235
- reply_handler.call(*args)
236
- nil
237
- end
238
- end
239
- end
240
-
241
- # @api private
242
- # Issues a call to the org.freedesktop.DBus.Introspectable.Introspect method
243
- # _dest_ is the service and _path_ the object path you want to introspect
244
- # If a code block is given, the introspect call in asynchronous. If not
245
- # data is returned
246
- #
247
- # FIXME: link to ProxyObject data definition
248
- # The returned object is a ProxyObject that has methods you can call to
249
- # issue somme METHOD_CALL messages, and to setup to receive METHOD_RETURN
250
- def introspect(dest, path)
251
- if !block_given?
252
- # introspect in synchronous !
253
- data = introspect_data(dest, path)
254
- pof = DBus::ProxyObjectFactory.new(data, self, dest, path)
255
- pof.build
256
- else
257
- introspect_data(dest, path) do |async_data|
258
- yield(DBus::ProxyObjectFactory.new(async_data, self, dest, path).build)
259
- end
260
- end
261
- end
262
-
263
- # Exception raised when a service name is requested that is not available.
264
- class NameRequestError < Exception
265
- end
266
-
267
- def handle_return_of_request_name(ret, name)
268
- details = if ret == REQUEST_NAME_REPLY_IN_QUEUE
269
- other = proxy.GetNameOwner(name).first
270
- other_creds = proxy.GetConnectionCredentials(other).first
271
- "already owned by #{other}, #{other_creds.inspect}"
272
- else
273
- "error code #{ret}"
274
- end
275
- raise NameRequestError, "Could not request #{name}, #{details}" unless ret == REQUEST_NAME_REPLY_PRIMARY_OWNER
276
-
277
- ret
278
- end
279
-
280
- # Attempt to request a service _name_.
281
- # @raise NameRequestError which cannot really be rescued as it will be raised when dispatching a later call.
282
- # @return [ObjectServer]
283
- # @deprecated Use {BusConnection#request_name}.
284
- def request_service(name)
285
- # Use RequestName, but asynchronously!
286
- # A synchronous call would not work with service activation, where
287
- # method calls to be serviced arrive before the reply for RequestName
288
- # (Ticket#29).
289
- proxy.RequestName(name, NAME_FLAG_REPLACE_EXISTING) do |rmsg, r|
290
- # check and report errors first
291
- raise rmsg if rmsg.is_a?(Error)
292
-
293
- handle_return_of_request_name(r, name)
294
- end
295
- object_server
35
+ send_hello
296
36
  end
297
37
 
298
38
  # Set up a ProxyObject for the bus itself, since the bus is introspectable.
@@ -301,10 +41,13 @@ module DBus
301
41
  # Returns the object.
302
42
  def proxy
303
43
  if @proxy.nil?
44
+ xml_filename = File.expand_path("org.freedesktop.DBus.xml", __dir__)
45
+ xml = File.read(xml_filename)
46
+
304
47
  path = "/org/freedesktop/DBus"
305
48
  dest = "org.freedesktop.DBus"
306
49
  pof = DBus::ProxyObjectFactory.new(
307
- DBUSXMLINTRO, self, dest, path,
50
+ xml, self, dest, path,
308
51
  api: ApiOptions::A0
309
52
  )
310
53
  @proxy = pof.build["org.freedesktop.DBus"]
@@ -312,72 +55,39 @@ module DBus
312
55
  @proxy
313
56
  end
314
57
 
315
- # @api private
316
- # Wait for a message to arrive. Return it once it is available.
317
- def wait_for_message
318
- @message_queue.pop # FIXME: EOFError
319
- end
320
-
321
- # @api private
322
- # Send a message _msg_ on to the bus. This is done synchronously, thus
323
- # the call will block until a reply message arrives.
324
- # @param msg [Message]
325
- # @param retc [Proc] the reply handler
326
- # @yieldparam rmsg [MethodReturnMessage] the reply
327
- # @yieldreturn [Array<Object>] the reply (out) parameters
328
- def send_sync(msg, &retc) # :yields: reply/return message
329
- return if msg.nil? # check if somethings wrong
330
-
331
- @message_queue.push(msg)
332
- @method_call_msgs[msg.serial] = msg
333
- @method_call_replies[msg.serial] = retc
334
-
335
- retm = wait_for_message
336
- return if retm.nil? # check if somethings wrong
337
-
338
- process(retm)
339
- while @method_call_replies.key? msg.serial
340
- retm = wait_for_message
341
- process(retm)
342
- end
343
- rescue EOFError
344
- new_err = DBus::Error.new("Connection dropped after we sent #{msg.inspect}")
345
- raise new_err
346
- end
347
-
348
- # @api private
349
- # Specify a code block that has to be executed when a reply for
350
- # message _msg_ is received.
351
- # @param msg [Message]
352
- def on_return(msg, &retc)
353
- # Have a better exception here
354
- if msg.message_type != Message::METHOD_CALL
355
- raise "on_return should only get method_calls"
356
- end
357
-
358
- @method_call_msgs[msg.serial] = msg
359
- @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)
360
70
  end
361
71
 
362
72
  # Asks bus to send us messages matching mr, and execute slot when
363
73
  # received
364
74
  # @param match_rule [MatchRule,#to_s]
75
+ # @return [void]
365
76
  def add_match(match_rule, &slot)
366
- # check this is a signal.
367
77
  mrs = match_rule.to_s
368
- DBus.logger.debug "#{@signal_matchrules.size} rules, adding #{mrs.inspect}"
78
+ rule_existed = super(mrs, &slot)
369
79
  # don't ask for the same match if we override it
370
- unless @signal_matchrules.key?(mrs)
371
- DBus.logger.debug "Asked for a new match"
372
- proxy.AddMatch(mrs)
373
- end
374
- @signal_matchrules[mrs] = slot
80
+ return if rule_existed
81
+
82
+ DBus.logger.debug "Asked for a new match"
83
+ proxy.AddMatch(mrs)
375
84
  end
376
85
 
377
86
  # @param match_rule [MatchRule,#to_s]
87
+ # @return [void]
378
88
  def remove_match(match_rule)
379
89
  mrs = match_rule.to_s
380
- rule_existed = @signal_matchrules.delete(mrs).nil?
90
+ rule_existed = super(mrs)
381
91
  # don't remove nonexisting matches.
382
92
  return if rule_existed
383
93
 
@@ -386,68 +96,10 @@ module DBus
386
96
  proxy.RemoveMatch(mrs)
387
97
  end
388
98
 
389
- # @api private
390
- # Process a message _msg_ based on its type.
391
- # @param msg [Message]
392
- def process(msg)
393
- return if msg.nil? # check if somethings wrong
394
-
395
- case msg.message_type
396
- when Message::ERROR, Message::METHOD_RETURN
397
- raise InvalidPacketException if msg.reply_serial.nil?
398
-
399
- mcs = @method_call_replies[msg.reply_serial]
400
- if !mcs
401
- DBus.logger.debug "no return code for mcs: #{mcs.inspect} msg: #{msg.inspect}"
402
- else
403
- if msg.message_type == Message::ERROR
404
- mcs.call(Error.new(msg))
405
- else
406
- mcs.call(msg)
407
- end
408
- @method_call_replies.delete(msg.reply_serial)
409
- @method_call_msgs.delete(msg.reply_serial)
410
- end
411
- when DBus::Message::METHOD_CALL
412
- if msg.path == "/org/freedesktop/DBus"
413
- DBus.logger.debug "Got method call on /org/freedesktop/DBus"
414
- end
415
- node = object_server.get_node(msg.path, create: false)
416
- # introspect a known path even if there is no object on it
417
- if node &&
418
- msg.interface == "org.freedesktop.DBus.Introspectable" &&
419
- msg.member == "Introspect"
420
- reply = Message.new(Message::METHOD_RETURN).reply_to(msg)
421
- reply.sender = @unique_name
422
- xml = node.to_xml(msg.path)
423
- reply.add_param(Type::STRING, xml)
424
- @message_queue.push(reply)
425
- # dispatch for an object
426
- elsif node&.object
427
- node.object.dispatch(msg)
428
- else
429
- reply = Message.error(msg, "org.freedesktop.DBus.Error.UnknownObject",
430
- "Object #{msg.path} doesn't exist")
431
- @message_queue.push(reply)
432
- end
433
- when DBus::Message::SIGNAL
434
- # the signal can match multiple different rules
435
- # clone to allow new signale handlers to be registered
436
- @signal_matchrules.dup.each do |mrs, slot|
437
- if DBus::MatchRule.new.from_s(mrs).match(msg)
438
- slot.call(msg)
439
- end
440
- end
441
- else
442
- # spec(Message Format): Unknown types must be ignored.
443
- DBus.logger.debug "Unknown message type: #{msg.message_type}"
444
- end
445
- rescue Exception => e
446
- raise msg.annotate_exception(e)
447
- end
448
-
449
- # Retrieves the Service with the given _name_.
450
- # @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]
451
103
  def service(name)
452
104
  # The service might not exist at this time so we cannot really check
453
105
  # anything
@@ -455,27 +107,6 @@ module DBus
455
107
  end
456
108
  alias [] service
457
109
 
458
- # @api private
459
- # Emit a signal event for the given _service_, object _obj_, interface
460
- # _intf_ and signal _sig_ with arguments _args_.
461
- # @param _service unused
462
- # @param obj [DBus::Object]
463
- # @param intf [Interface]
464
- # @param sig [Signal]
465
- # @param args arguments for the signal
466
- def emit(_service, obj, intf, sig, *args)
467
- m = Message.new(DBus::Message::SIGNAL)
468
- m.path = obj.path
469
- m.interface = intf.name
470
- m.member = sig.name
471
- i = 0
472
- sig.params.each do |par|
473
- m.add_param(par.type, args[i])
474
- i += 1
475
- end
476
- @message_queue.push(m)
477
- end
478
-
479
110
  ###########################################################################
480
111
  private
481
112
 
@@ -493,25 +124,6 @@ module DBus
493
124
  end
494
125
  end
495
126
 
496
- # A regular Bus {Connection}.
497
- # As opposed to a peer connection to a single counterparty with no daemon in between.
498
- # FIXME: move the remaining relevant methods from Connection here, but alias the constants
499
- class BusConnection < Connection
500
- # @param name [BusName] the requested name
501
- # @param flags [Integer] TODO: explain and add a better non-numeric API for this
502
- # @raise NameRequestError if we could not get the name
503
- # @example Usage
504
- # bus = DBus.session_bus
505
- # bus.object_server.export(DBus::Object.new("/org/example/Test"))
506
- # bus.request_name("org.example.Test")
507
- # @see https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-request-name
508
- def request_name(name, flags: 0)
509
- name = BusName.new(name)
510
- r = proxy.RequestName(name, flags).first
511
- handle_return_of_request_name(r, name)
512
- end
513
- end
514
-
515
127
  # = D-Bus session bus class
516
128
  #
517
129
  # The session bus is a session specific bus (mostly for desktop use).
@@ -522,7 +134,6 @@ module DBus
522
134
  # Get the the default session bus.
523
135
  def initialize
524
136
  super(self.class.session_bus_address)
525
- send_hello
526
137
  end
527
138
 
528
139
  def self.session_bus_address
@@ -573,7 +184,6 @@ module DBus
573
184
  # Get the default system bus.
574
185
  def initialize
575
186
  super(self.class.system_bus_address)
576
- send_hello
577
187
  end
578
188
 
579
189
  def self.system_bus_address
@@ -593,12 +203,8 @@ module DBus
593
203
  # you'll need to take care about authentification then, more info here:
594
204
  # https://gitlab.com/pangdudu/ruby-dbus/-/blob/master/README.rdoc
595
205
  # TODO: keep the name but update the docs
206
+ # @deprecated just use BusConnection
596
207
  class RemoteBus < BusConnection
597
- # Get the remote bus.
598
- def initialize(socket_name)
599
- super(socket_name)
600
- send_hello
601
- end
602
208
  end
603
209
 
604
210
  # See ASystemBus
@@ -607,13 +213,13 @@ module DBus
607
213
  end
608
214
 
609
215
  # Shortcut for the {SystemBus} instance
610
- # @return [Connection]
216
+ # @return [BusConnection]
611
217
  def self.system_bus
612
218
  SystemBus.instance
613
219
  end
614
220
 
615
221
  # Shortcut for the {SessionBus} instance
616
- # @return [Connection]
222
+ # @return [BusConnection]
617
223
  def self.session_bus
618
224
  SessionBus.instance
619
225
  end