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