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.
@@ -0,0 +1,350 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of the ruby-dbus project
4
+ # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
5
+ # Copyright (C) 2023 Martin Vidner
6
+ #
7
+ # This library is free software; you can redistribute it and/or
8
+ # modify it under the terms of the GNU Lesser General Public
9
+ # License, version 2.1 as published by the Free Software Foundation.
10
+ # See the file "COPYING" for the exact licensing terms.
11
+
12
+ module DBus
13
+ # D-Bus main connection class
14
+ #
15
+ # Main class that maintains a connection to a bus and can handle incoming
16
+ # and outgoing messages.
17
+ class Connection
18
+ # pop and push messages here
19
+ # @return [MessageQueue]
20
+ attr_reader :message_queue
21
+
22
+ # Create a new connection to the bus for a given connect _path_. _path_
23
+ # format is described in the D-Bus specification:
24
+ # http://dbus.freedesktop.org/doc/dbus-specification.html#addresses
25
+ # and is something like:
26
+ # "transport1:key1=value1,key2=value2;transport2:key1=value1,key2=value2"
27
+ # e.g. "unix:path=/tmp/dbus-test" or "tcp:host=localhost,port=2687"
28
+ def initialize(path)
29
+ @message_queue = MessageQueue.new(path)
30
+
31
+ # @return [Hash{Integer => Proc}]
32
+ # key: message serial
33
+ # value: block to be run when the reply to that message is received
34
+ @method_call_replies = {}
35
+
36
+ # @return [Hash{Integer => Message}]
37
+ # for debugging only: messages for which a reply was not received yet;
38
+ # key == value.serial
39
+ @method_call_msgs = {}
40
+ @signal_matchrules = {}
41
+ end
42
+
43
+ def object_server
44
+ @object_server ||= ObjectServer.new(self)
45
+ end
46
+
47
+ # Dispatch all messages that are available in the queue,
48
+ # but do not block on the queue.
49
+ # Called by a main loop when something is available in the queue
50
+ def dispatch_message_queue
51
+ while (msg = @message_queue.pop(blocking: false)) # FIXME: EOFError
52
+ process(msg)
53
+ end
54
+ end
55
+
56
+ # Tell a bus to register itself on the glib main loop
57
+ def glibize
58
+ require "glib2"
59
+ # Circumvent a ruby-glib bug
60
+ @channels ||= []
61
+
62
+ gio = GLib::IOChannel.new(@message_queue.socket.fileno)
63
+ @channels << gio
64
+ gio.add_watch(GLib::IOChannel::IN) do |_c, _ch|
65
+ dispatch_message_queue
66
+ true
67
+ end
68
+ end
69
+
70
+ # NAME_FLAG_* and REQUEST_NAME_* belong to BusConnection
71
+ # but users will have referenced them in Connection so they need to stay here
72
+
73
+ # FIXME: describe the following names, flags and constants.
74
+ # See DBus spec for definition
75
+ NAME_FLAG_ALLOW_REPLACEMENT = 0x1
76
+ NAME_FLAG_REPLACE_EXISTING = 0x2
77
+ NAME_FLAG_DO_NOT_QUEUE = 0x4
78
+
79
+ REQUEST_NAME_REPLY_PRIMARY_OWNER = 0x1
80
+ REQUEST_NAME_REPLY_IN_QUEUE = 0x2
81
+ REQUEST_NAME_REPLY_EXISTS = 0x3
82
+ REQUEST_NAME_REPLY_ALREADY_OWNER = 0x4
83
+
84
+ # @api private
85
+ # Send a _message_.
86
+ # If _reply_handler_ is not given, wait for the reply
87
+ # and return the reply, or raise the error.
88
+ # If _reply_handler_ is given, it will be called when the reply
89
+ # eventually arrives, with the reply message as the 1st param
90
+ # and its params following
91
+ def send_sync_or_async(message, &reply_handler)
92
+ ret = nil
93
+ if reply_handler.nil?
94
+ send_sync(message) do |rmsg|
95
+ raise rmsg if rmsg.is_a?(Error)
96
+
97
+ ret = rmsg.params
98
+ end
99
+ else
100
+ on_return(message) do |rmsg|
101
+ if rmsg.is_a?(Error)
102
+ reply_handler.call(rmsg)
103
+ else
104
+ reply_handler.call(rmsg, * rmsg.params)
105
+ end
106
+ end
107
+ @message_queue.push(message)
108
+ end
109
+ ret
110
+ end
111
+
112
+ # @api private
113
+ def introspect_data(dest, path, &reply_handler)
114
+ m = DBus::Message.new(DBus::Message::METHOD_CALL)
115
+ m.path = path
116
+ m.interface = "org.freedesktop.DBus.Introspectable"
117
+ m.destination = dest
118
+ m.member = "Introspect"
119
+ m.sender = unique_name
120
+ if reply_handler.nil?
121
+ send_sync_or_async(m).first
122
+ else
123
+ send_sync_or_async(m) do |*args|
124
+ # TODO: test async introspection, is it used at all?
125
+ args.shift # forget the message, pass only the text
126
+ reply_handler.call(*args)
127
+ nil
128
+ end
129
+ end
130
+ end
131
+
132
+ # @api private
133
+ # Issues a call to the org.freedesktop.DBus.Introspectable.Introspect method
134
+ # _dest_ is the service and _path_ the object path you want to introspect
135
+ # If a code block is given, the introspect call in asynchronous. If not
136
+ # data is returned
137
+ #
138
+ # FIXME: link to ProxyObject data definition
139
+ # The returned object is a ProxyObject that has methods you can call to
140
+ # issue somme METHOD_CALL messages, and to setup to receive METHOD_RETURN
141
+ def introspect(dest, path)
142
+ if !block_given?
143
+ # introspect in synchronous !
144
+ data = introspect_data(dest, path)
145
+ pof = DBus::ProxyObjectFactory.new(data, self, dest, path)
146
+ pof.build
147
+ else
148
+ introspect_data(dest, path) do |async_data|
149
+ yield(DBus::ProxyObjectFactory.new(async_data, self, dest, path).build)
150
+ end
151
+ end
152
+ end
153
+
154
+ # Exception raised when a service name is requested that is not available.
155
+ class NameRequestError < Exception
156
+ end
157
+
158
+ def handle_return_of_request_name(ret, name)
159
+ details = if ret == REQUEST_NAME_REPLY_IN_QUEUE
160
+ other = proxy.GetNameOwner(name).first
161
+ other_creds = proxy.GetConnectionCredentials(other).first
162
+ "already owned by #{other}, #{other_creds.inspect}"
163
+ else
164
+ "error code #{ret}"
165
+ end
166
+ raise NameRequestError, "Could not request #{name}, #{details}" unless ret == REQUEST_NAME_REPLY_PRIMARY_OWNER
167
+
168
+ ret
169
+ end
170
+
171
+ # Attempt to request a service _name_.
172
+ # @raise NameRequestError which cannot really be rescued as it will be raised when dispatching a later call.
173
+ # @return [ObjectServer]
174
+ # @deprecated Use {BusConnection#request_name}.
175
+ def request_service(name)
176
+ # Use RequestName, but asynchronously!
177
+ # A synchronous call would not work with service activation, where
178
+ # method calls to be serviced arrive before the reply for RequestName
179
+ # (Ticket#29).
180
+ proxy.RequestName(name, NAME_FLAG_REPLACE_EXISTING) do |rmsg, r|
181
+ # check and report errors first
182
+ raise rmsg if rmsg.is_a?(Error)
183
+
184
+ handle_return_of_request_name(r, name)
185
+ end
186
+ object_server
187
+ end
188
+
189
+ # @api private
190
+ # Wait for a message to arrive. Return it once it is available.
191
+ def wait_for_message
192
+ @message_queue.pop # FIXME: EOFError
193
+ end
194
+
195
+ # @api private
196
+ # Send a message _msg_ on to the bus. This is done synchronously, thus
197
+ # the call will block until a reply message arrives.
198
+ # @param msg [Message]
199
+ # @param retc [Proc] the reply handler
200
+ # @yieldparam rmsg [MethodReturnMessage] the reply
201
+ # @yieldreturn [Array<Object>] the reply (out) parameters
202
+ def send_sync(msg, &retc) # :yields: reply/return message
203
+ return if msg.nil? # check if somethings wrong
204
+
205
+ @message_queue.push(msg)
206
+ @method_call_msgs[msg.serial] = msg
207
+ @method_call_replies[msg.serial] = retc
208
+
209
+ retm = wait_for_message
210
+ return if retm.nil? # check if somethings wrong
211
+
212
+ process(retm)
213
+ while @method_call_replies.key? msg.serial
214
+ retm = wait_for_message
215
+ process(retm)
216
+ end
217
+ rescue EOFError
218
+ new_err = DBus::Error.new("Connection dropped after we sent #{msg.inspect}")
219
+ raise new_err
220
+ end
221
+
222
+ # @api private
223
+ # Specify a code block that has to be executed when a reply for
224
+ # message _msg_ is received.
225
+ # @param msg [Message]
226
+ def on_return(msg, &retc)
227
+ # Have a better exception here
228
+ if msg.message_type != Message::METHOD_CALL
229
+ raise "on_return should only get method_calls"
230
+ end
231
+
232
+ @method_call_msgs[msg.serial] = msg
233
+ @method_call_replies[msg.serial] = retc
234
+ end
235
+
236
+ # Asks bus to send us messages matching mr, and execute slot when
237
+ # received
238
+ # @param match_rule [MatchRule,#to_s]
239
+ # @return [void] actually return whether the rule existed, internal detail
240
+ def add_match(match_rule, &slot)
241
+ # check this is a signal.
242
+ mrs = match_rule.to_s
243
+ DBus.logger.debug "#{@signal_matchrules.size} rules, adding #{mrs.inspect}"
244
+ rule_existed = @signal_matchrules.key?(mrs)
245
+ @signal_matchrules[mrs] = slot
246
+ rule_existed
247
+ end
248
+
249
+ # @param match_rule [MatchRule,#to_s]
250
+ # @return [void] actually return whether the rule existed, internal detail
251
+ def remove_match(match_rule)
252
+ mrs = match_rule.to_s
253
+ @signal_matchrules.delete(mrs).nil?
254
+ end
255
+
256
+ # @api private
257
+ # Process a message _msg_ based on its type.
258
+ # @param msg [Message]
259
+ def process(msg)
260
+ return if msg.nil? # check if somethings wrong
261
+
262
+ case msg.message_type
263
+ when Message::ERROR, Message::METHOD_RETURN
264
+ raise InvalidPacketException if msg.reply_serial.nil?
265
+
266
+ mcs = @method_call_replies[msg.reply_serial]
267
+ if !mcs
268
+ DBus.logger.debug "no return code for mcs: #{mcs.inspect} msg: #{msg.inspect}"
269
+ else
270
+ if msg.message_type == Message::ERROR
271
+ mcs.call(Error.new(msg))
272
+ else
273
+ mcs.call(msg)
274
+ end
275
+ @method_call_replies.delete(msg.reply_serial)
276
+ @method_call_msgs.delete(msg.reply_serial)
277
+ end
278
+ when DBus::Message::METHOD_CALL
279
+ if msg.path == "/org/freedesktop/DBus"
280
+ DBus.logger.debug "Got method call on /org/freedesktop/DBus"
281
+ end
282
+ node = object_server.get_node(msg.path, create: false)
283
+ # introspect a known path even if there is no object on it
284
+ if node &&
285
+ msg.interface == "org.freedesktop.DBus.Introspectable" &&
286
+ msg.member == "Introspect"
287
+ reply = Message.new(Message::METHOD_RETURN).reply_to(msg)
288
+ reply.sender = @unique_name
289
+ xml = node.to_xml(msg.path)
290
+ reply.add_param(Type::STRING, xml)
291
+ @message_queue.push(reply)
292
+ # dispatch for an object
293
+ elsif node&.object
294
+ node.object.dispatch(msg)
295
+ else
296
+ reply = Message.error(msg, "org.freedesktop.DBus.Error.UnknownObject",
297
+ "Object #{msg.path} doesn't exist")
298
+ @message_queue.push(reply)
299
+ end
300
+ when DBus::Message::SIGNAL
301
+ # the signal can match multiple different rules
302
+ # clone to allow new signale handlers to be registered
303
+ @signal_matchrules.dup.each do |mrs, slot|
304
+ if DBus::MatchRule.new.from_s(mrs).match(msg)
305
+ slot.call(msg)
306
+ end
307
+ end
308
+ else
309
+ # spec(Message Format): Unknown types must be ignored.
310
+ DBus.logger.debug "Unknown message type: #{msg.message_type}"
311
+ end
312
+ rescue Exception => e
313
+ raise msg.annotate_exception(e)
314
+ end
315
+
316
+ # @api private
317
+ # Emit a signal event for the given _service_, object _obj_, interface
318
+ # _intf_ and signal _sig_ with arguments _args_.
319
+ # @param _service unused
320
+ # @param obj [DBus::Object]
321
+ # @param intf [Interface]
322
+ # @param sig [Signal]
323
+ # @param args arguments for the signal
324
+ def emit(_service, obj, intf, sig, *args)
325
+ m = Message.new(DBus::Message::SIGNAL)
326
+ m.path = obj.path
327
+ m.interface = intf.name
328
+ m.member = sig.name
329
+ i = 0
330
+ sig.params.each do |par|
331
+ m.add_param(par.type, args[i])
332
+ i += 1
333
+ end
334
+ @message_queue.push(m)
335
+ end
336
+ end
337
+
338
+ # A {Connection} that is talking directly to a peer, with no bus daemon in between.
339
+ # A prominent example is the PulseAudio connection,
340
+ # see https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/DBus/
341
+ # When starting, it still starts with authentication but omits the Hello message.
342
+ class PeerConnection < Connection
343
+ # Get a {ProxyPeerService}, a dummy helper to get {ProxyObject}s for
344
+ # a {PeerConnection}.
345
+ # @return [ProxyPeerService]
346
+ def peer_service
347
+ ProxyPeerService.new(self)
348
+ end
349
+ end
350
+ end
data/lib/dbus/object.rb CHANGED
@@ -19,31 +19,37 @@ module DBus
19
19
  # Objects that are going to be exported by a D-Bus service
