evilcap-ruby-vnc 1.2.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.
- 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: []
|