ruby-dbus 0.15.0 → 0.18.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/NEWS.md +41 -1
  3. data/README.md +3 -5
  4. data/Rakefile +18 -8
  5. data/VERSION +1 -1
  6. data/doc/Reference.md +93 -3
  7. data/examples/doc/_extract_examples +7 -0
  8. data/examples/gdbus/gdbus +31 -24
  9. data/examples/no-introspect/nm-test.rb +2 -0
  10. data/examples/no-introspect/tracker-test.rb +3 -1
  11. data/examples/rhythmbox/playpause.rb +2 -1
  12. data/examples/service/call_service.rb +2 -1
  13. data/examples/service/service_newapi.rb +1 -1
  14. data/examples/simple/call_introspect.rb +1 -0
  15. data/examples/simple/get_id.rb +2 -1
  16. data/examples/simple/properties.rb +2 -0
  17. data/examples/utils/listnames.rb +1 -0
  18. data/examples/utils/notify.rb +1 -0
  19. data/lib/dbus/api_options.rb +9 -0
  20. data/lib/dbus/auth.rb +20 -15
  21. data/lib/dbus/bus.rb +129 -74
  22. data/lib/dbus/bus_name.rb +31 -0
  23. data/lib/dbus/core_ext/class/attribute.rb +1 -1
  24. data/lib/dbus/error.rb +4 -2
  25. data/lib/dbus/introspect.rb +90 -34
  26. data/lib/dbus/logger.rb +3 -1
  27. data/lib/dbus/marshall.rb +119 -87
  28. data/lib/dbus/matchrule.rb +16 -16
  29. data/lib/dbus/message.rb +40 -27
  30. data/lib/dbus/message_queue.rb +26 -18
  31. data/lib/dbus/object.rb +401 -0
  32. data/lib/dbus/object_path.rb +28 -0
  33. data/lib/dbus/proxy_object.rb +23 -2
  34. data/lib/dbus/proxy_object_factory.rb +11 -7
  35. data/lib/dbus/proxy_object_interface.rb +26 -21
  36. data/lib/dbus/type.rb +59 -34
  37. data/lib/dbus/xml.rb +28 -17
  38. data/lib/dbus.rb +10 -8
  39. data/ruby-dbus.gemspec +8 -4
  40. data/spec/async_spec.rb +2 -0
  41. data/spec/binding_spec.rb +2 -0
  42. data/spec/bus_and_xml_backend_spec.rb +2 -0
  43. data/spec/bus_driver_spec.rb +2 -0
  44. data/spec/bus_name_spec.rb +27 -0
  45. data/spec/bus_spec.rb +2 -0
  46. data/spec/byte_array_spec.rb +2 -0
  47. data/spec/client_robustness_spec.rb +27 -0
  48. data/spec/err_msg_spec.rb +2 -0
  49. data/spec/introspect_xml_parser_spec.rb +2 -0
  50. data/spec/introspection_spec.rb +2 -0
  51. data/spec/main_loop_spec.rb +3 -1
  52. data/spec/node_spec.rb +23 -0
  53. data/spec/object_path_spec.rb +25 -0
  54. data/spec/property_spec.rb +64 -5
  55. data/spec/proxy_object_spec.rb +2 -0
  56. data/spec/server_robustness_spec.rb +2 -0
  57. data/spec/server_spec.rb +2 -0
  58. data/spec/service_newapi.rb +39 -70
  59. data/spec/session_bus_spec.rb +3 -1
  60. data/spec/session_bus_spec_manual.rb +2 -0
  61. data/spec/signal_spec.rb +5 -3
  62. data/spec/spec_helper.rb +23 -9
  63. data/spec/thread_safety_spec.rb +2 -0
  64. data/spec/tools/dbus-limited-session.conf +4 -0
  65. data/spec/type_spec.rb +2 -0
  66. data/spec/value_spec.rb +16 -1
  67. data/spec/variant_spec.rb +4 -2
  68. metadata +32 -12
  69. data/lib/dbus/export.rb +0 -131
