class Net::VNC
The VNC
class provides for simple rfb-protocol based control of a VNC
server. This can be used, eg, to automate applications.
Sample usage:
# launch xclock on localhost. note that there is an xterm in the top-left require 'net/vnc' Net::VNC.open 'localhost:0', :shared => true, :password => 'mypass' do |vnc| vnc.pointer_move 10, 10 vnc.type 'xclock' vnc.key_press :return end
TODO¶ ↑
-
The server read loop seems a bit iffy. Not sure how best to do it.
-
Should probably be changed to be more of a lower-level protocol wrapping thing, with the actual VNCClient sitting on top of that. all it should do is read/write the packets over the socket.
Constants
- BASE_PORT
- BUTTON_MAP
- CHALLENGE_SIZE
- DEFAULT_OPTIONS
- KEY_MAP
- VERSION
Attributes
desktop_name[R]
display[R]
options[R]
pointer[R]
server[R]
socket[R]
Public Class Methods
new(display=':0', options={})
click to toggle source
# File lib/net/vnc.rb, line 77 def initialize display=':0', options={} @server = 'localhost' if display =~ /^(.*)(:\d+)$/ @server, display = $1, $2 end @display = display[1..-1].to_i @desktop_name = nil @options = DEFAULT_OPTIONS.merge options @clipboard = nil @fb = nil @pointer = PointerState.new self @mutex = Mutex.new connect @packet_reading_state = nil @packet_reading_thread = Thread.new { packet_reading_thread } end
open(display=':0', options={}) { |vnc| ... }
click to toggle source
# File lib/net/vnc.rb, line 94 def self.open display=':0', options={} vnc = new display, options if block_given? begin yield vnc ensure vnc.close end else vnc end end
Public Instance Methods
clipboard() { || ... }
click to toggle source
# File lib/net/vnc.rb, line 299 def clipboard if block_given? @clipboard = nil yield 60.times do clipboard = @mutex.synchronize { @clipboard } return clipboard if clipboard sleep 0.5 end warn 'clipboard still empty after 30s' nil else @mutex.synchronize { @clipboard } end end
clipboard=(text)
click to toggle source
# File lib/net/vnc.rb, line 315 def clipboard= text text = text.to_s.gsub(/\R/, "\n") # eol of ClientCutText's text is LF byte_size = text.to_s.bytes.size packet = 0.chr * (8 + byte_size) packet[0] = 6.chr # message-type: 6 (ClientCutText) packet[4, 4] = [byte_size].pack('N') # length packet[8, byte_size] = text socket.write(packet) @clipboard = text end
close()
click to toggle source
# File lib/net/vnc.rb, line 275 def close # destroy packet reading thread if @packet_reading_state == :loop @packet_reading_state = :stop while @packet_reading_state # do nothing end end socket.close end
connect()
click to toggle source
# File lib/net/vnc.rb, line 111 def connect @socket = TCPSocket.open(server, port) unless socket.read(12) =~ /^RFB (\d{3}.\d{3})\n$/ raise 'invalid server response' end @server_version = $1 socket.write "RFB 003.003\n" data = socket.read(4) auth = data.to_s.unpack('N')[0] case auth when 0, nil raise 'connection failed' when 1 # ok... when 2 password = @options[:password] or raise 'Need to authenticate but no password given' challenge = socket.read CHALLENGE_SIZE response = Cipher::VNCDES.new(password).encrypt(challenge) socket.write response ok = socket.read(4).to_s.unpack('N')[0] raise 'Unable to authenticate - %p' % ok unless ok == 0 else raise 'Unknown authentication scheme - %d' % auth end # ClientInitialisation socket.write((options[:shared] ? 1 : 0).chr) # ServerInitialisation @framebuffer_width = socket.read(2).to_s.unpack('n')[0].to_i @framebuffer_height = socket.read(2).to_s.unpack('n')[0].to_i # TODO: parse this. pixel_format = socket.read(16) # read the name in byte chunks of 20 name_length = socket.read(4).to_s.unpack('N')[0] @desktop_name = [].tap do |it| while name_length > 0 len = [20, name_length].min it << socket.read(len) name_length -= len end end.join _load_frame_buffer end
key_down(which, options={})
click to toggle source
# File lib/net/vnc.rb, line 209 def key_down which, options={} packet = 0.chr * 8 packet[0] = 4.chr key_code = get_key_code which packet[4, 4] = [key_code].pack('N') packet[1] = 1.chr socket.write packet wait options end
key_press(*args) { || ... }
click to toggle source
this takes an array of keys, and successively holds each down then lifts them up in reverse order. FIXME: should wait. can't recurse in that case.
# File lib/net/vnc.rb, line 176 def key_press(*args) options = Hash === args.last ? args.pop : {} keys = args raise ArgumentError, 'Must have at least one key argument' if keys.empty? begin key_down keys.first if keys.length == 1 yield if block_given? else key_press(*(keys[1..-1] + [options])) end ensure key_up keys.first end end
key_up(which, options={})
click to toggle source
# File lib/net/vnc.rb, line 219 def key_up which, options={} packet = 0.chr * 8 packet[0] = 4.chr key_code = get_key_code which packet[4, 4] = [key_code].pack('N') packet[1] = 0.chr socket.write packet wait options end
pointer_move(x, y, options={})
click to toggle source
# File lib/net/vnc.rb, line 229 def pointer_move x, y, options={} # options[:relative] pointer.update x, y wait options end
port()
click to toggle source
# File lib/net/vnc.rb, line 107 def port BASE_PORT + @display end
reconnect()
click to toggle source
# File lib/net/vnc.rb, line 286 def reconnect 60.times do if @packet_reading_state.nil? connect @packet_reading_thread = Thread.new { packet_reading_thread } return true end sleep 0.5 end warn 'reconnect failed because packet reading state had not been stopped for 30 seconds.' false end
take_screenshot(dest=nil)
click to toggle source
take screenshot as PNG image @param dest [String|IO|nil] destination file path, or IO-object, or nil @return [String] PNG binary data as string when dest is null
[true] else case
# File lib/net/vnc.rb, line 266 def take_screenshot(dest=nil) fb = _load_frame_buffer # on-demand loading fb.save_pixel_data_as_png dest end
type(text, options={})
click to toggle source
this types text
on the server
# File lib/net/vnc.rb, line 160 def type text, options={} packet = 0.chr * 8 packet[0] = 4.chr text.split(//).each do |char| packet[7] = char[0] packet[1] = 1.chr socket.write packet packet[1] = 0.chr socket.write packet end wait options end
wait(options={})
click to toggle source
# File lib/net/vnc.rb, line 271 def wait options={} sleep options[:wait] || @options[:wait] end
Private Instance Methods
_load_frame_buffer()
click to toggle source
# File lib/net/vnc.rb, line 361 def _load_frame_buffer unless @fb require 'net/rfb/frame_buffer' @fb = Net::RFB::FrameBuffer.new @socket, @framebuffer_width, @framebuffer_height, @options[:pix_fmt], @options[:encoding] @fb.send_initial_data end @fb end
get_key_code(which)
click to toggle source
# File lib/net/vnc.rb, line 192 def get_key_code(which) case which when String if which.length != 1 raise ArgumentError, 'can only get key_code of single character strings' end which[0].ord when Symbol KEY_MAP[which] when Integer which else raise ArgumentError, "unsupported key value: #{which.inspect}" end end
packet_reading_thread()
click to toggle source
# File lib/net/vnc.rb, line 345 def packet_reading_thread @packet_reading_state = :loop loop do begin break if @packet_reading_state != :loop next unless IO.select [socket], nil, nil, 2 type = socket.read(1)[0] read_packet type.ord rescue warn "exception in packet_reading_thread: #{$!.class}:#{$!}\n#{$!.backtrace}" break end end @packet_reading_state = nil end
read_packet(type)
click to toggle source
# File lib/net/vnc.rb, line 328 def read_packet type case type when 0 # ----------------------------------------------- FramebufferUpdate @fb.handle_response type if @fb when 1 # --------------------------------------------- SetColourMapEntries @fb.handle_response type if @fb when 2 # ------------------------------------------------------------ Bell nil # not support when 3 # --------------------------------------------------- ServerCutText socket.read 3 # discard padding bytes len = socket.read(4).unpack('N')[0] @mutex.synchronize { @clipboard = socket.read len } else warn 'unhandled server packet type - %d' % type end end