em-ruby-dbus 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +504 -0
  3. data/NEWS +253 -0
  4. data/README.md +93 -0
  5. data/Rakefile +58 -0
  6. data/VERSION +1 -0
  7. data/doc/Reference.md +207 -0
  8. data/doc/Tutorial.md +480 -0
  9. data/doc/ex-calling-methods.body.rb +8 -0
  10. data/doc/ex-calling-methods.rb +3 -0
  11. data/doc/ex-properties.body.rb +9 -0
  12. data/doc/ex-properties.rb +3 -0
  13. data/doc/ex-setup.rb +7 -0
  14. data/doc/ex-signal.body.rb +20 -0
  15. data/doc/ex-signal.rb +3 -0
  16. data/doc/example-helper.rb +6 -0
  17. data/em-ruby-dbus.gemspec +20 -0
  18. data/examples/gdbus/gdbus +255 -0
  19. data/examples/gdbus/gdbus.glade +184 -0
  20. data/examples/gdbus/launch.sh +4 -0
  21. data/examples/no-introspect/nm-test.rb +21 -0
  22. data/examples/no-introspect/tracker-test.rb +16 -0
  23. data/examples/rhythmbox/playpause.rb +25 -0
  24. data/examples/service/call_service.rb +25 -0
  25. data/examples/service/service_newapi.rb +51 -0
  26. data/examples/simple/call_introspect.rb +34 -0
  27. data/examples/simple/properties.rb +19 -0
  28. data/examples/utils/listnames.rb +11 -0
  29. data/examples/utils/notify.rb +19 -0
  30. data/lib/dbus.rb +82 -0
  31. data/lib/dbus/auth.rb +269 -0
  32. data/lib/dbus/bus.rb +739 -0
  33. data/lib/dbus/core_ext/array/extract_options.rb +31 -0
  34. data/lib/dbus/core_ext/class/attribute.rb +129 -0
  35. data/lib/dbus/core_ext/kernel/singleton_class.rb +8 -0
  36. data/lib/dbus/core_ext/module/remove_method.rb +14 -0
  37. data/lib/dbus/error.rb +46 -0
  38. data/lib/dbus/export.rb +128 -0
  39. data/lib/dbus/introspect.rb +219 -0
  40. data/lib/dbus/logger.rb +31 -0
  41. data/lib/dbus/loop-em.rb +19 -0
  42. data/lib/dbus/marshall.rb +434 -0
  43. data/lib/dbus/matchrule.rb +101 -0
  44. data/lib/dbus/message.rb +276 -0
  45. data/lib/dbus/message_queue.rb +166 -0
  46. data/lib/dbus/proxy_object.rb +149 -0
  47. data/lib/dbus/proxy_object_factory.rb +41 -0
  48. data/lib/dbus/proxy_object_interface.rb +128 -0
  49. data/lib/dbus/type.rb +193 -0
  50. data/lib/dbus/xml.rb +161 -0
  51. data/test/async_spec.rb +47 -0
  52. data/test/binding_spec.rb +74 -0
  53. data/test/bus_and_xml_backend_spec.rb +39 -0
  54. data/test/bus_driver_spec.rb +20 -0
  55. data/test/bus_spec.rb +20 -0
  56. data/test/byte_array_spec.rb +38 -0
  57. data/test/err_msg_spec.rb +42 -0
  58. data/test/introspect_xml_parser_spec.rb +26 -0
  59. data/test/introspection_spec.rb +32 -0
  60. data/test/main_loop_spec.rb +82 -0
  61. data/test/property_spec.rb +53 -0
  62. data/test/server_robustness_spec.rb +66 -0
  63. data/test/server_spec.rb +53 -0
  64. data/test/service_newapi.rb +217 -0
  65. data/test/session_bus_spec_manual.rb +15 -0
  66. data/test/signal_spec.rb +90 -0
  67. data/test/spec_helper.rb +33 -0
  68. data/test/thread_safety_spec.rb +31 -0
  69. data/test/tools/dbus-launch-simple +35 -0
  70. data/test/tools/dbus-limited-session.conf +28 -0
  71. data/test/tools/test_env +13 -0
  72. data/test/tools/test_server +39 -0
  73. data/test/type_spec.rb +19 -0
  74. data/test/value_spec.rb +81 -0
  75. data/test/variant_spec.rb +66 -0
  76. metadata +145 -0