@@ -0,0 +1,401 @@
1
+ # frozen_string_literal: true
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_relative "core_ext/class/attribute"
12
+
13
+ module DBus
14
+ PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"
15
+
16
+ # Exported object type
17
+ # = Exportable D-Bus object class
18
+ #
19
+ # Objects that are going to be exported by a D-Bus service
20
+ # should inherit from this class. At the client side, use {ProxyObject}.
21
+ class Object
22
+ # The path of the object.
23
+ attr_reader :path
24
+
25
+ # The interfaces that the object supports. Hash: String => Interface
26
+ my_class_attribute :intfs
27
+ self.intfs = {}
28
+
29
+ # The service that the object is exported by.
30
+ attr_writer :service
31
+
32
+ @@cur_intf = nil # Interface
33
+ @@intfs_mutex = Mutex.new
34
+
35
+ # Create a new object with a given _path_.
36
+ # Use Service#export to export it.
37
+ def initialize(path)
38
+ @path = path
39
+ @service = nil
40
+ end
41
+
42
+ # Dispatch a message _msg_ to call exported methods
43
+ def dispatch(msg)
44
+ case msg.message_type
45
+ when Message::METHOD_CALL
46
+ reply = nil
47
+ begin
48
+ iface = intfs[msg.interface]
49
+ if !iface
50
+ raise DBus.error("org.freedesktop.DBus.Error.UnknownMethod"),
51
+ "Interface \"#{msg.interface}\" of object \"#{msg.path}\" doesn't exist"
52
+ end
53
+ member_sym = msg.member.to_sym
54
+ meth = iface.methods[member_sym]
55
+ if !meth
56
+ raise DBus.error("org.freedesktop.DBus.Error.UnknownMethod"),
57
+ "Method \"#{msg.member}\" on interface \"#{msg.interface}\" of object \"#{msg.path}\" doesn't exist"
58
+ end
59
+ methname = Object.make_method_name(msg.interface, msg.member)
60
+ retdata = method(methname).call(*msg.params)
61
+ retdata = [*retdata]
62
+
63
+ reply = Message.method_return(msg)
64
+ if iface.name == PROPERTY_INTERFACE && member_sym == :Get
65
+ # Use the specific property type instead of the generic variant
66
+ # returned by Get.
67
+ # TODO: GetAll and Set still missing
68
+ property = dbus_lookup_property(msg.params[0], msg.params[1])
69
+ rsigs = [property.type]
70
+ else
71
+ rsigs = meth.rets.map(&:type)
72
+ end
73
+ rsigs.zip(retdata).each do |rsig, rdata|
74
+ reply.add_param(rsig, rdata)
75
+ end
76
+ rescue StandardError => e
77
+ dbus_msg_exc = msg.annotate_exception(e)
78
+ reply = ErrorMessage.from_exception(dbus_msg_exc).reply_to(msg)
79
+ end
80
+ @service.bus.message_queue.push(reply)
81
+ end
82
+ end
83
+
84
+ # Select (and create) the interface that the following defined methods
85
+ # belong to.
86
+ # @param name [String] interface name like "org.example.ManagerManager"
87
+ # @see https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-interface
88
+ def self.dbus_interface(name)
89
+ @@intfs_mutex.synchronize do
90
+ @@cur_intf = intfs[name]
91
+ if !@@cur_intf
92
+ @@cur_intf = Interface.new(name) # validates the name
93
+ # As this is a mutable class_attr, we cannot use
94
+ # self.intfs[name] = @@cur_intf # Hash#[]=
95
+ # as that would modify parent class attr in place.
96
+ # Using the setter lets a subclass have the new value
97
+ # while the superclass keeps the old one.
98
+ self.intfs = intfs.merge(name => @@cur_intf)
99
+ end
100
+ yield
101
+ @@cur_intf = nil
102
+ end
103
+ end
104
+
105
+ # Forgetting to declare the interface for a method/signal/property
106
+ # is a ScriptError.
107
+ class UndefinedInterface < ScriptError # rubocop:disable Lint/InheritException
108
+ def initialize(sym)
109
+ super "No interface specified for #{sym}. Enclose it in dbus_interface."
110
+ end
111
+ end
112
+
113
+ # A read-write property accessing an instance variable.
114
+ # A combination of `attr_accessor` and {.dbus_accessor}.
115
+ #
116
+ # PropertiesChanged signal will be emitted whenever `foo_bar=` is used
117
+ # but not when @foo_bar is written directly.
118
+ #
119
+ # @param ruby_name [Symbol] :foo_bar is exposed as FooBar;
120
+ # use dbus_name to override
121
+ # @param type a signature like "s" or "a(uus)" or Type::STRING
122
+ # @param dbus_name [String] if not given it is made
123
+ # by CamelCasing the ruby_name. foo_bar becomes FooBar
124
+ # to convert the Ruby convention to the DBus convention.
125
+ # @return [void]
126
+ def self.dbus_attr_accessor(ruby_name, type, dbus_name: nil)
127
+ attr_accessor(ruby_name)
128
+
129
+ dbus_accessor(ruby_name, type, dbus_name: dbus_name)
130
+ end
131
+
132
+ # A read-only property accessing an instance variable.
133
+ # A combination of `attr_reader` and {.dbus_reader}.
134
+ #
135
+ # Whenever the property value gets changed from "inside" the object,
136
+ # you should emit the `PropertiesChanged` signal by calling
137
+ #
138
+ # object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {dbus_name.to_s => value}, [])
139
+ #
140
+ # or, omitting the value in the signal,
141
+ #
142
+ # object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {}, [dbus_name.to_s])
143
+ #
144
+ # @param (see .dbus_attr_accessor)
145
+ # @return (see .dbus_attr_accessor)
146
+ def self.dbus_attr_reader(ruby_name, type, dbus_name: nil)
147
+ attr_reader(ruby_name)
148
+
149
+ dbus_reader(ruby_name, type, dbus_name: dbus_name)
150
+ end
151
+
152
+ # A write-only property accessing an instance variable.
153
+ # A combination of `attr_writer` and {.dbus_writer}.
154
+ #
155
+ # @param (see .dbus_attr_accessor)
156
+ # @return (see .dbus_attr_accessor)
157
+ def self.dbus_attr_writer(ruby_name, type, dbus_name: nil)
158
+ attr_writer(ruby_name)
159
+
160
+ dbus_writer(ruby_name, type, dbus_name: dbus_name)
161
+ end
162
+
163
+ # A read-write property using a pair of reader/writer methods
164
+ # (which must already exist).
165
+ # (To directly access an instance variable, use {.dbus_attr_accessor} instead)
166
+ #
167
+ # Uses {.dbus_watcher} to set up the PropertiesChanged signal.
168
+ #
169
+ # @param (see .dbus_attr_accessor)
170
+ # @return (see .dbus_attr_accessor)
171
+ def self.dbus_accessor(ruby_name, type, dbus_name: nil)
172
+ raise UndefinedInterface, ruby_name if @@cur_intf.nil?
173
+
174
+ dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
175
+ property = Property.new(dbus_name, type, :readwrite, ruby_name: ruby_name)
176
+ @@cur_intf.define(property)
177
+
178
+ dbus_watcher(ruby_name, dbus_name: dbus_name)
179
+ end
180
+
181
+ # A read-only property accessing a reader method (which must already exist).
182
+ # (To directly access an instance variable, use {.dbus_attr_reader} instead)
183
+ #
184
+ # Whenever the property value gets changed from "inside" the object,
185
+ # you should emit the `PropertiesChanged` signal by calling
186
+ #
187
+ # object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {dbus_name.to_s => value}, [])
188
+ #
189
+ # or, omitting the value in the signal,
190
+ #
191
+ # object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {}, [dbus_name.to_s])
192
+ #
193
+ # @param (see .dbus_attr_accessor)
194
+ # @return (see .dbus_attr_accessor)
195
+ def self.dbus_reader(ruby_name, type, dbus_name: nil)
196
+ raise UndefinedInterface, ruby_name if @@cur_intf.nil?
197
+
198
+ dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
199
+ property = Property.new(dbus_name, type, :read, ruby_name: ruby_name)
200
+ @@cur_intf.define(property)
201
+ end
202
+
203
+ # A write-only property accessing a writer method (which must already exist).
204
+ # (To directly access an instance variable, use {.dbus_attr_writer} instead)
205
+ #
206
+ # Uses {.dbus_watcher} to set up the PropertiesChanged signal.
207
+ #
208
+ # @param (see .dbus_attr_accessor)
209
+ # @return (see .dbus_attr_accessor)
210
+ def self.dbus_writer(ruby_name, type, dbus_name: nil)
211
+ raise UndefinedInterface, ruby_name if @@cur_intf.nil?
212
+
213
+ dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
214
+ property = Property.new(dbus_name, type, :write, ruby_name: ruby_name)
215
+ @@cur_intf.define(property)
216
+
217
+ dbus_watcher(ruby_name, dbus_name: dbus_name)
218
+ end
219
+
220
+ # Enables automatic sending of the PropertiesChanged signal.
221
+ # For *ruby_name* `foo_bar`, wrap `foo_bar=` so that it sends
222
+ # the signal for FooBar.
223
+ # The original version remains as `_original_foo_bar=`.
224
+ #
225
+ # @param ruby_name [Symbol] :foo_bar and :foo_bar= both mean the same thing
226
+ # @param dbus_name [String] if not given it is made
227
+ # by CamelCasing the ruby_name. foo_bar becomes FooBar
228
+ # to convert the Ruby convention to the DBus convention.
229
+ # @return [void]
230
+ def self.dbus_watcher(ruby_name, dbus_name: nil)
231
+ raise UndefinedInterface, ruby_name if @@cur_intf.nil?
232
+
233
+ cur_intf = @@cur_intf
234
+
235
+ ruby_name = ruby_name.to_s.sub(/=$/, "").to_sym
236
+ ruby_name_eq = "#{ruby_name}=".to_sym
237
+ original_ruby_name_eq = "_original_#{ruby_name_eq}"
238
+
239
+ dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
240
+
241
+ # the argument order is alias_method(new_name, existing_name)
242
+ alias_method original_ruby_name_eq, ruby_name_eq
243
+ define_method ruby_name_eq do |value|
244
+ public_send(original_ruby_name_eq, value)
245
+
246
+ # TODO: respect EmitsChangedSignal to use invalidated_properties instead
247
+ # PropertiesChanged, "interface:s, changed_properties:a{sv}, invalidated_properties:as"
248
+ PropertiesChanged(cur_intf.name, { dbus_name.to_s => value }, [])
249
+ end
250
+ end
251
+
252
+ # Defines an exportable method on the object with the given name _sym_,
253
+ # _prototype_ and the code in a block.
254
+ # @param prototype [Prototype]
255
+ def self.dbus_method(sym, prototype = "", &block)
256
+ raise UndefinedInterface, sym if @@cur_intf.nil?
257
+
258
+ @@cur_intf.define(Method.new(sym.to_s).from_prototype(prototype))
259
+
260
+ ruby_name = Object.make_method_name(@@cur_intf.name, sym.to_s)
261
+ # ::Module#define_method(name) { body }
262
+ define_method(ruby_name, &block)
263
+ end
264
+
265
+ # Emits a signal from the object with the given _interface_, signal
266
+ # _sig_ and arguments _args_.
267
+ # @param intf [Interface]
268
+ # @param sig [Signal]
269
+ # @param args arguments for the signal
270
+ def emit(intf, sig, *args)
271
+ @service.bus.emit(@service, self, intf, sig, *args)
272
+ end
273
+
274
+ # Defines a signal for the object with a given name _sym_ and _prototype_.
275
+ def self.dbus_signal(sym, prototype = "")
276
+ raise UndefinedInterface, sym if @@cur_intf.nil?
277
+
278
+ cur_intf = @@cur_intf
279
+ signal = Signal.new(sym.to_s).from_prototype(prototype)
280
+ cur_intf.define(Signal.new(sym.to_s).from_prototype(prototype))
281
+
282
+ # ::Module#define_method(name) { body }
283
+ define_method(sym.to_s) do |*args|
284
+ emit(cur_intf, signal, *args)
285
+ end
286
+ end
287
+
288
+ # Helper method that returns a method name generated from the interface
289
+ # name _intfname_ and method name _methname_.
290
+ # @api private
291
+ def self.make_method_name(intfname, methname)
292
+ "#{intfname}%%#{methname}"
293
+ end
294
+
295
+ # TODO: borrow a proven implementation
296
+ # @param str [String]
297
+ # @return [String]
298
+ # @api private
299
+ def self.camelize(str)
300
+ str.split(/_/).map(&:capitalize).join("")
301
+ end
302
+
303
+ # Make a D-Bus conventional name, CamelCased.
304
+ # @param ruby_name [String,Symbol] eg :do_something
305
+ # @param dbus_name [String,Symbol,nil] use this if given
306
+ # @return [Symbol] eg DoSomething
307
+ def self.make_dbus_name(ruby_name, dbus_name: nil)
308
+ dbus_name ||= camelize(ruby_name.to_s)
309
+ dbus_name.to_sym
310
+ end
311
+
312
+ # @param interface_name [String]
313
+ # @param property_name [String]
314
+ # @return [Property]
315
+ # @raise [DBus::Error]
316
+ # @api private
317
+ def dbus_lookup_property(interface_name, property_name)
318
+ # what should happen for unknown properties
319
+ # plasma: InvalidArgs (propname), UnknownInterface (interface)
320
+ # systemd: UnknownProperty
321
+ interface = intfs[interface_name]
322
+ if !interface
323
+ raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"),
324
+ "Property '#{interface_name}.#{property_name}' (on object '#{@path}') not found: no such interface"
325
+ end
326
+
327
+ property = interface.properties[property_name.to_sym]
328
+ if !property
329
+ raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"),
330
+ "Property '#{interface_name}.#{property_name}' (on object '#{@path}') not found"
331
+ end
332
+
333
+ property
334
+ end
335
+
336
+ ####################################################################
337
+
338
+ # use the above defined methods to declare the property-handling
339
+ # interfaces and methods
340
+
341
+ dbus_interface PROPERTY_INTERFACE do
342
+ dbus_method :Get, "in interface_name:s, in property_name:s, out value:v" do |interface_name, property_name|
343
+ property = dbus_lookup_property(interface_name, property_name)
344
+
345
+ if property.readable?
346
+ ruby_name = property.ruby_name
347
+ value = public_send(ruby_name)
348
+ [value]
349
+ else
350
+ raise DBus.error("org.freedesktop.DBus.Error.PropertyWriteOnly"),
351
+ "Property '#{interface_name}.#{property_name}' (on object '#{@path}') is not readable"
352
+ end
353
+ end
354
+
355
+ dbus_method :Set, "in interface_name:s, in property_name:s, in val:v" do |interface_name, property_name, value|
356
+ property = dbus_lookup_property(interface_name, property_name)
357
+
358
+ if property.writable?
359
+ ruby_name_eq = "#{property.ruby_name}="
360
+ public_send(ruby_name_eq, value)
361
+ else
362
+ raise DBus.error("org.freedesktop.DBus.Error.PropertyReadOnly"),
363
+ "Property '#{interface_name}.#{property_name}' (on object '#{@path}') is not writable"
364
+ end
365
+ end
366
+
367
+ dbus_method :GetAll, "in interface_name:s, out value:a{sv}" do |interface_name|
368
+ interface = intfs[interface_name]
369
+ if !interface
370
+ raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"),
371
+ "Properties '#{interface_name}.*' (on object '#{@path}') not found: no such interface"
372
+ end
373
+
374
+ p_hash = {}
375
+ interface.properties.each do |p_name, property|
376
+ next unless property.readable?
377
+
378
+ ruby_name = property.ruby_name
379
+ begin
380
+ # D-Bus spec says:
381
+ # > If GetAll is called with a valid interface name for which some
382
+ # > properties are not accessible to the caller (for example, due
383
+ # > to per-property access control implemented in the service),
384
+ # > those properties should be silently omitted from the result
385
+ # > array.
386
+ # so we will silently omit properties that fail to read.
387
+ # Get'ting them individually will send DBus.Error
388
+ p_hash[p_name.to_s] = public_send(ruby_name)
389
+ rescue StandardError
390
+ DBus.logger.debug "Property '#{interface_name}.#{p_name}' (on object '#{@path}')" \
391
+ " has raised during GetAll, omitting it"
392
+ end
393
+ end
394
+
395
+ [p_hash]
396
+ end
397
+
398
+ dbus_signal :PropertiesChanged, "interface:s, changed_properties:a{sv}, invalidated_properties:as"
399
+ end
400
+ end
401
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of the ruby-dbus project
4
+ # Copyright (C) 2019 Martin Vidner
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
+ module DBus
12
+ # A {::String} that validates at initialization time
13
+ # @see https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-object-path
14
+ class ObjectPath < String
15
+ # @raise Error if not a valid object path
16
+ def initialize(str)
17
+ unless self.class.valid?(str)
18
+ raise DBus::Error, "Invalid object path #{str.inspect}"
19
+ end
20
+
21
+ super
22
+ end
23
+
24
+ def self.valid?(str)
25
+ str == "/" || str =~ %r{\A(/[A-Za-z0-9_]+)+\z}
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of the ruby-dbus project
2
4
  # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