20
20
  # should inherit from this class. At the client side, use {ProxyObject}.
21
21
  class Object
22
- # The path of the object.
22
+ # @return [ObjectPath] The path of the object.
23
23
  attr_reader :path
24
24
 
25
25
  # The interfaces that the object supports. Hash: String => Interface
26
26
  my_class_attribute :intfs
27
27
  self.intfs = {}
28
28
 
29
- # @return [Connection] the connection the object is exported by
30
- attr_reader :connection
31
-
32
29
  @@cur_intf = nil # Interface
33
30
  @@intfs_mutex = Mutex.new
34
31
 
35
32
  # Create a new object with a given _path_.
36
33
  # Use ObjectServer#export to export it.
34
+ # @param path [ObjectPath] The path of the object.
37
35
  def initialize(path)
38
36
  @path = path
39
- @connection = nil
37
+ # TODO: what parts of our API are supposed to work before we're exported?
38
+ self.object_server = nil
39
+ end
40
+
41
+ # @return [ObjectServer] the server the object is exported by
42
+ def object_server
43
+ # tests may mock the old ivar
44
+ @object_server || @service
40
45
  end
41
46
 
42
- # @param connection [Connection] the connection the object is exported by
43
- def connection=(connection)
44
- @connection = connection
45
- # deprecated, keeping @service for compatibility
46
- @service = connection&.object_server
47
+ # @param server [ObjectServer] the server the object is exported by
48
+ # @note only the server itself should call this in its #export/#unexport
49
+ def object_server=(server)
50
+ # until v0.22.1 there was attr_writer :service
51
+ # so subclasses only could use @service
52
+ @object_server = @service = server
47
53
  end
