ruby-dbus-openplacos 0.6.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.
- data/COPYING +504 -0
- data/NEWS +146 -0
- data/README +42 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/doc/tutorial/index.markdown +480 -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/auth.rb +258 -0
- data/lib/dbus/bus.rb +947 -0
- data/lib/dbus/core_ext/class/attribute.rb +91 -0
- data/lib/dbus/core_ext/kernel/singleton_class.rb +14 -0
- data/lib/dbus/core_ext/module/remove_method.rb +12 -0
- data/lib/dbus/error.rb +44 -0
- data/lib/dbus/export.rb +124 -0
- data/lib/dbus/introspect.rb +570 -0
- data/lib/dbus/marshall.rb +443 -0
- data/lib/dbus/matchrule.rb +100 -0
- data/lib/dbus/message.rb +310 -0
- data/lib/dbus/type.rb +222 -0
- data/lib/dbus.rb +83 -0
- data/ruby-dbus-openplacos.gemspec +17 -0
- data/test/binding_test.rb +56 -0
- data/test/bus_driver_test.rb +22 -0
- data/test/dbus-launch-simple +35 -0
- data/test/dbus-limited-session.conf +28 -0
- data/test/property_test.rb +55 -0
- data/test/server_robustness_test.rb +72 -0
- data/test/server_test.rb +53 -0
- data/test/service_newapi.rb +197 -0
- data/test/session_bus_test_manual.rb +20 -0
- data/test/signal_test.rb +64 -0
- data/test/t1 +4 -0
- data/test/t2.rb +66 -0
- data/test/t3-ticket27.rb +18 -0
- data/test/t5-report-dbus-interface.rb +58 -0
- data/test/t6-loop.rb +82 -0
- data/test/test_env +13 -0
- data/test/test_server +39 -0
- data/test/variant_test.rb +66 -0
- metadata +118 -0
@@ -0,0 +1,570 @@
|
|
1
|
+
# dbus/introspection.rb - module containing a low-level D-Bus introspection 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 can 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 'rexml/document'
|
12
|
+
|
13
|
+
module DBus
|
14
|
+
# Regular expressions that should match all method names.
|
15
|
+
MethodSignalRE = /^[A-Za-z][A-Za-z0-9_]*$/
|
16
|
+
# Regular expressions that should match all interface names.
|
17
|
+
InterfaceElementRE = /^[A-Za-z][A-Za-z0-9_]*$/
|
18
|
+
|
19
|
+
# Exception raised when an unknown signal is used.
|
20
|
+
class UnknownSignal < Exception
|
21
|
+
end
|
22
|
+
|
23
|
+
# Exception raised when an invalid class definition is encountered.
|
24
|
+
class InvalidClassDefinition < Exception
|
25
|
+
end
|
26
|
+
|
27
|
+
# = D-Bus interface class
|
28
|
+
#
|
29
|
+
# This class is the interface descriptor. In most cases, the Introspect()
|
30
|
+
# method call instanciates and configures this class for us.
|
31
|
+
#
|
32
|
+
# It also is the local definition of interface exported by the program.
|
33
|
+
class Interface
|
34
|
+
# The name of the interface.
|
35
|
+
attr_reader :name
|
36
|
+
# The methods that are part of the interface.
|
37
|
+
attr_reader :methods
|
38
|
+
# The signals that are part of the interface.
|
39
|
+
attr_reader :signals
|
40
|
+
|
41
|
+
# Creates a new interface with a given _name_.
|
42
|
+
def initialize(name)
|
43
|
+
validate_name(name)
|
44
|
+
@name = name
|
45
|
+
@methods, @signals = Hash.new, Hash.new
|
46
|
+
end
|
47
|
+
|
48
|
+
# Validates a service _name_.
|
49
|
+
def validate_name(name)
|
50
|
+
raise InvalidIntrospectionData if name.size > 255
|
51
|
+
raise InvalidIntrospectionData if name =~ /^\./ or name =~ /\.$/
|
52
|
+
raise InvalidIntrospectionData if name =~ /\.\./
|
53
|
+
raise InvalidIntrospectionData if not name =~ /\./
|
54
|
+
name.split(".").each do |element|
|
55
|
+
raise InvalidIntrospectionData if not element =~ InterfaceElementRE
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Helper method for defining a method _m_.
|
60
|
+
def define(m)
|
61
|
+
if m.kind_of?(Method)
|
62
|
+
@methods[m.name.to_sym] = m
|
63
|
+
elsif m.kind_of?(Signal)
|
64
|
+
@signals[m.name.to_sym] = m
|
65
|
+
end
|
66
|
+
end
|
67
|
+
alias :<< :define
|
68
|
+
|
69
|
+
# Defines a method with name _id_ and a given _prototype_ in the
|
70
|
+
# interface.
|
71
|
+
def define_method(id, prototype)
|
72
|
+
m = Method.new(id)
|
73
|
+
m.from_prototype(prototype)
|
74
|
+
define(m)
|
75
|
+
end
|
76
|
+
end # class Interface
|
77
|
+
|
78
|
+
# = A formal parameter has a name and a type
|
79
|
+
class FormalParameter
|
80
|
+
attr_reader :name
|
81
|
+
attr_reader :type
|
82
|
+
|
83
|
+
def initialize(name, type)
|
84
|
+
@name = name
|
85
|
+
@type = type
|
86
|
+
end
|
87
|
+
|
88
|
+
# backward compatibility, deprecated
|
89
|
+
def [](index)
|
90
|
+
case index
|
91
|
+
when 0 then name
|
92
|
+
when 1 then type
|
93
|
+
else nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# = D-Bus interface element class
|
99
|
+
#
|
100
|
+
# This is a generic class for entities that are part of the interface
|
101
|
+
# such as methods and signals.
|
102
|
+
class InterfaceElement
|
103
|
+
# The name of the interface element.
|
104
|
+
attr_reader :name
|
105
|
+
# The parameters of the interface element
|
106
|
+
attr_reader :params
|
107
|
+
|
108
|
+
# Validates element _name_.
|
109
|
+
def validate_name(name)
|
110
|
+
if (not name =~ MethodSignalRE) or (name.size > 255)
|
111
|
+
raise InvalidMethodName, name
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Creates a new element with the given _name_.
|
116
|
+
def initialize(name)
|
117
|
+
validate_name(name.to_s)
|
118
|
+
@name = name
|
119
|
+
@params = Array.new
|
120
|
+
end
|
121
|
+
|
122
|
+
# Adds a formal parameter with _name_ and _signature_
|
123
|
+
# (See also Message#add_param which takes signature+value)
|
124
|
+
def add_fparam(name, signature)
|
125
|
+
@params << FormalParameter.new(name, signature)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Deprecated, for backward compatibility
|
129
|
+
def add_param(name_signature_pair)
|
130
|
+
add_fparam(*name_signature_pair)
|
131
|
+
end
|
132
|
+
end # class InterfaceElement
|
133
|
+
|
134
|
+
# = D-Bus interface method class
|
135
|
+
#
|
136
|
+
# This is a class representing methods that are part of an interface.
|
137
|
+
class Method < InterfaceElement
|
138
|
+
# The list of return values for the method.
|
139
|
+
attr_reader :rets
|
140
|
+
|
141
|
+
# Creates a new method interface element with the given _name_.
|
142
|
+
def initialize(name)
|
143
|
+
super(name)
|
144
|
+
@rets = Array.new
|
145
|
+
end
|
146
|
+
|
147
|
+
# Add a return value _name_ and _signature_.
|
148
|
+
def add_return(name, signature)
|
149
|
+
@rets << FormalParameter.new(name, signature)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Add parameter types by parsing the given _prototype_.
|
153
|
+
def from_prototype(prototype)
|
154
|
+
prototype.split(/, */).each do |arg|
|
155
|
+
arg = arg.split(" ")
|
156
|
+
raise InvalidClassDefinition if arg.size != 2
|
157
|
+
dir, arg = arg
|
158
|
+
if arg =~ /:/
|
159
|
+
arg = arg.split(":")
|
160
|
+
name, sig = arg
|
161
|
+
else
|
162
|
+
sig = arg
|
163
|
+
end
|
164
|
+
case dir
|
165
|
+
when "in"
|
166
|
+
add_fparam(name, sig)
|
167
|
+
when "out"
|
168
|
+
add_return(name, sig)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
self
|
172
|
+
end
|
173
|
+
|
174
|
+
# Return an XML string representation of the method interface elment.
|
175
|
+
def to_xml
|
176
|
+
xml = %{<method name="#{@name}">\n}
|
177
|
+
@params.each do |param|
|
178
|
+
name = param.name ? %{name="#{param.name}" } : ""
|
179
|
+
xml += %{<arg #{name}direction="in" type="#{param.type}"/>\n}
|
180
|
+
end
|
181
|
+
@rets.each do |param|
|
182
|
+
name = param.name ? %{name="#{param.name}" } : ""
|
183
|
+
xml += %{<arg #{name}direction="out" type="#{param.type}"/>\n}
|
184
|
+
end
|
185
|
+
xml += %{</method>\n}
|
186
|
+
xml
|
187
|
+
end
|
188
|
+
end # class Method
|
189
|
+
|
190
|
+
# = D-Bus interface signal class
|
191
|
+
#
|
192
|
+
# This is a class representing signals that are part of an interface.
|
193
|
+
class Signal < InterfaceElement
|
194
|
+
# Add parameter types based on the given _prototype_.
|
195
|
+
def from_prototype(prototype)
|
196
|
+
prototype.split(/, */).each do |arg|
|
197
|
+
if arg =~ /:/
|
198
|
+
arg = arg.split(":")
|
199
|
+
name, sig = arg
|
200
|
+
else
|
201
|
+
sig = arg
|
202
|
+
end
|
203
|
+
add_fparam(name, sig)
|
204
|
+
end
|
205
|
+
self
|
206
|
+
end
|
207
|
+
|
208
|
+
# Return an XML string representation of the signal interface elment.
|
209
|
+
def to_xml
|
210
|
+
xml = %{<signal name="#{@name}">\n}
|
211
|
+
@params.each do |param|
|
212
|
+
name = param.name ? %{name="#{param.name}" } : ""
|
213
|
+
xml += %{<arg #{name}type="#{param.type}"/>\n}
|
214
|
+
end
|
215
|
+
xml += %{</signal>\n}
|
216
|
+
xml
|
217
|
+
end
|
218
|
+
end # class Signal
|
219
|
+
|
220
|
+
# = D-Bus introspect XML parser class
|
221
|
+
#
|
222
|
+
# This class parses introspection XML of an object and constructs a tree
|
223
|
+
# of Node, Interface, Method, Signal instances.
|
224
|
+
class IntrospectXMLParser
|
225
|
+
# Creates a new parser for XML data in string _xml_.
|
226
|
+
def initialize(xml)
|
227
|
+
@xml = xml
|
228
|
+
end
|
229
|
+
|
230
|
+
# Recursively parses the subnodes, constructing the tree.
|
231
|
+
def parse_subnodes
|
232
|
+
subnodes = Array.new
|
233
|
+
t = Time.now
|
234
|
+
d = REXML::Document.new(@xml)
|
235
|
+
d.elements.each("node/node") do |e|
|
236
|
+
subnodes << e.attributes["name"]
|
237
|
+
end
|
238
|
+
subnodes
|
239
|
+
end
|
240
|
+
|
241
|
+
# Parses the XML, constructing the tree.
|
242
|
+
def parse
|
243
|
+
ret = Array.new
|
244
|
+
subnodes = Array.new
|
245
|
+
t = Time.now
|
246
|
+
d = REXML::Document.new(@xml)
|
247
|
+
d.elements.each("node/node") do |e|
|
248
|
+
subnodes << e.attributes["name"]
|
249
|
+
end
|
250
|
+
d.elements.each("node/interface") do |e|
|
251
|
+
i = Interface.new(e.attributes["name"])
|
252
|
+
e.elements.each("method") do |me|
|
253
|
+
m = Method.new(me.attributes["name"])
|
254
|
+
parse_methsig(me, m)
|
255
|
+
i << m
|
256
|
+
end
|
257
|
+
e.elements.each("signal") do |se|
|
258
|
+
s = Signal.new(se.attributes["name"])
|
259
|
+
parse_methsig(se, s)
|
260
|
+
i << s
|
261
|
+
end
|
262
|
+
ret << i
|
263
|
+
end
|
264
|
+
d = Time.now - t
|
265
|
+
if d > 2
|
266
|
+
puts "Some XML took more that two secs to parse. Optimize me!" if $DEBUG
|
267
|
+
end
|
268
|
+
[ret, subnodes]
|
269
|
+
end
|
270
|
+
|
271
|
+
######################################################################
|
272
|
+
private
|
273
|
+
|
274
|
+
# Parses a method signature XML element _e_ and initialises
|
275
|
+
# method/signal _m_.
|
276
|
+
def parse_methsig(e, m)
|
277
|
+
e.elements.each("arg") do |ae|
|
278
|
+
name = ae.attributes["name"]
|
279
|
+
dir = ae.attributes["direction"]
|
280
|
+
sig = ae.attributes["type"]
|
281
|
+
if m.is_a?(DBus::Signal)
|
282
|
+
m.add_fparam(name, sig)
|
283
|
+
elsif m.is_a?(DBus::Method)
|
284
|
+
case dir
|
285
|
+
when "in"
|
286
|
+
m.add_fparam(name, sig)
|
287
|
+
when "out"
|
288
|
+
m.add_return(name, sig)
|
289
|
+
end
|
290
|
+
else
|
291
|
+
raise NotImplementedError, dir
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end # class IntrospectXMLParser
|
296
|
+
|
297
|
+
# = D-Bus proxy object interface class
|
298
|
+
#
|
299
|
+
# A class similar to the normal Interface used as a proxy for remote
|
300
|
+
# object interfaces.
|
301
|
+
class ProxyObjectInterface
|
302
|
+
# The proxied methods contained in the interface.
|
303
|
+
attr_accessor :methods
|
304
|
+
# The proxied signals contained in the interface.
|
305
|
+
attr_accessor :signals
|
306
|
+
# The proxy object to which this interface belongs.
|
307
|
+
attr_reader :object
|
308
|
+
# The name of the interface.
|
309
|
+
attr_reader :name
|
310
|
+
|
311
|
+
# Creates a new proxy interface for the given proxy _object_
|
312
|
+
# and the given _name_.
|
313
|
+
def initialize(object, name)
|
314
|
+
@object, @name = object, name
|
315
|
+
@methods, @signals = Hash.new, Hash.new
|
316
|
+
end
|
317
|
+
|
318
|
+
# Returns the string representation of the interface (the name).
|
319
|
+
def to_str
|
320
|
+
@name
|
321
|
+
end
|
322
|
+
|
323
|
+
# Returns the singleton class of the interface.
|
324
|
+
def singleton_class
|
325
|
+
(class << self ; self ; end)
|
326
|
+
end
|
327
|
+
|
328
|
+
# FIXME
|
329
|
+
def check_for_eval(s)
|
330
|
+
raise RuntimeError, "invalid internal data '#{s}'" if not s.to_s =~ /^[A-Za-z0-9_]*$/
|
331
|
+
end
|
332
|
+
|
333
|
+
# FIXME
|
334
|
+
def check_for_quoted_eval(s)
|
335
|
+
raise RuntimeError, "invalid internal data '#{s}'" if not s.to_s =~ /^[^"]+$/
|
336
|
+
end
|
337
|
+
|
338
|
+
# Defines a method on the interface from the Method descriptor _m_.
|
339
|
+
def define_method_from_descriptor(m)
|
340
|
+
check_for_eval(m.name)
|
341
|
+
check_for_quoted_eval(@name)
|
342
|
+
methdef = "def #{m.name}("
|
343
|
+
methdef += (0..(m.params.size - 1)).to_a.collect { |n|
|
344
|
+
"arg#{n}"
|
345
|
+
}.join(", ")
|
346
|
+
methdef += %{)
|
347
|
+
msg = Message.new(Message::METHOD_CALL)
|
348
|
+
msg.path = @object.path
|
349
|
+
msg.interface = "#{@name}"
|
350
|
+
msg.destination = @object.destination
|
351
|
+
msg.member = "#{m.name}"
|
352
|
+
msg.sender = @object.bus.unique_name
|
353
|
+
}
|
354
|
+
idx = 0
|
355
|
+
m.params.each do |fpar|
|
356
|
+
par = fpar.type
|
357
|
+
check_for_quoted_eval(par)
|
358
|
+
|
359
|
+
# This is the signature validity check
|
360
|
+
Type::Parser.new(par).parse
|
361
|
+
|
362
|
+
methdef += %{
|
363
|
+
msg.add_param("#{par}", arg#{idx})
|
364
|
+
}
|
365
|
+
idx += 1
|
366
|
+
end
|
367
|
+
methdef += "
|
368
|
+
ret = nil
|
369
|
+
if block_given?
|
370
|
+
@object.bus.on_return(msg) do |rmsg|
|
371
|
+
if rmsg.is_a?(Error)
|
372
|
+
yield(rmsg)
|
373
|
+
else
|
374
|
+
yield(rmsg, *rmsg.params)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
@object.bus.send(msg.marshall)
|
378
|
+
else
|
379
|
+
@object.bus.send_sync(msg) do |rmsg|
|
380
|
+
if rmsg.is_a?(Error)
|
381
|
+
raise rmsg
|
382
|
+
else
|
383
|
+
ret = rmsg.params
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
ret
|
388
|
+
end
|
389
|
+
"
|
390
|
+
singleton_class.class_eval(methdef)
|
391
|
+
@methods[m.name] = m
|
392
|
+
end
|
393
|
+
|
394
|
+
# Defines a signal from the descriptor _s_.
|
395
|
+
def define_signal_from_descriptor(s)
|
396
|
+
@signals[s.name] = s
|
397
|
+
end
|
398
|
+
|
399
|
+
# Defines a signal or method based on the descriptor _m_.
|
400
|
+
def define(m)
|
401
|
+
if m.kind_of?(Method)
|
402
|
+
define_method_from_descriptor(m)
|
403
|
+
elsif m.kind_of?(Signal)
|
404
|
+
define_signal_from_descriptor(m)
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
# Defines a proxied method on the interface.
|
409
|
+
def define_method(methodname, prototype)
|
410
|
+
m = Method.new(methodname)
|
411
|
+
m.from_prototype(prototype)
|
412
|
+
define(m)
|
413
|
+
end
|
414
|
+
|
415
|
+
# Registers a handler (code block) for a signal with _name_ arriving
|
416
|
+
# over the given _bus_. If no block is given, the signal is unregistered.
|
417
|
+
def on_signal(bus, name, &block)
|
418
|
+
mr = DBus::MatchRule.new.from_signal(self, name)
|
419
|
+
if block.nil?
|
420
|
+
bus.remove_match(mr)
|
421
|
+
else
|
422
|
+
bus.add_match(mr) { |msg| block.call(*msg.params) }
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"
|
427
|
+
|
428
|
+
# Read a property.
|
429
|
+
def [](propname)
|
430
|
+
self.object[PROPERTY_INTERFACE].Get(self.name, propname)[0]
|
431
|
+
end
|
432
|
+
|
433
|
+
# Write a property.
|
434
|
+
def []=(propname, value)
|
435
|
+
self.object[PROPERTY_INTERFACE].Set(self.name, propname, value)
|
436
|
+
end
|
437
|
+
|
438
|
+
# Read all properties at once, as a hash.
|
439
|
+
def all_properties
|
440
|
+
self.object[PROPERTY_INTERFACE].GetAll(self.name)[0]
|
441
|
+
end
|
442
|
+
end # class ProxyObjectInterface
|
443
|
+
|
444
|
+
# D-Bus proxy object class
|
445
|
+
#
|
446
|
+
# Class representing a remote object in an external application.
|
447
|
+
# Typically, calling a method on an instance of a ProxyObject sends a message
|
448
|
+
# over the bus so that the method is executed remotely on the correctponding
|
449
|
+
# object.
|
450
|
+
class ProxyObject
|
451
|
+
# The subnodes of the object in the tree.
|
452
|
+
attr_accessor :subnodes
|
453
|
+
# Flag determining whether the object has been introspected.
|
454
|
+
attr_accessor :introspected
|
455
|
+
# The (remote) destination of the object.
|
456
|
+
attr_reader :destination
|
457
|
+
# The path to the object.
|
458
|
+
attr_reader :path
|
459
|
+
# The bus the object is reachable via.
|
460
|
+
attr_reader :bus
|
461
|
+
# The default interface of the object.
|
462
|
+
attr_accessor :default_iface
|
463
|
+
|
464
|
+
# Creates a new proxy object living on the given _bus_ at destination _dest_
|
465
|
+
# on the given _path_.
|
466
|
+
def initialize(bus, dest, path)
|
467
|
+
@bus, @destination, @path = bus, dest, path
|
468
|
+
@interfaces = Hash.new
|
469
|
+
@subnodes = Array.new
|
470
|
+
end
|
471
|
+
|
472
|
+
# Returns the interfaces of the object.
|
473
|
+
def interfaces
|
474
|
+
@interfaces.keys
|
475
|
+
end
|
476
|
+
|
477
|
+
# Retrieves an interface of the proxy object (ProxyObjectInterface instance).
|
478
|
+
def [](intfname)
|
479
|
+
@interfaces[intfname]
|
480
|
+
end
|
481
|
+
|
482
|
+
# Maps the given interface name _intfname_ to the given interface _intf.
|
483
|
+
def []=(intfname, intf)
|
484
|
+
@interfaces[intfname] = intf
|
485
|
+
end
|
486
|
+
|
487
|
+
# Introspects the remote object. Allows you to find and select
|
488
|
+
# interfaces on the object.
|
489
|
+
def introspect
|
490
|
+
# Synchronous call here.
|
491
|
+
xml = @bus.introspect_data(@destination, @path)
|
492
|
+
ProxyObjectFactory.introspect_into(self, xml)
|
493
|
+
xml
|
494
|
+
end
|
495
|
+
|
496
|
+
# Returns whether the object has an interface with the given _name_.
|
497
|
+
def has_iface?(name)
|
498
|
+
raise "Cannot call has_iface? is not introspected" if not @introspected
|
499
|
+
@interfaces.member?(name)
|
500
|
+
end
|
501
|
+
|
502
|
+
# Registers a handler, the code block, for a signal with the given _name_.
|
503
|
+
def on_signal(name, &block)
|
504
|
+
if @default_iface and has_iface?(@default_iface)
|
505
|
+
@interfaces[@default_iface].on_signal(@bus, name, &block)
|
506
|
+
else
|
507
|
+
# TODO improve
|
508
|
+
raise NoMethodError
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
####################################################
|
513
|
+
private
|
514
|
+
|
515
|
+
# Handles all unkown methods, mostly to route method calls to the
|
516
|
+
# default interface.
|
517
|
+
def method_missing(name, *args)
|
518
|
+
if @default_iface and has_iface?(@default_iface)
|
519
|
+
begin
|
520
|
+
@interfaces[@default_iface].method(name).call(*args)
|
521
|
+
rescue NameError => e
|
522
|
+
# interesting, foo.method("unknown")
|
523
|
+
# raises NameError, not NoMethodError
|
524
|
+
match = /undefined method `([^']*)' for class `([^']*)'/.match e.to_s
|
525
|
+
raise unless match and match[2] == "DBus::ProxyObjectInterface"
|
526
|
+
# BTW e.exception("...") would preserve the class.
|
527
|
+
raise NoMethodError,"undefined method `#{match[1]}' for DBus interface `#{@default_iface}' on object `#{@path}'"
|
528
|
+
end
|
529
|
+
else
|
530
|
+
# TODO distinguish:
|
531
|
+
# - di not specified
|
532
|
+
#TODO
|
533
|
+
# - di is specified but not found in introspection data
|
534
|
+
raise NoMethodError, "undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
|
535
|
+
end
|
536
|
+
end
|
537
|
+
end # class ProxyObject
|
538
|
+
|
539
|
+
# = D-Bus proxy object factory class
|
540
|
+
#
|
541
|
+
# Class that generates and sets up a proxy object based on introspection data.
|
542
|
+
class ProxyObjectFactory
|
543
|
+
# Creates a new proxy object factory for the given introspection XML _xml_,
|
544
|
+
# _bus_, destination _dest_, and _path_.
|
545
|
+
def initialize(xml, bus, dest, path)
|
546
|
+
@xml, @bus, @path, @dest = xml, bus, path, dest
|
547
|
+
end
|
548
|
+
|
549
|
+
# Investigates the sub-nodes of the proxy object _po_ based on the
|
550
|
+
# introspection XML data _xml_ and sets them up recursively.
|
551
|
+
def ProxyObjectFactory.introspect_into(po, xml)
|
552
|
+
intfs, po.subnodes = IntrospectXMLParser.new(xml).parse
|
553
|
+
intfs.each do |i|
|
554
|
+
poi = ProxyObjectInterface.new(po, i.name)
|
555
|
+
i.methods.each_value { |m| poi.define(m) }
|
556
|
+
i.signals.each_value { |s| poi.define(s) }
|
557
|
+
po[i.name] = poi
|
558
|
+
end
|
559
|
+
po.introspected = true
|
560
|
+
end
|
561
|
+
|
562
|
+
# Generates, sets up and returns the proxy object.
|
563
|
+
def build
|
564
|
+
po = ProxyObject.new(@bus, @dest, @path)
|
565
|
+
ProxyObjectFactory.introspect_into(po, @xml)
|
566
|
+
po
|
567
|
+
end
|
568
|
+
end # class ProxyObjectFactory
|
569
|
+
end # module DBus
|
570
|
+
|