ruby-dbus 0.16.0 → 0.18.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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