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.
- 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"
|