ruby-dbus 0.10.0 → 0.11.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 +4 -4
- data/NEWS +8 -0
- data/README.md +41 -4
- data/Rakefile +15 -16
- data/VERSION +1 -1
- data/lib/dbus.rb +18 -14
- data/lib/dbus/bus.rb +32 -168
- data/lib/dbus/error.rb +4 -2
- data/lib/dbus/export.rb +3 -2
- data/lib/dbus/introspect.rb +0 -294
- data/lib/dbus/marshall.rb +1 -1
- data/lib/dbus/message.rb +8 -0
- data/lib/dbus/message_queue.rb +166 -0
- data/lib/dbus/proxy_object.rb +149 -0
- data/lib/dbus/proxy_object_factory.rb +41 -0
- data/lib/dbus/proxy_object_interface.rb +128 -0
- data/ruby-dbus.gemspec +1 -0
- data/test/{async_test.rb → async_spec.rb} +10 -11
- data/test/{binding_test.rb → binding_spec.rb} +23 -25
- data/test/bus_and_xml_backend_spec.rb +39 -0
- data/test/bus_driver_spec.rb +20 -0
- data/test/{bus_test.rb → bus_spec.rb} +8 -7
- data/test/byte_array_spec.rb +38 -0
- data/test/err_msg_spec.rb +42 -0
- data/test/{introspect_xml_parser_test.rb → introspect_xml_parser_spec.rb} +5 -6
- data/test/introspection_spec.rb +32 -0
- data/test/{main_loop_test.rb → main_loop_spec.rb} +10 -7
- data/test/property_spec.rb +53 -0
- data/test/server_robustness_spec.rb +66 -0
- data/test/{server_test.rb → server_spec.rb} +10 -11
- data/test/service_newapi.rb +1 -1
- data/test/session_bus_spec_manual.rb +15 -0
- data/test/{signal_test.rb → signal_spec.rb} +13 -14
- data/test/spec_helper.rb +33 -0
- data/test/{thread_safety_test.rb → thread_safety_spec.rb} +5 -6
- data/test/type_spec.rb +19 -0
- data/test/{value_test.rb → value_spec.rb} +23 -24
- data/test/variant_spec.rb +66 -0
- metadata +40 -22
- data/test/bus_and_xml_backend_test.rb +0 -43
- data/test/bus_driver_test.rb +0 -19
- data/test/byte_array_test.rb +0 -42
- data/test/err_msg_test.rb +0 -59
- data/test/introspection_test.rb +0 -32
- data/test/property_test.rb +0 -65
- data/test/server_robustness_test.rb +0 -73
- data/test/session_bus_test_manual.rb +0 -17
- data/test/test_helper.rb +0 -14
- data/test/type_test.rb +0 -23
- data/test/variant_test.rb +0 -67
@@ -0,0 +1,149 @@
|
|
1
|
+
# This file is part of the ruby-dbus project
|
2
|
+
# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
|
3
|
+
# Copyright (C) 2009-2014 Martin Vidner
|
4
|
+
#
|
5
|
+
# This library is free software; you can redistribute it and/or
|
6
|
+
# modify it under the terms of the GNU Lesser General Public
|
7
|
+
# License, version 2.1 as published by the Free Software Foundation.
|
8
|
+
# See the file "COPYING" for the exact licensing terms.
|
9
|
+
|
10
|
+
module DBus
|
11
|
+
# D-Bus proxy object class
|
12
|
+
#
|
13
|
+
# Class representing a remote object in an external application.
|
14
|
+
# Typically, calling a method on an instance of a ProxyObject sends a message
|
15
|
+
# over the bus so that the method is executed remotely on the correctponding
|
16
|
+
# object.
|
17
|
+
class ProxyObject
|
18
|
+
# The names of direct subnodes of the object in the tree.
|
19
|
+
attr_accessor :subnodes
|
20
|
+
# Flag determining whether the object has been introspected.
|
21
|
+
attr_accessor :introspected
|
22
|
+
# The (remote) destination of the object.
|
23
|
+
attr_reader :destination
|
24
|
+
# The path to the object.
|
25
|
+
attr_reader :path
|
26
|
+
# The bus the object is reachable via.
|
27
|
+
attr_reader :bus
|
28
|
+
# @return [String] The name of the default interface of the object.
|
29
|
+
attr_accessor :default_iface
|
30
|
+
|
31
|
+
# Creates a new proxy object living on the given _bus_ at destination _dest_
|
32
|
+
# on the given _path_.
|
33
|
+
def initialize(bus, dest, path)
|
34
|
+
@bus, @destination, @path = bus, dest, path
|
35
|
+
@interfaces = Hash.new
|
36
|
+
@subnodes = Array.new
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the interfaces of the object.
|
40
|
+
def interfaces
|
41
|
+
@interfaces.keys
|
42
|
+
end
|
43
|
+
|
44
|
+
# Retrieves an interface of the proxy object
|
45
|
+
# @param [String] intfname
|
46
|
+
# @return [ProxyObjectInterface]
|
47
|
+
def [](intfname)
|
48
|
+
@interfaces[intfname]
|
49
|
+
end
|
50
|
+
|
51
|
+
# Maps the given interface name _intfname_ to the given interface _intf.
|
52
|
+
# @param [String] intfname
|
53
|
+
# @param [ProxyObjectInterface] intf
|
54
|
+
# @return [ProxyObjectInterface]
|
55
|
+
def []=(intfname, intf)
|
56
|
+
@interfaces[intfname] = intf
|
57
|
+
end
|
58
|
+
|
59
|
+
# Introspects the remote object. Allows you to find and select
|
60
|
+
# interfaces on the object.
|
61
|
+
def introspect
|
62
|
+
# Synchronous call here.
|
63
|
+
xml = @bus.introspect_data(@destination, @path)
|
64
|
+
ProxyObjectFactory.introspect_into(self, xml)
|
65
|
+
define_shortcut_methods()
|
66
|
+
xml
|
67
|
+
end
|
68
|
+
|
69
|
+
# For each non duplicated method name in any interface present on the
|
70
|
+
# caller, defines a shortcut method dynamically.
|
71
|
+
# This function is automatically called when a {ProxyObject} is
|
72
|
+
# introspected.
|
73
|
+
def define_shortcut_methods
|
74
|
+
# builds a list of duplicated methods
|
75
|
+
dup_meths, univocal_meths = [],{}
|
76
|
+
@interfaces.each_value do |intf|
|
77
|
+
intf.methods.each_value do |meth|
|
78
|
+
name = meth.name.to_sym
|
79
|
+
# don't overwrite instance methods!
|
80
|
+
if dup_meths.include? name or self.class.instance_methods.include? name
|
81
|
+
next
|
82
|
+
elsif univocal_meths.include? name
|
83
|
+
univocal_meths.delete name
|
84
|
+
dup_meths << name
|
85
|
+
else
|
86
|
+
univocal_meths[name] = intf
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
univocal_meths.each do |name, intf|
|
91
|
+
# creates a shortcut function that forwards each call to the method on
|
92
|
+
# the appropriate intf
|
93
|
+
singleton_class.class_eval do
|
94
|
+
define_method name do |*args, &reply_handler|
|
95
|
+
intf.method(name).call(*args, &reply_handler)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns whether the object has an interface with the given _name_.
|
102
|
+
def has_iface?(name)
|
103
|
+
raise "Cannot call has_iface? if not introspected" if not @introspected
|
104
|
+
@interfaces.member?(name)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Registers a handler, the code block, for a signal with the given _name_.
|
108
|
+
# It uses _default_iface_ which must have been set.
|
109
|
+
# @return [void]
|
110
|
+
def on_signal(name, &block)
|
111
|
+
if @default_iface and has_iface?(@default_iface)
|
112
|
+
@interfaces[@default_iface].on_signal(name, &block)
|
113
|
+
else
|
114
|
+
# TODO improve
|
115
|
+
raise NoMethodError
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
####################################################
|
120
|
+
private
|
121
|
+
|
122
|
+
# Handles all unkown methods, mostly to route method calls to the
|
123
|
+
# default interface.
|
124
|
+
def method_missing(name, *args, &reply_handler)
|
125
|
+
if @default_iface and has_iface?(@default_iface)
|
126
|
+
begin
|
127
|
+
@interfaces[@default_iface].method(name).call(*args, &reply_handler)
|
128
|
+
rescue NameError => e
|
129
|
+
# interesting, foo.method("unknown")
|
130
|
+
# raises NameError, not NoMethodError
|
131
|
+
raise unless e.to_s =~ /undefined method `#{name}'/
|
132
|
+
# BTW e.exception("...") would preserve the class.
|
133
|
+
raise NoMethodError,"undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
|
134
|
+
end
|
135
|
+
else
|
136
|
+
# TODO distinguish:
|
137
|
+
# - di not specified
|
138
|
+
#TODO
|
139
|
+
# - di is specified but not found in introspection data
|
140
|
+
raise NoMethodError, "undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns the singleton class of the object.
|
145
|
+
def singleton_class
|
146
|
+
(class << self ; self ; end)
|
147
|
+
end
|
148
|
+
end # class ProxyObject
|
149
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# This file is part of the ruby-dbus project
|
2
|
+
# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
|
3
|
+
# Copyright (C) 2009-2014 Martin Vidner
|
4
|
+
#
|
5
|
+
# This library is free software; you can redistribute it and/or
|
6
|
+
# modify it under the terms of the GNU Lesser General Public
|
7
|
+
# License, version 2.1 as published by the Free Software Foundation.
|
8
|
+
# See the file "COPYING" for the exact licensing terms.
|
9
|
+
|
10
|
+
module DBus
|
11
|
+
# = D-Bus proxy object factory class
|
12
|
+
#
|
13
|
+
# Class that generates and sets up a proxy object based on introspection data.
|
14
|
+
class ProxyObjectFactory
|
15
|
+
# Creates a new proxy object factory for the given introspection XML _xml_,
|
16
|
+
# _bus_, destination _dest_, and _path_.
|
17
|
+
def initialize(xml, bus, dest, path)
|
18
|
+
@xml, @bus, @path, @dest = xml, bus, path, dest
|
19
|
+
end
|
20
|
+
|
21
|
+
# Investigates the sub-nodes of the proxy object _po_ based on the
|
22
|
+
# introspection XML data _xml_ and sets them up recursively.
|
23
|
+
def ProxyObjectFactory.introspect_into(po, xml)
|
24
|
+
intfs, po.subnodes = IntrospectXMLParser.new(xml).parse
|
25
|
+
intfs.each do |i|
|
26
|
+
poi = ProxyObjectInterface.new(po, i.name)
|
27
|
+
i.methods.each_value { |m| poi.define(m) }
|
28
|
+
i.signals.each_value { |s| poi.define(s) }
|
29
|
+
po[i.name] = poi
|
30
|
+
end
|
31
|
+
po.introspected = true
|
32
|
+
end
|
33
|
+
|
34
|
+
# Generates, sets up and returns the proxy object.
|
35
|
+
def build
|
36
|
+
po = ProxyObject.new(@bus, @dest, @path)
|
37
|
+
ProxyObjectFactory.introspect_into(po, @xml)
|
38
|
+
po
|
39
|
+
end
|
40
|
+
end # class ProxyObjectFactory
|
41
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# This file is part of the ruby-dbus project
|
2
|
+
# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
|
3
|
+
# Copyright (C) 2009-2014 Martin Vidner
|
4
|
+
#
|
5
|
+
# This library is free software; you can redistribute it and/or
|
6
|
+
# modify it under the terms of the GNU Lesser General Public
|
7
|
+
# License, version 2.1 as published by the Free Software Foundation.
|
8
|
+
# See the file "COPYING" for the exact licensing terms.
|
9
|
+
|
10
|
+
module DBus
|
11
|
+
# = D-Bus proxy object interface class
|
12
|
+
#
|
13
|
+
# A class similar to the normal Interface used as a proxy for remote
|
14
|
+
# object interfaces.
|
15
|
+
class ProxyObjectInterface
|
16
|
+
# The proxied methods contained in the interface.
|
17
|
+
attr_accessor :methods
|
18
|
+
# The proxied signals contained in the interface.
|
19
|
+
attr_accessor :signals
|
20
|
+
# The proxy object to which this interface belongs.
|
21
|
+
attr_reader :object
|
22
|
+
# The name of the interface.
|
23
|
+
attr_reader :name
|
24
|
+
|
25
|
+
# Creates a new proxy interface for the given proxy _object_
|
26
|
+
# and the given _name_.
|
27
|
+
def initialize(object, name)
|
28
|
+
@object, @name = object, name
|
29
|
+
@methods, @signals = Hash.new, Hash.new
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the string representation of the interface (the name).
|
33
|
+
def to_str
|
34
|
+
@name
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the singleton class of the interface.
|
38
|
+
def singleton_class
|
39
|
+
(class << self ; self ; end)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Defines a method on the interface from the Method descriptor _m_.
|
43
|
+
def define_method_from_descriptor(m)
|
44
|
+
m.params.each do |fpar|
|
45
|
+
par = fpar.type
|
46
|
+
# This is the signature validity check
|
47
|
+
Type::Parser.new(par).parse
|
48
|
+
end
|
49
|
+
|
50
|
+
singleton_class.class_eval do
|
51
|
+
define_method m.name do |*args, &reply_handler|
|
52
|
+
if m.params.size != args.size
|
53
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for #{m.params.size})"
|
54
|
+
end
|
55
|
+
|
56
|
+
msg = Message.new(Message::METHOD_CALL)
|
57
|
+
msg.path = @object.path
|
58
|
+
msg.interface = @name
|
59
|
+
msg.destination = @object.destination
|
60
|
+
msg.member = m.name
|
61
|
+
msg.sender = @object.bus.unique_name
|
62
|
+
m.params.each do |fpar|
|
63
|
+
par = fpar.type
|
64
|
+
msg.add_param(par, args.shift)
|
65
|
+
end
|
66
|
+
@object.bus.send_sync_or_async(msg, &reply_handler)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
@methods[m.name] = m
|
71
|
+
end
|
72
|
+
|
73
|
+
# Defines a signal from the descriptor _s_.
|
74
|
+
def define_signal_from_descriptor(s)
|
75
|
+
@signals[s.name] = s
|
76
|
+
end
|
77
|
+
|
78
|
+
# Defines a signal or method based on the descriptor _m_.
|
79
|
+
def define(m)
|
80
|
+
if m.kind_of?(Method)
|
81
|
+
define_method_from_descriptor(m)
|
82
|
+
elsif m.kind_of?(Signal)
|
83
|
+
define_signal_from_descriptor(m)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Defines a proxied method on the interface.
|
88
|
+
def define_method(methodname, prototype)
|
89
|
+
m = Method.new(methodname)
|
90
|
+
m.from_prototype(prototype)
|
91
|
+
define(m)
|
92
|
+
end
|
93
|
+
|
94
|
+
# @overload on_signal(name, &block)
|
95
|
+
# @overload on_signal(bus, name, &block)
|
96
|
+
# Registers a handler (code block) for a signal with _name_ arriving
|
97
|
+
# over the given _bus_. If no block is given, the signal is unregistered.
|
98
|
+
# Note that specifying _bus_ is discouraged and the option is kept only for
|
99
|
+
# backward compatibility.
|
100
|
+
# @return [void]
|
101
|
+
def on_signal(bus = @object.bus, name, &block)
|
102
|
+
mr = DBus::MatchRule.new.from_signal(self, name)
|
103
|
+
if block.nil?
|
104
|
+
bus.remove_match(mr)
|
105
|
+
else
|
106
|
+
bus.add_match(mr) { |msg| block.call(*msg.params) }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"
|
111
|
+
|
112
|
+
# Read a property.
|
113
|
+
def [](propname)
|
114
|
+
self.object[PROPERTY_INTERFACE].Get(self.name, propname)[0]
|
115
|
+
end
|
116
|
+
|
117
|
+
# Write a property.
|
118
|
+
def []=(propname, value)
|
119
|
+
self.object[PROPERTY_INTERFACE].Set(self.name, propname, value)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Read all properties at once, as a hash.
|
123
|
+
# @return [Hash{String}]
|
124
|
+
def all_properties
|
125
|
+
self.object[PROPERTY_INTERFACE].GetAll(self.name)[0]
|
126
|
+
end
|
127
|
+
end # class ProxyObjectInterface
|
128
|
+
end
|
data/ruby-dbus.gemspec
CHANGED
@@ -1,11 +1,10 @@
|
|
1
|
-
#!/usr/bin/env
|
1
|
+
#!/usr/bin/env rspec
|
2
2
|
# Test the binding of dbus concepts to ruby concepts
|
3
|
-
|
4
|
-
require "test/unit"
|
3
|
+
require_relative "spec_helper"
|
5
4
|
require "dbus"
|
6
5
|
|
7
|
-
|
8
|
-
|
6
|
+
describe "AsyncTest" do
|
7
|
+
before(:each) do
|
9
8
|
@bus = DBus::ASessionBus.new
|
10
9
|
@svc = @bus.service("org.ruby.service")
|
11
10
|
@obj = @svc.object "/org/ruby/MyInstance"
|
@@ -14,32 +13,32 @@ class AsyncTest < Test::Unit::TestCase
|
|
14
13
|
end
|
15
14
|
|
16
15
|
# https://github.com/mvidner/ruby-dbus/issues/13
|
17
|
-
|
16
|
+
it "tests async_call_to_default_interface" do
|
18
17
|
loop = DBus::Main.new
|
19
18
|
loop << @bus
|
20
19
|
|
21
20
|
immediate_answer = @obj.the_answer do |msg, retval|
|
22
|
-
|
21
|
+
expect(retval).to eq(42)
|
23
22
|
loop.quit
|
24
23
|
end
|
25
24
|
|
26
|
-
|
25
|
+
expect(immediate_answer).to be_nil
|
27
26
|
|
28
27
|
# wait for the async reply
|
29
28
|
loop.run
|
30
29
|
end
|
31
30
|
|
32
|
-
|
31
|
+
it "tests async_call_to_explicit_interface" do
|
33
32
|
loop = DBus::Main.new
|
34
33
|
loop << @bus
|
35
34
|
|
36
35
|
ifc = @obj["org.ruby.AnotherInterface"]
|
37
36
|
immediate_answer = ifc.Reverse("abcd") do |msg, retval|
|
38
|
-
|
37
|
+
expect(retval).to eq("dcba")
|
39
38
|
loop.quit
|
40
39
|
end
|
41
40
|
|
42
|
-
|
41
|
+
expect(immediate_answer).to be_nil
|
43
42
|
|
44
43
|
# wait for the async reply
|
45
44
|
loop.run
|
@@ -1,11 +1,11 @@
|
|
1
|
-
#!/usr/bin/env
|
1
|
+
#!/usr/bin/env rspec
|
2
2
|
# Test the binding of dbus concepts to ruby concepts
|
3
|
-
|
4
|
-
|
3
|
+
require_relative "spec_helper"
|
4
|
+
|
5
5
|
require "dbus"
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
describe "BindingTest" do
|
8
|
+
before(:each) do
|
9
9
|
@bus = DBus::ASessionBus.new
|
10
10
|
@svc = @bus.service("org.ruby.service")
|
11
11
|
@base = @svc.object "/org/ruby/MyInstance"
|
@@ -14,48 +14,46 @@ class BindingTest < Test::Unit::TestCase
|
|
14
14
|
end
|
15
15
|
|
16
16
|
# https://trac.luon.net/ruby-dbus/ticket/36#comment:3
|
17
|
-
|
17
|
+
it "tests class inheritance" do
|
18
18
|
derived = @svc.object "/org/ruby/MyDerivedInstance"
|
19
19
|
derived.introspect
|
20
20
|
|
21
21
|
# it should inherit from the parent
|
22
|
-
|
22
|
+
expect(derived["org.ruby.SampleInterface"]).not_to be_nil
|
23
23
|
end
|
24
24
|
|
25
25
|
# https://trac.luon.net/ruby-dbus/ticket/36
|
26
26
|
# Interfaces and methods/signals appeared on all classes
|
27
|
-
|
27
|
+
it "tests separation of classes" do
|
28
28
|
test2 = @svc.object "/org/ruby/MyInstance2"
|
29
29
|
test2.introspect
|
30
30
|
|
31
31
|
# it should have its own interface
|
32
|
-
|
32
|
+
expect(test2["org.ruby.Test2"]).not_to be_nil
|
33
33
|
# but not an interface of the Test class
|
34
|
-
|
34
|
+
expect(test2["org.ruby.SampleInterface"]).to be_nil
|
35
35
|
|
36
36
|
# and the parent should not get polluted by the child
|
37
|
-
|
37
|
+
expect(@base["org.ruby.Test2"]).to be_nil
|
38
38
|
end
|
39
39
|
|
40
|
-
|
40
|
+
it "tests translating errors into exceptions" do
|
41
41
|
# this is a generic call that will reply with the specified error
|
42
|
-
@base.Error "org.example.Fail", "as you wish"
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
assert_equal "as you wish", e.message
|
42
|
+
expect { @base.Error "org.example.Fail", "as you wish" }.to raise_error(DBus::Error) do |e|
|
43
|
+
expect(e.name).to eq("org.example.Fail")
|
44
|
+
expect(e.message).to match(/as you wish/)
|
45
|
+
end
|
47
46
|
end
|
48
47
|
|
49
|
-
|
48
|
+
it "tests generic dbus error" do
|
50
49
|
# this is a generic call that will reply with the specified error
|
51
|
-
@base.will_raise_error_failed
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
assert_equal "failed as designed", e.message
|
50
|
+
expect { @base.will_raise_error_failed }.to raise_error(DBus::Error) do |e|
|
51
|
+
expect(e.name).to eq("org.freedesktop.DBus.Error.Failed")
|
52
|
+
expect(e.message).to match(/failed as designed/)
|
53
|
+
end
|
56
54
|
end
|
57
55
|
|
58
|
-
|
56
|
+
it "tests dynamic interface definition" do
|
59
57
|
# interfaces can be defined dynamicaly
|
60
58
|
derived = DBus::Object.new "/org/ruby/MyDerivedInstance"
|
61
59
|
|
@@ -70,7 +68,7 @@ class BindingTest < Test::Unit::TestCase
|
|
70
68
|
|
71
69
|
# the object should have the new iface
|
72
70
|
ifaces = derived.intfs
|
73
|
-
|
71
|
+
expect(ifaces && ifaces.include?("org.ruby.DynamicInterface")).to be_true
|
74
72
|
end
|
75
73
|
|
76
74
|
end
|