em-ruby-dbus 0.11.0
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 +7 -0
- data/COPYING +504 -0
- data/NEWS +253 -0
- data/README.md +93 -0
- data/Rakefile +58 -0
- data/VERSION +1 -0
- data/doc/Reference.md +207 -0
- data/doc/Tutorial.md +480 -0
- data/doc/ex-calling-methods.body.rb +8 -0
- data/doc/ex-calling-methods.rb +3 -0
- data/doc/ex-properties.body.rb +9 -0
- data/doc/ex-properties.rb +3 -0
- data/doc/ex-setup.rb +7 -0
- data/doc/ex-signal.body.rb +20 -0
- data/doc/ex-signal.rb +3 -0
- data/doc/example-helper.rb +6 -0
- data/em-ruby-dbus.gemspec +20 -0
- data/examples/gdbus/gdbus +255 -0
- data/examples/gdbus/gdbus.glade +184 -0
- data/examples/gdbus/launch.sh +4 -0
- data/examples/no-introspect/nm-test.rb +21 -0
- data/examples/no-introspect/tracker-test.rb +16 -0
- data/examples/rhythmbox/playpause.rb +25 -0
- data/examples/service/call_service.rb +25 -0
- data/examples/service/service_newapi.rb +51 -0
- data/examples/simple/call_introspect.rb +34 -0
- data/examples/simple/properties.rb +19 -0
- data/examples/utils/listnames.rb +11 -0
- data/examples/utils/notify.rb +19 -0
- data/lib/dbus.rb +82 -0
- data/lib/dbus/auth.rb +269 -0
- data/lib/dbus/bus.rb +739 -0
- data/lib/dbus/core_ext/array/extract_options.rb +31 -0
- data/lib/dbus/core_ext/class/attribute.rb +129 -0
- data/lib/dbus/core_ext/kernel/singleton_class.rb +8 -0
- data/lib/dbus/core_ext/module/remove_method.rb +14 -0
- data/lib/dbus/error.rb +46 -0
- data/lib/dbus/export.rb +128 -0
- data/lib/dbus/introspect.rb +219 -0
- data/lib/dbus/logger.rb +31 -0
- data/lib/dbus/loop-em.rb +19 -0
- data/lib/dbus/marshall.rb +434 -0
- data/lib/dbus/matchrule.rb +101 -0
- data/lib/dbus/message.rb +276 -0
- data/lib/dbus/message_queue.rb +166 -0
- data/lib/dbus/proxy_object.rb +149 -0
- data/lib/dbus/proxy_object_factory.rb +41 -0
- data/lib/dbus/proxy_object_interface.rb +128 -0
- data/lib/dbus/type.rb +193 -0
- data/lib/dbus/xml.rb +161 -0
- data/test/async_spec.rb +47 -0
- data/test/binding_spec.rb +74 -0
- data/test/bus_and_xml_backend_spec.rb +39 -0
- data/test/bus_driver_spec.rb +20 -0
- data/test/bus_spec.rb +20 -0
- data/test/byte_array_spec.rb +38 -0
- data/test/err_msg_spec.rb +42 -0
- data/test/introspect_xml_parser_spec.rb +26 -0
- data/test/introspection_spec.rb +32 -0
- data/test/main_loop_spec.rb +82 -0
- data/test/property_spec.rb +53 -0
- data/test/server_robustness_spec.rb +66 -0
- data/test/server_spec.rb +53 -0
- data/test/service_newapi.rb +217 -0
- data/test/session_bus_spec_manual.rb +15 -0
- data/test/signal_spec.rb +90 -0
- data/test/spec_helper.rb +33 -0
- data/test/thread_safety_spec.rb +31 -0
- data/test/tools/dbus-launch-simple +35 -0
- data/test/tools/dbus-limited-session.conf +28 -0
- data/test/tools/test_env +13 -0
- data/test/tools/test_server +39 -0
- data/test/type_spec.rb +19 -0
- data/test/value_spec.rb +81 -0
- data/test/variant_spec.rb +66 -0
- metadata +145 -0
data/lib/dbus/bus.rb
ADDED
@@ -0,0 +1,739 @@
|
|
1
|
+
# dbus.rb - Module containing the low-level D-Bus implementation
|
2
|
+
#
|
3
|
+
# This file is part of the ruby-dbus project
|
4
|
+
# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
|
5
|
+
#
|
6
|
+
# This library is free software; you caan redistribute it and/or
|
7
|
+
# modify it under the terms of the GNU Lesser General Public
|
8
|
+
# License, version 2.1 as published by the Free Software Foundation.
|
9
|
+
# See the file "COPYING" for the exact licensing terms.
|
10
|
+
|
11
|
+
require 'socket'
|
12
|
+
require 'thread'
|
13
|
+
require 'singleton'
|
14
|
+
|
15
|
+
# = D-Bus main module
|
16
|
+
#
|
17
|
+
# Module containing all the D-Bus modules and classes.
|
18
|
+
module DBus
|
19
|
+
# This represents a remote service. It should not be instantiated directly
|
20
|
+
# Use Bus::service()
|
21
|
+
class Service
|
22
|
+
# The service name.
|
23
|
+
attr_reader :name
|
24
|
+
# The bus the service is running on.
|
25
|
+
attr_reader :bus
|
26
|
+
# The service root (FIXME).
|
27
|
+
attr_reader :root
|
28
|
+
|
29
|
+
# Create a new service with a given _name_ on a given _bus_.
|
30
|
+
def initialize(name, bus)
|
31
|
+
@name, @bus = name, bus
|
32
|
+
@root = Node.new("/")
|
33
|
+
end
|
34
|
+
|
35
|
+
# Determine whether the service name already exists.
|
36
|
+
def exists?
|
37
|
+
bus.proxy.ListNames[0].member?(@name)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Perform an introspection on all the objects on the service
|
41
|
+
# (starting recursively from the root).
|
42
|
+
def introspect
|
43
|
+
if block_given?
|
44
|
+
raise NotImplementedError
|
45
|
+
else
|
46
|
+
rec_introspect(@root, "/")
|
47
|
+
end
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Retrieves an object at the given _path_.
|
52
|
+
# @return [ProxyObject]
|
53
|
+
def object(path)
|
54
|
+
node = get_node(path, true)
|
55
|
+
if node.object.nil?
|
56
|
+
node.object = ProxyObject.new(@bus, @name, path)
|
57
|
+
end
|
58
|
+
node.object
|
59
|
+
end
|
60
|
+
|
61
|
+
# Export an object _obj_ (an DBus::Object subclass instance).
|
62
|
+
def export(obj)
|
63
|
+
obj.service = self
|
64
|
+
get_node(obj.path, true).object = obj
|
65
|
+
end
|
66
|
+
|
67
|
+
# Undo exporting an object _obj_.
|
68
|
+
# Raises ArgumentError if it is not a DBus::Object.
|
69
|
+
# Returns the object, or false if _obj_ was not exported.
|
70
|
+
def unexport(obj)
|
71
|
+
raise ArgumentError.new("DBus::Service#unexport() expects a DBus::Object argument") unless obj.kind_of?(DBus::Object)
|
72
|
+
return false unless obj.path
|
73
|
+
pathSep = obj.path.rindex("/") #last path seperator
|
74
|
+
parent_path = obj.path[1..pathSep-1]
|
75
|
+
node_name = obj.path[pathSep+1..-1]
|
76
|
+
|
77
|
+
parent_node = get_node(parent_path, false)
|
78
|
+
return false unless parent_node
|
79
|
+
obj.service = nil
|
80
|
+
parent_node.delete(node_name)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Get the object node corresponding to the given _path_. if _create_ is
|
84
|
+
# true, the the nodes in the path are created if they do not already exist.
|
85
|
+
def get_node(path, create = false)
|
86
|
+
n = @root
|
87
|
+
path.sub(/^\//, "").split("/").each do |elem|
|
88
|
+
if not n[elem]
|
89
|
+
if not create
|
90
|
+
return nil
|
91
|
+
else
|
92
|
+
n[elem] = Node.new(elem)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
n = n[elem]
|
96
|
+
end
|
97
|
+
if n.nil?
|
98
|
+
DBus.logger.debug "Warning, unknown object #{path}"
|
99
|
+
end
|
100
|
+
n
|
101
|
+
end
|
102
|
+
|
103
|
+
#########
|
104
|
+
private
|
105
|
+
#########
|
106
|
+
|
107
|
+
# Perform a recursive retrospection on the given current _node_
|
108
|
+
# on the given _path_.
|
109
|
+
def rec_introspect(node, path)
|
110
|
+
xml = bus.introspect_data(@name, path)
|
111
|
+
intfs, subnodes = IntrospectXMLParser.new(xml).parse
|
112
|
+
subnodes.each do |nodename|
|
113
|
+
subnode = node[nodename] = Node.new(nodename)
|
114
|
+
if path == "/"
|
115
|
+
subpath = "/" + nodename
|
116
|
+
else
|
117
|
+
subpath = path + "/" + nodename
|
118
|
+
end
|
119
|
+
rec_introspect(subnode, subpath)
|
120
|
+
end
|
121
|
+
if intfs.size > 0
|
122
|
+
node.object = ProxyObjectFactory.new(xml, @bus, @name, path).build
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# = Object path node class
|
128
|
+
#
|
129
|
+
# Class representing a node on an object path.
|
130
|
+
class Node < Hash
|
131
|
+
# The D-Bus object contained by the node.
|
132
|
+
attr_accessor :object
|
133
|
+
# The name of the node.
|
134
|
+
attr_reader :name
|
135
|
+
|
136
|
+
# Create a new node with a given _name_.
|
137
|
+
def initialize(name)
|
138
|
+
@name = name
|
139
|
+
@object = nil
|
140
|
+
end
|
141
|
+
|
142
|
+
# Return an XML string representation of the node.
|
143
|
+
# It is shallow, not recursing into subnodes
|
144
|
+
def to_xml
|
145
|
+
xml = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
146
|
+
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
147
|
+
<node>
|
148
|
+
'
|
149
|
+
self.each_pair do |k, v|
|
150
|
+
xml += "<node name=\"#{k}\" />"
|
151
|
+
end
|
152
|
+
if @object
|
153
|
+
@object.intfs.each_pair do |k, v|
|
154
|
+
xml += %{<interface name="#{v.name}">\n}
|
155
|
+
v.methods.each_value { |m| xml += m.to_xml }
|
156
|
+
v.signals.each_value { |m| xml += m.to_xml }
|
157
|
+
xml +="</interface>\n"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
xml += '</node>'
|
161
|
+
xml
|
162
|
+
end
|
163
|
+
|
164
|
+
# Return inspect information of the node.
|
165
|
+
def inspect
|
166
|
+
# Need something here
|
167
|
+
"<DBus::Node #{sub_inspect}>"
|
168
|
+
end
|
169
|
+
|
170
|
+
# Return instance inspect information, used by Node#inspect.
|
171
|
+
def sub_inspect
|
172
|
+
s = ""
|
173
|
+
if not @object.nil?
|
174
|
+
s += "%x " % @object.object_id
|
175
|
+
end
|
176
|
+
s + "{" + keys.collect { |k| "#{k} => #{self[k].sub_inspect}" }.join(",") + "}"
|
177
|
+
end
|
178
|
+
end # class Inspect
|
179
|
+
|
180
|
+
# FIXME: rename Connection to Bus?
|
181
|
+
|
182
|
+
# D-Bus main connection class
|
183
|
+
#
|
184
|
+
# Main class that maintains a connection to a bus and can handle incoming
|
185
|
+
# and outgoing messages.
|
186
|
+
class Connection
|
187
|
+
# The unique name (by specification) of the message.
|
188
|
+
attr_reader :unique_name
|
189
|
+
# pop and push messages here
|
190
|
+
attr_reader :message_queue
|
191
|
+
|
192
|
+
# Create a new connection to the bus for a given connect _path_. _path_
|
193
|
+
# format is described in the D-Bus specification:
|
194
|
+
# http://dbus.freedesktop.org/doc/dbus-specification.html#addresses
|
195
|
+
# and is something like:
|
196
|
+
# "transport1:key1=value1,key2=value2;transport2:key1=value1,key2=value2"
|
197
|
+
# e.g. "unix:path=/tmp/dbus-test" or "tcp:host=localhost,port=2687"
|
198
|
+
def initialize(path)
|
199
|
+
@message_queue = MessageQueue.new(path)
|
200
|
+
@unique_name = nil
|
201
|
+
@method_call_replies = Hash.new
|
202
|
+
@method_call_msgs = Hash.new
|
203
|
+
@signal_matchrules = Hash.new
|
204
|
+
@proxy = nil
|
205
|
+
@object_root = Node.new("/")
|
206
|
+
end
|
207
|
+
|
208
|
+
# Dispatch all messages that are available in the queue,
|
209
|
+
# but do not block on the queue.
|
210
|
+
# Called by a main loop when something is available in the queue
|
211
|
+
def dispatch_message_queue
|
212
|
+
while (msg = @message_queue.pop(:non_block)) # FIXME EOFError
|
213
|
+
process(msg)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Tell a bus to register itself on the glib main loop
|
218
|
+
def glibize
|
219
|
+
require 'glib2'
|
220
|
+
# Circumvent a ruby-glib bug
|
221
|
+
@channels ||= Array.new
|
222
|
+
|
223
|
+
gio = GLib::IOChannel.new(@message_queue.socket.fileno)
|
224
|
+
@channels << gio
|
225
|
+
gio.add_watch(GLib::IOChannel::IN) do |c, ch|
|
226
|
+
dispatch_message_queue
|
227
|
+
true
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
def eventmachinize
|
233
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "loop-em")
|
234
|
+
|
235
|
+
conn = ::EventMachine.watch(@message_queue.socket, Loop::EventMachine::Reader, self)
|
236
|
+
conn.notify_readable = true
|
237
|
+
end
|
238
|
+
|
239
|
+
# FIXME: describe the following names, flags and constants.
|
240
|
+
# See DBus spec for definition
|
241
|
+
NAME_FLAG_ALLOW_REPLACEMENT = 0x1
|
242
|
+
NAME_FLAG_REPLACE_EXISTING = 0x2
|
243
|
+
NAME_FLAG_DO_NOT_QUEUE = 0x4
|
244
|
+
|
245
|
+
REQUEST_NAME_REPLY_PRIMARY_OWNER = 0x1
|
246
|
+
REQUEST_NAME_REPLY_IN_QUEUE = 0x2
|
247
|
+
REQUEST_NAME_REPLY_EXISTS = 0x3
|
248
|
+
REQUEST_NAME_REPLY_ALREADY_OWNER = 0x4
|
249
|
+
|
250
|
+
DBUSXMLINTRO = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
251
|
+
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
252
|
+
<node>
|
253
|
+
<interface name="org.freedesktop.DBus.Introspectable">
|
254
|
+
<method name="Introspect">
|
255
|
+
<arg name="data" direction="out" type="s"/>
|
256
|
+
</method>
|
257
|
+
</interface>
|
258
|
+
<interface name="org.freedesktop.DBus">
|
259
|
+
<method name="RequestName">
|
260
|
+
<arg direction="in" type="s"/>
|
261
|
+
<arg direction="in" type="u"/>
|
262
|
+
<arg direction="out" type="u"/>
|
263
|
+
</method>
|
264
|
+
<method name="ReleaseName">
|
265
|
+
<arg direction="in" type="s"/>
|
266
|
+
<arg direction="out" type="u"/>
|
267
|
+
</method>
|
268
|
+
<method name="StartServiceByName">
|
269
|
+
<arg direction="in" type="s"/>
|
270
|
+
<arg direction="in" type="u"/>
|
271
|
+
<arg direction="out" type="u"/>
|
272
|
+
</method>
|
273
|
+
<method name="Hello">
|
274
|
+
<arg direction="out" type="s"/>
|
275
|
+
</method>
|
276
|
+
<method name="NameHasOwner">
|
277
|
+
<arg direction="in" type="s"/>
|
278
|
+
<arg direction="out" type="b"/>
|
279
|
+
</method>
|
280
|
+
<method name="ListNames">
|
281
|
+
<arg direction="out" type="as"/>
|
282
|
+
</method>
|
283
|
+
<method name="ListActivatableNames">
|
284
|
+
<arg direction="out" type="as"/>
|
285
|
+
</method>
|
286
|
+
<method name="AddMatch">
|
287
|
+
<arg direction="in" type="s"/>
|
288
|
+
</method>
|
289
|
+
<method name="RemoveMatch">
|
290
|
+
<arg direction="in" type="s"/>
|
291
|
+
</method>
|
292
|
+
<method name="GetNameOwner">
|
293
|
+
<arg direction="in" type="s"/>
|
294
|
+
<arg direction="out" type="s"/>
|
295
|
+
</method>
|
296
|
+
<method name="ListQueuedOwners">
|
297
|
+
<arg direction="in" type="s"/>
|
298
|
+
<arg direction="out" type="as"/>
|
299
|
+
</method>
|
300
|
+
<method name="GetConnectionUnixUser">
|
301
|
+
<arg direction="in" type="s"/>
|
302
|
+
<arg direction="out" type="u"/>
|
303
|
+
</method>
|
304
|
+
<method name="GetConnectionUnixProcessID">
|
305
|
+
<arg direction="in" type="s"/>
|
306
|
+
<arg direction="out" type="u"/>
|
307
|
+
</method>
|
308
|
+
<method name="GetConnectionSELinuxSecurityContext">
|
309
|
+
<arg direction="in" type="s"/>
|
310
|
+
<arg direction="out" type="ay"/>
|
311
|
+
</method>
|
312
|
+
<method name="ReloadConfig">
|
313
|
+
</method>
|
314
|
+
<signal name="NameOwnerChanged">
|
315
|
+
<arg type="s"/>
|
316
|
+
<arg type="s"/>
|
317
|
+
<arg type="s"/>
|
318
|
+
</signal>
|
319
|
+
<signal name="NameLost">
|
320
|
+
<arg type="s"/>
|
321
|
+
</signal>
|
322
|
+
<signal name="NameAcquired">
|
323
|
+
<arg type="s"/>
|
324
|
+
</signal>
|
325
|
+
</interface>
|
326
|
+
</node>
|
327
|
+
'
|
328
|
+
# This apostroph is for syntax highlighting editors confused by above xml: "
|
329
|
+
|
330
|
+
# @api private
|
331
|
+
# Send a _message_.
|
332
|
+
# If _reply_handler_ is not given, wait for the reply
|
333
|
+
# and return the reply, or raise the error.
|
334
|
+
# If _reply_handler_ is given, it will be called when the reply
|
335
|
+
# eventually arrives, with the reply message as the 1st param
|
336
|
+
# and its params following
|
337
|
+
def send_sync_or_async(message, &reply_handler)
|
338
|
+
ret = nil
|
339
|
+
if reply_handler.nil?
|
340
|
+
send_sync(message) do |rmsg|
|
341
|
+
if rmsg.is_a?(Error)
|
342
|
+
raise rmsg
|
343
|
+
else
|
344
|
+
ret = rmsg.params
|
345
|
+
end
|
346
|
+
end
|
347
|
+
else
|
348
|
+
on_return(message) do |rmsg|
|
349
|
+
if rmsg.is_a?(Error)
|
350
|
+
reply_handler.call(rmsg)
|
351
|
+
else
|
352
|
+
reply_handler.call(rmsg, * rmsg.params)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
@message_queue.push(message)
|
356
|
+
end
|
357
|
+
ret
|
358
|
+
end
|
359
|
+
|
360
|
+
# @api private
|
361
|
+
def introspect_data(dest, path, &reply_handler)
|
362
|
+
m = DBus::Message.new(DBus::Message::METHOD_CALL)
|
363
|
+
m.path = path
|
364
|
+
m.interface = "org.freedesktop.DBus.Introspectable"
|
365
|
+
m.destination = dest
|
366
|
+
m.member = "Introspect"
|
367
|
+
m.sender = unique_name
|
368
|
+
if reply_handler.nil?
|
369
|
+
send_sync_or_async(m).first
|
370
|
+
else
|
371
|
+
send_sync_or_async(m) do |*args|
|
372
|
+
# TODO test async introspection, is it used at all?
|
373
|
+
args.shift # forget the message, pass only the text
|
374
|
+
reply_handler.call(*args)
|
375
|
+
nil
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
# @api private
|
381
|
+
# Issues a call to the org.freedesktop.DBus.Introspectable.Introspect method
|
382
|
+
# _dest_ is the service and _path_ the object path you want to introspect
|
383
|
+
# If a code block is given, the introspect call in asynchronous. If not
|
384
|
+
# data is returned
|
385
|
+
#
|
386
|
+
# FIXME: link to ProxyObject data definition
|
387
|
+
# The returned object is a ProxyObject that has methods you can call to
|
388
|
+
# issue somme METHOD_CALL messages, and to setup to receive METHOD_RETURN
|
389
|
+
def introspect(dest, path)
|
390
|
+
if not block_given?
|
391
|
+
# introspect in synchronous !
|
392
|
+
data = introspect_data(dest, path)
|
393
|
+
pof = DBus::ProxyObjectFactory.new(data, self, dest, path)
|
394
|
+
return pof.build
|
395
|
+
else
|
396
|
+
introspect_data(dest, path) do |async_data|
|
397
|
+
yield(DBus::ProxyObjectFactory.new(async_data, self, dest, path).build)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
# Exception raised when a service name is requested that is not available.
|
403
|
+
class NameRequestError < Exception
|
404
|
+
end
|
405
|
+
|
406
|
+
# Attempt to request a service _name_.
|
407
|
+
#
|
408
|
+
# FIXME, NameRequestError cannot really be rescued as it will be raised
|
409
|
+
# when dispatching a later call. Rework the API to better match the spec.
|
410
|
+
# @return [Service]
|
411
|
+
def request_service(name)
|
412
|
+
# Use RequestName, but asynchronously!
|
413
|
+
# A synchronous call would not work with service activation, where
|
414
|
+
# method calls to be serviced arrive before the reply for RequestName
|
415
|
+
# (Ticket#29).
|
416
|
+
proxy.RequestName(name, NAME_FLAG_REPLACE_EXISTING) do |rmsg, r|
|
417
|
+
if rmsg.is_a?(Error) # check and report errors first
|
418
|
+
raise rmsg
|
419
|
+
elsif r != REQUEST_NAME_REPLY_PRIMARY_OWNER
|
420
|
+
raise NameRequestError
|
421
|
+
end
|
422
|
+
end
|
423
|
+
@service = Service.new(name, self)
|
424
|
+
@service
|
425
|
+
end
|
426
|
+
|
427
|
+
# Set up a ProxyObject for the bus itself, since the bus is introspectable.
|
428
|
+
# Returns the object.
|
429
|
+
def proxy
|
430
|
+
if @proxy == nil
|
431
|
+
path = "/org/freedesktop/DBus"
|
432
|
+
dest = "org.freedesktop.DBus"
|
433
|
+
pof = DBus::ProxyObjectFactory.new(DBUSXMLINTRO, self, dest, path)
|
434
|
+
@proxy = pof.build["org.freedesktop.DBus"]
|
435
|
+
end
|
436
|
+
@proxy
|
437
|
+
end
|
438
|
+
|
439
|
+
# @api private
|
440
|
+
# Wait for a message to arrive. Return it once it is available.
|
441
|
+
def wait_for_message
|
442
|
+
@message_queue.pop # FIXME EOFError
|
443
|
+
end
|
444
|
+
|
445
|
+
# @api private
|
446
|
+
# Send a message _m_ on to the bus. This is done synchronously, thus
|
447
|
+
# the call will block until a reply message arrives.
|
448
|
+
def send_sync(m, &retc) # :yields: reply/return message
|
449
|
+
return if m.nil? #check if somethings wrong
|
450
|
+
@message_queue.push(m)
|
451
|
+
@method_call_msgs[m.serial] = m
|
452
|
+
@method_call_replies[m.serial] = retc
|
453
|
+
|
454
|
+
retm = wait_for_message
|
455
|
+
return if retm.nil? #check if somethings wrong
|
456
|
+
|
457
|
+
process(retm)
|
458
|
+
while @method_call_replies.has_key? m.serial
|
459
|
+
retm = wait_for_message
|
460
|
+
process(retm)
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
# @api private
|
465
|
+
# Specify a code block that has to be executed when a reply for
|
466
|
+
# message _m_ is received.
|
467
|
+
def on_return(m, &retc)
|
468
|
+
# Have a better exception here
|
469
|
+
if m.message_type != Message::METHOD_CALL
|
470
|
+
raise "on_return should only get method_calls"
|
471
|
+
end
|
472
|
+
@method_call_msgs[m.serial] = m
|
473
|
+
@method_call_replies[m.serial] = retc
|
474
|
+
end
|
475
|
+
|
476
|
+
# Asks bus to send us messages matching mr, and execute slot when
|
477
|
+
# received
|
478
|
+
def add_match(mr, &slot)
|
479
|
+
# check this is a signal.
|
480
|
+
mrs = mr.to_s
|
481
|
+
DBus.logger.debug "#{@signal_matchrules.size} rules, adding #{mrs.inspect}"
|
482
|
+
# don't ask for the same match if we override it
|
483
|
+
unless @signal_matchrules.key?(mrs)
|
484
|
+
DBus.logger.debug "Asked for a new match"
|
485
|
+
proxy.AddMatch(mrs)
|
486
|
+
end
|
487
|
+
@signal_matchrules[mrs] = slot
|
488
|
+
end
|
489
|
+
|
490
|
+
def remove_match(mr)
|
491
|
+
mrs = mr.to_s
|
492
|
+
unless @signal_matchrules.delete(mrs).nil?
|
493
|
+
# don't remove nonexisting matches.
|
494
|
+
# FIXME if we do try, the Error.MatchRuleNotFound is *not* raised
|
495
|
+
# and instead is reported as "no return code for nil"
|
496
|
+
proxy.RemoveMatch(mrs)
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
# @api private
|
501
|
+
# Process a message _m_ based on its type.
|
502
|
+
def process(m)
|
503
|
+
return if m.nil? #check if somethings wrong
|
504
|
+
case m.message_type
|
505
|
+
when Message::ERROR, Message::METHOD_RETURN
|
506
|
+
raise InvalidPacketException if m.reply_serial == nil
|
507
|
+
mcs = @method_call_replies[m.reply_serial]
|
508
|
+
if not mcs
|
509
|
+
DBus.logger.debug "no return code for mcs: #{mcs.inspect} m: #{m.inspect}"
|
510
|
+
else
|
511
|
+
if m.message_type == Message::ERROR
|
512
|
+
mcs.call(Error.new(m))
|
513
|
+
else
|
514
|
+
mcs.call(m)
|
515
|
+
end
|
516
|
+
@method_call_replies.delete(m.reply_serial)
|
517
|
+
@method_call_msgs.delete(m.reply_serial)
|
518
|
+
end
|
519
|
+
when DBus::Message::METHOD_CALL
|
520
|
+
if m.path == "/org/freedesktop/DBus"
|
521
|
+
DBus.logger.debug "Got method call on /org/freedesktop/DBus"
|
522
|
+
end
|
523
|
+
node = @service.get_node(m.path)
|
524
|
+
if not node
|
525
|
+
reply = Message.error(m, "org.freedesktop.DBus.Error.UnknownObject",
|
526
|
+
"Object #{m.path} doesn't exist")
|
527
|
+
@message_queue.push(reply)
|
528
|
+
# handle introspectable as an exception:
|
529
|
+
elsif m.interface == "org.freedesktop.DBus.Introspectable" and
|
530
|
+
m.member == "Introspect"
|
531
|
+
reply = Message.new(Message::METHOD_RETURN).reply_to(m)
|
532
|
+
reply.sender = @unique_name
|
533
|
+
reply.add_param(Type::STRING, node.to_xml)
|
534
|
+
@message_queue.push(reply)
|
535
|
+
else
|
536
|
+
obj = node.object
|
537
|
+
return if obj.nil? # FIXME, pushes no reply
|
538
|
+
obj.dispatch(m) if obj
|
539
|
+
end
|
540
|
+
when DBus::Message::SIGNAL
|
541
|
+
# the signal can match multiple different rules
|
542
|
+
@signal_matchrules.each do |mrs, slot|
|
543
|
+
if DBus::MatchRule.new.from_s(mrs).match(m)
|
544
|
+
slot.call(m)
|
545
|
+
end
|
546
|
+
end
|
547
|
+
else
|
548
|
+
DBus.logger.debug "Unknown message type: #{m.message_type}"
|
549
|
+
end
|
550
|
+
rescue Exception => ex
|
551
|
+
raise m.annotate_exception(ex)
|
552
|
+
end
|
553
|
+
|
554
|
+
# Retrieves the Service with the given _name_.
|
555
|
+
# @return [Service]
|
556
|
+
def service(name)
|
557
|
+
# The service might not exist at this time so we cannot really check
|
558
|
+
# anything
|
559
|
+
Service.new(name, self)
|
560
|
+
end
|
561
|
+
alias :[] :service
|
562
|
+
|
563
|
+
# @api private
|
564
|
+
# Emit a signal event for the given _service_, object _obj_, interface
|
565
|
+
# _intf_ and signal _sig_ with arguments _args_.
|
566
|
+
def emit(service, obj, intf, sig, *args)
|
567
|
+
m = Message.new(DBus::Message::SIGNAL)
|
568
|
+
m.path = obj.path
|
569
|
+
m.interface = intf.name
|
570
|
+
m.member = sig.name
|
571
|
+
m.sender = service.name
|
572
|
+
i = 0
|
573
|
+
sig.params.each do |par|
|
574
|
+
m.add_param(par.type, args[i])
|
575
|
+
i += 1
|
576
|
+
end
|
577
|
+
@message_queue.push(m)
|
578
|
+
end
|
579
|
+
|
580
|
+
###########################################################################
|
581
|
+
private
|
582
|
+
|
583
|
+
# Send a hello messages to the bus to let it know we are here.
|
584
|
+
def send_hello
|
585
|
+
m = Message.new(DBus::Message::METHOD_CALL)
|
586
|
+
m.path = "/org/freedesktop/DBus"
|
587
|
+
m.destination = "org.freedesktop.DBus"
|
588
|
+
m.interface = "org.freedesktop.DBus"
|
589
|
+
m.member = "Hello"
|
590
|
+
send_sync(m) do |rmsg|
|
591
|
+
@unique_name = rmsg.destination
|
592
|
+
DBus.logger.debug "Got hello reply. Our unique_name is #{@unique_name}"
|
593
|
+
end
|
594
|
+
@service = Service.new(@unique_name, self)
|
595
|
+
end
|
596
|
+
end # class Connection
|
597
|
+
|
598
|
+
|
599
|
+
# = D-Bus session bus class
|
600
|
+
#
|
601
|
+
# The session bus is a session specific bus (mostly for desktop use).
|
602
|
+
#
|
603
|
+
# Use SessionBus, the non-singleton ASessionBus is
|
604
|
+
# for the test suite.
|
605
|
+
class ASessionBus < Connection
|
606
|
+
# Get the the default session bus.
|
607
|
+
def initialize
|
608
|
+
super(ENV["DBUS_SESSION_BUS_ADDRESS"] || address_from_file || "launchd:env=DBUS_LAUNCHD_SESSION_BUS_SOCKET")
|
609
|
+
send_hello
|
610
|
+
end
|
611
|
+
|
612
|
+
def address_from_file
|
613
|
+
# systemd uses /etc/machine-id
|
614
|
+
# traditional dbus uses /var/lib/dbus/machine-id
|
615
|
+
machine_id_path = Dir['{/etc,/var/lib/dbus}/machine-id'].first
|
616
|
+
return nil unless machine_id_path
|
617
|
+
machine_id = File.read(machine_id_path).chomp
|
618
|
+
|
619
|
+
display = ENV["DISPLAY"][/:(\d+)\.?/, 1]
|
620
|
+
|
621
|
+
bus_file_path = File.join(ENV["HOME"], "/.dbus/session-bus/#{machine_id}-#{display}")
|
622
|
+
return nil unless File.exists?(bus_file_path)
|
623
|
+
|
624
|
+
File.open(bus_file_path).each_line do |line|
|
625
|
+
if line =~ /^DBUS_SESSION_BUS_ADDRESS=(.*)/
|
626
|
+
return $1
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|
630
|
+
end
|
631
|
+
|
632
|
+
# See ASessionBus
|
633
|
+
class SessionBus < ASessionBus
|
634
|
+
include Singleton
|
635
|
+
end
|
636
|
+
|
637
|
+
|
638
|
+
# = D-Bus system bus class
|
639
|
+
#
|
640
|
+
# The system bus is a system-wide bus mostly used for global or
|
641
|
+
# system usages.
|
642
|
+
#
|
643
|
+
# Use SystemBus, the non-singleton ASystemBus is
|
644
|
+
# for the test suite.
|
645
|
+
class ASystemBus < Connection
|
646
|
+
# Get the default system bus.
|
647
|
+
def initialize
|
648
|
+
super(SystemSocketName)
|
649
|
+
send_hello
|
650
|
+
end
|
651
|
+
end
|
652
|
+
|
653
|
+
# = D-Bus remote (TCP) bus class
|
654
|
+
#
|
655
|
+
# This class may be used when connecting to remote (listening on a TCP socket)
|
656
|
+
# busses. You can also use it to connect to other non-standard path busses.
|
657
|
+
#
|
658
|
+
# The specified socket_name should look like this:
|
659
|
+
# (for TCP) tcp:host=127.0.0.1,port=2687
|
660
|
+
# (for Unix-socket) unix:path=/tmp/my_funky_bus_socket
|
661
|
+
#
|
662
|
+
# you'll need to take care about authentification then, more info here:
|
663
|
+
# http://github.com/pangdudu/ruby-dbus/blob/master/README.rdoc
|
664
|
+
class RemoteBus < Connection
|
665
|
+
|
666
|
+
# Get the remote bus.
|
667
|
+
def initialize socket_name
|
668
|
+
super(socket_name)
|
669
|
+
send_hello
|
670
|
+
end
|
671
|
+
end
|
672
|
+
|
673
|
+
# See ASystemBus
|
674
|
+
class SystemBus < ASystemBus
|
675
|
+
include Singleton
|
676
|
+
end
|
677
|
+
|
678
|
+
# Shortcut for the {SystemBus} instance
|
679
|
+
# @return [Connection]
|
680
|
+
def DBus.system_bus
|
681
|
+
SystemBus.instance
|
682
|
+
end
|
683
|
+
|
684
|
+
# Shortcut for the {SessionBus} instance
|
685
|
+
# @return [Connection]
|
686
|
+
def DBus.session_bus
|
687
|
+
SessionBus.instance
|
688
|
+
end
|
689
|
+
|
690
|
+
# = Main event loop class.
|
691
|
+
#
|
692
|
+
# Class that takes care of handling message and signal events
|
693
|
+
# asynchronously. *Note:* This is a native implement and therefore does
|
694
|
+
# not integrate with a graphical widget set main loop.
|
695
|
+
class Main
|
696
|
+
# Create a new main event loop.
|
697
|
+
def initialize
|
698
|
+
@buses = Hash.new
|
699
|
+
@quitting = false
|
700
|
+
end
|
701
|
+
|
702
|
+
# Add a _bus_ to the list of buses to watch for events.
|
703
|
+
def <<(bus)
|
704
|
+
@buses[bus.message_queue.socket] = bus
|
705
|
+
end
|
706
|
+
|
707
|
+
# Quit a running main loop, to be used eg. from a signal handler
|
708
|
+
def quit
|
709
|
+
@quitting = true
|
710
|
+
end
|
711
|
+
|
712
|
+
# Run the main loop. This is a blocking call!
|
713
|
+
def run
|
714
|
+
# before blocking, empty the buffers
|
715
|
+
# https://bugzilla.novell.com/show_bug.cgi?id=537401
|
716
|
+
@buses.each_value do |b|
|
717
|
+
while m = b.message_queue.message_from_buffer_nonblock
|
718
|
+
b.process(m)
|
719
|
+
end
|
720
|
+
end
|
721
|
+
while not @quitting and not @buses.empty?
|
722
|
+
ready = IO.select(@buses.keys, [], [], 5) # timeout 5 seconds
|
723
|
+
next unless ready # timeout exceeds so continue unless quitting
|
724
|
+
ready.first.each do |socket|
|
725
|
+
b = @buses[socket]
|
726
|
+
begin
|
727
|
+
b.message_queue.buffer_from_socket_nonblock
|
728
|
+
rescue EOFError, SystemCallError
|
729
|
+
@buses.delete socket # this bus died
|
730
|
+
next
|
731
|
+
end
|
732
|
+
while m = b.message_queue.message_from_buffer_nonblock
|
733
|
+
b.process(m)
|
734
|
+
end
|
735
|
+
end
|
736
|
+
end
|
737
|
+
end
|
738
|
+
end # class Main
|
739
|
+
end # module DBus
|