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.
- 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
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
59
|
-
|
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 =>
|
62
|
-
dbus_msg_exc = msg.annotate_exception(
|
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,389 @@ module DBus
|
|
68
75
|
|
69
76
|
# Select (and create) the interface that the following defined methods
|
70
77
|
# belong to.
|
71
|
-
|
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[
|
82
|
+
@@cur_intf = intfs[name]
|
74
83
|
if !@@cur_intf
|
75
|
-
@@cur_intf = Interface.new(
|
84
|
+
@@cur_intf = Interface.new(name) # validates the name
|
76
85
|
# As this is a mutable class_attr, we cannot use
|
77
|
-
# self.intfs[
|
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(
|
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
|
-
#
|
89
|
-
|
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
|
+
# Declare the behavior of PropertiesChanged signal,
|
106
|
+
# common for all properties in this interface
|
107
|
+
# (individual properties may override it)
|
108
|
+
# @example
|
109
|
+
# self.emits_changed_signal = :invalidates
|
110
|
+
# @param [true,false,:const,:invalidates] value
|
111
|
+
def self.emits_changed_signal=(value)
|
112
|
+
raise UndefinedInterface, :emits_changed_signal if @@cur_intf.nil?
|
113
|
+
|
114
|
+
@@cur_intf.emits_changed_signal = EmitsChangedSignal.new(value)
|
115
|
+
end
|
116
|
+
|
117
|
+
# A read-write property accessing an instance variable.
|
118
|
+
# A combination of `attr_accessor` and {.dbus_accessor}.
|
119
|
+
#
|
120
|
+
# PropertiesChanged signal will be emitted whenever `foo_bar=` is used
|
121
|
+
# but not when @foo_bar is written directly.
|
122
|
+
#
|
123
|
+
# @param ruby_name [Symbol] :foo_bar is exposed as FooBar;
|
124
|
+
# use dbus_name to override
|
125
|
+
# @param type a signature like "s" or "a(uus)" or Type::STRING
|
126
|
+
# @param dbus_name [String] if not given it is made
|
127
|
+
# by CamelCasing the ruby_name. foo_bar becomes FooBar
|
128
|
+
# to convert the Ruby convention to the DBus convention.
|
129
|
+
# @param emits_changed_signal [true,false,:const,:invalidates]
|
130
|
+
# see {EmitsChangedSignal}; if unspecified, ask the interface.
|
131
|
+
# @return [void]
|
132
|
+
def self.dbus_attr_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil)
|
133
|
+
attr_accessor(ruby_name)
|
134
|
+
|
135
|
+
dbus_accessor(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal)
|
136
|
+
end
|
137
|
+
|
138
|
+
# A read-only property accessing an instance variable.
|
139
|
+
# A combination of `attr_reader` and {.dbus_reader}.
|
140
|
+
#
|
141
|
+
# Whenever the property value gets changed from "inside" the object,
|
142
|
+
# you should emit the `PropertiesChanged` signal by calling
|
143
|
+
# {#dbus_properties_changed}.
|
144
|
+
#
|
145
|
+
# dbus_properties_changed(interface_name, {dbus_name.to_s => value}, [])
|
146
|
+
#
|
147
|
+
# or, omitting the value in the signal,
|
148
|
+
#
|
149
|
+
# dbus_properties_changed(interface_name, {}, [dbus_name.to_s])
|
150
|
+
#
|
151
|
+
# @param (see .dbus_attr_accessor)
|
152
|
+
# @return (see .dbus_attr_accessor)
|
153
|
+
def self.dbus_attr_reader(ruby_name, type, dbus_name: nil, emits_changed_signal: nil)
|
154
|
+
attr_reader(ruby_name)
|
155
|
+
|
156
|
+
dbus_reader(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal)
|
157
|
+
end
|
158
|
+
|
159
|
+
# A write-only property accessing an instance variable.
|
160
|
+
# A combination of `attr_writer` and {.dbus_writer}.
|
161
|
+
#
|
162
|
+
# @param (see .dbus_attr_accessor)
|
163
|
+
# @return (see .dbus_attr_accessor)
|
164
|
+
def self.dbus_attr_writer(ruby_name, type, dbus_name: nil, emits_changed_signal: nil)
|
165
|
+
attr_writer(ruby_name)
|
166
|
+
|
167
|
+
dbus_writer(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal)
|
168
|
+
end
|
169
|
+
|
170
|
+
# A read-write property using a pair of reader/writer methods
|
171
|
+
# (which must already exist).
|
172
|
+
# (To directly access an instance variable, use {.dbus_attr_accessor} instead)
|
173
|
+
#
|
174
|
+
# Uses {.dbus_watcher} to set up the PropertiesChanged signal.
|
175
|
+
#
|
176
|
+
# @param (see .dbus_attr_accessor)
|
177
|
+
# @return (see .dbus_attr_accessor)
|
178
|
+
def self.dbus_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil)
|
179
|
+
raise UndefinedInterface, ruby_name if @@cur_intf.nil?
|
180
|
+
|
181
|
+
dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
|
182
|
+
property = Property.new(dbus_name, type, :readwrite, ruby_name: ruby_name)
|
183
|
+
@@cur_intf.define(property)
|
184
|
+
|
185
|
+
dbus_watcher(ruby_name, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal)
|
186
|
+
end
|
187
|
+
|
188
|
+
# A read-only property accessing a reader method (which must already exist).
|
189
|
+
# (To directly access an instance variable, use {.dbus_attr_reader} instead)
|
190
|
+
#
|
191
|
+
# At the D-Bus side the property is read only but it makes perfect sense to
|
192
|
+
# implement it with a read-write attr_accessor. In that case this method
|
193
|
+
# uses {.dbus_watcher} to set up the PropertiesChanged signal.
|
194
|
+
#
|
195
|
+
# attr_accessor :foo_bar
|
196
|
+
# dbus_reader :foo_bar, "s"
|
197
|
+
#
|
198
|
+
# If the property value should change by other means than its attr_writer,
|
199
|
+
# you should emit the `PropertiesChanged` signal by calling
|
200
|
+
# {#dbus_properties_changed}.
|
201
|
+
#
|
202
|
+
# dbus_properties_changed(interface_name, {dbus_name.to_s => value}, [])
|
203
|
+
#
|
204
|
+
# or, omitting the value in the signal,
|
205
|
+
#
|
206
|
+
# dbus_properties_changed(interface_name, {}, [dbus_name.to_s])
|
207
|
+
#
|
208
|
+
# @param (see .dbus_attr_accessor)
|
209
|
+
# @return (see .dbus_attr_accessor)
|
210
|
+
def self.dbus_reader(ruby_name, type, dbus_name: nil, emits_changed_signal: nil)
|
211
|
+
raise UndefinedInterface, ruby_name if @@cur_intf.nil?
|
212
|
+
|
213
|
+
dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
|
214
|
+
property = Property.new(dbus_name, type, :read, ruby_name: ruby_name)
|
215
|
+
@@cur_intf.define(property)
|
216
|
+
|
217
|
+
ruby_name_eq = "#{ruby_name}=".to_sym
|
218
|
+
return unless method_defined?(ruby_name_eq)
|
219
|
+
|
220
|
+
dbus_watcher(ruby_name, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal)
|
221
|
+
end
|
222
|
+
|
223
|
+
# A write-only property accessing a writer method (which must already exist).
|
224
|
+
# (To directly access an instance variable, use {.dbus_attr_writer} instead)
|
225
|
+
#
|
226
|
+
# Uses {.dbus_watcher} to set up the PropertiesChanged signal.
|
227
|
+
#
|
228
|
+
# @param (see .dbus_attr_accessor)
|
229
|
+
# @return (see .dbus_attr_accessor)
|
230
|
+
def self.dbus_writer(ruby_name, type, dbus_name: nil, emits_changed_signal: nil)
|
231
|
+
raise UndefinedInterface, ruby_name if @@cur_intf.nil?
|
232
|
+
|
233
|
+
dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
|
234
|
+
property = Property.new(dbus_name, type, :write, ruby_name: ruby_name)
|
235
|
+
@@cur_intf.define(property)
|
236
|
+
|
237
|
+
dbus_watcher(ruby_name, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal)
|
238
|
+
end
|
239
|
+
|
240
|
+
# Enables automatic sending of the PropertiesChanged signal.
|
241
|
+
# For *ruby_name* `foo_bar`, wrap `foo_bar=` so that it sends
|
242
|
+
# the signal for FooBar.
|
243
|
+
# The original version remains as `_original_foo_bar=`.
|
244
|
+
#
|
245
|
+
# @param ruby_name [Symbol] :foo_bar and :foo_bar= both mean the same thing
|
246
|
+
# @param dbus_name [String] if not given it is made
|
247
|
+
# by CamelCasing the ruby_name. foo_bar becomes FooBar
|
248
|
+
# to convert the Ruby convention to the DBus convention.
|
249
|
+
# @param emits_changed_signal [true,false,:const,:invalidates]
|
250
|
+
# see {EmitsChangedSignal}; if unspecified, ask the interface.
|
251
|
+
# @return [void]
|
252
|
+
def self.dbus_watcher(ruby_name, dbus_name: nil, emits_changed_signal: nil)
|
253
|
+
raise UndefinedInterface, ruby_name if @@cur_intf.nil?
|
254
|
+
|
255
|
+
interface_name = @@cur_intf.name
|
256
|
+
|
257
|
+
ruby_name = ruby_name.to_s.sub(/=$/, "").to_sym
|
258
|
+
ruby_name_eq = "#{ruby_name}=".to_sym
|
259
|
+
original_ruby_name_eq = "_original_#{ruby_name_eq}"
|
260
|
+
|
261
|
+
dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
|
262
|
+
|
263
|
+
emits_changed_signal = EmitsChangedSignal.new(emits_changed_signal, interface: @@cur_intf)
|
264
|
+
|
265
|
+
# the argument order is alias_method(new_name, existing_name)
|
266
|
+
alias_method original_ruby_name_eq, ruby_name_eq
|
267
|
+
define_method ruby_name_eq do |value|
|
268
|
+
result = public_send(original_ruby_name_eq, value)
|
269
|
+
|
270
|
+
case emits_changed_signal.value
|
271
|
+
when true
|
272
|
+
# signature: "interface:s, changed_props:a{sv}, invalidated_props:as"
|
273
|
+
dbus_properties_changed(interface_name, { dbus_name.to_s => value }, [])
|
274
|
+
when :invalidates
|
275
|
+
dbus_properties_changed(interface_name, {}, [dbus_name.to_s])
|
276
|
+
when :const
|
277
|
+
# Oh my, seeing a value change of a supposedly constant property.
|
278
|
+
# Maybe should have raised at declaration time, don't make a fuss now.
|
279
|
+
when false
|
280
|
+
# Do nothing
|
281
|
+
end
|
282
|
+
|
283
|
+
result
|
92
284
|
end
|
93
285
|
end
|
94
286
|
|
95
287
|
# Defines an exportable method on the object with the given name _sym_,
|
96
288
|
# _prototype_ and the code in a block.
|
97
|
-
|
289
|
+
# @param prototype [Prototype]
|
290
|
+
def self.dbus_method(sym, prototype = "", &block)
|
98
291
|
raise UndefinedInterface, sym if @@cur_intf.nil?
|
99
|
-
|
100
|
-
|
292
|
+
|
293
|
+
@@cur_intf.define(Method.new(sym.to_s).from_prototype(prototype))
|
294
|
+
|
295
|
+
ruby_name = Object.make_method_name(@@cur_intf.name, sym.to_s)
|
296
|
+
# ::Module#define_method(name) { body }
|
297
|
+
define_method(ruby_name, &block)
|
101
298
|
end
|
102
299
|
|
103
300
|
# Emits a signal from the object with the given _interface_, signal
|
104
301
|
# _sig_ and arguments _args_.
|
302
|
+
# @param intf [Interface]
|
303
|
+
# @param sig [Signal]
|
304
|
+
# @param args arguments for the signal
|
105
305
|
def emit(intf, sig, *args)
|
106
306
|
@service.bus.emit(@service, self, intf, sig, *args)
|
107
307
|
end
|
108
308
|
|
109
309
|
# Defines a signal for the object with a given name _sym_ and _prototype_.
|
110
|
-
def self.dbus_signal(sym,
|
310
|
+
def self.dbus_signal(sym, prototype = "")
|
111
311
|
raise UndefinedInterface, sym if @@cur_intf.nil?
|
312
|
+
|
112
313
|
cur_intf = @@cur_intf
|
113
|
-
signal = Signal.new(sym.to_s).from_prototype(
|
114
|
-
cur_intf.define(Signal.new(sym.to_s).from_prototype(
|
314
|
+
signal = Signal.new(sym.to_s).from_prototype(prototype)
|
315
|
+
cur_intf.define(Signal.new(sym.to_s).from_prototype(prototype))
|
316
|
+
|
317
|
+
# ::Module#define_method(name) { body }
|
115
318
|
define_method(sym.to_s) do |*args|
|
116
319
|
emit(cur_intf, signal, *args)
|
117
320
|
end
|
118
321
|
end
|
119
322
|
|
120
|
-
####################################################################
|
121
|
-
|
122
323
|
# Helper method that returns a method name generated from the interface
|
123
324
|
# name _intfname_ and method name _methname_.
|
124
325
|
# @api private
|
125
326
|
def self.make_method_name(intfname, methname)
|
126
327
|
"#{intfname}%%#{methname}"
|
127
328
|
end
|
329
|
+
|
330
|
+
# TODO: borrow a proven implementation
|
331
|
+
# @param str [String]
|
332
|
+
# @return [String]
|
333
|
+
# @api private
|
334
|
+
def self.camelize(str)
|
335
|
+
str.split(/_/).map(&:capitalize).join("")
|
336
|
+
end
|
337
|
+
|
338
|
+
# Make a D-Bus conventional name, CamelCased.
|
339
|
+
# @param ruby_name [String,Symbol] eg :do_something
|
340
|
+
# @param dbus_name [String,Symbol,nil] use this if given
|
341
|
+
# @return [Symbol] eg DoSomething
|
342
|
+
def self.make_dbus_name(ruby_name, dbus_name: nil)
|
343
|
+
dbus_name ||= camelize(ruby_name.to_s)
|
344
|
+
dbus_name.to_sym
|
345
|
+
end
|
346
|
+
|
347
|
+
# Use this instead of calling PropertiesChanged directly. This one
|
348
|
+
# considers not only the PC signature (which says that all property values
|
349
|
+
# are variants) but also the specific property type.
|
350
|
+
# @param interface_name [String] interface name like "org.example.ManagerManager"
|
351
|
+
# @param changed_props [Hash{String => ::Object}]
|
352
|
+
# changed properties (D-Bus names) and their values.
|
353
|
+
# @param invalidated_props [Array<String>]
|
354
|
+
# names of properties whose changed value is not specified
|
355
|
+
def dbus_properties_changed(interface_name, changed_props, invalidated_props)
|
356
|
+
typed_changed_props = changed_props.map do |dbus_name, value|
|
357
|
+
property = dbus_lookup_property(interface_name, dbus_name)
|
358
|
+
type = property.type
|
359
|
+
typed_value = Data.make_typed(type, value)
|
360
|
+
variant = Data::Variant.new(typed_value, member_type: type)
|
361
|
+
[dbus_name, variant]
|
362
|
+
end.to_h
|
363
|
+
PropertiesChanged(interface_name, typed_changed_props, invalidated_props)
|
364
|
+
end
|
365
|
+
|
366
|
+
# @param interface_name [String]
|
367
|
+
# @param property_name [String]
|
368
|
+
# @return [Property]
|
369
|
+
# @raise [DBus::Error]
|
370
|
+
# @api private
|
371
|
+
def dbus_lookup_property(interface_name, property_name)
|
372
|
+
# what should happen for unknown properties
|
373
|
+
# plasma: InvalidArgs (propname), UnknownInterface (interface)
|
374
|
+
# systemd: UnknownProperty
|
375
|
+
interface = intfs[interface_name]
|
376
|
+
if !interface
|
377
|
+
raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"),
|
378
|
+
"Property '#{interface_name}.#{property_name}' (on object '#{@path}') not found: no such interface"
|
379
|
+
end
|
380
|
+
|
381
|
+
property = interface.properties[property_name.to_sym]
|
382
|
+
if !property
|
383
|
+
raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"),
|
384
|
+
"Property '#{interface_name}.#{property_name}' (on object '#{@path}') not found"
|
385
|
+
end
|
386
|
+
|
387
|
+
property
|
388
|
+
end
|
389
|
+
|
390
|
+
####################################################################
|
391
|
+
|
392
|
+
# use the above defined methods to declare the property-handling
|
393
|
+
# interfaces and methods
|
394
|
+
|
395
|
+
dbus_interface PROPERTY_INTERFACE do
|
396
|
+
dbus_method :Get, "in interface_name:s, in property_name:s, out value:v" do |interface_name, property_name|
|
397
|
+
property = dbus_lookup_property(interface_name, property_name)
|
398
|
+
|
399
|
+
if property.readable?
|
400
|
+
ruby_name = property.ruby_name
|
401
|
+
value = public_send(ruby_name)
|
402
|
+
# may raise, DBus.error or https://ruby-doc.com/core-3.1.0/TypeError.html
|
403
|
+
typed_value = Data.make_typed(property.type, value)
|
404
|
+
[typed_value]
|
405
|
+
else
|
406
|
+
raise DBus.error("org.freedesktop.DBus.Error.PropertyWriteOnly"),
|
407
|
+
"Property '#{interface_name}.#{property_name}' (on object '#{@path}') is not readable"
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
dbus_method :Set, "in interface_name:s, in property_name:s, in val:v" do |interface_name, property_name, value|
|
412
|
+
property = dbus_lookup_property(interface_name, property_name)
|
413
|
+
|
414
|
+
if property.writable?
|
415
|
+
ruby_name_eq = "#{property.ruby_name}="
|
416
|
+
# TODO: declare dbus_method :Set to take :exact argument
|
417
|
+
# and type check it here before passing its :plain value
|
418
|
+
# to the implementation
|
419
|
+
public_send(ruby_name_eq, value)
|
420
|
+
else
|
421
|
+
raise DBus.error("org.freedesktop.DBus.Error.PropertyReadOnly"),
|
422
|
+
"Property '#{interface_name}.#{property_name}' (on object '#{@path}') is not writable"
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
dbus_method :GetAll, "in interface_name:s, out value:a{sv}" do |interface_name|
|
427
|
+
interface = intfs[interface_name]
|
428
|
+
if !interface
|
429
|
+
raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"),
|
430
|
+
"Properties '#{interface_name}.*' (on object '#{@path}') not found: no such interface"
|
431
|
+
end
|
432
|
+
|
433
|
+
p_hash = {}
|
434
|
+
interface.properties.each do |p_name, property|
|
435
|
+
next unless property.readable?
|
436
|
+
|
437
|
+
ruby_name = property.ruby_name
|
438
|
+
begin
|
439
|
+
# D-Bus spec says:
|
440
|
+
# > If GetAll is called with a valid interface name for which some
|
441
|
+
# > properties are not accessible to the caller (for example, due
|
442
|
+
# > to per-property access control implemented in the service),
|
443
|
+
# > those properties should be silently omitted from the result
|
444
|
+
# > array.
|
445
|
+
# so we will silently omit properties that fail to read.
|
446
|
+
# Get'ting them individually will send DBus.Error
|
447
|
+
value = public_send(ruby_name)
|
448
|
+
# may raise, DBus.error or https://ruby-doc.com/core-3.1.0/TypeError.html
|
449
|
+
typed_value = Data.make_typed(property.type, value)
|
450
|
+
p_hash[p_name.to_s] = typed_value
|
451
|
+
rescue StandardError
|
452
|
+
DBus.logger.debug "Property '#{interface_name}.#{p_name}' (on object '#{@path}')" \
|
453
|
+
" has raised during GetAll, omitting it"
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
[p_hash]
|
458
|
+
end
|
459
|
+
|
460
|
+
dbus_signal :PropertiesChanged, "interface:s, changed_properties:a{sv}, invalidated_properties:as"
|
461
|
+
end
|
128
462
|
end
|
129
463
|
end
|
data/lib/dbus/object_path.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) 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(
|
14
|
-
unless self.class.valid?(
|
15
|
-
raise DBus::Error, "Invalid object path #{
|
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?(
|
21
|
-
|
25
|
+
def self.valid?(str)
|
26
|
+
str == "/" || str =~ %r{\A(/[A-Za-z0-9_]+)+\z}
|
22
27
|
end
|
23
28
|
end
|
24
29
|
end
|
data/lib/dbus/proxy_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
|
# 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
|
-
|
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,21 @@ module DBus
|
|
22
24
|
@api = api
|
23
25
|
end
|
24
26
|
|
25
|
-
# Investigates the sub-nodes of the proxy object
|
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
|
-
|
28
|
-
|
29
|
+
# @param pobj [ProxyObject]
|
30
|
+
# @param xml [String]
|
31
|
+
def self.introspect_into(pobj, xml)
|
32
|
+
# intfs [Array<Interface>], subnodes [Array<String>]
|
33
|
+
intfs, pobj.subnodes = IntrospectXMLParser.new(xml).parse
|
29
34
|
intfs.each do |i|
|
30
|
-
poi = ProxyObjectInterface.new(
|
35
|
+
poi = ProxyObjectInterface.new(pobj, i.name)
|
31
36
|
i.methods.each_value { |m| poi.define(m) }
|
32
37
|
i.signals.each_value { |s| poi.define(s) }
|
33
|
-
|
38
|
+
i.properties.each_value { |p| poi.define(p) }
|
39
|
+
pobj[i.name] = poi
|
34
40
|
end
|
35
|
-
|
41
|
+
pobj.introspected = true
|
36
42
|
end
|
37
43
|
|
38
44
|
# Generates, sets up and returns the proxy object.
|
@@ -41,5 +47,5 @@ module DBus
|
|
41
47
|
ProxyObjectFactory.introspect_into(po, @xml)
|
42
48
|
po
|
43
49
|
end
|
44
|
-
end
|
50
|
+
end
|
45
51
|
end
|