ruby-dbus 0.16.0 → 0.18.0.beta2

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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +46 -0
  3. data/README.md +3 -5
  4. data/Rakefile +18 -8
  5. data/VERSION +1 -1
  6. data/doc/Reference.md +94 -4
  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/complex-property.rb +21 -0
  14. data/examples/service/service_newapi.rb +1 -1
  15. data/examples/simple/call_introspect.rb +1 -0
  16. data/examples/simple/get_id.rb +2 -1
  17. data/examples/simple/properties.rb +2 -0
  18. data/examples/utils/listnames.rb +1 -0
  19. data/examples/utils/notify.rb +1 -0
  20. data/lib/dbus/api_options.rb +9 -0
  21. data/lib/dbus/auth.rb +20 -15
  22. data/lib/dbus/bus.rb +126 -74
  23. data/lib/dbus/bus_name.rb +12 -8
  24. data/lib/dbus/core_ext/class/attribute.rb +1 -1
  25. data/lib/dbus/data.rb +725 -0
  26. data/lib/dbus/error.rb +4 -2
  27. data/lib/dbus/introspect.rb +91 -30
  28. data/lib/dbus/logger.rb +3 -1
  29. data/lib/dbus/marshall.rb +228 -294
  30. data/lib/dbus/matchrule.rb +16 -16
  31. data/lib/dbus/message.rb +44 -37
  32. data/lib/dbus/message_queue.rb +16 -10
  33. data/lib/dbus/object.rb +296 -24
  34. data/lib/dbus/object_path.rb +11 -6
  35. data/lib/dbus/proxy_object.rb +22 -1
  36. data/lib/dbus/proxy_object_factory.rb +11 -7
  37. data/lib/dbus/proxy_object_interface.rb +26 -21
  38. data/lib/dbus/raw_message.rb +91 -0
  39. data/lib/dbus/type.rb +182 -80
  40. data/lib/dbus/xml.rb +28 -17
  41. data/lib/dbus.rb +13 -7
  42. data/ruby-dbus.gemspec +7 -3
  43. data/spec/async_spec.rb +2 -0
  44. data/spec/binding_spec.rb +2 -0
  45. data/spec/bus_and_xml_backend_spec.rb +2 -0
  46. data/spec/bus_driver_spec.rb +2 -0
  47. data/spec/bus_name_spec.rb +3 -1
  48. data/spec/bus_spec.rb +2 -0
  49. data/spec/byte_array_spec.rb +2 -0
  50. data/spec/client_robustness_spec.rb +4 -2
  51. data/spec/data/marshall.yaml +1639 -0
  52. data/spec/data_spec.rb +298 -0
  53. data/spec/err_msg_spec.rb +2 -0
  54. data/spec/introspect_xml_parser_spec.rb +2 -0
  55. data/spec/introspection_spec.rb +2 -0
  56. data/spec/main_loop_spec.rb +3 -1
  57. data/spec/node_spec.rb +23 -0
  58. data/spec/object_path_spec.rb +3 -0
  59. data/spec/packet_marshaller_spec.rb +34 -0
  60. data/spec/packet_unmarshaller_spec.rb +262 -0
  61. data/spec/property_spec.rb +88 -5
  62. data/spec/proxy_object_spec.rb +2 -0
  63. data/spec/server_robustness_spec.rb +2 -0
  64. data/spec/server_spec.rb +2 -0
  65. data/spec/service_newapi.rb +39 -70
  66. data/spec/session_bus_spec.rb +3 -1
  67. data/spec/session_bus_spec_manual.rb +2 -0
  68. data/spec/signal_spec.rb +5 -3
  69. data/spec/spec_helper.rb +35 -9
  70. data/spec/thread_safety_spec.rb +2 -0
  71. data/spec/tools/dbus-limited-session.conf +4 -0
  72. data/spec/type_spec.rb +69 -6
  73. data/spec/value_spec.rb +16 -1
  74. data/spec/variant_spec.rb +4 -2
  75. metadata +32 -10
data/lib/dbus/object.rb CHANGED
@@ -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
  #
@@ -6,10 +8,11 @@
6
8
  # License, version 2.1 as published by the Free Software Foundation.
7
9
  # See the file "COPYING" for the exact licensing terms.
8
10
 
9
- require "thread"
10
- require "dbus/core_ext/class/attribute"
11
+ require_relative "core_ext/class/attribute"
11
12
 
12
13
  module DBus
14
+ PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"
15
+
13
16
  # Exported object type
14
17
  # = Exportable D-Bus object class
15
18
  #
@@ -18,6 +21,7 @@ module DBus
18
21
  class Object