3
5
  # Copyright (C) 2009-2014 Martin Vidner
@@ -36,7 +38,7 @@ module DBus
36
38
  def initialize(bus, dest, path, api: ApiOptions::CURRENT)
37
39
  @bus = bus
38
40
  @destination = dest
39
- @path = path
41
+ @path = ObjectPath.new(path)
40
42
  @introspected = false
41
43
  @interfaces = {}
42
44
  @subnodes = []
@@ -44,6 +46,7 @@ module DBus
44
46
  end
45
47
 
46
48
  # Returns the interfaces of the object.
49
+ # @return [Array<String>] names of the interfaces
47
50
  def interfaces
48
51
  introspect unless introspected
49
52
  @interfaces.keys
@@ -56,6 +59,7 @@ module DBus
56
59
  introspect unless introspected
57
60
  ifc = @interfaces[intfname]
58
61
  raise DBus::Error, "no such interface `#{intfname}' on object `#{@path}'" unless ifc
62
+
59
63
  ifc
60
64
  end
61
65
 
@@ -92,6 +96,7 @@ module DBus
92
96
  # don't overwrite instance methods!
93
97
  next if dup_meths.include?(name)
94
98
  next if self.class.instance_methods.include?(name)
99
+
95
100
  if univocal_meths.include? name
