ruby-dbus 0.15.0 → 0.18.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/NEWS.md +41 -1
- data/README.md +3 -5
- data/Rakefile +18 -8
- data/VERSION +1 -1
- data/doc/Reference.md +93 -3
- 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/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 +129 -74
- data/lib/dbus/bus_name.rb +31 -0
- data/lib/dbus/core_ext/class/attribute.rb +1 -1
- data/lib/dbus/error.rb +4 -2
- data/lib/dbus/introspect.rb +90 -34
- data/lib/dbus/logger.rb +3 -1
- data/lib/dbus/marshall.rb +119 -87
- data/lib/dbus/matchrule.rb +16 -16
- data/lib/dbus/message.rb +40 -27
- data/lib/dbus/message_queue.rb +26 -18
- data/lib/dbus/object.rb +401 -0
- data/lib/dbus/object_path.rb +28 -0
- data/lib/dbus/proxy_object.rb +23 -2
- data/lib/dbus/proxy_object_factory.rb +11 -7
- data/lib/dbus/proxy_object_interface.rb +26 -21
- data/lib/dbus/type.rb +59 -34
- data/lib/dbus/xml.rb +28 -17
- data/lib/dbus.rb +10 -8
- data/ruby-dbus.gemspec +8 -4
- 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 +27 -0
- data/spec/bus_spec.rb +2 -0
- data/spec/byte_array_spec.rb +2 -0
- data/spec/client_robustness_spec.rb +27 -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 +25 -0
- data/spec/property_spec.rb +64 -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 +39 -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 +23 -9
- data/spec/thread_safety_spec.rb +2 -0
- data/spec/tools/dbus-limited-session.conf +4 -0
- data/spec/type_spec.rb +2 -0
- data/spec/value_spec.rb +16 -1
- data/spec/variant_spec.rb +4 -2
- metadata +32 -12
- data/lib/dbus/export.rb +0 -131
data/lib/dbus/object.rb
ADDED
@@ -0,0 +1,401 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of the ruby-dbus project
|
4
|
+
# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
|
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
|
+
require_relative "core_ext/class/attribute"
|
12
|
+
|
13
|
+
module DBus
|
14
|
+
PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"
|
15
|
+
|
16
|
+
# Exported object type
|
17
|
+
# = Exportable D-Bus object class
|
18
|
+
#
|
19
|
+
# Objects that are going to be exported by a D-Bus service
|
20
|
+
# should inherit from this class. At the client side, use {ProxyObject}.
|
21
|
+
class Object
|
22
|
+
# The path of the object.
|
23
|
+
attr_reader :path
|
24
|
+
|
25
|
+
# The interfaces that the object supports. Hash: String => Interface
|
26
|
+
my_class_attribute :intfs
|
27
|
+
self.intfs = {}
|
28
|
+
|
29
|
+
# The service that the object is exported by.
|
30
|
+
attr_writer :service
|
31
|
+
|
32
|
+
@@cur_intf = nil # Interface
|
33
|
+
@@intfs_mutex = Mutex.new
|
34
|
+
|
35
|
+
# Create a new object with a given _path_.
|
36
|
+
# Use Service#export to export it.
|
37
|
+
def initialize(path)
|
38
|
+
@path = path
|
39
|
+
@service = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
# Dispatch a message _msg_ to call exported methods
|
43
|
+
def dispatch(msg)
|
44
|
+
case msg.message_type
|
45
|
+
when Message::METHOD_CALL
|
46
|
+
reply = nil
|
47
|
+
begin
|
48
|
+
iface = intfs[msg.interface]
|
49
|
+
if !iface
|
50
|
+
raise DBus.error("org.freedesktop.DBus.Error.UnknownMethod"),
|
51
|
+
"Interface \"#{msg.interface}\" of object \"#{msg.path}\" doesn't exist"
|
52
|
+
end
|
53
|
+
member_sym = msg.member.to_sym
|
54
|
+
meth = iface.methods[member_sym]
|
55
|
+
if !meth
|
56
|
+
raise DBus.error("org.freedesktop.DBus.Error.UnknownMethod"),
|
57
|
+
"Method \"#{msg.member}\" on interface \"#{msg.interface}\" of object \"#{msg.path}\" doesn't exist"
|
58
|
+
end
|
59
|
+
methname = Object.make_method_name(msg.interface, msg.member)
|
60
|
+
retdata = method(methname).call(*msg.params)
|
61
|
+
retdata = [*retdata]
|
62
|
+
|
63
|
+
reply = Message.method_return(msg)
|
64
|
+
if iface.name == PROPERTY_INTERFACE && member_sym == :Get
|
65
|
+
# Use the specific property type instead of the generic variant
|
66
|
+
# returned by Get.
|
67
|
+
# TODO: GetAll and Set still missing
|
68
|
+
property = dbus_lookup_property(msg.params[0], msg.params[1])
|
69
|
+
rsigs = [property.type]
|
70
|
+
else
|
71
|
+
rsigs = meth.rets.map(&:type)
|
72
|
+
end
|
73
|
+
rsigs.zip(retdata).each do |rsig, rdata|
|
74
|
+
reply.add_param(rsig, rdata)
|
75
|
+
end
|
76
|
+
rescue StandardError => e
|
77
|
+
dbus_msg_exc = msg.annotate_exception(e)
|
78
|
+
reply = ErrorMessage.from_exception(dbus_msg_exc).reply_to(msg)
|
79
|
+
end
|
80
|
+
@service.bus.message_queue.push(reply)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Select (and create) the interface that the following defined methods
|
85
|
+
# belong to.
|
86
|
+
# @param name [String] interface name like "org.example.ManagerManager"
|
87
|
+
# @see https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-interface
|
88
|
+
def self.dbus_interface(name)
|
89
|
+
@@intfs_mutex.synchronize do
|
90
|
+
@@cur_intf = intfs[name]
|
91
|
+
if !@@cur_intf
|
92
|
+
@@cur_intf = Interface.new(name) # validates the name
|
93
|
+
# As this is a mutable class_attr, we cannot use
|
94
|
+
# self.intfs[name] = @@cur_intf # Hash#[]=
|
95
|
+
# as that would modify parent class attr in place.
|
96
|
+
# Using the setter lets a subclass have the new value
|
97
|
+
# while the superclass keeps the old one.
|
98
|
+
self.intfs = intfs.merge(name => @@cur_intf)
|
99
|
+
end
|
100
|
+
yield
|
101
|
+
@@cur_intf = nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Forgetting to declare the interface for a method/signal/property
|
106
|
+
# is a ScriptError.
|
107
|
+
class UndefinedInterface < ScriptError # rubocop:disable Lint/InheritException
|
108
|
+
def initialize(sym)
|
109
|
+
super "No interface specified for #{sym}. Enclose it in dbus_interface."
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# A read-write property accessing an instance variable.
|
114
|
+
# A combination of `attr_accessor` and {.dbus_accessor}.
|
115
|
+
#
|
116
|
+
# PropertiesChanged signal will be emitted whenever `foo_bar=` is used
|
117
|
+
# but not when @foo_bar is written directly.
|
118
|
+
#
|
119
|
+
# @param ruby_name [Symbol] :foo_bar is exposed as FooBar;
|
120
|
+
# use dbus_name to override
|
121
|
+
# @param type a signature like "s" or "a(uus)" or Type::STRING
|
122
|
+
# @param dbus_name [String] if not given it is made
|
123
|
+
# by CamelCasing the ruby_name. foo_bar becomes FooBar
|
124
|
+
# to convert the Ruby convention to the DBus convention.
|
125
|
+
# @return [void]
|
126
|
+
def self.dbus_attr_accessor(ruby_name, type, dbus_name: nil)
|
127
|
+
attr_accessor(ruby_name)
|
128
|
+
|
129
|
+
dbus_accessor(ruby_name, type, dbus_name: dbus_name)
|
130
|
+
end
|
131
|
+
|
132
|
+
# A read-only property accessing an instance variable.
|
133
|
+
# A combination of `attr_reader` and {.dbus_reader}.
|
134
|
+
#
|
135
|
+
# Whenever the property value gets changed from "inside" the object,
|
136
|
+
# you should emit the `PropertiesChanged` signal by calling
|
137
|
+
#
|
138
|
+
# object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {dbus_name.to_s => value}, [])
|
139
|
+
#
|
140
|
+
# or, omitting the value in the signal,
|
141
|
+
#
|
142
|
+
# object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {}, [dbus_name.to_s])
|
143
|
+
#
|
144
|
+
# @param (see .dbus_attr_accessor)
|
145
|
+
# @return (see .dbus_attr_accessor)
|
146
|
+
def self.dbus_attr_reader(ruby_name, type, dbus_name: nil)
|
147
|
+
attr_reader(ruby_name)
|
148
|
+
|
149
|
+
dbus_reader(ruby_name, type, dbus_name: dbus_name)
|
150
|
+
end
|
151
|
+
|
152
|
+
# A write-only property accessing an instance variable.
|
153
|
+
# A combination of `attr_writer` and {.dbus_writer}.
|
154
|
+
#
|
155
|
+
# @param (see .dbus_attr_accessor)
|
156
|
+
# @return (see .dbus_attr_accessor)
|
157
|
+
def self.dbus_attr_writer(ruby_name, type, dbus_name: nil)
|
158
|
+
attr_writer(ruby_name)
|
159
|
+
|
160
|
+
dbus_writer(ruby_name, type, dbus_name: dbus_name)
|
161
|
+
end
|
162
|
+
|
163
|
+
# A read-write property using a pair of reader/writer methods
|
164
|
+
# (which must already exist).
|
165
|
+
# (To directly access an instance variable, use {.dbus_attr_accessor} instead)
|
166
|
+
#
|
167
|
+
# Uses {.dbus_watcher} to set up the PropertiesChanged signal.
|
168
|
+
#
|
169
|
+
# @param (see .dbus_attr_accessor)
|
170
|
+
# @return (see .dbus_attr_accessor)
|
171
|
+
def self.dbus_accessor(ruby_name, type, dbus_name: nil)
|
172
|
+
raise UndefinedInterface, ruby_name if @@cur_intf.nil?
|
173
|
+
|
174
|
+
dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
|
175
|
+
property = Property.new(dbus_name, type, :readwrite, ruby_name: ruby_name)
|
176
|
+
@@cur_intf.define(property)
|
177
|
+
|
178
|
+
dbus_watcher(ruby_name, dbus_name: dbus_name)
|
179
|
+
end
|
180
|
+
|
181
|
+
# A read-only property accessing a reader method (which must already exist).
|
182
|
+
# (To directly access an instance variable, use {.dbus_attr_reader} instead)
|
183
|
+
#
|
184
|
+
# Whenever the property value gets changed from "inside" the object,
|
185
|
+
# you should emit the `PropertiesChanged` signal by calling
|
186
|
+
#
|
187
|
+
# object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {dbus_name.to_s => value}, [])
|
188
|
+
#
|
189
|
+
# or, omitting the value in the signal,
|
190
|
+
#
|
191
|
+
# object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {}, [dbus_name.to_s])
|
192
|
+
#
|
193
|
+
# @param (see .dbus_attr_accessor)
|
194
|
+
# @return (see .dbus_attr_accessor)
|
195
|
+
def self.dbus_reader(ruby_name, type, dbus_name: nil)
|
196
|
+
raise UndefinedInterface, ruby_name if @@cur_intf.nil?
|
197
|
+
|
198
|
+
dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
|
199
|
+
property = Property.new(dbus_name, type, :read, ruby_name: ruby_name)
|
200
|
+
@@cur_intf.define(property)
|
201
|
+
end
|
202
|
+
|
203
|
+
# A write-only property accessing a writer method (which must already exist).
|
204
|
+
# (To directly access an instance variable, use {.dbus_attr_writer} instead)
|
205
|
+
#
|
206
|
+
# Uses {.dbus_watcher} to set up the PropertiesChanged signal.
|
207
|
+
#
|
208
|
+
# @param (see .dbus_attr_accessor)
|
209
|
+
# @return (see .dbus_attr_accessor)
|
210
|
+
def self.dbus_writer(ruby_name, type, dbus_name: 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, :write, ruby_name: ruby_name)
|
215
|
+
@@cur_intf.define(property)
|
216
|
+
|
217
|
+
dbus_watcher(ruby_name, dbus_name: dbus_name)
|
218
|
+
end
|
219
|
+
|
220
|
+
# Enables automatic sending of the PropertiesChanged signal.
|
221
|
+
# For *ruby_name* `foo_bar`, wrap `foo_bar=` so that it sends
|
222
|
+
# the signal for FooBar.
|
223
|
+
# The original version remains as `_original_foo_bar=`.
|
224
|
+
#
|
225
|
+
# @param ruby_name [Symbol] :foo_bar and :foo_bar= both mean the same thing
|
226
|
+
# @param dbus_name [String] if not given it is made
|
227
|
+
# by CamelCasing the ruby_name. foo_bar becomes FooBar
|
228
|
+
# to convert the Ruby convention to the DBus convention.
|
229
|
+
# @return [void]
|
230
|
+
def self.dbus_watcher(ruby_name, dbus_name: nil)
|
231
|
+
raise UndefinedInterface, ruby_name if @@cur_intf.nil?
|
232
|
+
|
233
|
+
cur_intf = @@cur_intf
|
234
|
+
|
235
|
+
ruby_name = ruby_name.to_s.sub(/=$/, "").to_sym
|
236
|
+
ruby_name_eq = "#{ruby_name}=".to_sym
|
237
|
+
original_ruby_name_eq = "_original_#{ruby_name_eq}"
|
238
|
+
|
239
|
+
dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
|
240
|
+
|
241
|
+
# the argument order is alias_method(new_name, existing_name)
|
242
|
+
alias_method original_ruby_name_eq, ruby_name_eq
|
243
|
+
define_method ruby_name_eq do |value|
|
244
|
+
public_send(original_ruby_name_eq, value)
|
245
|
+
|
246
|
+
# TODO: respect EmitsChangedSignal to use invalidated_properties instead
|
247
|
+
# PropertiesChanged, "interface:s, changed_properties:a{sv}, invalidated_properties:as"
|
248
|
+
PropertiesChanged(cur_intf.name, { dbus_name.to_s => value }, [])
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# Defines an exportable method on the object with the given name _sym_,
|
253
|
+
# _prototype_ and the code in a block.
|
254
|
+
# @param prototype [Prototype]
|
255
|
+
def self.dbus_method(sym, prototype = "", &block)
|
256
|
+
raise UndefinedInterface, sym if @@cur_intf.nil?
|
257
|
+
|
258
|
+
@@cur_intf.define(Method.new(sym.to_s).from_prototype(prototype))
|
259
|
+
|
260
|
+
ruby_name = Object.make_method_name(@@cur_intf.name, sym.to_s)
|
261
|
+
# ::Module#define_method(name) { body }
|
262
|
+
define_method(ruby_name, &block)
|
263
|
+
end
|
264
|
+
|
265
|
+
# Emits a signal from the object with the given _interface_, signal
|
266
|
+
# _sig_ and arguments _args_.
|
267
|
+
# @param intf [Interface]
|
268
|
+
# @param sig [Signal]
|
269
|
+
# @param args arguments for the signal
|
270
|
+
def emit(intf, sig, *args)
|
271
|
+
@service.bus.emit(@service, self, intf, sig, *args)
|
272
|
+
end
|
273
|
+
|
274
|
+
# Defines a signal for the object with a given name _sym_ and _prototype_.
|
275
|
+
def self.dbus_signal(sym, prototype = "")
|
276
|
+
raise UndefinedInterface, sym if @@cur_intf.nil?
|
277
|
+
|
278
|
+
cur_intf = @@cur_intf
|
279
|
+
signal = Signal.new(sym.to_s).from_prototype(prototype)
|
280
|
+
cur_intf.define(Signal.new(sym.to_s).from_prototype(prototype))
|
281
|
+
|
282
|
+
# ::Module#define_method(name) { body }
|
283
|
+
define_method(sym.to_s) do |*args|
|
284
|
+
emit(cur_intf, signal, *args)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Helper method that returns a method name generated from the interface
|
289
|
+
# name _intfname_ and method name _methname_.
|
290
|
+
# @api private
|
291
|
+
def self.make_method_name(intfname, methname)
|
292
|
+
"#{intfname}%%#{methname}"
|
293
|
+
end
|
294
|
+
|
295
|
+
# TODO: borrow a proven implementation
|
296
|
+
# @param str [String]
|
297
|
+
# @return [String]
|
298
|
+
# @api private
|
299
|
+
def self.camelize(str)
|
300
|
+
str.split(/_/).map(&:capitalize).join("")
|
301
|
+
end
|
302
|
+
|
303
|
+
# Make a D-Bus conventional name, CamelCased.
|
304
|
+
# @param ruby_name [String,Symbol] eg :do_something
|
305
|
+
# @param dbus_name [String,Symbol,nil] use this if given
|
306
|
+
# @return [Symbol] eg DoSomething
|
307
|
+
def self.make_dbus_name(ruby_name, dbus_name: nil)
|
308
|
+
dbus_name ||= camelize(ruby_name.to_s)
|
309
|
+
dbus_name.to_sym
|
310
|
+
end
|
311
|
+
|
312
|
+
# @param interface_name [String]
|
313
|
+
# @param property_name [String]
|
314
|
+
# @return [Property]
|
315
|
+
# @raise [DBus::Error]
|
316
|
+
# @api private
|
317
|
+
def dbus_lookup_property(interface_name, property_name)
|
318
|
+
# what should happen for unknown properties
|
319
|
+
# plasma: InvalidArgs (propname), UnknownInterface (interface)
|
320
|
+
# systemd: UnknownProperty
|
321
|
+
interface = intfs[interface_name]
|
322
|
+
if !interface
|
323
|
+
raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"),
|
324
|
+
"Property '#{interface_name}.#{property_name}' (on object '#{@path}') not found: no such interface"
|
325
|
+
end
|
326
|
+
|
327
|
+
property = interface.properties[property_name.to_sym]
|
328
|
+
if !property
|
329
|
+
raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"),
|
330
|
+
"Property '#{interface_name}.#{property_name}' (on object '#{@path}') not found"
|
331
|
+
end
|
332
|
+
|
333
|
+
property
|
334
|
+
end
|
335
|
+
|
336
|
+
####################################################################
|
337
|
+
|
338
|
+
# use the above defined methods to declare the property-handling
|
339
|
+
# interfaces and methods
|
340
|
+
|
341
|
+
dbus_interface PROPERTY_INTERFACE do
|
342
|
+
dbus_method :Get, "in interface_name:s, in property_name:s, out value:v" do |interface_name, property_name|
|
343
|
+
property = dbus_lookup_property(interface_name, property_name)
|
344
|
+
|
345
|
+
if property.readable?
|
346
|
+
ruby_name = property.ruby_name
|
347
|
+
value = public_send(ruby_name)
|
348
|
+
[value]
|
349
|
+
else
|
350
|
+
raise DBus.error("org.freedesktop.DBus.Error.PropertyWriteOnly"),
|
351
|
+
"Property '#{interface_name}.#{property_name}' (on object '#{@path}') is not readable"
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
dbus_method :Set, "in interface_name:s, in property_name:s, in val:v" do |interface_name, property_name, value|
|
356
|
+
property = dbus_lookup_property(interface_name, property_name)
|
357
|
+
|
358
|
+
if property.writable?
|
359
|
+
ruby_name_eq = "#{property.ruby_name}="
|
360
|
+
public_send(ruby_name_eq, value)
|
361
|
+
else
|
362
|
+
raise DBus.error("org.freedesktop.DBus.Error.PropertyReadOnly"),
|
363
|
+
"Property '#{interface_name}.#{property_name}' (on object '#{@path}') is not writable"
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
dbus_method :GetAll, "in interface_name:s, out value:a{sv}" do |interface_name|
|
368
|
+
interface = intfs[interface_name]
|
369
|
+
if !interface
|
370
|
+
raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"),
|
371
|
+
"Properties '#{interface_name}.*' (on object '#{@path}') not found: no such interface"
|
372
|
+
end
|
373
|
+
|
374
|
+
p_hash = {}
|
375
|
+
interface.properties.each do |p_name, property|
|
376
|
+
next unless property.readable?
|
377
|
+
|
378
|
+
ruby_name = property.ruby_name
|
379
|
+
begin
|
380
|
+
# D-Bus spec says:
|
381
|
+
# > If GetAll is called with a valid interface name for which some
|
382
|
+
# > properties are not accessible to the caller (for example, due
|
383
|
+
# > to per-property access control implemented in the service),
|
384
|
+
# > those properties should be silently omitted from the result
|
385
|
+
# > array.
|
386
|
+
# so we will silently omit properties that fail to read.
|
387
|
+
# Get'ting them individually will send DBus.Error
|
388
|
+
p_hash[p_name.to_s] = public_send(ruby_name)
|
389
|
+
rescue StandardError
|
390
|
+
DBus.logger.debug "Property '#{interface_name}.#{p_name}' (on object '#{@path}')" \
|
391
|
+
" has raised during GetAll, omitting it"
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
[p_hash]
|
396
|
+
end
|
397
|
+
|
398
|
+
dbus_signal :PropertiesChanged, "interface:s, changed_properties:a{sv}, invalidated_properties:as"
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of the ruby-dbus project
|
4
|
+
# Copyright (C) 2019 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
|
+
# A {::String} that validates at initialization time
|
13
|
+
# @see https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-object-path
|
14
|
+
class ObjectPath < String
|
15
|
+
# @raise Error if not a valid object path
|
16
|
+
def initialize(str)
|
17
|
+
unless self.class.valid?(str)
|
18
|
+
raise DBus::Error, "Invalid object path #{str.inspect}"
|
19
|
+
end
|
20
|
+
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.valid?(str)
|
25
|
+
str == "/" || str =~ %r{\A(/[A-Za-z0-9_]+)+\z}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
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
|
@@ -36,7 +38,7 @@ module DBus
|
|
36
38
|
def initialize(bus, dest, path, api: ApiOptions::CURRENT)
|
37
39
|
@bus = bus
|
38
40
|
@destination = dest
|
39
|
-
@path = path
|
41
|
+
@path = ObjectPath.new(path)
|
40
42
|
@introspected = false
|
41
43
|
@interfaces = {}
|
42
44
|
@subnodes = []
|
@@ -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,19 @@ 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, pobj.subnodes = IntrospectXMLParser.new(xml).parse
|
29
33
|
intfs.each do |i|
|
30
|
-
poi = ProxyObjectInterface.new(
|
34
|
+
poi = ProxyObjectInterface.new(pobj, i.name)
|
31
35
|
i.methods.each_value { |m| poi.define(m) }
|
32
36
|
i.signals.each_value { |s| poi.define(s) }
|
33
|
-
|
37
|
+
pobj[i.name] = poi
|
34
38
|
end
|
35
|
-
|
39
|
+
pobj.introspected = true
|
36
40
|
end
|
37
41
|
|
38
42
|
# Generates, sets up and returns the proxy object.
|
@@ -41,5 +45,5 @@ module DBus
|
|
41
45
|
ProxyObjectFactory.introspect_into(po, @xml)
|
42
46
|
po
|
43
47
|
end
|
44
|
-
end
|
48
|
+
end
|
45
49
|
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
|
@@ -36,27 +38,28 @@ module DBus
|
|
36
38
|
@name
|
37
39
|
end
|
38
40
|
|
39
|
-
# Defines a method on the interface from the Method descriptor
|
40
|
-
|
41
|
-
|
41
|
+
# Defines a method on the interface from the Method descriptor _method_.
|
42
|
+
# @param method [Method]
|
43
|
+
def define_method_from_descriptor(method)
|
44
|
+
method.params.each do |fpar|
|
42
45
|
par = fpar.type
|
43
46
|
# This is the signature validity check
|
44
47
|
Type::Parser.new(par).parse
|
45
48
|
end
|
46
49
|
|
47
50
|
singleton_class.class_eval do
|
48
|
-
define_method
|
49
|
-
if
|
50
|
-
raise ArgumentError, "wrong number of arguments (#{args.size} for #{
|
51
|
+
define_method method.name do |*args, &reply_handler|
|
52
|
+
if method.params.size != args.size
|
53
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for #{method.params.size})"
|
51
54
|
end
|
52
55
|
|
53
56
|
msg = Message.new(Message::METHOD_CALL)
|
54
57
|
msg.path = @object.path
|
55
58
|
msg.interface = @name
|
56
59
|
msg.destination = @object.destination
|
57
|
-
msg.member =
|
60
|
+
msg.member = method.name
|
58
61
|
msg.sender = @object.bus.unique_name
|
59
|
-
|
62
|
+
method.params.each do |fpar|
|
60
63
|
par = fpar.type
|
61
64
|
msg.add_param(par, args.shift)
|
62
65
|
end
|
@@ -64,25 +67,27 @@ module DBus
|
|
64
67
|
if ret.nil? || @object.api.proxy_method_returns_array
|
65
68
|
ret
|
66
69
|
else
|
67
|
-
|
70
|
+
method.rets.size == 1 ? ret.first : ret
|
68
71
|
end
|
69
72
|
end
|
70
73
|
end
|
71
74
|
|
72
|
-
@methods[
|
75
|
+
@methods[method.name] = method
|
73
76
|
end
|
74
77
|
|
75
|
-
# Defines a signal from the descriptor
|
76
|
-
|
77
|
-
|
78
|
+
# Defines a signal from the descriptor _sig_.
|
79
|
+
# @param sig [Signal]
|
80
|
+
def define_signal_from_descriptor(sig)
|
81
|
+
@signals[sig.name] = sig
|
78
82
|
end
|
79
83
|
|
80
|
-
# Defines a signal or method based on the descriptor
|
81
|
-
def define(
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
84
|
+
# Defines a signal or method based on the descriptor _ifc_el_.
|
85
|
+
def define(ifc_el)
|
86
|
+
case ifc_el
|
87
|
+
when Method
|
88
|
+
define_method_from_descriptor(ifc_el)
|
89
|
+
when Signal
|
90
|
+
define_signal_from_descriptor(ifc_el)
|
86
91
|
end
|
87
92
|
end
|
88
93
|
|
@@ -109,7 +114,7 @@ module DBus
|
|
109
114
|
end
|
110
115
|
end
|
111
116
|
|
112
|
-
PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"
|
117
|
+
PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"
|
113
118
|
|
114
119
|
# Read a property.
|
115
120
|
# @param propname [String]
|
@@ -141,5 +146,5 @@ module DBus
|
|
141
146
|
ret
|
142
147
|
end
|
143
148
|
end
|
144
|
-
end
|
149
|
+
end
|
145
150
|
end
|