19
22
  # The path of the object.
20
23
  attr_reader :path
24
+
21
25
  # The interfaces that the object supports. Hash: String => Interface
22
26
  my_class_attribute :intfs
23
27
  self.intfs = {}
@@ -41,11 +45,13 @@ module DBus
41
45
  when Message::METHOD_CALL
42
46
  reply = nil
43
47
  begin
44
- if !intfs[msg.interface]
48
+ iface = intfs[msg.interface]
49
+ if !iface
45
50
  raise DBus.error("org.freedesktop.DBus.Error.UnknownMethod"),
46
51
  "Interface \"#{msg.interface}\" of object \"#{msg.path}\" doesn't exist"
47
52
  end
48
- meth = intfs[msg.interface].methods[msg.member.to_sym]
53
+ member_sym = msg.member.to_sym
54
+ meth = iface.methods[member_sym]
49
55
  if !meth
50
56
  raise DBus.error("org.freedesktop.DBus.Error.UnknownMethod"),
51
57
  "Method \"#{msg.member}\" on interface \"#{msg.interface}\" of object \"#{msg.path}\" doesn't exist"
@@ -55,11 +61,12 @@ module DBus
55
61
  retdata = [*retdata]
56
62
 
57
63
  reply = Message.method_return(msg)
58
- meth.rets.zip(retdata).each do |rsig, rdata|
59
- reply.add_param(rsig.type, rdata)
64
+ rsigs = meth.rets.map(&:type)
65
+ rsigs.zip(retdata).each do |rsig, rdata|
66
+ reply.add_param(rsig, rdata)
60
67
  end
61
- rescue => ex
62
- dbus_msg_exc = msg.annotate_exception(ex)
68
+ rescue StandardError => e
69
+ dbus_msg_exc = msg.annotate_exception(e)
63
70
  reply = ErrorMessage.from_exception(dbus_msg_exc).reply_to(msg)
64
71
  end
65
72
  @service.bus.message_queue.push(reply)
@@ -68,62 +75,327 @@ module DBus
68
75
 
69
76
  # Select (and create) the interface that the following defined methods
70
77
  # belong to.
71
- def self.dbus_interface(s)
78
+ # @param name [String] interface name like "org.example.ManagerManager"
79
+ # @see https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-interface
80
+ def self.dbus_interface(name)
72
81
  @@intfs_mutex.synchronize do
73
- @@cur_intf = intfs[s]
82
+ @@cur_intf = intfs[name]
74
83
  if !@@cur_intf
75
- @@cur_intf = Interface.new(s)
84
+ @@cur_intf = Interface.new(name) # validates the name
76
85
  # As this is a mutable class_attr, we cannot use
77
- # self.intfs[s] = @@cur_intf # Hash#[]=
86
+ # self.intfs[name] = @@cur_intf # Hash#[]=
78
87
  # as that would modify parent class attr in place.
79
88
  # Using the setter lets a subclass have the new value
80
89
  # while the superclass keeps the old one.
81
- self.intfs = intfs.merge(s => @@cur_intf)
90
+ self.intfs = intfs.merge(name => @@cur_intf)
82
91
  end
83
92
  yield
84
93
  @@cur_intf = nil
85
94
  end
86
95
  end
87
96
 
88
- # Dummy undefined interface class.
89
- class UndefinedInterface < ScriptError
97
+ # Forgetting to declare the interface for a method/signal/property
98
+ # is a ScriptError.
99
+ class UndefinedInterface < ScriptError # rubocop:disable Lint/InheritException
90
100
  def initialize(sym)