@@ -0,0 +1,101 @@
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
+ module DBus
10
+ # Exception raised when an erroneous match rule type is encountered.
11
+ class MatchRuleException < Exception
12
+ end
13
+
14
+ # = D-Bus match rule class
15
+ #
16
+ # FIXME
17
+ class MatchRule
18
+ # The list of possible match filters. TODO argN, argNpath
19
+ FILTERS = [:sender, :interface, :member, :path, :destination, :type]
20
+ # The sender filter.
21
+ attr_accessor :sender
22
+ # The interface filter.
23
+ attr_accessor :interface
24
+ # The member filter.
25
+ attr_accessor :member
26
+ # The path filter.
27
+ attr_accessor :path
28
+ # The destination filter.
29
+ attr_accessor :destination
30
+ # The type type that is matched.
31
+ attr_reader :type
32
+
33
+ # Create a new match rule
34
+ def initialize
35
+ @sender = @interface = @member = @path = @destination = @type = nil
36
+ end
37
+
38
+ # Set the message types to filter to type _t_.
39
+ # Possible message types are: signal, method_call, method_return, and error.
40
+ def type=(t)
41
+ if not ['signal', 'method_call', 'method_return', 'error'].member?(t)
42
+ raise MatchRuleException, t
43
+ end
44
+ @type = t
45
+ end
46
+
47
+ # Returns a match rule string version of the object.
48
+ # E.g.: "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='Foo',path='/bar/foo',destination=':452345.34',arg2='bar'"
49
+ def to_s
50
+ FILTERS.select do |sym|
51
+ not method(sym).call.nil?
52
+ end.collect do |sym|
53
+ "#{sym.to_s}='#{method(sym).call}'"
54
+ end.join(",")
55
+ end
56
+
57
+ # Parses a match rule string _s_ and sets the filters on the object.
58
+ def from_s(str)
59
+ str.split(",").each do |eq|
60
+ if eq =~ /^(.*)='([^']*)'$/
61
+ # "
62
+ name = $1
63
+ val = $2
64
+ if FILTERS.member?(name.to_sym)
65
+ method(name + "=").call(val)
66
+ else
67
+ raise MatchRuleException, name
68
+ end
69
+ end
70
+ end
71
+ self
72
+ end
73
+
74
+ # Sets the match rule to filter for the given _signal_ and the
75
+ # given interface _intf_.
76
+ def from_signal(intf, signal)
77
+ signal = signal.name unless signal.is_a?(String)
78
+ self.type = "signal"
79
+ self.interface = intf.name
80
+ self.member = signal
81
+ self.path = intf.object.path
82
+ self
83
+ end
84
+
85
+ # Determines whether a message _msg_ matches the match rule.
86
+ def match(msg)
87
+ if @type
88
+ if {Message::SIGNAL => "signal", Message::METHOD_CALL => "method_call",
89
+ Message::METHOD_RETURN => "method_return",
90
+ Message::ERROR => "error"}[msg.message_type] != @type
91
+ return false
92
+ end
93
+ end
94
+ return false if @interface and @interface != msg.interface
95
+ return false if @member and @member != msg.member
96
+ return false if @path and @path != msg.path
97
+ # FIXME sender and destination are ignored
98
+ true
99
+ end
100
+ end # class MatchRule
101
+ end # module D-Bus
@@ -0,0 +1,276 @@
1
+ # dbus.rb - Module containing the low-level D-Bus implementation
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
+ # = D-Bus main module
12
+ #
13
+ # Module containing all the D-Bus modules and classes.
14
+ module DBus
15
+ # = InvalidDestinationName class
16
+ # Thrown when you try to send a message to /org/freedesktop/DBus/Local, that
17
+ # is reserved.
18
+ class InvalidDestinationName < Exception
19
+ end
20
+
21
+ # = D-Bus message class
22
+ #
23
+ # Class that holds any type of message that travels over the bus.
24
+ class Message
25
+ # The serial number of the message.
26
+ @@serial = 1
27
+ # Mutex that protects updates on the serial number.
28
+ @@serial_mutex = Mutex.new
29
+ # Type of a message (by specification).
30
+ MESSAGE_SIGNATURE = "yyyyuua(yv)"
31
+
32
+ # FIXME: following message type constants should be under Message::Type IMO
33
+ # well, yeah sure
34
+ #
35
+ # Invalid message type.
36
+ INVALID = 0
37
+ # Method call message type.
38
+ METHOD_CALL = 1
39
+ # Method call return value message type.
40
+ METHOD_RETURN = 2
41
+ # Error message type.
42
+ ERROR = 3
43
+ # Signal message type.
44
+ SIGNAL = 4
45
+
46
+ # Message flag signyfing that no reply is expected.
47
+ NO_REPLY_EXPECTED = 0x1
48
+ # Message flag signifying that no automatic start is required/must be
49
+ # performed.
50
+ NO_AUTO_START = 0x2
51
+
52
+ # The type of the message.
53
+ attr_reader :message_type
54
+ # The path of the object instance the message must be sent to/is sent from.
55
+ attr_accessor :path
56
+ # The interface of the object that must be used/was used.
57
+ attr_accessor :interface
58
+ # The interface member (method/signal name) of the object that must be
59
+ # used/was used.
60
+ attr_accessor :member
61
+ # The name of the error (in case of an error message type).
62
+ attr_accessor :error_name
63
+ # The destination connection of the object that must be used/was used.
64
+ attr_accessor :destination
65
+ # The sender of the message.
66
+ attr_accessor :sender
67
+ # The signature of the message contents.
68
+ attr_accessor :signature
69
+ # The serial number of the message this message is a reply for.
70
+ attr_accessor :reply_serial
71
+ # The protocol.
72
+ attr_reader :protocol
73
+ # The serial of the message.
74
+ attr_reader :serial
75
+ # The parameters of the message.
76
+ attr_reader :params
77
+
78
+ # Create a message with message type _mtype_ with default values and a
79
+ # unique serial number.
80
+ def initialize(mtype = INVALID)
81
+ @message_type = mtype
82
+
83
+ @flags = 0
84
+ @protocol = 1
85
+ @body_length = 0
86
+ @signature = String.new
87
+ @@serial_mutex.synchronize do
88
+ @serial = @@serial
89
+ @@serial += 1
90
+ end
91
+ @params = Array.new
92
+ @destination = nil
93
+ @interface = nil
94
+ @error_name = nil
95
+ @member = nil
96
+ @path = nil
97
+ @reply_serial = nil
98
+
99
+ if mtype == METHOD_RETURN
100
+ @flags = NO_REPLY_EXPECTED
101
+ end
102
+ end
103
+
104
+ def to_s
105
+ "#{message_type} sender=#{sender} -> dest=#{destination} serial=#{serial} reply_serial=#{reply_serial} path=#{path}; interface=#{interface}; member=#{member} error_name=#{error_name}"
106
+ end
107
+
108
+ # Create a regular reply to a message _m_.
109
+ def self.method_return(m)
110
+ MethodReturnMessage.new.reply_to(m)
111
+ end
112
+
113
+ # Create an error reply to a message _m_.
114
+ def self.error(m, error_name, description=nil)
115
+ ErrorMessage.new(error_name, description).reply_to(m)
116
+ end
117
+
118
+ # Mark this message as a reply to a another message _m_, taking
119
+ # the serial number of _m_ as reply serial and the sender of _m_ as
120
+ # destination.
121
+ def reply_to(m)
122
+ @reply_serial = m.serial
123
+ @destination = m.sender
124
+ self
125
+ end
126
+
127
+ # Add a parameter _val_ of type _type_ to the message.
128
+ def add_param(type, val)
129
+ type = type.chr if type.kind_of?(Fixnum)
130
+ @signature += type.to_s
131
+ @params << [type, val]
132
+ end
133
+
134
+ # FIXME: what are these? a message element constant enumeration?
135
+ # See method below, in a message, you have and array of optional parameters
136
+ # that come with an index, to determine their meaning. The values are in
137
+ # spec, more a definition than an enumeration.
138
+
139
+ PATH = 1
140
+ INTERFACE = 2
141
+ MEMBER = 3
142
+ ERROR_NAME = 4
143
+ REPLY_SERIAL = 5
144
+ DESTINATION = 6
145
+ SENDER = 7
146
+ SIGNATURE = 8
147
+
148
+ # Marshall the message with its current set parameters and return
149
+ # it in a packet form.
150
+ def marshall
151
+ if @path == "/org/freedesktop/DBus/Local"
152
+ raise InvalidDestinationName
153
+ end
154
+
155
+ params = PacketMarshaller.new
156
+ @params.each do |param|
157
+ params.append(param[0], param[1])
158
+ end
159
+ @body_length = params.packet.bytesize
160
+
161
+ marshaller = PacketMarshaller.new
162
+ marshaller.append(Type::BYTE, HOST_END)
163
+ marshaller.append(Type::BYTE, @message_type)
164
+ marshaller.append(Type::BYTE, @flags)
165
+ marshaller.append(Type::BYTE, @protocol)
166
+ marshaller.append(Type::UINT32, @body_length)
167
+ marshaller.append(Type::UINT32, @serial)
168
+
169
+ headers = []
170
+ headers << [PATH, ["o", @path]] if @path
171
+ headers << [INTERFACE, ["s", @interface]] if @interface
172
+ headers << [MEMBER, ["s", @member]] if @member
173
+ headers << [ERROR_NAME, ["s", @error_name]] if @error_name
174
+ headers << [REPLY_SERIAL, ["u", @reply_serial]] if @reply_serial
175
+ headers << [DESTINATION, ["s", @destination]] if @destination
176
+ # SENDER is not sent, the message bus fills it in instead
177
+ headers << [SIGNATURE, ["g", @signature]] if @signature != ""
178
+ marshaller.append("a(yv)", headers)
179
+
180
+ marshaller.align(8)
181
+ @params.each do |param|
182
+ marshaller.append(param[0], param[1])
183
+ end
184
+ marshaller.packet
185
+ end
186
+
187
+ # Unmarshall a packet contained in the buffer _buf_ and set the
188
+ # parameters of the message object according the data found in the
189
+ # buffer.
190
+ # Return the detected message and the index pointer of the buffer where
191
+ # the message data ended.
192
+ def unmarshall_buffer(buf)
193
+ buf = buf.dup
194
+ if buf[0] == ?l
195
+ endianness = LIL_END
196
+ else
197
+ endianness = BIG_END
198
+ end
199
+ pu = PacketUnmarshaller.new(buf, endianness)
200
+ mdata = pu.unmarshall(MESSAGE_SIGNATURE)
201
+ _, @message_type, @flags, @protocol, @body_length, @serial,
202
+ headers = mdata
203
+
204
+ headers.each do |struct|
205
+ case struct[0]
206
+ when PATH
207
+ @path = struct[1]
208
+ when INTERFACE
209
+ @interface = struct[1]
210
+ when MEMBER
211
+ @member = struct[1]
212
+ when ERROR_NAME
213
+ @error_name = struct[1]
214
+ when REPLY_SERIAL
215
+ @reply_serial = struct[1]
216
+ when DESTINATION
217
+ @destination = struct[1]
218
+ when SENDER
219
+ @sender = struct[1]
220
+ when SIGNATURE
221
+ @signature = struct[1]
222
+ end
223
+ end
224
+ pu.align(8)
225
+ if @body_length > 0 and @signature
226
+ @params = pu.unmarshall(@signature, @body_length)
227
+ end
228
+ [self, pu.idx]
229
+ end # def unmarshall_buf
230
+
231
+ # Unmarshall the data of a message found in the buffer _buf_ using
232
+ # Message#unmarshall_buf.
233
+ # Return the message.
234
+ def unmarshall(buf)
235
+ ret, _ = unmarshall_buffer(buf)
236
+ ret
237
+ end
238
+
239
+ # Make a new exception from ex, mark it as being caused by this message
240
+ # @api private
241
+ def annotate_exception(ex)
242
+ new_ex = ex.exception("#{ex}; caused by #{self}")
243
+ new_ex.set_backtrace(ex.backtrace)
244
+ new_ex
245
+ end
246
+ end # class Message
247
+
248
+ class MethodReturnMessage < Message
249
+ def initialize
250
+ super(METHOD_RETURN)
251
+ end
252
+ end
253
+
254
+ class ErrorMessage < Message
255
+ def initialize(error_name, description=nil)
256
+ super(ERROR)
257
+ @error_name = error_name
258
+ unless description.nil?
259
+ add_param(Type::STRING, description)
260
+ end
261
+ end
262
+
263
+ def self.from_exception(ex)
264
+ name = if ex.is_a? DBus::Error
265
+ ex.name
266
+ else
267
+ "org.freedesktop.DBus.Error.Failed"
268
+ # ex.class.to_s # RuntimeError is not a valid name, has no dot
269
+ end
270
+ description = ex.message
271
+ msg = self.new(name, description)
272
+ msg.add_param(DBus.type("as"), ex.backtrace)
273
+ msg
274
+ end
275
+ end
276
+ end # module DBus
@@ -0,0 +1,166 @@
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
+ require "fcntl"
11
+ require "socket"
12
+
13
+ module DBus
14
+ class MessageQueue
15
+ # The socket that is used to connect with the bus.
16
+ attr_reader :socket
17
+
18
+ def initialize(address)
19
+ @address = address
20
+ @buffer = ""
21
+ @is_tcp = false
22
+ connect
23
+ end
24
+
25
+ # TODO failure modes
26
+ #
27
+ # If _non_block_ is true, return nil instead of waiting
28
+ # EOFError may be raised
29
+ def pop(non_block = false)
30
+ buffer_from_socket_nonblock
31
+ message = message_from_buffer_nonblock
32
+ unless non_block
33
+ # we can block
34
+ while message.nil?
35
+ r, d, d = IO.select([@socket])
36
+ if r and r[0] == @socket
37
+ buffer_from_socket_nonblock
38
+ message = message_from_buffer_nonblock
39
+ end
40
+ end
41
+ end
42
+ message
43
+ end
44
+
45
+ def push(message)
46
+ @socket.write(message.marshall)
47
+ end
48
+ alias :<< :push
49
+
50
+ private
51
+
52
+ # Connect to the bus and initialize the connection.
53
+ def connect
54
+ addresses = @address.split ";"
55
+ # connect to first one that succeeds
56
+ worked = addresses.find do |a|
57
+ transport, keyvaluestring = a.split ":"
58
+ kv_list = keyvaluestring.split ","
59
+ kv_hash = Hash.new
60
+ kv_list.each do |kv|
61
+ key, escaped_value = kv.split "="
62
+ value = escaped_value.gsub(/%(..)/) {|m| [$1].pack "H2" }
63
+ kv_hash[key] = value
64
+ end
65
+ case transport
66
+ when "unix"
67
+ connect_to_unix kv_hash
68
+ when "tcp"
69
+ connect_to_tcp kv_hash
70
+ when "launchd"
71
+ connect_to_launchd kv_hash
72
+ else
73
+ # ignore, report?
74
+ end
75
+ end
76
+ worked
77
+ # returns the address that worked or nil.
78
+ # how to report failure?
79
+ end
80
+
81
+ # Connect to a bus over tcp and initialize the connection.
82
+ def connect_to_tcp(params)
83
+ #check if the path is sufficient
84
+ if params.key?("host") and params.key?("port")
85
+ begin
86
+ #initialize the tcp socket
87
+ @socket = TCPSocket.new(params["host"],params["port"].to_i)
88
+ @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
89
+ init_connection
90
+ @is_tcp = true
91
+ rescue Exception => e
92
+ puts "Oops:", e
93
+ puts "Error: Could not establish connection to: #{@path}, will now exit."
94
+ exit(1) #a little harsh
95
+ end
96
+ else
97
+ #Danger, Will Robinson: the specified "path" is not usable
98
+ puts "Error: supplied path: #{@path}, unusable! sorry."
99
+ end
100
+ end
101
+
102
+ # Connect to an abstract unix bus and initialize the connection.
103
+ def connect_to_unix(params)
104
+ @socket = Socket.new(Socket::Constants::PF_UNIX,Socket::Constants::SOCK_STREAM, 0)
105
+ @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
106
+ if ! params['abstract'].nil?
107
+ if HOST_END == LIL_END
108
+ sockaddr = "\1\0\0#{params['abstract']}"
109
+ else
110
+ sockaddr = "\0\1\0#{params['abstract']}"
111
+ end
112
+ elsif ! params['path'].nil?
113
+ sockaddr = Socket.pack_sockaddr_un(params['path'])
114
+ end
115
+ @socket.connect(sockaddr)
116
+ init_connection
117
+ end
118
+
119
+ def connect_to_launchd(params)
120
+ socket_var = params['env']
121
+ socket = `launchctl getenv #{socket_var}`.chomp
122
+ connect_to_unix 'path' => socket
123
+ end
124
+
125
+ # Initialize the connection to the bus.
126
+ def init_connection
127
+ @client = Client.new(@socket)
128
+ @client.authenticate
129
+ end
130
+
131
+ public # FIXME: fix Main loop instead
132
+
133
+ # Get and remove one message from the buffer.
134
+ # Return the message or nil.
135
+ def message_from_buffer_nonblock
136
+ return nil if @buffer.empty?
137
+ ret = nil
138
+ begin
139
+ ret, size = Message.new.unmarshall_buffer(@buffer)
140
+ @buffer.slice!(0, size)
141
+ rescue IncompleteBufferException
142
+ # fall through, let ret be null
143
+ end
144
+ ret
145
+ end
146
+
147
+ # The buffer size for messages.
148
+ MSG_BUF_SIZE = 4096
149
+
150
+ # Fill (append) the buffer from data that might be available on the
151
+ # socket.
152
+ # EOFError may be raised
153
+ def buffer_from_socket_nonblock
154
+ @buffer += @socket.read_nonblock(MSG_BUF_SIZE)
155
+ rescue EOFError
156
+ raise # the caller expects it
157
+ rescue Errno::EAGAIN
158
+ # fine, would block
159
+ rescue Exception => e
160
+ puts "Oops:", e
161
+ raise if @is_tcp # why?
162
+ puts "WARNING: read_nonblock failed, falling back to .recv"
163
+ @buffer += @socket.recv(MSG_BUF_SIZE)
164
+ end
165
+ end
166
+ end