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.
- checksums.yaml +4 -4
- data/NEWS.md +28 -3
- data/VERSION +1 -1
- data/doc/Reference.md +1 -1
- data/examples/no-bus/pulseaudio.rb +50 -0
- data/lib/dbus/bus.rb +42 -436
- data/lib/dbus/connection.rb +350 -0
- data/lib/dbus/object.rb +22 -12
- data/lib/dbus/object_manager.rb +5 -1
- data/lib/dbus/object_server.rb +44 -14
- data/lib/dbus/org.freedesktop.DBus.xml +97 -0
- data/lib/dbus/proxy_service.rb +14 -2
- data/lib/dbus.rb +1 -0
- data/ruby-dbus.gemspec +1 -1
- data/spec/bus_connection_spec.rb +7 -8
- data/spec/connection_spec.rb +37 -0
- data/spec/dbus_spec.rb +22 -0
- data/spec/mock-service/spaghetti-monster.rb +3 -4
- data/spec/object_server_spec.rb +58 -24
- data/spec/object_spec.rb +16 -0
- data/spec/proxy_service_spec.rb +12 -7
- data/spec/tools/dbus-limited-session.conf +3 -0
- metadata +8 -3
@@ -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
|
-
|
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
|
43
|
-
|
44
|
-
|
45
|
-
#
|
46
|
-
@service
|
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
|
-
|
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
|
-
|
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_.
|
data/lib/dbus/object_manager.rb
CHANGED
@@ -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 =
|
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
|
data/lib/dbus/object_server.rb
CHANGED
@@ -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
|
-
|
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.
|
55
|
+
obj.object_server = self
|
56
56
|
object_manager_for(obj)&.object_added(obj)
|
57
57
|
end
|
58
58
|
|
59
|
-
# Undo exporting an object
|
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
|
63
|
-
def unexport(
|
64
|
-
|
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.
|
75
|
-
|
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>
|
data/lib/dbus/proxy_service.rb
CHANGED
@@ -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
|
-
#
|
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 =
|
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"
|