rbus 0.1.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 (59) hide show
  1. data/CHANGELOG.txt +9 -0
  2. data/COPYING.txt +341 -0
  3. data/HACKING.txt +104 -0
  4. data/Manifest.txt +58 -0
  5. data/README.txt +17 -0
  6. data/Rakefile +97 -0
  7. data/TUTORIAL.txt +303 -0
  8. data/bin/rbus-send +165 -0
  9. data/examples/async_rb_and_notify.rb +56 -0
  10. data/examples/async_rb_loop.rb +52 -0
  11. data/examples/glib_async_rb_loop.rb +57 -0
  12. data/examples/glib_rhythmbox.rb +54 -0
  13. data/examples/hal_device_info.rb +54 -0
  14. data/examples/listnames.rb +37 -0
  15. data/examples/network_manager_get_properties.rb +50 -0
  16. data/examples/notification_bubble.rb +46 -0
  17. data/examples/rhythmbox_print_playing_uri.rb +41 -0
  18. data/examples/rhythmbox_signal_print_playing.rb +54 -0
  19. data/examples/rhythmbox_start_service.rb +39 -0
  20. data/examples/rhythmbox_toggle_playing.rb +46 -0
  21. data/lib/rbus.rb +25 -0
  22. data/lib/rbus/auth/auth.rb +53 -0
  23. data/lib/rbus/auth/dbus_cookie_sha1.rb +66 -0
  24. data/lib/rbus/auth/dummy.rb +37 -0
  25. data/lib/rbus/auth/external.rb +34 -0
  26. data/lib/rbus/auth/state_machine.rb +168 -0
  27. data/lib/rbus/bus/bus.rb +101 -0
  28. data/lib/rbus/bus/proxy.rb +137 -0
  29. data/lib/rbus/bus/transport.rb +125 -0
  30. data/lib/rbus/default.rb +29 -0
  31. data/lib/rbus/etc/exception.rb +44 -0
  32. data/lib/rbus/etc/log.rb +100 -0
  33. data/lib/rbus/etc/types.rb +56 -0
  34. data/lib/rbus/etc/version.rb +34 -0
  35. data/lib/rbus/glib.rb +29 -0
  36. data/lib/rbus/mainloop/glib.rb +77 -0
  37. data/lib/rbus/mainloop/mainloop.rb +84 -0
  38. data/lib/rbus/mainloop/observers.rb +149 -0
  39. data/lib/rbus/mainloop/thread.rb +78 -0
  40. data/lib/rbus/message/constants.rb +51 -0
  41. data/lib/rbus/message/marshal.rb +139 -0
  42. data/lib/rbus/message/message.rb +110 -0
  43. data/lib/rbus/message/reader.rb +108 -0
  44. data/lib/rbus/message/serial_generator.rb +48 -0
  45. data/lib/rbus/message/unmarshal.rb +171 -0
  46. data/lib/rbus/message/writer.rb +69 -0
  47. data/setup.rb +1608 -0
  48. data/spec/auth_spec.rb +123 -0
  49. data/spec/bus_spec.rb +178 -0
  50. data/spec/helpers/bus_mocks.rb +64 -0
  51. data/spec/helpers/spec_helper.rb +24 -0
  52. data/spec/mainloop_spec.rb +74 -0
  53. data/spec/marshal_spec.rb +91 -0
  54. data/spec/message_spec.rb +61 -0
  55. data/spec/observers_spec.rb +28 -0
  56. data/spec/proxy_spec.rb +120 -0
  57. data/spec/transport_spec.rb +187 -0
  58. data/spec/unmarshal_spec.rb +186 -0
  59. metadata +118 -0
