ruby-vnc 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9b9252091b18c38896b7f1e8ee618dc77c1848c471c6f4eec36c9a2831b70640
4
+ data.tar.gz: d8599868187fa9f97649b68ded844306534609a9a9606bf022a629ab2174df1d
5
+ SHA512:
6
+ metadata.gz: e671beb2f0319145b5c1f930c02245026adef5ef81accaf2530b42c25e5cca5bc996c4c67445d3c308a8b012c579bd49f4899633f0e42093921d4396aa594c54
7
+ data.tar.gz: eae517b3eb5295687fad89289ca0c3ca9067d12e3264c91057a9430ebc782d1a6f1b72ae8fdf94adac5e94e2f4abc786c14a4ce3483d2f2e08cb84e0207a95b8
@@ -1,3 +1,12 @@
1
+ == Unreleased:
2
+
3
+ == 1.2.0 / 2021-09-21
4
+
5
+ - Replaced DES-algorithm with Ruby's built-in OpenSSL wrapper instead
6
+ - Parse framebuffer width/height and hostname from ServerInitialisation
7
+ - Added a project Gemfile
8
+ - Add a required ruby version (higher than Ruby 2.5)
9
+
1
10
  == 1.1.0 / 2012-06-03
2
11
 
3
12
  - Fixes to support ruby 1.9 (jedi4ever & codemonkeyjohn).
@@ -9,6 +9,18 @@ servers on any platform.
9
9
 
10
10
  The primary documentation is for the Net::VNC class.
11
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
+
12
24
  = Thanks
13
25
 
14
26
  Code borrows a lot from Tim Waugh's excellent rfbplaymacro. So far all it
data/Rakefile CHANGED
@@ -1,53 +1,38 @@
1
+ require 'bundler/gem_tasks'
1
2
  require 'rake'
2
- require 'rake/rdoctask'
3
- require 'rake/gempackagetask'
4
- require 'spec/rake/spectask'
5
- require 'spec/rake/verify_rcov'
6
3
 
7
- spec = eval File.read('ruby-vnc.gemspec')
8
-
9
- task :default => :spec
4
+ task default: :spec
10
5
 
11
6
  desc 'Run all specs'
12
- Spec::Rake::SpecTask.new :spec do |t|
13
- t.spec_opts = ['--format specdoc --colour']
7
+ begin
8
+ require 'rspec/core/rake_task'
9
+ RSpec::Core::RakeTask.new(:spec)
10
+ rescue LoadError
14
11
  end
15
12
 
16
13
  desc 'Run all specs and generate html spec document'
17
14
  namespace :spec do
18
- Spec::Rake::SpecTask.new :html do |t|
19
- t.spec_opts = ['--format html:spec.html']
20
- end
21
- end
22
-
23
- desc 'Run all specs and generate coverage'
24
- Spec::Rake::SpecTask.new :rcov do |t|
25
- t.rcov = true
26
- t.rcov_opts = ['--exclude', 'spec']
27
- t.rcov_opts << '--xrefs'
28
- t.rcov_opts << '--text-report'
15
+ RSpec::Core::RakeTask.new :html do |t|
16
+ t.rspec_opts = ['--format html --out spec.html']
17
+ end
29
18
  end
30
19
 
31
- namespace :rcov do
32
- RCov::VerifyTask.new :verify => :rcov do |t|
33
- t.threshold = 100.0
34
- t.index_html = 'coverage/index.html'
35
- end
36
- end
20
+ require 'rdoc/task'
37
21
 
38
22
  Rake::RDocTask.new do |t|
39
- t.rdoc_dir = 'doc'
40
- t.rdoc_files.include 'lib/**/*.rb'
41
- t.rdoc_files.include 'README'
42
- t.title = "#{PKG_NAME} documentation"
43
- t.options += %w[--line-numbers --inline-source --tab-width 2]
44
- t.main = 'README'
23
+ t.rdoc_dir = 'doc'
24
+ t.rdoc_files.include 'lib/**/*.rb'
25
+ t.rdoc_files.include 'README'
26
+ t.title = 'ruby-vnc documentation'
27
+ t.options += %w[--line-numbers --inline-source --tab-width 2]
28
+ t.main = 'README'
45
29
  end
