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
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

Constants

BASE_PORT
BUTTON_MAP
CHALLENGE_SIZE
DEFAULT_OPTIONS
KEY_MAP
VERSION

Attributes

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 73
def initialize display=':0', options={}
  @server = 'localhost'
  if display =~ /^(.*)(:\d+)$/
    @server, display = $1, $2
  end
  @display = display[1..-1].to_i
  @options = DEFAULT_OPTIONS.merge options
  @clipboard = 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 88
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

button_down(which=:left, options={}) click to toggle source
# File lib/net/vnc.rb, line 231
def button_down which=:left, options={}
  button = BUTTON_MAP[which] || which
  raise ArgumentError, 'Invalid button - %p' % which unless (0..2) === button
  pointer.button |= 1 << button
  wait options
end
button_press(button=:left, options={}) { || ... } click to toggle source
# File lib/net/vnc.rb, line 222
def button_press button=:left, options={}
  begin
    button_down button, options
    yield if block_given?
  ensure
    button_up button, options
  end
end
button_up(which=:left, options={}) click to toggle source
# File lib/net/vnc.rb, line 238
def button_up which=:left, options={}
  button = BUTTON_MAP[which] || which
  raise ArgumentError, 'Invalid button - %p' % which unless (0..2) === button
  pointer.button &= ~(1 << button)
  wait options
end
clipboard() { || ... } click to toggle source
# File lib/net/vnc.rb, line 260
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
close() click to toggle source
# File lib/net/vnc.rb, line 249
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 105
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::DES.encrypt password, 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
  # TODO: parse this.
  socket.read(20)
  data = socket.read(4)
  # read this many bytes in chunks of 20
  size = data.to_s.unpack('N')[0]
  while size > 0
    len = [20, size].min
    # this is the hostname, and other stuff i think...
    socket.read(len)
    size -= len
  end
end
key_down(which, options={}) click to toggle source
# File lib/net/vnc.rb, line 192
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 164
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 202
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 212
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 101
def port
  BASE_PORT + @display
end
type(text, options={}) click to toggle source

this types text on the server

# File lib/net/vnc.rb, line 148
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 245
def wait options={}
  sleep options[:wait] || @options[:wait]
end