48
54
 
49
55
  # Dispatch a message _msg_ to call exported methods
@@ -78,7 +84,9 @@ module DBus
78
84
  dbus_msg_exc = msg.annotate_exception(e)
79
85
  reply = ErrorMessage.from_exception(dbus_msg_exc).reply_to(msg)
80
86
  end
81
- @connection.message_queue.push(reply)
87
+ # TODO: this method chain is too long,
88
+ # we should probably just return reply [Message] like we get a [Message]
89
+ object_server.connection.message_queue.push(reply)
82
90
  end
83
91
  end
84
92
 
@@ -316,7 +324,9 @@ module DBus
316
324
  # @param sig [Signal]
317
325
  # @param args arguments for the signal
318
326
  def emit(intf, sig, *args)
319
- @connection.emit(nil, self, intf, sig, *args)
327
+ raise "Cannot emit signal #{intf.name}.#{sig.name} before #{path} is exported" if object_server.nil?
328
+
329
+ object_server.connection.emit(nil, self, intf, sig, *args)
320
330
  end
321
331
 
322
332
  # Defines a signal for the object with a given name _sym_ and _prototype_.
@@ -20,27 +20,31 @@ module DBus
20
20
  module ObjectManager
21
21
  OBJECT_MANAGER_INTERFACE = "org.freedesktop.DBus.ObjectManager"
