rbus 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+