ruby-dbus 0.16.0 → 0.18.1

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +160 -0
  3. data/README.md +3 -5
  4. data/Rakefile +18 -8
  5. data/VERSION +1 -1
  6. data/doc/Reference.md +106 -7
  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 +123 -75
  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 +821 -0
  26. data/lib/dbus/emits_changed_signal.rb +83 -0
  27. data/lib/dbus/error.rb +4 -2
  28. data/lib/dbus/introspect.rb +132 -31
  29. data/lib/dbus/logger.rb +3 -1
  30. data/lib/dbus/marshall.rb +247 -296
  31. data/lib/dbus/matchrule.rb +16 -16
  32. data/lib/dbus/message.rb +44 -37
  33. data/lib/dbus/message_queue.rb +16 -10
  34. data/lib/dbus/object.rb +358 -24
  35. data/lib/dbus/object_path.rb +11 -6
  36. data/lib/dbus/proxy_object.rb +22 -1
  37. data/lib/dbus/proxy_object_factory.rb +13 -7
  38. data/lib/dbus/proxy_object_interface.rb +63 -30
  39. data/lib/dbus/raw_message.rb +91 -0
  40. data/lib/dbus/type.rb +318 -86
  41. data/lib/dbus/xml.rb +32 -17
  42. data/lib/dbus.rb +14 -7
  43. data/ruby-dbus.gemspec +7 -3
  44. data/spec/async_spec.rb +2 -0
  45. data/spec/binding_spec.rb +2 -0
  46. data/spec/bus_and_xml_backend_spec.rb +2 -0
  47. data/spec/bus_driver_spec.rb +2 -0
  48. data/spec/bus_name_spec.rb +3 -1
  49. data/spec/bus_spec.rb +2 -0
  50. data/spec/byte_array_spec.rb +2 -0
  51. data/spec/client_robustness_spec.rb +4 -2
  52. data/spec/data/marshall.yaml +1667 -0
  53. data/spec/data_spec.rb +673 -0
  54. data/spec/emits_changed_signal_spec.rb +58 -0
  55. data/spec/err_msg_spec.rb +2 -0
  56. data/spec/introspect_xml_parser_spec.rb +2 -0
  57. data/spec/introspection_spec.rb +2 -0
  58. data/spec/main_loop_spec.rb +3 -1
  59. data/spec/node_spec.rb +23 -0
  60. data/spec/object_path_spec.rb +3 -0
  61. data/spec/object_spec.rb +138 -0
  62. data/spec/packet_marshaller_spec.rb +41 -0
  63. data/spec/packet_unmarshaller_spec.rb +248 -0
  64. data/spec/property_spec.rb +192 -5
  65. data/spec/proxy_object_spec.rb +2 -0
  66. data/spec/server_robustness_spec.rb +2 -0
  67. data/spec/server_spec.rb +2 -0
  68. data/spec/service_newapi.rb +70 -70
  69. data/spec/session_bus_spec.rb +3 -1
  70. data/spec/session_bus_spec_manual.rb +2 -0
  71. data/spec/signal_spec.rb +5 -3
  72. data/spec/spec_helper.rb +37 -9
  73. data/spec/thread_safety_spec.rb +2 -0
  74. data/spec/tools/dbus-limited-session.conf +4 -0
  75. data/spec/type_spec.rb +214 -6
  76. data/spec/value_spec.rb +16 -1
  77. data/spec/variant_spec.rb +4 -2
  78. data/spec/zzz_quit_spec.rb +16 -0
  79. metadata +34 -8
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of the ruby-dbus project
4
+ # Copyright (C) 2022 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
+ # Describes the behavior of PropertiesChanged signal, for a single property
13
+ # or for an entire interface.
14
+ #
15
+ # The possible values are:
16
+ #
17
+ # - *true*: the signal is emitted with the value included.
18
+ # - *:invalidates*: the signal is emitted but the value is not included
19
+ # in the signal.
20
+ # - *:const*: the property never changes value during the lifetime
21
+ # of the object it belongs to, and hence the signal
22
+ # is never emitted for it (but clients can cache the value)
23
+ # - *false*: the signal won't be emitted (clients should re-Get the property value)
24
+ #
25
+ # The default is:
26
+ # - for an interface: *true*
27
+ # - for a property: what the parent interface specifies
28
+ #
29
+ # @see DBus::Object.emits_changed_signal
30
+ # @see DBus::Object.dbus_attr_accessor
31
+ # @see https://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format
32
+ #
33
+ # Immutable once constructed.
34
+ class EmitsChangedSignal
35
+ # @return [true,false,:const,:invalidates]
36
+ attr_reader :value
37
+
38
+ # @param value [true,false,:const,:invalidates,nil]
39
+ # See class-level description above, {EmitsChangedSignal}.
40
+ # @param interface [Interface,nil]
41
+ # If the (property-level) *value* is unspecified (nil), this is the
42
+ # containing {Interface} to get the value from.
43
+ def initialize(value, interface: nil)
44
+ if value.nil?
45
+ raise ArgumentError, "Both arguments are nil" if interface.nil?
46
+
47
+ @value = interface.emits_changed_signal.value
48
+ else
49
+ expecting = [true, false, :const, :invalidates]
50
+ unless expecting.include?(value)
51
+ raise ArgumentError, "Expecting one of #{expecting.inspect}. Seen #{value.inspect}"
52
+ end
53
+
54
+ @value = value
55
+ end
56
+
57
+ freeze
58
+ end
59
+
60
+ # Return introspection XML string representation
61
+ # @return [String]
62
+ def to_xml
63
+ return "" if @value == true
64
+
65
+ " <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"#{@value}\"/>\n"
66
+ end
67
+
68
+ def to_s
69
+ @value.to_s
70
+ end
71
+
72
+ def ==(other)
73
+ if other.is_a?(self.class)
74
+ other.value == @value
75
+ else
76
+ other == value
77
+ end
78
+ end
79
+ alias eql? ==
80
+
81
+ DEFAULT_ECS = EmitsChangedSignal.new(true)
82
+ end
83
+ end
data/lib/dbus/error.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # error.rb
2
4
  #