22
22
 
23
+ # Implements `the GetManagedObjects` method.
23
24
  # @return [Hash{ObjectPath => Hash{String => Hash{String => Data::Base}}}]
24
25
  # object -> interface -> property -> value
25
26
  def managed_objects
26
- descendant_objects = connection.object_server.descendants_for(path)
27
+ descendant_objects = object_server.descendants_for(path)
27
28
  descendant_objects.each_with_object({}) do |obj, hash|
28
29
  hash[obj.path] = obj.interfaces_and_properties
29
30
  end
30
31
  end
31
32
 
33
+ # {ObjectServer#export} will call this for you to emit the `InterfacesAdded` signal.
32
34
  # @param object [DBus::Object]
33
35
  # @return [void]
34
36
  def object_added(object)
35
37
  InterfacesAdded(object.path, object.interfaces_and_properties)
36
38
  end
37
39
 
40
+ # {ObjectServer#unexport} will call this for you to emit the `InterfacesRemoved` signal.
38
41
  # @param object [DBus::Object]
39
42
  # @return [void]
40
43
  def object_removed(object)
41
44
  InterfacesRemoved(object.path, object.intfs.keys)
42
45
  end
43
46
 
47
+ # Module#included, a hook for `include ObjectManager`, declares its dbus_interface.
44
48
  def self.included(base)
