ruby-vnc 1.0.1 → 1.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.
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