46
30
 
47
- Rake::GemPackageTask.new(spec) do |t|
48
- t.gem_spec = spec
49
- t.need_tar = false
50
- t.need_zip = false
51
- t.package_dir = 'build'
52
- end
31
+ require 'rubygems/package_task'
53
32
 
33
+ spec = eval File.read('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
@@ -0,0 +1,54 @@
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 = 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
@@ -0,0 +1,175 @@
1
+ require 'vncrec'
2
+ require 'chunky_png'
3
+
4
+ module Net::RFB
5
+ # Manage FrameBuffer pixel data for RFB protocol
6
+ # This is a little wrapper for the `Proxy` class in vncrec-ruby https://github.com/d-theus/vncrec-ruby
7
+ class FrameBuffer
8
+ class VNCRecAuthStub
9
+ def initialize(io, *options); end
10
+ end
11
+
12
+ # @param io [IO, #read, #sysread, #syswrite, #read_nonblock] string stream from VNC server.
13
+ # @param w [Integer] width of the screen area
14
+ # @param h [Integer] height of the screen area
15
+ # @param bpp [Symbol] bits per pixel (BGR8 or BGRA)
16
+ # @param encodings [Array<Symbol>] encoding (RAW or HEXTILE or ZRLE) default: RAW
17
+ def initialize(io, w, h, bpp, encodings = nil)
18
+ @cb_mutex = Monitor.new
19
+ @cb_cv = @cb_mutex.new_cond
20
+
21
+ @encodings = encodings
22
+
23
+ @vnc_rec_pix_fmt = convert_to_vnc_rec_pix_fmt bpp
24
+
25
+ @proxy = VNCRec::RFB::Proxy.new(io, nil, nil, nil, [VNCRecAuthStub, nil])
26
+ @proxy.prepare_framebuffer w, h, @vnc_rec_pix_fmt[:bpp]
27
+ end
28
+
29
+ def send_initial_data
30
+ # set encoding
31
+ raise 'Error while setting encoding' unless set_encodings @encodings
32
+
33
+ # set pixel format
34
+ set_pixel_format @vnc_rec_pix_fmt
35
+
36
+ # request all pixel data
37
+ request_update_fb incremental: false
38
+ end
39
+
40
+ # raw pixel data of screen
41
+ def pixel_data
42
+ @proxy.data
43
+ end
44
+
45
+ # 32bit RGBA pixel data of screen
46
+ def rgba_pixel_data
47
+ px = pixel_data
48
+ raise 'Error in get raw pixel_data.' unless px
49
+
50
+ self.class.convert_raw_pixel_data_to_rgba px, @vnc_rec_pix_fmt[:string]
51
+ end
52
+
53
+ # convert raw pixel data to 32bit RGBA values according to VNC pixel format
54
+ # @param px [String] binary pixel data
55
+ # @param pix_fmt [String] pixel format (bgra, bgr8)
56
+ # @return [Array<Integer>] array of 32bit pixel data
57
+ def self.convert_raw_pixel_data_to_rgba(px, pix_fmt)
58
+ # see https://github.com/d-theus/vncrec-ruby/blob/master/lib/vncrec/constants.rb
59
+ case pix_fmt.to_s
60
+ when 'bgra'
61
+ # convert 32bit BGRA -> 32bit RGBA
62
+ px = px.unpack('V*')
63
+ px.map! { |p| (p << 8) | 0xff }
64
+ when 'bgr8'
65
+ # convert 8bit BGR -> 32bit RGBA
66
+ px = px.unpack('C*')
67
+ px.map! do |p|
68
+ r = (p & 0b00000111)
69
+ g = (p & 0b00111000) >> 3
70
+ b = (p & 0b11000000) >> 6
71
+ ((r * 36) << 24) | ((g * 36) << 16) | ((b * 85) << 8) | 0xff
72
+ end
73
+ else
74
+ raise "unknown pixel format #{pix_fmt.inspect}"
75
+ end
76
+ end
77
+
78
+ # Set a way that server should use to represent pixel data
79
+ # @param [Symbol|String] pixel format:
80
+ # * :BGR8
81
+ # * :BGRA
82
+ def set_pixel_format(format)
83
+ @proxy.set_pixel_format convert_to_vnc_rec_pix_fmt(format)
84
+ end
85
+
86
+ # Set way of encoding video frames.
87
+ # @param encodings [Symbol|String] list of encoding of video data used to transfer.
88
+ # * :RAW
89
+ # * :HEXTILE
90
+ # * :ZRLE
91
+ def set_encodings(*encodings)
92
+ @proxy.set_encodings [encodings].flatten.compact.map { |sym| VNCRec.const_get "ENC_#{sym}" }
93
+ end
94
+
95
+ # Send request for update framebuffer.
96
+ # if block given, called it with pixel data after the response received.
97
+ # @param [Boolean] incremental incremental, request just difference
98
+ # between previous and current framebuffer state.
99
+ # @param x [Integer]
100
+ # @param y [Integer]
101
+ # @param w [Integer]
102
+ # @param h [Integer]
103
+ # @param wait_for_response [Boolean] if true, wait for a FramebufferUpdate response
104
+ def request_update_fb(incremental: true, x: nil, y: nil, w: nil, h: nil, wait_for_response: false)
105
+ @cb_mutex.synchronize do
106
+ @proxy.fb_update_request incremental ? 1 : 0, x || 0, y || 0, w || @proxy.w, h || @proxy.h
107
+
108
+ @cb_cv.wait if wait_for_response
109
+ end
110
+ end
111
+
112
+ def handle_response(t)
113
+ case t
114
+ when 0 # ----------------------------------------------- FramebufferUpdate
115
+ ret = handle_fb_update
116
+ @cb_mutex.synchronize do
117
+ @cb_cv.broadcast
118
+ end
119
+ ret
120
+ when 1 # --------------------------------------------- SetColourMapEntries
121
+ handle_set_colormap_entries
122
+ end
123
+ end
124
+
125
+ # save current screen pixel data as PNG image
126
+ # @param dest [String|IO|nil] destination file path, or IO-object, or nil
127
+ # @return [String] PNG binary data as string when dest is null
128
+ # [true] else case
129
+ def save_pixel_data_as_png(dest = nil)
130
+ request_update_fb(wait_for_response: true)
131
+
132
+ image = ChunkyPNG::Image.new(@proxy.w, @proxy.h, rgba_pixel_data)
133
+
134
+ if dest.is_a? IO
135
+ # write to IO-object
136
+ image.write dest
137
+ elsif dest.is_a?(String) || dest.is_a?(Pathname)
138
+ # write to file
139
+ image.save dest.to_s
140
+ elsif dest.nil?
141
+ # return binary data as string
142
+ return image.to_blob
143
+ else
144
+ raise ArgumentError, "Unsupported destination type #{dest.inspect}"
145
+ end
146
+ true
147
+ end
148
+
149
+ private
150
+
151
+ # convert pixel_format symbol to VNCRec::PIX_FMT_XXX symbol.
152
+ # @param pix_fmt [Symbol|String] bits per pixel (BGR8 or BGRA)
153
+ def convert_to_vnc_rec_pix_fmt(pix_fmt)
154
+ return pix_fmt if pix_fmt.is_a?(Hash)
155
+
156
+ pf = pix_fmt.to_s.prepend('PIX_FMT_').upcase.to_sym
157
+ unless VNCRec.const_defined? pf
158
+ raise ArgumentError,
159
+ "Unsupported pixel_format '#{pix_fmt}', now supported values are: BGR8, BGRA"
160
+ end
161
+
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
@@ -1,6 +1,5 @@
1
1
  module Net
2
- class VNC
3
- VERSION = '1.1.0'
4
- end
2
+ class VNC
3
+ VERSION = '1.2.0'.freeze
4
+ end
5
5
  end
6
-