45
49
  base.class_eval do
46
50
  dbus_interface OBJECT_MANAGER_INTERFACE do
@@ -36,7 +36,7 @@ module DBus
36
36
 
37
37
  # Retrieves an object at the given _path_
38
38
  # @param path [ObjectPath]
39
- # @return [DBus::Object]
39
+ # @return [DBus::Object,nil]
40
40
  def object(path)
41
41
  node = get_node(path, create: false)
42
42
  node&.object
@@ -45,34 +45,48 @@ module DBus
45
45
 
46
46
  # Export an object
47
47
  # @param obj [DBus::Object]
48
+ # @raise RuntimeError if there's already an exported object at the same path
48
49
  def export(obj)
49
50
  node = get_node(obj.path, create: true)
50
- # TODO: clarify that this is indeed the right thing, and document
51
- # raise "At #{obj.path} there is already an object #{node.object.inspect}" if node.object
51
+ raise "At #{obj.path} there is already an object #{node.object.inspect}" if node.object
52
52
 
53
53
  node.object = obj
54
54
 
55
- obj.connection = @connection
55
+ obj.object_server = self
56
56
  object_manager_for(obj)&.object_added(obj)
57
57
  end
58
58
 
59
- # Undo exporting an object _obj_.
59
+ # Undo exporting an object *obj_or_path*.
60
60
  # Raises ArgumentError if it is not a DBus::Object.
61
61
  # Returns the object, or false if _obj_ was not exported.
