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
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
|