3
5
  # This file is part of the ruby-dbus project
@@ -32,7 +34,7 @@ module DBus
32
34
  end
33
35
  # TODO: validate error name
34
36
  end
35
- end # class Error
37
+ end
36
38
 
37
39
  # @example raise a generic error
38
40
  # raise DBus.error, "message"
@@ -43,4 +45,4 @@ module DBus
43
45
  DBus::Error.new(nil, name)
44
46
  end
45
47
  module_function :error
46
- end # module DBus
48
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # dbus/introspection.rb - module containing a low-level D-Bus introspection implementation
2
4
  #
3
5
  # This file is part of the ruby-dbus project
@@ -10,9 +12,9 @@
10
12
 
11
13
  module DBus
12
14
  # Regular expressions that should match all method names.
13
- METHOD_SIGNAL_RE = /^[A-Za-z][A-Za-z0-9_]*$/
15
+ METHOD_SIGNAL_RE = /^[A-Za-z][A-Za-z0-9_]*$/.freeze
14
16
  # Regular expressions that should match all interface names.
15
- INTERFACE_ELEMENT_RE = /^[A-Za-z][A-Za-z0-9_]*$/
17
+ INTERFACE_ELEMENT_RE = /^[A-Za-z][A-Za-z0-9_]*$/.freeze
16
18
 
17
19
  # Exception raised when an invalid class definition is encountered.
18
20
  class InvalidClassDefinition < Exception
@@ -24,21 +26,42 @@ module DBus
24
26
  # method call instantiates and configures this class for us.
25
27
  #
26
28
  # It also is the local definition of interface exported by the program.
27
- # At the client side, see ProxyObjectInterface
29
+ # At the client side, see {ProxyObjectInterface}.
28
30
  class Interface
29
- # The name of the interface. String
31
+ # @return [String] The name of the interface.
30
32
  attr_reader :name
31
- # The methods that are part of the interface. Hash: Symbol => DBus::Method
33
+ # @return [Hash{Symbol => DBus::Method}] The methods that are part of the interface.
32
34
  attr_reader :methods
33
- # The signals that are part of the interface. Hash: Symbol => Signal
35
+ # @return [Hash{Symbol => Signal}] The signals that are part of the interface.
34
36
  attr_reader :signals
35
37
 
38
+ # @return [Hash{Symbol => Property}]
39
+ attr_reader :properties
40
+
41
+ # @return [EmitsChangedSignal]
42
+ attr_reader :emits_changed_signal
43
+
36
44
  # Creates a new interface with a given _name_.
37
45
  def initialize(name)
38
46
  validate_name(name)
39
47
  @name = name
40
48
  @methods = {}
41
49
  @signals = {}
50
+ @properties = {}
51
+ @emits_changed_signal = EmitsChangedSignal::DEFAULT_ECS
52
+ end
53
+
54
+ # Helper for {Object.emits_changed_signal=}.
55
+ # @api private
56
+ def emits_changed_signal=(ecs)
57
+ raise TypeError unless ecs.is_a? EmitsChangedSignal
58
+ # equal?: object identity
59
+ unless @emits_changed_signal.equal?(EmitsChangedSignal::DEFAULT_ECS) ||
60
+ @emits_changed_signal.value == ecs.value
61
+ raise "emits_change_signal was assigned more than once"
62
+ end
63
+
64
+ @emits_changed_signal = ecs
42
65
  end