@@ -0,0 +1,34 @@
1
+ #--
2
+ #
3
+ # R-Bus is a native Ruby implementation of the D-Bus protocol.
4
+ # Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
+ # MA 02110-1301, USA. A copy of the GNU General Public License is
20
+ # also available at http://www.gnu.org/copyleft/gpl.html.
21
+ #
22
+ #++
23
+ #
24
+ module RBus
25
+ module Auth
26
+
27
+ # Implements the AUTH EXTERNAL mechanism.
28
+ class External
29
+ def auth
30
+ ['EXTERNAL', Process.uid, :OK]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,168 @@
1
+ #--
2
+ #
3
+ # R-Bus is a native Ruby implementation of the D-Bus protocol.
4
+ # Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
+ # MA 02110-1301, USA. A copy of the GNU General Public License is
20
+ # also available at http://www.gnu.org/copyleft/gpl.html.
21
+ #
22
+ #++
23
+ #
24
+ module RBus
25
+ module Auth
26
+
27
+ # Implements a statemachine for Authorization like the one described in
28
+ # the D-Bus specification.
29
+ class StateMachine
30
+ def initialize(transport)
31
+ @transport = transport
32
+ @mechanism = FIRST_MECHANISM.new
33
+ end
34
+
35
+ # Main entry point, returns guid if succesful, or raises otherwise.
36
+ def authorize
37
+ state = :AUTH
38
+
39
+ loop do
40
+ case state
41
+ when :AUTH
42
+ command, data, state = @mechanism.auth
43
+ send_auth(command, data)
44
+
45
+ when :REJECTED
46
+ response = Command.new(@transport.readline)
47
+ case response.command
48
+ when 'REJECTED'
49
+ next_mechanism(response.data)
50
+ state = :AUTH
51
+ else
52
+ raise AuthException
53
+ end
54
+
55
+ when :OK
56
+ response = Command.new(@transport.readline)
57
+ case response.command
58
+ when 'OK'
59
+ send_begin
60
+ return response.data
61
+ when 'REJECT'
62
+ next_mechanism(response.data)
63
+ state = :AUTH
64
+ when 'DATA'
65
+ send_cancel
66
+ state = :REJECT
67
+ when 'ERROR'
68
+ send_cancel
69
+ state = :REJECT
70
+ else
71
+ send_error
72
+ state = :OK
73
+ end
74
+
75
+ when :DATA
76
+ response = Command.new(@transport.readline)
77
+ case response.command
78
+ when 'DATA'
79
+ begin
80
+ message, state = @mechanism.data(response.data)
81
+ rescue AuthException => e
82
+ send_cancel
83
+ state = :REJECT
84
+ else
85
+ send_data(message)
86
+ end
87
+ when 'REJECT'
88
+ next_mechanism(response.data)
89
+ state = :AUTH
90
+ when 'ERROR'
91
+ send_cancel
92
+ state = :REJECT
93
+ when 'OK'
94
+ send_begin
95
+ return response.data
96
+ else
97
+ send_error
98
+ state = :DATA
99
+ end
100
+ else
101
+ raise
102
+ end
103
+ end
104
+ end
105
+
106
+ # Move to next mechanism
107
+ def next_mechanism(data)
108
+ if @mechanisms.nil?
109
+ populate_mechanisms(data)
110
+ end
111
+ if @mechanisms.empty?
112
+ raise AuthException
113
+ else
114
+ @mechanism = @mechanisms.shift.new
115
+ end
116
+ end
117
+
118
+ # Build initial mechanism list
119
+ def populate_mechanisms(data)
120
+ server_mechanisms = data.split(' ')
121
+
122
+ # Find common mechanisms, or try them all if server didn't tell us
123
+ if server_mechanisms.empty?
124
+ mechanisms = MECHANISMS.keys
125
+ else
126
+ mechanisms = (server_mechanisms & MECHANISMS.keys)
127
+ end
128
+
129
+ @mechanisms = mechanisms.map{|name| MECHANISMS[name]}
130
+ end
131
+
132
+ # Send AUTH ...
133
+ def send_auth(command, data)
134
+ line = ['AUTH', command, hex_encode(data)].compact.join(' ')
135
+ @transport.sendline(line)
136
+ end
137
+
138
+ # send_begin => BEGIN, etc
139
+ def method_missing(symbol, *args)
140
+ command = symbol.to_s
141
+ if command.sub!('send_', '')
142
+ line = [command.upcase, hex_encode(args[0])].compact.join(' ')
143
+ @transport.sendline(line)
144
+ end
145
+ end
146
+
147
+ # Server expects all data to be encoded in hex
148
+ def hex_encode(plain)
149
+ return nil if plain.nil?
150
+ plain.to_s.unpack('H*')[0]
151
+ end
152
+ end
153
+
154
+ # Parses server response and splits it into component parts
155
+ # Decodes where appropriate (only DATA)
156
+ class Command
157
+ attr_reader :command, :data
158
+ def initialize(string)
159
+ @command, data = string.split(/ /, 2)
160
+ @data = (@command == 'DATA') ? hex_decode(data) : data
161
+ end
162
+
163
+ def hex_decode(encoded)
164
+ encoded.scan(/[[:xdigit:]]{2}/).map{|h|h.hex.chr}.join
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,101 @@
1
+ #--
2
+ #
3
+ # R-Bus is a native Ruby implementation of the D-Bus protocol.
4
+ # Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
+ # MA 02110-1301, USA. A copy of the GNU General Public License is
20
+ # also available at http://www.gnu.org/copyleft/gpl.html.
21
+ #
22
+ #++
23
+ #
24
+
25
+ require File.dirname(__FILE__) + '/proxy'
26
+ require 'singleton'
27
+
28
+ module RBus
29
+
30
+ # Get the session bus
31
+ def self.session_bus
32
+ SessionBus.instance
33
+ end
34
+
35
+ # Get the system bus
36
+ def self.system_bus
37
+ SystemBus.instance
38
+ end
39
+
40
+ # Get Bus with specific address
41
+ def self.get_bus(address)
42
+ Bus.new(address)
43
+ end
44
+
45
+
46
+ # Represents a connection to a Bus. It is also a Proxy object for
47
+ # sending messages to the bus itself.
48
+ class Bus < Proxy
49
+ NAMES = []
50
+
51
+ attr_reader :message_loop
52
+
53
+ # Connect to Bus with +server_address+, which can contain multiple
54
+ # addresses to try, separated by semicolons.
55
+ def initialize(server_address)
56
+ raise InvalidAddressException, 'Empty address' if server_address.empty?
57
+
58
+ # Can be several addresses, separated by ;
59
+ server_address.split(';').each do |address|
60
+ @transport = Transport.connect(address)
61
+ end
62
+
63
+ # Not used for anything, save at all?
64
+ @guid = Auth.authorize(@transport)
65
+
66
+ # Start main message loop.
67
+ #@message_loop = Message::Loop.new(@transport).run
68
+ @message_loop = Mainloop.new(@transport)
69
+
70
+ @well_known_name = 'org.freedesktop.DBus'
71
+ @object_path = '/org/freedesktop/DBus'
72
+ @interface = 'org.freedesktop.DBus'
73
+ NAMES << Hello()
74
+ Introspect()
75
+
76
+ Log.debug('Bus names:', NAMES)
77
+
78
+ end
79
+
80
+ # Returns a remote object for well-known name and object path.
81
+ def get_object(well_known_name, object_path)
82
+ Proxy.new(self, well_known_name, object_path)
83
+ end
84
+ end
85
+
86
+ # Represents the connection to the Session Bus.
87
+ class SessionBus < Bus
88
+ include Singleton
89
+ def initialize
90
+ super(ENV['DBUS_SESSION_BUS_ADDRESS'])
91
+ end
92
+ end
93
+
94
+ # Represents the connection to the System Bus.
95
+ class SystemBus < Bus
96
+ include Singleton
97
+ def initialize
98
+ super(ENV['DBUS_SYSTEM_BUS_ADDRESS'] || 'unix:path=/var/run/dbus/system_bus_socket')
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,137 @@
1
+ #--
2
+ #
3
+ # R-Bus is a native Ruby implementation of the D-Bus protocol.
4
+ # Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
+ # MA 02110-1301, USA. A copy of the GNU General Public License is
20
+ # also available at http://www.gnu.org/copyleft/gpl.html.
21
+ #
22
+ #++
23
+ #
24
+ require 'rexml/document'
25
+
26
+ module RBus
27
+
28
+ VALID_METHOD_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,254}$/
29
+
30
+ # BlankSlate is a superclass that empties (almost) all instance methods
31
+ # from it's subclasses.
32
+ # See: http://onestepback.org/index.cgi/Tech/Ruby/BlankSlate.rdoc
33
+ class BlankSlate
34
+ instance_methods.each { |m| undef_method m unless m =~ /^__|[!\?]$/ }
35
+ end
36
+
37
+ # The object that represents the remote bus object.
38
+ class Proxy < BlankSlate
39
+
40
+ # Parses Introspect() XML into a hash with
41
+ # {method => signature}
42
+ def self.parse_introspect(xml)
43
+ methods = {}
44
+ doc = REXML::Document.new(xml)
45
+ doc.elements.each("//method") do |method|
46
+ name = method.attribute('name').to_s
47
+ signature = ''
48
+ method.elements.each("*[@direction!='out']") do |arg|
49
+ signature << arg.attribute('type').to_s
50
+ end
51
+ methods[name] = signature
52
+ end
53
+ methods
54
+ end
55
+
56
+ def initialize(bus, well_known_name, object_path)
57
+ @bus = bus
58
+ @message_loop = @bus.message_loop
59
+ @well_known_name = well_known_name
60
+ @object_path = object_path
61
+ Introspect()
62
+ end
63
+
64
+ def Introspect # :nodoc:
65
+ # TODO: store interfaces as well, if ever needed
66
+ interface = @interface
67
+ @interface = 'org.freedesktop.DBus.Introspectable'
68
+ @methods = Proxy.parse_introspect(method_missing(:Introspect))
69
+ Log.debug("Methods from introspect: " + @methods.inspect)
70
+ @interface = interface
71
+ end
72
+
73
+ # Set interface and return or yield self.
74
+ # If a block is given, a copy of the object will be
75
+ # the new interface will only be set for that block, and reset
76
+ # afterwards.
77
+ def interface!(interface)
78
+ old_i = @interface
79
+ @interface = interface
80
+ if block_given?
81
+ yield self
82
+ @interface = old_i
83
+ end
84
+ self
85
+ end
86
+
87
+ # Connect block to a signal
88
+ def connect!(signal, options = {}, &block)
89
+ raise MessageException, "connect! needs a block" unless block_given?
90
+
91
+ rules = {
92
+ :type => 'signal',
93
+ :member => signal.to_s,
94
+ :path => @object_path,
95
+ #:sender => @well_known_name
96
+ }
97
+ rules['interface'] = @interface if @interface
98
+
99
+ rules.merge!(options)
100
+
101
+ Log.debug(rules)
102
+ @bus.AddMatch(rules, &block)
103
+ end
104
+
105
+ # Define a method with signature.
106
+ # +name+ can be a string or a symbol.
107
+ def method!(name, signature)
108
+ @methods[name.to_s] = signature
109
+ end
110
+
111
+ # Routes all method calls to remote object
112
+ def method_missing(name, *args, &block) # :nodoc:
113
+
114
+ method = name.to_s
115
+
116
+ Log.debug('Proxy method called: ' + method)
117
+
118
+ unless method.match(VALID_METHOD_NAME)
119
+ raise InvalidNameException, "Invalid method name '#{name}'"
120
+ end
121
+
122
+ call = MethodCall.new do |m|
123
+ m.destination = @well_known_name
124
+ m.object_path = @object_path
125
+ m.interface = @interface if @interface
126
+ m.member = method
127
+ m.arguments = args
128
+ if !args.empty? && @methods.has_key?(method)
129
+ Log.debug('Signature: ' + @methods[method])
130
+ m.signature = @methods[method]
131
+ end
132
+ end
133
+ Log.debug(block_given? ? 'Block given' : 'Block NOT given')
134
+ @message_loop.send_message(call, block_given? ? block : nil)
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,125 @@
1
+ #--
2
+ #
3
+ # R-Bus is a native Ruby implementation of the D-Bus protocol.
4
+ # Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
+ # MA 02110-1301, USA. A copy of the GNU General Public License is
20
+ # also available at http://www.gnu.org/copyleft/gpl.html.
21
+ #
22
+ #++
23
+ #
24
+ require 'socket'
25
+
26
+ module RBus
27
+
28
+ # Transport represents the connection to the bus,
29
+ # and supports sending and recieving messages
30
+ # The class itself is a factory that returns the
31
+ # appropriate transport for an address
32
+ class Transport
33
+
34
+ # Abstract base implementation of a transport.
35
+ class AbstractTransport
36
+ # Newline
37
+ CRLF = "\015\012"
38
+ attr_reader :socket
39
+
40
+ def initialize(connection_info)
41
+ @connection_info = connection_info
42
+ create_socket
43
+ end
44
+
45
+ def send(message)
46
+ @socket.write(message)
47
+ end
48
+
49
+ def read(len = 2**27)
50
+ @socket.read(len)
51
+ #@socket.recv(len)
52
+ end
53
+
54
+ def sendline(message)
55
+ @socket.write(message + CRLF)
56
+ end
57
+
58
+ def readline
59
+ @socket.readline.chomp
60
+ end
61
+
62
+ def close
63
+ @socket.close
64
+ end
65
+
66
+ end
67
+
68
+ # Represents a connection to a UNIX Socket, abstract or regular.
69
+ class UNIXTransport < AbstractTransport
70
+ def create_socket
71
+ if @connection_info.has_key?('abstract')
72
+ @socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
73
+ @socket.connect("\1\0\0#{@connection_info['abstract']}")
74
+ elsif @connection_info.has_key?('path')
75
+ @socket = UNIXSocket.open(@connection_info['path'])
76
+ end
77
+ end
78
+ end
79
+
80
+ # Represents a connection to a TCP Socket.
81
+ class TCPTransport < AbstractTransport
82
+ def create_socket
83
+ @socket = TCPSocket.new(@connection_info['host'], @connection_info['port'])
84
+ end
85
+ end
86
+
87
+ # Unescape simple hex encoding (%XX) for values
88
+ def self.unescape(value)
89
+ value.gsub(/%([[:xdigit:]]{2})/) {$1.hex.chr}
90
+ end
91
+
92
+ # Parse a valid connection address
93
+ def self.parse_address(address)
94
+ connection_info = {}
95
+ connection_info['transport'], info = address.split(':', 2)
96
+
97
+ raise InvalidAddressException if info.nil? || info.empty?
98
+
99
+ info.split(',').each do |pair|
100
+ key, value = pair.split('=')
101
+ connection_info[key] = unescape(value)
102
+ end
103
+ connection_info
104
+ end
105
+
106
+ # Define factory method connect
107
+ class << self
108
+ def connect(address)
109
+ connection_info = parse_address(address)
110
+
111
+ klass =
112
+ case connection_info['transport']
113
+ when 'unix'
114
+ UNIXTransport
115
+ when 'tcp'
116
+ TCPTransport
117
+ else
118
+ raise InvalidTransportException, "Unknown transport #{connection_info['transport']}"
119
+ end
120
+ klass::new(connection_info)
121
+ end
122
+ end
123
+ end
124
+ end
125
+