62
- # @param obj [DBus::Object]
63
- def unexport(obj)
64
- raise ArgumentError, "Expecting a DBus::Object argument" unless obj.is_a?(DBus::Object)
65
-
66
- last_path_separator_idx = obj.path.rindex("/")
67
- parent_path = obj.path[1..last_path_separator_idx - 1]
68
- node_name = obj.path[last_path_separator_idx + 1..-1]
62
+ # @param obj_or_path [DBus::Object,ObjectPath,String] an object or a valid object path
63
+ def unexport(obj_or_path)
64
+ path = self.class.path_of(obj_or_path)
65
+ parent_path, _separator, node_name = path.rpartition("/")
69
66
 
70
67
  parent_node = get_node(parent_path, create: false)
71
68
  return false unless parent_node
72
69
 
70
+ node = if node_name == "" # path == "/"
71
+ parent_node
72
+ else
73
+ parent_node[node_name]
74
+ end
75
+ obj = node&.object
76
+ raise ArgumentError, "Cannot unexport, no object at #{path}" unless obj
77
+
73
78
  object_manager_for(obj)&.object_removed(obj)
74
- obj.connection = nil
75
- parent_node.delete(node_name).object
79
+ obj.object_server = nil
80
+ node.object = nil
81
+
82
+ # node can be deleted if
83
+ # - it has no children
84
+ # - it is not root
85
+ if node.empty? && !node.equal?(parent_node)
86
+ parent_node.delete(node_name)
87
+ end
88
+
89
+ obj
76
90
  end
77
91
 
78
92
  # Find the (closest) parent of *object*
@@ -98,6 +112,22 @@ module DBus
98
112
  node.descendant_objects
99
113
  end
100
114
 
115
+ # @param obj_or_path [DBus::Object,ObjectPath,String] an object or a valid object path
116
+ # @return [ObjectPath]
117
+ # @api private
118
+ def self.path_of(obj_or_path)
119
+ case obj_or_path
120
+ when ObjectPath
121
+ obj_or_path
122
+ when String
123
+ ObjectPath.new(obj_or_path)
124
+ when DBus::Object
125
+ obj_or_path.path
126
+ else
127
+ raise ArgumentError, "Expecting a DBus::Object argument or DBus::ObjectPath or String which parses as one"
128
+ end
129
+ end
130
+
101
131
  #########
102
132
 
103
133
  private