91
- super "No interface specified for #{sym}"
101
+ super "No interface specified for #{sym}. Enclose it in dbus_interface."
102
+ end
103
+ end
104
+
105
+ # A read-write property accessing an instance variable.
106
+ # A combination of `attr_accessor` and {.dbus_accessor}.
107
+ #
108
+ # PropertiesChanged signal will be emitted whenever `foo_bar=` is used
109
+ # but not when @foo_bar is written directly.
110
+ #
111
+ # @param ruby_name [Symbol] :foo_bar is exposed as FooBar;
112
+ # use dbus_name to override
113
+ # @param type a signature like "s" or "a(uus)" or Type::STRING
114
+ # @param dbus_name [String] if not given it is made
115
+ # by CamelCasing the ruby_name. foo_bar becomes FooBar
116
+ # to convert the Ruby convention to the DBus convention.
117
+ # @return [void]
118
+ def self.dbus_attr_accessor(ruby_name, type, dbus_name: nil)
119
+ attr_accessor(ruby_name)
120
+
121
+ dbus_accessor(ruby_name, type, dbus_name: dbus_name)
122
+ end
123
+
124
+ # A read-only property accessing an instance variable.
125
+ # A combination of `attr_reader` and {.dbus_reader}.
126
+ #
127
+ # Whenever the property value gets changed from "inside" the object,
128
+ # you should emit the `PropertiesChanged` signal by calling
129
+ #
130
+ # object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {dbus_name.to_s => value}, [])
131
+ #
132
+ # or, omitting the value in the signal,
133
+ #
134
+ # object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {}, [dbus_name.to_s])
135
+ #
136
+ # @param (see .dbus_attr_accessor)
137
+ # @return (see .dbus_attr_accessor)
138
+ def self.dbus_attr_reader(ruby_name, type, dbus_name: nil)
139
+ attr_reader(ruby_name)
140
+
141
+ dbus_reader(ruby_name, type, dbus_name: dbus_name)
142
+ end
143
+
144
+ # A write-only property accessing an instance variable.
145
+ # A combination of `attr_writer` and {.dbus_writer}.
146
+ #
147
+ # @param (see .dbus_attr_accessor)
148
+ # @return (see .dbus_attr_accessor)
149
+ def self.dbus_attr_writer(ruby_name, type, dbus_name: nil)
150
+ attr_writer(ruby_name)
151
+
152
+ dbus_writer(ruby_name, type, dbus_name: dbus_name)
153
+ end
154
+
155
+ # A read-write property using a pair of reader/writer methods
156
+ # (which must already exist).
157
+ # (To directly access an instance variable, use {.dbus_attr_accessor} instead)
158
+ #
159
+ # Uses {.dbus_watcher} to set up the PropertiesChanged signal.
160
+ #
161
+ # @param (see .dbus_attr_accessor)
162
+ # @return (see .dbus_attr_accessor)
163
+ def self.dbus_accessor(ruby_name, type, dbus_name: nil)
164
+ raise UndefinedInterface, ruby_name if @@cur_intf.nil?
165
+
166
+ dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
167
+ property = Property.new(dbus_name, type, :readwrite, ruby_name: ruby_name)
168
+ @@cur_intf.define(property)
169
+
170
+ dbus_watcher(ruby_name, dbus_name: dbus_name)
171
+ end
172
+
173
+ # A read-only property accessing a reader method (which must already exist).
174
+ # (To directly access an instance variable, use {.dbus_attr_reader} instead)
175
+ #
176
+ # Whenever the property value gets changed from "inside" the object,
177
+ # you should emit the `PropertiesChanged` signal by calling
178
+ #
179
+ # object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {dbus_name.to_s => value}, [])
180
+ #
181
+ # or, omitting the value in the signal,
182
+ #
183
+ # object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {}, [dbus_name.to_s])
184
+ #
185
+ # @param (see .dbus_attr_accessor)
186
+ # @return (see .dbus_attr_accessor)
187
+ def self.dbus_reader(ruby_name, type, dbus_name: nil)
188
+ raise UndefinedInterface, ruby_name if @@cur_intf.nil?
189
+
190
+ dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
191
+ property = Property.new(dbus_name, type, :read, ruby_name: ruby_name)
192
+ @@cur_intf.define(property)
193
+ end
194
+
195
+ # A write-only property accessing a writer method (which must already exist).
196
+ # (To directly access an instance variable, use {.dbus_attr_writer} instead)
197
+ #
198
+ # Uses {.dbus_watcher} to set up the PropertiesChanged signal.
199
+ #
200
+ # @param (see .dbus_attr_accessor)
201
+ # @return (see .dbus_attr_accessor)
202
+ def self.dbus_writer(ruby_name, type, dbus_name: nil)
203
+ raise UndefinedInterface, ruby_name if @@cur_intf.nil?
204
+
205
+ dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
206
+ property = Property.new(dbus_name, type, :write, ruby_name: ruby_name)
207
+ @@cur_intf.define(property)
208
+
209
+ dbus_watcher(ruby_name, dbus_name: dbus_name)
210
+ end
211
+
212
+ # Enables automatic sending of the PropertiesChanged signal.
213
+ # For *ruby_name* `foo_bar`, wrap `foo_bar=` so that it sends
214
+ # the signal for FooBar.
215
+ # The original version remains as `_original_foo_bar=`.
216
+ #
217
+ # @param ruby_name [Symbol] :foo_bar and :foo_bar= both mean the same thing
218
+ # @param dbus_name [String] if not given it is made
219
+ # by CamelCasing the ruby_name. foo_bar becomes FooBar
220
+ # to convert the Ruby convention to the DBus convention.
221
+ # @return [void]
222
+ def self.dbus_watcher(ruby_name, dbus_name: nil)
223
+ raise UndefinedInterface, ruby_name if @@cur_intf.nil?
224
+
225
+ cur_intf = @@cur_intf
226
+
227
+ ruby_name = ruby_name.to_s.sub(/=$/, "").to_sym
228
+ ruby_name_eq = "#{ruby_name}=".to_sym
229
+ original_ruby_name_eq = "_original_#{ruby_name_eq}"
230
+
231
+ dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
232
+
233
+ # the argument order is alias_method(new_name, existing_name)
234
+ alias_method original_ruby_name_eq, ruby_name_eq
235
+ define_method ruby_name_eq do |value|
236
+ public_send(original_ruby_name_eq, value)
237
+
238
+ # TODO: respect EmitsChangedSignal to use invalidated_properties instead
239
+ # PropertiesChanged, "interface:s, changed_properties:a{sv}, invalidated_properties:as"
240
+ PropertiesChanged(cur_intf.name, { dbus_name.to_s => value }, [])
92
241
  end
