ruby-dbus 0.22.1 → 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 +38 -0
- data/VERSION +1 -1
- data/doc/Reference.md +5 -5
- data/examples/no-bus/pulseaudio.rb +50 -0
- data/examples/service/complex-property.rb +2 -2
- data/examples/service/service_newapi.rb +2 -2
- data/lib/dbus/bus.rb +48 -694
- data/lib/dbus/connection.rb +350 -0
- data/lib/dbus/logger.rb +3 -2
- data/lib/dbus/main.rb +66 -0
- data/lib/dbus/message.rb +6 -8
- data/lib/dbus/node_tree.rb +105 -0
- data/lib/dbus/object.rb +28 -9
- data/lib/dbus/object_manager.rb +6 -3
- data/lib/dbus/object_server.rb +149 -0
- data/lib/dbus/org.freedesktop.DBus.xml +97 -0
- data/lib/dbus/proxy_object.rb +4 -4
- data/lib/dbus/proxy_service.rb +107 -0
- data/lib/dbus.rb +10 -8
- data/ruby-dbus.gemspec +1 -1
- data/spec/bus_connection_spec.rb +80 -0
- data/spec/connection_spec.rb +37 -0
- data/spec/coverage_helper.rb +39 -0
- data/spec/dbus_spec.rb +22 -0
- data/spec/main_loop_spec.rb +14 -0
- data/spec/message_spec.rb +21 -0
- data/spec/mock-service/cockpit-dbustests.rb +29 -0
- data/spec/mock-service/com.redhat.Cockpit.DBusTests.xml +180 -0
- data/spec/mock-service/org.ruby.service.service +4 -0
- data/spec/mock-service/org.rubygems.ruby_dbus.DBusTests.service +4 -0
- data/spec/{service_newapi.rb → mock-service/spaghetti-monster.rb} +21 -10
- data/spec/node_spec.rb +1 -5
- data/spec/object_server_spec.rb +138 -0
- data/spec/object_spec.rb +46 -0
- data/spec/{bus_driver_spec.rb → proxy_service_spec.rb} +13 -8
- data/spec/spec_helper.rb +9 -45
- data/spec/thread_safety_spec.rb +9 -11
- data/spec/tools/dbus-launch-simple +4 -1
- data/spec/tools/dbus-limited-session.conf +3 -0
- data/spec/tools/test_env +26 -6
- metadata +24 -10
- data/spec/server_spec.rb +0 -55
- data/spec/service_spec.rb +0 -18
- data/spec/tools/test_server +0 -39
data/lib/dbus/bus.rb
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
# This file is part of the ruby-dbus project
|
6
6
|
# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
|
7
7
|
#
|
8
|
-
# This library is free software; you
|
8
|
+
# This library is free software; you can redistribute it and/or
|
9
9
|
# modify it under the terms of the GNU Lesser General Public
|
10
10
|
# License, version 2.1 as published by the Free Software Foundation.
|
11
11
|
# See the file "COPYING" for the exact licensing terms.
|
@@ -13,505 +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
|
-
#
|
21
|
-
#
|
22
|
-
class
|
23
|
-
# The service name.
|
24
|
-
attr_reader :name
|
25
|
-
# The bus the service is running on.
|
26
|
-
attr_reader :bus
|
27
|
-
# The service root (FIXME).
|
28
|
-
attr_reader :root
|
29
|
-
|
30
|
-
# Create a new service with a given _name_ on a given _bus_.
|
31
|
-
def initialize(name, bus)
|
32
|
-
@name = BusName.new(name)
|
33
|
-
@bus = bus
|
34
|
-
@root = Node.new("/")
|
35
|
-
end
|
36
|
-
|
37
|
-
# Determine whether the service name already exists.
|
38
|
-
def exists?
|
39
|
-
bus.proxy.ListNames[0].member?(@name)
|
40
|
-
end
|
41
|
-
|
42
|
-
# Perform an introspection on all the objects on the service
|
43
|
-
# (starting recursively from the root).
|
44
|
-
def introspect
|
45
|
-
raise NotImplementedError if block_given?
|
46
|
-
|
47
|
-
rec_introspect(@root, "/")
|
48
|
-
self
|
49
|
-
end
|
50
|
-
|
51
|
-
# Retrieves an object at the given _path_.
|
52
|
-
# @param path [ObjectPath]
|
53
|
-
# @return [ProxyObject]
|
54
|
-
def [](path)
|
55
|
-
object(path, api: ApiOptions::A1)
|
56
|
-
end
|
57
|
-
|
58
|
-
# Retrieves an object at the given _path_
|
59
|
-
# whose methods always return an array.
|
60
|
-
# @param path [ObjectPath]
|
61
|
-
# @param api [ApiOptions]
|
62
|
-
# @return [ProxyObject]
|
63
|
-
def object(path, api: ApiOptions::A0)
|
64
|
-
node = get_node(path, create: true)
|
65
|
-
if node.object.nil? || node.object.api != api
|
66
|
-
node.object = ProxyObject.new(
|
67
|
-
@bus, @name, path,
|
68
|
-
api: api
|
69
|
-
)
|
70
|
-
end
|
71
|
-
node.object
|
72
|
-
end
|
73
|
-
|
74
|
-
# Export an object
|
75
|
-
# @param obj [DBus::Object]
|
76
|
-
def export(obj)
|
77
|
-
obj.service = self
|
78
|
-
get_node(obj.path, create: true).object = obj
|
79
|
-
object_manager_for(obj)&.object_added(obj)
|
80
|
-
end
|
81
|
-
|
82
|
-
# Undo exporting an object _obj_.
|
83
|
-
# Raises ArgumentError if it is not a DBus::Object.
|
84
|
-
# Returns the object, or false if _obj_ was not exported.
|
85
|
-
# @param obj [DBus::Object]
|
86
|
-
def unexport(obj)
|
87
|
-
raise ArgumentError, "DBus::Service#unexport() expects a DBus::Object argument" unless obj.is_a?(DBus::Object)
|
88
|
-
return false unless obj.path
|
89
|
-
|
90
|
-
last_path_separator_idx = obj.path.rindex("/")
|
91
|
-
parent_path = obj.path[1..last_path_separator_idx - 1]
|
92
|
-
node_name = obj.path[last_path_separator_idx + 1..-1]
|
93
|
-
|
94
|
-
parent_node = get_node(parent_path, create: false)
|
95
|
-
return false unless parent_node
|
96
|
-
|
97
|
-
object_manager_for(obj)&.object_removed(obj)
|
98
|
-
obj.service = nil
|
99
|
-
parent_node.delete(node_name).object
|
100
|
-
end
|
101
|
-
|
102
|
-
# Get the object node corresponding to the given *path*.
|
103
|
-
# @param path [ObjectPath]
|
104
|
-
# @param create [Boolean] if true, the the {Node}s in the path are created
|
105
|
-
# if they do not already exist.
|
106
|
-
# @return [Node,nil]
|
107
|
-
def get_node(path, create: false)
|
108
|
-
n = @root
|
109
|
-
path.sub(%r{^/}, "").split("/").each do |elem|
|
110
|
-
if !(n[elem])
|
111
|
-
return nil if !create
|
112
|
-
|
113
|
-
n[elem] = Node.new(elem)
|
114
|
-
end
|
115
|
-
n = n[elem]
|
116
|
-
end
|
117
|
-
n
|
118
|
-
end
|
119
|
-
|
120
|
-
# Find the (closest) parent of *object*
|
121
|
-
# implementing the ObjectManager interface, or nil
|
122
|
-
# @return [DBus::Object,nil]
|
123
|
-
def object_manager_for(object)
|
124
|
-
path = object.path
|
125
|
-
node_chain = get_node_chain(path)
|
126
|
-
om_node = node_chain.reverse_each.find do |node|
|
127
|
-
node.object&.is_a? DBus::ObjectManager
|
128
|
-
end
|
129
|
-
om_node&.object
|
130
|
-
end
|
131
|
-
|
132
|
-
# All objects (not paths) under this path (except itself).
|
133
|
-
# @param path [ObjectPath]
|
134
|
-
# @return [Array<DBus::Object>]
|
135
|
-
# @raise ArgumentError if the *path* does not exist
|
136
|
-
def descendants_for(path)
|
137
|
-
node = get_node(path, create: false)
|
138
|
-
raise ArgumentError, "Object path #{path} doesn't exist" if node.nil?
|
139
|
-
|
140
|
-
node.descendant_objects
|
141
|
-
end
|
142
|
-
|
143
|
-
#########
|
144
|
-
|
145
|
-
private
|
146
|
-
|
147
|
-
#########
|
148
|
-
|
149
|
-
# @raise ArgumentError if the *path* does not exist
|
150
|
-
def get_node_chain(path)
|
151
|
-
n = @root
|
152
|
-
result = [n]
|
153
|
-
path.sub(%r{^/}, "").split("/").each do |elem|
|
154
|
-
n = n[elem]
|
155
|
-
raise ArgumentError, "Object path #{path} doesn't exist" if n.nil?
|
156
|
-
|
157
|
-
result.push(n)
|
158
|
-
end
|
159
|
-
result
|
160
|
-
end
|
161
|
-
|
162
|
-
# Perform a recursive retrospection on the given current _node_
|
163
|
-
# on the given _path_.
|
164
|
-
def rec_introspect(node, path)
|
165
|
-
xml = bus.introspect_data(@name, path)
|
166
|
-
intfs, subnodes = IntrospectXMLParser.new(xml).parse
|
167
|
-
subnodes.each do |nodename|
|
168
|
-
subnode = node[nodename] = Node.new(nodename)
|
169
|
-
subpath = if path == "/"
|
170
|
-
"/#{nodename}"
|
171
|
-
else
|
172
|
-
"#{path}/#{nodename}"
|
173
|
-
end
|
174
|
-
rec_introspect(subnode, subpath)
|
175
|
-
end
|
176
|
-
return if intfs.empty?
|
177
|
-
|
178
|
-
node.object = ProxyObjectFactory.new(xml, @bus, @name, path).build
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
# = Object path node class
|
183
|
-
#
|
184
|
-
# Class representing a node on an object path.
|
185
|
-
class Node < Hash
|
186
|
-
# @return [DBus::Object,DBus::ProxyObject,nil]
|
187
|
-
# The D-Bus object contained by the node.
|
188
|
-
attr_accessor :object
|
189
|
-
|
190
|
-
# The name of the node.
|
191
|
-
# @return [String] the last component of its object path, or "/"
|
192
|
-
attr_reader :name
|
193
|
-
|
194
|
-
# Create a new node with a given _name_.
|
195
|
-
def initialize(name)
|
196
|
-
super()
|
197
|
-
@name = name
|
198
|
-
@object = nil
|
199
|
-
end
|
200
|
-
|
201
|
-
# Return an XML string representation of the node.
|
202
|
-
# It is shallow, not recursing into subnodes
|
203
|
-
# @param node_opath [String]
|
204
|
-
def to_xml(node_opath)
|
205
|
-
xml = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
206
|
-
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
207
|
-
'
|
208
|
-
xml += "<node name=\"#{node_opath}\">\n"
|
209
|
-
each_key do |k|
|
210
|
-
xml += " <node name=\"#{k}\" />\n"
|
211
|
-
end
|
212
|
-
@object&.intfs&.each_value do |v|
|
213
|
-
xml += v.to_xml
|
214
|
-
end
|
215
|
-
xml += "</node>"
|
216
|
-
xml
|
217
|
-
end
|
218
|
-
|
219
|
-
# Return inspect information of the node.
|
220
|
-
def inspect
|
221
|
-
# Need something here
|
222
|
-
"<DBus::Node #{sub_inspect}>"
|
223
|
-
end
|
224
|
-
|
225
|
-
# Return instance inspect information, used by Node#inspect.
|
226
|
-
def sub_inspect
|
227
|
-
s = ""
|
228
|
-
if !@object.nil?
|
229
|
-
s += format("%x ", @object.object_id)
|
230
|
-
end
|
231
|
-
contents_sub_inspect = keys
|
232
|
-
.map { |k| "#{k} => #{self[k].sub_inspect}" }
|
233
|
-
.join(",")
|
234
|
-
"#{s}{#{contents_sub_inspect}}"
|
235
|
-
end
|
236
|
-
|
237
|
-
# All objects (not paths) under this path (except itself).
|
238
|
-
# @return [Array<DBus::Object>]
|
239
|
-
def descendant_objects
|
240
|
-
children_objects = values.map(&:object).compact
|
241
|
-
descendants = values.map(&:descendant_objects)
|
242
|
-
flat_descendants = descendants.reduce([], &:+)
|
243
|
-
children_objects + flat_descendants
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
# FIXME: rename Connection to Bus?
|
248
|
-
|
249
|
-
# D-Bus main connection class
|
250
|
-
#
|
251
|
-
# Main class that maintains a connection to a bus and can handle incoming
|
252
|
-
# and outgoing messages.
|
253
|
-
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
|
254
25
|
# The unique name (by specification) of the message.
|
255
26
|
attr_reader :unique_name
|
256
|
-
# pop and push messages here
|
257
|
-
attr_reader :message_queue
|
258
27
|
|
259
|
-
#
|
260
|
-
#
|
261
|
-
#
|
262
|
-
|
263
|
-
|
264
|
-
# e.g. "unix:path=/tmp/dbus-test" or "tcp:host=localhost,port=2687"
|
265
|
-
def initialize(path)
|
266
|
-
@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
|
267
33
|
@unique_name = nil
|
268
|
-
|
269
|
-
# @return [Hash{Integer => Proc}]
|
270
|
-
# key: message serial
|
271
|
-
# value: block to be run when the reply to that message is received
|
272
|
-
@method_call_replies = {}
|
273
|
-
|
274
|
-
# @return [Hash{Integer => Message}]
|
275
|
-
# for debugging only: messages for which a reply was not received yet;
|
276
|
-
# key == value.serial
|
277
|
-
@method_call_msgs = {}
|
278
|
-
@signal_matchrules = {}
|
279
34
|
@proxy = nil
|
280
|
-
|
281
|
-
|
282
|
-
# Dispatch all messages that are available in the queue,
|
283
|
-
# but do not block on the queue.
|
284
|
-
# Called by a main loop when something is available in the queue
|
285
|
-
def dispatch_message_queue
|
286
|
-
while (msg = @message_queue.pop(blocking: false)) # FIXME: EOFError
|
287
|
-
process(msg)
|
288
|
-
end
|
289
|
-
end
|
290
|
-
|
291
|
-
# Tell a bus to register itself on the glib main loop
|
292
|
-
def glibize
|
293
|
-
require "glib2"
|
294
|
-
# Circumvent a ruby-glib bug
|
295
|
-
@channels ||= []
|
296
|
-
|
297
|
-
gio = GLib::IOChannel.new(@message_queue.socket.fileno)
|
298
|
-
@channels << gio
|
299
|
-
gio.add_watch(GLib::IOChannel::IN) do |_c, _ch|
|
300
|
-
dispatch_message_queue
|
301
|
-
true
|
302
|
-
end
|
303
|
-
end
|
304
|
-
|
305
|
-
# FIXME: describe the following names, flags and constants.
|
306
|
-
# See DBus spec for definition
|
307
|
-
NAME_FLAG_ALLOW_REPLACEMENT = 0x1
|
308
|
-
NAME_FLAG_REPLACE_EXISTING = 0x2
|
309
|
-
NAME_FLAG_DO_NOT_QUEUE = 0x4
|
310
|
-
|
311
|
-
REQUEST_NAME_REPLY_PRIMARY_OWNER = 0x1
|
312
|
-
REQUEST_NAME_REPLY_IN_QUEUE = 0x2
|
313
|
-
REQUEST_NAME_REPLY_EXISTS = 0x3
|
314
|
-
REQUEST_NAME_REPLY_ALREADY_OWNER = 0x4
|
315
|
-
|
316
|
-
DBUSXMLINTRO = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
317
|
-
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
318
|
-
<node>
|
319
|
-
<interface name="org.freedesktop.DBus.Introspectable">
|
320
|
-
<method name="Introspect">
|
321
|
-
<arg direction="out" type="s"/>
|
322
|
-
</method>
|
323
|
-
</interface>
|
324
|
-
<interface name="org.freedesktop.DBus">
|
325
|
-
<method name="Hello">
|
326
|
-
<arg direction="out" type="s"/>
|
327
|
-
</method>
|
328
|
-
<method name="RequestName">
|
329
|
-
<arg direction="in" type="s"/>
|
330
|
-
<arg direction="in" type="u"/>
|
331
|
-
<arg direction="out" type="u"/>
|
332
|
-
</method>
|
333
|
-
<method name="ReleaseName">
|
334
|
-
<arg direction="in" type="s"/>
|
335
|
-
<arg direction="out" type="u"/>
|
336
|
-
</method>
|
337
|
-
<method name="StartServiceByName">
|
338
|
-
<arg direction="in" type="s"/>
|
339
|
-
<arg direction="in" type="u"/>
|
340
|
-
<arg direction="out" type="u"/>
|
341
|
-
</method>
|
342
|
-
<method name="UpdateActivationEnvironment">
|
343
|
-
<arg direction="in" type="a{ss}"/>
|
344
|
-
</method>
|
345
|
-
<method name="NameHasOwner">
|
346
|
-
<arg direction="in" type="s"/>
|
347
|
-
<arg direction="out" type="b"/>
|
348
|
-
</method>
|
349
|
-
<method name="ListNames">
|
350
|
-
<arg direction="out" type="as"/>
|
351
|
-
</method>
|
352
|
-
<method name="ListActivatableNames">
|
353
|
-
<arg direction="out" type="as"/>
|
354
|
-
</method>
|
355
|
-
<method name="AddMatch">
|
356
|
-
<arg direction="in" type="s"/>
|
357
|
-
</method>
|
358
|
-
<method name="RemoveMatch">
|
359
|
-
<arg direction="in" type="s"/>
|
360
|
-
</method>
|
361
|
-
<method name="GetNameOwner">
|
362
|
-
<arg direction="in" type="s"/>
|
363
|
-
<arg direction="out" type="s"/>
|
364
|
-
</method>
|
365
|
-
<method name="ListQueuedOwners">
|
366
|
-
<arg direction="in" type="s"/>
|
367
|
-
<arg direction="out" type="as"/>
|
368
|
-
</method>
|
369
|
-
<method name="GetConnectionUnixUser">
|
370
|
-
<arg direction="in" type="s"/>
|
371
|
-
<arg direction="out" type="u"/>
|
372
|
-
</method>
|
373
|
-
<method name="GetConnectionUnixProcessID">
|
374
|
-
<arg direction="in" type="s"/>
|
375
|
-
<arg direction="out" type="u"/>
|
376
|
-
</method>
|
377
|
-
<method name="GetAdtAuditSessionData">
|
378
|
-
<arg direction="in" type="s"/>
|
379
|
-
<arg direction="out" type="ay"/>
|
380
|
-
</method>
|
381
|
-
<method name="GetConnectionSELinuxSecurityContext">
|
382
|
-
<arg direction="in" type="s"/>
|
383
|
-
<arg direction="out" type="ay"/>
|
384
|
-
</method>
|
385
|
-
<method name="ReloadConfig">
|
386
|
-
</method>
|
387
|
-
<method name="GetId">
|
388
|
-
<arg direction="out" type="s"/>
|
389
|
-
</method>
|
390
|
-
<method name="GetConnectionCredentials">
|
391
|
-
<arg direction="in" type="s"/>
|
392
|
-
<arg direction="out" type="a{sv}"/>
|
393
|
-
</method>
|
394
|
-
<property name="Features" type="as" access="read">
|
395
|
-
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
|
396
|
-
</property>
|
397
|
-
<property name="Interfaces" type="as" access="read">
|
398
|
-
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
|
399
|
-
</property>
|
400
|
-
<signal name="NameOwnerChanged">
|
401
|
-
<arg type="s"/>
|
402
|
-
<arg type="s"/>
|
403
|
-
<arg type="s"/>
|
404
|
-
</signal>
|
405
|
-
<signal name="NameLost">
|
406
|
-
<arg type="s"/>
|
407
|
-
</signal>
|
408
|
-
<signal name="NameAcquired">
|
409
|
-
<arg type="s"/>
|
410
|
-
</signal>
|
411
|
-
</interface>
|
412
|
-
</node>
|
413
|
-
'
|
414
|
-
# This apostroph is for syntax highlighting editors confused by above xml: "
|
415
|
-
|
416
|
-
# @api private
|
417
|
-
# Send a _message_.
|
418
|
-
# If _reply_handler_ is not given, wait for the reply
|
419
|
-
# and return the reply, or raise the error.
|
420
|
-
# If _reply_handler_ is given, it will be called when the reply
|
421
|
-
# eventually arrives, with the reply message as the 1st param
|
422
|
-
# and its params following
|
423
|
-
def send_sync_or_async(message, &reply_handler)
|
424
|
-
ret = nil
|
425
|
-
if reply_handler.nil?
|
426
|
-
send_sync(message) do |rmsg|
|
427
|
-
raise rmsg if rmsg.is_a?(Error)
|
428
|
-
|
429
|
-
ret = rmsg.params
|
430
|
-
end
|
431
|
-
else
|
432
|
-
on_return(message) do |rmsg|
|
433
|
-
if rmsg.is_a?(Error)
|
434
|
-
reply_handler.call(rmsg)
|
435
|
-
else
|
436
|
-
reply_handler.call(rmsg, * rmsg.params)
|
437
|
-
end
|
438
|
-
end
|
439
|
-
@message_queue.push(message)
|
440
|
-
end
|
441
|
-
ret
|
442
|
-
end
|
443
|
-
|
444
|
-
# @api private
|
445
|
-
def introspect_data(dest, path, &reply_handler)
|
446
|
-
m = DBus::Message.new(DBus::Message::METHOD_CALL)
|
447
|
-
m.path = path
|
448
|
-
m.interface = "org.freedesktop.DBus.Introspectable"
|
449
|
-
m.destination = dest
|
450
|
-
m.member = "Introspect"
|
451
|
-
m.sender = unique_name
|
452
|
-
if reply_handler.nil?
|
453
|
-
send_sync_or_async(m).first
|
454
|
-
else
|
455
|
-
send_sync_or_async(m) do |*args|
|
456
|
-
# TODO: test async introspection, is it used at all?
|
457
|
-
args.shift # forget the message, pass only the text
|
458
|
-
reply_handler.call(*args)
|
459
|
-
nil
|
460
|
-
end
|
461
|
-
end
|
462
|
-
end
|
463
|
-
|
464
|
-
# @api private
|
465
|
-
# Issues a call to the org.freedesktop.DBus.Introspectable.Introspect method
|
466
|
-
# _dest_ is the service and _path_ the object path you want to introspect
|
467
|
-
# If a code block is given, the introspect call in asynchronous. If not
|
468
|
-
# data is returned
|
469
|
-
#
|
470
|
-
# FIXME: link to ProxyObject data definition
|
471
|
-
# The returned object is a ProxyObject that has methods you can call to
|
472
|
-
# issue somme METHOD_CALL messages, and to setup to receive METHOD_RETURN
|
473
|
-
def introspect(dest, path)
|
474
|
-
if !block_given?
|
475
|
-
# introspect in synchronous !
|
476
|
-
data = introspect_data(dest, path)
|
477
|
-
pof = DBus::ProxyObjectFactory.new(data, self, dest, path)
|
478
|
-
pof.build
|
479
|
-
else
|
480
|
-
introspect_data(dest, path) do |async_data|
|
481
|
-
yield(DBus::ProxyObjectFactory.new(async_data, self, dest, path).build)
|
482
|
-
end
|
483
|
-
end
|
484
|
-
end
|
485
|
-
|
486
|
-
# Exception raised when a service name is requested that is not available.
|
487
|
-
class NameRequestError < Exception
|
488
|
-
end
|
489
|
-
|
490
|
-
# Attempt to request a service _name_.
|
491
|
-
#
|
492
|
-
# FIXME, NameRequestError cannot really be rescued as it will be raised
|
493
|
-
# when dispatching a later call. Rework the API to better match the spec.
|
494
|
-
# @return [Service]
|
495
|
-
def request_service(name)
|
496
|
-
# Use RequestName, but asynchronously!
|
497
|
-
# A synchronous call would not work with service activation, where
|
498
|
-
# method calls to be serviced arrive before the reply for RequestName
|
499
|
-
# (Ticket#29).
|
500
|
-
proxy.RequestName(name, NAME_FLAG_REPLACE_EXISTING) do |rmsg, r|
|
501
|
-
# check and report errors first
|
502
|
-
raise rmsg if rmsg.is_a?(Error)
|
503
|
-
|
504
|
-
details = if r == REQUEST_NAME_REPLY_IN_QUEUE
|
505
|
-
other = proxy.GetNameOwner(name).first
|
506
|
-
other_creds = proxy.GetConnectionCredentials(other).first
|
507
|
-
"already owned by #{other}, #{other_creds.inspect}"
|
508
|
-
else
|
509
|
-
"error code #{r}"
|
510
|
-
end
|
511
|
-
raise NameRequestError, "Could not request #{name}, #{details}" unless r == REQUEST_NAME_REPLY_PRIMARY_OWNER
|
512
|
-
end
|
513
|
-
@service = Service.new(name, self)
|
514
|
-
@service
|
35
|
+
send_hello
|
515
36
|
end
|
516
37
|
|
517
38
|
# Set up a ProxyObject for the bus itself, since the bus is introspectable.
|
@@ -520,10 +41,13 @@ module DBus
|
|
520
41
|
# Returns the object.
|
521
42
|
def proxy
|
522
43
|
if @proxy.nil?
|
44
|
+
xml_filename = File.expand_path("org.freedesktop.DBus.xml", __dir__)
|
45
|
+
xml = File.read(xml_filename)
|
46
|
+
|
523
47
|
path = "/org/freedesktop/DBus"
|
524
48
|
dest = "org.freedesktop.DBus"
|
525
49
|
pof = DBus::ProxyObjectFactory.new(
|
526
|
-
|
50
|
+
xml, self, dest, path,
|
527
51
|
api: ApiOptions::A0
|
528
52
|
)
|
529
53
|
@proxy = pof.build["org.freedesktop.DBus"]
|
@@ -531,72 +55,39 @@ module DBus
|
|
531
55
|
@proxy
|
532
56
|
end
|
533
57
|
|
534
|
-
# @
|
535
|
-
#
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
#
|
541
|
-
#
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
# @yieldreturn [Array<Object>] the reply (out) parameters
|
547
|
-
def send_sync(msg, &retc) # :yields: reply/return message
|
548
|
-
return if msg.nil? # check if somethings wrong
|
549
|
-
|
550
|
-
@message_queue.push(msg)
|
551
|
-
@method_call_msgs[msg.serial] = msg
|
552
|
-
@method_call_replies[msg.serial] = retc
|
553
|
-
|
554
|
-
retm = wait_for_message
|
555
|
-
return if retm.nil? # check if somethings wrong
|
556
|
-
|
557
|
-
process(retm)
|
558
|
-
while @method_call_replies.key? msg.serial
|
559
|
-
retm = wait_for_message
|
560
|
-
process(retm)
|
561
|
-
end
|
562
|
-
rescue EOFError
|
563
|
-
new_err = DBus::Error.new("Connection dropped after we sent #{msg.inspect}")
|
564
|
-
raise new_err
|
565
|
-
end
|
566
|
-
|
567
|
-
# @api private
|
568
|
-
# Specify a code block that has to be executed when a reply for
|
569
|
-
# message _msg_ is received.
|
570
|
-
# @param msg [Message]
|
571
|
-
def on_return(msg, &retc)
|
572
|
-
# Have a better exception here
|
573
|
-
if msg.message_type != Message::METHOD_CALL
|
574
|
-
raise "on_return should only get method_calls"
|
575
|
-
end
|
576
|
-
|
577
|
-
@method_call_msgs[msg.serial] = msg
|
578
|
-
@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)
|
579
70
|
end
|
580
71
|
|
581
72
|
# Asks bus to send us messages matching mr, and execute slot when
|
582
73
|
# received
|
583
74
|
# @param match_rule [MatchRule,#to_s]
|
75
|
+
# @return [void]
|
584
76
|
def add_match(match_rule, &slot)
|
585
|
-
# check this is a signal.
|
586
77
|
mrs = match_rule.to_s
|
587
|
-
|
78
|
+
rule_existed = super(mrs, &slot)
|
588
79
|
# don't ask for the same match if we override it
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
@signal_matchrules[mrs] = slot
|
80
|
+
return if rule_existed
|
81
|
+
|
82
|
+
DBus.logger.debug "Asked for a new match"
|
83
|
+
proxy.AddMatch(mrs)
|
594
84
|
end
|
595
85
|
|
596
86
|
# @param match_rule [MatchRule,#to_s]
|
87
|
+
# @return [void]
|
597
88
|
def remove_match(match_rule)
|
598
89
|
mrs = match_rule.to_s
|
599
|
-
rule_existed =
|
90
|
+
rule_existed = super(mrs)
|
600
91
|
# don't remove nonexisting matches.
|
601
92
|
return if rule_existed
|
602
93
|
|
@@ -605,97 +96,17 @@ module DBus
|
|
605
96
|
proxy.RemoveMatch(mrs)
|
606
97
|
end
|
607
98
|
|
608
|
-
#
|
609
|
-
#
|
610
|
-
#
|
611
|
-
|
612
|
-
return if msg.nil? # check if somethings wrong
|
613
|
-
|
614
|
-
case msg.message_type
|
615
|
-
when Message::ERROR, Message::METHOD_RETURN
|
616
|
-
raise InvalidPacketException if msg.reply_serial.nil?
|
617
|
-
|
618
|
-
mcs = @method_call_replies[msg.reply_serial]
|
619
|
-
if !mcs
|
620
|
-
DBus.logger.debug "no return code for mcs: #{mcs.inspect} msg: #{msg.inspect}"
|
621
|
-
else
|
622
|
-
if msg.message_type == Message::ERROR
|
623
|
-
mcs.call(Error.new(msg))
|
624
|
-
else
|
625
|
-
mcs.call(msg)
|
626
|
-
end
|
627
|
-
@method_call_replies.delete(msg.reply_serial)
|
628
|
-
@method_call_msgs.delete(msg.reply_serial)
|
629
|
-
end
|
630
|
-
when DBus::Message::METHOD_CALL
|
631
|
-
if msg.path == "/org/freedesktop/DBus"
|
632
|
-
DBus.logger.debug "Got method call on /org/freedesktop/DBus"
|
633
|
-
end
|
634
|
-
node = @service.get_node(msg.path, create: false)
|
635
|
-
# introspect a known path even if there is no object on it
|
636
|
-
if node &&
|
637
|
-
msg.interface == "org.freedesktop.DBus.Introspectable" &&
|
638
|
-
msg.member == "Introspect"
|
639
|
-
reply = Message.new(Message::METHOD_RETURN).reply_to(msg)
|
640
|
-
reply.sender = @unique_name
|
641
|
-
xml = node.to_xml(msg.path)
|
642
|
-
reply.add_param(Type::STRING, xml)
|
643
|
-
@message_queue.push(reply)
|
644
|
-
# dispatch for an object
|
645
|
-
elsif node&.object
|
646
|
-
node.object.dispatch(msg)
|
647
|
-
else
|
648
|
-
reply = Message.error(msg, "org.freedesktop.DBus.Error.UnknownObject",
|
649
|
-
"Object #{msg.path} doesn't exist")
|
650
|
-
@message_queue.push(reply)
|
651
|
-
end
|
652
|
-
when DBus::Message::SIGNAL
|
653
|
-
# the signal can match multiple different rules
|
654
|
-
# clone to allow new signale handlers to be registered
|
655
|
-
@signal_matchrules.dup.each do |mrs, slot|
|
656
|
-
if DBus::MatchRule.new.from_s(mrs).match(msg)
|
657
|
-
slot.call(msg)
|
658
|
-
end
|
659
|
-
end
|
660
|
-
else
|
661
|
-
# spec(Message Format): Unknown types must be ignored.
|
662
|
-
DBus.logger.debug "Unknown message type: #{msg.message_type}"
|
663
|
-
end
|
664
|
-
rescue Exception => e
|
665
|
-
raise msg.annotate_exception(e)
|
666
|
-
end
|
667
|
-
|
668
|
-
# Retrieves the Service with the given _name_.
|
669
|
-
# @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]
|
670
103
|
def service(name)
|
671
104
|
# The service might not exist at this time so we cannot really check
|
672
105
|
# anything
|
673
|
-
|
106
|
+
ProxyService.new(name, self)
|
674
107
|
end
|
675
108
|
alias [] service
|
676
109
|
|
677
|
-
# @api private
|
678
|
-
# Emit a signal event for the given _service_, object _obj_, interface
|
679
|
-
# _intf_ and signal _sig_ with arguments _args_.
|
680
|
-
# @param service [Service]
|
681
|
-
# @param obj [DBus::Object]
|
682
|
-
# @param intf [Interface]
|
683
|
-
# @param sig [Signal]
|
684
|
-
# @param args arguments for the signal
|
685
|
-
def emit(service, obj, intf, sig, *args)
|
686
|
-
m = Message.new(DBus::Message::SIGNAL)
|
687
|
-
m.path = obj.path
|
688
|
-
m.interface = intf.name
|
689
|
-
m.member = sig.name
|
690
|
-
m.sender = service.name
|
691
|
-
i = 0
|
692
|
-
sig.params.each do |par|
|
693
|
-
m.add_param(par.type, args[i])
|
694
|
-
i += 1
|
695
|
-
end
|
696
|
-
@message_queue.push(m)
|
697
|
-
end
|
698
|
-
|
699
110
|
###########################################################################
|
700
111
|
private
|
701
112
|
|
@@ -710,7 +121,6 @@ module DBus
|
|
710
121
|
@unique_name = rmsg.destination
|
711
122
|
DBus.logger.debug "Got hello reply. Our unique_name is #{@unique_name}"
|
712
123
|
end
|
713
|
-
@service = Service.new(@unique_name, self)
|
714
124
|
end
|
715
125
|
end
|
716
126
|
|
@@ -720,11 +130,10 @@ module DBus
|
|
720
130
|
#
|
721
131
|
# Use SessionBus, the non-singleton ASessionBus is
|
722
132
|
# for the test suite.
|
723
|
-
class ASessionBus <
|
133
|
+
class ASessionBus < BusConnection
|
724
134
|
# Get the the default session bus.
|
725
135
|
def initialize
|
726
136
|
super(self.class.session_bus_address)
|
727
|
-
send_hello
|
728
137
|
end
|
729
138
|
|
730
139
|
def self.session_bus_address
|
@@ -771,11 +180,10 @@ module DBus
|
|
771
180
|
#
|
772
181
|
# Use SystemBus, the non-singleton ASystemBus is
|
773
182
|
# for the test suite.
|
774
|
-
class ASystemBus <
|
183
|
+
class ASystemBus < BusConnection
|
775
184
|
# Get the default system bus.
|
776
185
|
def initialize
|
777
186
|
super(self.class.system_bus_address)
|
778
|
-
send_hello
|
779
187
|
end
|
780
188
|
|
781
189
|
def self.system_bus_address
|
@@ -794,12 +202,9 @@ module DBus
|
|
794
202
|
#
|
795
203
|
# you'll need to take care about authentification then, more info here:
|
796
204
|
# https://gitlab.com/pangdudu/ruby-dbus/-/blob/master/README.rdoc
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
super(socket_name)
|
801
|
-
send_hello
|
802
|
-
end
|
205
|
+
# TODO: keep the name but update the docs
|
206
|
+
# @deprecated just use BusConnection
|
207
|
+
class RemoteBus < BusConnection
|
803
208
|
end
|
804
209
|
|
805
210
|
# See ASystemBus
|
@@ -808,65 +213,14 @@ module DBus
|
|
808
213
|
end
|
809
214
|
|
810
215
|
# Shortcut for the {SystemBus} instance
|
811
|
-
# @return [
|
216
|
+
# @return [BusConnection]
|
812
217
|
def self.system_bus
|
813
218
|
SystemBus.instance
|
814
219
|
end
|
815
220
|
|
816
221
|
# Shortcut for the {SessionBus} instance
|
817
|
-
# @return [
|
222
|
+
# @return [BusConnection]
|
818
223
|
def self.session_bus
|
819
224
|
SessionBus.instance
|
820
225
|
end
|
821
|
-
|
822
|
-
# = Main event loop class.
|
823
|
-
#
|
824
|
-
# Class that takes care of handling message and signal events
|
825
|
-
# asynchronously. *Note:* This is a native implement and therefore does
|
826
|
-
# not integrate with a graphical widget set main loop.
|
827
|
-
class Main
|
828
|
-
# Create a new main event loop.
|
829
|
-
def initialize
|
830
|
-
@buses = {}
|
831
|
-
@quitting = false
|
832
|
-
end
|
833
|
-
|
834
|
-
# Add a _bus_ to the list of buses to watch for events.
|
835
|
-
def <<(bus)
|
836
|
-
@buses[bus.message_queue.socket] = bus
|
837
|
-
end
|
838
|
-
|
839
|
-
# Quit a running main loop, to be used eg. from a signal handler
|
840
|
-
def quit
|
841
|
-
@quitting = true
|
842
|
-
end
|
843
|
-
|
844
|
-
# Run the main loop. This is a blocking call!
|
845
|
-
def run
|
846
|
-
# before blocking, empty the buffers
|
847
|
-
# https://bugzilla.novell.com/show_bug.cgi?id=537401
|
848
|
-
@buses.each_value do |b|
|
849
|
-
while (m = b.message_queue.message_from_buffer_nonblock)
|
850
|
-
b.process(m)
|
851
|
-
end
|
852
|
-
end
|
853
|
-
while !@quitting && !@buses.empty?
|
854
|
-
ready = IO.select(@buses.keys, [], [], 5) # timeout 5 seconds
|
855
|
-
next unless ready # timeout exceeds so continue unless quitting
|
856
|
-
|
857
|
-
ready.first.each do |socket|
|
858
|
-
b = @buses[socket]
|
859
|
-
begin
|
860
|
-
b.message_queue.buffer_from_socket_nonblock
|
861
|
-
rescue EOFError, SystemCallError
|
862
|
-
@buses.delete socket # this bus died
|
863
|
-
next
|
864
|
-
end
|
865
|
-
while (m = b.message_queue.message_from_buffer_nonblock)
|
866
|
-
b.process(m)
|
867
|
-
end
|
868
|
-
end
|
869
|
-
end
|
870
|
-
end
|
871
|
-
end
|
872
226
|
end
|