ruby-dbus 0.16.0 → 0.18.1

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