@@ -0,0 +1,97 @@
1
+ <!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
2
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
3
+ <node>
4
+ <interface name="org.freedesktop.DBus.Introspectable">
5
+ <method name="Introspect">
6
+ <arg direction="out" type="s"/>
7
+ </method>
8
+ </interface>
9
+ <interface name="org.freedesktop.DBus">
10
+ <method name="Hello">
11
+ <arg direction="out" type="s"/>
12
+ </method>
13
+ <method name="RequestName">
14
+ <arg direction="in" type="s"/>
15
+ <arg direction="in" type="u"/>
16
+ <arg direction="out" type="u"/>
17
+ </method>
18
+ <method name="ReleaseName">
19
+ <arg direction="in" type="s"/>
20
+ <arg direction="out" type="u"/>
21
+ </method>
22
+ <method name="StartServiceByName">
23
+ <arg direction="in" type="s"/>
24
+ <arg direction="in" type="u"/>
25
+ <arg direction="out" type="u"/>
26
+ </method>
27
+ <method name="UpdateActivationEnvironment">
28
+ <arg direction="in" type="a{ss}"/>
29
+ </method>
30
+ <method name="NameHasOwner">
31
+ <arg direction="in" type="s"/>
32
+ <arg direction="out" type="b"/>
33
+ </method>
34
+ <method name="ListNames">
35
+ <arg direction="out" type="as"/>
36
+ </method>
37
+ <method name="ListActivatableNames">
38
+ <arg direction="out" type="as"/>
39
+ </method>
40
+ <method name="AddMatch">
41
+ <arg direction="in" type="s"/>
42
+ </method>
43
+ <method name="RemoveMatch">
44
+ <arg direction="in" type="s"/>
45
+ </method>
46
+ <method name="GetNameOwner">
47
+ <arg direction="in" type="s"/>
48
+ <arg direction="out" type="s"/>
49
+ </method>
50
+ <method name="ListQueuedOwners">
51
+ <arg direction="in" type="s"/>
52
+ <arg direction="out" type="as"/>
53
+ </method>
54
+ <method name="GetConnectionUnixUser">
55
+ <arg direction="in" type="s"/>
56
+ <arg direction="out" type="u"/>
57
+ </method>
58
+ <method name="GetConnectionUnixProcessID">
59
+ <arg direction="in" type="s"/>
60
+ <arg direction="out" type="u"/>
61
+ </method>
62
+ <method name="GetAdtAuditSessionData">
63
+ <arg direction="in" type="s"/>
64
+ <arg direction="out" type="ay"/>
65
+ </method>
66
+ <method name="GetConnectionSELinuxSecurityContext">
67
+ <arg direction="in" type="s"/>
68
+ <arg direction="out" type="ay"/>
69
+ </method>
70
+ <method name="ReloadConfig">
71
+ </method>
72
+ <method name="GetId">
73
+ <arg direction="out" type="s"/>
74
+ </method>
75
+ <method name="GetConnectionCredentials">
76
+ <arg direction="in" type="s"/>
77
+ <arg direction="out" type="a{sv}"/>
78
+ </method>
79
+ <property name="Features" type="as" access="read">
80
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
81
+ </property>
82
+ <property name="Interfaces" type="as" access="read">
83
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
84
+ </property>
85
+ <signal name="NameOwnerChanged">
86
+ <arg type="s"/>
87
+ <arg type="s"/>
88
+ <arg type="s"/>
89
+ </signal>
90
+ <signal name="NameLost">
91
+ <arg type="s"/>
92
+ </signal>
93
+ <signal name="NameAcquired">
94
+ <arg type="s"/>
95
+ </signal>
96
+ </interface>
97
+ </node>
@@ -22,11 +22,12 @@ module DBus
22
22
  # p manager.ListImages
23
23
  class ProxyService < NodeTree
24
24
  # @return [BusName,nil] The service name.
25
- # May be nil for peer connections
25
+ # Will be nil for a {PeerConnection}
26
26
  attr_reader :name
27
27
  # @return [Connection] The connection we're using.
28
28
  attr_reader :connection
29
29
 
30
+ # @param connection [Connection] The connection we're using.
30
31
  def initialize(name, connection)
31
32
  @name = BusName.new(name)
32
33
  @connection = connection
@@ -76,7 +77,7 @@ module DBus
76
77
  # Perform a recursive retrospection on the given current _node_
77
78
  # on the given _path_.
78
79
  def rec_introspect(node, path)
79
- xml = bus.introspect_data(@name, path)
80
+ xml = connection.introspect_data(@name, path)
80
81
  intfs, subnodes = IntrospectXMLParser.new(xml).parse
81
82
  subnodes.each do |nodename|
82
83
  subnode = node[nodename] = Node.new(nodename)
@@ -92,4 +93,15 @@ module DBus
92
93
  node.object = ProxyObjectFactory.new(xml, @connection, @name, path).build
93
94
  end
94
95
  end
96
+
97
+ # A hack for pretending that a {PeerConnection} has a single unnamed {ProxyService}
98
+ # so that we can get {ProxyObject}s from it.
99
+ class ProxyPeerService < ProxyService
100
+ # @param connection [Connection] The peer connection we're using.
101
+ def initialize(connection)
102
+ # this way we disallow ProxyService taking a nil name by accident
103
+ super(":0.0", connection)
104
+ @name = nil
105
+ end
106
+ end
95
107
  end
data/lib/dbus.rb CHANGED
@@ -26,6 +26,7 @@ require_relative "dbus/api_options"
26
26
  require_relative "dbus/auth"
27
27
  require_relative "dbus/bus"
28
28
  require_relative "dbus/bus_name"
29
+ require_relative "dbus/connection"
29
30
  require_relative "dbus/data"
30
31
  require_relative "dbus/emits_changed_signal"
31
32
  require_relative "dbus/error"