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.
- checksums.yaml +4 -4
- data/NEWS.md +160 -0
- data/README.md +3 -5
- data/Rakefile +18 -8
- data/VERSION +1 -1
- data/doc/Reference.md +106 -7
- data/examples/doc/_extract_examples +7 -0
- data/examples/gdbus/gdbus +31 -24
- data/examples/no-introspect/nm-test.rb +2 -0
- data/examples/no-introspect/tracker-test.rb +3 -1
- data/examples/rhythmbox/playpause.rb +2 -1
- data/examples/service/call_service.rb +2 -1
- data/examples/service/complex-property.rb +21 -0
- data/examples/service/service_newapi.rb +1 -1
- data/examples/simple/call_introspect.rb +1 -0
- data/examples/simple/get_id.rb +2 -1
- data/examples/simple/properties.rb +2 -0
- data/examples/utils/listnames.rb +1 -0
- data/examples/utils/notify.rb +1 -0
- data/lib/dbus/api_options.rb +9 -0
- data/lib/dbus/auth.rb +20 -15
- data/lib/dbus/bus.rb +123 -75
- data/lib/dbus/bus_name.rb +12 -8
- data/lib/dbus/core_ext/class/attribute.rb +1 -1
- data/lib/dbus/data.rb +821 -0
- data/lib/dbus/emits_changed_signal.rb +83 -0
- data/lib/dbus/error.rb +4 -2
- data/lib/dbus/introspect.rb +132 -31
- data/lib/dbus/logger.rb +3 -1
- data/lib/dbus/marshall.rb +247 -296
- data/lib/dbus/matchrule.rb +16 -16
- data/lib/dbus/message.rb +44 -37
- data/lib/dbus/message_queue.rb +16 -10
- data/lib/dbus/object.rb +358 -24
- data/lib/dbus/object_path.rb +11 -6
- data/lib/dbus/proxy_object.rb +22 -1
- data/lib/dbus/proxy_object_factory.rb +13 -7
- data/lib/dbus/proxy_object_interface.rb +63 -30
- data/lib/dbus/raw_message.rb +91 -0
- data/lib/dbus/type.rb +318 -86
- data/lib/dbus/xml.rb +32 -17
- data/lib/dbus.rb +14 -7
- data/ruby-dbus.gemspec +7 -3
- data/spec/async_spec.rb +2 -0
- data/spec/binding_spec.rb +2 -0
- data/spec/bus_and_xml_backend_spec.rb +2 -0
- data/spec/bus_driver_spec.rb +2 -0
- data/spec/bus_name_spec.rb +3 -1
- data/spec/bus_spec.rb +2 -0
- data/spec/byte_array_spec.rb +2 -0
- data/spec/client_robustness_spec.rb +4 -2
- data/spec/data/marshall.yaml +1667 -0
- data/spec/data_spec.rb +673 -0
- data/spec/emits_changed_signal_spec.rb +58 -0
- data/spec/err_msg_spec.rb +2 -0
- data/spec/introspect_xml_parser_spec.rb +2 -0
- data/spec/introspection_spec.rb +2 -0
- data/spec/main_loop_spec.rb +3 -1
- data/spec/node_spec.rb +23 -0
- data/spec/object_path_spec.rb +3 -0
- data/spec/object_spec.rb +138 -0
- data/spec/packet_marshaller_spec.rb +41 -0
- data/spec/packet_unmarshaller_spec.rb +248 -0
- data/spec/property_spec.rb +192 -5
- data/spec/proxy_object_spec.rb +2 -0
- data/spec/server_robustness_spec.rb +2 -0
- data/spec/server_spec.rb +2 -0
- data/spec/service_newapi.rb +70 -70
- data/spec/session_bus_spec.rb +3 -1
- data/spec/session_bus_spec_manual.rb +2 -0
- data/spec/signal_spec.rb +5 -3
- data/spec/spec_helper.rb +37 -9
- data/spec/thread_safety_spec.rb +2 -0
- data/spec/tools/dbus-limited-session.conf +4 -0
- data/spec/type_spec.rb +214 -6
- data/spec/value_spec.rb +16 -1
- data/spec/variant_spec.rb +4 -2
- data/spec/zzz_quit_spec.rb +16 -0
- 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
|
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
|
48
|
+
end
|
data/lib/dbus/introspect.rb
CHANGED
@@ -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.
|
31
|
+
# @return [String] The name of the interface.
|
30
32
|
attr_reader :name
|
31
|
-
# The methods that are part of the interface.
|
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.
|
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
|
-
#
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
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
|
145
|
+
# @return [Symbol] The name of the interface element
|
99
146
|
attr_reader :name
|
100
|
-
|
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
|
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
|
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 =
|
224
|
+
xml = " <method name=\"#{@name}\">\n"
|
171
225
|
@params.each do |param|
|
172
|
-
name = param.name ?
|
173
|
-
xml +=
|
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 ?
|
177
|
-
xml +=
|
230
|
+
name = param.name ? "name=\"#{param.name}\" " : ""
|
231
|
+
xml += " <arg #{name}direction=\"out\" type=\"#{param.type}\"/>\n"
|
178
232
|
end
|
179
|
-
xml +=
|
233
|
+
xml += " </method>\n"
|
180
234
|
xml
|
181
235
|
end
|
182
|
-
end
|
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 =
|
258
|
+
xml = " <signal name=\"#{@name}\">\n"
|
205
259
|
@params.each do |param|
|
206
|
-
name = param.name ?
|
207
|
-
xml +=
|
260
|
+
name = param.name ? "name=\"#{param.name}\" " : ""
|
261
|
+
xml += " <arg #{name}type=\"#{param.type}\"/>\n"
|
208
262
|
end
|
209
|
-
xml +=
|
263
|
+
xml += " </signal>\n"
|
210
264
|
xml
|
211
265
|
end
|
212
|
-
end
|
213
|
-
|
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(
|
21
|
+
@logger = Logger.new($stderr)
|
20
22
|
@logger.level = $DEBUG ? Logger::DEBUG : Logger::INFO
|
21
23
|
end
|
22
24
|
@logger
|