ruby-dbus 0.23.0.beta1 → 0.23.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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