43
66
 
44
67
  # Validates a service _name_.
@@ -47,33 +70,57 @@ module DBus
47
70
  raise InvalidIntrospectionData if name =~ /^\./ || name =~ /\.$/
48
71
  raise InvalidIntrospectionData if name =~ /\.\./
49
72
  raise InvalidIntrospectionData if name !~ /\./
73
+
50
74
  name.split(".").each do |element|
51
75
  raise InvalidIntrospectionData if element !~ INTERFACE_ELEMENT_RE
52
76
  end
53
77
  end
54
78
 
55
- # Helper method for defining a method _m_.
56
- def define(m)
57
- if m.is_a?(Method)
58
- @methods[m.name.to_sym] = m
59
- elsif m.is_a?(Signal)
60
- @signals[m.name.to_sym] = m
61
- end
79
+ # Add _ifc_el_ as a known {Method}, {Signal} or {Property}
80
+ # @param ifc_el [InterfaceElement]
81
+ def define(ifc_el)
82
+ name = ifc_el.name.to_sym
83
+ category = case ifc_el
84
+ when Method
85
+ @methods
86
+ when Signal
87
+ @signals
88
+ when Property
89
+ @properties
90
+ end
91
+ category[name] = ifc_el
62
92
  end
93
+ alias declare define
63
94
  alias << define
64
95
 
65
96
  # Defines a method with name _id_ and a given _prototype_ in the
66
97
  # interface.
98
+ # Better name: declare_method
67
99
  def define_method(id, prototype)
68
100
  m = Method.new(id)
69
101
  m.from_prototype(prototype)
70
102
  define(m)
71
103
  end
72
- end # class Interface
104
+ alias declare_method define_method
105
+
106
+ # Return introspection XML string representation of the property.
107
+ # @return [String]
108
+ def to_xml
109
+ xml = " <interface name=\"#{name}\">\n"
110
+ xml += emits_changed_signal.to_xml
111
+ methods.each_value { |m| xml += m.to_xml }
112
+ signals.each_value { |m| xml += m.to_xml }
113
+ properties.each_value { |m| xml += m.to_xml }
114
+ xml += " </interface>\n"
115
+ xml
116
+ end
117
+ end
73
118
 
74
119
  # = A formal parameter has a name and a type
75
120
  class FormalParameter
121
+ # @return [#to_s]
76
122
  attr_reader :name
123
+ # @return [SingleCompleteType]
77
124
  attr_reader :type
78
125
 
79
126
  def initialize(name, type)
@@ -95,14 +142,16 @@ module DBus
95
142
  # This is a generic class for entities that are part of the interface
96
143
  # such as methods and signals.
97
144
  class InterfaceElement
98
- # The name of the interface element. Symbol
145
+ # @return [Symbol] The name of the interface element
99
146
  attr_reader :name
100
- # The parameters of the interface element. Array: FormalParameter
147
+
148
+ # @return [Array<FormalParameter>] The parameters of the interface element
101
149
  attr_reader :params
102
150
 
103
151
  # Validates element _name_.
104
152
  def validate_name(name)
105
153
  return if (name =~ METHOD_SIGNAL_RE) && (name.bytesize <= 255)
154
+
106
155
  raise InvalidMethodName, name
107
156
  end
108
157
 
@@ -123,13 +172,13 @@ module DBus
123
172
  def add_param(name_signature_pair)
124
173
  add_fparam(*name_signature_pair)
125
174
  end
126
- end # class InterfaceElement
175
+ end
127
176
 
128
177
  # = D-Bus interface method class
129
178
  #
130
179
  # This is a class representing methods that are part of an interface.
131
180
  class Method < InterfaceElement
132
- # The list of return values for the method. Array: FormalParameter
181
+ # @return [Array<FormalParameter>] The list of return values for the method
133
182
  attr_reader :rets
134
183
 
135
184
  # Creates a new method interface element with the given _name_.
@@ -139,15 +188,19 @@ module DBus
139
188
  end
140
189
 
141
190
  # Add a return value _name_ and _signature_.
191
+ # @param name [#to_s]
192
+ # @param signature [SingleCompleteType]
142
193
  def add_return(name, signature)
143
194
  @rets << FormalParameter.new(name, signature)
144
195
  end
145
196
 
146
197
  # Add parameter types by parsing the given _prototype_.
198
+ # @param prototype [Prototype]
147
199
  def from_prototype(prototype)
