ruby-dbus 0.14.0 → 0.17.0
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 +5 -5
- data/NEWS.md +44 -0
- data/README.md +3 -5
- data/Rakefile +26 -8
- data/VERSION +1 -1
- data/doc/Reference.md +84 -1
- data/examples/doc/_extract_examples +5 -0
- data/examples/gdbus/gdbus +21 -20
- data/examples/service/call_service.rb +1 -1
- data/lib/dbus/auth.rb +8 -8
- data/lib/dbus/bus.rb +23 -11
- data/lib/dbus/bus_name.rb +27 -0
- data/lib/dbus/core_ext/class/attribute.rb +23 -41
- data/lib/dbus/core_ext/module/redefine_method.rb +51 -0
- data/lib/dbus/introspect.rb +71 -26
- data/lib/dbus/marshall.rb +2 -1
- data/lib/dbus/message_queue.rb +17 -14
- data/lib/dbus/object.rb +380 -0
- data/lib/dbus/object_path.rb +24 -0
- data/lib/dbus/proxy_object.rb +11 -2
- data/lib/dbus/proxy_object_interface.rb +1 -0
- data/lib/dbus/type.rb +18 -0
- data/lib/dbus/xml.rb +4 -8
- data/lib/dbus.rb +3 -2
- data/ruby-dbus.gemspec +11 -9
- data/spec/binding_spec.rb +6 -2
- data/spec/bus_name_spec.rb +25 -0
- data/spec/client_robustness_spec.rb +25 -0
- data/spec/introspect_xml_parser_spec.rb +13 -13
- data/spec/main_loop_spec.rb +1 -1
- data/spec/object_path_spec.rb +23 -0
- data/spec/property_spec.rb +53 -3
- data/spec/proxy_object_spec.rb +9 -0
- data/spec/service_newapi.rb +20 -66
- data/spec/session_bus_spec.rb +6 -6
- data/spec/signal_spec.rb +33 -18
- data/spec/spec_helper.rb +23 -11
- data/spec/tools/dbus-limited-session.conf +4 -0
- data/spec/type_spec.rb +2 -2
- metadata +32 -15
- data/lib/dbus/core_ext/array/extract_options.rb +0 -31
- data/lib/dbus/core_ext/kernel/singleton_class.rb +0 -8
- data/lib/dbus/core_ext/module/remove_method.rb +0 -14
- data/lib/dbus/export.rb +0 -130
data/lib/dbus/object.rb
ADDED
@@ -0,0 +1,380 @@
|
|
1
|
+
# This file is part of the ruby-dbus project
|
2
|
+
# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
|
3
|
+
#
|
4
|
+
# This library is free software; you can redistribute it and/or
|
5
|
+
# modify it under the terms of the GNU Lesser General Public
|
6
|
+
# License, version 2.1 as published by the Free Software Foundation.
|
7
|
+
# See the file "COPYING" for the exact licensing terms.
|
8
|
+
|
9
|
+
require "thread"
|
10
|
+
require_relative "core_ext/class/attribute"
|
11
|
+
|
12
|
+
module DBus
|
13
|
+
PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties".freeze
|
14
|
+
|
15
|
+
# Exported object type
|
16
|
+
# = Exportable D-Bus object class
|
17
|
+
#
|
18
|
+
# Objects that are going to be exported by a D-Bus service
|
19
|
+
# should inherit from this class. At the client side, use {ProxyObject}.
|
20
|
+
class Object
|
21
|
+
# The path of the object.
|
22
|
+
attr_reader :path
|
23
|
+
# The interfaces that the object supports. Hash: String => Interface
|
24
|
+
my_class_attribute :intfs
|
25
|
+
self.intfs = {}
|
26
|
+
|
27
|
+
# The service that the object is exported by.
|
28
|
+
attr_writer :service
|
29
|
+
|
30
|
+
@@cur_intf = nil # Interface
|
31
|
+
@@intfs_mutex = Mutex.new
|
32
|
+
|
33
|
+
# Create a new object with a given _path_.
|
34
|
+
# Use Service#export to export it.
|
35
|
+
def initialize(path)
|
36
|
+
@path = path
|
37
|
+
@service = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# Dispatch a message _msg_ to call exported methods
|
41
|
+
def dispatch(msg)
|
42
|
+
case msg.message_type
|
43
|
+
when Message::METHOD_CALL
|
44
|
+
reply = nil
|
45
|
+
begin
|
46
|
+
if !intfs[msg.interface]
|
47
|
+
raise DBus.error("org.freedesktop.DBus.Error.UnknownMethod"),
|
48
|
+
"Interface \"#{msg.interface}\" of object \"#{msg.path}\" doesn't exist"
|
49
|
+
end
|
50
|
+
meth = intfs[msg.interface].methods[msg.member.to_sym]
|
51
|
+
if !meth
|
52
|
+
raise DBus.error("org.freedesktop.DBus.Error.UnknownMethod"),
|
53
|
+
"Method \"#{msg.member}\" on interface \"#{msg.interface}\" of object \"#{msg.path}\" doesn't exist"
|
54
|
+
end
|
55
|
+
methname = Object.make_method_name(msg.interface, msg.member)
|
56
|
+
retdata = method(methname).call(*msg.params)
|
57
|
+
retdata = [*retdata]
|
58
|
+
|
59
|
+
reply = Message.method_return(msg)
|
60
|
+
meth.rets.zip(retdata).each do |rsig, rdata|
|
61
|
+
reply.add_param(rsig.type, rdata)
|
62
|
+
end
|
63
|
+
rescue StandardError => ex
|
64
|
+
dbus_msg_exc = msg.annotate_exception(ex)
|
65
|
+
reply = ErrorMessage.from_exception(dbus_msg_exc).reply_to(msg)
|
66
|
+
end
|
67
|
+
@service.bus.message_queue.push(reply)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Select (and create) the interface that the following defined methods
|
72
|
+
# belong to.
|
73
|
+
def self.dbus_interface(s)
|
74
|
+
@@intfs_mutex.synchronize do
|
75
|
+
@@cur_intf = intfs[s]
|
76
|
+
if !@@cur_intf
|
77
|
+
@@cur_intf = Interface.new(s)
|
78
|
+
# As this is a mutable class_attr, we cannot use
|
79
|
+
# self.intfs[s] = @@cur_intf # Hash#[]=
|
80
|
+
# as that would modify parent class attr in place.
|
81
|
+
# Using the setter lets a subclass have the new value
|
82
|
+
# while the superclass keeps the old one.
|
83
|
+
self.intfs = intfs.merge(s => @@cur_intf)
|
84
|
+
end
|
85
|
+
yield
|
86
|
+
@@cur_intf = nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Forgetting to declare the interface for a method/signal/property
|
91
|
+
# is a ScriptError.
|
92
|
+
class UndefinedInterface < ScriptError # rubocop:disable Lint/InheritException
|
93
|
+
def initialize(sym)
|
94
|
+
super "No interface specified for #{sym}. Enclose it in dbus_interface."
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# A read-write property accessing an instance variable.
|
99
|
+
# A combination of `attr_accessor` and {.dbus_accessor}.
|
100
|
+
#
|
101
|
+
# PropertiesChanged signal will be emitted whenever `foo_bar=` is used
|
102
|
+
# but not when @foo_bar is written directly.
|
103
|
+
#
|
104
|
+
# @param ruby_name [Symbol] :foo_bar is exposed as FooBar;
|
105
|
+
# use dbus_name to override
|
106
|
+
# @param type a signature like "s" or "a(uus)" or Type::STRING
|
107
|
+
# @param dbus_name [String] if not given it is made
|
108
|
+
# by CamelCasing the ruby_name. foo_bar becomes FooBar
|
109
|
+
# to convert the Ruby convention to the DBus convention.
|
110
|
+
# @return [void]
|
111
|
+
def self.dbus_attr_accessor(ruby_name, type, dbus_name: nil)
|
112
|
+
attr_accessor(ruby_name)
|
113
|
+
dbus_accessor(ruby_name, type, dbus_name: dbus_name)
|
114
|
+
end
|
115
|
+
|
116
|
+
# A read-only property accessing an instance variable.
|
117
|
+
# A combination of `attr_reader` and {.dbus_reader}.
|
118
|
+
#
|
119
|
+
# Whenever the property value gets changed from "inside" the object,
|
120
|
+
# you should emit the `PropertiesChanged` signal by calling
|
121
|
+
#
|
122
|
+
# object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {dbus_name.to_s => value}, [])
|
123
|
+
#
|
124
|
+
# or, omitting the value in the signal,
|
125
|
+
#
|
126
|
+
# object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {}, [dbus_name.to_s])
|
127
|
+
#
|
128
|
+
# @param (see .dbus_attr_accessor)
|
129
|
+
# @return (see .dbus_attr_accessor)
|
130
|
+
def self.dbus_attr_reader(ruby_name, type, dbus_name: nil)
|
131
|
+
attr_reader(ruby_name)
|
132
|
+
dbus_reader(ruby_name, type, dbus_name: dbus_name)
|
133
|
+
end
|
134
|
+
|
135
|
+
# A write-only property accessing an instance variable.
|
136
|
+
# A combination of `attr_writer` and {.dbus_writer}.
|
137
|
+
#
|
138
|
+
# @param (see .dbus_attr_accessor)
|
139
|
+
# @return (see .dbus_attr_accessor)
|
140
|
+
def self.dbus_attr_writer(ruby_name, type, dbus_name: nil)
|
141
|
+
attr_writer(ruby_name)
|
142
|
+
dbus_writer(ruby_name, type, dbus_name: dbus_name)
|
143
|
+
end
|
144
|
+
|
145
|
+
# A read-write property using a pair of reader/writer methods
|
146
|
+
# (which must already exist).
|
147
|
+
# (To directly access an instance variable, use {.dbus_attr_accessor} instead)
|
148
|
+
#
|
149
|
+
# Uses {.dbus_watcher} to set up the PropertiesChanged signal.
|
150
|
+
#
|
151
|
+
# @param (see .dbus_attr_accessor)
|
152
|
+
# @return (see .dbus_attr_accessor)
|
153
|
+
def self.dbus_accessor(ruby_name, type, dbus_name: nil)
|
154
|
+
raise UndefinedInterface, ruby_name if @@cur_intf.nil?
|
155
|
+
|
156
|
+
dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
|
157
|
+
property = Property.new(dbus_name, type, :readwrite, ruby_name: ruby_name)
|
158
|
+
@@cur_intf.define(property)
|
159
|
+
|
160
|
+
dbus_watcher(ruby_name, dbus_name: dbus_name)
|
161
|
+
end
|
162
|
+
|
163
|
+
# A read-only property accessing a reader method (which must already exist).
|
164
|
+
# (To directly access an instance variable, use {.dbus_attr_reader} instead)
|
165
|
+
#
|
166
|
+
# Whenever the property value gets changed from "inside" the object,
|
167
|
+
# you should emit the `PropertiesChanged` signal by calling
|
168
|
+
#
|
169
|
+
# object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {dbus_name.to_s => value}, [])
|
170
|
+
#
|
171
|
+
# or, omitting the value in the signal,
|
172
|
+
#
|
173
|
+
# object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {}, [dbus_name.to_s])
|
174
|
+
#
|
175
|
+
# @param (see .dbus_attr_accessor)
|
176
|
+
# @return (see .dbus_attr_accessor)
|
177
|
+
def self.dbus_reader(ruby_name, type, dbus_name: nil)
|
178
|
+
raise UndefinedInterface, ruby_name if @@cur_intf.nil?
|
179
|
+
|
180
|
+
dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
|
181
|
+
property = Property.new(dbus_name, type, :read, ruby_name: ruby_name)
|
182
|
+
@@cur_intf.define(property)
|
183
|
+
end
|
184
|
+
|
185
|
+
# A write-only property accessing a writer method (which must already exist).
|
186
|
+
# (To directly access an instance variable, use {.dbus_attr_writer} instead)
|
187
|
+
#
|
188
|
+
# Uses {.dbus_watcher} to set up the PropertiesChanged signal.
|
189
|
+
#
|
190
|
+
# @param (see .dbus_attr_accessor)
|
191
|
+
# @return (see .dbus_attr_accessor)
|
192
|
+
def self.dbus_writer(ruby_name, type, dbus_name: nil)
|
193
|
+
raise UndefinedInterface, ruby_name if @@cur_intf.nil?
|
194
|
+
|
195
|
+
dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
|
196
|
+
property = Property.new(dbus_name, type, :write, ruby_name: ruby_name)
|
197
|
+
@@cur_intf.define(property)
|
198
|
+
|
199
|
+
dbus_watcher(ruby_name, dbus_name: dbus_name)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Enables automatic sending of the PropertiesChanged signal.
|
203
|
+
# For *ruby_name* `foo_bar`, wrap `foo_bar=` so that it sends
|
204
|
+
# the signal for FooBar.
|
205
|
+
# The original version remains as `_original_foo_bar=`.
|
206
|
+
#
|
207
|
+
# @param ruby_name [Symbol] :foo_bar and :foo_bar= both mean the same thing
|
208
|
+
# @param dbus_name [String] if not given it is made
|
209
|
+
# by CamelCasing the ruby_name. foo_bar becomes FooBar
|
210
|
+
# to convert the Ruby convention to the DBus convention.
|
211
|
+
# @return [void]
|
212
|
+
def self.dbus_watcher(ruby_name, dbus_name: nil)
|
213
|
+
raise UndefinedInterface, ruby_name if @@cur_intf.nil?
|
214
|
+
cur_intf = @@cur_intf
|
215
|
+
|
216
|
+
ruby_name = ruby_name.to_s.sub(/=$/, "").to_sym
|
217
|
+
ruby_name_eq = "#{ruby_name}=".to_sym
|
218
|
+
original_ruby_name_eq = "_original_#{ruby_name_eq}"
|
219
|
+
|
220
|
+
dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
|
221
|
+
|
222
|
+
# the argument order is alias_method(new_name, existing_name)
|
223
|
+
alias_method original_ruby_name_eq, ruby_name_eq
|
224
|
+
define_method ruby_name_eq do |value|
|
225
|
+
public_send(original_ruby_name_eq, value)
|
226
|
+
|
227
|
+
# TODO: respect EmitsChangedSignal to use invalidated_properties instead
|
228
|
+
# PropertiesChanged, "interface:s, changed_properties:a{sv}, invalidated_properties:as"
|
229
|
+
PropertiesChanged(cur_intf.name, { dbus_name.to_s => value }, [])
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Defines an exportable method on the object with the given name _sym_,
|
234
|
+
# _prototype_ and the code in a block.
|
235
|
+
# @param prototype [Prototype]
|
236
|
+
def self.dbus_method(sym, prototype = "", &block)
|
237
|
+
raise UndefinedInterface, sym if @@cur_intf.nil?
|
238
|
+
@@cur_intf.define(Method.new(sym.to_s).from_prototype(prototype))
|
239
|
+
|
240
|
+
ruby_name = Object.make_method_name(@@cur_intf.name, sym.to_s)
|
241
|
+
# ::Module#define_method(name) { body }
|
242
|
+
define_method(ruby_name, &block)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Emits a signal from the object with the given _interface_, signal
|
246
|
+
# _sig_ and arguments _args_.
|
247
|
+
# @param intf [Interface]
|
248
|
+
# @param sig [Signal]
|
249
|
+
# @param args arguments for the signal
|
250
|
+
def emit(intf, sig, *args)
|
251
|
+
@service.bus.emit(@service, self, intf, sig, *args)
|
252
|
+
end
|
253
|
+
|
254
|
+
# Defines a signal for the object with a given name _sym_ and _prototype_.
|
255
|
+
def self.dbus_signal(sym, prototype = "")
|
256
|
+
raise UndefinedInterface, sym if @@cur_intf.nil?
|
257
|
+
cur_intf = @@cur_intf
|
258
|
+
signal = Signal.new(sym.to_s).from_prototype(prototype)
|
259
|
+
cur_intf.define(Signal.new(sym.to_s).from_prototype(prototype))
|
260
|
+
|
261
|
+
# ::Module#define_method(name) { body }
|
262
|
+
define_method(sym.to_s) do |*args|
|
263
|
+
emit(cur_intf, signal, *args)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# Helper method that returns a method name generated from the interface
|
268
|
+
# name _intfname_ and method name _methname_.
|
269
|
+
# @api private
|
270
|
+
def self.make_method_name(intfname, methname)
|
271
|
+
"#{intfname}%%#{methname}"
|
272
|
+
end
|
273
|
+
|
274
|
+
# TODO: borrow a proven implementation
|
275
|
+
# @param str [String]
|
276
|
+
# @return [String]
|
277
|
+
# @api private
|
278
|
+
def self.camelize(str)
|
279
|
+
str.split(/_/).map(&:capitalize).join("")
|
280
|
+
end
|
281
|
+
|
282
|
+
# Make a D-Bus conventional name, CamelCased.
|
283
|
+
# @param ruby_name [String,Symbol] eg :do_something
|
284
|
+
# @param dbus_name [String,Symbol,nil] use this if given
|
285
|
+
# @return [Symbol] eg DoSomething
|
286
|
+
def self.make_dbus_name(ruby_name, dbus_name: nil)
|
287
|
+
dbus_name ||= camelize(ruby_name.to_s)
|
288
|
+
dbus_name.to_sym
|
289
|
+
end
|
290
|
+
|
291
|
+
# @param interface_name [String]
|
292
|
+
# @param property_name [String]
|
293
|
+
# @return [Property]
|
294
|
+
# @raise [DBus::Error]
|
295
|
+
# @api private
|
296
|
+
def dbus_lookup_property(interface_name, property_name)
|
297
|
+
# what should happen for unknown properties
|
298
|
+
# plasma: InvalidArgs (propname), UnknownInterface (interface)
|
299
|
+
# systemd: UnknownProperty
|
300
|
+
interface = intfs[interface_name]
|
301
|
+
if !interface
|
302
|
+
raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"),
|
303
|
+
"Property '#{interface_name}.#{property_name}' (on object '#{@path}') not found: no such interface"
|
304
|
+
end
|
305
|
+
|
306
|
+
property = interface.properties[property_name.to_sym]
|
307
|
+
if !property
|
308
|
+
raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"),
|
309
|
+
"Property '#{interface_name}.#{property_name}' (on object '#{@path}') not found"
|
310
|
+
end
|
311
|
+
|
312
|
+
property
|
313
|
+
end
|
314
|
+
|
315
|
+
####################################################################
|
316
|
+
|
317
|
+
# use the above defined methods to declare the property-handling
|
318
|
+
# interfaces and methods
|
319
|
+
|
320
|
+
dbus_interface PROPERTY_INTERFACE do
|
321
|
+
dbus_method :Get, "in interface_name:s, in property_name:s, out value:v" do |interface_name, property_name|
|
322
|
+
property = dbus_lookup_property(interface_name, property_name)
|
323
|
+
|
324
|
+
if property.readable?
|
325
|
+
ruby_name = property.ruby_name
|
326
|
+
value = public_send(ruby_name)
|
327
|
+
[value]
|
328
|
+
else
|
329
|
+
raise DBus.error("org.freedesktop.DBus.Error.PropertyWriteOnly"),
|
330
|
+
"Property '#{interface_name}.#{property_name}' (on object '#{@path}') is not readable"
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
dbus_method :Set, "in interface_name:s, in property_name:s, in val:v" do |interface_name, property_name, value|
|
335
|
+
property = dbus_lookup_property(interface_name, property_name)
|
336
|
+
|
337
|
+
if property.writable?
|
338
|
+
ruby_name_eq = "#{property.ruby_name}="
|
339
|
+
public_send(ruby_name_eq, value)
|
340
|
+
else
|
341
|
+
raise DBus.error("org.freedesktop.DBus.Error.PropertyReadOnly"),
|
342
|
+
"Property '#{interface_name}.#{property_name}' (on object '#{@path}') is not writable"
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
dbus_method :GetAll, "in interface_name:s, out value:a{sv}" do |interface_name|
|
347
|
+
interface = intfs[interface_name]
|
348
|
+
if !interface
|
349
|
+
raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"),
|
350
|
+
"Properties '#{interface_name}.*' (on object '#{@path}') not found: no such interface"
|
351
|
+
end
|
352
|
+
|
353
|
+
p_hash = {}
|
354
|
+
interface.properties.each do |p_name, property|
|
355
|
+
next unless property.readable?
|
356
|
+
|
357
|
+
ruby_name = property.ruby_name
|
358
|
+
begin
|
359
|
+
# D-Bus spec says:
|
360
|
+
# > If GetAll is called with a valid interface name for which some
|
361
|
+
# > properties are not accessible to the caller (for example, due
|
362
|
+
# > to per-property access control implemented in the service),
|
363
|
+
# > those properties should be silently omitted from the result
|
364
|
+
# > array.
|
365
|
+
# so we will silently omit properties that fail to read.
|
366
|
+
# Get'ting them individually will send DBus.Error
|
367
|
+
p_hash[p_name.to_s] = public_send(ruby_name)
|
368
|
+
rescue StandardError
|
369
|
+
DBus.logger.debug "Property '#{interface_name}.#{p_name}' (on object '#{@path}')" \
|
370
|
+
" has raised during GetAll, omitting it"
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
[p_hash]
|
375
|
+
end
|
376
|
+
|
377
|
+
dbus_signal :PropertiesChanged, "interface:s, changed_properties:a{sv}, invalidated_properties:as"
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# This file is part of the ruby-dbus project
|
2
|
+
# Copyright (C) 2019 Martin Vidner
|
3
|
+
#
|
4
|
+
# This library is free software; you can redistribute it and/or
|
5
|
+
# modify it under the terms of the GNU Lesser General Public
|
6
|
+
# License, version 2.1 as published by the Free Software Foundation.
|
7
|
+
# See the file "COPYING" for the exact licensing terms.
|
8
|
+
|
9
|
+
module DBus
|
10
|
+
# A {::String} that validates at initialization time
|
11
|
+
class ObjectPath < String
|
12
|
+
# @raise Error if not a valid object path
|
13
|
+
def initialize(s)
|
14
|
+
unless self.class.valid?(s)
|
15
|
+
raise DBus::Error, "Invalid object path #{s.inspect}"
|
16
|
+
end
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.valid?(s)
|
21
|
+
s == "/" || s =~ %r{\A(/[A-Za-z0-9_]+)+\z}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/dbus/proxy_object.rb
CHANGED
@@ -36,7 +36,7 @@ module DBus
|
|
36
36
|
def initialize(bus, dest, path, api: ApiOptions::CURRENT)
|
37
37
|
@bus = bus
|
38
38
|
@destination = dest
|
39
|
-
@path = path
|
39
|
+
@path = ObjectPath.new(path)
|
40
40
|
@introspected = false
|
41
41
|
@interfaces = {}
|
42
42
|
@subnodes = []
|
@@ -44,6 +44,7 @@ module DBus
|
|
44
44
|
end
|
45
45
|
|
46
46
|
# Returns the interfaces of the object.
|
47
|
+
# @return [Array<String>] names of the interfaces
|
47
48
|
def interfaces
|
48
49
|
introspect unless introspected
|
49
50
|
@interfaces.keys
|
@@ -54,7 +55,9 @@ module DBus
|
|
54
55
|
# @return [ProxyObjectInterface]
|
55
56
|
def [](intfname)
|
56
57
|
introspect unless introspected
|
57
|
-
@interfaces[intfname]
|
58
|
+
ifc = @interfaces[intfname]
|
59
|
+
raise DBus::Error, "no such interface `#{intfname}' on object `#{@path}'" unless ifc
|
60
|
+
ifc
|
58
61
|
end
|
59
62
|
|
60
63
|
# Maps the given interface name _intfname_ to the given interface _intf.
|
@@ -127,6 +130,11 @@ module DBus
|
|
127
130
|
####################################################
|
128
131
|
private
|
129
132
|
|
133
|
+
# rubocop:disable Style/MethodMissing
|
134
|
+
# but https://github.com/rubocop-hq/ruby-style-guide#no-method-missing
|
135
|
+
# and http://blog.marc-andre.ca/2010/11/15/methodmissing-politely/
|
136
|
+
# have a point to be investigated
|
137
|
+
|
130
138
|
# Handles all unkown methods, mostly to route method calls to the
|
131
139
|
# default interface.
|
132
140
|
def method_missing(name, *args, &reply_handler)
|
@@ -148,5 +156,6 @@ module DBus
|
|
148
156
|
raise NoMethodError, "undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
|
149
157
|
end
|
150
158
|
end
|
159
|
+
# rubocop:enable Style/MethodMissing
|
151
160
|
end # class ProxyObject
|
152
161
|
end
|
data/lib/dbus/type.rb
CHANGED
@@ -9,6 +9,24 @@
|
|
9
9
|
# See the file "COPYING" for the exact licensing terms.
|
10
10
|
|
11
11
|
module DBus
|
12
|
+
# Like a {Signature} but containing only a single complete type.
|
13
|
+
#
|
14
|
+
# For documentation purposes only.
|
15
|
+
class SingleCompleteType < String; end
|
16
|
+
|
17
|
+
# Zero or more {SingleCompleteType}s; its own type code is "g".
|
18
|
+
# For example "ssv" for a method taking two Strings and a Variant/
|
19
|
+
#
|
20
|
+
# For documentation purposes only.
|
21
|
+
class Signature < String; end
|
22
|
+
|
23
|
+
# Similar to {Signature} but for {DBus::Object.define_method},
|
24
|
+
# contains names and direction of the parameters.
|
25
|
+
# For example "in query:s, in case_sensitive:b, out results:ao".
|
26
|
+
#
|
27
|
+
# For documentation purposes only.
|
28
|
+
class Prototype < String; end
|
29
|
+
|
12
30
|
# = D-Bus type module
|
13
31
|
#
|
14
32
|
# This module containts the constants of the types specified in the D-Bus
|
data/lib/dbus/xml.rb
CHANGED
@@ -41,21 +41,17 @@ module DBus
|
|
41
41
|
|
42
42
|
# required methods
|
43
43
|
# returns node attribute value
|
44
|
-
def [](key)
|
45
|
-
end
|
44
|
+
def [](key); end
|
46
45
|
|
47
46
|
# yields child nodes which match xpath of type AbstractXML::Node
|
48
|
-
def each(xpath)
|
49
|
-
end
|
47
|
+
def each(xpath); end
|
50
48
|
end
|
51
49
|
# required methods
|
52
50
|
# initialize parser with xml string
|
53
|
-
def initialize(xml)
|
54
|
-
end
|
51
|
+
def initialize(xml); end
|
55
52
|
|
56
53
|
# yields nodes which match xpath of type AbstractXML::Node
|
57
|
-
def each(xpath)
|
58
|
-
end
|
54
|
+
def each(xpath); end
|
59
55
|
end
|
60
56
|
|
61
57
|
class NokogiriParser < AbstractXML
|
data/lib/dbus.rb
CHANGED
@@ -11,15 +11,16 @@
|
|
11
11
|
require_relative "dbus/api_options"
|
12
12
|
require_relative "dbus/auth"
|
13
13
|
require_relative "dbus/bus"
|
14
|
-
require_relative "dbus/
|
14
|
+
require_relative "dbus/bus_name"
|
15
15
|
require_relative "dbus/error"
|
16
|
-
require_relative "dbus/export"
|
17
16
|
require_relative "dbus/introspect"
|
18
17
|
require_relative "dbus/logger"
|
19
18
|
require_relative "dbus/marshall"
|
20
19
|
require_relative "dbus/matchrule"
|
21
20
|
require_relative "dbus/message"
|
22
21
|
require_relative "dbus/message_queue"
|
22
|
+
require_relative "dbus/object"
|
23
|
+
require_relative "dbus/object_path"
|
23
24
|
require_relative "dbus/proxy_object"
|
24
25
|
require_relative "dbus/proxy_object_factory"
|
25
26
|
require_relative "dbus/proxy_object_interface"
|
data/ruby-dbus.gemspec
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
require "rubygems"
|
3
|
-
require "rake"
|
4
3
|
|
5
4
|
GEMSPEC = Gem::Specification.new do |s|
|
6
5
|
s.name = "ruby-dbus"
|
@@ -8,25 +7,28 @@ GEMSPEC = Gem::Specification.new do |s|
|
|
8
7
|
s.summary = "Ruby module for interaction with D-Bus"
|
9
8
|
s.description = "Pure Ruby module for interaction with D-Bus IPC system"
|
10
9
|
s.version = File.read("VERSION").strip
|
11
|
-
s.license = "LGPL
|
10
|
+
s.license = "LGPL-2.1"
|
12
11
|
s.author = "Ruby DBus Team"
|
13
|
-
s.email = "
|
14
|
-
s.homepage = "https://
|
15
|
-
s.files =
|
12
|
+
s.email = "martin.github@vidner.net"
|
13
|
+
s.homepage = "https://github.com/mvidner/ruby-dbus"
|
14
|
+
s.files = Dir[
|
16
15
|
"{doc,examples,lib,spec}/**/*",
|
17
16
|
"COPYING", "NEWS.md", "Rakefile", "README.md",
|
18
|
-
"ruby-dbus.gemspec", "VERSION", ".rspec"
|
17
|
+
"ruby-dbus.gemspec", "VERSION", ".rspec"
|
18
|
+
]
|
19
19
|
s.require_path = "lib"
|
20
20
|
|
21
|
-
s.required_ruby_version = ">= 2.
|
21
|
+
s.required_ruby_version = ">= 2.1.0"
|
22
|
+
|
23
|
+
s.add_dependency "rexml"
|
22
24
|
|
23
25
|
# This is optional
|
24
26
|
# s.add_runtime_dependency "nokogiri"
|
25
27
|
|
26
|
-
s.add_development_dependency "coveralls"
|
27
28
|
s.add_development_dependency "packaging_rake_tasks"
|
28
29
|
s.add_development_dependency "rake"
|
29
30
|
s.add_development_dependency "rspec", "~> 3"
|
30
|
-
s.add_development_dependency "rubocop", "= 0.
|
31
|
+
s.add_development_dependency "rubocop", "= 0.50.0"
|
31
32
|
s.add_development_dependency "simplecov"
|
33
|
+
s.add_development_dependency "simplecov-lcov"
|
32
34
|
end
|
data/spec/binding_spec.rb
CHANGED
@@ -28,10 +28,14 @@ describe "BindingTest" do
|
|
28
28
|
# it should have its own interface
|
29
29
|
expect(test2["org.ruby.Test2"]).not_to be_nil
|
30
30
|
# but not an interface of the Test class
|
31
|
-
expect
|
31
|
+
expect { test2["org.ruby.SampleInterface"] }.to raise_error(DBus::Error) do |e|
|
32
|
+
expect(e.message).to match(/no such interface/)
|
33
|
+
end
|
32
34
|
|
33
35
|
# and the parent should not get polluted by the child
|
34
|
-
expect
|
36
|
+
expect { @base["org.ruby.Test2"] }.to raise_error(DBus::Error) do |e|
|
37
|
+
expect(e.message).to match(/no such interface/)
|
38
|
+
end
|
35
39
|
end
|
36
40
|
|
37
41
|
it "tests translating errors into exceptions" do
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
require_relative "spec_helper"
|
3
|
+
require "dbus"
|
4
|
+
|
5
|
+
describe DBus::BusName do
|
6
|
+
describe ".valid?" do
|
7
|
+
it "recognizes valid bus names" do
|
8
|
+
expect(described_class.valid?("org.freedesktop.DBus")).to be_truthy
|
9
|
+
expect(described_class.valid?(":1.42")).to be_truthy
|
10
|
+
expect(described_class.valid?("org._7_zip.Archiver")).to be_truthy
|
11
|
+
end
|
12
|
+
|
13
|
+
it "recognizes invalid bus names" do
|
14
|
+
expect(described_class.valid?("")).to be_falsey
|
15
|
+
expect(described_class.valid?("Empty..Component")).to be_falsey
|
16
|
+
expect(described_class.valid?(".Empty.First.Component")).to be_falsey
|
17
|
+
expect(described_class.valid?("Empty.Last.Component.")).to be_falsey
|
18
|
+
expect(described_class.valid?("Invalid.Ch@r@cter")).to be_falsey
|
19
|
+
expect(described_class.valid?("/Invalid-Character")).to be_falsey
|
20
|
+
long_name = "a." + ("long." * 100) + "name"
|
21
|
+
expect(described_class.valid?(long_name)).to be_falsey
|
22
|
+
expect(described_class.valid?("org.7_zip.Archiver")).to be_falsey
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
# Test that a client survives various error cases
|
3
|
+
require_relative "spec_helper"
|
4
|
+
require "dbus"
|
5
|
+
|
6
|
+
describe "ClientRobustnessTest" do
|
7
|
+
before(:each) do
|
8
|
+
@bus = DBus::ASessionBus.new
|
9
|
+
@svc = @bus.service("org.ruby.service")
|
10
|
+
end
|
11
|
+
|
12
|
+
context "when the bus name is invalid" do
|
13
|
+
it "tells the user the bus name is invalid" do
|
14
|
+
# user mistake, should be "org.ruby.service"
|
15
|
+
expect { @bus.service(".org.ruby.service") }.to raise_error(DBus::Error)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when the object path is invalid" do
|
20
|
+
it "tells the user the path is invalid" do
|
21
|
+
# user mistake, should be "/org/ruby/MyInstance"
|
22
|
+
expect { @svc.object("org.ruby.MyInstance") }.to raise_error(DBus::Error)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -4,19 +4,19 @@ require "dbus"
|
|
4
4
|
|
5
5
|
describe "IntrospectXMLParserTest" do
|
6
6
|
it "tests split interfaces" do
|
7
|
-
xml =
|
8
|
-
<node>
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
</node>
|
19
|
-
|
7
|
+
xml = <<-XML
|
8
|
+
<node>
|
9
|
+
<interface name="org.example.Foo">
|
10
|
+
<method name="Dwim"/>
|
11
|
+
</interface>
|
12
|
+
<interface name="org.example.Bar">
|
13
|
+
<method name="Drink"/>
|
14
|
+
</interface>
|
15
|
+
<interface name="org.example.Foo">
|
16
|
+
<method name="Smurf"/>
|
17
|
+
</interface>
|
18
|
+
</node>
|
19
|
+
XML
|
20
20
|
|
21
21
|
interfaces, _subnodes = DBus::IntrospectXMLParser.new(xml).parse
|
22
22
|
|