93
242
  end
94
243
 
95
244
  # Defines an exportable method on the object with the given name _sym_,
96
245
  # _prototype_ and the code in a block.
97
- def self.dbus_method(sym, protoype = "", &block)
246
+ # @param prototype [Prototype]
247
+ def self.dbus_method(sym, prototype = "", &block)
98
248
  raise UndefinedInterface, sym if @@cur_intf.nil?
99
- @@cur_intf.define(Method.new(sym.to_s).from_prototype(protoype))
100
- define_method(Object.make_method_name(@@cur_intf.name, sym.to_s), &block)
249
+
250
+ @@cur_intf.define(Method.new(sym.to_s).from_prototype(prototype))
251
+
252
+ ruby_name = Object.make_method_name(@@cur_intf.name, sym.to_s)
253
+ # ::Module#define_method(name) { body }
254
+ define_method(ruby_name, &block)
101
255
  end
102
256
 
103
257
  # Emits a signal from the object with the given _interface_, signal
104
258
  # _sig_ and arguments _args_.
259
+ # @param intf [Interface]
260
+ # @param sig [Signal]
261
+ # @param args arguments for the signal
105
262
  def emit(intf, sig, *args)
106
263
  @service.bus.emit(@service, self, intf, sig, *args)
107
264
  end
108
265
 
109
266
  # Defines a signal for the object with a given name _sym_ and _prototype_.
110
- def self.dbus_signal(sym, protoype = "")
267
+ def self.dbus_signal(sym, prototype = "")
111
268
  raise UndefinedInterface, sym if @@cur_intf.nil?
269
+
112
270
  cur_intf = @@cur_intf
113
- signal = Signal.new(sym.to_s).from_prototype(protoype)
114
- cur_intf.define(Signal.new(sym.to_s).from_prototype(protoype))
271
+ signal = Signal.new(sym.to_s).from_prototype(prototype)
272
+ cur_intf.define(Signal.new(sym.to_s).from_prototype(prototype))
273
+
274
+ # ::Module#define_method(name) { body }
115
275
  define_method(sym.to_s) do |*args|
116
276
  emit(cur_intf, signal, *args)
117
277
  end
118
278
  end
119
279
 
120
- ####################################################################
121
-
122
280
  # Helper method that returns a method name generated from the interface
123
281
  # name _intfname_ and method name _methname_.
124
282
  # @api private
125
283
  def self.make_method_name(intfname, methname)
126
284
  "#{intfname}%%#{methname}"
127
285
  end
