ruby-dbus-openplacos 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/COPYING +504 -0
  2. data/NEWS +146 -0
  3. data/README +42 -0
  4. data/Rakefile +54 -0
  5. data/VERSION +1 -0
  6. data/doc/tutorial/index.markdown +480 -0
  7. data/examples/gdbus/gdbus +255 -0
  8. data/examples/gdbus/gdbus.glade +184 -0
  9. data/examples/gdbus/launch.sh +4 -0
  10. data/examples/no-introspect/nm-test.rb +21 -0
  11. data/examples/no-introspect/tracker-test.rb +16 -0
  12. data/examples/rhythmbox/playpause.rb +25 -0
  13. data/examples/service/call_service.rb +25 -0
  14. data/examples/service/service_newapi.rb +51 -0
  15. data/examples/simple/call_introspect.rb +34 -0
  16. data/examples/simple/properties.rb +19 -0
  17. data/examples/utils/listnames.rb +11 -0
  18. data/examples/utils/notify.rb +19 -0
  19. data/lib/dbus/auth.rb +258 -0
  20. data/lib/dbus/bus.rb +947 -0
  21. data/lib/dbus/core_ext/class/attribute.rb +91 -0
  22. data/lib/dbus/core_ext/kernel/singleton_class.rb +14 -0
  23. data/lib/dbus/core_ext/module/remove_method.rb +12 -0
  24. data/lib/dbus/error.rb +44 -0
  25. data/lib/dbus/export.rb +124 -0
  26. data/lib/dbus/introspect.rb +570 -0
  27. data/lib/dbus/marshall.rb +443 -0
  28. data/lib/dbus/matchrule.rb +100 -0
  29. data/lib/dbus/message.rb +310 -0
  30. data/lib/dbus/type.rb +222 -0
  31. data/lib/dbus.rb +83 -0
  32. data/ruby-dbus-openplacos.gemspec +17 -0
  33. data/test/binding_test.rb +56 -0
  34. data/test/bus_driver_test.rb +22 -0
  35. data/test/dbus-launch-simple +35 -0
  36. data/test/dbus-limited-session.conf +28 -0
  37. data/test/property_test.rb +55 -0
  38. data/test/server_robustness_test.rb +72 -0
  39. data/test/server_test.rb +53 -0
  40. data/test/service_newapi.rb +197 -0
  41. data/test/session_bus_test_manual.rb +20 -0
  42. data/test/signal_test.rb +64 -0
  43. data/test/t1 +4 -0
  44. data/test/t2.rb +66 -0
  45. data/test/t3-ticket27.rb +18 -0
  46. data/test/t5-report-dbus-interface.rb +58 -0
  47. data/test/t6-loop.rb +82 -0
  48. data/test/test_env +13 -0
  49. data/test/test_server +39 -0
  50. data/test/variant_test.rb +66 -0
  51. 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
+