96
101
  univocal_meths.delete name
97
102
  dup_meths << name
@@ -123,12 +128,20 @@ module DBus
123
128
  def on_signal(name, &block)
124
129
  # TODO: improve
125
130
  raise NoMethodError unless @default_iface && has_iface?(@default_iface)
131
+
126
132
  @interfaces[@default_iface].on_signal(name, &block)
127
133
  end
128
134
 
129
135
  ####################################################
130
136
  private
131
137
 
138
+ # rubocop:disable Lint/MissingSuper
139
+ # as this should forward everything
140
+ #
141
+ # https://github.com/rubocop-hq/ruby-style-guide#no-method-missing
142
+ # and http://blog.marc-andre.ca/2010/11/15/methodmissing-politely/
143
+ # have a point to be investigated
144
+
132
145
  # Handles all unkown methods, mostly to route method calls to the
133
146
  # default interface.
134
147
  def method_missing(name, *args, &reply_handler)
@@ -146,9 +159,17 @@ module DBus
146
159
  # interesting, foo.method("unknown")
147
160
  # raises NameError, not NoMethodError
148
161
  raise unless e.to_s =~ /undefined method `#{name}'/
162
+
149
163
  # BTW e.exception("...") would preserve the class.
150
164
  raise NoMethodError, "undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
151
165
  end
152
166
  end
153
- end # class ProxyObject
167
+ # rubocop:enable Lint/MissingSuper
168
+
169
+ def respond_to_missing?(name, _include_private = false)
170
+ @default_iface &&
171
+ has_iface?(@default_iface) &&
172
+ @interfaces[@default_iface].methods.key?(name) or super
173
+ end
174
+ end
154
175
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of the ruby-dbus project
2
4
  # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
3
5
  # Copyright (C) 2009-2014 Martin Vidner
@@ -22,17 +24,19 @@ module DBus
22
24
  @api = api
23
25
  end
24
26
 
25
- # Investigates the sub-nodes of the proxy object _po_ based on the
27
+ # Investigates the sub-nodes of the proxy object _pobj_ based on the
26
28
  # introspection XML data _xml_ and sets them up recursively.
27
- def self.introspect_into(po, xml)
28
- intfs, po.subnodes = IntrospectXMLParser.new(xml).parse
29
+ # @param pobj [ProxyObject]
30
+ # @param xml [String]
31
+ def self.introspect_into(pobj, xml)
32
+ intfs, pobj.subnodes = IntrospectXMLParser.new(xml).parse
29
33
  intfs.each do |i|
30
- poi = ProxyObjectInterface.new(po, i.name)
34
+ poi = ProxyObjectInterface.new(pobj, i.name)
31
35
  i.methods.each_value { |m| poi.define(m) }
32
36
  i.signals.each_value { |s| poi.define(s) }
33
- po[i.name] = poi
37
+ pobj[i.name] = poi
34
38
  end
35
- po.introspected = true
39
+ pobj.introspected = true
36
40
  end
37
41
 
38
42
  # Generates, sets up and returns the proxy object.
@@ -41,5 +45,5 @@ module DBus
41
45
  ProxyObjectFactory.introspect_into(po, @xml)
42
46
  po
43
47
  end
44
- end # class ProxyObjectFactory
48
+ end
45
49
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of the ruby-dbus project
2
4
  # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
3
5
  # Copyright (C) 2009-2014 Martin Vidner
@@ -36,27 +38,28 @@ module DBus
36
38
  @name
37
39
  end
38
40
 
39
- # Defines a method on the interface from the Method descriptor _m_.
40
- def define_method_from_descriptor(m)
41
- m.params.each do |fpar|
41
+ # Defines a method on the interface from the Method descriptor _method_.
42
+ # @param method [Method]
43
+ def define_method_from_descriptor(method)
44
+ method.params.each do |fpar|
42
45
  par = fpar.type
43
46
  # This is the signature validity check
44
47
  Type::Parser.new(par).parse
45
48
  end
46
49
 
47
50
  singleton_class.class_eval do
48
- define_method m.name do |*args, &reply_handler|
49
- if m.params.size != args.size
50
- raise ArgumentError, "wrong number of arguments (#{args.size} for #{m.params.size})"
51
+ define_method method.name do |*args, &reply_handler|
52
+ if method.params.size != args.size
53
+ raise ArgumentError, "wrong number of arguments (#{args.size} for #{method.params.size})"
51
54
  end
52
55
 
53
56
  msg = Message.new(Message::METHOD_CALL)
54
57
  msg.path = @object.path
55
58
  msg.interface = @name
56
59
  msg.destination = @object.destination
57
- msg.member = m.name
60
+ msg.member = method.name
58
61
  msg.sender = @object.bus.unique_name
59
- m.params.each do |fpar|
62
+ method.params.each do |fpar|
60
63
  par = fpar.type
61
64
  msg.add_param(par, args.shift)
62
65
  end
@@ -64,25 +67,27 @@ module DBus
64
67
  if ret.nil? || @object.api.proxy_method_returns_array
65
68
  ret
66
69
  else
67
- m.rets.size == 1 ? ret.first : ret
70
+ method.rets.size == 1 ? ret.first : ret
68
71
  end
69
72
  end
70
73
  end
71
74
 
72
- @methods[m.name] = m
75
+ @methods[method.name] = method
73
76
  end
74
77
 
75
- # Defines a signal from the descriptor _s_.
76
- def define_signal_from_descriptor(s)
77
- @signals[s.name] = s
78
+ # Defines a signal from the descriptor _sig_.
79
+ # @param sig [Signal]
80
+ def define_signal_from_descriptor(sig)
81
+ @signals[sig.name] = sig
78
82
  end
79
83
 
80
- # Defines a signal or method based on the descriptor _m_.
81
- def define(m)
82
- if m.is_a?(Method)
83
- define_method_from_descriptor(m)
84
- elsif m.is_a?(Signal)
85
- define_signal_from_descriptor(m)
84
+ # Defines a signal or method based on the descriptor _ifc_el_.
85
+ def define(ifc_el)
86
+ case ifc_el
87
+ when Method
88
+ define_method_from_descriptor(ifc_el)
89
+ when Signal
90
+ define_signal_from_descriptor(ifc_el)
86
91
  end
87
92
  end
88
93
 
@@ -109,7 +114,7 @@ module DBus
109
114
  end
110
115
  end
111
116
 
112
- PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties".freeze
117
+ PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"
113
118
 
114
119
  # Read a property.
115
120
  # @param propname [String]
@@ -141,5 +146,5 @@ module DBus
141
146
  ret
142
147
  end
143
148
  end
144
- end # class ProxyObjectInterface
149
+ end
145
150
  end