ruby-dbus 0.10.0 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|