286
+
287
+ # TODO: borrow a proven implementation
288
+ # @param str [String]
289
+ # @return [String]
290
+ # @api private
291
+ def self.camelize(str)
292
+ str.split(/_/).map(&:capitalize).join("")
293
+ end
294
+
295
+ # Make a D-Bus conventional name, CamelCased.
296
+ # @param ruby_name [String,Symbol] eg :do_something
297
+ # @param dbus_name [String,Symbol,nil] use this if given
298
+ # @return [Symbol] eg DoSomething
299
+ def self.make_dbus_name(ruby_name, dbus_name: nil)
300
+ dbus_name ||= camelize(ruby_name.to_s)
301
+ dbus_name.to_sym
302
+ end
303
+
304
+ # @param interface_name [String]
305
+ # @param property_name [String]
306
+ # @return [Property]
307
+ # @raise [DBus::Error]
308
+ # @api private
309
+ def dbus_lookup_property(interface_name, property_name)
310
+ # what should happen for unknown properties
311
+ # plasma: InvalidArgs (propname), UnknownInterface (interface)
312
+ # systemd: UnknownProperty
313
+ interface = intfs[interface_name]
314
+ if !interface
315
+ raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"),
316
+ "Property '#{interface_name}.#{property_name}' (on object '#{@path}') not found: no such interface"
317
+ end
318
+
319
+ property = interface.properties[property_name.to_sym]
320
+ if !property
321
+ raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"),
322
+ "Property '#{interface_name}.#{property_name}' (on object '#{@path}') not found"
323
+ end
324
+
325
+ property
326
+ end
327
+
328
+ ####################################################################
329
+
330
+ # use the above defined methods to declare the property-handling
331
+ # interfaces and methods
332
+
333
+ dbus_interface PROPERTY_INTERFACE do
334
+ dbus_method :Get, "in interface_name:s, in property_name:s, out value:v" do |interface_name, property_name|
335
+ property = dbus_lookup_property(interface_name, property_name)
336
+
337
+ if property.readable?
338
+ ruby_name = property.ruby_name
339
+ value = public_send(ruby_name)
340
+ # may raise, DBus.error or https://ruby-doc.com/core-3.1.0/TypeError.html
341
+ typed_value = Data.make_typed(property.type, value)
342
+ [typed_value]
343
+ else
344
+ raise DBus.error("org.freedesktop.DBus.Error.PropertyWriteOnly"),
345
+ "Property '#{interface_name}.#{property_name}' (on object '#{@path}') is not readable"
346
+ end
347
+ end
348
+
349
+ dbus_method :Set, "in interface_name:s, in property_name:s, in val:v" do |interface_name, property_name, value|
350
+ property = dbus_lookup_property(interface_name, property_name)
351
+
352
+ if property.writable?
353
+ ruby_name_eq = "#{property.ruby_name}="
354
+ # TODO: declare dbus_method :Set to take :exact argument
355
+ # and type check it here before passing its :plain value
356
+ # to the implementation
357
+ public_send(ruby_name_eq, value)
358
+ else
359
+ raise DBus.error("org.freedesktop.DBus.Error.PropertyReadOnly"),
360
+ "Property '#{interface_name}.#{property_name}' (on object '#{@path}') is not writable"
361
+ end
362
+ end
363
+
364
+ dbus_method :GetAll, "in interface_name:s, out value:a{sv}" do |interface_name|
365
+ interface = intfs[interface_name]
366
+ if !interface
367
+ raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"),
368
+ "Properties '#{interface_name}.*' (on object '#{@path}') not found: no such interface"
369
+ end
370
+
371
+ p_hash = {}
372
+ interface.properties.each do |p_name, property|
373
+ next unless property.readable?
374
+
375
+ ruby_name = property.ruby_name
376
+ begin
377
+ # D-Bus spec says:
378
+ # > If GetAll is called with a valid interface name for which some
379
+ # > properties are not accessible to the caller (for example, due
380
+ # > to per-property access control implemented in the service),
381
+ # > those properties should be silently omitted from the result
382
+ # > array.
383
+ # so we will silently omit properties that fail to read.
384
+ # Get'ting them individually will send DBus.Error
385
+ value = public_send(ruby_name)
386
+ # may raise, DBus.error or https://ruby-doc.com/core-3.1.0/TypeError.html
387
+ typed_value = Data.make_typed(property.type, value)
388
+ p_hash[p_name.to_s] = typed_value
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
128
400
  end
129
401
  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) 2019 Martin Vidner
3
5
  #
@@ -7,18 +9,21 @@
7
9
  # See the file "COPYING" for the exact licensing terms.
8
10
 
9
11
  module DBus
10
- # A {::String} that validates at initialization time
12
+ # A {::String} that validates at initialization time.
13
+ # See also {DBus::Data::ObjectPath}
14
+ # @see https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-object-path
11
15
  class ObjectPath < String
12
16
  # @raise Error if not a valid object path
13
- def initialize(s)
14
- unless self.class.valid?(s)
15
- raise DBus::Error, "Invalid object path #{s.inspect}"
17
+ def initialize(str)
18
+ unless self.class.valid?(str)
19
+ raise DBus::Error, "Invalid object path #{str.inspect}"
16
20
  end
21
+
17
22
  super
18
23
  end
19
24
 
20
- def self.valid?(s)
21
- s == "/" || s =~ %r{\A(/[A-Za-z0-9_]+)+\z}
25
+ def self.valid?(str)
26
+ str == "/" || str =~ %r{\A(/[A-Za-z0-9_]+)+\z}
22
27
  end
23
28
  end
24
29
  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
@@ -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