148
200
  prototype.split(/, */).each do |arg|
149
201
  arg = arg.split(" ")
150
202
  raise InvalidClassDefinition if arg.size != 2
203
+
151
204
  dir, arg = arg
152
205
  if arg =~ /:/
153
206
  arg = arg.split(":")
@@ -166,20 +219,21 @@ module DBus
166
219
  end
167
220
 
168
221
  # Return an XML string representation of the method interface elment.
222
+ # @return [String]
169
223
  def to_xml
170
- xml = %(<method name="#{@name}">\n)
224
+ xml = " <method name=\"#{@name}\">\n"
171
225
  @params.each do |param|
172
- name = param.name ? %(name="#{param.name}" ) : ""
173
- xml += %(<arg #{name}direction="in" type="#{param.type}"/>\n)
226
+ name = param.name ? "name=\"#{param.name}\" " : ""
227
+ xml += " <arg #{name}direction=\"in\" type=\"#{param.type}\"/>\n"
174
228
  end
175
229
  @rets.each do |param|
176
- name = param.name ? %(name="#{param.name}" ) : ""
177
- xml += %(<arg #{name}direction="out" type="#{param.type}"/>\n)
230
+ name = param.name ? "name=\"#{param.name}\" " : ""
231
+ xml += " <arg #{name}direction=\"out\" type=\"#{param.type}\"/>\n"
178
232
  end
179
- xml += %(</method>\n)
233
+ xml += " </method>\n"
180
234
  xml
181
235
  end
182
- end # class Method
236
+ end
183
237
 
184
238
  # = D-Bus interface signal class
185
239
  #
@@ -201,13 +255,60 @@ module DBus
201
255
 
202
256
  # Return an XML string representation of the signal interface elment.
203
257
  def to_xml
204
- xml = %(<signal name="#{@name}">\n)
258
+ xml = " <signal name=\"#{@name}\">\n"
205
259
  @params.each do |param|
206
- name = param.name ? %(name="#{param.name}" ) : ""
207
- xml += %(<arg #{name}type="#{param.type}"/>\n)
260
+ name = param.name ? "name=\"#{param.name}\" " : ""
261
+ xml += " <arg #{name}type=\"#{param.type}\"/>\n"
208
262
  end
209
- xml += %(</signal>\n)
263
+ xml += " </signal>\n"
210
264
  xml
211
265
  end
212
- end # class Signal
213
- end # module DBus
266
+ end
267
+
268
+ # An (exported) property
269
+ # https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties
270
+ class Property
271
+ # @return [Symbol] The name of the property, for example FooBar.
272
+ attr_reader :name
273
+ # @return [SingleCompleteType]
274
+ attr_reader :type
275
+ # @return [Symbol] :read :write or :readwrite
276
+ attr_reader :access
277
+
278
+ # @return [Symbol,nil] What to call at Ruby side.
279
+ # (Always without the trailing `=`)
280
+ # It is `nil` IFF representing a client-side proxy.
281
+ attr_reader :ruby_name
282
+
283
+ def initialize(name, type, access, ruby_name:)
284
+ @name = name.to_sym
285
+ @type = type
286
+ @access = access
287
+ @ruby_name = ruby_name
288
+ end
289
+
290
+ # @return [Boolean]
291
+ def readable?
292
+ access == :read || access == :readwrite
293
+ end
294
+
295
+ # @return [Boolean]
296
+ def writable?
297
+ access == :write || access == :readwrite
298
+ end
299
+
300
+ # Return introspection XML string representation of the property.
301
+ def to_xml
302
+ " <property type=\"#{@type}\" name=\"#{@name}\" access=\"#{@access}\"/>\n"
303
+ end
304
+
305
+ # @param xml_node [AbstractXML::Node]
306
+ # @return [Property]
307
+ def self.from_xml(xml_node)
308
+ name = xml_node["name"].to_sym
309
+ type = xml_node["type"]
310
+ access = xml_node["access"].to_sym
311
+ new(name, type, access, ruby_name: nil)
312
+ end
313
+ end
314
+ end
data/lib/dbus/logger.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # dbus/logger.rb - debug logging
2
4
  #
3
5
  # This file is part of the ruby-dbus project
@@ -16,7 +18,7 @@ module DBus
16
18
  # with DEBUG if $DEBUG is set, otherwise INFO.
17
19
  def logger
18
20
  unless defined? @logger
19
- @logger = Logger.new(STDERR)
21
+ @logger = Logger.new($stderr)
20
22
  @logger.level = $DEBUG ? Logger::DEBUG : Logger::INFO
21
23
  end
22
24
  @logger