ruby-vnc 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog CHANGED
@@ -1,9 +1,13 @@
1
- == 1.0.1 / 2011-09-15
2
-
3
- - Split out gemspec into separate file and use for Rakefile.
4
- - Add homepage and rubyforge project to gemspec.
5
-
6
- == 1.0.0 / 2008-08-29
7
-
8
- - First public release
9
-
1
+ == 1.1.0 / 2012-06-03
2
+
3
+ - Fixes to support ruby 1.9 (jedi4ever & codemonkeyjohn).
4
+
5
+ == 1.0.1 / 2011-09-15
6
+
7
+ - Split out gemspec into separate file and use for Rakefile.
8
+ - Add homepage and rubyforge project to gemspec.
9
+
10
+ == 1.0.0 / 2008-08-29
11
+
12
+ - First public release
13
+
data/README CHANGED
@@ -1,26 +1,26 @@
1
- = Introduction
2
-
3
- The ruby-vnc library provides for simple rfb-protocol based control of a
4
- VNC server. This can be used, eg, to automate applications (sometimes there
5
- is no better way), or script some sort of interaction.
6
-
7
- Being VNC based gives it the advantage of being able to be run against
8
- servers on any platform.
9
-
10
- The primary documentation is for the Net::VNC class.
11
-
12
- = Thanks
13
-
14
- Code borrows a lot from Tim Waugh's excellent rfbplaymacro. So far all it
15
- really offers on top of that is access to the host clipboard, and the ease
16
- with which it can be scripted, ie taking conditional actions based on the
17
- contents thereof.
18
-
19
- = TODO
20
-
21
- * Having the ability to take and analyse screen dumps would be useful.
22
-
23
- * Finish the specs.
24
-
25
- * Fix issues with the way we use a background thread to handle the clipboard
26
- etc chatter from the server.
1
+ = Introduction
2
+
3
+ The ruby-vnc library provides for simple rfb-protocol based control of a
4
+ VNC server. This can be used, eg, to automate applications (sometimes there
5
+ is no better way), or script some sort of interaction.
6
+
7
+ Being VNC based gives it the advantage of being able to be run against
8
+ servers on any platform.
9
+
10
+ The primary documentation is for the Net::VNC class.
11
+
12
+ = Thanks
13
+
14
+ Code borrows a lot from Tim Waugh's excellent rfbplaymacro. So far all it
15
+ really offers on top of that is access to the host clipboard, and the ease
16
+ with which it can be scripted, ie taking conditional actions based on the
17
+ contents thereof.
18
+
19
+ = TODO
20
+
21
+ * Having the ability to take and analyse screen dumps would be useful.
22
+
23
+ * Finish the specs.
24
+
25
+ * Fix issues with the way we use a background thread to handle the clipboard
26
+ etc chatter from the server.
data/Rakefile CHANGED
@@ -46,7 +46,7 @@ end
46
46
 
47
47
  Rake::GemPackageTask.new(spec) do |t|
48
48
  t.gem_spec = spec
49
- t.need_tar = true
49
+ t.need_tar = false
50
50
  t.need_zip = false
51
51
  t.package_dir = 'build'
52
52
  end
@@ -169,7 +169,7 @@ module Cipher
169
169
  pc1m = (0...56).map do |j|
170
170
  l = PC1[j]
171
171
  m = l & 07
172
- (key[l >> 3] & BYTEBIT[m]) != 0 ? 1 : 0;
172
+ (key[l >> 3].ord & BYTEBIT[m]) != 0 ? 1 : 0;
173
173
  end
174
174
 
175
175
  16.times do |i|
