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.
@@ -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
+