evilcap-ruby-vnc 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYING +20 -0
- data/Changelog.rdoc +19 -0
- data/README.rdoc +33 -0
- data/Rakefile +39 -0
- data/data/keys.yaml +128 -0
- data/lib/cipher/vncdes.rb +55 -0
- data/lib/net/rfb/frame_buffer.rb +175 -0
- data/lib/net/vnc.rb +371 -0
- data/lib/net/vnc/version.rb +6 -0
- data/spec/cipher_vncdes_spec.rb +22 -0
- data/spec/net_vnc_spec.rb +136 -0
- data/spec/real_net_vnc_spec.rb +95 -0
- metadata +148 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3c6cb5b602176bf4af4687a2771a19b5d24e902e6137be88b572b0f3f98ee960
|
4
|
+
data.tar.gz: f9d1482b9dfd3454ffaa01c3c9fb0c7aba39102d29ee449fe2e84eec09894e76
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bc3bbf48ff15d28609e84b9a789fb5a767679a263bc74417e9836d4653fef1a7aebe4fe2a72b02ccc4a7f14fbe83233b6c3c3e1c698f9e4d85fb30f13f2a54be
|
7
|
+
data.tar.gz: b7d0d3538d01d2f93630d2ac158174e623c404cd0efb876a8650d5a2756e44df42266d3f20833c67935d1d87eea44cbbf5c430bc644ae9bd2bf6d9dd1b9111d8
|
data/COPYING
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007-2010 Charles Lowe
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
20
|
+
|
data/Changelog.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
== Unreleased:
|
2
|
+
|
3
|
+
- Replaced DES-algorithm with Ruby's built-in OpenSSL wrapper instead
|
4
|
+
- Parse framebuffer width/height and hostname from ServerInitialisation
|
5
|
+
- Added a project Gemfile
|
6
|
+
|
7
|
+
== 1.1.0 / 2012-06-03
|
8
|
+
|
9
|
+
- Fixes to support ruby 1.9 (jedi4ever & codemonkeyjohn).
|
10
|
+
|
11
|
+
== 1.0.1 / 2011-09-15
|
12
|
+
|
13
|
+
- Split out gemspec into separate file and use for Rakefile.
|
14
|
+
- Add homepage and rubyforge project to gemspec.
|
15
|
+
|
16
|
+
== 1.0.0 / 2008-08-29
|
17
|
+
|
18
|
+
- First public release
|
19
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,33 @@
|
|
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
|
+
= Running the tests
|
13
|
+
|
14
|
+
* Boot up the two VNC servers with `docker-compose up`.
|
15
|
+
|
16
|
+
* Run the test-suite with `bundle exec rake spec`.
|
17
|
+
|
18
|
+
= Resources
|
19
|
+
|
20
|
+
* {The Remote Framebuffer Protocol (RFC6143)}[https://tools.ietf.org/html/rfc6143]
|
21
|
+
|
22
|
+
* https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst
|
23
|
+
|
24
|
+
= Thanks
|
25
|
+
|
26
|
+
Code borrows a lot from Tim Waugh's excellent rfbplaymacro. So far all it
|
27
|
+
really offers on top of that is access to the host clipboard, and the ease
|
28
|
+
with which it can be scripted, ie taking conditional actions based on the
|
29
|
+
contents thereof.
|
30
|
+
|
31
|
+
= P.S.
|
32
|
+
|
33
|
+
This gem forked from [https://github.com/aquasync/ruby-vnc] for normally reads and additions. This is my frist gem publishing, so be patient.
|
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
task :default => :spec
|
5
|
+
|
6
|
+
desc 'Run all specs'
|
7
|
+
begin
|
8
|
+
require 'rspec/core/rake_task'
|
9
|
+
RSpec::Core::RakeTask.new(:spec)
|
10
|
+
rescue LoadError
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Run all specs and generate html spec document'
|
14
|
+
namespace :spec do
|
15
|
+
RSpec::Core::RakeTask.new :html do |t|
|
16
|
+
t.rspec_opts = ['--format html --out spec.html']
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rdoc/task'
|
21
|
+
|
22
|
+
Rake::RDocTask.new do |t|
|
23
|
+
t.rdoc_dir = 'doc'
|
24
|
+
t.rdoc_files.include 'lib/**/*.rb'
|
25
|
+
t.rdoc_files.include 'README'
|
26
|
+
t.title = 'evilcap-ruby-vnc documentation'
|
27
|
+
t.options += %w[--line-numbers --inline-source --tab-width 2]
|
28
|
+
t.main = 'README'
|
29
|
+
end
|
30
|
+
|
31
|
+
require 'rubygems/package_task'
|
32
|
+
|
33
|
+
spec = eval File.read('evilcap-ruby-vnc.gemspec')
|
34
|
+
Gem::PackageTask.new(spec) do |pkg|
|
35
|
+
pkg.need_tar = false
|
36
|
+
pkg.need_zip = false
|
37
|
+
pkg.package_dir = 'build'
|
38
|
+
end
|
39
|
+
|
data/data/keys.yaml
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
backspace: 0xff08
|
2
|
+
tab: 0xff09
|
3
|
+
linefeed: 0xff0a
|
4
|
+
clear: 0xff0b
|
5
|
+
return: 0xff0d
|
6
|
+
pause: 0xff13
|
7
|
+
scroll_lock: 0xff14
|
8
|
+
sys_req: 0xff15
|
9
|
+
escape: 0xff1b
|
10
|
+
delete: 0xffff
|
11
|
+
home: 0xff50
|
12
|
+
left: 0xff51
|
13
|
+
up: 0xff52
|
14
|
+
right: 0xff53
|
15
|
+
down: 0xff54
|
16
|
+
prior: 0xff55
|
17
|
+
page_up: 0xff55
|
18
|
+
next: 0xff56
|
19
|
+
page_down: 0xff56
|
20
|
+
end: 0xff57
|
21
|
+
begin: 0xff58
|
22
|
+
select: 0xff60
|
23
|
+
print: 0xff61
|
24
|
+
execute: 0xff62
|
25
|
+
insert: 0xff63
|
26
|
+
undo: 0xff65
|
27
|
+
redo: 0xff66
|
28
|
+
menu: 0xff67
|
29
|
+
find: 0xff68
|
30
|
+
cancel: 0xff69
|
31
|
+
help: 0xff6a
|
32
|
+
break: 0xff6b
|
33
|
+
mode_switch: 0xff7e
|
34
|
+
script_switch: 0xff7e
|
35
|
+
num_lock: 0xff7f
|
36
|
+
kp_space: 0xff80
|
37
|
+
kp_tab: 0xff89
|
38
|
+
kp_enter: 0xff8d
|
39
|
+
kp_f1: 0xff91
|
40
|
+
kp_f2: 0xff92
|
41
|
+
kp_f3: 0xff93
|
42
|
+
kp_f4: 0xff94
|
43
|
+
kp_home: 0xff95
|
44
|
+
kp_left: 0xff96
|
45
|
+
kp_up: 0xff97
|
46
|
+
kp_right: 0xff98
|
47
|
+
kp_down: 0xff99
|
48
|
+
kp_prior: 0xff9a
|
49
|
+
kp_page_up: 0xff9a
|
50
|
+
kp_next: 0xff9b
|
51
|
+
kp_page_down: 0xff9b
|
52
|
+
kp_end: 0xff9c
|
53
|
+
kp_begin: 0xff9d
|
54
|
+
kp_insert: 0xff9e
|
55
|
+
kp_delete: 0xff9f
|
56
|
+
kp_equal: 0xffbd
|
57
|
+
kp_multiply: 0xffaa
|
58
|
+
kp_add: 0xffab
|
59
|
+
kp_separator: 0xffac
|
60
|
+
kp_subtract: 0xffad
|
61
|
+
kp_decimal: 0xffae
|
62
|
+
kp_divide: 0xffaf
|
63
|
+
kp_0: 0xffb0
|
64
|
+
kp_1: 0xffb1
|
65
|
+
kp_2: 0xffb2
|
66
|
+
kp_3: 0xffb3
|
67
|
+
kp_4: 0xffb4
|
68
|
+
kp_5: 0xffb5
|
69
|
+
kp_6: 0xffb6
|
70
|
+
kp_7: 0xffb7
|
71
|
+
kp_8: 0xffb8
|
72
|
+
kp_9: 0xffb9
|
73
|
+
f1: 0xffbe
|
74
|
+
f2: 0xffbf
|
75
|
+
f3: 0xffc0
|
76
|
+
f4: 0xffc1
|
77
|
+
f5: 0xffc2
|
78
|
+
f6: 0xffc3
|
79
|
+
f7: 0xffc4
|
80
|
+
f8: 0xffc5
|
81
|
+
f9: 0xffc6
|
82
|
+
f10: 0xffc7
|
83
|
+
f11: 0xffc8
|
84
|
+
f12: 0xffc9
|
85
|
+
f13: 0xffca
|
86
|
+
f14: 0xffcb
|
87
|
+
f15: 0xffcc
|
88
|
+
f16: 0xffcd
|
89
|
+
f17: 0xffce
|
90
|
+
f18: 0xffcf
|
91
|
+
f19: 0xffd0
|
92
|
+
f20: 0xffd1
|
93
|
+
f21: 0xffd2
|
94
|
+
f22: 0xffd3
|
95
|
+
f23: 0xffd4
|
96
|
+
f24: 0xffd5
|
97
|
+
f25: 0xffd6
|
98
|
+
f26: 0xffd7
|
99
|
+
f27: 0xffd8
|
100
|
+
f28: 0xffd9
|
101
|
+
f29: 0xffda
|
102
|
+
f30: 0xffdb
|
103
|
+
f31: 0xffdc
|
104
|
+
f32: 0xffdd
|
105
|
+
f33: 0xffde
|
106
|
+
f34: 0xffdf
|
107
|
+
f35: 0xffe0
|
108
|
+
left_shift: 0xffe1
|
109
|
+
right_shift: 0xffe2
|
110
|
+
left_control: 0xffe3
|
111
|
+
right_control: 0xffe4
|
112
|
+
caps_lock: 0xffe5
|
113
|
+
shift_lock: 0xffe6
|
114
|
+
left_meta: 0xffe7
|
115
|
+
right_meta: 0xffe8
|
116
|
+
left_alt: 0xffe9
|
117
|
+
right_alt: 0xffea
|
118
|
+
left_super: 0xffeb
|
119
|
+
right_super: 0xffec
|
120
|
+
left_hyper: 0xffed
|
121
|
+
right_hyper: 0xffee
|
122
|
+
# some convenience aliases
|
123
|
+
shift: 0xffe1
|
124
|
+
control: 0xffe3
|
125
|
+
meta: 0xffe7
|
126
|
+
alt: 0xffe9
|
127
|
+
super: 0xffeb
|
128
|
+
hyper: 0xffed
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
# MIT-licensed code by Andrew Dorofeyev
|
4
|
+
# from https://github.com/d-theus/vncrec-ruby
|
5
|
+
|
6
|
+
# The server sends a random 16-byte challenge:
|
7
|
+
#
|
8
|
+
# +--------------+--------------+-------------+
|
9
|
+
# | No. of bytes | Type [Value] | Description |
|
10
|
+
# +--------------+--------------+-------------+
|
11
|
+
# | 16 | U8 | challenge |
|
12
|
+
# +--------------+--------------+-------------+
|
13
|
+
#
|
14
|
+
# The client encrypts the challenge with DES (ECB), using a password supplied
|
15
|
+
# by the user as the key. To form the key, the password is truncated
|
16
|
+
# to eight characters, or padded with null bytes on the right.
|
17
|
+
# Actually, each byte is also reversed. Challenge string is split
|
18
|
+
# in two chunks of 8 bytes, which are encrypted separately and clashed together
|
19
|
+
# again. The client then sends the resulting 16-byte response:
|
20
|
+
#
|
21
|
+
# +--------------+--------------+-------------+
|
22
|
+
# | No. of bytes | Type [Value] | Description |
|
23
|
+
# +--------------+--------------+-------------+
|
24
|
+
# | 16 | U8 | response |
|
25
|
+
# +--------------+--------------+-------------+
|
26
|
+
#
|
27
|
+
# The protocol continues with the SecurityResult message.
|
28
|
+
|
29
|
+
module Cipher
|
30
|
+
class VNCDES
|
31
|
+
attr_reader :key
|
32
|
+
|
33
|
+
def initialize(key)
|
34
|
+
@key = normalized(key[0..7])
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def encrypt(challenge)
|
39
|
+
chunks = [challenge.slice(0, 8), challenge.slice(8, 8)]
|
40
|
+
cipher = OpenSSL::Cipher::DES.new(:ECB)
|
41
|
+
cipher.encrypt
|
42
|
+
cipher.key = self.key
|
43
|
+
chunks.reduce('') { |a, e| cipher.reset; a << cipher.update(e) }.force_encoding('UTF-8')
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def normalized(key)
|
49
|
+
rev = ->(n) { (0...8).reduce(0) { |a, e| a + 2**e * n[7 - e] } }
|
50
|
+
inv = key.each_byte.map { |b| rev[b].chr }.join
|
51
|
+
inv.ljust(8, "\x00")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'vncrec'
|
2
|
+
require 'chunky_png'
|
3
|
+
|
4
|
+
module Net::RFB
|
5
|
+
|
6
|
+
# Manage FrameBuffer pixel data for RFB protocol
|
7
|
+
# This is a little wrapper for the `Proxy` class in vncrec-ruby https://github.com/d-theus/vncrec-ruby
|
8
|
+
class FrameBuffer
|
9
|
+
class VNCRecAuthStub
|
10
|
+
def initialize(io, *options)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param io [IO, #read, #sysread, #syswrite, #read_nonblock] string stream from VNC server.
|
15
|
+
# @param w [Integer] width of the screen area
|
16
|
+
# @param h [Integer] height of the screen area
|
17
|
+
# @param bpp [Symbol] bits per pixel (BGR8 or BGRA)
|
18
|
+
# @param encodings [Array<Symbol>] encoding (RAW or HEXTILE or ZRLE) default: RAW
|
19
|
+
def initialize(io, w, h, bpp, encodings=nil)
|
20
|
+
@cb_mutex = Monitor.new
|
21
|
+
@cb_cv = @cb_mutex.new_cond
|
22
|
+
|
23
|
+
@encodings = encodings
|
24
|
+
|
25
|
+
@vnc_rec_pix_fmt = convert_to_vnc_rec_pix_fmt bpp
|
26
|
+
|
27
|
+
@proxy = VNCRec::RFB::Proxy.new(io, nil, nil, nil, [VNCRecAuthStub, nil])
|
28
|
+
@proxy.prepare_framebuffer w, h, @vnc_rec_pix_fmt[:bpp]
|
29
|
+
end
|
30
|
+
|
31
|
+
def send_initial_data
|
32
|
+
# set encoding
|
33
|
+
unless self.set_encodings @encodings
|
34
|
+
raise 'Error while setting encoding'
|
35
|
+
end
|
36
|
+
|
37
|
+
# set pixel format
|
38
|
+
self.set_pixel_format @vnc_rec_pix_fmt
|
39
|
+
|
40
|
+
# request all pixel data
|
41
|
+
self.request_update_fb incremental: false
|
42
|
+
end
|
43
|
+
|
44
|
+
# raw pixel data of screen
|
45
|
+
def pixel_data
|
46
|
+
@proxy.data
|
47
|
+
end
|
48
|
+
|
49
|
+
# 32bit RGBA pixel data of screen
|
50
|
+
def rgba_pixel_data
|
51
|
+
px = self.pixel_data
|
52
|
+
raise 'Error in get raw pixel_data.' unless px
|
53
|
+
self.class.convert_raw_pixel_data_to_rgba px, @vnc_rec_pix_fmt[:string]
|
54
|
+
end
|
55
|
+
|
56
|
+
# convert raw pixel data to 32bit RGBA values according to VNC pixel format
|
57
|
+
# @param px [String] binary pixel data
|
58
|
+
# @param pix_fmt [String] pixel format (bgra, bgr8)
|
59
|
+
# @return [Array<Integer>] array of 32bit pixel data
|
60
|
+
def self.convert_raw_pixel_data_to_rgba(px, pix_fmt)
|
61
|
+
# see https://github.com/d-theus/vncrec-ruby/blob/master/lib/vncrec/constants.rb
|
62
|
+
case pix_fmt.to_s
|
63
|
+
when 'bgra'
|
64
|
+
# convert 32bit BGRA -> 32bit RGBA
|
65
|
+
px = px.unpack("V*")
|
66
|
+
px.map! { |p| (p << 8) | 0xff }
|
67
|
+
when 'bgr8'
|
68
|
+
# convert 8bit BGR -> 32bit RGBA
|
69
|
+
px = px.unpack("C*")
|
70
|
+
px.map! do |p|
|
71
|
+
r = (p & 0b00000111)
|
72
|
+
g = (p & 0b00111000) >> 3
|
73
|
+
b = (p & 0b11000000) >> 6
|
74
|
+
((r * 36) << 24) | ((g * 36) << 16) | ((b * 85) << 8) | 0xff
|
75
|
+
end
|
76
|
+
else
|
77
|
+
raise "unknown pixel format #{pix_fmt.inspect}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Set a way that server should use to represent pixel data
|
82
|
+
# @param [Symbol|String] pixel format:
|
83
|
+
# * :BGR8
|
84
|
+
# * :BGRA
|
85
|
+
def set_pixel_format(format)
|
86
|
+
@proxy.set_pixel_format convert_to_vnc_rec_pix_fmt(format)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Set way of encoding video frames.
|
90
|
+
# @param encodings [Symbol|String] list of encoding of video data used to transfer.
|
91
|
+
# * :RAW
|
92
|
+
# * :HEXTILE
|
93
|
+
# * :ZRLE
|
94
|
+
def set_encodings(*encodings)
|
95
|
+
@proxy.set_encodings [encodings].flatten.compact.map{|sym| VNCRec::const_get "ENC_#{sym}"}
|
96
|
+
end
|
97
|
+
|
98
|
+
# Send request for update framebuffer.
|
99
|
+
# if block given, called it with pixel data after the response received.
|
100
|
+
# @param [Boolean] incremental incremental, request just difference
|
101
|
+
# between previous and current framebuffer state.
|
102
|
+
# @param x [Integer]
|
103
|
+
# @param y [Integer]
|
104
|
+
# @param w [Integer]
|
105
|
+
# @param h [Integer]
|
106
|
+
# @param wait_for_response [Boolean] if true, wait for a FramebufferUpdate response
|
107
|
+
def request_update_fb(incremental: true, x: nil, y: nil, w: nil, h: nil, wait_for_response: false)
|
108
|
+
@cb_mutex.synchronize do
|
109
|
+
@proxy.fb_update_request incremental ? 1 : 0, x||0, y||0, w||@proxy.w, h||@proxy.h
|
110
|
+
|
111
|
+
if wait_for_response
|
112
|
+
@cb_cv.wait
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def handle_response(t)
|
118
|
+
case t
|
119
|
+
when 0 # ----------------------------------------------- FramebufferUpdate
|
120
|
+
ret = handle_fb_update
|
121
|
+
@cb_mutex.synchronize do
|
122
|
+
@cb_cv.broadcast
|
123
|
+
end
|
124
|
+
return ret
|
125
|
+
when 1 # --------------------------------------------- SetColourMapEntries
|
126
|
+
return handle_set_colormap_entries
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# save current screen pixel data as PNG image
|
131
|
+
# @param dest [String|IO|nil] destination file path, or IO-object, or nil
|
132
|
+
# @return [String] PNG binary data as string when dest is null
|
133
|
+
# [true] else case
|
134
|
+
def save_pixel_data_as_png(dest=nil)
|
135
|
+
self.request_update_fb(wait_for_response: true)
|
136
|
+
|
137
|
+
image = ChunkyPNG::Image.new(@proxy.w, @proxy.h, rgba_pixel_data)
|
138
|
+
|
139
|
+
if dest.is_a? IO
|
140
|
+
# write to IO-object
|
141
|
+
image.write dest
|
142
|
+
elsif dest.is_a?(String) || dest.is_a?(Pathname)
|
143
|
+
# write to file
|
144
|
+
image.save dest.to_s
|
145
|
+
elsif dest.nil?
|
146
|
+
# return binary data as string
|
147
|
+
return image.to_blob
|
148
|
+
else
|
149
|
+
raise ArgumentError, "Unsupported destination type #{dest.inspect}"
|
150
|
+
end
|
151
|
+
true
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
# convert pixel_format symbol to VNCRec::PIX_FMT_XXX symbol.
|
157
|
+
# @param pix_fmt [Symbol|String] bits per pixel (BGR8 or BGRA)
|
158
|
+
def convert_to_vnc_rec_pix_fmt(pix_fmt)
|
159
|
+
return pix_fmt if pix_fmt.is_a?(Hash)
|
160
|
+
pf = pix_fmt.to_s.prepend('PIX_FMT_').upcase.to_sym
|
161
|
+
raise ArgumentError, "Unsupported pixel_format '#{pix_fmt}', now supported values are: BGR8, BGRA" unless VNCRec.const_defined? pf
|
162
|
+
VNCRec.const_get(pf)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Receives data and applies diffs(if incremental) to the @data
|
166
|
+
def handle_fb_update
|
167
|
+
@proxy.handle_fb_update
|
168
|
+
end
|
169
|
+
|
170
|
+
# @return [Array] palette
|
171
|
+
def handle_set_colormap_entries
|
172
|
+
@proxy.handle_colormap_update
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
data/lib/net/vnc.rb
ADDED
@@ -0,0 +1,371 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'yaml'
|
3
|
+
require 'thread'
|
4
|
+
require 'cipher/vncdes'
|
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
|
+
#
|
16
|
+
# require 'net/vnc'
|
17
|
+
# Net::VNC.open 'localhost:0', :shared => true, :password => 'mypass' do |vnc|
|
18
|
+
# vnc.pointer_move 10, 10
|
19
|
+
# vnc.type 'xclock'
|
20
|
+
# vnc.key_press :return
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# = TODO
|
24
|
+
#
|
25
|
+
# * The server read loop seems a bit iffy. Not sure how best to do it.
|
26
|
+
# * Should probably be changed to be more of a lower-level protocol wrapping thing, with the
|
27
|
+
# actual VNCClient sitting on top of that. all it should do is read/write the packets over
|
28
|
+
# the socket.
|
29
|
+
#
|
30
|
+
class VNC
|
31
|
+
class PointerState
|
32
|
+
attr_reader :x, :y, :button
|
33
|
+
|
34
|
+
def initialize vnc
|
35
|
+
@x = @y = @button = 0
|
36
|
+
@vnc = vnc
|
37
|
+
end
|
38
|
+
|
39
|
+
# could have the same for x=, and y=
|
40
|
+
def button= button
|
41
|
+
@button = button
|
42
|
+
refresh
|
43
|
+
end
|
44
|
+
|
45
|
+
def update x, y, button=@button
|
46
|
+
@x, @y, @button = x, y, button
|
47
|
+
refresh
|
48
|
+
end
|
49
|
+
|
50
|
+
def refresh
|
51
|
+
packet = 0.chr * 6
|
52
|
+
packet[0] = 5.chr
|
53
|
+
packet[1] = button.chr
|
54
|
+
packet[2, 2] = [x].pack 'n'
|
55
|
+
packet[4, 2] = [y].pack 'n'
|
56
|
+
@vnc.socket.write packet
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
BASE_PORT = 5900
|
61
|
+
CHALLENGE_SIZE = 16
|
62
|
+
DEFAULT_OPTIONS = {
|
63
|
+
:shared => false,
|
64
|
+
:wait => 0.1,
|
65
|
+
:pix_fmt => :BGRA,
|
66
|
+
:encoding => :RAW
|
67
|
+
}
|
68
|
+
|
69
|
+
keys_file = File.dirname(__FILE__) + '/../../data/keys.yaml'
|
70
|
+
KEY_MAP = YAML.load_file(keys_file).inject({}) { |h, (k, v)| h.update k.to_sym => v }
|
71
|
+
def KEY_MAP.[] key
|
72
|
+
super or raise ArgumentError.new('Invalid key name - %s' % key)
|
73
|
+
end
|
74
|
+
|
75
|
+
attr_reader :server, :display, :options, :socket, :pointer, :desktop_name
|
76
|
+
|
77
|
+
def initialize display=':0', options={}
|
78
|
+
@server = 'localhost'
|
79
|
+
if display =~ /^(.*)(:\d+)$/
|
80
|
+
@server, display = $1, $2
|
81
|
+
end
|
82
|
+
@display = display[1..-1].to_i
|
83
|
+
@desktop_name = nil
|
84
|
+
@options = DEFAULT_OPTIONS.merge options
|
85
|
+
@clipboard = nil
|
86
|
+
@fb = nil
|
87
|
+
@pointer = PointerState.new self
|
88
|
+
@mutex = Mutex.new
|
89
|
+
connect
|
90
|
+
@packet_reading_state = nil
|
91
|
+
@packet_reading_thread = Thread.new { packet_reading_thread }
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.open display=':0', options={}
|
95
|
+
vnc = new display, options
|
96
|
+
if block_given?
|
97
|
+
begin
|
98
|
+
yield vnc
|
99
|
+
ensure
|
100
|
+
vnc.close
|
101
|
+
end
|
102
|
+
else
|
103
|
+
vnc
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def port
|
108
|
+
BASE_PORT + @display
|
109
|
+
end
|
110
|
+
|
111
|
+
def connect
|
112
|
+
@socket = TCPSocket.open(server, port)
|
113
|
+
unless socket.read(12) =~ /^RFB (\d{3}.\d{3})\n$/
|
114
|
+
raise 'invalid server response'
|
115
|
+
end
|
116
|
+
@server_version = $1
|
117
|
+
socket.write "RFB 003.003\n"
|
118
|
+
data = socket.read(4)
|
119
|
+
auth = data.to_s.unpack('N')[0]
|
120
|
+
case auth
|
121
|
+
when 0, nil
|
122
|
+
raise 'connection failed'
|
123
|
+
when 1
|
124
|
+
# ok...
|
125
|
+
when 2
|
126
|
+
password = @options[:password] or raise 'Need to authenticate but no password given'
|
127
|
+
challenge = socket.read CHALLENGE_SIZE
|
128
|
+
response = Cipher::VNCDES.new(password).encrypt(challenge)
|
129
|
+
socket.write response
|
130
|
+
ok = socket.read(4).to_s.unpack('N')[0]
|
131
|
+
raise 'Unable to authenticate - %p' % ok unless ok == 0
|
132
|
+
else
|
133
|
+
raise 'Unknown authentication scheme - %d' % auth
|
134
|
+
end
|
135
|
+
|
136
|
+
# ClientInitialisation
|
137
|
+
socket.write((options[:shared] ? 1 : 0).chr)
|
138
|
+
|
139
|
+
# ServerInitialisation
|
140
|
+
@framebuffer_width = socket.read(2).to_s.unpack('n')[0].to_i
|
141
|
+
@framebuffer_height = socket.read(2).to_s.unpack('n')[0].to_i
|
142
|
+
|
143
|
+
# TODO: parse this.
|
144
|
+
pixel_format = socket.read(16)
|
145
|
+
|
146
|
+
# read the name in byte chunks of 20
|
147
|
+
name_length = socket.read(4).to_s.unpack('N')[0]
|
148
|
+
@desktop_name = [].tap do |it|
|
149
|
+
while name_length > 0
|
150
|
+
len = [20, name_length].min
|
151
|
+
it << socket.read(len)
|
152
|
+
name_length -= len
|
153
|
+
end
|
154
|
+
end.join
|
155
|
+
|
156
|
+
_load_frame_buffer
|
157
|
+
end
|
158
|
+
|
159
|
+
# this types +text+ on the server
|
160
|
+
def type text, options={}
|
161
|
+
packet = 0.chr * 8
|
162
|
+
packet[0] = 4.chr
|
163
|
+
text.split(//).each do |char|
|
164
|
+
packet[7] = char[0]
|
165
|
+
packet[1] = 1.chr
|
166
|
+
socket.write packet
|
167
|
+
packet[1] = 0.chr
|
168
|
+
socket.write packet
|
169
|
+
end
|
170
|
+
wait options
|
171
|
+
end
|
172
|
+
|
173
|
+
# this takes an array of keys, and successively holds each down then lifts them up in
|
174
|
+
# reverse order.
|
175
|
+
# FIXME: should wait. can't recurse in that case.
|
176
|
+
def key_press(*args)
|
177
|
+
options = Hash === args.last ? args.pop : {}
|
178
|
+
keys = args
|
179
|
+
raise ArgumentError, 'Must have at least one key argument' if keys.empty?
|
180
|
+
begin
|
181
|
+
key_down keys.first
|
182
|
+
if keys.length == 1
|
183
|
+
yield if block_given?
|
184
|
+
else
|
185
|
+
key_press(*(keys[1..-1] + [options]))
|
186
|
+
end
|
187
|
+
ensure
|
188
|
+
key_up keys.first
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def get_key_code(which)
|
193
|
+
case which
|
194
|
+
when String
|
195
|
+
if which.length != 1
|
196
|
+
raise ArgumentError, 'can only get key_code of single character strings'
|
197
|
+
end
|
198
|
+
which[0].ord
|
199
|
+
when Symbol
|
200
|
+
KEY_MAP[which]
|
201
|
+
when Integer
|
202
|
+
which
|
203
|
+
else
|
204
|
+
raise ArgumentError, "unsupported key value: #{which.inspect}"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
private :get_key_code
|
208
|
+
|
209
|
+
def key_down which, options={}
|
210
|
+
packet = 0.chr * 8
|
211
|
+
packet[0] = 4.chr
|
212
|
+
key_code = get_key_code which
|
213
|
+
packet[4, 4] = [key_code].pack('N')
|
214
|
+
packet[1] = 1.chr
|
215
|
+
socket.write packet
|
216
|
+
wait options
|
217
|
+
end
|
218
|
+
|
219
|
+
def key_up which, options={}
|
220
|
+
packet = 0.chr * 8
|
221
|
+
packet[0] = 4.chr
|
222
|
+
key_code = get_key_code which
|
223
|
+
packet[4, 4] = [key_code].pack('N')
|
224
|
+
packet[1] = 0.chr
|
225
|
+
socket.write packet
|
226
|
+
wait options
|
227
|
+
end
|
228
|
+
|
229
|
+
def pointer_move x, y, options={}
|
230
|
+
# options[:relative]
|
231
|
+
pointer.update x, y
|
232
|
+
wait options
|
233
|
+
end
|
234
|
+
|
235
|
+
BUTTON_MAP = {
|
236
|
+
:left => 0
|
237
|
+
}
|
238
|
+
|
239
|
+
def button_press button=:left, options={}
|
240
|
+
begin
|
241
|
+
button_down button, options
|
242
|
+
yield if block_given?
|
243
|
+
ensure
|
244
|
+
button_up button, options
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def button_down which=:left, options={}
|
249
|
+
button = BUTTON_MAP[which] || which
|
250
|
+
raise ArgumentError, 'Invalid button - %p' % which unless (0..2) === button
|
251
|
+
pointer.button |= 1 << button
|
252
|
+
wait options
|
253
|
+
end
|
254
|
+
|
255
|
+
def button_up which=:left, options={}
|
256
|
+
button = BUTTON_MAP[which] || which
|
257
|
+
raise ArgumentError, 'Invalid button - %p' % which unless (0..2) === button
|
258
|
+
pointer.button &= ~(1 << button)
|
259
|
+
wait options
|
260
|
+
end
|
261
|
+
|
262
|
+
# take screenshot as PNG image
|
263
|
+
# @param dest [String|IO|nil] destination file path, or IO-object, or nil
|
264
|
+
# @return [String] PNG binary data as string when dest is null
|
265
|
+
# [true] else case
|
266
|
+
def take_screenshot(dest=nil)
|
267
|
+
fb = _load_frame_buffer # on-demand loading
|
268
|
+
fb.save_pixel_data_as_png dest
|
269
|
+
end
|
270
|
+
|
271
|
+
def wait options={}
|
272
|
+
sleep options[:wait] || @options[:wait]
|
273
|
+
end
|
274
|
+
|
275
|
+
def close
|
276
|
+
# destroy packet reading thread
|
277
|
+
if @packet_reading_state == :loop
|
278
|
+
@packet_reading_state = :stop
|
279
|
+
while @packet_reading_state
|
280
|
+
# do nothing
|
281
|
+
end
|
282
|
+
end
|
283
|
+
socket.close
|
284
|
+
end
|
285
|
+
|
286
|
+
def reconnect
|
287
|
+
60.times do
|
288
|
+
if @packet_reading_state.nil?
|
289
|
+
connect
|
290
|
+
@packet_reading_thread = Thread.new { packet_reading_thread }
|
291
|
+
return true
|
292
|
+
end
|
293
|
+
sleep 0.5
|
294
|
+
end
|
295
|
+
warn 'reconnect failed because packet reading state had not been stopped for 30 seconds.'
|
296
|
+
false
|
297
|
+
end
|
298
|
+
|
299
|
+
def clipboard
|
300
|
+
if block_given?
|
301
|
+
@clipboard = nil
|
302
|
+
yield
|
303
|
+
60.times do
|
304
|
+
clipboard = @mutex.synchronize { @clipboard }
|
305
|
+
return clipboard if clipboard
|
306
|
+
sleep 0.5
|
307
|
+
end
|
308
|
+
warn 'clipboard still empty after 30s'
|
309
|
+
nil
|
310
|
+
else
|
311
|
+
@mutex.synchronize { @clipboard }
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def clipboard= text
|
316
|
+
text = text.to_s.gsub(/\R/, "\n") # eol of ClientCutText's text is LF
|
317
|
+
byte_size = text.to_s.bytes.size
|
318
|
+
packet = 0.chr * (8 + byte_size)
|
319
|
+
packet[0] = 6.chr # message-type: 6 (ClientCutText)
|
320
|
+
packet[4, 4] = [byte_size].pack('N') # length
|
321
|
+
packet[8, byte_size] = text
|
322
|
+
socket.write(packet)
|
323
|
+
@clipboard = text
|
324
|
+
end
|
325
|
+
|
326
|
+
private
|
327
|
+
|
328
|
+
def read_packet type
|
329
|
+
case type
|
330
|
+
when 0 # ----------------------------------------------- FramebufferUpdate
|
331
|
+
@fb.handle_response type if @fb
|
332
|
+
when 1 # --------------------------------------------- SetColourMapEntries
|
333
|
+
@fb.handle_response type if @fb
|
334
|
+
when 2 # ------------------------------------------------------------ Bell
|
335
|
+
nil # not support
|
336
|
+
when 3 # --------------------------------------------------- ServerCutText
|
337
|
+
socket.read 3 # discard padding bytes
|
338
|
+
len = socket.read(4).unpack('N')[0]
|
339
|
+
@mutex.synchronize { @clipboard = socket.read len }
|
340
|
+
else
|
341
|
+
warn 'unhandled server packet type - %d' % type
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def packet_reading_thread
|
346
|
+
@packet_reading_state = :loop
|
347
|
+
loop do
|
348
|
+
begin
|
349
|
+
break if @packet_reading_state != :loop
|
350
|
+
next unless IO.select [socket], nil, nil, 2
|
351
|
+
type = socket.read(1)[0]
|
352
|
+
read_packet type.ord
|
353
|
+
rescue
|
354
|
+
warn "exception in packet_reading_thread: #{$!.class}:#{$!}\n#{$!.backtrace}"
|
355
|
+
break
|
356
|
+
end
|
357
|
+
end
|
358
|
+
@packet_reading_state = nil
|
359
|
+
end
|
360
|
+
|
361
|
+
def _load_frame_buffer
|
362
|
+
unless @fb
|
363
|
+
require 'net/rfb/frame_buffer'
|
364
|
+
|
365
|
+
@fb = Net::RFB::FrameBuffer.new @socket, @framebuffer_width, @framebuffer_height, @options[:pix_fmt], @options[:encoding]
|
366
|
+
@fb.send_initial_data
|
367
|
+
end
|
368
|
+
@fb
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'cipher/vncdes'
|
3
|
+
|
4
|
+
RSpec.describe Cipher::VNCDES do
|
5
|
+
it 'should pad key with zeroes if key is shorter than 8 characters' do
|
6
|
+
key = Cipher::VNCDES.new('test').key
|
7
|
+
|
8
|
+
expect(key.size).to eq 8
|
9
|
+
expect(key[4..7]).to eq(0.chr * 4)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should cut the key if the key is longer than 8 characters' do
|
13
|
+
expect(Cipher::VNCDES.new('iamdefinitelylongerthan8characters').key.size).to eq 8
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should correctly encrypt keys' do
|
17
|
+
encrypted_string = Cipher::VNCDES.new('matzisnicesowearenice').encrypt("\x9D\xBBU\n\x05b\x96L \b'&\x18\xCE(\xD8")
|
18
|
+
expect(encrypted_string.encoding.to_s).to eq 'UTF-8'
|
19
|
+
expect(encrypted_string.size).to eq 16
|
20
|
+
expect(encrypted_string).to eq "2\x95\xA7\xAE\xD4A\xF3\xDCt\x82d\e\xAE\x8A\xB9c"
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'net/vnc'
|
3
|
+
|
4
|
+
=begin
|
5
|
+
class SocketMock
|
6
|
+
class MockIOError < IOError
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
# this can be used to make detailed assertions
|
11
|
+
@trace = []
|
12
|
+
@read_buf = ''
|
13
|
+
@write_buf = ''
|
14
|
+
end
|
15
|
+
|
16
|
+
def read len
|
17
|
+
@trace << [:read, len]
|
18
|
+
if @read_buf.length < len
|
19
|
+
msg = 'bad socket read sequence - read(%d) but only %d byte(s) available' % [len, @read_buf.length]
|
20
|
+
raise MockIOError, msg
|
21
|
+
end
|
22
|
+
@read_buf.slice! 0, len
|
23
|
+
end
|
24
|
+
|
25
|
+
def write data
|
26
|
+
@trace << [:write, data]
|
27
|
+
@write_buf << data
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class VNCServerSocketMock < SocketMock
|
32
|
+
TICK_TIME = 0.1
|
33
|
+
|
34
|
+
def initialize(&block)
|
35
|
+
super
|
36
|
+
|
37
|
+
@pending_read = nil
|
38
|
+
obj = self
|
39
|
+
@t = Thread.new { block.call obj; @pending_read = nil }
|
40
|
+
100.times do |i|
|
41
|
+
break if @pending_read
|
42
|
+
if i == 99
|
43
|
+
msg = 'blah'
|
44
|
+
raise MockIOError, msg
|
45
|
+
end
|
46
|
+
sleep TICK_TIME
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def run
|
51
|
+
yield
|
52
|
+
100.times do |i|
|
53
|
+
break unless @pending_read
|
54
|
+
if i == 99
|
55
|
+
msg = 'missing socket write sequence'
|
56
|
+
raise MockIOError, msg
|
57
|
+
end
|
58
|
+
sleep TICK_TIME
|
59
|
+
end
|
60
|
+
raise 'wrote to much' if @write_buf.length != 0
|
61
|
+
raise 'did not read enough' if @read_buf.length != 0
|
62
|
+
end
|
63
|
+
|
64
|
+
def read len
|
65
|
+
@trace << [:read, len]
|
66
|
+
100.times do |i|
|
67
|
+
break if @read_buf.length >= len
|
68
|
+
if i == 99
|
69
|
+
msg = 'timeout during socket read sequence - read(%d) but only %d byte(s) available' % [len, @read_buf.length]
|
70
|
+
raise MockIOError, msg
|
71
|
+
end
|
72
|
+
sleep TICK_TIME
|
73
|
+
end
|
74
|
+
@read_buf.slice! 0, len
|
75
|
+
end
|
76
|
+
|
77
|
+
def write data
|
78
|
+
unless @read_buf.empty?
|
79
|
+
raise MockIOError, 'tried to write with non empty read buffer - (%p, %p)' % [@read_buf, data]
|
80
|
+
end
|
81
|
+
super
|
82
|
+
if !@pending_read
|
83
|
+
raise MockIOError, "wrote to socket but server is not expecting it"
|
84
|
+
end
|
85
|
+
if @write_buf.length >= @pending_read
|
86
|
+
@pending_read = @write_buf.slice!(0, @pending_read)
|
87
|
+
sleep TICK_TIME while @pending_read.is_a? String
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def provide_data data
|
92
|
+
@read_buf << data
|
93
|
+
end
|
94
|
+
|
95
|
+
def expect_data len
|
96
|
+
@pending_read = len
|
97
|
+
sleep TICK_TIME while @pending_read.is_a? Fixnum
|
98
|
+
@pending_read
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe 'Net::VNC' do
|
103
|
+
VNC = Net::VNC
|
104
|
+
|
105
|
+
it 'should do something' do
|
106
|
+
=begin
|
107
|
+
socket_mock.should_receive(:read).once.ordered.with(12).and_return("RFB 003.003\n")
|
108
|
+
socket_mock.should_receive(:write).once.ordered.with(/^RFB (\d{3}.\d{3})\n$/)
|
109
|
+
socket_mock.should_receive(:read).once.ordered.with(4).and_return([1].pack('N'))
|
110
|
+
socket_mock.should_receive(:write).once.ordered.with("\000")
|
111
|
+
socket_mock.should_receive(:read).once.ordered.with(20).and_return('')
|
112
|
+
socket_mock.should_receive(:read).once.ordered.with(4).and_return([0].pack('N'))
|
113
|
+
#m = mock('my mock')
|
114
|
+
#m.should_receive(:test1).ordered.once.with('argument').and_return(1)
|
115
|
+
#m.should_receive(:test2).ordered.once.with('argument').and_return(2)
|
116
|
+
#p m.test1('argument')
|
117
|
+
#p m.test2('argument')
|
118
|
+
vnc = VNC.open('192.168.0.1:0')
|
119
|
+
#=end
|
120
|
+
|
121
|
+
server = VNCServerSocketMock.new do |s|
|
122
|
+
s.provide_data "RFB 003.003\n"
|
123
|
+
p :read => s.expect_data(12)
|
124
|
+
s.provide_data [1].pack('N')
|
125
|
+
p :read => s.expect_data(1)
|
126
|
+
s.provide_data ' ' * 20
|
127
|
+
s.provide_data [0].pack('N')
|
128
|
+
end
|
129
|
+
server.run do
|
130
|
+
TCPSocket.should_receive(:open).with('192.168.0.1', 5900).and_return(server)
|
131
|
+
vnc = VNC.open('192.168.0.1:0')
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
=end
|
136
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'net/vnc'
|
3
|
+
|
4
|
+
RSpec.describe Net::VNC do
|
5
|
+
NO_AUTH_SERVER_DISPLAY = ':1'
|
6
|
+
WITH_AUTH_SERVER_DISPLAY = ':2'
|
7
|
+
|
8
|
+
context 'no auth' do
|
9
|
+
it 'should connect with no password' do
|
10
|
+
Net::VNC.open(NO_AUTH_SERVER_DISPLAY) do |vnc|
|
11
|
+
vnc.pointer_move(10, 15)
|
12
|
+
expect(vnc.pointer.x).to eq 10
|
13
|
+
expect(vnc.pointer.y).to eq 15
|
14
|
+
|
15
|
+
vnc.pointer_move(20, 25)
|
16
|
+
expect(vnc.pointer.x).to eq 20
|
17
|
+
expect(vnc.pointer.y).to eq 25
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should connect with password even though it is not needed' do
|
22
|
+
Net::VNC.open(NO_AUTH_SERVER_DISPLAY, password: 'password') do |vnc|
|
23
|
+
vnc.pointer_move(10, 15)
|
24
|
+
expect(vnc.pointer.x).to eq 10
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with auth' do
|
30
|
+
it 'should connect with a password' do
|
31
|
+
Net::VNC.open(WITH_AUTH_SERVER_DISPLAY, password: 'matzisnicesowearenice') do |vnc|
|
32
|
+
vnc.pointer_move(10, 15)
|
33
|
+
expect(vnc.pointer.x).to eq 10
|
34
|
+
expect(vnc.pointer.y).to eq 15
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should give error with a wrong password' do
|
39
|
+
expect { Net::VNC.open(WITH_AUTH_SERVER_DISPLAY, password: 'wrongPasssword') }.to raise_error(RuntimeError, 'Unable to authenticate - 1')
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should give error with no password' do
|
43
|
+
expect { Net::VNC.open(WITH_AUTH_SERVER_DISPLAY) }.to raise_error(RuntimeError, 'Need to authenticate but no password given')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'screenshotting' do
|
48
|
+
def verify_screenshot(input)
|
49
|
+
image_size = ImageSize.path(input)
|
50
|
+
expect(image_size.format).to eq :png
|
51
|
+
expect(image_size.width).to eq 1366
|
52
|
+
expect(image_size.height).to eq 768
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should allow you to take a screenshot with a path' do
|
56
|
+
Tempfile.open('ruby-vnc-spec') do |screenshotfile|
|
57
|
+
Net::VNC.open(NO_AUTH_SERVER_DISPLAY) do |vnc|
|
58
|
+
vnc.pointer_move(10, 15)
|
59
|
+
vnc.take_screenshot(screenshotfile.path)
|
60
|
+
end
|
61
|
+
verify_screenshot(screenshotfile.path)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should allow you to take a screenshot with a blob' do
|
66
|
+
Tempfile.open('ruby-vnc-spec-blob') do |screenshotfile|
|
67
|
+
vnc = Net::VNC.open(NO_AUTH_SERVER_DISPLAY)
|
68
|
+
begin
|
69
|
+
vnc.pointer_move(10, 15)
|
70
|
+
blob = vnc.take_screenshot(nil)
|
71
|
+
screenshotfile.write(blob)
|
72
|
+
ensure
|
73
|
+
vnc.close
|
74
|
+
end
|
75
|
+
|
76
|
+
verify_screenshot(screenshotfile.path)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should allow you to take a screenshot with a IO-object' do
|
81
|
+
screenshotfile = File.new("out.png", "w")
|
82
|
+
|
83
|
+
begin
|
84
|
+
Net::VNC.open(NO_AUTH_SERVER_DISPLAY) do |vnc|
|
85
|
+
vnc.pointer_move(10, 15)
|
86
|
+
vnc.take_screenshot(screenshotfile)
|
87
|
+
end
|
88
|
+
verify_screenshot(screenshotfile)
|
89
|
+
ensure
|
90
|
+
screenshotfile.close
|
91
|
+
File.delete(screenshotfile)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
metadata
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: evilcap-ruby-vnc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Charles Lowe
|
8
|
+
- Egor Topolnyak
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2019-07-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: vncrec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 1.0.6
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 1.0.6
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: chunky_png
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 1.3.0
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 1.3.0
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rake
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '12.3'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '12.3'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rspec
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '3.7'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '3.7'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: simplecov
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0.16'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0.16'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: image_size
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '2.0'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '2.0'
|
98
|
+
description: A library which implements the client VNC protocol to control VNC servers.
|
99
|
+
email: topolnyak012@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files:
|
103
|
+
- README.rdoc
|
104
|
+
- Changelog.rdoc
|
105
|
+
files:
|
106
|
+
- COPYING
|
107
|
+
- Changelog.rdoc
|
108
|
+
- README.rdoc
|
109
|
+
- Rakefile
|
110
|
+
- data/keys.yaml
|
111
|
+
- lib/cipher/vncdes.rb
|
112
|
+
- lib/net/rfb/frame_buffer.rb
|
113
|
+
- lib/net/vnc.rb
|
114
|
+
- lib/net/vnc/version.rb
|
115
|
+
- spec/cipher_vncdes_spec.rb
|
116
|
+
- spec/net_vnc_spec.rb
|
117
|
+
- spec/real_net_vnc_spec.rb
|
118
|
+
homepage: https://github.com/evilcap/ruby-vnc
|
119
|
+
licenses:
|
120
|
+
- MIT
|
121
|
+
metadata: {}
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options:
|
124
|
+
- "--main"
|
125
|
+
- README.rdoc
|
126
|
+
- "--title"
|
127
|
+
- evilcap-ruby-vnc documentation
|
128
|
+
- "--tab-width"
|
129
|
+
- '2'
|
130
|
+
require_paths:
|
131
|
+
- lib
|
132
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
requirements: []
|
143
|
+
rubyforge_project: evilcap-ruby-vnc
|
144
|
+
rubygems_version: 2.7.6
|
145
|
+
signing_key:
|
146
|
+
specification_version: 4
|
147
|
+
summary: Evilcap Ruby VNC library.
|
148
|
+
test_files: []
|