@@ -1,306 +1,306 @@
1
- require 'socket'
2
- require 'yaml'
3
- require 'thread'
4
- require 'cipher/des'
5
- require 'net/vnc/version'
6
-
7
- module Net
8
- #
9
- # The VNC class provides for simple rfb-protocol based control of
10
- # a VNC server. This can be used, eg, to automate applications.
11
- #
12
- # Sample usage:
13
- #
14
- # # launch xclock on localhost. note that there is an xterm in the top-left
15
- # Net::VNC.open 'localhost:0', :shared => true, :password => 'mypass' do |vnc|
16
- # vnc.pointer_move 10, 10
17
- # vnc.type 'xclock'
18
- # vnc.key_press :return
19
- # end
20
- #
21
- # = TODO
22
- #
23
- # * The server read loop seems a bit iffy. Not sure how best to do it.
24
- # * Should probably be changed to be more of a lower-level protocol wrapping thing, with the
25
- # actual VNCClient sitting on top of that. all it should do is read/write the packets over
26
- # the socket.
27
- #
28
- class VNC
29
- class PointerState
30
- attr_reader :x, :y, :button
31
-
32
- def initialize vnc
33
- @x = @y = @button = 0
34
- @vnc = vnc
35
- end
36
-
37
- # could have the same for x=, and y=
38
- def button= button
39
- @button = button
40
- refresh
41
- end
42
-
43
- def update x, y, button=@button
44
- @x, @y, @button = x, y, button
45
- refresh
46
- end
47
-
48
- def refresh
49
- packet = 0.chr * 6
50
- packet[0] = 5
51
- packet[1] = button
52
- packet[2, 2] = [x].pack 'n'
53
- packet[4, 2] = [y].pack 'n'
54
- @vnc.socket.write packet
55
- end
56
- end
57
-
58
- BASE_PORT = 5900
59
- CHALLENGE_SIZE = 16
60
- DEFAULT_OPTIONS = {
61
- :shared => false,
62
- :wait => 0.1
63
- }
64
-
65
- keys_file = File.dirname(__FILE__) + '/../../data/keys.yaml'
66
- KEY_MAP = YAML.load_file(keys_file).inject({}) { |h, (k, v)| h.update k.to_sym => v }
67
- def KEY_MAP.[] key
68
- super or raise ArgumentError.new('Invalid key name - %s' % key)
69
- end
70
-
71
- attr_reader :server, :display, :options, :socket, :pointer
72
-
73
- def initialize display=':0', options={}
74
- @server = 'localhost'
75
- if display =~ /^(.*)(:\d+)$/
76
- @server, display = $1, $2
77
- end
78
- @display = display[1..-1].to_i
79
- @options = DEFAULT_OPTIONS.merge options
80
- @clipboard = nil
81
- @pointer = PointerState.new self
82
- @mutex = Mutex.new
83
- connect
84
- @packet_reading_state = nil
85
- @packet_reading_thread = Thread.new { packet_reading_thread }
86
- end
87
-
88
- def self.open display=':0', options={}
89
- vnc = new display, options
90
- if block_given?
91
- begin
92
- yield vnc
93
- ensure
94
- vnc.close
95
- end
96
- else
97
- vnc
98
- end
99
- end
100
-
101
- def port
102
- BASE_PORT + @display
103
- end
104
-
105
- def connect
106
- @socket = TCPSocket.open server, port
107
- unless socket.read(12) =~ /^RFB (\d{3}.\d{3})\n$/
108
- raise 'invalid server response'
109
- end
110
- @server_version = $1
111
- socket.write "RFB 003.003\n"
112
- data = socket.read(4)
113
- auth = data.to_s.unpack('N')[0]
114
- case auth
115
- when 0, nil
116
- raise 'connection failed'
117
- when 1
118
- # ok...
119
- when 2
120
- password = @options[:password] or raise 'Need to authenticate but no password given'
121
- challenge = socket.read CHALLENGE_SIZE
122
- response = Cipher::DES.encrypt password, challenge
123
- socket.write response
124
- ok = socket.read(4).to_s.unpack('N')[0]
125
- raise 'Unable to authenticate - %p' % ok unless ok == 0
126
- else
127
- raise 'Unknown authentication scheme - %d' % auth
128
- end
129
-
130
- # ClientInitialisation
131
- socket.write((options[:shared] ? 1 : 0).chr)
132
-
133
- # ServerInitialisation
134
- # TODO: parse this.
135
- socket.read(20)
136
- data = socket.read(4)
137
- # read this many bytes in chunks of 20
138
- size = data.to_s.unpack('N')[0]
139
- while size > 0
140
- len = [20, size].min
141
- # this is the hostname, and other stuff i think...
142
- socket.read(len)
143
- size -= len
144
- end
145
- end
146
-
147
- # this types +text+ on the server
148
- def type text, options={}
149
- packet = 0.chr * 8
150
- packet[0] = 4
151
- text.split(//).each do |char|
152
- packet[7] = char[0]
153
- packet[1] = 1
154
- socket.write packet
155
- packet[1] = 0
156
- socket.write packet
157
- end
158
- wait options
159
- end
160
-
161
- # this takes an array of keys, and successively holds each down then lifts them up in
162
- # reverse order.
163
- # FIXME: should wait. can't recurse in that case.
164
- def key_press(*args)
165
- options = Hash === args.last ? args.pop : {}
166
- keys = args
167
- raise ArgumentError, 'Must have at least one key argument' if keys.empty?
168
- begin
169
- key_down keys.first
170
- if keys.length == 1
171
- yield if block_given?
172
- else
173
- key_press(*(keys[1..-1] + [options]))
174
- end
175
- ensure
176
- key_up keys.first
177
- end
178
- end
179
-
180
- def get_key_code which
181
- if String === which
182
- if which.length != 1
183
- raise ArgumentError, 'can only get key_code of single character strings'
184
- end
185
- which[0]
186
- else
187
- KEY_MAP[which]
188
- end
189
- end
190
- private :get_key_code
191
-
192
- def key_down which, options={}
193
- packet = 0.chr * 8
194
- packet[0] = 4
195
- key_code = get_key_code which
196
- packet[4, 4] = [key_code].pack('N')
197
- packet[1] = 1
198
- socket.write packet
199
- wait options
200
- end
201
-
202
- def key_up which, options={}
203
- packet = 0.chr * 8
204
- packet[0] = 4
205
- key_code = get_key_code which
206
- packet[4, 4] = [key_code].pack('N')
207
- packet[1] = 0
208
- socket.write packet
209
- wait options
210
- end
211
-
212
- def pointer_move x, y, options={}
213
- # options[:relative]
214
- pointer.update x, y
215
- wait options
216
- end
217
-
218
- BUTTON_MAP = {
219
- :left => 0
220
- }
221
-
222
- def button_press button=:left, options={}
223
- begin
224
- button_down button, options
225
- yield if block_given?
226
- ensure
227
- button_up button, options
228
- end
229
- end
230
-
231
- def button_down which=:left, options={}
232
- button = BUTTON_MAP[which] || which
233
- raise ArgumentError, 'Invalid button - %p' % which unless (0..2) === button
234
- pointer.button |= 1 << button
235
- wait options
236
- end
237
-
238
- def button_up which=:left, options={}
239
- button = BUTTON_MAP[which] || which
240
- raise ArgumentError, 'Invalid button - %p' % which unless (0..2) === button
241
- pointer.button &= ~(1 << button)
242
- wait options
243
- end
244
-
245
- def wait options={}
246
- sleep options[:wait] || @options[:wait]
247
- end
248
-
249
- def close
250
- # destroy packet reading thread
251
- if @packet_reading_state == :loop
252
- @packet_reading_state = :stop
253
- while @packet_reading_state
254
- # do nothing
255
- end
256
- end
257
- socket.close
258
- end
259
-
260
- def clipboard
261
- if block_given?
262
- @clipboard = nil
263
- yield
264
- 60.times do
265
- clipboard = @mutex.synchronize { @clipboard }
266
- return clipboard if clipboard
267
- sleep 0.5
268
- end
269
- warn 'clipboard still empty after 30s'
270
- nil
271
- else
272
- @mutex.synchronize { @clipboard }
273
- end
274
- end
275
-
276
- private
277
-
278
- def read_packet type
279
- case type
280
- when 3 # ServerCutText
281
- socket.read 3 # discard padding bytes
282
- len = socket.read(4).unpack('N')[0]
283
- @mutex.synchronize { @clipboard = socket.read len }
284
- else
285
- raise NotImplementedError, 'unhandled server packet type - %d' % type
286
- end
287
- end
288
-
289
- def packet_reading_thread
290
- @packet_reading_state = :loop
291
- loop do
292
- begin
293
- break if @packet_reading_state != :loop
294
- next unless IO.select [socket], nil, nil, 2
295
- type = socket.read(1)[0]
296
- read_packet type
297
- rescue
298
- warn "exception in packet_reading_thread: #{$!.class}:#{$!}"
299
- break
300
- end
301
- end
302
- @packet_reading_state = nil
303
- end
304
- end
305
- end
306
-
1
+ require 'socket'
2
+ require 'yaml'
3
+ require 'thread'
4
+ require 'cipher/des'
5
+ require 'net/vnc/version'
6
+
7
+ module Net
8
+ #
9
+ # The VNC class provides for simple rfb-protocol based control of
10
+ # a VNC server. This can be used, eg, to automate applications.
11
+ #
12
+ # Sample usage:
13
+ #
14
+ # # launch xclock on localhost. note that there is an xterm in the top-left
15
+ # Net::VNC.open 'localhost:0', :shared => true, :password => 'mypass' do |vnc|
16
+ # vnc.pointer_move 10, 10
17
+ # vnc.type 'xclock'
18
+ # vnc.key_press :return
19
+ # end
20
+ #
21
+ # = TODO
22
+ #
23
+ # * The server read loop seems a bit iffy. Not sure how best to do it.
24
+ # * Should probably be changed to be more of a lower-level protocol wrapping thing, with the
25
+ # actual VNCClient sitting on top of that. all it should do is read/write the packets over
26
+ # the socket.
27
+ #
28
+ class VNC
29
+ class PointerState
30
+ attr_reader :x, :y, :button
31
+
32
+ def initialize vnc
33
+ @x = @y = @button = 0
34
+ @vnc = vnc
35
+ end
36
+
37
+ # could have the same for x=, and y=
38
+ def button= button
39
+ @button = button
40
+ refresh
41
+ end
42
+
43
+ def update x, y, button=@button
44
+ @x, @y, @button = x, y, button
45
+ refresh
46
+ end
47
+
48
+ def refresh
49
+ packet = 0.chr * 6
50
+ packet[0] = 5.chr
51
+ packet[1] = button.chr
52
+ packet[2, 2] = [x].pack 'n'
53
+ packet[4, 2] = [y].pack 'n'
54
+ @vnc.socket.write packet
55
+ end
56
+ end
57
+
58
+ BASE_PORT = 5900
59
+ CHALLENGE_SIZE = 16
60
+ DEFAULT_OPTIONS = {
61
+ :shared => false,
62
+ :wait => 0.1
63
+ }
64
+
65
+ keys_file = File.dirname(__FILE__) + '/../../data/keys.yaml'
66
+ KEY_MAP = YAML.load_file(keys_file).inject({}) { |h, (k, v)| h.update k.to_sym => v }
67
+ def KEY_MAP.[] key
68
+ super or raise ArgumentError.new('Invalid key name - %s' % key)
69
+ end
70
+
71
+ attr_reader :server, :display, :options, :socket, :pointer
72
+
73
+ def initialize display=':0', options={}
74
+ @server = 'localhost'
75
+ if display =~ /^(.*)(:\d+)$/
76
+ @server, display = $1, $2
77
+ end
78
+ @display = display[1..-1].to_i
79
+ @options = DEFAULT_OPTIONS.merge options
80
+ @clipboard = nil
81
+ @pointer = PointerState.new self
82
+ @mutex = Mutex.new
83
+ connect
84
+ @packet_reading_state = nil
85
+ @packet_reading_thread = Thread.new { packet_reading_thread }
86
+ end
87
+
88
+ def self.open display=':0', options={}
89
+ vnc = new display, options
90
+ if block_given?
91
+ begin
92
+ yield vnc
93
+ ensure
94
+ vnc.close
95
+ end
96
+ else
97
+ vnc
98
+ end
99
+ end
100
+
101
+ def port
102
+ BASE_PORT + @display
103
+ end
104
+
105
+ def connect
106
+ @socket = TCPSocket.open server, port
107
+ unless socket.read(12) =~ /^RFB (\d{3}.\d{3})\n$/
108
+ raise 'invalid server response'
109
+ end
110
+ @server_version = $1
111
+ socket.write "RFB 003.003\n"
112
+ data = socket.read(4)
113
+ auth = data.to_s.unpack('N')[0]
114
+ case auth
115
+ when 0, nil
116
+ raise 'connection failed'
117
+ when 1
118
+ # ok...
119
+ when 2
120
+ password = @options[:password] or raise 'Need to authenticate but no password given'
121
+ challenge = socket.read CHALLENGE_SIZE
122
+ response = Cipher::DES.encrypt password, challenge
123
+ socket.write response
124
+ ok = socket.read(4).to_s.unpack('N')[0]
125
+ raise 'Unable to authenticate - %p' % ok unless ok == 0
126
+ else
127
+ raise 'Unknown authentication scheme - %d' % auth
128
+ end
129
+
130
+ # ClientInitialisation
131
+ socket.write((options[:shared] ? 1 : 0).chr)
132
+
133
+ # ServerInitialisation
134
+ # TODO: parse this.
135
+ socket.read(20)
136
+ data = socket.read(4)
137
+ # read this many bytes in chunks of 20
138
+ size = data.to_s.unpack('N')[0]
139
+ while size > 0
140
+ len = [20, size].min
141
+ # this is the hostname, and other stuff i think...
142
+ socket.read(len)
143
+ size -= len
144
+ end
145
+ end
146
+
147
+ # this types +text+ on the server
148
+ def type text, options={}
149
+ packet = 0.chr * 8
150
+ packet[0] = 4.chr
151
+ text.split(//).each do |char|
152
+ packet[7] = char[0]
153
+ packet[1] = 1.chr
154
+ socket.write packet
155
+ packet[1] = 0.chr
156
+ socket.write packet
157
+ end
158
+ wait options
159
+ end
160
+
161
+ # this takes an array of keys, and successively holds each down then lifts them up in
162
+ # reverse order.
163
+ # FIXME: should wait. can't recurse in that case.
164
+ def key_press(*args)
165
+ options = Hash === args.last ? args.pop : {}
166
+ keys = args
167
+ raise ArgumentError, 'Must have at least one key argument' if keys.empty?
168
+ begin
169
+ key_down keys.first
170
+ if keys.length == 1
171
+ yield if block_given?
172
+ else
173
+ key_press(*(keys[1..-1] + [options]))
174
+ end
175
+ ensure
176
+ key_up keys.first
177
+ end
178
+ end
179
+
180
+ def get_key_code which
181
+ if String === which
182
+ if which.length != 1
183
+ raise ArgumentError, 'can only get key_code of single character strings'
184
+ end
185
+ which[0]
186
+ else
187
+ KEY_MAP[which]
188
+ end
189
+ end
190
+ private :get_key_code
191
+
192
+ def key_down which, options={}
193
+ packet = 0.chr * 8
194
+ packet[0] = 4.chr
195
+ key_code = get_key_code which
196
+ packet[4, 4] = [key_code].pack('N')
197
+ packet[1] = 1.chr
198
+ socket.write packet
199
+ wait options
200
+ end
201
+
202
+ def key_up which, options={}
203
+ packet = 0.chr * 8
204
+ packet[0] = 4.chr
205
+ key_code = get_key_code which
206
+ packet[4, 4] = [key_code].pack('N')
207
+ packet[1] = 0.chr
208
+ socket.write packet
209
+ wait options
210
+ end
211
+
212
+ def pointer_move x, y, options={}
213
+ # options[:relative]
214
+ pointer.update x, y
215
+ wait options
216
+ end
217
+
218
+ BUTTON_MAP = {
219
+ :left => 0
220
+ }
221
+
222
+ def button_press button=:left, options={}
223
+ begin
224
+ button_down button, options
225
+ yield if block_given?
226
+ ensure
227
+ button_up button, options
228
+ end
229
+ end
230
+
231
+ def button_down which=:left, options={}
232
+ button = BUTTON_MAP[which] || which
233
+ raise ArgumentError, 'Invalid button - %p' % which unless (0..2) === button
234
+ pointer.button |= 1 << button
235
+ wait options
236
+ end
237
+
238
+ def button_up which=:left, options={}
239
+ button = BUTTON_MAP[which] || which
240
+ raise ArgumentError, 'Invalid button - %p' % which unless (0..2) === button
241
+ pointer.button &= ~(1 << button)
242
+ wait options
243
+ end
244
+
245
+ def wait options={}
246
+ sleep options[:wait] || @options[:wait]
247
+ end
248
+
249
+ def close
250
+ # destroy packet reading thread
251
+ if @packet_reading_state == :loop
252
+ @packet_reading_state = :stop
253
+ while @packet_reading_state
254
+ # do nothing
255
+ end
256
+ end
257
+ socket.close
258
+ end
259
+
260
+ def clipboard
261
+ if block_given?
262
+ @clipboard = nil
263
+ yield
264
+ 60.times do
265
+ clipboard = @mutex.synchronize { @clipboard }
266
+ return clipboard if clipboard
267
+ sleep 0.5
268
+ end
269
+ warn 'clipboard still empty after 30s'
270
+ nil
271
+ else
272
+ @mutex.synchronize { @clipboard }
273
+ end
274
+ end
275
+
276
+ private
277
+
278
+ def read_packet type
279
+ case type
280
+ when 3 # ServerCutText
281
+ socket.read 3 # discard padding bytes
282
+ len = socket.read(4).unpack('N')[0]
283
+ @mutex.synchronize { @clipboard = socket.read len }
284
+ else
285
+ raise NotImplementedError, 'unhandled server packet type - %d' % type
286
+ end
287
+ end
288
+
289
+ def packet_reading_thread
290
+ @packet_reading_state = :loop
291
+ loop do
292
+ begin
293
+ break if @packet_reading_state != :loop
294
+ next unless IO.select [socket], nil, nil, 2
295
+ type = socket.read(1)[0]
296
+ read_packet type
297
+ rescue
298
+ warn "exception in packet_reading_thread: #{$!.class}:#{$!}"
299
+ break
300
+ end
301
+ end
302
+ @packet_reading_state = nil
303
+ end
304
+ end
305
+ end
306
+
@@ -1,6 +1,6 @@
1
- module Net
2
- class VNC
3
- VERSION = '1.0.1'
4
- end
5
- end
6
-
1
+ module Net
2
+ class VNC
3
+ VERSION = '1.1.0'
4
+ end
5
+ end
6
+
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-vnc
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
- - 0
9
8
  - 1
10
- version: 1.0.1
9
+ - 0
10
+ version: 1.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Charles Lowe
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-09-15 00:00:00 -04:00
18
+ date: 2012-06-03 00:00:00 +10:00
19
19
  default_executable:
20
20
  dependencies: []
21
21