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 +13 -9
- data/README +26 -26
- data/Rakefile +1 -1
- data/lib/cipher/des.rb +1 -1
- data/lib/net/vnc.rb +306 -306
- data/lib/net/vnc/version.rb +6 -6
- metadata +4 -4
data/ChangeLog
CHANGED
@@ -1,9 +1,13 @@
|
|
1
|
-
== 1.0
|
2
|
-
|
3
|
-
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
-
|
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
data/lib/cipher/des.rb
CHANGED
data/lib/net/vnc.rb
CHANGED
@@ -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
|
+
|
data/lib/net/vnc/version.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
module Net
|
2
|
-
class VNC
|
3
|
-
VERSION = '1.0
|
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:
|
4
|
+
hash: 19
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
- 0
|
9
8
|
- 1
|
10
|
-
|
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:
|
18
|
+
date: 2012-06-03 00:00:00 +10:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|