em-ruby-dbus 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.
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